DEV Community

Cover image for Django Tutorial #6: Home Page
Eric Hu
Eric Hu

Posted on • Updated on • Originally published at ericsdevblog.com

Django Tutorial #6: Home Page

Starting from this article, we are going to start putting what we've learned about Django so far into real-life application. And to not overwhelm you, we'll start with something easier. In this article, we are going to create only a homepage. This homepage is going to be very simple, we need a logo, a site name and a description. But through this example, you'll see exactly how the model, view and template layer can work together.

You can download the source code for this tutorial here:

Download the Source Code

As a quick reminder, this is how we can create a new Django project:

python -m pip install Django
django-admin startproject example
cd example
python manage.py startapp blog
Enter fullscreen mode Exit fullscreen mode

Remember to make the necessary changes to the configuration file (settings.py) like we talked about in this article (https://www.ericsdevblog.com/index.php/2022/01/django-tutorial-1-setup-the-project/).

The Model Layer

Now, let's start with the model layer. Since we are only building a home page, we only need one model to store all the data, let's call it home. Inside this model, we are going to need three fields, title, description and logo.

from django.db import models

class Home(models.Model):
    title = models.CharField(max_length=255)
    description = models.TextField()
    logo = models.ImageField(upload_to='uploads/images/logo')
Enter fullscreen mode Exit fullscreen mode

Notice that the logo is an ImageField. This field will create a column in the database, which stores the path that points to the location of the image, and the upload_to attribute defines that path. You'll see exactly how this works later.

Also, you need to have the Pillow package installed to use the ImageField. Here is how you can install the package if you haven't already done so.

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

Next, we can generate the migration files and apply them to our database.

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

This is what the database should look like. Notice that the logo column is of type VARCHAR, which means it should be a string.

https://www.ericsdevblog.com/wp-content/uploads/2022/01/Screen-Shot-2022-01-27-at-12.59.19-PM-1024x384.png

View, Template and URL

Next, it's time to deal with views and templates. Let's first ask ourselves a question, how many views and templates do we need to create a home page?

We mentioned before that in the field of web development, there is something called CRUD (Create, Read, Update and Delete) operation. Basically, we need to make sure that the user can browse the items in the database table, read the details of an item, create new items and store them in the database, update an item and of course, delete an item.

To create a complete CRUD operation, we'll need at least seven view functions.

  • index() - Display a listing of items.
  • show(id) - Display the specified item.
  • create() - Show the form for creating a new item.
  • store(request) - Store a newly created item in database.
  • edit(id) - Show the form for editing the specified item.
  • update(request, id) - Update the specified item in database.
  • destroy(id) - Remove the specified item from database.

For our homepage, we don't need index() since we are only going to have one item in the database, and we are not going to talk about edit() and update(), since they are basically the same as create() and store().

As for the template, we need a create.html page to display a form that we can use to create a new Home. A success.html page that tells us we've successfully created a new Home, or successfully deleted one. And finally, a home.html page that displays the home.

Create

So, let's start with the create page. We need a layout.html and a create.html file. I put all the templates in the templates directory. Remember to change the corresponding setting in the settings.py file.

https://www.ericsdevblog.com/wp-content/uploads/2022/01/image-21.png

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

    {% block meta %} {% endblock %}

    <!-- Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>

<body>
    <!-- Responsive navbar-->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="#">{{ title }}</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse"
                data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
                aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                    <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Home</a></li>
                    <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" id="navbarDropdown" href="#" role="button"
                            data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a>
                        <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                            <li><a class="dropdown-item" href="#">Action</a></li>
                            <li><a class="dropdown-item" href="#">Another action</a></li>
                            <li>
                                <hr class="dropdown-divider" />
                            </li>
                            <li><a class="dropdown-item" href="#">Something else here</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>
    <!-- Page content-->
    <div class="container">
        {% block content %} {% endblock %}
    </div>
    <!-- Bootstrap core JS-->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous">
    </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode
{% extends 'layout.html' %}

{% block meta %}
<meta name="description" content="{{ title }}" />
<title>{{ title }}</title>
{% endblock %}

{% block content %}
<br>
<form action="/save/" method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    <div class="mb-3">
        <label for="inputTitle" class="form-label">Website Title</label>
        <input type="text" class="form-control" id="inputTitle" name="title">
    </div>
    <div class="mb-3">
        <label for="inputDescription" class="form-label">Website Description</label>
        <textarea type="text" class="form-control" id="inputDescription" name="description"></textarea>
    </div>
    <div class="mb-3">
        <label for="inputLogo" class="form-label">Choose a logo</label>
        <input type="file" class="form-control" id="inputLogo" name="logo">
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

There are three things we need to talk about in this form.

First, line 10, the action attribute defines the URL that your browser will request when the Submit button is clicked. The method attribute defines the method of that HTTP request, which in our case, is POST. The enctype attribute defines how the data will be encrypted, since we are uploading an image file, multipart/form-data is our only option. If you are interested in other options, consider reading this article (https://www.w3schools.com/tags/att_form_enctype.asp).

Second, line 14, 18 and 22. Notice that all of them have a name attribute. This is very important, we are going to use it to retrieve the corresponding user input, which we'll talk about later.

And lastly, the button must have type submit, or the form won't do anything.

Next, let's create the corresponding view. We need a create() method that displays the create.html page.

from django.shortcuts import render
from blog.models import Home

def create(request):

    return render(request, 'create.html', {
        'title': "Create a new Home",
    })
Enter fullscreen mode Exit fullscreen mode

The create() view is relatively easy. We only need to pass one title variable to the template.

Save

For the save page, we need a success.html page that tells us the data has been saved.

{% extends 'layout.html' %}

{% block meta %}
<meta name="description" content="{{ description }}" />
<title>{{ title }}</title>
{% endblock %}

{% block content %}
<div class="text-center mt-5">
    <h1>Success!</h1>
</div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode
def save(request):
    home = Home(
        title = request.POST.get('title', ''),
        description = request.POST.get('description', ''),
        logo = request.FILES['logo'],
    )

    home.save()

    return render(request, 'success.html', {
        'title': "Success",
        'description': "Successfully Saved"
    })
Enter fullscreen mode Exit fullscreen mode

In the save() view, from line 2 to 6, we created a new instance of Home, and remember that our Home model should take three attributes, title, description and logo.

Line 3, request.POST.get('title', '') is how we can access the body of the HTTP POST request. The get() method takes two parameters, the first one 'title' matches the value of the name attribute in our form, which we just talked about. And the second parameter is the default value, if the user input is empty, the default value (empty string '') will be assigned.

Line 5, request.FILES['logo'] is how we can store the image file. Dealing with uploaded files is a little bit tricky in Django. We need to add something to the settings.py file.

import os

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

This code will inform Django where to put the uploaded media files, and what URL to use when trying to access these files. In our case, the media files will be put inside the media directory.

Now, when you upload an image, that image file will be put into directory media/uploads/images/logo, as defined in our model. And the path that points to the image file will be stored in the database.

Remember to add the URL dispatchers:

from blog import views
from django.urls import path

urlpatterns = [
    ...
    path('create/', views.create),
    path('save/', views.save),
]
Enter fullscreen mode Exit fullscreen mode

You can test the create and save page by going to http://127.0.0.1:8000/create/.

https://www.ericsdevblog.com/wp-content/uploads/2022/01/image-22.png

https://www.ericsdevblog.com/wp-content/uploads/2022/01/image-23-1024x128.png

Show

The show page should be very simple for you now. First, we need a home.html file.

{% extends 'layout.html' %}

{% block meta %}
<meta name="description" content="{{ description }}" />
<title>{{ title }}</title>
{% endblock %}

{% block content %}
<div class="text-center mt-5">
    <img src="{{ logo }}" width="200px">
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>

    <form action="/delete/" method="POST" enctype="multipart/form-data">
        {% csrf_token %}
        <button type="submit" class="btn btn-danger">Delete Home</button>
    </form>

</div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

The view function:

def show(request):
    home  = Home.objects.first()

    title = home.title
    description = home.description
    logo = home.logo.url

    return render(request, 'home.html', {
        'title': title,
        'description': description,
        'logo': logo,
    })
Enter fullscreen mode Exit fullscreen mode

Line 6, home.logo.url retrieves the URL that points to the images, so that we can use it directly in the template.

And don't forget the URL:

path('', views.show),
Enter fullscreen mode Exit fullscreen mode

However, if you go to http://127.0.0.1:8000/, you will notice that the image file is not loading, but the link is pointing to the correct location. This is because, by default, Django does not serve media files, to change this, we need to add another line in the url.py file.

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    ...
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Enter fullscreen mode Exit fullscreen mode

Now, refresh the browser, and everything should work fine. Remember that this is how we serve median files in the dev environment, in the production environment, we'll have to do something different, which we'll talk about in the future.

Delete

Notice that in the home.html file, we have another form, which points to the URL /delete/. This leads us to the final part of this article, the delete view. To save us some time, we'll use the same success.html file, but in a real application, you should probably use a different template.

def delete(request):
    home  = Home.objects.first()

    home.delete()

    return render(request, 'success.html', {
        'title': "Success",
        'description': "Successfully Deleted"
    })
Enter fullscreen mode Exit fullscreen mode

When this view is executed, it will find the first record in the Home model, and delete it.

Discussion (0)