2009-05-18 17 views
41

Ich möchte ein komplettes Inline Formset innerhalb eines Admin Change Formulars zwingend machen. In meinem aktuellen Szenario, wenn ich in einem Rechnungsformular (in Admin) auf "Speichern" klicke, ist das Inline-Bestellformular leer. Ich möchte die Leute daran hindern, Rechnungen ohne Aufträge zu erstellen.Inline Form Validierung in Django

Wer weiß, eine einfache Möglichkeit, das zu tun?

Normale Validierung wie (required=True) auf dem Modellfeld scheint in diesem Fall nicht zu funktionieren.

Antwort

63

Der beste Weg, dies zu tun, ist ein benutzerdefiniertes Formset zu definieren, mit einer sauberen Methode, die bestätigt, dass mindestens ein Rechnungsauftrag existiert.

class InvoiceOrderInlineFormset(forms.models.BaseInlineFormSet): 
    def clean(self): 
     # get forms that actually have valid data 
     count = 0 
     for form in self.forms: 
      try: 
       if form.cleaned_data: 
        count += 1 
      except AttributeError: 
       # annoyingly, if a subform is invalid Django explicity raises 
       # an AttributeError for cleaned_data 
       pass 
     if count < 1: 
      raise forms.ValidationError('You must have at least one order') 

class InvoiceOrderInline(admin.StackedInline): 
    formset = InvoiceOrderInlineFormset 


class InvoiceAdmin(admin.ModelAdmin): 
    inlines = [InvoiceOrderInline] 
+0

Perfekte Lösung, dank – user108791

+3

Ich fand, dass, wenn das Löschfeld aktiviert ist, es möglich ist, mit 0 Aufträgen zu validieren. Sehen Sie meine Antwort für eine überarbeitete Klasse, die das Problem löst. –

+0

Vielen Dank für dieses Update (und Dan für die Verbesserung). Als möglicher Hinweis für andere habe ich eine 'Klasse MandatoryInlineFormSet (BaseInlineFormSet)' und dann InvoiceAdminFormSet daraus abgeleitet. In meinem InvoiceAdminFormSet habe ich eine clean() -Methode, die eine benutzerdefinierte Validierung durchführt, aber zuerst zu MandatoryInlineFromSet.clean() zurückruft. – Kurt

18

Daniels Antwort ist ausgezeichnet und es funktionierte an einem Projekt für mich, aber dann merkte ich, aufgrund der Art und Weise Django Arbeit bildet, wenn Sie can_delete und überprüfen Sie die Lösch Box verwenden beim Speichern, ist es möglich, w zu validieren/o irgendwelche Bestellungen (in diesem Fall).

Ich verbrachte eine Weile damit herauszufinden, wie man das verhindern kann. Die erste Situation war einfach - schließen Sie nicht die Formulare ein, die bei der Zählung gelöscht werden. Die zweite Situation war kniffliger ... Wenn alle die Löschfelder überprüft werden, dann wurde clean nicht aufgerufen.

Der Code ist leider nicht gerade geradlinig. Die clean-Methode wird von full_clean aufgerufen, die aufgerufen wird, wenn auf die error-Eigenschaft zugegriffen wird. Auf diese Eigenschaft wird nicht zugegriffen, wenn ein Teilformular gelöscht wird. Daher wird full_clean niemals aufgerufen. Ich bin kein Django-Experte, also könnte das ein schrecklicher Weg sein, aber es scheint zu funktionieren.

Hier ist die modifizierte Klasse:

class InvoiceOrderInlineFormset(forms.models.BaseInlineFormSet): 
    def is_valid(self): 
     return super(InvoiceOrderInlineFormset, self).is_valid() and \ 
        not any([bool(e) for e in self.errors]) 

    def clean(self): 
     # get forms that actually have valid data 
     count = 0 
     for form in self.forms: 
      try: 
       if form.cleaned_data and not form.cleaned_data.get('DELETE', False): 
        count += 1 
      except AttributeError: 
       # annoyingly, if a subform is invalid Django explicity raises 
       # an AttributeError for cleaned_data 
       pass 
     if count < 1: 
      raise forms.ValidationError('You must have at least one order') 
2
class MandatoryInlineFormSet(BaseInlineFormSet): 

    def is_valid(self): 
     return super(MandatoryInlineFormSet, self).is_valid() and \ 
        not any([bool(e) for e in self.errors]) 
    def clean(self):   
     # get forms that actually have valid data 
     count = 0 
     for form in self.forms: 
      try: 
       if form.cleaned_data and not form.cleaned_data.get('DELETE', False): 
        count += 1 
      except AttributeError: 
       # annoyingly, if a subform is invalid Django explicity raises 
       # an AttributeError for cleaned_data 
       pass 
     if count < 1: 
      raise forms.ValidationError('You must have at least one of these.') 

class MandatoryTabularInline(admin.TabularInline): 
    formset = MandatoryInlineFormSet 

class MandatoryStackedInline(admin.StackedInline): 
    formset = MandatoryInlineFormSet 

class CommentInlineFormSet(MandatoryInlineFormSet): 

    def clean_rating(self,form): 
     """ 
     rating must be 0..5 by .5 increments 
     """ 
     rating = float(form.cleaned_data['rating']) 
     if rating < 0 or rating > 5: 
      raise ValidationError("rating must be between 0-5") 

     if (rating/0.5) != int(rating/0.5): 
      raise ValidationError("rating must have .0 or .5 decimal") 

    def clean(self): 

     super(CommentInlineFormSet, self).clean() 

     for form in self.forms: 
      self.clean_rating(form) 


class CommentInline(MandatoryTabularInline): 
    formset = CommentInlineFormSet 
    model = Comment 
    extra = 1 
+0

Ist es verwenden möglich, das Gleiche mit extra = 0 zu tun? –

+1

@Siva - Ich habe gerade überprüft, und ja, Sie können Extra = 0 haben. Wenn Sie jedoch (in meinem Fall) einen Kommentar benötigen, sollten Sie dem Benutzer wahrscheinlich ein leeres Formular geben oder es nicht zwingend machen. – Kurt

4

@ Daniel Roseman Lösung ist in Ordnung, aber ich habe einige Änderungen mit etwas weniger Code das gleiche zu tun.

class RequiredFormSet(forms.models.BaseInlineFormSet): 
     def __init__(self, *args, **kwargs): 
      super(RequiredFormSet, self).__init__(*args, **kwargs) 
      self.forms[0].empty_permitted = False 

class InvoiceOrderInline(admin.StackedInline): 
     model = InvoiceOrder 
     formset = RequiredFormSet 


class InvoiceAdmin(admin.ModelAdmin): 
    inlines = [InvoiceOrderInline] 

versuchen, dies funktioniert auch :)

+0

Whoops, wollte das nicht verbessern. Es funktioniert nicht, wenn die Kontrollkästchen "Löschen" aktiviert sind. – Tobu

+0

hat Ihre Frage nicht verstanden? Dieser Code stellt sicher, dass jede "Rechnung" eine "Rechnungsbestellung" enthalten muss. Und zu diesem Zeitpunkt gibt es keine Checkboxen zum Löschen! – Ahsan