Building a Food Delivery App With Django and Python 3: Part 5 Staff Login and Restaurant Dashboard

LegionScript
8 min readNov 16, 2020

--

Video Tutorial

Code on Github

In this tutorial we are going to continue building our food delivery application. Now that we have a good portion of the customer side built, we are going to start building the restaurant side of the application. We are going to start by using allauth to quickly add the ability to log in. For now, we are going to disable the register page and keep it where only an admin can add staff accounts. We will protect the restaurant side where only accounts inside of a staff group will be able to view the pages. We will also create a simple dashboard where we will display the total daily revenue, total daily orders, and a list of all of the orders for the day. Let’s get started by adding the authentication.

AllAuth Authentication

First we need to set up AllAuth. Here is the documentation. First we need to install it.

pip install django-allauth

Next we need to make some changes to our settings.py file:

AUTHENTICATION_BACKENDS = [
# Needed to login by username in Django admin, regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',
# `allauth` specific authentication methods, such as login by e-mail
'allauth.account.auth_backends.AuthenticationBackend',
]
INSTALLED_APPS = [
# The following apps are required:
'django.contrib.auth',
'django.contrib.messages',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
]
SITE_ID = 1

Make sure to keep the apps in the INSTALLED_APPS as well.

Finally we need to add a line to our foodelivery/urls.py:

urlpatterns = [
...
path('accounts/', include('allauth.urls')),
]

Now let’s just migrate the changes to the database:

python manage.py migrate

Now with these changes, we should have working login/logout/register views. We want to make the templates look better and we want to prevent any random person from signing up for an account. We can do the latter by adding a couple things to our application.

First we need to add a line to our settings.py file:

ACCOUNT_ADAPTER = 'restaurant.account_adapter.NoNewUsersAccountAdapter'

Next we need to create a account_adapter.py file inside of the restaurant directory and add the following into it:

from allauth.account.adapter import DefaultAccountAdapterclass NoNewUsersAccountAdapter(DefaultAccountAdapter):    def is_open_for_signup(self, request):
"""
Checks whether or not the site is open for signups.
Next to simply returning True/False you can also intervene the
regular flow by raising an ImmediateHttpResponse
(Comment reproduced from the overridden method.)
"""
return False

That should prevent any new users from signing up from the register route. Finally, we want to make the templates look better. I’m not going to go through that code here as it is just adding some basic bootstrap to the templates, however I will go through the set up steps to override the default templates. We need to install crispy forms and add the template pack setting to our settings.py file:

pip install django-crispy-formsINSTALLED_APPS = [
...
'crispy_forms',
]
CRISPY_TEMPLATE_PACK = 'bootstrap4'

Finally we can copy the the default templates from the template folder, here. We can paste this in the root directory of our project. Finally we can add this line to use these templates instead:

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',
],
},
},
]

You will need to make sure the path created by the os.path.join points to the location of the templates folder that you copied in.

From here you can style them however you would like to, the example for this project is available on GitHub.

Finally, you will need to set the redirect url to be different than the default profile route. We are going to set it to the dashboard view that we will make next, so we will add this to our settings.py:

LOGIN_REDIRECT_URL = 'dashboard'

Building the Dashboard

Finally, we need to build a dashboard once the user logs in. We also want to protect this view so that only logged in users who are staff can log in. We will add a staff group to specify who is staff.

First lets open the admin panel and add that staff group. If you add a group, you should be able to give it the name of staff. Below that you will see a list of permissions, we want to give staff access to everything that says customer by it. If you select all of those and move them over to the right (using the arrows in the middle) that will give this group those permissions.

While we are in here, let’s give our admin user staff access by going to the users tab and adding staff as a group to admin, once again by selecting the group and choosing the arrow to move it over to the right.

Now with that set up, we can start building this dashboard.

Let’s start by creating the view.

from django.shortcuts import render, redirect
from django.views import View
from customer.models import OrderModel
from django.utils.timezone import datetime
class Dashboard(LoginRequiredMixin, UserPassesTestMixin, View):
def get(self, request, *args, **kwargs):
today = datetime.today()
orders = OrderModel.objects.filter(created_on__year=today.year,
created_on__month=today.month, created_on__day=today.day)
total_revenue = 0
for order in orders:
total_revenue += order.price

context = {
'orders': orders,
'total_revenue': total_revenue,
'total_orders': len(orders)
}
return render(request, 'restaurant/dashboard.html', context)

Here, we are grabbing all of the orders from the current date, we are also adding up the price on each order to get the total revenue so far for the day. We pass all of this into the context and into a template called dashboard.html. Before we create that, there is one more thing we want to do here.

We want to restrict this view to only be viewable by those with staff access using the group we created earlier. We can do this using two mixins, UserPassesTestMixin and LoginRequiredMixin. LoginRequiredMixin will just make sure the user in the request is logged in, the UserPassesTestMixin will check the request from a boolean expression we define and if it returns true it will let the user in. We can set this up using a test_func method that will return either true or false. Here is the updated view with this added:

