Home Web Development Django Project Blueprints

Django Project Blueprints

By Asad Jibran Ahmed
books-svg-icon Book
eBook $35.99 $24.99
Print $43.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $35.99 $24.99
Print $43.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Blueblog – a Blogging Platform
About this book
Django is a high-level web framework that eases the creation of complex, database-driven websites. It emphasizes on the reusability and pluggability of components, rapid development, and the principle of don't repeat yourself. It lets you build high-performing, elegant web applications quickly. There are several Django tutorials available online, which take as many shortcuts as possible, but leave you wondering how you can adapt them to your own needs. This guide takes the opposite approach by demonstrating how to work around common problems and client requests, without skipping the important details. If you have built a few Django projects and are on the lookout for a guide to get you past the basics and to solve modern development tasks, this is your book. Seven unique projects will take you through the development process from scratch, leaving no stone unturned. In the first two projects, you will learn everything from adding ranking and voting capabilities to your App to building a multiuser blog platform with a unique twist. The third project tackles APIs with Django and walks us through building a Nagios-inspired infrastructure monitoring system. And that is just the start! The other projects deal with customizing the Django admin to create a CMS for your clients, translating your web applications to multiple languages, and using the Elasticsearch search server with Django to create a high performing e-commerce web site. The seventh chapter includes a surprise usage of Django, and we dive deep into the internals of Django to create something exciting! When you're done, you'll have consistent patterns and techniques that you can build on for many projects to come.
Publication date:
May 2016
Publisher
Packt
Pages
264
ISBN
9781783985425

 

Chapter 1. Blueblog – a Blogging Platform

We are going to start with a simple blogging platform in Django. In recent years, Django has emerged as one of the clear leaders in web frameworks. When most people decide to start using a web framework, their searches lead them to either Ruby on Rails (RoR) or Django. Both are mature, stable, and extensively used. It appears that the decision to use one or the other depends mostly on which programming language you're familiar with. Rubyists go with RoR, Pythonistas go with Django. In terms of features, both can be used to achieve the same results, although they have different approaches to how things are done.

One of the most popular blogging platforms these days is Medium, widely used by a number of high profile bloggers. Its popularity stems from its elegant theme, and simple-to-use interface. I'll walk you through creating a similar application in Django, with a few surprise features that most blogging platforms don't have. This will give you a taste of things to come, and show you just how versatile Django can be.

Before starting any software development project, it's a good idea to have a rough roadmap of what we would like to achieve. Here's a list of features that our blogging platform will have:

  • User should be able to register an account and create their blogs

  • Users should be able to tweak the settings of their blogs

  • There should be simple interface for users to create and edit blog posts

  • Users should be able to share their blog posts on other blogs on the platform

I know this seems like a lot of work, but Django comes with a couple of contrib packages that speed up our work considerably.

 

The contrib packages


The contrib packages are a part of Django that contain some very useful applications that the Django developers decided should be shipped with Django. The included applications provide an impressive set of features, including some that we'll be using in this application:

  • Admin is a full featured CMS that can be used to manage the content of a Django site. The Admin application is an important reason for the popularity of Django. We'll use this to provide an interface for site administrators to moderate and manage the data in our application

  • Auth provides user registration and authentication without requiring us to do any work. We'll be using this module to allow users to sign up, sign in, and manage their profiles in our application

Note

There are a lot more goodies in the contrib module. I suggest you take a look at the complete list at https://docs.djangoproject.com/en/stable/ref/contrib/#contrib-packages.

I usually end up using at least three of the contrib packages in all my Django projects. They provide often-required features like user registration and management, and free you to work on the core parts of your project, providing a solid foundation to build upon.

 

Setting up our development environment


For this first chapter, I'll go into some details about setting up the development environment. For later chapters, I'll only be providing minimal instructions. For further details about how I setup the development environment and why, take a look at Appendix, Development Environment Setup Details and Debugging Techniques.

Let's start by creating the directory structure for our project, setting up the virtual environment and configuring some base Django settings that need to be set up in every project. Let's call our blogging platform BlueBlog.

Note

Detailed explanations of the steps you're about to see are given in Appendix, Development Environment Setup Details and Debugging Techniques. Please refer to that if you're unsure about why we're doing something or what a particular command does.

To start a new project, you need to first open up your terminal program. In Mac OS X, it is the built-in terminal. In Linux, the terminal is named separately for each distribution, but you should not have trouble finding it; try searching your program list for the word terminal and something relevant should show up. In Windows, the terminal program is called the command line. You'll need to start the relevant program depending on your operating system.

Note

If you are using the Windows operating system, you will need to slightly modify the commands shown in the book. Please refer to the Developing on Windows section of Appendix, Development Environment Setup Details and Debugging Techniques for details.

Open the relevant terminal program for your operating system and start by creating the directory structure for our project; cd (ing) into the root project directory using the commands shown below:

> mkdir –p blueblog
> cd blueblog

Next let's create the virtual environment, install Django, and start our project:

> pyvenv blueblogEnv
> source blueblogEnv/bin/activate
> pip install django
> django-admin.py startproject blueblog src

With that out of the way, we're ready to start developing our blogging platform.

Database settings

Open up the settings found at $PROJECT_DIR/src/blueblog/settings.py in your favorite editor and make sure that the DATABASES settings variable matches this:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

In order to initialize the database file, run the following commands:

> cd src
> python manage.py migrate

Static files settings

The last step in setting up our development environment, is configuring the staticfiles contrib application. The staticfiles application provides a number of features that make it easy to manage the static files (css, images, JavaScript) of your projects. While our usage will be minimal, you should look at the Django documentation for staticfiles in further detail, since it is used quite heavily in most real world Django projects. You can find the documentation at https://docs.djangoproject.com/en/stable/howto/static-files/.

In order to set up the staticfiles application we have to configure a few settings in the settings.py file. First, make sure that django.contrib.staticfiles is added to the INSTALLED_APPS. Django should have done that by default.

Next, set STATIC_URL to whatever URL you want your static files to be served from. I usually leave this to the default value, /static/. This is the URL that Django will put in your templates when you use the static template tag to get the path to a static file.

A base template

