Using get_absolute_url() [works in a for loop but not in a single href link]

I’m struggling with get_absolute_url(). I’m unsure what’s causing issues since elsewhere in another template the get_absolute_url() is functioning fine (it works in a for loop but not in a single href link). Probably also useful to note that there aren’t any errors when clicking, it’s just not loading the detail page of the person logged in. I thought since it’s not adding any uuid in the url when I click the link, it might have something to do with passing in the id somewhere in the template, however since the id is already being passed to the url in the models file and successfully works in other places I’m “thrown for a loop” (so to speak) as to what I’ve done wrong.

Help appreciated, and code excerpts below:

MODELS excerpt

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

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

URLS excerpt

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"), 
]

TEMPLATE THAT DOESN’T WORK excerpt
(note: I replicated the link in a separate static page just so I could focus on this alone before trying to add it anywhere more complicated)

    <h1>Static Page</h1>
    <a href="{{ profile.get_absolute_url }}">My Profile</a>

TEMPLATE THAT ALREADY WORKS excerpt

        {% 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 %}

Thanks in advance!

Please post the view that is rendering the template that is not working.

Oops, sorry about that:

VIEW excerpt

from django.views.generic import ListView, DetailView  

from .models import Profile

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

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

Which view is rendering the template that is not working? You’re showing two views here.

ProfileDetailView.

To elaborate, both the ProfileDetailView and the ProfileListView are currently already being used with get_absolute_url() successfully. What I was trying to do now was to add another additional link elsewhere to send the user directly to their own specific ‘profile_detail’ URL path / ProfileDetailView page / profiles/profile_detail.html template.

Maybe I’m struggling because the same view can’t be used twice in this manner? If that’s the case, can a template and URL path be pointed to twice by two separate views?

Hope that makes sense, and thanks again.

It’s generally easiest to discuss this in the context of the specific code. It’s rarely helpful to try and talk about this in the abstract when there is code available.

If you’re having problems with a view rendering the template, then I’d like to address that issue. Once we have a resolution, then we can discuss the reasons behind it.

To answer your questions in your second paragraph, there are no limitations on how you can use views, templates, and url paths.

So to clarify, you’re saying that you’re creating a link in ProfileDetailView to send you to ProfileDetailView? The same page that you’re currently looking at?

No, I’m creating a link in a separate template file to direct to the authenticated user’s specific ProfileDetailView. Specifically, the template file is the one showing <h1>Static Page</h1> in the original post, and it is a page that’s being used primarily just to learn how to do this before using what I’ve learned on a more confusing template with a lot of text.

So that’s what needs to be addressed.

We need to see the view that is rendering that template file.

Ohh, sorry, got it:

VIEW excerpt

from django.views.generic import TemplateView

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

Note: it’s named like an “About Page” but I’m currently treating it more like just a simple page to play with code before putting what I learn elsewhere.

Also, to summarize in case I’ve been explaining poorly: this “AboutPageView” loads like this:
Screen Shot 2023-05-22 at 6.23.34 AM
but the “My Profile” link here doesn’t send to the authenticated user’s ProfileDetailView as I thought it would using <a href="{{ profile.get_absolute_url }}">My Profile</a>.

PS: I’m not sure if this is helpful since it’s a slightly different topic, but in trying to make this work I also wondered if it was best practice to instead use <a href="{% url 'profile_detail' profile.id %}">My Profile</a>. When I tried that, I got this error when loading the page:

