2010-01-26 14 views
46

Ho un modello Django simile a questo.Django's ModelForm validazione unique_together

class Solution(models.Model): 
    ''' 
    Represents a solution to a specific problem. 
    ''' 
    name = models.CharField(max_length=50) 
    problem = models.ForeignKey(Problem) 
    description = models.TextField(blank=True) 
    date = models.DateTimeField(auto_now_add=True) 

    class Meta: 
     unique_together = ("name", "problem") 

Io uso una forma per l'aggiunta di modelli che assomiglia a questo:

class SolutionForm(forms.ModelForm): 
    class Meta: 
     model = Solution 
     exclude = ['problem'] 

mio problema è che il SolutionForm non convalida Solution s' unique_together vincolo e, quindi, restituisce un IntegrityError quando si cerca di salva il modulo. So che potrei usare validate_unique per verificare manualmente questo problema, ma mi chiedevo se c'è un modo per capirlo nella convalida del modulo e restituire automaticamente un errore del modulo.

Grazie.

+2

Sei sicuro che si imposta tutto correttamente, perché la documentazione relativa modello forme Syas chiaramente: "Per impostazione predefinita, il metodo clean() convalida l'unicità dei campi contrassegnati come univoci, unique_together o unique_for_date | month | year sul modello." Http://docs.djangoproject.com/en/1.1/topics/forms/modelforms/# overriding-the-clean-method –

+2

puoi provarlo senza la parte di esclusione? seleziona manualmente il problema che presumo sia determinato dalla tua vista. –

risposta

12

sono riuscito a risolvere il problema senza modificare la vista con l'aggiunta di un metodo pulito per il mio modulo:

class SolutionForm(forms.ModelForm): 
    class Meta: 
     model = Solution 
     exclude = ['problem'] 

    def clean(self): 
     cleaned_data = self.cleaned_data 

     try: 
      Solution.objects.get(name=cleaned_data['name'], problem=self.problem) 
     except Solution.DoesNotExist: 
      pass 
     else: 
      raise ValidationError('Solution with this Name already exists for this problem') 

     # Always return cleaned_data 
     return cleaned_data 

L'unica cosa che mi serve fare ora nella vista è aggiungere una proprietà problema al modulo prima di eseguire is_valid.

+9

Non utilizzare una clausola except tranne. Questo passerà anche se l'eccezione è dovuta al server di database colpito da una meteora. Utilizzare invece "eccetto Solution.DoesNotExist:". – GDorn

18

Come dice Felix, i ModelForms devono controllare il vincolo unique_together nella loro convalida.

Tuttavia, nel tuo caso in realtà stai escludendo un elemento di tale vincolo dal tuo modulo. Immagino che questo sia il tuo problema: in che modo il modulo controllerà il vincolo, se la metà non è nemmeno nella forma?

+2

In effetti quello era il problema. Quindi suppongo di non poter ottenere un errore sul modulo senza includere anche il campo del problema e che dovrò controllare manualmente questo caso. – sttwister

0

Avrete bisogno di fare qualcosa di simile:

def your_view(request): 
    if request.method == 'GET': 
     form = SolutionForm() 
    elif request.method == 'POST': 
     problem = ... # logic to find the problem instance 
     solution = Solution(problem=problem) # or solution.problem = problem 
     form = SolutionForm(request.POST, instance=solution) 
     # the form will validate because the problem has been provided on solution instance 
     if form.is_valid(): 
      solution = form.save() 
      # redirect or return other response 
    # show the form 
+0

Il modulo continua a non convalidare il vincolo 'unique_together', probabilmente perché il problema è menzionato nella proprietà' exclude', anche se ha un'istanza valida – sttwister

32

Ho risolto questo stesso problema sostituendo il metodo del ModelForm validate_unique():


def validate_unique(self): 
    exclude = self._get_validation_exclusions() 
    exclude.remove('problem') # allow checking against the missing attribute 

    try: 
     self.instance.validate_unique(exclude=exclude) 
    except ValidationError, e: 
     self._update_errors(e.message_dict) 

Ora ho solo sempre assicurarsi che l'attributo non previsto sul modulo è ancora disponibile, ad esempio instance=Solution(problem=some_problem) sull'inizializzatore.

+1

Si noti che questo convalida solo tutti i moduli per questo modello, mentre unique_together viene utilizzato nel database sottostante. Ciò significa che qualsiasi cosa che utilizza direttamente gli oggetti del modello non è vincolata da questa convalida. – Herge

+1

è buono usare i metodi protetti ..! – Satyajeet

1

Con l'aiuto di risposta di Jarmo, il seguente sembra funzionare bene per me (in Django 1.3), ma è possibile che io ho rotto alcuni casi angolo (ci sono un sacco di biglietti circostanti _get_validation_exclusions):

class SolutionForm(forms.ModelForm): 
    class Meta: 
     model = Solution 
     exclude = ['problem'] 

    def _get_validation_exclusions(self): 
     exclude = super(SolutionForm, self)._get_validation_exclusions() 
     exclude.remove('problem') 
     return exclude 

Non sono sicuro, ma questo mi sembra un bug di Django ... ma dovrei dare un'occhiata ai problemi precedentemente segnalati.


Modifica: ho parlato troppo presto. Forse quello che ho scritto sopra funzionerà in alcune situazioni, ma non nelle mie; Ho finito per usare direttamente la risposta di Jarmo.

5

la soluzione di @sttwister ha ragione ma può essere semplificata.

class SolutionForm(forms.ModelForm): 

    class Meta: 
     model = Solution 
     exclude = ['problem'] 

    def clean(self): 
     cleaned_data = self.cleaned_data 
     if Solution.objects.filter(name=cleaned_data['name'],   
            problem=self.problem).exists(): 
      raise ValidationError(
        'Solution with this Name already exists for this problem') 

     # Always return cleaned_data 
     return cleaned_data 

Come bonus voi non retreive l'oggetto in caso di duplicato, ma verificare solo se esiste nel database risparmiando un po 'di prestazioni.

0

Se si desidera che il messaggio di errore da un associato al campo name (e apparire accanto ad essa):

def clean(self): 
    cleaned_data = super().clean() 
    name_field = 'name' 
    name = cleaned_data.get(name_field) 

    if name: 
     if Solution.objects.filter(name=name, problem=self.problem).exists(): 
      cleaned_data.pop(name_field) # is also done by add_error 
      self.add_error(name_field, _('There is already a solution with this name.')) 

    return cleaned_data