Résolue

Formset factory : required

# Django # Formulaires

Gabriel Trouvé

Mentor

Bonjour ! :)

Si j'ai bien compris on n'a pas de required field avec un formet factory.

J'utilise ce formulaire :

class PremiumForm(forms.ModelForm):
    quality = forms.IntegerField(label="Qualité")
    width = forms.IntegerField(label="Largeur", required=False)
    height = forms.IntegerField(label="Hauteur", required=False)

    class Meta:
        fields = ["quality", "width", "height"]

Effectivement quand j'inspecte la page je n'ai pas de required :

<input id="id_form-0-quality" name="form-0-quality" type="number"/>

Dans le cas d'un formset il n'y a pas moyen de rendre un champ requis ? Je n'ai rien trouvé de concluant.

J'ai voulu surcharger la méthode clean dans ma classe mais ça me valide tout de même le formulaire même si rien n'est renseigné dans qualité.

Merci d'avance

Thibault houdon

Mentor

Salut Gab !

À quoi ressemble ton modèle et le champ quality ? Est-ce que tu as une valeur par défaut pour ce champ ?

Gabriel Trouvé

Mentor

Salut Thibault !

Je viens de voir que je n'ai pas spécifié mon modèle dans le ModelForm au niveau de la class Meta.

Les champs du formulaire ne sont pas liés aux champs du modèle.

class UpImage(models.Model):
    image = models.ImageField(upload_to="upImage")
    user = models.ForeignKey(to=AUTH_USER_MODEL, on_delete=models.CASCADE,
                             verbose_name="Utilisateur", null=True, blank=True)
    published = models.DateField(null=True)
    expire = models.DateField(null=True)
    archived = models.BooleanField(default=False)

Je me sers du formulaire pour le formset :

@user_passes_test(user_is_premium, login_url="compressor:subscription")
def premium_images_view(request):
    user = request.user
    images = UpImage.objects.filter(user=user, archived=False)
    # Je crée une classe depuis le formsetfactory
    UpFormSet = modelformset_factory(UpImage, form=PremiumForm, extra=0)
    formset = UpFormSet(queryset=images)
    return render(request, "compressor/images-premium.html", context={"forms": formset})

Huum, j'aurais peut-être dû utiliser forms.Form plutôt que ModelForm peut-être... Mais là je ne suis pas chez moi je ne peux pas tester. Faudrait que je regarde ce soir ^^

Thibault houdon

Mentor

Ah oui effectivement, commence par mettre ton modèle ;)

Gabriel Trouvé

Mentor

C'est fait, mais au final ce n'est pas grave si on ne renseigne pas l'attribut model à la classe meta dans mon cas. C'est le formset qui sait quel modèle est utilisé je pense comme on le renseigne dans tous les cas ? (mon modèle c'est UImage).

Enfin vaut mieux le renseigner dans le ModelForm dans tous les cas c'est plus clair je pense. Car habituellement on est bien obligé de le renseigner pour accéder aux champs du modèle.

UpFormSet = modelformset_factory(UpImage, form=PremiumForm, extra=0)

Du coup, dans mon Form que je crée c'est un ModelForm qui n'utilise aucun attribut de mon modèle. C'est un peu particulier dans mon cas.

class PremiumForm(forms.ModelForm):
    quality = forms.IntegerField(label="Qualité", min_value=1, max_value=95)
    width = forms.IntegerField(label="Largeur", required=False)
    height = forms.IntegerField(label="Hauteur", required=False)

    class Meta:
        model = UpImage
        fields = ["quality", "width", "height"]

Mais pourtant je n'ai pas de required False pour quality comme tu peux le voir. Ni de initial.
Et ... je peux très bien valider les formulaires sans renseigner la qualité...
J'ai l'impression qu'on ne peut pas avoir un champ required avec un formset factory.

Thibault houdon

Mentor

Et ton formset il est valide dans ta vue Django ?

Tu as regardé du côté du HTML en inspectant le code source si tu vois required comme attribut sur le champ quality ?

Gabriel Trouvé

Mentor

Ok, le required quand j'inspecte n'apparait pas j'avais déjà vérifié. Je viens de regarder encore et je confirme que pas de required dans le HTML.

Et le champ qualité en question si je ne mets rien je n'ai pas d'erreur, mais on voit que rien ne se passe après le formset.is_valid(). Pas de compression.
En gros si rien de renseigné dans qualité formset is_valid est False.

<hr/>

Si je mets required=False au champ qualité, formset is valid est True.
Mais bon ça plante comme je ne renseigne pas de qualité PIL n'est pas content.

<hr/>

En gros si j'avais bien le required qui se mettait dans le HTML ça serait bien plus sympa pour la cohérence de l'app...

Donc si effectivement le required n'apparaît pas je pense qu'il faut que tu surcharge la méthode clean pour faire une validation de ton côté, quelque chose du genre :

def clean(self):
    cleaned_data = super().clean()
    quality = cleaned_data.get("quality")
    if quality is None:
        self.add_error('quality', 'Hey pote, tu as oublié la qualité !')

Gabriel Trouvé

Mentor

Ahh je ne connaissais pas le add_error.

Je peux aussi partir sur un raise forms.ValidationError je pense ? Puis afficher les erreurs dans le template ? j'ai déjà fait ça une fois sur un projet

J'essaye ce soir je suis au boulot.

Merci Thibault !

Gabriel Trouvé

Mentor

merci Thibault je vais surcharger alors ! :)

