Почему при просмотре уже отправленной формы запускается чистая модель?

У меня есть веб-сайт, который позволяет пользователям отправлять одну и ту же форму несколько раз (но с разными входными данными), но также позволяет им просматривать каждую отправленную форму и редактировать ее.

Проблема связана с полем "имя_ключа". Если я просматриваю уже отправленную форму (edit_keydef), то появляется сообщение ValidationError с полем, выделенным красным цветом, тогда как на самом деле я хотел, чтобы ValidationError возникала только тогда, когда они собираются отправить форму, но указали то же имя, что и в другой форме. (Представление, используемое при заполнении формы, отличается от представления при редактировании формы - так как при редактировании больше полей), но оба представления используют одну и ту же модель.

Я не понимаю, почему DJango запускает функцию clean(), когда форма уже заполнена, т.е. валидация уже произошла и данные уже отправлены

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(),
        }

Метод KeyDefinition действительно существует: соответствует именно тот, который вы редактируете. Метод clean() будет всегда выполняться при валидации формы. Поэтому вы должны исключить этот объект, поэтому с:

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

Таким образом, мы исключаем собственный объект с помощью .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.

Вернуться на верх