Django: 'unique_together' и 'blank = True'
У меня есть модель 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
пустое. Возможно ли это как-то?
Ответы
Ответ 1
Во-первых, blank (пустая строка) НЕ ТОЛЬКО как null ('' != 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, ...)
таким образом, если вы оставите его пустым, он будет хранить null в базе данных вместо пустой строки (''
)
Ответ 2
Используя 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)
Надеюсь, что это поможет.
Ответ 3
Это решение очень похоже на то, которое задано @bigmattyh, однако я нашел нижеприведенную страницу, где описывается, где должна выполняться проверка:
http://docs.djangoproject.com/en/1.3/ref/models/instances/#validating-objects
Решение я в конечном итоге использует следующее:
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-шаблоне.
Сообщите мне, если вы не согласны с этим решением.
Ответ 4
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.')