URLs with no prefixes

I wonder if it is possible to access all pages and its detail pages without any extra prefix.

Example:

‘’’
/vegetables
/carrot --> (detail page for vegetables)
/fruits
/apple --> (detail page for fruits)
‘’’
and different views should be used for fruits and vegetables.

Sample URL patterns,

#urls.py
urlpatterns = [
path(’’, views.index, name=‘index’),
path(‘fruits/’, views.all_fruits, name=‘fruits’),
path(‘vegetables/’, views.all_vegetables, name=‘vegetables’),
path(‘slug:slug/’, views.fruit_detail, name=‘fruit’),
path(‘slug:slug/’, views.vegatable_detail, name=‘vegetable’),
]
Predictably; when trying to access the vegetable detail page, gives an error because of using fruit’s views.

Thanks in advance for your feedbacks.

As the urls.py file is written currently, the error behavior makes sense. Any slug is going to match the first path('<slug:slug>', views.fruit_detail, name='fruit') and stop trying to match after that.

A path must have a unique pattern to disambiguate between which view you want the request to be handled by.

One possible way to work around this would be to create a wrapper view that can delegate to the appropriate detail view based on the slug. A rough sketch of that might look like:

# urls.py
...
path('<slug:slug>', views.produce_dispatcher, name='produce_dispatcher'),
...

# views.py
def produce_dispatcher(request, slug):
    if Fruit.objects.filter(slug=slug).exists():
        return fruit_detail(request, slug)
    if Vegetable.objects.filter(slug=slug).exists():
        return vegetable_detail(request, slug)

    raise Http404()

That would do some extra queries, but I think it would give you the interface you are looking for.

3 Likes

Matt’s answer is likely the most sensible, but as a bit of fun at least, you could use a custom path converter to wrap the Fruit lookup, raising a ValueError from to_python in order to say Try the next pattern.

Daniel Hepper has a post and talk on this topic: https://consideratecode.com/2018/05/11/the-hidden-powers-of-custom-django-2-0-path-converters/

2 Likes

That’s a neat alternative. Thanks for sharing, @carltongibson!

1 Like

I’m looking for a more efficient method for this.
Now, I have to use it for many detail views and that means a lot of queries.

I have an idea to solve this with two queries, but I couldn’t find a complete solution.

Possible Roadmap that I think;

  • Create new page model with slug field
  • Associate with all related models.
  • Write a main view and get slug from the page object with single query.
  • Call related view with slug using a single query.

Draft codes to accomplish these,

#page.models
class Page(models.Model):
    slug = models.SlugField(blank=True, unique=True)
    fruit = models.OneToOneField(Fruit, on_delete=models.SET_NULL, null=True, blank=True)
    vegetable = models.OneToOneField(Vegetable, on_delete=models.SET_NULL, null=True, blank=True)
    #... other fields
    # Association can be different. Generic Foreign key could be used.

    
#main.views
def page(request, slug):
  page = Page.objects.get(slug=slug)
  # need to call related view with single query using slug info ???
  #...
  
  
def fruit(request, slug):
  #...
  return render(request, "fruit-detail.html", context)
 
  
def vegetable(request, slug): 
  #...
  return render(request, "vegetable-detail.html", context)
    

#main.urls
urlpatterns = [
  #...
  path('<slug:slug>/', views.page, name='page'), ]

How can I reach an efficent solution?
Thanks in advance.

I have a question about performance.
Is it possible doing same thing with better performance?

#page.models
class Page(models.Model):
    slug = models.SlugField(blank=True, unique=True)
    fruit = models.OneToOneField(Fruit, on_delete=models.SET_NULL, null=True, blank=True)
    vegetable = models.OneToOneField(Vegetable, on_delete=models.SET_NULL, null=True, blank=True)
    #... other fields more than 15
#main.views
def page(request, slug):
    page = Page.objects.get(slug=slug)
    if page.fruit:
      return fruit(request, slug)
    if page.vegetable:
      return vegetable(request, slug)
   #...it continues like this more than 15 times

It looks like you need to use select_related to avoid the extra queries when accessing the related objects.

1 Like