How to use URL regexp to match exact date of a post in Django?

Viewed 113

I'm developing a website and blog for myself using Django. I'm using Python 3.8 and Django 3.0 and I've been having a lot of trouble trying to include the date of post creation in the URL. While I was finally able to get my URL to look something like: "mywebsite.com/blog/2020/mm/dd/.html", I am not getting a 404 if I change any part of the date.

For example if I manually change the URL to: mywebsite.com/blog/2021/mm/dd/.html, the webpage still loads without presenting an error. Can someone help me figure out where I can edit the code to correct this issue? Something where I can issue a 404 if the URL isn't exactly what is pulled from the datetime field? My models.py, views.py, and URLs.py are attached below:

Models.py

class Blog(models.Model):
    title = models.CharField(max_length=500, help_text='Title of the Blog Post')
    author = models.ForeignKey(Author, on_delete=models.SET_NULL, null=True)
    **created_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)
    content = models.TextField(help_text='Content of the Blog Post')
    topic = models.ManyToManyField(Topic, help_text='Topic of this Blog Post')

slug = models.SlugField(
    null=False, 
    unique=True,
    max_length=500,
    editable=False,
)

class Meta:
    ordering = ['-created_on']

def __str__(self):
    return self.title

def get_absolute_url(self):
    kwargs = {
    'year': self.**created_on.strftime("%Y"),
    'month': self.**created_on.strftime("%m"),
    'day': self.**created_on.strftime("%d"),
    'slug': self.slug,
    }
    return reverse('blog-detail', kwargs=kwargs)

def save(self, *args, **kwargs):
    self.slug = slugify(self.title, allow_unicode=True)
    super(Blog, self).save(*args, **kwargs)

Views.py

class BlogListView(ListView):
    model = Blog
    template_name = 'blog_list.html'

class BlogDetailView(DetailView):
    model = Blog
    context_name = 'blog-detail'
    **year_url_kwarg = 'year'
    slug_url_kwarg = 'slug'
    **month_url_kwarg = 'month'
    **day_url_kwarg = 'day'

URLs.py

urlpatterns = [
    path('index/',views.index, name='index'),
    **re_path(r'^blog/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)$', views.BlogDetailView.as_view(), name='blog-detail'),
    re_path(r'^blog/$',views.BlogListView.as_view(), name='blog_main'),

]

I put ** in the code to draw attention to relevant fields. Thank you for taking the time to reading through and trying to help me with this!

1 Answers

You can filter by overriding the .get_queryset(…) method [Django-doc]:

class BlogDetailView(DetailView):
    model = Blog
    context_name = 'blog-detail'

    def get_queryset(self, *args, **kwargs):
        return super().get_queryset(*args, **kwargs).filter(
            created_on__year=self.kwargs['year'],
            created_on__month=self.kwargs['month'],
            created_on__day=self.kwargs['day']
        )

Note: I would advise using an name for context_name that is a valid Python identifier, so not something with a hyphen. While it might work in the template engine, it is possible that later they slightly change the "grammar" of the Django template engine.

Related