form.is_valid() always false where prefilled form used

Hello team,
I have an issue about my form. I using pre-filled form using an instance.

Here how i declare my form:

class PinjammanFormReason(forms.ModelForm):
    class Meta:
        model = Pinjamman
        fields = ['amount_borrowed','installment','information','status_reason']
        labels = {'status_reason':'Alasan Penolakan','amount_borrowed':'Nominal','installment':'Tenor(bulan)','information':'Tujuan'}
        def __str__(self):
            return str(self.amount_borrowed)

Here how i declare my view for the form

if request.method == 'POST':
        form = PinjammanFormReason(instance=pinjaman)
        print(form.is_valid())
        if form.is_valid():
            print('VALID')
            p = form.save(commit=False)
            p.status_reason = form.cleaned_data['status_reason']
            print(p.status_reason)
           ...
            return histori_peminjaman_admin(request)
        else:
            form = PinjammanFormReason(instance=pinjaman)
context={
            'u':request.user,
            'form':form
            }
    return render(request, 'allpages/pengajuan_reason.html', context)

And here is the issue: when i try to debug the method used while called this view, the method always “POST” and Pinjamman object is found. It has been prefilled with the object except the status_reason where it should be filled with text. When i click update on my UI, it returns nothing and the data is not updated.

I suggest you review the docs at Working with forms | Django documentation | Django and Creating forms from models | Django documentation | Django to see how you should be using your forms. Pay particular attention to the examples as to how things are done.

You might also want to review the work you would have done at Writing your first Django app, part 4 | Django documentation | Django when working through the official tutorial.

A couple things that I see right off-hand:

  • You’re not binding the POST data to the form.

  • You’re recreating the form in the else clause of the is_valid check. If there are errors and that else is being executed, you’re going to lose whatever errors were identified in the form.

  • You’re returning the output of view from the is_valid test. You should be returning a redirect to the URL for that view and not calling it directly.

(You’re also not showing the complete view, making it difficult to identify other potential issues.)

Hello Ken,
Thanks for your brief response, sorry for checking it late due New Year season.

Based on your suggestion from the input here I have updated for the specific output (sorry i only put the specific view function to make the discussion focused on this particular form)

  1. I have put POST data to the form initialization
  2. I change the else clause (if is_valid is false) to only print the error
  3. I change the return to the action page

I need to put some context on how the form will be triggered:

  1. Here, an admin will enter a page with the yellowed marker
  2. When the admin hits “TOLAK PENGAJUAN” button (“REJECT APPROVAL in English), it should trigger the erroneous form. Although I have put the initial value (from model query search), i found out the form is loaded but with all initiated forms raised " This field is required.

Here is the model and the form related to it:

class Pinjamman(models.Model):
    Anggota = models.ForeignKey('allpages.Anggota', on_delete=models.CASCADE,related_name='member_id')
    amount_borrowed = models.DecimalField(max_digits=12, decimal_places=2)
    installment = models.CharField(max_length=2)
    date_of_application = models.DateField(null=True,auto_now_add=True, blank=True)
    date_review = models.DateField(null=True)
    date_rejection = models.DateField(null=True)
    date_approved = models.DateField(null=True)
    interest = models.FloatField(default=0.01)
    information = models.TextField()
    status=models.TextField(default='AWAIT REVIEW')
    status_reason=models.TextField(null = True)
    def __str__(self):
        return self.Anggota.last_name
class PinjammanFormReason(forms.ModelForm):
    class Meta:
        model = Pinjamman
        fields = ['amount_borrowed','installment','information','status_reason']
        labels = {'status_reason':'Alasan Penolakan','amount_borrowed':'Nominal','installment':'Tenor(bulan)','information':'Tujuan'}
        def __str__(self):
            return str(self.amount_borrowed)

views.py (focused)
this is the logic if each button is clicked

@login_required(login_url='login/user')
def ActionPinjaman(request,pinjaman_id,anggota_id,status):
    s = SessionStore(session_key=request.session.session_key)
    u_admin = User.objects.get(id=s['_auth_user_id'])
    pinjaman =Pinjamman.objects.filter(id = int(pinjaman_id)).filter(Anggota_id=int(anggota_id)).all()[:1].get()
    a = Anggota.objects.get(id = anggota_id)
    u = User.objects.get(id = a.user_id)
    action_date = datetime.now()
    action_delta = action_date+timedelta(days=2)
    keputusan = action_delta.strftime('%Y-%m-%d')
    action_date_form = action_date.strftime('%Y-%m-%d')
    if request.method=='POST':
        if status=='UNDER REVIEW':
            ...
            return histori_peminjaman_admin(request)
        elif status=='APPROVED':
            ...
            return histori_peminjaman_admin(request)
        **elif status=='REJECTED':**