J'ai compris ce qui n'allait pas !

Pas besoin de surcharger clean().

En fait si le formulaire n'était pas valide je faisais un redirect sur la vue du formulaire... Donc retour à la case départ.
Donc j'ai supprimé le redirect comme ça je retourne dans le render sans redirect.

Toujours pas de required dans le html, mais si je ne mets rien dans le champ qualité lorsque j'envoi, après le rechargement j'ai une erreur au dessus du cham qualité : "Champ obligatoire". Et ça ne sa valide pas.

Du coup je n'ai pas de required, mais Django sait quand même que c'est un champ obligatoire....

C'est normal ? lol

Vue corrigée :

@user_passes_test(user_has_sub, login_url="compressor:subscription")
def premium_images_view(request):
    user = request.user
    images = UpImage.objects.filter(user=user, archived=False)
    # Je crée une classe depuis le formsetfactory
    UpFormSet = modelformset_factory(UpImage, form=PremiumForm, extra=0)
    formset = UpFormSet(queryset=images)

    if request.method == "POST":
        formset = UpFormSet(request.POST, queryset=images)
        print(formset.errors)
        if formset.is_valid():
            for form in formset:
                quality = form.cleaned_data["quality"]
                width = form.cleaned_data["width"]
                height = form.cleaned_data["height"]
                my_image = form.instance
                image = Image.open(form.instance.image)

                if width and height:
                    image.thumbnail((width, height))

                im_io = BytesIO()

                ext = my_image.get_extension()
                if ext != "JPEG":
                    image.save(im_io, "JPEG", quality=quality)
                else:
                    image.save(im_io, ext.upper(), quality=quality)

                # ajuste pour ne pas créer des sous dossiers
                file_name = my_image.adjust_file_name()
                my_image.image.delete()
                my_image.image.save(file_name, ContentFile(im_io.getvalue()), save=False)
                my_image.archived = True
                my_image.save()

            return redirect("compressor:all-premium-images")

    return render(request, "compressor/images-premium.html", context={"forms": formset})

Salut Gab,

En fait, avec Django, quand on dit qu'un champ est obligatoire (required=True), ça signifie que lors de la validation des données du formulaire, Django s'assure que la clé correspondant à ce champ est présente dans le dictionnaire des données. Si elle ne l'est pas, Django génère une erreur de validation.

Que le required apparaisse ou non dans le HTML n'a pas d'importance pour la validation côté serveur, c'est plus pour que le navigateur sache qu'il doit empêcher l'envoi du formulaire tant que le champ n'est pas rempli.

Donc si tu testes form.is_valid(), il vérifie que tous les champs required ont bien une valeur. Si ce n'est pas le cas, le formulaire n'est pas valide. Les erreurs que tu vois ensuite sont créées par Django.

Donc oui, c'est normal ce que tu vois !

Et bien joué pour ton débogage ;)

Gabriel Trouvé

Mentor

ok merci Thibault ! ce projet m'a permis se bosser les formulaires. Je trouve que jusqu'ici c'est le concept avec lequel j'ai le plus de mal mais je commence à bien m'y faire aux forms , formset, methode clean etc...

Inscris-toi

(c'est gratuit !)

Inscris-toi

Tu dois créer un compte pour participer aux discussions.

Créer un compte

Rechercher sur le site

Formulaire de contact

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.