DEV Community

Cover image for Create a Modern Application with Django and Vue – Part One
Eric Hu
Eric Hu

Posted on • Originally published at ericsdevblog.com

Create a Modern Application with Django and Vue – Part One

Previously, in the beginner's tutorial on web development, we talked about how to create a web application using Django, a full-stack Python-based web framework that follows the MTV design pattern. We call it full-stack because we can create both the frontend and the backend with it.

You can download the source code for this tutorial here:

Download the Source Code

This solution, however, has one small flaw. When the user requests a webpage, the page will need to be rendered in the backend, and then the rendered HTML page will be sent to the user. As you can imagine, when you have a lot of users, that will put a lot of pressure on your server.

To solve this problem, developers usually split the application into two parts, the backend and the frontend. This way, when the user requests a webpage, instead of rendering the webpage, the backend only gathers the necessary data and transfers it to the frontend. The client's machine, which usually has a lot more excessive computing power, will use that data to render the webpage inside the browser directly, hence relieving the pressure on the server.

In this tutorial, I'm going to demonstrate how to create a modern web application using Django as the backend, Vue as the frontend, and GraphQL as the API manipulation language that connects them together.

A Brief Review on Django

Let's start with a brief review of the Django framework. Django is a Python-based web framework that follows the MTV architecture. The model (M) is an interface that allows us to interact with the database, such as retrieving, creating, updating or deleting records. The template (T) is the frontend part of the framework, it is the part that the end-users are going to see. And finally, the view (V) is the backend logic of the application, it uses the model to interact with the database, such as retrieving the data that is required by the user. Then the view would manipulate the data in some way, and return the result (usually a customized template) to the user.

