Admin Site's "VIEW ON SITE" URL Incorrect

I recently discovered Django Admin’s VIEW ON SITE function, which is pretty cool. I defined get_absolute_url in my model:

def get_absolute_url(self):
    return reverse('product-detail', kwargs={'slug': self.slug})

When I hover the link in the admin, I see this URL: http://localhost:8000/adminr/10/4/ and when I click it, it takes me to here (after redirect?): http//localhost:8000/product/<name> (note the missing colon in the URL).

I can define view_on_site in my ProductAdmin and get things to work:

def view_on_site(self, obj):
    return reverse('product-detail', kwargs={'slug': obj.slug})

Assuming this just uses the “site” I’ve defined in the admin already (for domain), which is http://localhost:8000. What am I missing? Is this a bug or am I doing something wrong?

1 Like

Hi!

First of all, I wasn’t able to reproduce what you said about the missing colon in the URL.

My two questions are:

  1. Does the URL without the colon works?
  2. What’s the output of reverse('product-detail', kwargs={'slug': self.slug})?

PD: The URL generated by the admin follows this format:

Thanks for the reply @marcorichetta !

To answer your questions:

  1. No, since it’s an invalid URL, the address can’t be found in the browser.
  2. The reverse of product-detail is /product/<product_slug>, as the view_on_site method in the ProductAdmin seems to fix my issue.

Here’s a quick test:

>>> from products.models import Product
>>> p = Product(name="Bob", slug='bobby')
>>> print(p.slug)
bobby
>>> p.get_absolute_url()
'/product/bobby'

Could it be related to my definition of site in the admin (http://localhost:8000)? Or maybe a redirect issue in the browser?

Side note: if I copy the link from VIEW ON SITE hyperlink (right click copy link location), it appears correct. I can then test with wget to see the redirect (abbreviated output below):

user@host $ wget --spider http://localhost:8000/adminr/10/5/
Connecting to localhost (localhost)|127.0.0.1|:8000... connected.
HTTP request sent, awaiting response... 302 Found
Location: /adminlogin/?next=/adminr/10/5/ [following]
--2020-10-02 08:40:45--  http://localhost:8000/adminlogin/?next=/adminr/10/5/
Connecting to localhost (localhost)|127.0.0.1|:8000... connected.
HTTP request sent, awaiting response... 200 OK

The missing colon thing only happens when I actually click the hyperlink in the browser. I tried in Firefox and private mode Chrome (eg, no cache or history saved) with same results. I’m on Ubuntu 18.04.

Maybe this is the cause of the problem, because Django uses the Sites framework to do redirection and to calculate the object url

The shortcut view (django.contrib.contenttypes.views.shortcut) uses the domain of the current Site object when calculating an object’s URL.

How did you set it up?

I think it’s my setup of the Sites Framework that’s causing the problem. I defined my site Domain name and Display name fields with http://localhost:8000 (by replacing example.com) in the Django Admin. This has been my method for local development for many years, and I’ve never had problems. Of course, I only recently started using the VIEW ON SITE shortcut to the objects.

After testing this hypothesis by changing those fields to localhost:8000, the VIEW ON SITE link now works correctly. I’m still unsure why my definition of view_on_site in the ProductAdmin works but the default admin method doesn’t. Maybe the redirect code is choking on the extra http:// in the Domain Name field.

Perhaps I’ll try and submit a patch to validate that field doesn’t accept the URL “scheme”. Not sure if this is the way Django intended it but might be worth looking into.

Thanks @marcorichetta for your help with this.

Looking at the Sites framework documentation, it seems pretty clear to me that the domain name setting is just the domain name, which does not include the scheme.

When you define your own view_on_site django uses your function to return the url.

If you don’t, it will try to create the url via this function.

@KenWhitesell
Agreed. But since Django is pretty good at validating fields, I’m surprised it doesn’t catch this. Looking at the validation code, it only checks for whitespace in the string.

def _simple_domain_name_validator(value):
    """
    Validate that the given value contains no whitespaces to prevent common
    typos.
    """
    checks = ((s in value) for s in string.whitespace)
    if any(checks):
        raise ValidationError(
            _("The domain name cannot contain any spaces or tabs."),
            code='invalid',
        )