Tengo el modelo Foo que tiene barra de campo. El campo de la barra debe ser único, pero permitir nulos en él, lo que significa que quiero permitir más de un registro si el campo de la barra es null
, pero si no lo es, null
los valores deben ser únicos.
Aquí está mi modelo:
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
Y aquí está el SQL correspondiente para la tabla:
CREATE TABLE appl_foo
(
id serial NOT NULL,
"name" character varying(40) NOT NULL,
bar character varying(40),
CONSTRAINT appl_foo_pkey PRIMARY KEY (id),
CONSTRAINT appl_foo_bar_key UNIQUE (bar)
)
Cuando uso la interfaz de administrador para crear más de 1 objetos foo donde la barra es nula, me da un error: "Foo con esta barra ya existe".
Sin embargo, cuando inserto en la base de datos (PostgreSQL):
insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)
Esto funciona, está bien, me permite insertar más de 1 registro con la barra nula, por lo que la base de datos me permite hacer lo que quiero, es simplemente algo mal con el modelo Django. ¿Algunas ideas?
EDITAR
La portabilidad de la solución en lo que respecta a DB no es un problema, estamos contentos con Postgres. Intenté establecer un valor único para un invocable, que era mi función que devolvía Verdadero / Falso para valores específicos de barra , no daba ningún error, sin embargo, parecía que no tenía ningún efecto.
Hasta ahora, eliminé el especificador único de la propiedad de la barra y manejé la singularidad de la barra en la aplicación, sin embargo, todavía busco una solución más elegante. ¿Alguna recomendación?
fuente
def get_db_prep_value(self, value, connection, prepared=False)
como método de llamada. Visite groups.google.com/d/msg/django-users/Z_AXgg2GCqs/zKEsfu33OZMJ para obtener más información. El siguiente método también funciona para mí: def get_prep_value (self, value): if value == "": #si Django intenta guardar la cadena '', envíe el db None (NULL) return Ninguno más: valor de retorno #otherwise, solo pasar el valorRespuestas:
Django no ha considerado que NULL sea igual a NULL para fines de comprobaciones de unicidad ya que el ticket # 9039 fue corregido, vea:
http://code.djangoproject.com/ticket/9039
El problema aquí es que el valor "en blanco" normalizado para un formulario CharField es una cadena vacía, no None. Entonces, si deja el campo en blanco, obtendrá una cadena vacía, no NULL, almacenada en la base de datos. Las cadenas vacías son iguales a las cadenas vacías para las comprobaciones de unicidad, tanto en Django como en las reglas de la base de datos.
Puede forzar a la interfaz de administración a almacenar NULL para una cadena vacía proporcionando su propio formulario de modelo personalizado para Foo con un método clean_bar que convierte la cadena vacía en Ninguno:
fuente
fields
oexclude
enModelForm
instancias. PuedeMeta
solucionar esto omitiendo la clase interna de ModelForm para usarla en admin. Referencia: docs.djangoproject.com/en/1.10/ref/contrib/admin/…** editar 30/11/2015 : en python 3, la
__metaclass__
variable global del módulo ya no es compatible . Además, a partir deDjango 1.10
laSubfieldBase
clase quedó en desuso :Por lo tanto, como lo sugiere la
from_db_value()
documentación y este ejemplo , esta solución debe cambiarse a:Creo que una mejor manera de anular el clean_data en el administrador sería subclasificar el charfield; de esta manera, no importa de qué forma acceda al campo, "simplemente funcionará". Puede capturar
''
justo antes de que se envíe a la base de datos, y capturar el NULL justo después de que salga de la base de datos, y el resto de Django no lo sabrá / no le importará. Un ejemplo rápido y sucio:Para mi proyecto, volqué esto en un
extras.py
archivo que vive en la raíz de mi sitio, luego puedo simplementefrom mysite.extras import CharNullField
en elmodels.py
archivo de mi aplicación . El campo actúa como un CharField: solo recuerde configurarloblank=True, null=True
al declarar el campo, o de lo contrario Django arrojará un error de validación (campo requerido) o creará una columna db que no acepta NULL.fuente
CharField
para que sea aCharNullField
, deberá hacerlo en tres pasos. Primero, agreguenull=True
al campo y migre eso. Luego, realice una migración de datos para actualizar los valores en blanco para que sean nulos. Finalmente, convierta el campo a CharNullField. Si convierte el campo antes de realizar la migración de datos, su migración de datos no hará nada.from_db_value()
no debe tener esecontex
parámetro adicional . Debería serdef from_db_value(self, value, expression, connection):
Debido a que soy nuevo en stackoverflow, todavía no puedo responder a las respuestas, pero me gustaría señalar que desde un punto de vista filosófico, no puedo estar de acuerdo con la respuesta más popular a esta pregunta. (por Karen Tracey)
El OP requiere que su campo de barra sea único si tiene un valor, y nulo de lo contrario. Entonces debe ser que el modelo mismo se asegura de que este sea el caso. No se puede dejar que el código externo verifique esto, porque eso significaría que se puede omitir. (O puede olvidarse de verificarlo si escribe una nueva vista en el futuro)
Por lo tanto, para mantener su código verdaderamente OOP, debe usar un método interno de su modelo Foo. Modificar el método save () o el campo son buenas opciones, pero usar un formulario para hacerlo ciertamente no lo es.
Personalmente prefiero usar el CharNullField sugerido, para la portabilidad a los modelos que pueda definir en el futuro.
fuente
La solución rápida es hacer:
fuente
MyModel.objects.bulk_create()
evitaría este método.Otra posible solución
fuente
Esto se soluciona ahora que https://code.djangoproject.com/ticket/4136 está resuelto. En Django 1.11+ puede usarlo
models.CharField(unique=True, null=True, blank=True)
sin tener que convertir manualmente valores en blancoNone
.fuente
Recientemente tuve el mismo requisito. En lugar de subclasificar diferentes campos, elegí anular el método save () en mi modelo (llamado 'MyModel' a continuación) de la siguiente manera:
fuente
Si tiene un modelo MyModel y desea que my_field sea Nulo o único, puede anular el método de guardado del modelo:
De esta manera, el campo no puede estar en blanco solo será no vacío o nulo. los nulos no contradicen la unicidad
fuente
Puede agregar
UniqueConstraint
con la condición denullable_field=null
y no incluir este campo en lafields
lista. Si también necesita una restricción cuyonullable_field
valor no esnull
, puede agregar uno adicional.Nota: UniqueConstraint se agregó desde django 2.2
fuente
Para bien o para mal, Django considera
NULL
que es equivalente a losNULL
fines de las comprobaciones de unicidad. Realmente no hay otra forma de escribir su propia implementación de la verificación de unicidad que se consideraNULL
única, sin importar cuántas veces ocurra en una tabla.(y tenga en cuenta que algunas soluciones de DB tienen la misma visión
NULL
, por lo que el código que se basa en las ideas de un DBNULL
puede no ser portátil para otros)fuente