Reverse for ‘profile_detail’ with arguments ‘(’‘,)’ not found. 1 pattern(s) tried: [‘profiles/(?P[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\Z’]

This made me think I was passing in the id wrong so I also tried profile.pk and profile.uuid neither of which worked.

So the issue is that you’re not providing a profile object in the context to be rendered in the template.

Your template has:

That means that the template rendering engine is going to try and get an object named profile from the context, get the get_absolute_url function from that object, call it, and replace the {{ ... }} with the value returned by that function. (The reality is a bit more complex than that - see The Django template language | Django documentation | Django)

But your view:

doesn’t define any context. There’s no data being passed to the rendering engine, so Django doesn’t know what to do with that variable.

In overriding TemplateView, you’ll also want to override the get_context_data method within that view, to retrieve the desired Profile object and put it in the context.

Thanks, Ken. I’ve been reading docs and trying to understand; I’m still stuck but here’s what I think has been the closest edit so far:

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

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["my_profile"] = Profile.objects.get(id=Profile.id)
        return context

Does it seem like I’m going in the right direction?

Relatedly, I think I’m doing something wrong with the line that has “Profile.objects.get(id=Profile.id)” - I’m new to programming; am I making an obvious mistake here? I feel like I have to refer to the authenticated user explicitly or something so that it gets their specific uuid, but I’m not sure how and am struggling to find a clear answer.

Here’s the Profile model again for reference:

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

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

You’re almost there.

You have identified the right area on this, just a couple more points to straighten out.

First, in your template you have:

That means that the entry in the context must be named “profile” - that’s the name that the template is looking for.

However, here:

you’re creating an entry in the context named “my_profile”, which is not what the template is looking for.

Either (or both) the template or the code must be changed - it doesn’t really matter what you call this variable - it’s just that the two must match each other.

Now addressing this same line:

context["my_profile"] = Profile.objects.get(id=Profile.id)
                                               ^^^^^^^^^^

You are correct, this isn’t right. “Profile” is a reference to the class, so a reference to “Profile.id” is a reference to the field itself and not its content.

And Django makes this very easy to do in the CBVs (Class-based views).

In an view being accessed by an authenticated user, Django stores the request in the object, accessible by the name self.request. One of the objects inside self.request is a reference to the User making the request, with the name user.

This means that anywhere within your view, you can access the User object for the user making the request as self.object.user.

Finally, your Profile class is related to CustomUser by a one-to-one field, making the reverse relationship available by using the lower-case version of the class name, profile.

This means you can access the current user’s profile as self.request.user.profile. No specific query is necessary.

Thanks, Ken - with your help, I was able to get it to work although I wouldn’t say I fully understand. I have some clarifying questions if it’s not too much trouble:

QUESTION 1

Are you referring to an HTTP request when you say “request” here? Is the following rephrase of what you described accurate?

  1. authenticated user accesses a view by visiting a webpage
  2. the HTTP request sent when they visit the webpage is stored in an object named self.request
  3. the self.request object includes another object within it named user which stores the authenticated user’s information (and “information” here means all of the attributes that are defined in the project’s user model are stored?)

I tried looking at the Django code to see what you’re describing but it’s so advanced and there are hundreds of places that say self.request so I got a little lost :sweat_smile:

QUESTION 2
For these two phrases:

and

Am I understanding correctly that “request” in the second quote is substituting the “object” in the first quote (because Django stores the request in an object)? In other words, is it true that “object” would always get replaced by the object you’re referring to / you wouldn’t really ever use self.object.user without putting an actual object’s name instead of the word “object”?

QUESTION 3
If this were a view where the class wasn’t a one-to-one field with CustomUser, would that mean using something that looks like this for that same line?

        context["my_profile"] = Profile.objects.get(id=self.request.user.id)

Honestly, I have more questions, but I think including any more than the above probably starts getting a little too far away from the original topic of the thread.

Thank you again; I appreciate it.

Your rephrase here is spot-on. The link that I posted for that name (self.request) is a link to the most brief description in the docs as to what’s happening here.

I would change the wording slightly here in the interest of precision. You wrote:

I would consider it somewhat more accurate that the self.request object contains a reference to another object. That other object is an object of type User and is given the name user. That user object can contain references to yet more objects.

(Is this difference in wording important? Yes, under some circumstances. In many cases you can ignore the difference, but you should always be aware of it.)

Oops - typo. That should have been self.request.user. I had objects on the brain.

Yes, that would do it.

No worries, we’re here to help.