Как проверить/очистить() уникальное поле = True без использования ModelForm?

В пользовательской форме, как проверять уникальность поля модели (т.е. имеет unique=True)?

Я знаю, что django ModelForm автоматически выполняет функцию validate_unique(), которая вызывается в методе BaseModelForm clean(), поэтому при использовании ModelForm это будет обрабатываться правильно (как в Admin).

Тем не менее, я создаю свою собственную форму с нуля и задаюсь вопросом, как я могу сам справиться с этим? Я думаю, что мой самый большой камень преткновения - это знать, какой объект прикреплен к форме, когда данные очищаются...

Некоторые коды:

class UserProfile(CreatedModifiedModel):
    user            = models.ForeignKey(User, unique=True)
    display_name    = models.CharField('Display Name',max_length=30,
                        blank=True,unique=True)

class EditUserProfileForm(forms.Form):
    display_name    = forms.CharField(required=False,max_length=30)

    # "notifications" are created from a different model, not the UserProfile
    notifications    = forms.MultipleChoiceField(
                        label="Email Notifications",
                        required=False,
                        widget=forms.CheckboxSelectMultiple,)

    def clean_display_name(self):
        # how do I run my own validate_unique() on this form?
        # how do I know which UserProfile object I am working with?

    # more code follows, including the __init__ which sets up the notifications

Ответ 1

Уникальная проверка трудно получить полностью, поэтому я бы рекомендовал использовать ModelForm в любом случае:

class EditUserProfileForm(forms.ModelForm):
    # "notifications" are created from a different model, not the UserProfile
    notifications    = forms.MultipleChoiceField(
                        label="Email Notifications",
                        required=False,
                        widget=forms.CheckboxSelectMultiple,)

    class Meta:
        model = UserProfile
        fields = ('display_name',)

Создание формы из нескольких моделей непросто, но в этом случае вы можете просто добавить поле notifications в ModelForm и вытащить его из .cleaned_data, как обычно:

# view
if request.method == 'POST':
    form = EditUserProfileForm(request.POST, instance=user_profile)
    if form.is_valid():
        user_profile = form.save()
        notifications = form.cleaned_data['notifications']
        # Do something with notifications.

То, как я это сделаю, но если вы настроились на проверку уникальности самостоятельно, вы всегда можете сделать что-то вроде:

def clean_display_name(self):
    display_name = self.cleaned_data['display_name']
    if UserProfile.objects.filter(display_name=display_name).count() > 0:
        raise ValidationError('This display name is already in use.')
    return display_name

Есть две проблемы, которые я вижу здесь. Во-первых, вы можете столкнуться с проблемами concurrency, где два человека отправляют одно и то же имя, оба проходят уникальные проверки, но затем получают ошибку БД. Другая проблема заключается в том, что вы не можете редактировать профиль пользователя, потому что у вас нет идентификатора для исключения из поиска. Вы должны сохранить его в своем __init__, а затем использовать его при очистке:

def __init__(self, *args, **kwargs):
    ...
    if 'instance' in kwargs:
        self.id = kwargs['instance'].id
    ...

def clean_display_name(self):
    display_name = self.cleaned_data['display_name']
    qs = UserProfile.objects.filter(display_name=display_name)
    if self.id:
        qs = qs.exclude(pk=self.id)
    if qs.count() > 0:
        raise ValidationError('This display name is already in use.')
    return display_name

Но в этот момент вы просто дублируете логику в ModelForms.