Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: 'unique_together' and 'blank=True'

Tags:

I have a Django model which looks like this:

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

This works as expected; If there is the same name more than once in the same parent then I get an error: "MyModel with this Name and Parent already exists."

However, I also get an error when I save more than one MyModel with the same parent but with the name field blank, but this should be allowed. So basically I don't want to get the above error when the name field is blank. Is that possible somehow?

like image 728
WesDec Avatar asked Apr 24 '11 17:04

WesDec


People also ask

What does blank true in Django mean?

If a string-based field has null=True , that means it has two possible values for “no data”: NULL, and the empty string. In most cases, it's redundant to have two possible values for “no data;” the Django convention is to use the empty string, not NULL.

What is null true and blank true in Django?

null=True will set the field's value to NULL i.e., no data. It is basically for the databases column value. blank=True determines whether the field will be required in forms. This includes the admin and your own custom forms.

What is unique together in Django?

unique_together may be deprecated in the future. This is a list of lists that must be unique when considered together. It's used in the Django admin and is enforced at the database level (i.e., the appropriate UNIQUE statements are included in the CREATE TABLE statement).

Is null false Django?

The Django convention is to use the empty string, not NULL. The default values of null and blank are False. Also there is a special case, when you need to accept NULL values for a BooleanField , use NullBooleanField instead.


2 Answers

Firstly, blank (empty string) IS NOT same as null ('' != None).

Secondly, Django CharField when used through forms will be storing empty string when you leave field empty.

So if your field was something else than CharField you should just add null=True to it. But in this case you need to do more than that. You need to create subclass of forms.CharField and override it's clean method to return None on empty string, something like this:

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 

and then use it in form for your ModelForm:

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

this way if you leave it blank it will store null in database instead of empty string ('')

like image 109
rombarcz Avatar answered Sep 23 '22 04:09

rombarcz


Using unique_together, you're telling Django that you don't want any two MyModel instances with the same parent and name attributes -- which applies even when name is an empty string.

This is enforced at the database level using the unique attribute on the appropriate database columns. So to make any exceptions to this behavior, you'll have to avoid using unique_together in your model.

Instead, you can get what you want by overriding the save method on the model and enforcing the unique restraint there. When you try to save an instance of your model, your code can check to see if there are any existing instances that have the same parent and name combination, and refuse to save the instance if there are. But you can also allow the instance to be saved if the name is an empty string. A basic version of this might look like this:

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) 

Hope that helps.

like image 44
Matt Howell Avatar answered Sep 23 '22 04:09

Matt Howell