Skip to main content

Django application from scratch to deploy with apache2

Setting Up My Environment

To start developing my Django application, I need to have Python installed on my system. If it's not installed yet, I can download it from the official website here.

Once Python is installed, I can proceed to install Django. It's recommended to use a virtual environment to avoid conflicts with other projects. I will use virtualenv. To install virtualenv, I use the following command:

diego@diego-romero.xyz:~/blog/django-scartch-to-deploy$ pip install virtualenv

After installing virtualenv, I create a new virtual environment with the following command:

diego@diego-romero.xyz:~/blog/django-scartch-to-deploy$ python -m virtualenv venv

This creates a new folder named venv in the current directory. To activate the virtual environment, I use this command:

diego@diego-romero.xyz:~/blog/django-scartch-to-deploy$ source venv/bin/activate

Once the virtual environment is activated, I see the name of the environment in the terminal. Now I can install Django using pip:

(venv) diego@diego-romero.xyz:~/blog/django-scartch-to-deploy$ pip install django==4.2.0

Setting Up My Django Project

After creating my new virtual environment, I navigate to the folder where I want to store the application. I use the following command to start a new Django project:

(venv) diego@diego-romero.xyz:~/blog/django-scartch-to-deploy$ django-admin startproject PROJECT_NAME

Django creates a folder with the specified PROJECT_NAME. For this example, I create a simple CRUD (Create, Read, Update, Delete) application for products named crud_products.

The created folder structure looks like this:

Folder Structure

Now, I change the working directory to the folder created by Django, then test if Django is working correctly by running:

(venv) diego@diego-romero.xyz:~/blog/django-scartch-to-deploy$ cd crud_products

(venv) diego@diego-romero.xyz:~/blog/django-scartch-to-deploy/crud_products$ python manage.py runserver

I should see a result like this:

Django server running

Creating My Application

Now that I have my project set up, I create my first application using the following command:

(venv) diego-romero.xyz:~/blog/django-scartch-to-deploy/crud_products$ python manage.py startapp products

This creates a new folder named products inside the crud_products folder. The folder structure now looks like this:

Project structure with app created

Explaining the Folder Structure

The crud_products folder is the main folder of my project. It contains the settings, URLs, and other configurations for my project. The products folder is the application I just created. It contains the models, views, and other components of my application.

Inside the models.py file, I add the database entities that I need to use in my application. In my case, I only create one model called Product.

Inside the views.py file, I add the logic for my application. This is where I define the behavior of my application.

Inside the urls.py file, I define the URLs for my application. This is where I define the routes for my application.

I also need to create a file called forms.py and one called urls.py that will contain the forms and urls for my application respectively.

This is how my models.py looks like:

    from django.db import models

    class Product(models.Model):
        name = models.CharField(max_length=100)
        description = models.TextField()
        price = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return self.name
    

I create a new model called Product with three fields: name, description, and price. The __str__ method returns the name of the product when printed.

This is how my forms.py looks like:

    from django import forms
    from .models import Product
    
    class ProductForm(forms.ModelForm):
        class Meta:
            model = Product
            fields = ('name', 'description', 'price',)
    

I define a form for my Product model using Django's ModelForm to create a form for creating and updating products.

This is how my views.py looks like:

    from django.views.generic import ListView, DetailView
    from django.views.generic.edit import CreateView, UpdateView, DeleteView
    from django.urls import reverse_lazy
    from .models import Product
    from .forms import ProductForm
    
    class ProductListView(ListView):
        model = Product
        template_name = 'products/list.html'
    
    class ProductDetailView(DetailView):
        model = Product
        template_name = 'products/detail.html'
    
    class CreateProductView(CreateView):
        model = Product
        form_class = ProductForm
        template_name = 'products/edit.html'
        success_url = reverse_lazy('product_list')
    
    class UpdateProductView(UpdateView):
        model = Product
        form_class = ProductForm
        template_name = 'products/edit.html'
        success_url = reverse_lazy('product_list')
    
    class DeleteProductView(DeleteView):
        model = Product
        template_name = 'products/confirm_delete.html'
        success_url = reverse_lazy('product_list')            
    

I define views for my application using Django's generic views to create a list view, detail view, create view, update view, and delete view. I also use a ProductForm to create and update products.

This is how my urls.py looks like:

    from django.urls import path
    from .views import ProductListView, ProductDetailView, CreateProductView, UpdateProductView, DeleteProductView
    
    urlpatterns = [
        path('', ProductListView.as_view(), name='product_list'),
        path('product//', ProductDetailView.as_view(), name='product_detail'),
        path('product/new/', CreateProductView.as_view(), name='new_product'),
        path('product//edit/', UpdateProductView.as_view(), name='edit_product'),
        path('product//delete/', DeleteProductView.as_view(), name='delete_product'),
    ]
    

I define the URLs for my application using Django's path function to define routes.

Now, I add my application to the INSTALLED_APPS list in the settings.py file.

This is how the INSTALLED_APPS list looks like:

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',

        # Local apps
        'products',
    ]
    

Now I run the migrations to create the database tables for my application with the following commands:

(venv) diego-romero.xyz:~/blog/django-scartch-to-deploy/crud_products$ python manage.py makemigrations

