Profile picture of Hunter Kehoe
Hunter Kehoe Web Developer

Redirect After Saving Django Model

When I'm using Django, sometimes I want to link from a custom page to the built-in Model Edit page, make some changes and save them to the database, and redirect back to my page. Django doesn't do this out of the box...

Connecting Custom Page to Django's Model Edit Page

I will often create a custom page to show data from an instance of a DB model. Or I might create a page that has a table with data from multiple instances of a model.

In each row of the table, I might want to include a link that will let me edit the data for that instance. I will usually use Django's built-in url syntax in my template to dynamically generate a link that takes me straight to the built-in Model Edit page.

Here's an example block of code that I might include in a template file to create a table with data from multiple model instances. This is showing a table full of contact messages I have received through the site:

    <table class="table table-bordered">
        <tr>
          <th>Name</th>
          <th>Email</th>
          <th>Date</th>
          <th>Subject</th>
          <th>Message</th>
          <th>Responded</th>
          <th>Update</th>
        </tr>
        {% for contact in contacts %}
          <tr>
            <td>{{ contact.name }}</td>
            <td>{{ contact.email }}</td>
            <td>{{ contact.date }}</td>
            <td>{{ contact.subject }}</td>
            <td>{{ contact.message }}</td>
            <td>{{ contact.responded }}</td>
            <td><a href="{% url 'admin:contacts_contact_change' contact.id %}">Update</a></td>
          </tr>
        {% endfor %}
      </table>

I set up the table headers first. Then I have a for-loop that will go through my model instances one at a time.

For each instance, I put the data from the contact into a separate column in the table. The second to last column shows whether or not I have responded to the message yet.

Once I send off an email to the user, I want to change the responded value to true for that specific instance. In the last column, I include a link that takes me straight to the Model Edit page that is included as part of Django's Admin Tools.

Dynamic URL

There are three main parts to the dynamic URL: {% url 'admin:contacts_contact_change' contact.id %}. The part before the colon (admin) specifies the namespace of the app in the Django project you're trying to target.

In this case, I want to create a URL that is defined in the admin app that is included with Django by default.

The section after the colon (contacts_contact_change) can be broken up into three different sections of its own.

The first word refers to the app that includes the model you want to edit. In this case, I have an app in the Django project called contacts. This is where I keep all the code to manage any contact forms on the website.

Inside that app, I have a model called Contact. That matches the second word in this section. Inside that model I have all of the fields that we are showing in the database defined (e.g., name, email, date, etc.).

The last word in this section depends on what we want to do to the model. We can add a new instance, delete an existing instance, or in this case change an existing instance.

In order for Django to know which instance we want to edit, we include the instance ID as the last section of the url command (contact.id).

For more information about these dynamic URLs, check out the Django documentation.

Getting Back to the Table

The code I showed above works great for getting us from my custom table showing data from each instance of a DB model to the Model Edit page for a specific instance. However, after I make a change on the Edit Page and click "SAVE", I am left on the Model Edit page instead of going back to the table that I came from.

We fix this by editing the admin.py file in the contacts app that I mentioned earlier.

Generally, my admin.py file will look something like this, nice and simple.

from django.contrib import admin
from .models import Contact

admin.site.register(Contact)

We need to make a few changes, though, so that we get redirected to the page we came from after making a change to the instance on the Admin page.

This is the new admin.py file:

from django.contrib import admin
from django.shortcuts import redirect
from .models import Contact

@admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
    def response_change(self, request, obj):
        if request.GET.get('next'):
            return redirect(request.GET.get('next'))
        else:
            return super().response_change(request, obj)

What we're doing is overriding the response that the Admin page creates after a model change event happens. Once we click "SAVE" after editing an instance of the Contact model, we want Django to check if there is a next parameter included in the URL.

If there is, Django will redirect us to that URL. If there isn't, Django will just return the normal response (i.e., reload the same Admin Edit page with the new changes).

Now that we have the redirect code in place, we need to adjust the dynamic URL that we create in our table. Here's the new code:

    <table class="table table-bordered">
        <tr>
          <th>Name</th>
          <th>Email</th>
          <th>Date</th>
          <th>Subject</th>
          <th>Message</th>
          <th>Responded</th>
          <th>Update</th>
        </tr>
        {% for contact in contacts %}
          <tr>
            <td>{{ contact.name }}</td>
            <td>{{ contact.email }}</td>
            <td>{{ contact.date }}</td>
            <td>{{ contact.subject }}</td>
            <td>{{ contact.message }}</td>
            <td>{{ contact.responded }}</td>
            <td><a href="{% url 'admin:contacts_contact_change' contact.id %}?next={% url 'contacts:table' %}">Update</a></td>
          </tr>
        {% endfor %}
      </table>

Notice that the only thing we changed was adding ?next={% url 'contacts.table' %} to the end of the href section of the <a> tag.

Now, when we click the link to "Update" the instance in the table, we will be taken to the Admin Edit page for that instance of the Contact model, but if you look in the URL, now you'll see the next parameter that tells Django where to take us after we save the changes.

After making a change to model instance, once you click "SAVE", you'll be redirected to the page specified in the next parameter! In my case, this takes me right back to the custom table I was working with before.

Note: the next parameter can include a hard-coded redirect URL, such as ?next=/contacts/table_view. I decided to use another dynamic URL that will automatically update in case I make any changes to the URL in the future. This dynamic URL will always point to the table URL that is defined in the urls.py file in the contacts app in this project.

Conclusion

Learning this trick has made my web apps so much more convenient to use. It allows me to quickly go from my custom page, to Django's built-in Admin page, back to my custom page. I hope this will help improve your workflows, too! Let me know if you know of a different way to do this or if you run into any problems implementing this in your own project!

Subscribe to receive updates!