Take a look at this tutorial series (https://www.ericsdevblog.com/index.php/django-for-beginners/) if you are interested in learning these concepts in detail.

For this particular tutorial, we are only going to use Django for the backend, which means we are not going to use Django's template or view. Instead, we are using Vue as the frontend, and use GraphQL to connect the front and the back. We are going to talk about them in the following articles. In this article, let's start with setting up the Django end.

Create a New Django Project

Personally, I like to separate the backend app and the frontend app. So this is how I created the project structure:

blog
--backend
--frontend
Enter fullscreen mode Exit fullscreen mode

Go to the backend folder, and create a Python virtual environment. A Python virtual environment is an isolated environment with a fresh Python install, without all the packages. When you install packages inside this environment, it will not affect your system's Python environment, which is very important if you are using Linux or macOS, and you don't want to mess with it.

cd backend
python3 -m venv env
Enter fullscreen mode Exit fullscreen mode

This command will create a folder called env, and the virtual environment is generated inside. To activate this virtual environment, use the following command:

source env/bin/activate
Enter fullscreen mode Exit fullscreen mode

And your terminal will look like this. Notice the (env) in front of the username.

https://www.ericsdevblog.com/wp-content/uploads/2022/02/image-1.png

This indicates that you are currently working in the virtual environment.

Next, it is time for us to create a new Django project. You should already be familiar with this process.

python -m pip install Django
django-admin startproject backend
Enter fullscreen mode Exit fullscreen mode

Create a new application:

cd backend
python manage.py startapp blog
Enter fullscreen mode Exit fullscreen mode

Create Models

Next, it is time for us to setup the model. Recall that model is the interface which we can use to interact with the database. And one of the greatest feature of Django is that it can automatically detect the changes you made to the models, and generate the corresponding migration files, which we can use to make changes to the database. Again, if you are not sure how this process works, please consider going through my beginner's tutorial on Django first.

The Site Model

We'll start with the Site model, which stores the basic information of our website.

class Site(models.Model):
    name = models.CharField(max_length=200)
    description = models.TextField()
    logo = models.ImageField(upload_to='site/logo/')

    class Meta:
        verbose_name = 'site'
        verbose_name_plural = '1. Site'

    def __str__(self):
        return self.name
Enter fullscreen mode Exit fullscreen mode

On line 4, we have an ImageField which will upload the image to 'site/logo/' directory. To make this work, there are two things we need to do. First, we need to install the Pillow package. Django need to use it to deal with images.

python -m pip install Pillow
Enter fullscreen mode Exit fullscreen mode

Second, we need a new setting in the settings.py. We need to tell Django where we are storing these media files and what URL we'll use when accessing these files.

import os

# Media Files
MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles')
MEDIA_URL = '/media/'
Enter fullscreen mode Exit fullscreen mode

This setting means that the media files will be stored inside the /mediafiles directory, and we'll need to use the URL prefix /media/ to access them.

The User Model

Next, for the User model. We know that Django comes with a built-in User model, which offers basic permission and authorization functions. However, for our project, we'll try something more complicated. We'll add a profile avatar, a bio, and some other information. To do that, we need to extend to the AbstractUser class.

from django.contrib.auth.models import AbstractUser

# New user model
class User(AbstractUser):
    avatar = models.ImageField(
        upload_to='users/avatars/%Y/%m/%d/',
        default='users/avatars/default.jpg'
    )
    bio = models.TextField(max_length=500, null=True)
    location = models.CharField(max_length=30, null=True)
    website = models.CharField(max_length=100, null=True)
    joined_date = models.DateField(auto_now_add=True)

    class Meta:
        verbose_name = 'user'
        verbose_name_plural = '2. Users'

    def __str__(self):
        return self.username
Enter fullscreen mode Exit fullscreen mode

Django's AbstractUser class look like this:

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.

    Username and password are required. Other fields are optional.
    """
    username_validator = UnicodeUsernameValidator()

    username = models.CharField(
        _('username'),
        max_length=150,
        unique=True,
        help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
        validators=[username_validator],
        error_messages={
            'unique': _("A user with that username already exists."),
        },
    )
    first_name = models.CharField(_('first name'), max_length=150, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)
    email = models.EmailField(_('email address'), blank=True)
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        abstract = True

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def get_full_name(self):
        """
        Return the first_name plus the last_name, with a space in between.
        """
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)

Enter fullscreen mode Exit fullscreen mode

As you can see, it offers some basic fields like first_namelast_name, etc.

Next, we need to make sure that Django is using this model as its default User model, or the authentication won't work. Go to settings.py and add the following code:

# Change Default User Model
AUTH_USER_MODEL = 'blog.User'
Enter fullscreen mode Exit fullscreen mode

The CategoryTag and Post Model

This part would be very easy for you if if you already understand database relations.

class Category(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField()
    description = models.TextField()

    class Meta:
        verbose_name = 'category'
        verbose_name_plural = '3. Categories'

    def __str__(self):
        return self.name
Enter fullscreen mode Exit fullscreen mode
class Tag(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField()
    description = models.TextField()

    class Meta:
        verbose_name = 'tag'
        verbose_name_plural = '4. Tags'

    def __str__(self):
        return self.name
Enter fullscreen mode Exit fullscreen mode
class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField()
    content = RichTextField()
    featured_image = models.ImageField(
        upload_to='posts/featured_images/%Y/%m/%d/')
    is_published = models.BooleanField(default=False)
    is_featured = models.BooleanField(default=False)
    created_at = models.DateField(auto_now_add=True)
    modified_at = models.DateField(auto_now=True)

    # Each post can receive likes from multiple users, and each user can like multiple posts
    likes = models.ManyToManyField(User, related_name='post_like')

    # Each post belong to one user and one category.
    # Each post has many tags, and each tag has many posts.
    category = models.ForeignKey(
        Category, on_delete=models.SET_NULL, null=True)
    tag = models.ManyToManyField(Tag)
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)

    class Meta:
        verbose_name = 'post'
        verbose_name_plural = '5. Posts'

    def __str__(self):
        return self.title

    def get_number_of_likes(self):
        return self.likes.count()
Enter fullscreen mode Exit fullscreen mode

Notice how the like system is implemented on line 13. It is not a simple IntegerField, but instead, it works just like tags. And we'll use get_number_of_likes() method to get the number of likes for each post.

The Comment Model

This time, we'll go one step further, and create a comment section.

class Comment(models.Model):
    content = models.TextField(max_length=1000)
    created_at = models.DateField(auto_now_add=True)
    is_approved = models.BooleanField(default=False)

    # Each comment can receive likes from multiple users, and each user can like multiple comments
    likes = models.ManyToManyField(User, related_name='comment_like')

    # Each comment belongs to one user and one post
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    post = models.ForeignKey(Post, on_delete=models.SET_NULL, null=True)

    class Meta:
        verbose_name = 'comment'
        verbose_name_plural = '6. Comments'

    def __str__(self):
        if len(self.content) > 50:
            comment = self.content[:50] + '...'
        else:
            comment = self.content
        return comment

    def get_number_of_likes(self):
        return self.likes.count()
Enter fullscreen mode Exit fullscreen mode

Setup Django Admin

Finally, let's setup the Django admin.

from django.contrib import admin
from .models import *

# Register your models here.
class UserAdmin(admin.ModelAdmin):
    list_display = ('username', 'first_name', 'last_name', 'email', 'date_joined')

class CategoryAdmin(admin.ModelAdmin):
    prepopulated_fields = {'slug': ('name',)}

class TagAdmin(admin.ModelAdmin):
    prepopulated_fields = {'slug': ('name',)}

class PostAdmin(admin.ModelAdmin):
    prepopulated_fields = {'slug': ('title',)}
    list_display = ('title', 'is_published', 'is_featured', 'created_at')

class CommentAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'is_approved', 'created_at')

admin.site.register(Site)
admin.site.register(User, UserAdmin)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Post, PostAdmin)
admin.site.register(Comment, CommentAdmin)
Enter fullscreen mode Exit fullscreen mode

For the CommentAdmin__str__ refers to the __str__() method in the Comment model. Which will return the first 50 characters plus "...".

Now, start the development server and see if everything works:

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

https://www.ericsdevblog.com/wp-content/uploads/2022/02/image-2-1024x528.png

Before we move to the next step, remember to add some pseudo information for our blog.

Discussion (0)