from django.shortcuts import render, redirect
from django.views import View
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from customer.models import OrderModel
from django.utils.timezone import datetime
class Dashboard(LoginRequiredMixin, UserPassesTestMixin, View):
def get(self, request, *args, **kwargs):
today = datetime.today()
orders = OrderModel.objects.filter(created_on__year=today.year, created_on__month=today.month, created_on__day=today.day)
total_revenue = 0
for order in orders:
total_revenue += order.price

context = {
'orders': orders,
'total_revenue': total_revenue,
'total_orders': len(orders)
}
return render(request, 'restaurant/dashboard.html', context)
def test_func(self):
return self.request.user.groups.filter(name='Staff').exists()

you can see in the test_func method we are returning true if the request.user has a group with the name of Staff. If that exists, it will return true otherwise, it returns false. If it returns true it will allow the user to view the dashboard if it returns false it will send back a 403 error.

Now that we have all of the logic in our view set up, we can create the template and the url pattern for this. Let’s start with the template. We will create three templates, a base template, a navigation template and the dashboard template.

Here is the restaurant/templates/restaurant/base.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
<!-- Google Fonts -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
<!-- Bootstrap core CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet">
<!-- Material Design Bootstrap -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.19.1/css/mdb.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="fooddelivery/media/style/style.css" > <title>Deliver</title>
</head>
<body>
{% include 'restaurant/navigation.html' %}
{% block content %}
{% endblock content %}
{% include 'customer/footer.html' %}

<!-- JQuery -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!-- Bootstrap tooltips -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.4/umd/popper.min.js"></script>
<!-- Bootstrap core JavaScript -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js"></script>
<!-- MDB core JavaScript -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.19.1/js/mdb.min.js"></script>
</body>
</html>

Here is the restaurant/templates/restaurant/navigation.html:

<nav class="navbar navbar-expand-lg navbar-dark indigo">
<a class="navbar-brand" href="{% url 'index' %}">Deliver</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText"
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{% url 'account_logout' %}">Sign Out</a>
</li>
</ul>
</div>
</nav>

Finally we need to make our dashboard template. This will be pretty simple for now, we just want to show the total revenue for the day, the total number of orders for the day, and a table with all of the orders from today.

{% extends 'restaurant/base.html' %}{% block content %}<div class="container">
<div class="row justify-content-center mt-3">
<div class="card col-md-5 mr-3">
<h4 class="text-center pt-2">Today's Total Revenue</h4>
<h1 class="text-center" style="color: green;">${{ total_revenue }}</h1>
</div>
<div class="card col-md-5 mr-3">
<h4 class="text-center pt-2">Today's Total Orders</h4>
<h1 class="text-center" style="color: #ad0003;">{{ total_orders }}</h1>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-12 mt-5">
<table class="table table-hover table-striped">
<thead>
<tr>
<th scope="col">Order ID</th>
<th scope="col">Price</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Street</th>
<th scope="col">City</th>
<th scope="col">State</th>
<th scope="col">Zip Code</th>
<th scope="col">Is Paid?</th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<th scope="row">{{ order.pk }}</th>
<td>{{ order.price }}</td>
<td>{{ order.name }}</td>
<td>{{ order.email }}</td>
<td>{{ order.street }}</td>
<td>{{ order.city }}</td>
<td>{{ order.state }}</td>
<td>{{ order.zip_code }}</td>
<td>
{% if order.is_paid %}
<i style="color: green;" class="fas fa-check"></i>
{% else %}
<i style="color: red;" class="fas fa-times"></i>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock content %}

We also used a few fontawesome icons to show if the order is paid or not, if it is it will display a green checkmark, if not, it will show a red x. We can do that easily with an if statement.

Now the last step is to create the url pattern for all of this, first we will add a new urls.py file in our restaurant app and add this url pattern:

from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from .views import Dashboard
urlpatterns = [
path('dashboard/', Dashboard.as_view(), name='dashboard'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Now we need to include this file in our root urls.py file inside of our fooddelivery directory:

from django.contrib import admin
from django.urls import path, include
from customer.views import Index, About, Order, OrderConfirmation, OrderPayConfirmation
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('allauth.urls')),
path('restaurant/', include('restaurant.urls')),
path('', Index.as_view(), name='index'),
path('about/', About.as_view(), name='about'),
path('order/', Order.as_view(), name='order'),
path('order-confirmation/<int:pk>/', OrderConfirmation.as_view(), name='order-confirmation'),
path('payment-confirmation/', OrderPayConfirmation.as_view(), name='payment-confirmation'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

With all of that done, you should be able to login and view the dashboard.

That is where we will stop for today, we will come back in the future and add more details and features to the restaurant side of the application.

--

--

No responses yet