2013-07-07 11 views
11

Utilizzo di Django 1.5.1. Python 2.7.3.Django unico insieme errore di vincolo?

Volevo eseguire un vincolo unico univoco con un campo chiave esterna e un campo di interferenza. Così nel mio modello di meta, ho fatto

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

class Meta: 
    unique_together = ("foreign_key", "slug") 

Ho anche controllato la descrizione della tabella in Postgres (9.1) e il vincolo è stato messo nella tabella del database.

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

Tuttavia, ho potuto ancora salvare nella tabella del database un foreign_key di Nessuno/null e duplicare le stringhe.

Per esempio,

potevo immissione e salvare

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

Così, dopo aver usato unique_together, perché posso ancora di ingresso e tre le stesse righe valutati?

Sto solo supponendo che potrebbe avere a che fare con il valore predefinito di None per il campo foreign_key, perché prima di unique_together, quando avevo solo unique = True su slug, tutto funzionava bene. Quindi, se questo è il caso, quale valore predefinito dovrei avere che indica un valore nullo, ma mantiene anche il vincolo univoco?

risposta

16

In Postgresql NULL non è uguale a nessun altro NULL. Pertanto le righe che crei non sono le stesse (dalla prospettiva di Postgres).

Aggiornamento

avete un paio di modi per trattare con esso:

  • proibire il valore Null per la chiave estera e di utilizzare alcuni valore predefinito
  • l'override del metodo save del modello a verifica che non esista alcuna riga
  • Modifica standard SQL :)
+1

come si corregge questo? Esiste un valore che posso assegnare al campo chiave esterna o qualcosa di simile a univoco su slug se la chiave esterna è NULL? – Derek

+0

Si prega di consultare l'aggiornamento. – Yossi

+0

C'è un valore predefinito per una chiave esterna in postgres tale che il valore non verrà mai utilizzato in modo naturale? – Derek

1

Aggiungere un metodo clean al modello, in modo da poter modificare una riga esistente.

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

Attenzione, clean (o full_clean) non viene chiamato dal metodo predefinito save.

NB: se si inserisce questo codice nel metodo save, i moduli di aggiornamento (come nell'admin) non funzioneranno: si verificherà un errore di traceback a causa dell'eccezione ValidationError.

0

basta creare manualmente indice secondario su slug campo, ma solo per i valori NULL in foreign_key_id:

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

Si prega di notare, che Django non supporta questo, quindi senza modulo personalizzato/validazione del modello si otterrà IntegrityError puro/500.

Eventuali duplicati di Create unique constraint with null columns

0

Come hobbyte detto, "in PostgreSQL NULL non è uguale a nessun altro NULL. Pertanto le file creati non sono gli stessi (dal punto di vista Postgres')."

Un altro modo per affrontare questa sfida è aggiungere una convalida personalizzata a livello di vista nel metodo form_valid.

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

Questo approccio è utile se si utilizza una vista basata classe, soprattutto se si sta assegnando automaticamente i valori per i campi che si desidera nascondere dal utente.

Pro:

  • non c'è bisogno di creare valori predefiniti fittizi nel database
  • È comunque possibile utilizzare forme di aggiornamento (vedi risposta di Toff)

Contro: - Questo non protegge da file duplicate create direttamente a livello di database. - Se si utilizza il back-end di amministrazione di Django per creare nuovi oggetti MyModel, è necessario aggiungere la stessa logica di convalida al modulo di amministrazione.

Problemi correlati