Next let's setup a base template that all the other templates in our application will inherit from. I prefer to have templates that are used by more than one application of a project in a directory named templates in the project source folder. To set that up, add os.path.join(BASE_DIR, 'templates') to the DIRS array of the TEMPLATES config dictionary in the settings file, and then create a directory named templates in $PROJECT_ROOT/src. Next, using your favorite text editor, create a file named base.html in the new folder with the following content:

<html>
<head>
    <title>BlueBlog</title>
</head>
<body>
    {% block content %}
    {% endblock %}
</body>
</html>

Much like Python classes inheriting from other classes, Django templates can also inherit from other templates. And just like Python classes can have functions overridden by their subclasses, Django templates can also define blocks that children templates can override. Our base.html template provides one block for inheriting templates to override, called content.

The reason for using template inheritance is code reuse. We should put HTML that we want to be visible on every page of our site, such as headers, footers, copyright notices, meta tags, and so on, in the base template. Then, any template inheriting from it will automatically get all that common HTML included automatically, and we will only need to override the HTML code for the block we want to customize. You'll see this principal of creating and overriding blocks in base templates used throughout the projects in this book.

 

User accounts


With the database setup out of the way, let's start creating our application. If you remember, the first thing on our list of features is to allow users to register accounts on our site. As I've mentioned before, we'll be using the auth package from the Django contrib packages to provide user account features.

In order to use the auth package, we'll need to add it our INSTALLED_APPS list in the settings file (found at $PROJECT_ROOT/src/blueblog/settings.py). In the settings file, find the line defining INSTALLED_APPS and make sure that the string django.contrib.auth is part of the list. It should be by default, but for some reason if it's not there, add it manually.

You'll see that Django has included the auth package and couple of other contrib applications to the list by default. A new Django project includes these applications by default because almost all Django projects end up using these.

Note

If you need to add the auth application to the list, remember to use quotes to surround the application name.

We also need to make sure that the MIDDLEWARE_CLASSES list contains django.contrib.sessions.middleware.SessionMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware, and django.contrib.auth.middleware.SessionAuthenticationMiddleware. These middleware classes give us access to the logged in user in our views, and also make sure that if I change the password for my account, I'm logged out from all other devices that I previously logged on to.

As you learn more about the various contrib applications and their purpose, you can start removing any that you know you won't need in your project. Now, let's add the URLs, views and templates that allow the users to register with our application.

A user accounts app

In order to create the various views, URLs, and templates related to user accounts, we'll start a new application. To do so, type the following in your command line:

> python manage.py startapp accounts

This should create a new accounts folder inside the src folder. We'll add code that deals with user accounts in files found inside this folder. To let Django know that we want to use this application in our project, add the application name (accounts) to the INSTALLED_APPS setting variable; making sure to surround it in quotes.

Account registration

The first feature we will work on is user registration. Let's start by writing the code for the registration view inside accounts/views.py. Make sure that the contents of views.py match what is shown here:

from django.contrib.auth.forms import UserCreationForm
from django.core.urlresolvers import reverse
from django.views.generic import CreateView


class UserRegistrationView(CreateView):
    form_class = UserCreationForm
    template_name = 'user_registration.html'

    def get_success_url(self):
        return reverse('home')

I'll explain what each line of this code is doing in a bit. But first, I'd like you to get to a state where you can register a new user and see for yourself how the flow works. Next, we'll create the template for this view. In order to create the template, you first need to create a new folder called templates inside the accounts folder. The name of the folder is important, since Django automatically searches for templates in folders of that name. To create this folder, just type the following command:

> mkdir accounts/templates

Next, create a new file called user_registration.html inside the templates folder and type in the code shown below:

{% extends "base.html" %}

{% block content %}
<h1>Create New User</h1>
<form action="" method="post">{% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Create Account" />
</form>
{% endblock %}

Finally, remove the existing code in blueblog/urls.py and replace it with this:

from django.conf.urls import include
from django.conf.urls import url
from django.contrib import admin
from django.views.generic import TemplateView
from accounts.views import UserRegistrationView

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^$', TemplateView.as_view(template_name='base.html'), name='home'),
    url(r'^new-user/$', UserRegistrationView.as_view(), name='user_registration'),
]

That's all the code we need to get user registration in our project! Let's do a quick demonstration. Run the development server by typing the following command:

> python manage.py runser
ver

In your browser, visit http://127.0.0.1:8000/new-user/ and you'll see a user registration form. Fill that in, and click submit. You'll be taken to a blank page on successful registration. If there are some errors the form will be shown again with the appropriate error messages. Let's verify that our new account was indeed created in our database.

For the next step, we will need to have an administrator account. The Django auth contrib application can assign permissions to user accounts. The user with the highest level of permission is called the super user. The super user account has free reign over the application and can perform any administrator actions. To create a super user account, run this command:

> python manage.py createsuperuser

Note

Since you already have the runserver command running in your terminal, you will need to quit it first by pressing Ctrl + C in the terminal. You can then run the createsuperuser command in the same terminal. After running the createsuperuser command, you'll need to start the runserver command again to browse the site.

If you want to keep the runserver command running and run the createsuperuser command in a new terminal window, you will need to make sure you activate the virtual environment for this application by running the same source blueblogEnv/bin/activate command that we ran earlier when we created our new project.

After you have created the account visit http://127.0.0.1:8000/admin/ and log in with the admin account. You will see a link titled Users. Click that and you should see a list of users registered in our app. It will include the user you just created.

Congrats! In most other frameworks, getting to this point with a working user registration feature would take a lot more effort. Django, with it's batteries included approach, allows us to do the same with a minimum of effort.

Next, I'll explain what each line of code that you wrote does.

Generic views

Here's the code for the user registration view again:

class UserRegistrationView(CreateView):
    form_class = UserCreationForm
    template_name = 'user_registration.html'

    def get_success_url(self):
        return reverse('home')

Our view is pretty short for something that does such a lot of work. That's because instead of writing code from scratch to handle all the work, we use one of the most useful features of Django, generic views. Generic views are base classes included with Django that provide functionality commonly required by a lot of web apps. The power of generic views comes from the ability to customize them to a great degree with ease.

Note

You can read more about Django generic views in the documentation available at https://docs.djangoproject.com/en/stable/topics/class-based-views/.

Here, we're using the CreateView generic view. This generic view can display a ModelForm using a template and on submission can either redisplay the page with errors if the form data was invalid or call the save method on the form and redirect the user to a configurable URL. The CreateView can be configured in a number of ways.

