J'ai le modèle Foo qui a une barre de champ. Le champ de la barre doit être unique, mais autoriser les valeurs nulles, ce qui signifie que je souhaite autoriser plus d'un enregistrement si le champ de la barre l'est null
, mais si ce n'est pas null
le cas, les valeurs doivent être uniques.
Voici mon modèle:
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
Et voici le SQL correspondant pour la table:
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)
)
Lorsque vous utilisez l'interface d'administration pour créer plus de 1 objets foo où bar est nul, cela me donne une erreur: "Foo avec cette barre existe déjà."
Cependant, lorsque j'insère dans la base de données (PostgreSQL):
insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)
Cela fonctionne, très bien, cela me permet d'insérer plus d'un enregistrement avec une barre nulle, donc la base de données me permet de faire ce que je veux, c'est juste quelque chose qui ne va pas avec le modèle Django. Des idées?
ÉDITER
La portabilité de la solution dans la mesure où DB n'est pas un problème, nous sommes satisfaits de Postgres. J'ai essayé de définir un paramètre unique pour un appelable, qui était ma fonction retournant Vrai / Faux pour des valeurs spécifiques de barre , cela ne donnait aucune erreur, même si cela n'avait aucun effet.
Jusqu'à présent, j'ai supprimé le spécificateur unique de la propriété bar et géré l' unicité de la barre dans l'application, tout en recherchant une solution plus élégante. Des recommandations?
la source
def get_db_prep_value(self, value, connection, prepared=False)
d'un appel de méthode. Consultez groups.google.com/d/msg/django-users/Z_AXgg2GCqs/zKEsfu33OZMJ pour plus d'informations. La méthode suivante fonctionne aussi pour moi: def get_prep_value (self, value): if value == "": #if Django essaie de sauvegarder la chaîne '', envoie la base de données None (NULL) return None else: renvoie la valeur #otherwise, juste passer la valeurRéponses:
Django n'a pas considéré NULL comme étant égal à NULL aux fins des vérifications d'unicité depuis que le ticket # 9039 a été corrigé, voir:
http://code.djangoproject.com/ticket/9039
Le problème ici est que la valeur «vide» normalisée pour un formulaire CharField est une chaîne vide, pas None. Donc, si vous laissez le champ vide, vous obtenez une chaîne vide, et non NULL, stockée dans la base de données. Les chaînes vides sont égales aux chaînes vides pour les vérifications d'unicité, sous les règles de Django et de base de données.
Vous pouvez forcer l'interface d'administration à stocker NULL pour une chaîne vide en fournissant votre propre formulaire de modèle personnalisé pour Foo avec une méthode clean_bar qui transforme la chaîne vide en None:
la source
fields
ouexclude
dans lesModelForm
instances. Vous pouvez contourner ceMeta
problème en omettant la classe interne du ModelForm pour une utilisation dans admin. Référence: docs.djangoproject.com/en/1.10/ref/contrib/admin/…** edit 30/11/2015 : En python 3, la
__metaclass__
variable module-global n'est plus prise en charge . En outre, à partir deDjango 1.10
laSubfieldBase
classe était obsolète :Par conséquent, comme suggéré par la
from_db_value()
documentation et cet exemple , cette solution doit être remplacée par:Je pense qu'un meilleur moyen que de remplacer les données nettoyées dans l'administrateur serait de sous-classer le champ de caractères - de cette façon, quel que soit le formulaire accédant au champ, cela "fonctionnera simplement". Vous pouvez attraper le
''
juste avant qu'il ne soit envoyé à la base de données, et attraper le NULL juste après qu'il sort de la base de données, et le reste de Django ne le saura pas. Un exemple rapide et sale:Pour mon projet, je l'ai vidé dans un
extras.py
fichier qui vit à la racine de mon site, puis je peux justefrom mysite.extras import CharNullField
dans lemodels.py
fichier de mon application . Le champ agit comme un CharField - n'oubliez pas de définirblank=True, null=True
lors de la déclaration du champ, sinon Django lèvera une erreur de validation (champ obligatoire) ou créera une colonne db qui n'accepte pas NULL.la source
CharField
pour devenir unCharNullField
, vous devrez le faire en trois étapes. Tout d'abord, ajouteznull=True
au champ et migrez-le. Ensuite, effectuez une migration de données pour mettre à jour les valeurs vides afin qu'elles soient nulles. Enfin, convertissez le champ en CharNullField. Si vous convertissez le champ avant d'effectuer la migration de données, votre migration de données ne fera rien.from_db_value()
ne devrait pas avoir cecontex
paramètre supplémentaire . Ça devrait êtredef from_db_value(self, value, expression, connection):
Parce que je suis nouveau dans stackoverflow, je ne suis pas encore autorisé à répondre aux réponses, mais je tiens à souligner que d'un point de vue philosophique, je ne peux pas être d'accord avec la réponse la plus populaire à cette question. (par Karen Tracey)
L'OP exige que son champ de barre soit unique s'il a une valeur, et nul dans le cas contraire. Ensuite, il faut que le modèle lui-même s'assure que ce soit le cas. Il ne peut pas être laissé au code externe de vérifier cela, car cela signifierait qu'il peut être contourné. (Ou vous pouvez oublier de le vérifier si vous écrivez une nouvelle vue à l'avenir)
Par conséquent, pour garder votre code véritablement POO, vous devez utiliser une méthode interne de votre modèle Foo. Modifier la méthode save () ou le champ sont de bonnes options, mais l'utilisation d'un formulaire pour le faire ne l'est certainement pas.
Personnellement, je préfère utiliser le CharNullField suggéré, pour la portabilité vers des modèles que je pourrais définir dans le futur.
la source
La solution rapide est de faire:
la source
MyModel.objects.bulk_create()
contournerait cette méthode.Une autre solution possible
la source
Ce problème est résolu maintenant que https://code.djangoproject.com/ticket/4136 est résolu. Dans Django 1.11+, vous pouvez utiliser
models.CharField(unique=True, null=True, blank=True)
sans avoir à convertir manuellement les valeurs vides enNone
.la source
J'ai récemment eu la même exigence. Au lieu de sous-classer différents champs, j'ai choisi de remplacer le metod save () sur mon modèle (nommé `` MyModel '' ci-dessous) comme suit:
la source
Si vous avez un modèle MyModel et que vous souhaitez que my_field soit Null ou unique, vous pouvez remplacer la méthode de sauvegarde du modèle:
De cette façon, le champ ne peut pas être vide sera seulement non vide ou nul. les valeurs nulles ne contredisent pas l'unicité
la source
Vous pouvez ajouter
UniqueConstraint
avec condition denullable_field=null
et ne pas inclure ce champ dans lafields
liste. Si vous avez également besoin d'une contraintenullable_field
dont la valeur n'est pasnull
, vous pouvez en ajouter une supplémentaire.Remarque: UniqueConstraint a été ajouté depuis django 2.2
la source
Pour le meilleur ou pour le pire, Django considère
NULL
comme équivalent àNULL
des fins de vérification d'unicité. Il n'y a vraiment aucun moyen de contourner cela sans écrire votre propre implémentation du contrôle d'unicité qui considèreNULL
comme unique, quel que soit le nombre de fois qu'il se produit dans une table.(et gardez à l'esprit que certaines solutions de base de données adoptent le même point de vue
NULL
, donc le code reposant sur les idées d'une base de donnéesNULL
peut ne pas être portable pour d'autres)la source