Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.`
like image 432
weeheavy Avatar asked Feb 17 '14 11:02

weeheavy


2 Answers

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!

like image 53
weeheavy Avatar answered Oct 02 '22 01:10

weeheavy


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 %}
like image 31
Daniel Roseman Avatar answered Oct 02 '22 01:10

Daniel Roseman