If you want a ModelForm to be created automatically from some Django model, just set the model attribute to the model class, and the form will be generated automatically from the fields of the model. If you want the form only show certain fields from the model, use the fields attribute to list the fields you want, exactly like you'd do when using a ModelForm.

In our case, instead of having a ModelForm generated automatically, we're providing one of our own; UserCreationForm. We do this by setting the form_class attribute on the view. This form, which is part of the auth contrib app, provides the fields and a save method that can be used to create a new user. As we start developing more complicated applications in later chapter, you'll see that this theme of composing solutions from small reusable parts provided by Django is a common practice in Django web app development, and in my opinion is one of the best features of the framework.

Finally, we define a get_success_url function that does a simple reverse URL and returns the generated URL. The CreateView calls this function to get URL to redirect the user to when a valid form is submitted and saved successfully. To get something up and running quickly, we left out a real success page and just redirected the user to a blank page. We'll fix this later.

Template and URLs

The template, which extends the base template we created earlier simply displays the form passed to it by the CreateView using the form.as_p method, which you might have seen in the simple Django projects you may have worked on before.

The urls.py file is a bit more interesting. You should be familiar with most of it, the parts where we include the admin site URLs and the part where we assign our view a URL. It's the usage of TemplateView that I want to explain here.

Like the CreateView, the TemplateView is another generic view provided to us by Django. As the name suggests, this view can render and display a template to the user. It has a number of customization options. The most important one is template_name, which tells it which template to render and display to the user.

We could have created another view class that subclassed the TemplateView, and customized it by setting attributes and overriding functions like we did for our registration view. But I wanted to show you another method of using a generic view in Django. If you only need to customize some basic parameters of a generic view; in this case we only wanted to set the template_name parameter of the view, you can just pass the values as key=value pairs as function keyword arguments to the as_view method of the class when including it in the urls.py file. Here, we pass the template name which the view renders when the user access it's URL. Since we just needed a placeholder URL to redirect the user to, we simply use the blank base.html template.

Tip

This technique of customizing generic views by passing key/value pairs only makes sense when you're interested in customizing very basic attributes, like we do here. In case you want more complicated customizations, I advice you subclass the view, otherwise you will quickly get messy code that is difficult to maintain.

Login and logout

With registration out of the way, let's write code to provide users with the ability to log in and log out. To start, the user needs some way to go to the login and registration pages from any page on the site. To do this, we'll need to add header links to our template. This is the perfect opportunity to demonstrate how template inheritance can lead to much cleaner and less code in our templates.

Add the following lines right after the body tag in our base.html file:

{% block header %}
<ul>
    <li><a href="">Login</a></li>
    <li><a href="">Logout</a></li>
    <li><a href="{% url "user_registration"%}">Register Account</a></li>
</ul>
{% endblock %}

