{% url %} template tag, get_absolute_url(), and views

GENERAL CONTEXT

  • My understanding of this related post and the get_absolute_url() Django docs section is essentially that get_absolute_url() is useful and preferred because using it 1) allows templates to be more generic, 2) reduces the number of places to update if a change is needed / check if a problem arises, and 3) is used in the django admin app etc. to make links work.
  • I recently posted this question about using get_absolute_url() where get_absolute_url() is on a model and the main problem I had was that I was missing the context data in the right view.

EXAMPLE PROJECT STRUCTURE CONTEXT
Note: I used excerpts where possible to avoid clutter
I have a _base.html file that includes a header. One functioning link in the header’s dropdown menu is:
<li><a class="dropdown-item" href="{% url 'profile_list' %}">Profile List</a></li>

I have a profiles app that includes:

Profile model using get_absolute_url()

class Profile(models.Model):
    id = models.UUIDField( 
        primary_key=True,
        default=uuid.uuid4,
        editable=False)
    user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)

    def get_absolute_url(self):
        return reverse("profile_detail", args=[str(self.id)])

URLs

from django.urls import path

from .views import ProfileListView, ProfileDetailView

urlpatterns = [
    path("", ProfileListView.as_view(), name="profile_list"),
    path("<uuid:pk>", ProfileDetailView.as_view(), name="profile_detail"), 
]

View

from django.views.generic import ListView, UpdateView  

from .models import Profile


class ProfileListView(ListView):
    model = Profile
    context_object_name = "profile_list"
    template_name = "profiles/profile_list.html"


class ProfileDetailView(UpdateView):
    model = Profile
    context_object_name = "profile"  
    template_name = "profiles/profile_detail.html"

Template - profiles/profile_list.html

{% for profile in profile_list %}
            <div class="card mb-3" style="max-width: 540px;">
                <div class="row g-0">
                    <div class="col-md-4">
                        <img src="{% static 'images/testimage.jpg' %}" class="img-fluid rounded-start" alt="Profile Image">
                    </div>
                    <div class="col-md-8">
                        <div class="card-body">
                            <h5 class="card-title">{{ profile.user.username }}</h5>
                            <p class="card-text"><a href="{{ profile.get_absolute_url }}">{{ profile.user.username }}</a></p>
                            <br />
                            <p class="card-text"><small class="text-body-secondary">{{ profile.date_modified }}</small></p>
                        </div>
                    </div>
                </div>
            </div>
        {% endfor %}

Template - profiles/profile_detail.html

<h1>{{ profile.user.username }}'s Profile</h1>

I have a pages app that includes static pages, one of which is the following:

URL

from django.urls import path

from .views import AboutPageView

urlpatterns = [
    path("about/", AboutPageView.as_view(), name="about"),
]

View - Created with help from this post about using get_absolute_url()

class AboutPageView(TemplateView): 
    template_name = "pages/about.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["my_profile"] = self.request.user.profile
        return context

Template

<a href="{{ my_profile.get_absolute_url }}">My Profile</a>

QUESTIONS

1a. Why does the link for the Profile List in the _base.html header menu not need context data in a _base.html view file to work? Is it because the {% url %} tag can be used without passing anything except the URL name in?
1b. Is get_absolute_url() preferred for template links like this (aka: non-detail page related links) too? If yes, how would you update this profile list link as an example? Would adding the below to the Profile model work? / Can there be more than one get_absolute_url() per model class?

    def get_absolute_url(self):
        return reverse("profile_list")

2a. If I wanted to add a link to the _base.html header menu that links to the authenticated user’s profile, it seems like I need to create a _base.html view file; is that correct?
2b. If yes, is it normal to just have a _base.html view file kind of free floating in the main folder of the project (like in the same folder as the manage.py file?). In other words, I’m used to having the views files be within the app folders but since the _base file isn’t app-specific I want to know if there’s a certain place a related view file normally goes. If no, how/where would you put the context you need for the link to work?

Because the url being rendered doesn’t require any parameters.

This one doesn’t require any data to render:

But this one requires a context variable to populate the field named pk:

No. The get_absolute_url function is intended to return a url for a specific instance of an object - in which case you need to specify which instance you want referenced.

