Я просто написал поле и подклассы виджетов, которые решают эту конкретную проблему и могут использоваться с автозавершением JS, например, - и могут использоваться повторно. Тем не менее, для этого требуется больше работы, чем ваше решение, и я не уверен, хотите ли вы использовать мой или нет. В любом случае - я надеюсь, что я получу несколько upvotes - я провел довольно много времени и усилий написания этого ...
Вместо определения вашего ModelForm, как вы делали и баловаться с clean_
я предлагаю что-то вроде этого:
class SongForm(forms.ModelForm):
artist = CustomModelChoiceField(queryset = Artist.objects.all(), query_field = "name")
class Meta:
model = Song
Теперь CustomModelChoiceField (я не могу придумать лучшего имени для класса) является подклассом ModelChoiceField, что хорошо, потому что мы можем использовать аргумент queryset
для сужения приемлемых вариантов. Если аргумент widget
отсутствует, как и выше, используется значение по умолчанию для этого поля (подробнее об этом позже). query_field
не является обязательным и по умолчанию "pk"
. Итак, вот код поля:
class CustomModelChoiceField(forms.ModelChoiceField):
def __init__(self, queryset, query_field = "pk", **kwargs):
if "widget" not in kwargs:
kwargs["widget"] = ModelTextInput(model_class = queryset.model, query_field = query_field)
super(CustomModelChoiceField, self).__init__(queryset, **kwargs)
def to_python(self, value):
try:
int(value)
except:
from django.core.exceptions import ValidationError
raise ValidationError(self.error_messages['invalid_choice'])
return super(CustomModelChoiceField, self).to_python(value)
Что тело __init__
средств является то, что установка widget = None
при создании CustomModelChoiceField
дает нам равнину ModelChoiceField
(что было очень полезно при отладке ...). Теперь, фактическая работа выполняется в ModelTextInput
виджета:
class ModelTextInput(forms.TextInput):
def __init__(self, model_class, query_field, attrs = None ):
self.model_class = model_class
self.query_field = query_field
super(ModelTextInput, self).__init__(attrs)
def render(self, name, value, attrs = None):
try:
obj = self.model_class.objects.get(pk = value)
value = getattr(obj, self.query_field)
except:
pass
return super(ModelTextInput, self).render(name, value, attrs)
def value_from_datadict(self, data, files, name):
try:
return self.model_class.objects.get(**{ self.query_field : data[name] }).id
except:
return data[name]
Это существенно TextInput, что известно о двух дополнительных вещей - какой атрибут из которых модель представляет. (model_class
следует заменить на queryset
для сужения возможных вариантов работы, я исправлю это позже). Глядя на реализацию value_from_datadict
, легко заметить, почему to_python
в поле пришлось переопределить - он ожидает значение int
, но не проверяет, правда ли оно, и просто передает значение связанной модели, которая не выполняется с уродливым исключением.
Я тестировал это некоторое время, и он работает - вы можете указать разные поля модели, по которым поле формы попытается найти ваш artist
, обработка ошибок формы выполняется автоматически базовыми классами, и вам не нужно писать пользовательские clean_
каждый раз, когда вы хотите использовать аналогичные функции.
Я слишком устал прямо сейчас, но я попытаюсь отредактировать этот пост (и код) завтра.
еще не пробовал, но выглядит очень интересно – juanefren