If you open the home page for our site now (at http://127.0.0.1:8000/), you should see that we now have three links on what was previously a blank page. It should look similar to the following screenshot:

Click on the Register Account link. You'll see the registration form we had before, and the same three links again. Note how we only added those links to the base.html template. But since the user registration template extends the base template, it got those links without any effort on our part. This is where template inheritance really shines.

You might have noticed that the href for the login/logout links is empty. Let's start with the login part.

The login view

Let's define the URL first. In blueblog/urls.py import the login view from the auth app:

from django.contrib.auth.views import login

Next, add this to the urlpatterns list:

url(r'^login/$', login, {'template_name': 'login.html'}, name='login'),

Then create a new file inside accounts/templates called login.html. Put in the following content:

{% extends "base.html" %}

{% block content %}
<h1>Login</h1>
<form action="{% url "login" %}" method="post">{% csrf_token %}
    {{ form.as_p }}

    <input type="hidden" name="next" value="{{ next }}" />
    <input type="submit" value="Submit" />
</form>
{% endblock %}

Finally, open up blueblog/settings.py and add the following line to the end of the file:

LOGIN_REDIRECT_URL = '/'

Let's go over what we've done here. First, notice that instead of creating our own code to handle the login feature, we used the view provided by the auth app. We import it using from django.contrib.auth.views import login. Next, we associate it with the login/URL. If you remember the user registration part, we passed the template name to the home page view as a keyword parameter in the as_view() function. That approach is used for class-based views. For old-style view functions, we can pass a dictionary to the url function that is passed as keyword arguments to the view. Here, we use the template we created in login.html.

If you look at the documentation for the login view (https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.views.login), you'll see that on successfully logging in, it redirects the user to settings.LOGIN_REDIRECT_URL. By default, this setting has a value of /accounts/profile/. Since we don't have such a URL defined, we change the setting to point to our home page URL instead.

Next, let's define the logout view.

The logout view

In blueblog/urls.py import the logout view using from django.contrib.auth.views import logout and add the following to the urlpatterns list:

url(r'^logout/$', logout, {'next_page': '/login/'}, name='logout'),

And that's it. The logout view doesn't need a template; it just needs to be configured with a URL to redirect the user to after login them out. We just redirect the user back to the login page.

Navigation links

Having added the login/logout view, we need to make the links we added in our navigation menu earlier take the user to those views. Change the list of links we had in templates/base.html to the following:

<ul>
    {% if request.user.is_authenticated %}
    <li><a href="{% url "logout" %}">Logout</a></li>
    {% else %}
    <li><a href="{% url "login" %}">Login</a></li>
    <li><a href="{% url "user_registration"%}">Register Account</a></li>
    {% endif %}
</ul>

This will show the Login and Register Account links to the user if they aren't already logged in. If they are logged in, which we check using the request.user.is_authenticated function, they are only shown the Logout link. You can test all of these links yourself and see how little code was needed to make such a major feature of our site work. This is all possible because of the contrib applications that Django provides.

The blog

With the user registration out of the way, let's get started with the blogging side of the application. We'll create a new application for the blog, so in the console, type in the following:

> python manage.py startapp blog
> mkdir blog/templates

Add the blog application to the list of INSTALLED_APPS in our settings.py file. With the app created and installed, let's start with the models we'll be using.

Models

In blog/models.py, type the code shown below:

from django.contrib.auth.models import User
from django.db import models


class Blog(models.Model):
    owner = models.ForeignKey(User, editable=False)
    title = models.CharField(max_length=500)

    slug = models.CharField(max_length=500, editable=False)


class BlogPost(models.Model):
    blog = models.ForeignKey(Blog)
    title = models.CharField(max_length=500)
    body = models.TextField()

    is_published = models.BooleanField(default=False)

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

After typing in this code, run the following commands to create the database tables for these models:

> python manage.py makemigrations blog
> python manage.py migrate blog

This will create the database tables necessary to support our new models. The models are pretty basic. One field type that you might not have used before is the SlugField. A slug is a piece of text that is used to uniquely identify something. In our case, we use two slug fields to identify both our blog and our blog post. Since the fields are non-editable, we'll have to write the code to give them some values ourselves. We'll look into that later.

Creating a blog view

Let's create a view where the user can setup his blog. Let's make the form that the user will use to create a new blog. Create a new file blog/forms.py and enter the following:

from django import forms

from blog.models import Blog


class BlogForm(forms.ModelForm):
    class Meta:
        model = Blog

        fields = [
                 'title'
                 ]

This creates a model form that allows edits to only the title field of our Blog model. Let's create a template and view to go along with this form.

Create a file called blog/templates/blog_settings.html and type in the following HTML code:

{% extends "base.html" %}

{% block content %}
<h1>Blog Settings</h1>
<form action="{% url "new-blog" %}" method="post">{% csrf_token %}
    {{ form.as_p }}

    <input type="submit" value="Submit" />
</form>
{% endblock %}

As you may have noticed, I've used the url tag on the blog-settings named URL, but haven't created that URL pattern yet. We'll do that after we create the view, but just remember the name for later and make sure our URL gets the same name.

Note

There is no right order in which to create your view, template and URLs. It's up to you to decide whichever you are more comfortable with.

In your blog/views.py file, add the following code to create the view:

from django.core.urlresolvers import reverse
from django.http.response import HttpResponseRedirect
from django.utils.text import slugify
from django.views.generic import CreateView

from blog.forms import BlogForm


class NewBlogView(CreateView):
    form_class = BlogForm
    template_name = 'blog_settings.html'

    def form_valid(self, form):
        blog_obj = form.save(commit=False)
        blog_obj.owner = self.request.user
        blog_obj.slug = slugify(blog_obj.title)

        blog_obj.save()
        return HttpResponseRedirect(reverse('home'))

Modify blueblog/urls.py. Add this to the top of the file from blog.views import NewBlogView and add this to the urlpatterns list:

url(r'^blog/new/$', NewBlogView.as_view(), name='new-blog'),

As a final step, we need some way for the user to access our new view. Change the header block in base.html to look like this:

{% block header %}
<ul>
    {% if request.user.is_authenticated %}
    <li><a href="{% url "new-blog" %}">Create New Blog</a></li>
    <li><a href="{% url "logout" %}">Logout</a></li>
    {% else %}
    <li><a href="{% url "login" %}">Login</a></li>
    <li><a href="{% url "user_registration"%}">Register Account</a></li>
    {% endif %}
</ul>
{% endblock %}

To test our latest feature, open up the home page at http://127.0.0.1:8000 and click the Create New Blog link. It will present a form where you can enter the blog title and save your new blog. The page should look similar to the following screenshot:

Most of the code we have added is pretty basic. The interesting part is the NewBlogView. Let's look at how it works. First of all, notice that we subclass it from the CreateView generic view. The create view allows us to easily display and process a form that will create a new object of the given model. To configure it, we can either set the model and fields attribute of the view, which the create view will then use to generate a model form, or we can manually create a model form and assign it to the view, like we've done here.

We also configure the template that will be used to display the form. We then define the form_valid function, which the create view calls when the form is submitted with valid data. In our implementation, we call the model forms save method with the commit keyword parameter set to False. This tells the form to create a new object of our model with the data it was passed, but not to save the created object to the database. Then we set the owner of the new blog object to the logged in user and set its slug to a slugified version of the title entered by the user. slugify is one of the many utility functions that Django provides. Once we've modified the blog object per our requirement, we save it and return an HttpResponseRedirect from the form_valid function. This response is returned to the browser which then takes the user to the home page.

Until now, we've made do with a blank page with just a navigation bar as our home page. But it has a serious problem. Start by creating a new blog by following the link in the navigation bar. On successfully creating a new blog, we are redirected back to the home page, where we are again greeted with a link to create another blog. But this isn't the behavior we want. Ideally, our users should be limited to one blog per account.

Let's fix this. First, we'll restrict the blog creation view to only allow users to create a blog if they don't already have one. Import HttpResponseForbidden and the Blog model in blog/views.py:

from django.http.response import HttpResponseForbidden
from blog.models import Blog

Add a dispatch method to the NewBlogView class with the following code:

def dispatch(self, request, *args, **kwargs):
    user = request.user
    if Blog.objects.filter(owner=user).exists():
        return HttpResponseForbidden ('You can not create more than one blogs per account')
    else:
        return super(NewBlogView, self).dispatch(request, *args, **kwargs)

The dispatch method is one of the most useful methods to override on generic views. It is the first method that is called when the view URL is hit, and decides based on the request type whether to call the get or post methods on the view class to process the request. Thus, if you ever want to have some code that is run on all request types (GET, POST, HEAD, PUT, and so on), dispatch is the best method to override.

In this case, we make sure that the user doesn't already have a blog object associated with their account. If they do, we return the Not Allowed response by using the HttpResponseForbidden response class. Try it out. You shouldn't even be able to access the new blog page now if you have already created a blog before and should see an error instead.

One last thing. Try accessing the URL http://127.0.0.1:8000/blog/new/ after logging out. Notice how you'll get an AnonymousUser object is not iterable error. This is because even though you're not logged in as a registered user, the code for the view still assumes that you are. Also, you should not be able to access the new blog page without logging in first. To fix this, first put these two import lines at the top of blog/views.py:

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

Then change the definition line of the dispatch method to match the following:

@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):

If you try to access the page now without logging in first, you should see a Page not found (404) Django error page. If you look at the URL for that page, you'll see that Django is trying to serve the /accounts/login/ URL. That's the default behavior for the login_required decorator. To fix this we need to change the value of the LOGIN_URL variable in our settings file. Put this in blueblog/settings.py:

