2010-01-26 17 views
46

que tienen un modelo Django que tiene este aspecto.ModelForm validación unique_together de Django

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

Puedo usar un formulario de añadido de los modelos que tiene este aspecto:

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

Mi problema es que el SolutionForm no valida Solution 's unique_together restricción y, por tanto, devuelve un IntegrityError cuando se trata de guarda el formulario Sé que podría utilizar validate_unique para comprobar manualmente para esto, pero me preguntaba si hay alguna manera de coger esto en la validación de formularios y devolver un error de forma automática.

Gracias.

+2

¿Seguro que configuró todo correctamente debido a que la documentación acerca de los modelos de formularios syas claramente: "De forma predeterminada, el método clean() valida la exclusividad de los campos marcados como únicos, unique_together o unique_for_date | month | year en el modelo." Http://docs.djangoproject.com/en/1.1/topics/forms/modelforms/# overriding-the-clean-method –

+2

¿Puedes probarlo sin la parte de exclusión? seleccionar manualmente el problema que supongo que está determinado por su vista. –

Respuesta

12

logré solucionar este problema sin modificar la vista mediante la adición de un método limpio a mi forma:

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 

La única cosa que necesito que hacer ahora en la vista es añadir una característica del problema de la forma antes de ejecutar is_valid.

+9

No utilice una cláusula except bare. Esto pasará incluso si la excepción se debe a que el servidor de la base de datos es golpeado por un meteoro. En su lugar, use "excepto Solution.DoesNotExist:". – GDorn

18

Como dice Félix, se supone ModelForms para comprobar la restricción unique_together en su validación.

Sin embargo, en su caso, en realidad se está excluyendo a un elemento de esa limitación de su formulario. Me imagino que esto es su problema - cómo se forma va a comprobar la restricción, si la mitad de ella ni siquiera está en el formulario?

+2

De hecho, ese era el problema. Así que supongo que no puedo obtener un error en el formulario sin incluir también el campo del problema y que tendré que verificar manualmente este caso. – sttwister

0

Usted tendrá que hacer algo como esto:

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

El formulario todavía no valida la restricción 'unique_together', probablemente porque el problema se menciona en la propiedad' exclude', aunque tenga una instancia válida – sttwister

32

que resolvieron este mismo problema reemplazando el método de la 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) 

Ahora sólo hay que procurar siempre el atributo no previsto en el formulario que está disponible, por ejemplo, instance=Solution(problem=some_problem) en el inicializador.

+1

Tenga en cuenta que esto solo valida formularios para este modelo, mientras que unique_together se usa en la base de datos subyacente. Eso significa que cualquier cosa que use los objetos del modelo directamente no está sujeta a esta validación. – Herge

+1

es bueno usar los métodos Protegidos ..! – Satyajeet

1

Con la ayuda de la respuesta de Jarmo, la siguiente parece que funciona muy bien para mí (en Django 1.3), pero es posible que he roto algún caso esquina (hay una gran cantidad de entradas que rodean _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 

No estoy seguro, pero esto me parece un error de Django ... pero tendría que buscar en los problemas informados anteriormente.


Editar: Hablé demasiado pronto. Tal vez lo que escribí arriba funcionará en algunas situaciones, pero no en la mía; Terminé usando la respuesta de Jarmo directamente.

5

la solución de @sttwister es correcta pero se puede simplificar.

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 

Como beneficio adicional que no retreive el objeto en caso de duplicado, pero sólo comprobar si existe en la base de datos de guardar un poco de actuaciones.

0

Si desea que el mensaje de error que se va a asociar con el campo name (y aparecen junto a él):

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 
Cuestiones relacionadas