How to add an "Edit Page" admin link on a Django template

How to add an "Edit Page" admin link on a Django template

There are two ways to do this: 1) Add a hard-coded admin link in the template or 2) Build a link in the views.py and put it in a context variable so you can use it in multiple apps or models. Bonus: we'll make a modification for putting the edit link in an included HTML file with styles to make the button pop out of the content and follow you as you scroll. We'll also make sure this edit button only ever appears for admin users that are at least marked as staff.



Let's take a look:

This is the hard-coded snippet you can put in your template HTML file:

{% if user.is_staff %}
<a href="{% url 'admin:blog_blog_change' blog.id %}">Edit Page</a>
{% endif %}

Breakdown

Where did this 'admin:blog_blog_change' come from? This was constructed from my app and model labels like so:

{{ app_label }}_{{ model_name }}_{{ adminpagetype }}

The token, adminpagetype above is placeholder for a reserved keyword that Django has so you can choose which type of admin page or action you want for the link. These are your options:

Admin Page Type Keyword
Change list changelist
Add add
History history
Delete delete
Change change
The Django admin site | Django documentation | Django
Source for Admin link patterns, Page Types and their keywords

Lastly, where is this blog.id coming from in the URL code here?

{% url 'admin:blog_blog_change' blog.id %}

"blog" is whatever context variable you defined that contains your page content. For example, in your views.py, it may look something like this:

class blogDetail(request):
    blog = get_object_or_404(Blog, slug=slug)
    ...
    context = {'blog':blog, ...}
    
    return render(request, template_name, context)

In this case, since we've gotten a blog object, it knows what its ID is, which means that we can use blog.id in the template, and more specifically, we can build an admin link for the specific Blog post that we're viewing.

Now, all you have to do is add that first snippet to your template and the "Edit Page" link will appear if you are logged into the admin and you're at least a "staff" member:

Clicking on the "Edit Page" link takes you right to the correct admin change form page:

Note that In my example, I've modified the admin theme styles, but this is the actual changeform that correlates to the blog post I was on when I clicked "Edit Page."

Make this edit feature extensible and dynamic

It's always better if you can make your code work in multiple places, rather than just hard-coding everything. Let's make a quick modification so we can add an "Edit Page" link to any template we want across our entire Django site.

In my templates folder, I've already been overriding some of the admin templates and styles, so I'm going to put a new file called "edit-page-tool.html" inside the mymainproject/templates/admin/

You can name your file whatever you'd like, and you don't have to put it in an admin-specific template folder. However, you will want to put it somewhere that Django knows where to look for it. Ideally, you've added a certain template folder to the DIRS in your settings.py file like this:

TEMPLATES = [
    {
        ...
        'DIRS': [
            'mymainproject/templates'
        ],
        ...
    }

Inside the new HTML file (edit-page-tool.html), we're going to paste in our snippet from before, but modify the link's href slightly, like this:

{% if user.is_staff %}
    <a href="{{ change_url }}">Edit Page</a>
{% endif %}

This means that we're no longer hard-coding the admin URL like we did above. Now we've defined this "change_url" context variable—so let's hurry and edit our views.py to hook up this new context variable.

views.py:

...
from django.urls import reverse

def blogDetail(request, slug):
    blog = get_object_or_404(Blog, slug=slug)

    change_url = reverse('admin:blog_blog_change', args=(blog.id,))
    ...
    context = {'content':blog_post, 'change_url': change_url}
	...

Here, we've added a change_url that holds the admin link language we were using above. Then we're adding that change_url to part of the context that gets passed when the page gets viewed.

Now, all we have to do is include our HTML snippet in the template:

blog-post.html

...
{% include 'admin/edit-page-tool.html' %}
...

You will need to change the include path/file-name.html if you used a different name or template directory than I've shown. Viola! The edit link now appears on the blog template...AND we can throw that include line on any other template we want. We just need to make sure we add the appropriate change_url in the correlated views.py.

For example, if we had an app called "Portfolio" and a model called "Project," we would add this to the views.py in the Portfolio app:

def portfolioDetail(request, slug):
    portfolio_detail = get_object_or_404(Project, slug=slug)

    change_url= reverse('admin:portfolio_project_change', args=(portfolio_detail.id,))
     ...
    context = {'content':portfolio_detail, 'change_url': change_url}
Example of a rendered page that shows the "Edit Page" link where I placed it in the template.

Making it even fancier

We're going to edit our included file to make it more fancy, safe, and easier to spot as a site editor.

First, we'll surround our edit link with an if statement, checking that we actually have a change_url available, as an extra precaution if, for example, we forget to set it in the views.py, it won't break the page.

edit-page-tool.html

{% if user.is_staff %}
    {% if change_url %}
    <a href="{{ change_url }}">Edit Page</a>
    {% endif %}
{% endif %}

Next, let's add some HTML structure and CSS to put this edit link at a consistent spot so that we don't have to hunt for it every time we want to edit our page.

edit-page-tool.html

{% if user.is_staff %}
    <div class="admin-tools">
        {% if change_url %}
        <a href="{{ change_url }}">Edit Page</a>
        {% endif %}
    </div>

    <style>
        .admin-tools {
            background-color: white;
            padding: 1rem;
            position: fixed;
            top: 50%;
            transform: translateY(-50%);
            right:0;
            box-shadow: 1px 3px 10px rgb(0 0 0 / 0.2);
            z-index: 10000;
        }
    </style>
{% endif %}

This turns our link into a floating button that we can see more easily, even as we scroll:

Feel free to keep modifying the design of your button until you're satisfied. I have chosen to keep the <style> inside the {% if user.is_staff %} block so that site visitors don't ever have to load in those extra styles, and it keeps it self-contained, so I know right where to go for a style update.

Also, since this link is now fixed to the side, I would recommend placing the include snippet at the bottom of your template. I'm putting mine just above the footer area of the templates (inside my {% block content %} but at the very bottom so that it's not nested too much into other HTML elements).

Troubleshooting common errors:

If you followed method 2, making the edit link dynamic, you may encounter this error:

Value after * must be an iterable, not int

You likely have missed or deleted the comma after the object id in the views.py change_url link (see args=...):

change_url = reverse('admin:portfolio_project_change', args=(portfolio_detail.id,))

Another pitfall of method 2, forgetting to import the reverse function.

Error: name 'reverse' is not defined

Simply add this line to the top of your views.py file:

from django.urls import reverse