queryset - filter django
Campos Ășnicos que permiten nulos en Django (7)
Tengo el modelo Foo que tiene barra de campo. El campo de barras debe ser único, pero debe tener nulos, lo que significa que quiero permitir más de un registro si el campo de barras es null
, pero si no 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 lo 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, muy bien, me permite insertar más de 1 registro con la barra como nula, por lo que la base de datos me permite hacer lo que quiero, es solo algo malo con el modelo de 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 único para un invocable, que era mi función que devolvía True / False para los valores específicos de la barra , no daba ningún error, sin embargo parecía que no tenía ningún efecto.
Hasta ahora, he eliminado el especificador exclusivo de la propiedad de la barra y manejo la singularidad de la barra en la aplicación, sin embargo, aún busco una solución más elegante. ¿Alguna recomendación?
** edit 30/11/2015 : en python 3, la variable module-global __metaclass__
ya no es compatible . Adicionalmente, a partir de Django 1.10
la clase SubfieldBase
quedó en deprecated :
de los deprecated :
django.db.models.fields.subclassing.SubfieldBase
ha quedado obsoleto y se eliminará en Django 1.10. Históricamente, se usaba para manejar campos en los que se necesitaba la conversión de tipo cuando se cargaba desde la base de datos, pero no se usaba en.values()
a los.values()
ni en forma agregada. Ha sido reemplazado porfrom_db_value()
. Tenga en cuenta que el nuevo enfoque no llama al métodoto_python()
en la asignación como en el caso deSubfieldBase
.
Por lo tanto, como lo sugiere la from_db_value() from_db_value()
y este example , esta solución se debe cambiar a:
class CharNullField(models.CharField):
"""
Subclass of the CharField that allows empty strings to be stored as NULL.
"""
description = "CharField that stores NULL but returns ''''."
def from_db_value(self, value, expression, connection, contex):
"""
Gets value right out of the db and changes it if its ``None``.
"""
if value is None:
return ''''
else:
return value
def to_python(self, value):
"""
Gets value right out of the db or an instance, and changes it if its ``None``.
"""
if isinstance(value, models.CharField):
# If an instance, just return the instance.
return value
if value is None:
# If db has NULL, convert it to ''''.
return ''''
# Otherwise, just return the value.
return value
def get_prep_value(self, value):
"""
Catches value right before sending to db.
"""
if value == '''':
# If Django tries to save an empty string, send the db None (NULL).
return None
else:
# Otherwise, just pass the value.
return value
Creo que una mejor manera de reemplazar el clean_data en el administrador sería subclasificar el charfield; de esta forma, sin importar qué forma tenga acceso al campo, simplemente "funcionará". Puede capturar el ''''
justo antes de enviarlo 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á ni le importará. Un ejemplo rápido y sucio:
from django.db import models
class CharNullField(models.CharField): # subclass the CharField
description = "CharField that stores NULL but returns ''''"
__metaclass__ = models.SubfieldBase # this ensures to_python will be called
def to_python(self, value):
# this is the value right out of the db, or an instance
# if an instance, just return the instance
if isinstance(value, models.CharField):
return value
if value is None: # if the db has a NULL (None in Python)
return '''' # convert it into an empty string
else:
return value # otherwise, just return the value
def get_prep_value(self, value): # catches value right before sending to db
if value == '''':
# if Django tries to save an empty string, send the db None (NULL)
return None
else:
# otherwise, just pass the value
return value
Para mi proyecto, extras.py
esto en un archivo extras.py
que vive en la raíz de mi sitio, luego puedo from mysite.extras import CharNullField
en el archivo models.py
mi aplicación. El campo actúa como un CharField; solo recuerda establecer en blank=True, null=True
al declarar el campo, o de lo contrario Django lanzará un error de validación (se requiere campo) o creará una columna de db que no acepte NULL.
Como soy nuevo en , todavía no puedo responder a las respuestas, pero me gustaría señalar que, desde un punto de vista filosófico, no estoy 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 en caso contrario. Entonces debe ser que el modelo en sí mismo se asegure de que este sea el caso. No se puede dejar que el código externo lo compruebe, porque eso significaría que se puede omitir. (O puede olvidarse de verificar 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 hacer esto definitivamente no lo es.
Personalmente prefiero utilizar el CharNullField sugerido, para la portabilidad de los modelos que podría definir en el futuro.
Django no ha considerado que NULL sea igual a NULL a los efectos de las verificaciones de singularidad desde que se corrigió el ticket # 9039, ver:
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 NULA, almacenada en el DB. Las cadenas vacías son iguales a las cadenas vacías para verificaciones de exclusividad, 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 al proporcionar su propio formulario de modelo personalizado para Foo con un método clean_bar que convierte la cadena vacía en None:
class FooForm(forms.ModelForm):
class Meta:
model = Foo
def clean_bar(self):
return self.cleaned_data[''bar''] or None
class FooAdmin(admin.ModelAdmin):
form = FooForm
La solución rápida es hacer:
def save(self, *args, **kwargs):
if not self.bar:
self.bar = None
super(Foo, self).save(*args, **kwargs)
Otra posible solución
class Foo(models.Model):
value = models.CharField(max_length=255, unique=True)
class Bar(models.Model):
foo = models.OneToOneField(Foo, null=True)
Para bien o para mal, Django considera que NULL
es equivalente a NULL
para fines de comprobación de exclusividad. Realmente no hay forma de evitar escribir su propia implementación de la verificación de singularidad que considera que NULL
es único, sin importar cuántas veces ocurra en una tabla.
(y tenga en cuenta que algunas soluciones DB tienen la misma visión de NULL
, por lo que el código que se basa en las ideas de un DB sobre NULL
puede no ser portátil para otros)
Recientemente tuve el mismo requisito. En lugar de crear subclases en diferentes campos, elegí anular el método save () en mi modelo (denominado ''MyModel'' a continuación) de la siguiente manera:
def save(self):
"""overriding save method so that we can save Null to database, instead of empty string (project requirement)"""
# get a list of all model fields (i.e. self._meta.fields)...
emptystringfields = [ field for field in self._meta.fields /
# ...that are of type CharField or Textfield...
if ((type(field) == django.db.models.fields.CharField) or (type(field) == django.db.models.fields.TextField)) /
# ...and that contain the empty string
and (getattr(self, field.name) == "") ]
# set each of these fields to None (which tells Django to save Null)
for field in emptystringfields:
setattr(self, field.name, None)
# call the super.save() method
super(MyModel, self).save()