**            print('REJECT')**
**            return UpdateRejectReason(request,pinjaman_id,anggota_id,action_date_form)**

And updated form view:

@login_required(login_url='login/user')
def UpdateRejectReason(request,pinjaman_id,anggota_id,action_date_form):
    s = SessionStore(session_key=request.session.session_key)
    u_admin = User.objects.get(id=s['_auth_user_id'])
    a = Anggota.objects.get(id = anggota_id)
    u = User.objects.get(id = a.user_id)
    pinjaman =Pinjamman.objects.filter(id = int(pinjaman_id)).filter(Anggota_id=int(anggota_id)).all()[:1].get()
    print(pinjaman.id,pinjaman.Anggota_id,pinjaman.amount_borrowed)
    i = {'amount_borrowed': pinjaman.amount_borrowed, 
                   'installment': pinjaman.installment, 
                   'information': pinjaman.information,
                   'status':'REJECTED'
                   }
    if request.method == 'POST':
        form = PinjammanFormReason(request.POST,initial=i)
        print(form.is_valid())
        if form.is_valid():
            print('VALID')
            p.status_reason = form.cleaned_data['status_reason']
            p.save()
            notif = NotifikasiUser()
            notif.users=u
            notif.messages='Halo{} ,pinjamanmu dengan ID {} ditolak admin dengan alasan {}. Silahkan mencoba kembali pengajuan dengan melengkapi informasi yang diberikan pada alasan.'.format((a.first_name+' '+a.last_name),pinjaman_id,form.cleaned_data['status_reason'])
            notif_admin = NotifikasiUser()
            notif_admin.users= u_admin
            notif_admin.messages='Halo admin, pinjaman dengan ID {} telah ditolak pada {}'.format(pinjaman_id,action_date_form)
            notif.save()
            notif_admin.save()
            context={
            'u':request.user,
            "pinjaman":p
            }
            context={
                'u':request.user,
                'form':form
                }
            return histori_peminjaman_admin(request)
        else:
            field_errors = [ (field.label, field.errors) for field in form] 
            print(field_errors)
            context={
                'u':request.user,
                'form':form
                }
            return render (request,'allpages/pengajuan_reason.html',context)

And this is when the button is clicked

Please show the portion of your runserver log showing the requests being made when you’re submitting this form.

What does the url definition look like for the ActionPinjaman view?

this is the ActionPinjaman view url definition

urlpatterns = [
 ...
    path('admin/cek_peminjaman/<int:pinjaman_id>,<int:anggota_id>,<status>',views.ActionPinjaman,name='pinjaman_action'),
...
    path('jsi18n', JavaScriptCatalog.as_view(), name='js-catlog'),
    
]

Since the form is error with required part needed,
here is the error from the console:
[('Nominal', ['This field is required.']), ('Tenor(bulan)', ['This field is required.']), ('Tujuan', ['This field is required.']), ('Alasan Penolakan', ['This field is required.'])]
Generated from the else option on the view

I have found out the solution for this case but until now i still confused why the form is invalid.

Here i made some change in the else section if the is_valid() return false:

else:
            form = PinjammanFormReason(initial={'amount_borrowed': pinjaman.amount_borrowed, 
                   'installment': pinjaman.installment, 
                   'information': pinjaman.information,
                   'status_reason':'-'})
            context={
                'u':request.user,
                'form':form
                }
            return render (request,'allpages/pengajuan_reason.html',context)

and if is valid() instead read the whole form, i directly assign the value to the ‘Pinjamman’ object itself:

if form.is_valid():
            print('VALID')
            pinjaman.status='REJECTED'
            pinjaman.status_reason=form.cleaned_data['status_reason']
            pinjaman.date_rejection=action_date_form
            pinjaman.save()
            notif = NotifikasiUser()
            notif.users=u
            notif.messages='Halo{} ,pinjamanmu dengan ID {} ditolak admin dengan alasan {}. Silahkan mencoba kembali pengajuan dengan melengkapi informasi yang diberikan pada alasan.'.format((a.first_name+' '+a.last_name),pinjaman_id,form.cleaned_data['status_reason'])
            notif_admin = NotifikasiUser()
            notif_admin.users= u_admin
            notif_admin.messages='Halo admin, pinjaman dengan ID {} telah ditolak pada {}'.format(pinjaman_id,action_date_form)
            notif.save()
            notif_admin.save()
            context={
                'u':request.user,
                'form':form
                }
            return histori_peminjaman_admin(request)