The contents of this repository is the finished version of the MWC3 2024 Django workshop.
This README that you are reading is a step-by-step written guide to what we did in the workshop.
Commits of this project should line up and be in sync with the steps listed in this README.
Open up the Windows Command prompt (Terminal) WINDOWS KEY
and search for CMD
-
Ensure you have python installed and on the path. In the terminal type:
python --version
Which should return something like:
Python 3.11.8
-
Navigate to where you would like to start the project. EX:
cd Documents
-
Create a folder for the new project and change directories to it.
mkdir mwc3_ticket_tracker cd mwc3_ticket_tracker
-
Create a new virtual environment for the project and activate it. NOTE: Will be different if using something other than CMD.
python -m venv .venv .venv\Scripts\activate.bat
-
Update pip if needed
python -m pip install --upgrade pip
-
Install the required packages. NOTE: django must be version 4.2
python -m pip install django~=4.2 python -m pip install django-adminlte2-pdq
-
Dump the requirements so that the packages can easily be installed again later if needed.
python -m pip freeze > requirements.txt
-
Start the new Django project. NOTE: Do NOT forget the period on the end.
django-admin startproject django_project .
-
Verify that the install worked by running the test server and pulling up the default landing page for a Django site.
python manage.py runserver
-
Then in a browser visit
http:https://localhost:8000
and verify the site came up. You should see a rocket ship.You can stop the server by pressing
CTRL-BREAK
orCTRL-C
If usingCTRL-C
, you sometimes have to press it a couple of times.
-
Every Django project comes with some default database tables that can be used for User Authentication. In order to utilize that, we need to have Django create the database tables for us. This is called migrating.
python manage.py migrate
-
The migration process will take python code and use it to generate a database for us. The database will be a simple SQLite, file based database, located in the root of our project called
db.sqlite
.
-
Open the project in VSCode. If VSCode is added to the path, you can just type the following on the command line to open it. If it is not, you need to manually open VSCode and then open the correct folder.
code .
-
Follow Steps 2 - 5 of the Quickstart instructions for Django-AdminLTE2-PDQ located at the following link: https://django-adminlte2-pdq.readthedocs.io/en/latest/quickstart.html
-
Re-run the development server and verify that AdminLTE2-PDQ is working.
python manage.py runserver
-
Once that is confirmed to be working, we can add some customization to how AdminLTE2-PDQ behaves by editing some lines in the
settings.py
file of thedjango_project
folder.
django_project/settings.py
# AdminLTE2 settings ADMINLTE2_USE_LOGIN_REQUIRED = True ADMINLTE2_LOGO_TEXT = "Ticket Tracking" ADMINLTE2_LOGO_TEXT_SMALL = "TT" ADMINLTE2_INCLUDE_ADMIN_HOME_LINK = True ADMINLTE2_INCLUDE_MAIN_NAV_ON_ADMIN_PAGES = True ADMINLTE2_INCLUDE_ADMIN_NAV_ON_MAIN_PAGES = True
-
Since we now turned on Login Required, it will be impossible to get to the site without a user. So, let's make a superuser that is able to login to the site. In the terminal:
python manage.py createsuperuser
-
Re-run the development server again. Verify that you can get to the login page and that you can log in as the newly created superuser.
python manage.py runserver
-
In the browser, Visit:
http:https://localhost:8000/
which should redirect tohttp:https://localhost:8000/accounts/login/
. Then login which should then take you to the main Dashboard.
Great! We now have the stock adminLTE2-pdq site up and running. Time to start making our actual ticket tracking app.
-
Apps are small plugable parts of a Django project. There will always be one single project, but potentially many apps. We will only have one app. But there could be more. Create the one new app that we plan to use. In the terminal:
python manage.py startapp tickets
-
Register the new app in the
INSTALLED_APPS
list of the Django project settingssettings.py
django_project/settings.py
INSTALLED_APPS = [ "tickets.apps.TicketsConfig", "adminlte2_pdq", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ]
-
Create the required database model in the app's
models.py
file
tickets/models.py
from django.db import models # Choices for Priority PRIORITY_CHOICES = ( (1, "High"), (2, "Medium"), (3, "Low"), ) class Ticket(models.Model): """Represents a ticket for project.""" assigned_to = models.ForeignKey( "auth.User", on_delete=models.CASCADE, related_name="tickets", blank=True, null=True, ) name = models.CharField(max_length=250) description = models.TextField() priority = models.IntegerField(choices=PRIORITY_CHOICES) complete_by = models.DateField(blank=True, null=True) completed = models.BooleanField(default=False) def __str__(self): """str conversion""" return self.name
-
Now that we have our model created, we need to migrate that work into the database. It is a two step process. First, create a migration that can do the DB work for us. Second, apply the migration to actually do that DB work. In the terminal:
python manage.py makemigrations tickets python manage.py migrate
-
Django comes with a very powerful part of the site that lets certain users interact with the database. The only requirement is that the programmer register the models with the admin.
tickets/admin.py
from django.contrib import admin from .models import Ticket admin.register(Ticket)
-
Restart the development server if it is not running and then in the browser navigate to the following URL and see what is available for interacting with our new model.
http:https://localhost:8000/admin/
-
Create a
urls.py
file to store the end points for our app.Right click on the
tickets
folder and selectnew file
. Typeurls.py
for the name of the new file and pressenter
.
-
Register the new
urls.py
file with the projectsurls.py
file. NOTE: The order matters. Ensure yours matches the below code.
django_project/urls.py
from django.contrib import admin from django.urls import path, include urlpatterns = [ path("accounts/", include("django.contrib.auth.urls")), path("admin/", admin.site.urls), path("", include("tickets.urls")), path("", include("adminlte2_pdq.urls")), ]
-
Create the first URL endpoint for the app that will show all of the open tickets.
NOTE: We have not created the OpenTicketListView class yet. As a result, the import may have issues for now but should be fixed in one of the next steps.
tickets/urls.py
from django.urls import path from .views import ( OpenTicketListView, ) urlpatterns = [ path("", OpenTicketListView.as_view(), name="open_ticket_list"), ]
-
Update
settings.py
and set the AdminLTE2-PDQ home route to the newly created URL endpoint.
django_project/settings.py
# AdminLTE2 settings ADMINLTE2_HOME_ROUTE = "open_ticket_list"
-
Update the
views.py
file to contain our first view for the url endpoint we set up in the previous step.NOTE: Some of the import statements will not be used right now. But we are adding them now because we will need them later on.
NOTE: The
from django.shortcuts import render
line in the file can be removed.
tickets/views.py
from django.contrib import messages from django.views.generic import ListView, TemplateView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.urls import reverse from .models import Ticket class OpenTicketListView(TemplateView): """Show lists of open tickets by priority""" template_name = "ticket_list_open.html" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.extra_context = { "open_high_tickets": Ticket.objects.filter(priority=1, completed=False), "open_medium_tickets": Ticket.objects.filter(priority=2, completed=False), "open_low_tickets": Ticket.objects.filter(priority=3, completed=False), }
- In the VSCode sidebar, right click below the file tree and select
New Folder
. Name the new foldertemplates
. This should put the new folder into the root of the project. It needs to be in the root of the project to work correctly.
-
Update the
settings.py
file to tell Django to look in our newtemplates
directory for our templates.You just need to find the setting called
TEMPLATES
insettings.py
and update it to contain a new directory in theDIRS
part of that setting.
django_project/settings.py
Change
"DIRS": [],
To
"DIRS": [BASE_DIR / "templates"],
The full altered setting should look like the below when done.
django_project/settings.py
TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [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", ], }, }, ]
-
Create all of the template files that we will need for the whole app. Create all of them right now so we are done with it and don't have to keep creating them as we need them.
For each of the filenames listed below, right click on the
templates
directory and chooseNew File
and then create each file._ticket_box.html _ticket_breadcrumb.html _ticket_form.html ticket_create.html ticket_delete.html ticket_list_closed.html ticket_list_open.html ticket_update.html
-
Template partials are small snippets that are meant to be reused multiple times when creating templates. They are prefixed with an underscore to denote that they are not a full page.
Create the
_ticket_box.html
partial.
templates/_ticket_box.html
<div class="box box-solid box-{{ color }}"> <div class="box-header with-border"> <h3 class="box-title">{{ priority }} Tickets</h3> </div> <div class="box-body"> {% if tickets.exists %} <table class="table"> <tr> <th>Name</th> <th>Assigned To</th> <th>Complete By</th> <th class="text-right">Actions</th> </tr> {% for ticket in tickets.all %} <tr> <td>{{ ticket.name }}</td> <td>{{ ticket.assigned_to.username }}</td> <td>{{ ticket.complete_by }}</td> <td class="text-right"> <a href="{% url "ticket_update" ticket.pk %}" class="btn btn-success"> <i class="fa fa-pencil"></i> Update </a> <a href="{% url "ticket_delete" ticket.pk %}" class="btn btn-danger"> <i class="fa fa-times"></i> Delete </a> </td> </tr> {% endfor %} </table> {% else %} <h3>No Tickets For This Priority Level</h3> {% endif %} </div> </div>
-
Create the
_ticket_breadcrumb.html
partial.
templates/_ticket_breadcrumb.html
<ol class="breadcrumb"> <li> <i class="fa fa-dashboard"></i> <a href="{% url 'open_ticket_list' %}">Home</a> </li> {% if icon and action %} <li class="active"><i class="fa fa-{{ icon }}"></i> {{ action }} Ticket</li> {% endif %} </ol>
-
Create the
_ticket_form.html
partial.
templates/_ticket_form.html
{% load adminlte_tags %} <div class="box box-solid box-{{ color }}"> <div class="box-header with-border"> <h3 class="box-title">{{ action }} Ticket</h3> </div> <div class="box-body"> <form method="post"> {% csrf_token %} {% if action == "Delete" %} <p> <h4>Are you sure you want to delete this ticket?</h4> - {{ ticket.name }} </p> {% else %} {% render_form form %} {% endif %} <button type="submit" class="btn btn-{{ color }}"> <i class=""></i> {{ action }} </button> </form> </div> </div>
-
Use two of the above partials to create the
ticket_list_open.html
page.
templates/ticket_list_open.html
{% extends "adminlte2/base.html" %} {% block page_name %} Open Tickets {% endblock page_name %} {% block breadcrumbs %} {% include "_ticket_breadcrumb.html" with icon="ticket" action="Open" %} {% endblock breadcrumbs %} {% block content %} {% comment %}<a href="{% url "ticket_create" %}" class="btn btn-primary pull-right"> <i class="fa fa-plus"></i> New Ticket </a>{% endcomment %} <br><br> {% include "_ticket_box.html" with tickets=open_high_tickets color="danger" priority="High Priority" %} {% include "_ticket_box.html" with tickets=open_medium_tickets color="warning" priority="Medium Priority" %} {% include "_ticket_box.html" with tickets=open_low_tickets color="info" priority="Low Priority" %} {% endblock content %}
-
Test out the site and verify that we can see the boxes. Run the server if it is not already running and visit
http:https://localhost:8000
python manage.py runserver
-
After confirming that our first page works and before finishing the other pages, we need to uncomment the create link that was commented out in step 5 so that the link will work when finished.
NOTE: After doing this uncomment, the site will not work until we finish the rest of the steps in this workshop.
templates/ticket_list_open.html
Change this:
{% block content %} {% comment %}<a href="{% url "ticket_create" %}" class="btn btn-primary pull-right"> <i class="fa fa-plus"></i> New Ticket </a>{% endcomment %} <br><br>
To This:
{% block content %} <a href="{% url "ticket_create" %}" class="btn btn-primary pull-right"> <i class="fa fa-plus"></i> New Ticket </a> <br><br>
-
Use partials to create the
ticket_list_closed.html
page.
templates/ticket_list_closed.html
{% extends "adminlte2/base.html" %} {% block page_name %} Closed Tickets {% endblock page_name %} {% block breadcrumbs %} {% include "_ticket_breadcrumb.html" with icon="ticket" action="Closed" %} {% endblock breadcrumbs %} {% block content %} {% include "_ticket_box.html" with tickets=closed_tickets color="purple" priority="Closed" %} {% endblock content %}
-
Use partials to create the
ticket_create.html
page.
templates/ticket_create.html
{% extends "adminlte2/base.html" %} {% block page_name %} Create Ticket {% endblock page_name %} {% block breadcrumbs %} {% include "_ticket_breadcrumb.html" with icon="plus" action="Create" %} {% endblock breadcrumbs %} {% block content %} {% include "_ticket_form.html" with color="primary" action="Create" %} {% endblock content %}
-
Use partials to create the
ticket_update.html
page.
templates/ticket_update.html
{% extends "adminlte2/base.html" %} {% block page_name %} Update Ticket {% endblock page_name %} {% block breadcrumbs %} {% include "_ticket_breadcrumb.html" with icon="pencil" action="Update" %} {% endblock breadcrumbs %} {% block content %} {% include "_ticket_form.html" with color="success" action="Update" %} {% endblock content %}
-
Use partials to create the
ticket_delete.html
page.
templates/ticket_delete.html
{% extends "adminlte2/base.html" %} {% block page_name %} Delete Ticket {% endblock page_name %} {% block breadcrumbs %} {% include "_ticket_breadcrumb.html" with icon="times" action="Delete" %} {% endblock breadcrumbs %} {% block content %} {% include "_ticket_form.html" with color="danger" action="Delete" %} {% endblock content %}
-
Update the
views.py
file by appending the following Views that will complete our site.NOTE: By the end of this, all import statements at the top of the file should be used.
tickets/views.py
class ClosedTicketListView(ListView): """Show list of closed tickets""" model = Ticket template_name = "ticket_list_closed.html" def get_queryset(self): return Ticket.objects.filter(completed=True) class TicketCreateView(CreateView): """Create a new ticket""" model = Ticket template_name = "ticket_create.html" fields = "__all__" def get_success_url(self): """Where to send user on success""" messages.success(self.request, "Ticket Created Successfully") return reverse("open_ticket_list") class TicketUpdateView(UpdateView): """Update a ticket""" model = Ticket template_name = "ticket_update.html" fields = "__all__" def get_success_url(self): """Where to send user on success""" messages.success(self.request, "Ticket Updated Successfully") return reverse("open_ticket_list")
tickets/views.py
(Continued)class TicketDeleteView(DeleteView): """Delete a ticket""" model = Ticket template_name = "ticket_delete.html" def get_success_url(self): """Where to send user on success""" messages.success(self.request, "Ticket Deleted Successfully") return reverse("open_ticket_list")
-
Import the views that we just made and then create the remaining URL endpoints needed to complete the app.
NOTE: The below code is what the finished file looks like.
tickets/urls.py
from django.urls import path from .views import ( OpenTicketListView, ClosedTicketListView, TicketCreateView, TicketUpdateView, TicketDeleteView, ) urlpatterns = [ path("", OpenTicketListView.as_view(), name="open_ticket_list"), path("tickets/closed/", ClosedTicketListView.as_view(), name="closed_ticket_list"), path("tickets/create/", TicketCreateView.as_view(), name="ticket_create"), path("tickets/<int:pk>/update/", TicketUpdateView.as_view(), name="ticket_update"), path("tickets/<int:pk>/delete/", TicketDeleteView.as_view(), name="ticket_delete"), ]
-
We now need to tell AdminLTE2-PDQ what URL route we want to be our home page. Additionally, we want to define the sidebar menu. Both things are done by creating a setting in the
settings.py
file. Append the following lines to the bottom of thesettings.py
file.
django_project/settings.py
# AdminLTE2 menu ADMINLTE2_MENU = [ { "text": "Home", "nodes": [ { "route": "open_ticket_list", "text": "Open Tickets", "icon": "fa fa-dashboard", "active_requires_exact_url_match": True, }, { "route": "closed_ticket_list", "text": "Closed Tickets", "icon": "fa fa-ticket", }, ], }, { "text": "Profile", "nodes": [ { "route": "password_change", "text": "Change Password", "icon": "fa fa-lock", }, ], }, ]
Assuming everything went well, you should not be able to run the server again and verify that everything works as intended. In the terminal run:
python manage.py runserver
Then in a browser visit:
http:https://localhost:8000