Home > front end >  Why is model's clean run when viewing an already submitted form?
Why is model's clean run when viewing an already submitted form?

Time:07-26

I have a website that allows users to submit the same form several times (but with different input) but also allows them to view each form they submitted and edit it.

The problem is with the "key_name" field. If I view an already submitted form (edit_keydef) then there is the ValidationError message with the field highlighted in red, when in fact my intention is for that ValidationError to occur only when they are about to submit but have provided the same name as another form. (The view used when filling in the form is different to that when editing the form - as with the editing there's more fields) but both views use the same model.

I don't understand why DJango runs the clean() function when the form has already been sumbitted, i.e. the validation has already occurred and the data has already been submitted

views.py

def edit_keydef(request, id):

    data = {'title': 'View or Edit Key Definition'}
    template = 'generate_keys/edit_keydef.html'

    keydef = get_object_or_404(KeyDefinition, pk=id)

    if request.method == "POST":
        keydetails_form_instance = KeyDefDetailsForm(request.POST, instance=keydef)

        if 'Save' in request.POST:
            if keydetails_form_instance.is_valid():
                if keydetails_form_instance.cleaned_data['encoded_activation_key']:
                    activation_obj = Activation()
                    result = activation_obj.GenerateKey(keydef)
                keydetails_form_instance.save()
                return redirect('/key/edit/'   str(id))
            else:
                log.debug(keydetails_form_instance.errors)

        if 'Generate' in request.POST:
            if keydetails_form_instance.is_valid():
                activation_obj = Activation()
                result = activation_obj.GenerateKey(keydef)
                if "error" in result:
                    data['error_msg'] = format_html(
                        '<p>Failed to generate activation key because:</p>'
                          '<p>'
                          str(result['error'])
                          '</p>'
                    )
                else:
                    keydetails_form_instance.save()
                    return redirect('/key/edit/'   str(id))
            else:
                log.debug(keydetails_form_instance.errors)
    else:
        keydetails_form_instance = None
        if not id:
            keydetails_form_instance = KeyDefDetailsForm()
        else:
            # Retrieve a previously saved form.
            try:
                keydetails = KeyDefinition.objects.get(pk=id)
                keydetails_form_instance = KeyDefDetailsForm(keydetails.to_dict(),
                                             instance = keydetails)
            except KeyDefinition.DoesNotExist as e:
                return redirect('/record_does_not_exist')

    # Form instance has been saved at this stage so update the variant list.

    data['form'] = keydetails_form_instance
    data['set_product_options_url'] = reverse_lazy('set_product_options', kwargs={'id':id})

    return render(request, template, data)

models.py

class KeyDefinition (models.Model) :
 ...
  
 def clean (self) :
    
    ....
    
    # ensure that each user can't have two keys with the same name
    key_name_exists = KeyDefinition.objects.filter(key_name=self.key_name, developer_email=self.developer_email)
    if key_name_exists:
        raise ValidationError (
             {'key_name' : ['This Key Name already exists']} 
        )

 class Meta:
    verbose_name = "Key Definition"
    unique_together = [['key_name', 'developer_email']]

forms.py

class KeyDefDetailsForm (ModelForm) :

    def __init__(self, *args, **kwargs) :
        super(ModelForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_method = 'post'
        self.fields['version'].widget.attrs['class'] = "major_minor"

    def update_variants(self, keydef_obj):
        self.fields.update({
            'feature_variant': forms.ModelChoiceField(
                widget = Select(),
                queryset = FeatureVariant.objects.filter(product__feature_name=keydef_obj.feature),
                required = False,
                label = "Product"
            )
        })
    
    def update_available_hosts(self, keydef_obj):
        # only show hosts that have been created by this user
        self.fields.update({
            'available_hosts': forms.ModelChoiceField(
                empty_label=None,
                initial = AvailableHosts.objects.get(host_label=keydef_obj.host_label).id,
                widget=Select(),
                queryset = AvailableHosts.objects.filter(host_developer_email=keydef_obj.developer_email),
                required = False,                
                label = "Available Hosts"
            )
        }) 

    class Meta :
        model = KeyDefinition
        fields = '__all__'
        widgets = {
            'customer_name' : TextInput(),
            'host_method' : TextInput(),
            'issue_date' : TextInput(attrs={'type':'date'}),
            'expiry_date': TextInput(attrs={"type":"date"}),
            'available_hosts' : Select(),
            'country' : Select(),
            'feature' : Select(),
            'activation_request' : HiddenInput(),
            'hostid_provision' : HiddenInput(),
        }

CodePudding user response:

The KeyDefinition indeed exists: exactly the one you are editing matches. The clean() method will always run when validating a form. That is why you should exclude that object, so with:

class KeyDefinition(models.Model):
    def clean(self):
        # …
        # ensure that each user can't have two keys with the same name
        key_name_exists = (
            KeyDefinition.objects.filter(
                key_name=self.key_name, developer_email=self.developer_email
            )
            .exclude(pk=self.pk)
            .exists()
        )
        if key_name_exists:
            raise ValidationError({'key_name': ['This Key Name already exists']})
        return super().clean()

We here thus exclude our own object with .exclude(pk=self.pk).


Note: It is more efficient to use .exists() [Django-doc] than to check the truthiness of a QuerySet, since this will not load the records from the queryset, and thus will minimize the bandwidth used between the database and the Django/Python layer.

  • Related