Displaying both slug and ID in URL, but route by ID only in Django
Asked Answered
G

2

8

What I'm trying to achieve is: my News app should display a slug, but only query the article by ID in the form of /news/24/this-is-the-slug

Unfortunately I'm getting a NoReverseMatch: Reverse for 'news_detail' with arguments '('',)' and keyword arguments '{}' not found. when trying to browse an article. The URL generated in the template looks correct as stated above (I can confirm this by doing a search via Haystack, which delivers the correct URL).

models.py

class News(models.Model):
    id = models.IntegerField(primary_key=True, editable=False)
    category = models.CharField(max_length=50L)
    title = models.CharField(max_length=200L)
    rss_summary = models.TextField(max_length=2000L)
    body_text = models.TextField(max_length=5000L)
    post_date = models.DateTimeField()
    prettyurl = models.SlugField(max_length=100L)

    class Meta:
        db_table = 'news'

    def __unicode__(self):
        return self.title

    def get_absolute_url(self):
        return urlresolvers.reverse('news_detail', kwargs={'pk': self.id, 'slug': self.prettyurl })

urls.py

urlpatterns = patterns(
    '',
    url(
        r'^$',
        view=views.NewsListView.as_view(),
        name='news_index'),
    url(
        r'^(?P<pk>\d+)/',
        view=views.NewsDetailView.as_view(),
        name='news_detail'),
    url(
        r'^(?P<pk>\d+)/(?P<slug>[-\w]+)/$',
        view=views.NewsDetailView.as_view(),
        name='news_detail'),
    url(
        r'^archive/$',
        view=views.NewsArchiveIndexView.as_view(),
        name="archive_month"),
    [... more unrelated urls ...]

views.py

class NewsDetailView(DetailView):
    #name='news_detail'),
    model = News
    context_object_name = 'news'
    #slug_url_kwarg = 'prettyurl'
    #slug_field = 'prettyurl'
    template_name = 'news/detail.html'

Template

`<p><a href="{% url 'news_detail' news.slug %}">Permalink</a> for this article.`
Gloucestershire answered 17/2, 2014 at 11:29 Comment(1)
if you don't need the slug, but only the id, then there's no reason to pass it as an argument to the view, right?Diarchy
G
4

Thanks @Daniel Roseman and @yuvi. With your help I managed to solve my problem by defining the URL pattern to this:

r'^(?P<pk>\d+)(?:/(?P<slug>[\w\d-]+))?/$',

Which allows all my wanted forms of

  • news/nn
  • news/nn/
  • news/nn/a-slug
  • news/nn/a-slug/

In the template, I use

{% url 'news_detail' news.id news.prettyurl %}

Which shows the fourth version in the listing above.

Thanks again!

Gloucestershire answered 18/2, 2014 at 20:13 Comment(5)
you can also use {% url 'news_detail' news.id news.title|slugify %}Tutelary
but with this solution you can get the content with /news/24/this-is-the-slug or with /news/24/dadadsdasdasTutelary
Yes, but that was my intention. ID is for querying, slug is only eye-candy.Gloucestershire
@Gloucestershire what does the colon mean in the regular expression? I trying to do the same thing and when I unittest it I get the NoReverseMatch as well. At the moment, it works when you include the slugs, but not when no slugs are included.Tripp
You could also have (?:/(?P<slug>.*))?/$ to allow other characters that the user might put in the slug like #@%$ or empty spaces etc...Teraterai
P
1

I'm not quite sure why you're bothering to capture the slug at all. Rather than having a named group in the URL pattern, you could just have one that ignores everything after the PK:

r'^(?P<pk>\d+)/.*',

which would work just as well whether or not you passed the slug, so you could then get rid of your duplicated patterns.

There are two basic problems with what you have, though. Firstly, even though you state you want to actually match only on PK, you don't even pass the PK to the URL, just the slug. Secondly, even the slug appears to be blank, as the error message states (the args variable is just '').

You should instead pass the actual PK:

{% url 'news_detail' news.pk %}
Pharisaic answered 17/2, 2014 at 17:5 Comment(1)
While I agree that he doesn't need to catch the slug, the url pattern /.* doesn't ignore the rest. In fact its the opposite, it will catch almost anything you throw at it, providing you have a /number/ preceding it. This kind of pattern should generally be avoided (I think so), but when needed, it should appear as the last url pattern, to avoid the possibility of it receiving requests meant for similar patterns that are more specific (i.e., a pattern like r'^(?P<pk>\d+)/(?P<some_other_num>\d+)/$ will be swallowed if it doesn't appear first)Diarchy

© 2022 - 2024 — McMap. All rights reserved.