简体   繁体   中英

How can I pass modelformset_factory validation in Django?

I have 2 two models with a one-to-one relation as follow.

class Kategori(models.Model):
    urun = models.CharField(db_column='Urun', max_length=255, blank=True, null=True)  # Field name made lowercase.
    kategori = models.CharField(db_column='Kategori', max_length=255, blank=True, null=True)  # Field name made lowercase.
    ust_kategori = models.CharField(db_column='Ust_Kategori', max_length=255, blank=True, null=True)  # Field name made lowercase.
    urun_adi = models.CharField(db_column='URUN_ADI', max_length=255, blank=True, null=True)  # Field name made lowercase.
    ur_id = models.CharField(db_column='UR_ID', max_length=255, blank=True, null=True)  # Field name made lowercase.
    marka = models.CharField(db_column='MARKA', max_length=255, blank=True, null=True)  # Field name made lowercase.
    cesidi = models.CharField(db_column='CESIDI', max_length=255, blank=True, null=True)  # Field name made lowercase.
    miktar = models.FloatField(db_column='MIKTAR', blank=True, null=True)  # Field name made lowercase.
    birim = models.CharField(db_column='BIRIM', max_length=255, blank=True, null=True)  # Field name made lowercase.
    adet = models.FloatField(db_column='ADET', blank=True, null=True)  # Field name made lowercase.

class categoryprob(models.Model):
    urun = models.OneToOneField(Kategori,on_delete=models.CASCADE,related_name="prob")
    kategori = models.CharField(max_length=255, blank=True, null=True)  # Field name made lowercase.
    ust_kategori = models.CharField(max_length=255, blank=True, null=True)  # Field name made lowercase.
    urun_adi = models.CharField(max_length=255, blank=True, null=True)  # Field name made lowercase.
    marka = models.CharField(max_length=255, blank=True, null=True)  # Field name made lowercase.
    cesidi = models.CharField(max_length=255, blank=True, null=True)  # Field name made lowercase.
    miktar = models.FloatField(blank=True, null=True)  # Field name made lowercase.
    birim = models.CharField(max_length=255, blank=True, null=True)  # Field name made lowercase.
    adet = models.FloatField(blank=True, null=True)  # Field name made lowercase.

I am trying to update Kategori model objects depend on categoryprob inputs. I prepare a form to get Kategori objects depend on their probability at categoryprob as follows.

from django import forms
from mailservice.models import categoryprob,Kategori 

PASS_PROB = 0.8

class predictionForm(forms.ModelForm):
    
    class Meta:
        model = Kategori
        fields = ["urun","kategori","ust_kategori","urun_adi","marka","cesidi","miktar","birim","adet"]
        
        widgets = {
            'kategori': forms.Select(choices=set(Kategori.objects.all().values_list('kategori','kategori'))),
            'ust_kategori': forms.Select(choices=set(Kategori.objects.all().values_list('ust_kategori','ust_kategori'))),
            'marka': forms.Select(choices=set(Kategori.objects.all().values_list('marka','marka'))),
            'cesidi': forms.Select(choices=set(Kategori.objects.all().values_list('cesidi','cesidi'))),
            'miktar': forms.Select(choices=set(Kategori.objects.all().values_list('miktar','miktar'))),
            'birim': forms.Select(choices=set(Kategori.objects.all().values_list('birim','birim'))),
            'adet': forms.Select(choices=set(Kategori.objects.all().values_list('adet','adet'))),
        }
        
        labels ={
            'urun':""
        }

    def __init__(self, *args, **kwargs):
        super(predictionForm, self).__init__(*args, **kwargs)
        instance = getattr(self, 'instance', None)
        prob = categoryprob.objects.get(urun=instance)

        self.fields['urun'].widget.attrs['readonly'] = True

        if float(prob.kategori) > PASS_PROB:
            self.fields['kategori'].widget.attrs['disabled'] = True
            self.fields['kategori'].widget.attrs['style'] = 'border-color:red'
        else:
            self.fields['kategori'].widget.attrs['style'] = 'border-color:green'

        if float(prob.ust_kategori) > PASS_PROB:
            self.fields['ust_kategori'].widget.attrs['disabled'] = True
            self.fields['ust_kategori'].widget.attrs['style'] = 'border-color:red'
        else:
            self.fields['ust_kategori'].widget.attrs['style'] = 'border-color:green'
        
        if float(prob.urun_adi) > PASS_PROB:
            self.fields['urun_adi'].widget.attrs['disabled'] = True
            self.fields['urun_adi'].widget.attrs['style'] = 'border-color:red'
        else:
            self.fields['urun_adi'].widget.attrs['style'] = 'border-color:green'
        
        if float(prob.marka) > PASS_PROB:
            self.fields['marka'].widget.attrs['disabled'] = True
            self.fields['marka'].widget.attrs['style'] = 'border-color:red'
        else:
            self.fields['marka'].widget.attrs['style'] = 'border-color:green'
        
        if float(prob.cesidi) > PASS_PROB:
            self.fields['cesidi'].widget.attrs['disabled'] = True
            self.fields['cesidi'].widget.attrs['style'] = 'border-color:red'
        else:
            self.fields['cesidi'].widget.attrs['style'] = 'border-color:green'

        if float(prob.miktar) > PASS_PROB:
            self.fields['miktar'].widget.attrs['disabled'] = True
            self.fields['miktar'].widget.attrs['style'] = 'border-color:red'
        else:
            self.fields['miktar'].widget.attrs['style'] = 'border-color:green'
        
        if float(prob.birim) > PASS_PROB:
            self.fields['birim'].widget.attrs['disabled'] = True
            self.fields['birim'].widget.attrs['style'] = 'border-color:red'
        else:
            self.fields['birim'].widget.attrs['style'] = 'border-color:green'
        
        if float(prob.adet) > PASS_PROB:
            self.fields['adet'].widget.attrs['disabled'] = True
            self.fields['adet'].widget.attrs['style'] = 'border-color:red'
        else:
            self.fields['adet'].widget.attrs['style'] = 'border-color:green'

