Django: «unique_together» и «blank = True» - PullRequest
22 голосов
/ 24 апреля 2011

У меня есть модель Django, которая выглядит следующим образом:

class MyModel(models.Model):
    parent = models.ForeignKey(ParentModel)
    name   = models.CharField(blank=True, max_length=200)
    ... other fields ...

    class Meta:
        unique_together = ("name", "parent")

Это работает, как и ожидалось;Если один и тот же name больше одного раза в одном и том же parent, я получаю сообщение об ошибке: «MyModel с этим именем и родителем уже существует».

Однако, я также получаю сообщение об ошибке, когда я сохраняю более одного MyModel с тем же parent, но с пустым полем name, но это должно быть разрешено.Поэтому я не хочу получать вышеуказанную ошибку, когда поле name пустое.Это возможно как-то?

Ответы [ 4 ]

16 голосов
/ 25 апреля 2011

Во-первых, пустое значение (пустая строка) НЕ совпадает с нулевым ('' != None).

Во-вторых, Django CharField при использовании через формы будет хранить пустую строку при выходе из поляпусто.

Так что, если ваше поле было чем-то иным, чем CharField, вы должны просто добавить null=True к нему. Но в этом случае вам нужно сделать больше, чем это.Вам нужно создать подкласс forms.CharField и переопределить его метод clean, чтобы он возвращал None в пустой строке, что-то вроде этого:

class NullCharField(forms.CharField):
    def clean(self, value):
        value = super(NullCharField, self).clean(value)
        if value in forms.fields.EMPTY_VALUES:
            return None
        return value

, а затем использовать его в форме для вашей ModelForm:

class MyModelForm(forms.ModelForm):
    name = NullCharField(required=False, ...)

таким образом, если вы оставите это поле пустым, в базе данных будет храниться ноль вместо пустой строки ('')

11 голосов
/ 24 апреля 2011

Используя unique_together, вы говорите Django, что вам не нужны два экземпляра MyModel с одинаковыми атрибутами parent и name - что применимо, даже если name - пустая строка.

Это применяется на уровне базы данных с использованием атрибута unique в соответствующих столбцах базы данных.Поэтому, чтобы сделать какие-либо исключения из этого поведения, вам следует избегать использования unique_together в вашей модели.

Вместо этого вы можете получить то, что хотите, переопределив метод save в модели и применивуникальная сдержанность там.Когда вы пытаетесь сохранить экземпляр вашей модели, ваш код может проверить, существуют ли какие-либо существующие экземпляры, которые имеют одинаковую комбинацию parent и name, и отказаться от сохранения экземпляра, если таковые имеются.Но вы также можете разрешить сохранение экземпляра, если name - пустая строка.Базовая версия может выглядеть так:

class MyModel(models.Model):
    ...

    def save(self, *args, **kwargs):

        if self.name != '':
            conflicting_instance = MyModel.objects.filter(parent=self.parent, \
                                                          name=self.name)
            if self.id:
                # This instance has already been saved. So we need to filter out
                # this instance from our results.
                conflicting_instance = conflicting_instance.exclude(pk=self.id)

            if conflicting_instance.exists():
                raise Exception('MyModel with this name and parent already exists.')

        super(MyModel, self).save(*args, **kwargs)

Надеюсь, это поможет.

2 голосов
/ 26 апреля 2011

Это решение очень похоже на решение, данное @bigmattyh, однако я обнаружил нижеприведенную страницу, которая описывает, где должна быть выполнена проверка:

http://docs.djangoproject.com/en/1.3/ref/models/instances/#validating-objects

Решение iв конечном итоге используется следующее:

from django    import forms

class MyModel(models.Model):
...

def clean(self):
    if self.name != '':
        instance_exists = MyModel.objects.filter(parent=self.parent,
                                                 name=self.name).exists()
        if instance_exists:
            raise forms.ValidationError('MyModel with this name and parent already exists.')

Обратите внимание, что вместо общего исключения возникает ошибка ValidationError.Преимущество этого решения заключается в том, что при проверке ModelForm с использованием .is_valid () автоматически вызывается описанный выше метод models .clean (), который сохраняет строку ValidationError в .errors, чтобы ее можно было отобразить в шаблоне html.

Дайте мне знать, если вы не согласны с этим решением.

0 голосов
/ 25 апреля 2011

bigmattyh дает хорошее объяснение того, что происходит.Я просто добавлю возможный метод save.

def save(self, *args, **kwargs):
    if self.parent != None and MyModels.objects.filter(parent=self.parent, name=self.name).exists():
        raise Exception('MyModel with this name and parent exists.')
    super(MyModel, self).save(*args, **kwargs)

Я думаю, я решил сделать нечто подобное, переопределив метод очистки моей модели, и он выглядел примерно так:

from django.core.exceptions import ValidationError
def clean(self):
    if self.parent != None and MyModels.objects.filter(parent=self.parent, name=self.name).exists():
        raise ValidationError('MyModel with this name and parent exists.')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...