Problem updating django form with action = "{%url 'link_name' %}"

I am hoping my question makes sense.

I am trying to update a django form which I have in a function based view.

Slight twist. This form is rendered on a template handled by another function.

For some reason, the title field entered in the existing form is erased when submitting the updated form.

I thought the code, somewhere, was re-creating a blank form when calling the update function. However, other fields (where a FK or a M2M is involved are actually updated an not erased after submit. As an example, if I add another user to the m2m relationship, the previous user will still be recorded on the updated form.

I tried different solutions, below is my lastest iteration. There is no error message appearing, so there must be some logic somewhere that is causing the issue.

What I am trying to achieve:
When a request.user clicks the “add button” on template, the userprofile_id is added to the model object: this part works.

The problem is that the text that comes with this object model is blank.

model

class Voucher(models.Model):
    user = models.ManyToManyField(User, blank=True)
    venue = models.ForeignKey(Venue, blank=True, null=True, related_name="vouchervenues", on_delete=models.CASCADE)
    title = models.TextField('voucher title', blank=True)

url

path('venue_loyalty_card/<userprofile_id>', views.venue_loyalty_card,name="venue-loyalty-card"),
path('allocate_voucher/<userprofile_id>/<voucher_id>',views.allocate_voucher, name ="allocate-voucher"),

form

class VoucherForm(ModelForm):
    class Meta:
        model = Voucher
        fields = ('title')

views

def venue_loyalty_card(request, userprofile_id):

    venue = UserProfile.objects.filter(user=request.user).values('venue')
    venue_vouchers_available = Voucher.objects.filter(venue=request.user.userprofile.venue)
 
    form=LoyaltyCardForm()
    voucherform=VoucherForm()
    updateform=VoucherForm()

    return render(request,"main/account/venue_loyalty_card.html",{'venue':venue,'venue_vouchers_available':venue_vouchers_available,'updateform':updateform})

def allocate_voucher(request, userprofile_id,voucher_id):
    url = request.META.get('HTTP_REFERER')
    venue = UserProfile.objects.filter(user=request.user).values('venue')
    userprofile = get_object_or_404(UserProfile, pk=userprofile_id)
    voucher = get_object_or_404(Voucher, pk=voucher_id)

    if request.method == "POST":
        updateform = VoucherForm(request.POST, instance = voucher)
        if updateform.is_valid():
            data = updateform.save(commit=False)

            data.save()
            updateform.save_m2m()
            current_voucher_instance = get_object_or_404(Voucher, id=data.id)
            current_voucher_instance.user.add(userprofile.id)
            return redirect(url)
        else:
            print(updateform.errors)
    else:
        updateform = VoucherForm()

    return redirect('venue-loyalty-card',{'userprofile':userprofile,'voucher':voucher,'venue':venue})

template (main/account/venue_loyalty_card.html)


    {%for voucher in venue_vouchers_available%}
{{voucher.id}}
            {{voucher}}
            <form action="{%url 'allocate-voucher' userprofile.id voucher.id%}" method="POST">
            {% csrf_token %}
            <input type="Submit"  value="Add" class="btn btn-primary custom-btn">  
            </form>

    {%endfor%}

I’m a little confused by a couple things.

First, you have:

but I don’t see a field named ‘terms’ in the Voucher model.

Second, you wrote:

What “text” are you referring to?

Your form is submitting (as part of the url), the userprofile.id and voucher.id. There is no other text within that form.

Nor do I see you rendering any form within your template.

When faced with situations like this, it’s generally helpful to visually inspect the html as it has been rendered in the browser to verify it, along with verifying that the data you’re expecting to post is being sent from the browser to the server. Both of these options are available by using your browser’s developer tools.

Hi there,

Sorry for the confusion, I tried to keep the code as minimal as possible so I didnt include terms in the model as the situation is the same with title and forgot to remove it from the forms.py. Just edited this bit, so it only show title

The text I am referring to is the text coming out of the title field. I will update my original post to change text with title.

The form submitting as part of the url is an exitsing form with the following fields already entered:

  1. user(s)
  2. venue
  3. title

I havent added the function that deals with the creation of the form as I didnt think it was necessary and was worried to overcomplicate my question.

There is basically 3 functions:

  1. Function 1 - this one deals with the rendering of the template and already includes a form that has another pupose;
  2. Function 2 - this one allows the user to create a VoucherForm from the template;
  3. Function 3 - this one allows the user to update the VoucherForm created by Function 2.

The reason why there is no room for adding any text in Function 3 is because the user is not meant to update the text and can only add users to the VoucherForm.

Which is why I thought if I call updateform = VoucherForm(request.POST, instance = voucher) then I would be able to keep the existing title from VoucherForm created in Function 2 and simply add a new userprofile_id to the exsting voucher_id.

However currently when updating VoucherForm, anyting entered in title from Function 2 is erased when updating from Function 3.

Does it make more sense? (thanks for the quick reply!)

Forgot to cover this point. The problem is not the rendering, it’s what is changed in the database (maybe I should have said that in my post!).

Let’s say:

  1. I create VoucherForm_A - assign user_1, a venue and a title
  2. I want to update VoucherForm_A - I decide to assign user_2, in addition to user_1 and click submit.

The title field is now deleted in the database.

The problem I am trying to solve is that I want to keep title as it was created when I created Voucher_A. Even if I update it later on, title should remain unchanged.

What am I doing wrong in the logic that erases the title value when submitting updated form?

Except it appears to me that the problem with what’s not getting changed in the database is caused by what’s not being rendered in the template.

A form being submitted only submits the data within the form tag.

You are rendering:

There’s nothing in there to submit as post data, other than the csrf_token.

My initial reasoning for only rendering the submit button in the form (which given your response makes me think was wrong) was because I didnt want to leave the option to change anything except add another user.

By clicking on submit, request.user adds userprofile_id.

Which works fine. and for some reason the venue field also remains unchanged.

Before posting my question, I did add a title field in the template to see if it was pre-populated with the exitsing data from the model object. Obvioulsy it was empty. This is what made me think I was doing something wrong in the views as I wasnt calling the original text contained in title.

I have run out of ideas on how to do this. In my lastest attempt I tried to use initial in the views but not luck either. (see below)

def allocate_voucher(request, userprofile_id,voucher_id):
    url = request.META.get('HTTP_REFERER')
    venue = UserProfile.objects.filter(user=request.user).values('venue')
    userprofile = get_object_or_404(UserProfile, pk=userprofile_id)
    voucher = get_object_or_404(Voucher, pk=voucher_id)

    if request.method == "POST":
        updateform = VoucherForm(request.POST, instance = voucher)
        if updateform.is_valid():
            data = updateform.save(commit=False)
            data.title = updateform.cleaned_data['title']
            data.save()
            updateform.save_m2m()
            current_voucher_instance = get_object_or_404(Voucher, id=data.id)
            current_voucher_instance.user.add(userprofile.id)
            return redirect(url)
        else:
            print(updateform.errors)
    else:
        a = {'title': voucher.title}
        updateform = VoucherForm(initial = a)

    return redirect('venue-loyalty-card',{'userprofile':userprofile,'voucher':voucher,'venue':venue})

Something else I have been wondering is if I should render the initial model object content in the “parent function”, which is the one handling the template. The only one time I managed to populate something in the field was by adding

updateform=VoucherForm(instance = Voucher)

(ideally I would tie it up with the voucher_id, but couldnt find anyway to do that as voucher_id is in the other function.)

Which rendered : <django.db.models.query_utils.DeferredAttribute object at 0x0000016CFB7684C0>

Which is far from the result expected, but wondered whether I should consider it as a first step.

Any pointers would be appreciated!

(thanks for sticking around by the way!)

Ok, I’m confused here. I need to take a step back and try to understand what you’re looking to do here.

You’re rendering a page:

  • What forms are on this page?
  • What do you want to have happen when this page is submitted?

Keep in mind that any submit is going to cause the view to be called and a new page to be rendered. Unless you get into AJAX-style interfaces, there’s no facility to send part of a page and keep the same page.

As a result, I don’t understand the reasoning behind having these different functions, or what the “flow” is supposed to be from a UI/UX perspective.

It’s also going to be helpful if you post the complete views, templates, and url definitions here.

Hi there,

Thanks for not giving up!

We have 3 forms:

  1. Form 1 - adds points to a customer loyalty card; (this one works)
  2. Form 2 - Creates voucher; (this one works)
  3. Form 3 - Update voucher (this one is a problem)

Instead of having to redirect the user to 3 different pages, I want all three forms to be handled on the same template.

I am sure I understand the question. The page (template) is an interface/dashboard for the user to see data or take various actions (through the forms).

This part I am not sure I understand what you mean. But definitely not planning on using AJAX.

What I am trying to do is to have various possible action on the same page, so the user doesnt have to navigate through numerous pages.

I think my problem is a lot simpler than I make it sound. I just cant find a way to keep the data in a model object when I update it. Or to say otherwise, I seem to delete the data in the field of the object when I want to update another specific field.

To give you an idea, I put a story board below with a few screen shots.

We have page (with userprofile_id in url that looks like this):

This is a voucher I create through the dashboard (51 is the id)

Now if I decide to click “add” to add userprofile_id to it, the user is successfully added but the title “voucher created for Django Forum” is gone.

image

Does it make more sense?

Good - I’m with you so far.

Good - I’ll have more to say about this a little farther down.

Ok, let’s do some “level-setting” here.

For all browser - server interactions that don’t involve JavaScript / AJAX, the sequence of events looks something like this:

  • Browser issues a GET request to the server

  • The server returns a page.

    • That page may contain one or more forms, with one or more submit buttons.
  • A person using the browser may fill out a form and click a submit button.

    • The form being submitted is the form containing the submit button being clicked. (That’s the data between the <form> and </form> tags.)
    • The only data being submitted by clicking the submit button are those fields between the same <form> and </form> tags. No other data is submitted by that button.
    • The data being submitted is formatted as HTML POST data.
  • When a form is submitted, the web application receives and processes that data. It must then return HTML data as a response.

    • That response may be a new page, or it may be a redirect to a different url.

    • In either case, the former contents of the page are replaced by the response. All data in all form fields are discarded by the browser.

Now, the standard Django pattern is that the view that handles the submit checks the data being submitted.

If it passes the validation tests, it’s then processed. (Perhaps saving data to a model) The view then sends a redirect to some url to retrieve the new page.

If the form fails the validation tests, that same form is re-rendered, but with the error messages added to the page.

Note: All this is covered in much more detail in the docs. At a minimum, you should review the full page of information at Working with forms | Django documentation | Django

So, what you need to do is evaluate your code keeping all this in mind.

Look at what’s happening for each form that you submit.

Look at what’s being submitted by the browser.

Look at what your code is doing with that data.

Unfortunately, I can’t be more specific than this because there’s too much missing from what you’re trying to describe. (Either that, or what’s missing is actually missing from your code and is what’s causing the problems.) That’s what I was trying to get at with my response at Problem updating django form with action = "{%url 'link_name' %}" - #5 by KenWhitesell. You’ve got a <form> tag, but you’re not submitting any data with it. However, you’re trying to process data from that submission in your view.

Hi there,

sorry for the late response. I solved the problem, but I am not sure this is the most effective method.

I simply ended up adding a hidden filed in the form. (see below)

I would be surprised there is not a better way to simply handle this in the view. Would you have any pointers? (thank you for the help by the way!)

            <form action="{%url 'allocate-voucher' userprofile.id voucher.id%}" method="POST">
            {% csrf_token %}
           
            <input id="title" type="hidden" name="title" value="{{ voucher.title }}">


            <input type="Submit"  value="Add" class="btn btn-primary custom-btn">  
            </form>

Yes, there’s a much better way of handling this.

In your allocate_voucher view you have:

See that second line? (The reference to cleaned_data)

The specific issue here is that you have that line in this view.

If updateform doesn’t contain a field named title, then there is no cleaned data for that field.

As a result, you’re assigning a blank value to that field. The work-around that you have created is to add that field to the data being submitted.

The bigger picture is that you might have more fundamental issues with how your application/views/forms/templates are structured, but there’s not enough of the code/templates provided here to make an accurate judgement on that.

I keep forgetting to thanking you for your answer! So there it is

I havent had the time to do the last bit, but you helped question the logic and find the answer by myself :slight_smile: