Create a blog application with django, part 5: write django views

A Django view is just a Python function that receives a web request and returns a web response. All the logic to return the desired response goes inside the view.

First, we will create our application views, then we will define a URL pattern for each view, and finally, we will create HTML templates to render the data generated by the views. Each view will render a template passing variables to it and will return an HTTP response with the rendered output.

Creating list and detail views

Let's start by creating a view to display the list of posts. Edit the blog/views.py file of your blog application and make it look like this:

# blog/views.py

from django.shortcuts import render, get_object_or_404

from .models import Post

def post_list(request):
   posts = Post.published.all()
   return render(request,
                 'blog/post_list.html',
                 {'posts': posts})

The post_list view takes the request object as the only argument. Remember that this argument is required by all views. In this view, we are retrieving all the posts with the published status using the published manager we created previously.

The render() function takes the request object, the template path, and the context variables to render the given template. It returns an HttpResponse object with the rendered text (normally, HTML code).

Let's create a second view to display a single post. Add the following function to the blog/views.py file:

def post_detail(request, year, month, day, post):
   post = get_object_or_404(Post, slug=post,
                                  status='published',
                                  publish__year=year,
                                  publish__month=month,
                                  publish__day=day)
   return render(request,
                 'blog/post_detail.html',
                 {'post': post})

This is the post detail view. This view takes year, month, day, and post arguments to retrieve a published post with the given slug and date. Note that when we created the Post model, we added the unique_for_date argument to the slug field. This way, we ensure that there will be only one post with a slug for a given date, and thus, we can retrieve single posts using date and slug. In the detail view, we use the get_object_or_404() function to retrieve the desired post. This function retrieves the object that matches the given arguments or raises HTTP 404 (not found) if the object doesn’t exist. Finally, we use the render() function to render the retrieved post using a template.

Adding URL patterns for your views

URL patterns allow you to map URLs to views. A URL pattern is composed of a string pattern, a view, and, optionally, a name that allows you to name the URL project-wide. Django runs through each URL pattern and stops at the first one that matches the requested URL. Then, Django imports the view of the matching URL pattern and executes it, passing an instance of the HttpRequest class and keyword or positional arguments.

Create an urls.py file in the directory of the blog application and add the following lines to it:

# blog/urls.py

from django.urls import path

from . import views

app_name = 'blog'

urlpatterns = [
   # post views
   path('', views.post_list, name='post_list'),
   path('<int:year>/<int:month>/<int:day>/<slug:post>/',
        views.post_detail,
        name='post_detail'),
]

In the preceding code, we defined an application namespace with the app_name variable. This allows us to organize URLs by application and use the name when referring to them. We defined two different patterns using the path() function. The first URL pattern doesn't take any arguments and is mapped to the post_list view. The second pattern takes the following four arguments and is mapped to the post_detail view:

  • year: Requires an integer
  • month: Requires an integer
  • day: Requires an integer
  • post: Can be composed of words and hyphens

We use angle brackets to capture the values from the URL. Any value specified in the URL pattern as <parameter> is captured as a string. We use path converters, such as <int:year>, to specifically match and return an integer and <slug:post> to specifically match a slug (a string consisting of ASCII letters or numbers, plus the hyphen and underscore characters). You can see all path converters provided by Django here .

Now, you have to include the URL patterns of the blog application in the main URL patterns of the project. Edit the mysite/urls.py file make it look like this:

# mysite/urls.py

from django.urls import path, include
from django.contrib import admin

urlpatterns = [

   path('blog/', include('blog.urls', namespace='blog')),
   path('admin/', admin.site.urls),

]

The new URL pattern defined with include refers to the URL patterns defined in the blog application so that they are included under the blog/ path. We include these patterns under the namespace blog. Namespaces have to be unique across your entire project. Later, we will refer to our blog URLs easily by including the namespace, building them, for example, blog:post_list and blog:post_detail. You can learn more about URL namespaces here .

Canonical URLs for models

You can use the post_detail URL that you have defined in the preceding section to build the canonical URL for Post objects. The convention in Django is to add a get_absolute_url() method to the model that returns the canonical URL of the object. For this method, we will use the reverse() method that allows you to build URLs by their name and passing optional parameters. Edit the blog/models.py file and add the following:

# blog/models.py

from django.urls import reverse

class Post(models.Model):
   # ...
   def get_absolute_url(self):

       args=[
           self.publish.year,
           self.publish.month,
           self.publish.day,
           self.slug,
           ]
       return reverse('blog:post_detail', args=args)

We will use the get_absolute_url() method in our templates to link to specific posts.

So far so good, the blog/models.py file should now look like this:

# blog/models.py

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse


class PublishedManager(models.Manager):
   def get_queryset(self):
       # Displays only published post
       return super().get_queryset().filter(status='published')

class Post(models.Model):
   STATUS_CHOICES = (
       ('draft', 'Draft'),
       ('published', 'Published'),
   )
   title = models.CharField(max_length=250)
   slug = models.SlugField(max_length=250,
                           unique_for_date='publish')
   author = models.ForeignKey(User,
                              on_delete=models.CASCADE,
                              related_name='blog_posts')
   body = models.TextField()
   publish = models.DateTimeField(default=timezone.now)
   created = models.DateTimeField(auto_now_add=True)
   updated = models.DateTimeField(auto_now=True)
   status = models.CharField(max_length=10,
                             choices=STATUS_CHOICES,
                             default='draft')

   objects = models.Manager() # The default manager.
   published = PublishedManager() # The custom manager.

   class Meta:
       ordering = ('-publish',)

   def __str__(self):
       return self.title

   def get_absolute_url(self):
       args=[
           self.publish.year,
           self.publish.month,
           self.publish.day,
           self.slug,
           ]
       return reverse('blog:post_detail', args=args)

That's it for now. The next tutorial is to add HTML templates to display posts.


If you like my content, please consider buying me a coffee.
Thank you for your support!

Related posts