¿Django único fallo de restricción juntos?

Utilizando Django 1.5.1. Python 2.7.3.

Quería hacer una restricción única conjunta con un campo de clave externa y un campo de slug. Así que en mi modelo meta, lo hice.

foreign_key = models.ForeignKey("self", null=True, default=None) slug = models.SlugField(max_length=40, unique=False) class Meta: unique_together = ("foreign_key", "slug") 

Incluso verifiqué la descripción de la tabla en Postgres (9.1) y la restricción se colocó en la tabla de la base de datos.

 -- something like "table_name_foreign_key_id_slug_key" UNIQUE CONSTRAINT, btree (foreign_key_id, slug) 

Sin embargo, todavía podría guardar en la tabla de la base de datos una clave_extracial de Ninguna / nula y cadenas duplicadas.

Por ejemplo,

Podría ingresar y guardar

 # model objects with slug="python" three times; all three foreign_key(s) # are None/null because that is their default value MO(slug="python").save() MO(slug="python").save() MO(slug="python").save() 

Entonces, después de usar unique_together, ¿por qué puedo ingresar tres filas del mismo valor?

Ahora mismo estoy adivinando que podría tener que ver con el valor predeterminado de Ninguno para el campo foreign_key, porque antes de unique_together, cuando tenía unique = True en slug, todo funcionaba bien. Entonces, si ese es el caso, ¿qué valor predeterminado debería tener que indique un valor nulo, pero que también mantenga la restricción única?

En Postgresql, NULL no es igual a ningún otro NULL . Por lo tanto, las filas que creas no son las mismas (desde la perspectiva de Postgres).

Actualizar

Tienes algunas maneras de lidiar con esto:

  • Prohibir el valor Null para la clave externa y usar algún valor predeterminado
  • Reemplace el método de save de su modelo para verificar que no existe tal fila
  • Cambiar el estándar de SQL 🙂

Agregue un método clean a su modelo, para que pueda editar una fila existente.

 def clean(self): queryset = MO.objects.exclude(id=self.id).filter(slug=self.slug) if self.foreign_key is None: if queryset.exists(): raise ValidationError("A row already exists with this slug and no key") else: if queryset.filter(foreign_key=self.foreign_key).exists(): raise ValidationError("This row already exists") 

Cuidado, el método de save predeterminado no llama a clean (o full_clean ).

NB: si coloca este código en el método de save , los formularios de actualización (como en el administrador) no funcionarán: tendrá un error de rastreo debido a la excepción ValidationError .

Solo cree manualmente el índice secundario en el campo de slug , pero solo para valores NULL en foreign_key_id :

 CREATE INDEX table_name_unique_null_foreign_key ON table_name (slug) WHERE foreign_key_id is NULL 

Tenga en cuenta que Django no admite esto, por lo que, sin la validación de formulario / modelo personalizada, obtendrá IntegrityError / 500 puro.

Posible duplicado de Crear restricción única con columnas nulas

Como mencionó hobbyte, “En Postgresql NULL no es igual a ningún otro NULL. Por lo tanto, las filas que creas no son las mismas (desde la perspectiva de Postgres)”.

Otra forma posible de abordar este desafío es agregar validación personalizada en el nivel de vista en el método form_valid.

En views.py:

 def form_valid(self, form): --OTHER VALIDATION AND FIELD VALUE ASSIGNMENT LOGIC-- if ModelForm.objects.filter(slug=slug,foreign_key=foreign_key: form.add_error('field', forms.ValidationError( _("Validation error message that shows up in your form. "), code='duplicate_row', )) return self.form_invalid(form)