I use modelformset_factory to render 3 of them together. Everything works fine when I am rending on the template as follows.


<form method="post" enctype="multipart/form-data">
    
    <div class="form-row">
        {{ p_form.management_form }}
        {% csrf_token %}
        {%for form in p_form%}
        <div class="form-group col-md-12">
            <div class="card">
                <div class="card-header">
                    {{form.urun|as_crispy_field}}
                </div>
                <div class="card-body">
                    
                    <div class="row">   
                        <div class ="col-md-3 col ">
                            {{form.kategori|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.ust_kategori|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.urun_adi|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.marka|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.cesidi|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.miktar|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.birim|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.adet|as_crispy_field}}
                        </div>
                        
                    </div>
                    
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
    
    <button type="submit" class="btn btn-success">Kaydet</button>
</form>

When I submit with post method as follows:

def editCategories(request):
    categories = Kategori.objects.all().order_by('?')[:3]
    CategoryFormSet = modelformset_factory(Kategori, form = predictionForm,extra=0)
    formset = CategoryFormSet(queryset = categories)

    if request.method == "POST":
        if formset.is_valid():
            formset.save()
        return redirect(request.META['HTTP_REFERER'])
    content = {
        "p_form":formset,
    }  
    
    return render(request,'edit_categories.html',content)

formset.is_valid return false all the time.

I couldn't figure out how to validate all forms at once. Do you have any suggestion?

Picture of template output:

在此处输入图像描述

The output of the request.POST:

<QueryDict: {'form-TOTAL_FORMS': ['3'], 'form-INITIAL_FORMS': ['3'], 'form-MIN_NUM_FORMS': ['0'], 'form-MAX_NUM_FORMS': ['1000'], 'csrfmiddlewaretoken': ['*****'], 'form-0-id': ['125'], 'form-0-urun': ['_MotorinEcoForce'], 'form-0-urun_adi': ['asdasd'], 'form-1-id': ['186'], 'form-1-urun': ['7 stick sakız karpuz aromalı 12 adet'], 'form-1-marka': ['7 days'], 'form-2-id': ['159'], 'form-2-urun': ['7 Days Kruvasan Kayısılı Tekli 72 g'], 'form-2-kategori': ['Sebze']}>

One can say there are two kinds of forms a bound form and an unbound form. What is the difference between these? Well a bound form is passed some data MyForm(request.POST, request.FILES) and an unbound form isn't passed any data MyForm() . By logic an unbound form can never be valid because, well it was never submitted and it is assumed to be created to simply display / render the form.

This logic similarly follows for formsets, and hence as you haven't passed any data to your formset it will never be valid. Another thing to be considered is that you haven't rendered the hidden fields for the formset. A formset makes certain hidden fields so that it can recognize which sub form is for which objects and some other things like deletion, etc. Without these hidden fields also your formset would be invalid.

Hence your view should be like:

def editCategories(request):
    categories = Kategori.objects.all().order_by('?')[:3]
    CategoryFormSet = modelformset_factory(Kategori, form = predictionForm,extra=0)
    formset = CategoryFormSet(queryset = categories)

    if request.method == "POST":
        # Will get inefficient later on when you have many objects in the database, look for a different solution
        formset = CategoryFormSet(request.POST, request.FILES) # Make a bound formset in case of a POST request
        if formset.is_valid():
            formset.save()
        return redirect(request.META['HTTP_REFERER'])
    content = {
        "p_form":formset,
    }
    return render(request,'edit_categories.html',content)

Your template should be like:

<form method="post" enctype="multipart/form-data">
    
    <div class="form-row">
        {{ p_form.management_form }}
        {% csrf_token %}
        {%for form in p_form%}
            {# Render hidden fields #}
            {% for hidden in form.hidden_fields %}
                {{ hidden }}
            {% endfor %}
        <div class="form-group col-md-12">
            <div class="card">
                <div class="card-header">
                    {{form.urun|as_crispy_field}}
                </div>
                <div class="card-body">
                    
                    <div class="row">   
                        <div class ="col-md-3 col ">
                            {{form.kategori|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.ust_kategori|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.urun_adi|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.marka|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.cesidi|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.miktar|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.birim|as_crispy_field}}
                        </div>
                        <div class ="col-md-3 col">
                            {{form.adet|as_crispy_field}}
                        </div>
                        
                    </div>
                    
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
    
    <button type="submit" class="btn btn-success">Kaydet</button>
</form>

Also you set fields to be disabled only by adding a disabled attribute to their widget, this means they are not posted with the form, but since the field itself is not disabled it looks for their data, doesn't find it and sets it to None , instead you want to disable the field itself:

class predictionForm(forms.ModelForm):
    ...
    
    def __init__(self, *args, **kwargs):
        ...
        # truncated for shortness
        # similar needs to be done for other fields too
        if float(prob.kategori) > PASS_PROB:
            self.fields['kategori'].disabled = True
            self.fields['kategori'].widget.attrs['style'] = 'border-color:red'
        ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM