Formset factory : required
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
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 ^^
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.
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.
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.
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é !')
Yes, il y a plusieurs façons de faire mais ValidationError est ce qu'on utilise généralement, tu as pas mal de lecture sur ce sujet dans la doc :
https://docs.djangoproject.com/fr/4.2/ref/forms/validation/#raising-validation-error
https://docs.djangoproject.com/fr/4.2/ref/forms/api/#django.forms.Form.add_error
https://docs.djangoproject.com/fr/4.2/ref/forms/validation/#form-and-field-validation
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 ;)
Inscris-toi
(c'est gratuit !)
Tu dois créer un compte pour participer aux discussions.
Créer un compte