update queryset query cual consultas avanzadas django orm django-models

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 por from_db_value() . Tenga en cuenta que el nuevo enfoque no llama al método to_python() en la asignación como en el caso de SubfieldBase .

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()