Part V - Creating the models.py, views.py, urls.py, admin.py & the superuser

Updated: 

Discover how to create essential Django files: models.py, views.py, urls.py, and admin.py. Learn to set up a superuser for efficient project management.

Vicente Reyes

Vicente Reyes

In writing models in Django, we have to follow the coding standards that are stated in the docs. A few pointers to remember are all field names should be in lower case, and should be using underscores instead of camelCase. The next tip is that the class Meta: should always be first after defining the fields, then the standard methods after. Like this:


class Meta:
    ...

  def __str__(self)
      ...

  def save()
      ...

  def get_absolute_url()
      ...

And since our app has choices, as shown in the figure above, Django's coding style suggests that we define the choice as a list tuple with an all-uppercase name as a class attribute on the model.

Now let's start.

# blog_tutorial/main/models.py
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse

STATUS_CHOICES = [
    ("draft", "Draft"),
    ("published", "Published"),
  ]


class Project(models.Model):
    """ This model defines our Project class which will
    handles the portfolio of the user.
    """
    title = models.CharField(max_length=100)
    slug = models.SlugField(max_length=140, default=title)
    image = models.ImageField(upload_to="projects/", blank=True)
    live_site = models.CharField(max_length=255)
    github_link = models.CharField(max_length=255)
    description = models.TextField()

    class Meta:
    """ Meta for the naming in the django admin that
    describes a model if the object is singular or
    plural
    """
        verbose_name = _("Project")
        verbose_name_plural = _("Project")

    def __str__(self):
      """ Returns the title of Project models instead
      of a primary key
      """
      return self.title


class Category(models.Model):
    """ This model defines the categories field in the Post model
        with a ManyToManyField.
    """
    title = models.CharField(max_length=140)
    slug = models.SlugField(max_length=140, default=title)

    class Meta:
      """ Meta for the naming in the django admin that
        describes a model if the object is singular or
        plural
      """
      verbose_name = _("Category")
      verbose_name_plural = _("Category")

    def __str__(self):
      """ Returns the title of Project models instead
      of a primary key
      """
      return self.title

    def get_context_data(self, **kwargs):
      context = super(self).get_context_data(**kwargs)
      context['posts'] = Post.objects.filter('category')
      return context

    def get_absolute_url(self):
      return reverse("category-list", kwargs={"slug": self.slug})


class Post(models.Model):
    """ The main model that defines the Post class which
        has a relationship with the Category class
    """
    title = models.CharField(max_length=140)
    slug = models.SlugField(max_length=140, default=title)
    overview = models.CharField(max_length=255)
    body = models.TextField()
    image = models.ImageField(upload_to="blog/")
    created_on = models.DateField()
    updated_on = models.DateField()
    categories = models.ManyToManyField(Category, related_name='posts')
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="draft")

    class Meta:
       """ Meta for the naming in the django admin that
         describes a model if the object is singular or
         plural
       """
       verbose_name = _("Post")
       verbose_name_plural = _("Post")

    def __str__(self):
       """ Returns the title of Project models instead
       of a primary key
       """
       return self.title


class Contact(models.Model):
     """ The Contact model that accepts name, email and message
       in the contact page.
     """
     name = models.CharField(max_length=100)
     email = models.EmailField()
     message = models.TextField()

    class Meta:
      """ Meta for the naming in the django admin that
        describes a model if the object is singular or
        plural
      """
      verbose_name = _("Contact")
      verbose_name_plural = _("Contact")

    def __str__(self):
      """ Returns the title of Project models instead
      of a primary key
      """
      return self.name


In writing views.py for a Django app, Class Based Views are written much simpler than the Function Based views. Here's an example:


# FBV
# views.py
def blog_index(request):    
    blogs = Post.objects.all()    
    context = {        
    "blogs": blogs,    
    }    
    return render(request, "pages/blog_index.html", context)

# blog_index.html
{% for blog in blogs %}
    {{ blog.title }}
    ...
{% endfor %}

# CBV
# views.py

class BlogListView(ListView):    
    model = Post
    template_name = 'pages/blog.html'
    context_object_name = 'posts'

# blog_list.html
{% for post in object_list %}
    {{ post.title }}
    ...
{% endfor }}

Both views above render the same data. But can you see which one's simpler? Yep, that's how concise Class-Based Views are. Now let's write our views.py for our blog

# main/views.py

from django.contrib import messages
from django.shortcuts import render
from django.views.generic import DetailView
from django.views.generic.list import ListView
from django.views.generic.edit import FormView
from django.views.generic import TemplateView

from blog_tutorial.main.models import (
    Post,
    Category,
    Project,
    Contact,
   )