LOGIN_URL = '/login/'

Try accessing http://localhost:8000/blog/new/ now and you will be redirected to the login page. If you put in the correct username/password combination, you will be logged in and taken to the page you were trying to access before, the Create New Blog page. This functionality is provided to us for free because we use the built-in login view of Django.

We'll discuss the method_decorator and the login_required decorator in later chapters. If you want more info on these now, look at their documentation in the Django docs. It does an excellent job of explaining both.

You will find the documentation for login_required at https://docs.djangoproject.com/en/stable/topics/auth/default/#the-login-required-decorator. For the method_decorator, you can look at https://docs.djangoproject.com/en/stable/topics/class-based-views/intro/#decorating-the-class.

The home page

It's high time that we created a proper home page for our users instead of showing a blank page with some navigation links. Also, it seems very unprofessional to show users the Create New Blog link when it leads to an error page. Let's fix all these issues by creating a home page view that contains a bit of intelligence. We'll put the code for our home page view in the blog application. Technically it can go anywhere, but I personally like to put such views in either the main application of the project (the blog in this case) or create a new application for such common views. In your blog/views.py file, import the TemplateView generic view from django.views.generic import TemplateView and put the following code for the view:

class HomeView(TemplateView):
    template_name = 'home.html'

    def get_context_data(self, **kwargs):
        ctx = super(HomeView, self).get_context_data(**kwargs)

        if self.request.user.is_authenticated():
            ctx['has_blog'] = Blog.objects.filter(owner=self.request.user).exists()

        return ctx

Tie this new view to the home page URL by importing it in blueblog/urls.py using from blog.views import HomeView and changing the existing root URL config from url(r'^$', TemplateView.as_view(template_name='base.html'), name='home'), to url(r'^$', HomeView.as_view(), name='home'),.

Since the TemplateView class is no longer required, you can remove it from the imports. You should already have a good idea of what we're doing here. The only new thing is the TemplateView and it's get_context_data method. The TemplateView is another one of Djangos built-in generic views. We configure it by providing a template file name and the view renders that template by passing it the dictionary returned by our get_context_data function as the context. Here, we are setting the has_blog context variable to True if the user has an existing blog associated with his account.

With our view done, we'll need to make a few changes to our base.html template and add a new home.html template. For the base.html template, change the code in the header block to match:

{% block header %}
<ul>
    {% if request.user.is_authenticated %}
    {% block logged_in_nav %}{% endblock %}
    <li><a href="{% url "logout" %}">Logout</a></li>
    {% else %}
    <li><a href="{% url "login" %}">Login</a></li>
    <li><a href="{% url "user_registration"%}">Register Account</a></li>
    {% endif %}
</ul>
{% endblock %}

We've removed the Create New Blog link and replaced it with another block called logged_in_nav. The idea is that each page that inherits from the base template can add navigation links here to be shown to a logged in user. Finally, create a new file called blog/templates/home.html and add the following code:

{% extends "base.html" %}

{% block logged_in_nav %}
{% if not has_blog %}
<li><a href="{% url "new-blog" %}">Create New Blog</a></li>
{% else %}
<li><a href="">Edit Blog Settings</a></li>
{% endif %}
{% endblock %}

Just like we discussed, the home page template overrides the logged_in_nav block to add a link to create a new blog if the user doesn't have an existing blog, or to edit the settings for the existing blog. You can test that all of our changes work by visiting the home page with a user that has a blog already created, and a new user without a blog. You'll see that link to create a new blog only shows up if the user hasn't already created one.

Next, let's work on the settings view.

The blog settings view

Put the code for the view in blog/views.py:

class UpdateBlogView(UpdateView):
    form_class = BlogForm
    template_name = 'blog_settings.html'
    success_url = '/'
    model = Blog

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        return super(UpdateBlogView, self).dispatch(request, *args, **kwargs)

You'll need to import UpdateView from django.views.generic. Also, update the get_context_data method of the HomeView in the same file to match this one:

def get_context_data(self, **kwargs):
    ctx = super(HomeView, self).get_context_data(**kwargs)

    if self.request.user.is_authenticated():
        if Blog.objects.filter(owner=self.request.user).exists():
            ctx['has_blog'] = True
            ctx['blog'] = Blog.objects.get(owner=self.request.user)
    return ctx

Change the blog/templates/blog_settings.html to look like the following:

{% extends "base.html" %}

{% block content %}
<h1>Blog Settings</h1>
<form action="" method="post">{% csrf_token %}
    {{ form.as_p }}

    <input type="submit" value="Submit" />
</form>
{% endblock %}

The only change we've done is to remove the URL we defined explicitly in the form action before. This way, the form will always submit to whatever URL it is served from. This is important as we'll see later.

Update blog/templates/home.html as shown in the following code:

{% extends "base.html" %}

{% block logged_in_nav %}
{% if not has_blog %}
<li><a href="{% url "new-blog" %}">Create New Blog</a></li>
{% else %}
<li><a href="{% url "update-blog" pk=blog.pk %}">Edit Blog Settings</a></li>
{% endif %}
{% endblock %}

Finally, import the UpdateBlogView in blueblog/urls.py and add the following to the urlpatterns.

url(r'^blog/(?P<pk>\d+)/update/$', UpdateBlogView.as_view(), name='update-blog'),

That's it. Visit the home page with the user you used to create the blog in the last section and this time you'll see a link to edit your blog instead of creating a new one. The interesting thing to look at here in the UpdateView subclass; UpdateBlogView. We only defined the form class, the template name, the success URL and the model to get a complete working update view. With these things configured, and our URLs set up so that the primary key of the object we want to edit is passed to our view as the keyword argument named pk, the UpdateView displays a form tied to the instance of the model we want to edit. In the home view, we add the users blog to the context and use it in the home template to generate a URL for the update view.

In the form, we needed to change the action attribute of the form so that on submit, it posted to the current page. Since we use the same template in both the create and update views, we need the form to submit to whatever URL it is rendered from. As you'll see in the upcoming projects as well, it is a common practice in Django to use the same template with similar views. And the way Django generic views are structured makes it easier to do.

Creating and editing blog posts

Let's create the views that users can use to create and edit blog posts. Let's start with creating a new blog post. We already created the model earlier, so let's start with the form and template we'll use. In blog/forms.py, create this form:

