2009-05-18 24 views
41

Me gustaría hacer obligatoriamente un formset entero en línea dentro de un formulario de cambio de administrador. Por lo tanto, en mi situación actual, cuando pulso guardar en un formulario de Factura (en Admin), el formulario de Pedido en línea está en blanco. Me gustaría evitar que las personas creen facturas sin pedidos asociados.Validación de formulario en línea en Django

¿Alguien sabe una manera fácil de hacer eso?

La validación normal como (required=True) en el campo de modelo no parece funcionar en esta instancia.

Respuesta

63

La mejor manera de hacerlo es definir un formset personalizado, con un método limpio que valida que exista al menos un pedido de factura.

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

La solución perfecta, gracias – user108791

+3

He descubierto que si la caja de borrado está marcada, es posible validar con 0 órdenes. Vea mi respuesta para una clase revisada que resuelve ese problema. –

+0

Muchas gracias por este parche (y Dan por la mejora). Como una posible pista para otros, he hecho una 'clase MandatoryInlineFormSet (BaseInlineFormSet)' y luego de esto obtuve InvoiceAdminFormSet. En mi InvoiceAdminFormSet tengo un método clean() que realiza la validación personalizada, pero las primeras devuelven llamadas a MandatoryInlineFromSet.clean(). – Kurt

18

respuesta de Daniel es excelente y que trabajó para mí en un proyecto, pero luego me di cuenta debido a la forma de Django forma de trabajo, si está utilizando can_delete y marca la casilla de borrado mientras que el ahorro, es posible validar w/o cualquier orden (en este caso).

Pasé un tiempo tratando de encontrar la manera de evitar que eso ocurra. La primera situación fue fácil: no incluya los formularios que se eliminarán en el recuento. La segunda situación fue más complicada ... si todos se marcan los cuadros de eliminación, entonces no se llamó a clean.

El código no es exactamente sencillo, desafortunadamente. El método clean se llama desde full_clean al que se llama cuando se accede a la propiedad error. No se tiene acceso a esta propiedad cuando se elimina un subformulario, por lo que nunca se llama al full_clean. No soy un experto en Django, así que esta podría ser una manera terrible de hacerlo, pero parece funcionar.

Aquí está la clase de modificación:

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

¿Es posible hacer lo mismo con extra = 0? –

+1

@Siva - Acabo de verificar, y sí, puede tener extra = 0. Sin embargo, si desea que un comentario (en mi caso) sea obligatorio, probablemente debería darle al usuario un formulario en blanco o no hacerlo obligatorio. – Kurt

4

solución @ Daniel Roseman está muy bien, pero tengo algunas modificaciones con algo de código a menos que hacer esto mismo.

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] 

probar esto también funciona :)

+0

Vaya, no quise votar esto. No funciona cuando las casillas de verificación "eliminar" están marcadas. – Tobu

+0

no entendió su pregunta? este código se asegura de que cada 'Factura' debe tener un' Factura' en él. ¡Y en ese momento no hay casillas de verificación de eliminación! – Ahsan

Cuestiones relacionadas