Tengo dos objetos que están conectados entre sí por una ForeignKey.

class Question(models.Model):
    text = models.Charfield()

class AnswerOption(models.Model):
    text = models.Charfield()
    question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="options")

Cuando se crea Question en la interfaz de administración, estoy usando un formulario en línea para AnswerOptions para que puedan crearse al mismo tiempo. Me gustaría realizar alguna validación en el Question y necesita acceso al nuevo AnswerOptions para tomar la decisión.

Agregué un método clean a Question pero la propiedad options está vacía.

¿Cuál es la forma correcta de validar Question?

[EDITAR] Dejó en claro que Question necesita acceso a AnswerOptions para poder validar todo.

[EDITAR] Se agregó una referencia explícita al uso de un InlineForm para AnswerOptions en la interfaz de administración.

1
Ben 17 dic. 2019 a las 19:24

2 respuestas

La mejor respuesta

Esto es lo que he descubierto:

Al crear formularios en línea en la interfaz de administración, Django crea un Formset para manejar los múltiples formularios. (El ejemplo aquí es el mismo que mi caso de uso)

Formsets tienen un método clean() como otros formularios y tienen una propiedad forms para acceder a los formularios secundarios. Al igual que los formularios normales, tienen una propiedad instance que se refiere a la clase 'base' y los formularios individuales tienen una propiedad instance que le proporciona una instancia de los datos recién enviados.

Poniendolo todo junto:

# models.py

class Question(models.Model):
    text = models.Charfield()

class AnswerOption(models.Model):
    text = models.Charfield()
    question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="options")

# admin.py

from django.contrib import admin
from django.forms.models import BaseInlineFormSet

class AnswerOptionFormset(BaseInlineFormset):
    def clean(self):
        super().clean() # See note in docs about calling this to check unique constraints

        #self.instance -> Question, with all the newly submitted, and validated, data.
        #self.forms -> iterator over all the submitted AnswerOption forms
        #for f in self.forms:
        #    f.instance -> instance of AnswerOption containing the new validated data

        #Note: self.instance.options will refer to the previous AnswerOptions

        #raise ValidationError for anything that is wrong.
        #It is also possible to modify the data in self.instance or form.instance instead.


class AnswerOptionInline(admin.TabularInline):
    formset = AnswerOptionFormset # note formset on AnswerOption NOT QuestionAdmin 


class QuestionAdmin(admin.ModelAdmin):
    inlines = [AnswerOptionInline]

1
Ben 19 dic. 2019 a las 13:58

Lo haría a través de un formulario de Django, que tiene una interfaz más robusta para validación. El método clean en su formulario es el lugar para este tipo de validación.

# forms.py 

from django import forms
from .models import Question

class QuestionForm(forms.Form):
    text = models.Charfield()

    class Meta:
        model = Question

    def clean(self):
        options = self.cleaned_data['options']
        if not option.are_ok:
          raise forms.ValidationError
# admin.py

from django import admin
from .forms import QuestionForm


class QuestionAdmin(admin.ModelAdmin):
    form = QuestionForm

...

De los documentos:

El método clean () de la subclase de formularios puede realizar una validación que requiere acceso a múltiples campos de formulario. Aquí es donde puede poner controles como "si se proporciona el campo A, el campo B debe contener una dirección de correo electrónico válida". Este método puede devolver un diccionario completamente diferente si lo desea, que se utilizará como la limpieza de datos.

1
Brian Dant 17 dic. 2019 a las 21:30