(venv) diego-romero.xyz:~/blog/django-scartch-to-deploy/crud_products$ python manage.py migrate

After running the migrations, I see output like this:

Migrations

Migrations

Migrate

Migrate

Now my application is ready, but I can't access it yet. I need to create the templates for my application. I start by setting the templates configuration in the settings.py file.

    import os #Add this import
    
    ## Rest of the code

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    

This allows me to use a templates folder to store templates. Now I create the templates folder in the root folder of my project and then create a base.html inside this directory where I define the structure of the page.

This is how my base.html looks like:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>{% block title %}My Django App{% endblock title %}</title>
        <!-- Additional CSS files or external stylesheets can be linked here -->
    </head>
    <body>
        <header>
            <nav>
                <!-- Navigation menu -->
                <ul>
                    
                </ul>
            </nav>
        </header>
        <main>
            {% block content %}
            <!-- Content will be overridden in child templates -->
            {% endblock content %}
        </main>
        <footer>
            <p>© 2024 My Django App. All rights reserved.</p>
        </footer>
        <!-- Additional JavaScript files or external scripts can be included here -->
    </body>
    </html>
    

Now I create the templates for my application. I create a folder called templates inside my application's folder, where I create files for my views.

  • list.html
  • detail.html
  • edit.html
  • confirm_delete.html

This is how my list.html looks like:

    <!-- templates/products/list.html -->
    {% extends 'base.html' %}
    
    {% block title %}Product List{% endblock %}
    
    {% block content %}
        <h2>Product List</h2>
        <a href="{% url 'new_product' %}">Add New Product</a>
        <ul>
        {% for product in object_list %}
            <li>
            {{ product.name }} - {{ product.price }}
            <a href="{% url 'product_detail' product.pk %}">View</a> | 
            <a href="{% url 'edit_product' product.pk %}">Edit</a> | 
            <a href="{% url 'delete_product' product.pk %}">Delete</a>
            </li>
        {% empty %}
            <li>No products available.</li>
        {% endfor %}
        </ul>
    {% endblock content %}
    

This is how my detail.html looks like:

    <!-- templates/products/detail.html -->
    {% extends 'base.html' %}
    
    {% block title %}Product Detail{% endblock %}
    
    {% block content %}
        <h2>{{ product.name }}</h2>
        <p>Description: {{ product.description }}</p>
        <p>Price: {{ product.price }}</p>
        <a href="{% url 'edit_product' product.pk %}">Edit</a> | 
        <a href="{% url 'delete_product' product.pk %}">Delete</a> | 
        <a href="{% url 'product_list' %}">Back to list</a>
    {% endblock content %}
    

The other templates are similar in structure, with specific content for each action (create, update, delete).

Finally, I add my application's URL configuration to the project's URL configuration. I open the urls.py file in the project's folder and include the URLs from my application.

    from django.urls import path, include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('products/', include('products.urls')),
    ]
    

Now, my application is fully configured and ready to use.

I can access to the application going to localhost:8000/

This is how my application look like:

List

Create

List after create

Detail

Edit

Delete

Deploying My Django App Using Apache 2

Setting Up Apache and mod_wsgi

To deploy my Django application using Apache 2, I first need to install Apache and mod_wsgi, an Apache module for hosting Python web applications.

Install Apache:

diego@diego-romero.xyz:~$ sudo apt-get install apache2

Install mod_wsgi:

For Python 3 applications, use:

diego@diego-romero.xyz:~$ sudo apt-get install libapache2-mod-wsgi-py3

Enable the module if it's not automatically activated:

diego@diego-romero.xyz:~$ sudo a2enmod wsgi

Configuring Apache to Serve the Django Application

Prepare the Django Application:

Set DEBUG = False in Django settings.

Configure ALLOWED_HOSTS with domain names/IP addresses.

Collect Static Files:

diego@diego-romero.xyz:~/blog/django-scartch-to-deploy/crud_products$ python manage.py collectstatic
  • Create an Apache Virtual Host:
    sudo nano /etc/apache2/sites-available/myapp.conf

    Add the configuration, adjusting paths as needed:

    <VirtualHost *:80>
        ServerName mydomain.com
        ServerAdmin webmaster@localhost
        Alias /static /path/to/myapp/static
        <Directory /path/to/myapp/static>
            Require all granted
        </Directory>
        Alias /media /path/to/myapp/media
        <Directory /path/to/myapp/media>
            Require all granted
        </Directory>
        WSGIDaemonProcess myapp python-path=/path/to/myapp python-home=/path/to/venv
        WSGIProcessGroup myapp
        WSGIScriptAlias / /path/to/myapp/myapp/wsgi.py
        <Directory /path/to/myapp/myapp>
            <Files wsgi.py>
                Require all granted
            </Files>
        </Directory>
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
    </VirtualHost>

    Enable the virtual host:

    diego@diego-romero.xyz:/etc/apache2/sites-available$ sudo a2ensite myapp

    diego@diego-romero.xyz:/etc/apache2/sites-available$ sudo a2dissite 000-default

    Restart apache:

    diego@diego-romero.xyz:/etc/apache2/sites-available$ udo systemctl restart apache2

    Final steps

    • After restarting Apache, my Django application should be live at the specified ServerName.
    • Test the application by visiting http://mydomain.com in a web browser.
  • Comments