from blog_tutorial.main.forms import ContactForm

class ProjectListView(ListView):
    model = Project
    paginate_by = 5
    template_name = 'pages/projects.html'
    context_object_name = 'projects'


class ProjectDetailView(DetailView):
    model = Project
    template_name = 'pages/project_details.html'


class BlogListView(ListView):
    model = Post
    paginate_by = 4
    template_name = 'pages/home.html'
    context_object_name = 'posts'
    ordering = ['-created_on']


class BlogDetailView(DetailView):
    model = Post
    template_name = 'pages/post_detail.html'


def blog_category(request, category):
    posts = Post.objects.filter(
        categories__slug__contains=category
    )
    context = {
        "category": category,
        "posts": posts
    }
    return render(request, "pages/category_list.html", context)


class ContactFormView(FormView):
    template_name = 'pages/contact.html'
    form_class = ContactForm
    success_url = '/contact/'

    def form_valid(self, form):
       name = form.cleaned_data['name']
       email = form.cleaned_data['email']
       message = form.cleaned_data['message']

       m = Contact(
       name=name,
       email=email,
       message=message,
       )
       m.save()

       messages.success(self.request, 'Your message has been sent.')

       return super().form_valid(form)


class AboutView(TemplateView):
     template_name = 'pages/about.html'

Let's create the forms.py

from django import forms
from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Field


class ContactForm(forms.Form):
   def __init__(self, *args, **kwargs):
      super(ContactForm, self).__init__(*args, **kwargs)
      self.helper = FormHelper()

      self.helper.form_method = 'post'
      self.helper.form_action = '/contact/'
      self.helper.form_class = "form-group"
      self.helper.form_id = 'contact-form'

      self.helper.add_input(Submit('submit', 'Submit'))

      name = forms.CharField(max_length=100, label="Your name", widget=forms.TextInput(attrs={'class': 'col', 'placeholder':'Vicente Reyes'}))
      email = forms.CharField(label="Your email", widget=forms.EmailInput(attrs={'class': 'col', 'placeholder':'[email protected]'}))
      message = forms.CharField(max_length=500, label="Your inquiry", widget=forms.Textarea(attrs={'placeholder':'I need to...'}))

To view our models' data that are passed to the views, we have to pass the views to our URLs. 

# config/urls.py
...

from blog_tutorial.main.views import (
    BlogListView,
    BlogDetailView,
    blog_category,
    ContactFormView,
    ProjectListView,
    ProjectDetailView
)

urlpatterns = [
    path("", BlogListView.as_view(), name="home"),
    path( "about/", TemplateView.as_view(template_name="pages/about.html"), name="about"),
    # Django Admin, use {% url 'admin:index' %}
    path(settings.ADMIN_URL, admin.site.urls),
    # User management
    path("users/", include("blog_tutorial.users.urls", namespace="users")),
    path("accounts/", include("allauth.urls")),
    # Your stuff: custom urls includes go here
    path("blog/<slug:slug>/", BlogDetailView.as_view(), name="blog-detail"),
    path("blog/category/<slug:category>/", blog_category, name='categories'),
    path("portfolio/", ProjectListView.as_view(), name="portfolio"),
    path("portfolio/<slug:slug>", ProjectDetailView.as_view(), name="project-details"),
    path("contact/", ContactFormView.as_view(), name="contact"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Now let's migrate our models and create our superuser to access the admin.

$ python manage.py makemigrations && python manage.py migrate && python manage.py createsuperuser
Username: admin
Email address:
Password:
Password (again):
Superuser created successfully.

Now for the admin, we're using one of the included features of Cookiecutter-Django that can generate an admin.py file called django-extensions

We'll run one command to generate the file:

$ python manage.py admin_generator main

The command will generate the output in the shell:

# -*- coding: utf-8 -*-
from django.contrib import admin

from .models import Project, Category, Post, Contact


@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
    list_display = (
        'id',
        'title',
        'slug',
        'image',
        'live_site',
        'github_link',
        'description',
    )
    search_fields = ('slug',)


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'slug')
    search_fields = ('slug',)


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = (
        'id',
        'title',
        'slug',
        'overview',
        'body',
        'image',
        'created_on',
        'updated_on',
        'status',
    )
    list_filter = ('created_on', 'updated_on')
    raw_id_fields = ('categories',)
    search_fields = ('slug',)


@admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'email', 'message')
    search_fields = ('name',)

Let's run the Django development server and head over to http://localhost:8000

Watching for file changes with StatReloader
INFO 2020-07-22 14:59:07,112 autoreload 47052 4566134208 Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
July 22, 2020 - 14:59:15
Django version 3.0.8, using settings 'config.settings.local'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.