class BlogPostForm(forms.ModelForm):
    class Meta:
        model = BlogPost

        fields = [
                 'title',
                 'body'
                 ]

You'll need to import the BlogPost model as well. For the template, create a new file blog/templates/blog_post.html, and add the following content:

{% extends "base.html" %}

{% block content %}
<h1>Create New Blog Post</h1>
<form action="" method="post">{% csrf_token %}
    {{ form.as_p }}

    <input type="submit" value="Submit" />
</form>
{% endblock %}

In blog/views.py, import the BlogPostForm and BlogPost model and then create the NewBlogPostView:

class NewBlogPostView(CreateView):
    form_class = BlogPostForm
    template_name = 'blog_post.html'

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        return super(NewBlogPostView, self).dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        blog_post_obj = form.save(commit=False)
        blog_post_obj.blog = Blog.objects.get(owner=self.request.user)
        blog_post_obj.slug = slugify(blog_post_obj.title)
        blog_post_obj.is_published = True

        blog_post_obj.save()

        return HttpResponseRedirect(reverse('home'))

In blueblog/urls.py, import the preceding view and add the following URL pattern:

url(r'blog/post/new/$', NewBlogPostView.as_view(), name='new-blog-post'),

And finally, change the homepage template blog/template/home.html to link to our new page:

{% extends "base.html" %}

{% block logged_in_nav %}
    {% if not has_blog %}
    <li><a href="{% url "new-blog" %}">Create New Blog</a></li>
    {% else %}
    <li><a href="{% url "update-blog" pk=blog.pk %}">Edit Blog Settings</a></li>
    <li><a href="{% url "new-blog-post" %}">Create New Blog Post</a></li>
    {% endif %}
{% endblock %}

All of this code should be pretty familiar to you by now. We've used model forms and generic views to get the functionality we need, and all we needed to do was configure some stuff. We haven't written one line of code to create the relevant form fields, validate the user input, and to handle the various error and success scenarios.

You can test out our new view by using the Create New Blog Post link in the navigation on the home page.

Editing blog posts

As we did before with the Blog model, we'll create an edit view for the blog post using the same template as the create view. But first, we need to add a way for the user to see his blog posts with links to the edit page. To keep things simple, let's add this list to our home page view. In the HomeView, edit the get_context_data method to match the following:

def get_context_data(self, **kwargs):
    ctx = super(HomeView, self).get_context_data(**kwargs)

    if self.request.user.is_authenticated():
        if Blog.objects.filter(owner=self.request.user).exists():
            ctx['has_blog'] = True
            blog = Blog.objects.get(owner=self.request.user)

            ctx['blog'] = blog
            ctx['blog_posts'] = BlogPost.objects.filter(blog=blog)

    return ctx

At the end of blog/templates/home.html; after the logged_in_nav block ends, add the following code to override the content block and show the blog posts:

{% block content %}
<h1>Blog Posts</h1>
<ul>
    {% for post in blog_posts %}
    <li>{{ post.title }} | <a href="">Edit Post</a></li>
    {% endfor %}
</ul>
{% endblock %}

If you visit the home page now, you'll see a list of posts that the user has made. Let's create the functionality to edit the posts. Create the following view in blog/views.py:

class UpdateBlogPostView(UpdateView):
    form_class = BlogPostForm
    template_name = 'blog_post.html'
    success_url = '/'
    model = BlogPost

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        return super(UpdateBlogPostView, self).dispatch(request, *args, **kwargs)

Import this view into your blueblog/urls.py file and add the following pattern:

url(r'blog/post/(?P<pk>\d+)/update/$', UpdateBlogPostView.as_view(), name='update-blog-post'),

Edit the list of blog posts we created earlier in the home page template to add the URL for editing a post:

{% for post in blog_posts %}
    <li>{{ post.title }} | <a href="{% url "update-blog-post" pk=post.pk %}">Edit Post</a></li>
{% endfor %}

If you open the home page now, you'll see that you can click on the Edit Post link and that it takes you to the editing page for the blog post. One last thing we need to fix is the title of the edit blog post page. You may have noticed that even when editing, the title said Create New Blog Post. In order to fix this, replace the h1 tag inside blog/templates/blog_post.html with the following:

<h1>{% if object %}Edit{% else %}Create{% endif %} Blog Post</h1>

The context passed to the template by the UpdateView includes a variable called object. This is the instance that the user is currently editing. We check for the existence of this variable in the template. If we find it, we know that we're editing an existing blog post. If not, we know it's a new blog post being created. We detect this and set the title accordingly.

Viewing blog posts

To add a view to show blog posts, add the following view class to blog/views.py:

class BlogPostDetailsView(DetailView):
    model = BlogPost
    template_name = 'blog_post_details.html'

Remember to import DetailView generic view from django.views.generic. Next, create the blog/templates/blog_post_details.html template with the following code:

{% extends "base.html" %}

{% block content %}
<h1>{{ object.title }}</h1>
<p>{{ object.body }}</p>
{% endblock %}

Import the details view and add the following URL pattern to the urls.py file:

url(r'blog/post/(?P<pk>\d+)/$', BlogPostDetailsView.as_view(), name='blog-post-details'),

Finally, change the list of blog posts in the home page template to link to the post details page from the post title:

{% for post in blog_posts %}
    <li><a href="{% url "blog-post-details" pk=post.pk %}">{{ post.title }}</a> | <a href="{% url "update-blog-post" pk=post.pk %}">Edit Post</a></li>
{% endfor %}

On the home page, the blog post titles should now link to the details page.

 

Multiple users


Until now, we've only been playing with a single user account and making our site work for that one user. Let's get to the exciting part and add sharing posts to other users blogs. However, once multiple users get added to the mix, there is one thing we should look at before moving forward.

Security

To demonstrate the complete lack of security in our application, let's create a new user account. Log out using the header link and register a new account. Next, log in with that user. You should end up on the home page and should not see any blog posts in the list.

Now, type in the URL http://127.0.0.1:8000/blog/post/1/update/. You should see the blog post we created from our first user in the edit view. Change either the title or the body of the blog post and click save. You are redirected back to the home page and it appears that the save has succeeded. Log in back to the first account and you'll see that the title of the blog post has been updated. This is a serious security breach and must be fixed, otherwise, any user can edit the blog posts for any other user without any restrictions.