No, that is not correct. You have one view to render the entire page, the identified template and the base template it extends (as well as any other templates it may include). Your base template has access to the context of the template being rendered.

Thanks, Ken.

If I’m understanding correctly, in practice you would only ever have a maximum of one get_absolute_url function in each model which would use self.id (or whatever parameter is required by the URL) to complete the URLs for all of the individual instances of all the objects within the model. When the template tag uses just the general model name, like in {{ profile.get_absolute_url }}, it can work only because the template tag is within a for loop (or something that iterates through several of the objects). This is possible because the self.id in that model’s get_absolute_url function can be used to refer to any/all of the instances.

In order to direct to one specific object in the model, you set a context data variable in that page’s views file to equal the parameter you need to complete the URL of the specific object that you want. Then, in the template (using this example), the my_profile part of {{ my_profile.get_absolute_url }} is the parameter information needed in the URL which is used by the get_absolute_url function to replace the self.id with the specific id associated with, in this case, self.request.user.profile.

Is all of that accurate? I’m most unsure about that last sentence.

Got it. So, I updated the _base.html file to include this link in the header menu:
<li><a class="dropdown-item" href="{{ my_profile.get_absolute_url }}">My Profile</a></li>
and this works if I go to the about URL specifically (since it has the context data), however it didn’t work on any other page. Given what you noted above, I tried adding this:

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["my_profile"] = self.request.user.profile
        return context

to the other views as well. Doing this did allow the “My Profile” link to work in the header menu on each page.

Adding the same context data snippet to each view seems repetitive, so I’m wondering: is it normal to do the above? If no, how do folks normally do this less repetitively?

You can only have one function of a given name in a class. You may not have multiple get_absolute_url functions in the same class.

For clarity - the profile in that tag, is not a “general model name”, it’s a reference to a Python variable. That variable can be set any number of ways - being the iteration variable in a loop is just one of them.

I’m not entirely comfortable with the way you’ve worded this.

A Python class, such as a Django Model, is a “cookie cutter” if you will. It’s a type of template from which you will create an indeterminate number of instances. Each of those instances has access to its own variables by referencing the variable named self. (Note: The usage of self is a convention - it’s not actually required to be self. You’ll find some old Python articles that use s as a shortened version.)

The my_profile part of that tag, {{ my_profile.get_absolute_url }}, is the key to the context dict. The value for that key is supposed to be an object with an attribute named get_absolute_url. Note that there’s no requirement that get_absolute_url be a function. See the docs at The Django template language | Django documentation | Django

So specifically, it has nothing to do with the url itself! From the perspective of the template, my_profile.get_absolute_url is any one of the types of data identified by the link above.

You have a couple different options. First, you can create your own context processor to add data to the context of every render operation. This is the general process used in the common case.

Or, in this specific case (and ones similar to it), you can take advantage of the existing auth context processor and leverage the fact that you already have a reference to a user object in your template.

Thanks, Ken; your clarifications throughout have all been incredibly helpful. I think I’m starting to put it together.

Regarding the last section, I successfully used a context processor to avoid editing each view to make the My Profile menu link work. Just in case it’s helpful for any potential future readers, this was what it looks like:

def profile_associated_with_authenticated_user(request):
    my_profile = request.user.profile
    return {'my_profile': my_profile}

Plus adding this to the project settings.py file: 'context_processors.profile_associated_with_authenticated_user',

Though that’s functioning, for the sake of learning, might you also please elaborate on this:

Specifically, I see that 'django.contrib.auth.context_processors.auth', is already in the project settings.py file. I guess I’m unclear about which template you were referring to and how that would help avoid repeating the get_context_data snippet in all the views. In other words, even if a reference to a user object is in one of my templates, how does this help me bridge to the need for the context to be available for all of the pages across the site (needed because the link was in the header menu)?

What this means is that in every template you render, you have user available as a variable. You don’t have to add it to every context in your views and you don’t need to add any other context processor to make it available.

If you already have user available, then you don’t need to do anything else to get the profile. It’s already there as user.profile.

Ohh, I see now & tried it out. Thank you again; I’ve learned a lot and really appreciate your help.