The simple way in which we are able to solve this problem again demonstrates the power and simplicity of the Django framework. Add the following method to the UpdateBlogPostView class:

def get_queryset(self):
    queryset = super(UpdateBlogPostView, self).get_queryset()
    return queryset.filter(blog__owner=self.request.user)

That's it! Try opening http://127.0.0.1:8000/blog/post/1/update/ again. This time instead of allowing you to edit the blog post of another user, you see a 404 page.

What this small piece of code does can be understood after looking at how the UpdateView generic view works. The generic view calls a number of small methods, each of which does a specific job. Here's a list of some of the methods that are defined by the UpdateView class:

  • get_object

  • get_queryset

  • get_context_object_name

  • get_context_data

  • get_slug_field

The thing about having small methods like these is that in order to change the functionality of subclasses, we can override only one of these and fulfill our purpose, like we've done here. Read the Django documentation to figure out what these and many of the other methods used by the generic views do.

For our case, the get_queryset method, as the name suggests, gets the queryset within which the object to edit is searched for. We get the default queryset from the super method (which just returns a self.model.objects.all()) and return a version further filtered to only include blog posts owned by the currently logged in user. You should be familiar with relationship filters. If these are new to you, read the Django tutorial to familiarize yourself with the basics of filtering model querysets.

The reason you now see a 404 if you try to access someone else's blog post is that when the CreateView tries to get the object to edit, it receives a queryset that only includes blog posts owned by the currently logged in user. Since we're trying to edit someone else's blog post, it's not included in that queryset. Not finding the object to edit, the CreateView returns a 404.

Sharing blog post

The blog post sharing feature allows users to select the blog of another user they would like to share their blog posts with. This would allow users to gain more readers by sharing their content on the blogs of more popular writers, and readers would get to read more relevant content in one place instead of needing to discover more blogs.

The first step in making sharing possible is to add a field on the BlogPost model to indicate which blogs the post is shared with. Add this field to the BlogPost model in blog/models.py:

shared_to = models.ManyToManyField(Blog, related_name='shared_posts')

We are simply adding a basic Django many to many relationship field. If you'd like to review your knowledge of the features a many to many field provides, I advice you take a look at the Django tutorial again, specifically, the part that deals with M2M relationships.

One thing to note about the new field is that we had to specify related_name explicitly. As you might know, whenever you associate a model with another using any relationship field (ForeignKey, OneToMany, ManyToMany) Django automatically adds an attribute to the other model that allows easy access to the linked model.

Before we added the shared_to field, the BlogPost model already had a ForeignKey pointed at the Blog model. If you looked at the attributes available on the Blog model (using the shell), you would have found a blogpost_set attribute, which was a manager object that allowed access to BlogPost models that referenced that Blog. If we try to add the ManyToMany field without a related_name, Django would complain because the new relationship would also try to add a reverse relationship, also called blogpost_set. Because of this we need to give the reverse relationship another name.

After defining the M2M relationship, you can now access blog posts shared with a blog model by using the shared_posts attributes all() method on the Blog model. We'll see an example of that later.

After defining the new field, run the following commands to migrate your DB to create the new relationship:

> python manage.py makemigrations blog
> python manage.py migrate blog

Next, let's create the view that allows the user to select a blog to share their post with. Add this to blog/views.py:

class ShareBlogPostView(TemplateView):
    template_name = 'share_blog_post.html'

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        return super(ShareBlogPostView, self).dispatch(request, *args, **kwargs)

    def get_context_data(self, pk, **kwargs):
        blog_post = BlogPost.objects.get(pk=pk)
        currently_shared_with = blog_post.shared_to.all()
        currently_shared_with_ids = map(lambda x: x.pk, currently_shared_with)
        exclude_from_can_share_list = [blog_post.blog.pk] + list(currently_shared_with_ids)

        can_be_shared_with = Blog.objects.exclude(pk__in=exclude_from_can_share_list)

        return {
            'post': blog_post,
            'is_shared_with': currently_shared_with,
            'can_be_shared_with': can_be_shared_with
        }

This view is a subclass of the template view. You should have a pretty good idea of how it works by now. The important bit to look at here is the code inside the get_context_data method. First, we get the blog post object using the id passed in the keyword arguments gathered from the parsed URL pattern. Next, we get a list of all blog objects this post has been shared with. We do this because we don't want to confuse the user by allowing sharing to a blog that the post is already shared with.

The next line of code uses the Python built-in map method on the queryset of the blogs the post is shared with. map is one of the most useful methods when working with any kind of lists (or list-like objects) in Python. It takes as it's first argument a function that takes a single argument and returns one argument, and a list as it's second argument. map then calls the given function on each element in the input list and gathers the results in a final list that is returned. Here, we use a lambda to extract the ID of the blog objects that this post is already shared with.

Finally, we can get the list of blog objects that this post can be shared with. We use the exclude method to not include the blog objects the post is already shared with. We pass this to the template in the context. Next, let's take a look at the template that you need to create in blog/templates/share_blog_post.html:

{% extends "base.html" %}

{% block content %}
{% if can_be_shared_with %}
<h2>Share {{ post.title }}</h2>
<ul>
    {% for blog in can_be_shared_with %}
    <li><a href="{% url "share-post-with-blog" post_pk=post.pk blog_pk=blog.pk %}">{{ blog.title }}</a></li>
    {% endfor %}
</ul>
{% endif %}

{% if is_shared_with %}
<h2>Stop sharing with:</h2>
<ul>
    {% for blog in is_shared_with %}
    <li><a href="{% url "stop-sharing-post-with-blog" post_pk=post.pk blog_pk=blog.pk %}">{{ blog.title }}</a></li>
    {% endfor %}
</ul>
{% endif %}
{% endblock %}

There's nothing special in this template. Let's move on to the two URLs and views that this refers to, since without those we can't render this template. First, let's look at SharepostWithBlog, which you need to create in blog/views.py. You will need to add this import line to the top of the file as well:

from django.views.generic import View

The code for the view is this:

class SharePostWithBlog(View):
    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        return super(SharePostWithBlog, self).dispatch(request, *args, **kwargs)

    def get(self, request, post_pk, blog_pk):
        blog_post = BlogPost.objects.get(pk=post_pk)
        if blog_post.blog.owner != request.user:
            return HttpResponseForbidden('You can only share posts that you created')

        blog = Blog.objects.get(pk=blog_pk)
        blog_post.shared_to.add(blog)

        return HttpResponseRedirect(reverse('home'))

Import this into blueblog/urls.py and add it with the following URL pattern:

url(r'blog/post/(?P<pk>\d+)/share/$', SharePostWithBlog.as_view(), name='share-blog-post-with-blog'),

Unlike all our previous views, this view doesn't fit nicely into any of the generic views that Django provides. But Django has a base generic view that makes our life easier than creating a function that handles the request.

The View generic view is used whenever you need something completely custom to handle a request. Like all generic views, it has a dispatch method that you can override to intercept a request before any further processing is done. Here, we make sure that the user is logged in before allowing them to proceed.

In a View subclass, you create methods with the same name as the request types you want to handle. Here, we create a get method as we only care about handling GET requests. The View class takes care of calling our method when the correct request method is used by the client. In our get method, we're doing a basic check to see if the user owns the blog post. If they do, we add the blog to the shared_to ManyToMany relationship of the BlogPost model.

The last view we need to create is one to allow the user to remove a blog post they have already shared. The code for that is shown here:

class StopSharingPostWithBlog(View):
    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        return super(StopSharingPostWithBlog, self).dispatch(request, *args, **kwargs)

    def get(self, request, post_pk, blog_pk):
        blog_post = BlogPost.objects.get(pk=post_pk)
        if blog_post.blog.owner != request.user:
            return HttpResponseForbidden('You can only stop sharing posts that you created')

        blog = Blog.objects.get(pk=blog_pk)
        blog_post.shared_to.remove(blog)

        return HttpResponseRedirect(reverse('home'))

Like the SharePostWithBlog view, this one subclasses the View generic view. The code is almost exactly the same as the previous view. The only difference is that in the previous view we used blog_post.shared_to.add, whereas in this view we use the blog_post.shared_to.remove method.

Finally, import these two views into blueblog/urls.py and add the following patterns:

url(r'blog/post/(?P<post_pk>\d+)/share/to/(?P<blog_pk>\d+)/$', SharePostWithBlog.as_view(), name='share-post-with-blog'),
    url(r'blog/post/(?P<post_pk>\d+)/stop/share/to/(?P<blog_pk>\d+)/$', StopSharingPostWithBlog.as_view(), name='stop-sharing-post-with-blog'),

In order to show a link to the share this post page, edit the home.html template to change the entire code inside the content block to this:

{% if blog_posts %}
<h2>Blog Posts</h2>
<ul>
    {% for post in blog_posts %}
    <li>
        <a href="{% url "blog-post-details" pk=post.pk %}">{{ post.title }}</a> |
        <a href="{% url "update-blog-post" pk=post.pk %}">Edit Post</a> |
        <a href="{% url "share-blog-post" pk=post.pk %}">Share Post</a>
    </li>
    {% endfor %}
</ul>
{% endif %}

And that's it. Now when you visit the home page, each blog post should have a Share Post link next to it. When you click it, you'll see a second page with links to share the blog post on other user blogs. Clicking the link should share your post and also show a corresponding remove link on the same page. Of course, in order to test this, you should create a second user account and add a blog using that account.

One last thing we should do is modify the get_context_data method of the HomeView to also include shared posts in the blog post list:

def get_context_data(self, **kwargs):
    ctx = super(HomeView, self).get_context_data(**kwargs)

    if self.request.user.is_authenticated():
            if Blog.objects.filter(owner=self.request.user).exists():
            ctx['has_blog'] = True
            blog = Blog.objects.get(owner=self.request.user)

            ctx['blog'] = blog
            ctx['blog_posts'] = BlogPost.objects.filter(blog=blog)
            ctx['shared_posts'] = blog.shared_posts.all()

    return ctx

Add this to the bottom of the content block inside the blog/templates/home.html template:

{% if shared_posts %}
<h2>Shared Blog Posts</h2>
<ul>
    {% for post in shared_posts %}
    <li>
        <a href="{% url "blog-post-details" pk=post.pk %}">{{ post.title }}</a>
    </li>
    {% endfor %}
</ul>
{% endif %}
{% endblock %}

And that's it, our first application is complete! If you open the home page now, you should see a Share Post link next to each blog post. Clicking this should open up another page where you can select which blog to share this post with. To test it you should create another blog with the other account we created earlier when we were looking at the security of our application. Once you have another blog configured, your share blog post page should look similar to this:

Clicking the title of the other blog should share the post and take you back to the home page. If you click the Share Post link again on the same post, you should now see a heading saying Stop sharing with, and the name of the blog you shared this post with.

If you log in to the other account now, you should see that the post is now shared there, and is listed under the Shared Blog Posts section.

 

Summary


In this chapter, we've seen how to start our application and set it up properly so that we can develop things rapidly. We've looked at using template inheritance to achieve code reuse and give our site common elements such as navigation bars. Here's a list of topics we have covered so far:

  • Basic project layout and setup with sqlite3 database

  • Simple Django form and model form usage

  • Django contrib apps

  • Using django.contrib.auth to add user registration and authentication to an application

  • Template inheritance

  • Generic views for editing and displaying database objects

  • Database migrations

We'll use the lessons we've learned here throughout the rest of the chapters in this book.

About the Author
  • Asad Jibran Ahmed

    Asad Jibran Ahmed is an experienced programmer who has worked mostly with Django-based web applications for the past 5 years. Based in Dubai, UAE, he has worked with some of the biggest web properties in the region, including Dubizzle, the number one classifieds platform in UAE; Nabbesh, one of the top freelancing platforms in the MENA region; and Just Property, a hot and rising name in the property portal space of the region. His experience with such big names has given him a keen insight into how to design performant, stable, and user friendly web applications, all the while using programming practices that make sure that the code base is maintainable for years.

    Browse publications by this author
Latest Reviews (7 reviews total)
I got what I wanted, precise and profound knowledge
rien à signaler pas de problème
Nice resource with step by step guide on how to build real-world and production-ready projects.
Django Project Blueprints
Unlock this book and the full library FREE for 7 days
Start now