I like django-crispy-forms. You can use it for stylish uniform HTML forms with Bootstrap, TailwindCSS, or even your custom template pack. But when it comes to custom widgets and dynamic form handling, it was always a challenge. Recently I discovered htmx. It's a JavaScript framework that handles Ajax communication based on custom HTML attributes. In this article, I will explore how you can use django-crispy-forms with htmx to provide a form with server-side validation in a modal dialog.
For this experiment, I will be using these PyPI packages:
Also, I will use the CDN versions of Bootstrap5 and htmx.
I decided to add some crispy style to the login form by extending Django's authentication form and attaching a crispy helper to it.
from django.contrib.auth.forms import AuthenticationForm
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout
from crispy_bootstrap5 import bootstrap5
class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.include_media = False
self.helper.layout = Layout(
bootstrap5.FloatingField("username", autocomplete="username"),
bootstrap5.FloatingField("password", autocomplete="current-password"),
)
Here I set form_tag
to False
to skip the <form>
tag from the rendered form elements because I want to customize it in the templates. And I set include_media
to False
because I want to manually handle the media inclusion, as you will see later, instead of automatically including them in the form.
I will have two views:
from django.shortcuts import render, redirect
from django.contrib.auth import login as auth_login, logout as auth_logout
from django_htmx.http import HttpResponseClientRefresh
from .forms import LoginForm
def home(request):
if request.user.is_authenticated:
return render(request, "dashboard.html")
return render(request, "home.html", context)
def login(request):
if request.method == "POST":
form = LoginForm(request=request, data=request.POST, prefix="login")
template_name = "login_form.html"
if form.is_valid():
user = form.get_user()
auth_login(request, user)
return HttpResponseClientRefresh()
else:
form = LoginForm(request=request, prefix="login")
template_name = "login_dialog.html"
context = {"form": form}
return render(request, template_name, context)
The home view just returns different templates based on whether the user is logged in or not.
The login view handles a login form and renders different templates based on whether the view was accessed by GET or POST method. If the login succeeds, a special HttpResponseClientRefresh
response is returned, which tells htmx to refresh the page from where the login form was loaded and submitted. It's an empty response with the HX-Refresh: true
header.
Then I plug those two views into my urls.py
rules.
At the end of the base.html template, I include htmx from CDN and my custom dialog.js
:
<script src="https://unpkg.com/htmx.org@1.8.2" integrity="sha384-+8ISc/waZcRdXCLxVgbsLzay31nCdyZXQxnsUy++HJzJliTzxKWr0m1cIEMyUzQu" crossorigin="anonymous"></script>
<script src="{% static 'js/dialog.js' %}"></script>
In the home.html
template, I add a button which will open the dialog:
<button
data-hx-get="{% url 'login' %}"
data-hx-target="main"
data-hx-swap="beforeend"
type="button"
class="btn btn-primary"
>Log in</button>
Note that htmx allows either hx-*
syntax or data-hx-*
for its HTML attributes and I prefer the latter because data-*
attributes make a valid HTML document.
In the snippet above, I tell htmx to load the login page on the button click and insert it before the end of the <main>
HTML tag.
Let's have a look at my dialog.js
file:
function init_widgets_for_htmx_element(target) {
// init other widgets
// init modal dialogs
if (target.tagName === 'DIALOG') {
target.showModal();
htmx.on('.close-dialog', 'click', function(event) {
var dialog = htmx.find('dialog[open]');
dialog.close();
htmx.remove(dialog);
});
}
}
htmx.onLoad(init_widgets_for_htmx_element);
Here, when a page loads or a htmx inserts a snippet, the init_widgets_for_htmx_element
function will be called with the <body>
or the inserted element as the target
. One can use this function to initialize widgets, such as rich text fields, autocompletes, tabs, custom frontend validators, etc. Also, I use this function to open the loaded modal dialogs and add event handlers to close them.
Now the login_dialog.html
template looks like this (I just stripped the styling markup):
<dialog id="htmx-dialog">
<h1>Login</h1>
<button type="button" class="close-dialog btn-close" aria-label="Close"></button>
{% include "login_form.html" %}
<button type="submit" form="htmx-dialog-form">Log in</button>
</dialog>
And the login_form.html
looks like this:
<form
id="htmx-dialog-form"
novalidate
data-hx-post="{% url 'login' %}"
data-hx-swap="outerHTML"
>
{% load crispy_forms_tags %}
{% crispy form %}
</form>
The htmx attributes tell htmx to submit the form data to the login view by Ajax and replace the <form>
HTML tag with the response received. That is used for form validation. If the response has the HX-Refresh: true
header, as mentioned before, then the home page is refreshed.
Here is what the result looks like:
If you noticed before, we excluded the media from the form. Otherwise, the home page would load the media of its own forms (for example, a search form) and the media of the dialog forms. And that would cause double executions of shared scripts, for example, the ones for autocompletes and rich text fields.
You can nicely combine media from different forms by concatenating the media instances. This way, each CSS and Javascript file is included just once.
As the login form isn't part of the home page but instead included on demand, I need to have its media in the home view or any other view where the login button is shown.
Here comes this custom context processor for help:
def login_dialog(request):
from .forms import LoginForm
if not request.user.is_authenticated:
form = LoginForm(request=request)
if hasattr(request, "combined_media"):
request.combined_media += form.media
else:
request.combined_media = form.media
return {}
It checks for any request.combined_media
and attaches the form media from the login form.
Lastly, I attach this context processor to the template settings and render the value of combined media before </body>
:
{{ request.combined_media }}
Get the code to play with from Github. As you can see, htmx makes Ajax communications pretty straightforward, even when it's about dynamically loading and reloading parts of the content or initializing custom widgets.
Cover photo by Pixabay
When I initially created my MVP (minimal viable product) for 1st things 1st, I considered the whole Django project to be about prioritization. After a few years, I realized that the Django project is about SaaS (software as a service), and prioritization is just a part of all functionalities necessary for a SaaS to function. I ended up needing to rename apps to have clean and better-organized code. Here is how I did that.
Ensure you have the latest git pull and execute all database migrations.
Put django-rename-app
into pip requirements and install them or just run:
(venv)$ pip install django-rename-app
Put the app into INSTALLED_APPS
in your settings:
INSTALLED_APPS = [
# …
"django_rename_app",
]
Rename the oldapp
as newapp
in your apps and templates.
Rename the app in all your imports, relations, migrations, and template paths.
You can do a global search for oldapp
and then check case by case where you need to rename that term to newapp
, and where not.
Run the management command rename_app
:
(env)$ python manage.py rename_app oldapp newapp
This command renames the app prefix the app tables and the records in django_content_type
and django_migrations
tables.
If you plan to update staging or production servers, add the rename_app
command before running migrations in your deployment scripts (Ansible, Docker, etc.)
Lastly, create an empty database migration for the app with custom code to update indexes and foreign-key constraints.
(env)$ python manage.py makemigrations newapp --empty --name rename_indexes
Fill the migration with the following code:
# newapp/migrations/0002_rename_indexes.py
from django.db import migrations
def named_tuple_fetch_all(cursor):
"Return all rows from a cursor as a namedtuple"
from collections import namedtuple
desc = cursor.description
Result = namedtuple("Result", [col[0] for col in desc])
return [Result(*row) for row in cursor.fetchall()]
def rename_indexes(apps, schema_editor):
from django.db import connection
with connection.cursor() as cursor:
cursor.execute(
"""SELECT indexname FROM pg_indexes
WHERE tablename LIKE 'newapp%'"""
)
for result in named_tuple_fetch_all(cursor):
old_index_name = result.indexname
new_index_name = old_index_name.replace(
"oldapp_", "newapp_", 1
)
cursor.execute(
f"""ALTER INDEX IF EXISTS {old_index_name}
RENAME TO {new_index_name}"""
)
def rename_foreignkeys(apps, schema_editor):
from django.db import connection
with connection.cursor() as cursor:
cursor.execute(
"""SELECT table_name, constraint_name
FROM information_schema.key_column_usage
WHERE constraint_catalog=CURRENT_CATALOG
AND table_name LIKE 'newapp%'
AND position_in_unique_constraint notnull"""
)
for result in named_tuple_fetch_all(cursor):
table_name = result.table_name
old_foreignkey_name = result.constraint_name
new_foreignkey_name = old_foreignkey_name.replace(
"oldapp_", "newapp_", 1
)
cursor.execute(
f"""ALTER TABLE {table_name}
RENAME CONSTRAINT {old_foreignkey_name}
TO {new_foreignkey_name}"""
)
class Migration(migrations.Migration):
dependencies = [
("newapp", "0001_initial"),
]
operations = [
migrations.RunPython(rename_indexes, migrations.RunPython.noop),
migrations.RunPython(rename_foreignkeys, migrations.RunPython.noop),
]
Run the migrations:
(env)$ python manage.py migrate
If something doesn't work as wanted, migrate back, fix the code, and migrate again. You can unmigrate by migrating to one step before the last migration, for example:
(env)$ python manage.py migrate 0001
After applying the migration in all necessary environments, you can clean them up by removing django-rename-app
from your pip requirements and deployment scripts.
It's rarely possible to build a system that meets all your needs from the beginning. Proper systems always require continuous improvement and refactoring. Using a combination of Django migrations and django-rename-app
, you can work on your websites in an Agile, clean, and flexible way.
Happy coding!
Cover photo by freestocks.
As you might know, I have been developing, providing, and supporting the prioritization tool 1st things 1st. One of the essential features to implement was exporting calculated priorities to other productivity tools. Usually, building an export from one app to another takes 1-2 weeks for me. But this time, I decided to go a better route and use Zapier to export priorities to almost all possible apps in a similar amount of time. Whaaat!?? In this article, I will tell you how.
The no-code tool Zapier takes input from a wide variety of web apps and outputs it to many other apps. Optionally you can filter the input based on conditions. Or format the input differently (for example, convert HTML to Markdown). In addition, you can stack the output actions one after the other. Usually, people use 2-3 steps for their automation, but there are power users who create 50-step workflows.
The input is managed by Zapier's triggers. The output is controlled by Zapier's actions. These can be configured at the website UI or using a command-line tool. I used the UI as this was my first integration. Trigger events accept a JSON feed of objects with unique IDs. Each new item there is treated as a new input item. With a free tier, the triggers are checked every 15 minutes. Multiple triggers are handled in parallel, and the sorting order of execution is not guaranteed. As it is crucial to have the sorting order correct for 1st things 1st priorities, people from Zapier support suggested providing each priority with a 1-minute interval to make sure the priorities get listed in the target app sequentially.
The most challenging part of Zapier integration was setting up OAuth 2.0 provider. Even though I used a third-party Django app django-oauth-toolkit for that. Zapier accepts other authentication options too, but this one is the least demanding for the end-users.
OAuth 2.0 allows users of one application to use specific data of another application while keeping private information private. You might have used the OAuth 2.0 client directly or via a wrapper for connecting to Twitter apps. For Zapier, one has to set OAuth 2.0 provider.
The official tutorial for setting up OAuth 2.0 provider with django-oauth-toolkit
is a good start. However, one problem with it is that by default, any registered user can create OAuth 2.0 applications at your Django website, where in reality, you need just one global application.
First of all, I wanted to allow OAuth 2.0 application creation only for superusers.
For that, I created a new Django app oauth2_provider_adjustments
with modified views and URLs to use instead of the ones from django-oauth-toolkit
.
The views related to OAuth 2.0 app creation extended this SuperUserOnlyMixin
instead of LoginRequiredMixin
:
from django.contrib.auth.mixins import AccessMixin
class SuperUserOnlyMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_superuser:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
Then I replaced the default oauth2_provider
URLs:
urlpatterns = [
# …
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
]
with my custom ones:
urlpatterns = [
# …
path("o/", include("oauth2_provider_adjustments.urls", namespace="oauth2_provider")),
]
I set the new OAuth 2.0 application by going to /o/applications/register/
and filling in this info:
Name: Zapier
Client type: Confidential
Authorization grant type: Authorization code
Redirect uris: https://zapier.com/dashboard/auth/oauth/return/1stThings1stCLIAPI/
(copied from Zapier)
Algorithm: No OIDC support
If you have some expertise in the setup choices and see any flaws, let me know.
Zapier requires creating a test view that will return anything to check if there are no errors authenticating a user with OAuth 2.0. So I made a simple JSON view like this:
from django.http.response import JsonResponse
def user_info(request, *args, **kwargs):
if not request.user.is_authenticated:
return JsonResponse(
{
"error": "User not authenticated",
},
status=200,
)
return JsonResponse(
{
"first_name": request.user.first_name,
"last_name": request.user.last_name,
},
status=200,
)
Also, I had to have login and registration views for those cases when the user's session was not present.
Lastly, at Zapier, I had to set these values for OAuth 2.0:
Client ID: The Client ID from registered app
Client Secret: The Client Secret from registered app
Authorization URL: https://apps.1st-things-1st.com/o/authorize/
Scope: read write
Access Token Request: https://apps.1st-things-1st.com/o/token/
Refresh Token Request: https://apps.1st-things-1st.com/o/token/
I want to automatically refresh on unauthorized error:
Checked
Test: https://apps.1st-things-1st.com/user-info/
Connection Label: {{first_name}} {{last_name}}
There are two types of triggers in Zapier:
The feeds for triggers should (ideally) be paginated. But without meta information for the item count, page number, following page URL, etc., you would usually have with django-rest-framework
or other REST frameworks. Provide only an array of objects with unique IDs for each page. The only field name that matters is "id" – others can be anything. Here is an example:
[
{
"id": "39T7NsgQarYf",
"project": "5xPrQbPZNvJv",
"title": "01. Custom landing pages for several project types (83%)",
"plain_title": "Custom landing pages for several project types",
"description": "",
"score": 83,
"priority": 1,
"category": "Choose"
},
{
"id": "4wBSgq3spS49",
"project": "5xPrQbPZNvJv",
"title": "02. Zapier integration (79%)",
"plain_title": "Zapier integration",
"description": "",
"score": 79,
"priority": 2,
"category": "Choose"
},
{
"id": "6WvwwB7QAnVS",
"project": "5xPrQbPZNvJv",
"title": "03. Electron.js desktop app for several project types (42%)",
"plain_title": "Electron.js desktop app for several project types",
"description": "",
"score": 41,
"priority": 3,
"category": "Consider"
}
]
The feeds should list items in reverse order for the (A) type of triggers: the newest things go at the beginning. The pagination is only used to cut the number of items: the second and further pages of the paginated list are ignored by Zapier.
In my specific case of priorities, the order matters, and no items should be lost in the void. So I listed the priorities sequentially (not newest first) and set the number of items per page unrealistically high so that you basically get all the things on the first page of the feed.
The feeds for the triggers of (B) type are normally paginated from the first page until the page returns empty results. The order should be alphabetical, chronological, or by sorting order field, whatever makes sense. There you need just two fields, the ID and the title of the item (but more fields are allowed too), for example:
[
{
"id": "5xPrQbPZNvJv",
"title": "1st things 1st",
"owner": "Aidas Bendoraitis"
},
{
"id": "VEXGzThxL6Sr",
"title": "Make Impact",
"owner": "Aidas Bendoraitis"
},
{
"id": "WoqQbuhdUHGF",
"title": "DjangoTricks website",
"owner": "Aidas Bendoraitis"
},
]
I used django-rest-framework
to implement the API because of the batteries included, such as browsable API, permissions, serialization, pagination, etc.
For the specific Zapier requirements, I had to write a custom pagination class, SimplePagination
, to use with my API lists. It did two things: omitted the meta section and showed an empty list instead of a 404 error for pages that didn't have any results:
from django.core.paginator import InvalidPage
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
class SimplePagination(PageNumberPagination):
page_size = 20
def get_paginated_response(self, data):
return Response(data) # <-- Simple pagination without meta
def get_paginated_response_schema(self, schema):
return schema # <-- Simple pagination without meta
def paginate_queryset(self, queryset, request, view=None):
"""
Paginate a queryset if required, either returning a
page object, or `None` if pagination is not configured for this view.
"""
page_size = self.get_page_size(request)
if not page_size:
return None
paginator = self.django_paginator_class(queryset, page_size)
page_number = self.get_page_number(request, paginator)
try:
self.page = paginator.page(page_number)
except InvalidPage as exc:
msg = self.invalid_page_message.format(
page_number=page_number, message=str(exc)
)
return [] # <-- If no items found, don't raise NotFound error
if paginator.num_pages > 1 and self.template is not None:
# The browsable API should display pagination controls.
self.display_page_controls = True
self.request = request
return list(self.page)
To preserve the order of items, I had to make the priorities appear one by one at 1-minute intervals. I did that by having a Boolean field exported_to_zapier
at the priorities. The API showed priorities only if that field was set to True
, which wasn't the case by default. Then, background tasks were scheduled 1 minute after each other, triggered by a button click at 1st things 1st, which set the exported_to_zapier
to True
for each next priority. I was using huey
, but the same can be achieved with Celery
, cron jobs, or other background task manager:
# zapier_api/tasks.py
from django.conf import settings
from django.utils.translation import gettext
from huey.contrib.djhuey import db_task
@db_task()
def export_next_initiative_to_zapier(project_id):
from evaluations.models import Initiative
next_initiatives = Initiative.objects.filter(
project__pk=project_id,
exported_to_zapier=False,
).order_by("-total_weight", "order")
count = next_initiatives.count()
if count > 0:
next_initiative = next_initiatives.first()
next_initiative.exported_to_zapier = True
next_initiative.save(update_fields=["exported_to_zapier"])
if count > 1:
result = export_next_initiative_to_zapier.schedule(
kwargs={"project_id": project_id},
delay=settings.ZAPIER_EXPORT_DELAY,
)
result(blocking=False)
One gotcha: Zapier starts pagination from 0, whereas django-rest-framework
starts pagination from 1. To make them work together, I had to modify the API request (written in JavaScript) at Zapier trigger configuration:
const options = {
url: 'https://apps.1st-things-1st.com/api/v1/projects/',
method: 'GET',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${bundle.authData.access_token}`
},
params: {
'page': bundle.meta.page + 1 // <-- The custom line for pagination
}
}
return z.request(options)
.then((response) => {
response.throwForStatus();
const results = response.json;
// You can do any parsing you need for results here before returning them
return results;
});
For the v1 of Zapier integration, I didn't need any Zapier actions, so they are yet something to explore, experiment with, and learn about. But the Zapier triggers seem already pretty helpful and a big win compared to individual exports without this tool.
If you want to try the result, do this:
Cover photo by Anna Nekrashevich
When you have some generic functionality like anything commentable, likable, or upvotable, it’s common to use Generic Relations in Django. The problem with Generic Relations is that they create the relationships at the application level instead of the database level, and that requires a lot of database queries if you want to aggregate content that shares the generic functionality. There is another way that I will show you in this article.
I learned this technique at my first job in 2002 and then rediscovered it again with Django a few years ago. The trick is to have a generic Item
model where every other autonomous model has a one-to-one relation to the Item
. Moreover, the Item
model has an item_type
field, allowing you to recognize the backward one-to-one relationship.
Then whenever you need to have some generic categories, you link them to the Item.
Whenever you create generic functionality like media gallery, comments, likes, or upvotes, you attach them to the Item
. Whenever you need to work with permissions, publishing status, or workflows, you deal with the Item
. Whenever you need to create a global search or trash bin, you work with the Item
instances.
Let’s have a look at some code.
First, I'll create the items
app with two models: the previously mentioned Item
and the abstract model ItemBase
with the one-to-one relation for various models to inherit:
# items/models.py
import sys
from django.db import models
from django.apps import apps
if "makemigrations" in sys.argv:
from django.utils.translation import gettext_noop as _
else:
from django.utils.translation import gettext_lazy as _
class Item(models.Model):
"""
A generic model for all autonomous models to link to.
Currently these autonomous models are available:
- content.Post
- companies.Company
- accounts.User
"""
ITEM_TYPE_CHOICES = (
("content.Post", _("Post")),
("companies.Company", _("Company")),
("accounts.User", _("User")),
)
item_type = models.CharField(
max_length=200, choices=ITEM_TYPE_CHOICES, editable=False, db_index=True
)
class Meta:
verbose_name = _("Item")
verbose_name_plural = _("Items")
def __str__(self):
content_object_title = (
str(self.content_object) if self.content_object else "BROKEN REFERENCE"
)
return (
f"{content_object_title} ({self.get_item_type_display()})"
)
@property
def content_object(self):
app_label, model_name = self.item_type.split(".")
model = apps.get_model(app_label, model_name)
return model.objects.filter(item=self).first()
class ItemBase(models.Model):
"""
An abstract model for the autonomous models that will link to the Item.
"""
item = models.OneToOneField(
Item,
verbose_name=_("Item"),
editable=False,
blank=True,
null=True,
on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s",
)
class Meta:
abstract = True
def save(self, *args, **kwargs):
if not self.item:
model = type(self)
item = Item.objects.create(
item_type=f"{model._meta.app_label}.{model.__name__}"
)
self.item = item
super().save()
def delete(self, *args, **kwargs):
if self.item:
self.item.delete()
super().delete(*args, **kwargs)
Then let's create some autonomous models that will have one-to-one relations with the Item
. By "autonomous models," I mean those which are enough by themselves, such as posts, companies, or accounts. Models like types, categories, tags, or likes, wouldn't be autonomous.
Second, I create the content
app with the Post
model. This model extends ItemBase
which will create the one-to-one relation on save, and will define the item_type
as content.Post
:
# content/models.py
import sys
from django.contrib.auth.base_user import BaseUserManager
from django.db import models
from django.contrib.auth.models import AbstractUser
if "makemigrations" in sys.argv:
from django.utils.translation import gettext_noop as _
else:
from django.utils.translation import gettext_lazy as _
from items.models import ItemBase
class Post(ItemBase):
title = models.CharField(_("Title"), max_length=255)
slug = models.SlugField(_("Slug"), max_length=255)
content = models.TextField(_("Content"))
class Meta:
verbose_name = _("Post")
verbose_name_plural = _("Posts")
Third, I create the companies
app with the Company
model. This model also extends ItemBase
which will create the one-to-one relation on save, and will define the item_type
as companies.Company
:
# companies/models.py
import sys
from django.contrib.auth.base_user import BaseUserManager
from django.db import models
from django.contrib.auth.models import AbstractUser
if "makemigrations" in sys.argv:
from django.utils.translation import gettext_noop as _
else:
from django.utils.translation import gettext_lazy as _
from items.models import ItemBase
class Company(ItemBase):
name = models.CharField(_("Name"), max_length=255)
slug = models.SlugField(_("Slug"), max_length=255)
description = models.TextField(_("Description"))
class Meta:
verbose_name = _("Company")
verbose_name_plural = _("Companies")
Fourth, I'll have a more extensive example with the accounts
app containing the User
model. This model extends AbstractUser
from django.contrib.auth
as well as ItemBase
for the one-to-one relation. The item_type
set at the Item
model will be accounts.User
:
# accounts/models.py
import sys
from django.db import models
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
if "makemigrations" in sys.argv:
from django.utils.translation import gettext_noop as _
else:
from django.utils.translation import gettext_lazy as _
from items.models import ItemBase
class UserManager(BaseUserManager):
def create_user(self, username="", email="", password="", **extra_fields):
if not email:
raise ValueError("Enter an email address")
email = self.normalize_email(email)
user = self.model(username=username, email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username="", email="", password=""):
user = self.create_user(email=email, password=password, username=username)
user.is_superuser = True
user.is_staff = True
user.save(using=self._db)
return user
class User(AbstractUser, ItemBase):
# change username to non-editable non-required field
username = models.CharField(
_("Username"), max_length=150, editable=False, blank=True
)
# change email to unique and required field
email = models.EmailField(_("Email address"), unique=True)
bio = models.TextField(_("Bio"))
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
objects = UserManager()
I will use the Django shell to create several autonomous model instances and the related Items too:
>>> from content.models import Post
>>> from companies.models import Company
>>> from accounts.models import User
>>> from items.models import Item
>>> post = Post.objects.create(
... title="Hello, World!",
... slug="hello-world",
... content="Lorem ipsum…",
... )
>>> company = Company.objects.create(
... name="Aidas & Co",
... slug="aidas-co",
... description="Lorem ipsum…",
... )
>>> user = User.objects.create_user(
... username="aidas",
... email="aidas@example.com",
... password="jdf234oha&6sfhasdfh",
... )
>>> Item.objects.count()
3
Lastly, here is an example of having posts, companies, and users in a single view. For that, we will use the Item
queryset with annotations:
from django import forms
from django.db import models
from django.shortcuts import render
from django.utils.translation import gettext, gettext_lazy as _
from .models import Item
class SearchForm(forms.Form):
q = forms.CharField(label=_("Search"), required=False)
def all_items(request):
qs = Item.objects.annotate(
title=models.Case(
models.When(
item_type="content.Post",
then="content_post__title",
),
models.When(
item_type="companies.Company",
then="companies_company__name",
),
models.When(
item_type="accounts.User",
then="accounts_user__email",
),
default=models.Value(gettext("<Untitled>")),
),
description=models.Case(
models.When(
item_type="content.Post",
then="content_post__content",
),
models.When(
item_type="companies.Company",
then="companies_company__description",
),
models.When(
item_type="accounts.User",
then="accounts_user__bio",
),
default=models.Value(""),
),
)
form = SearchForm(data=request.GET, prefix="search")
if form.is_valid():
query = form.cleaned_data["q"]
if query:
qs = qs.annotate(
search=SearchVector(
"title",
"description",
)
).filter(search=query)
context = {
"queryset": qs,
"search_form": form,
}
return render(request, "items/all_items.html", context)
You can have generic functionality and still avoid multiple hits to the database by using the Item
one-to-one approach instead of generic relations.
The name of the Item
model can be different, and you can even have multiple such models for various purposes, for example, TaggedItem
for tags only.
Do you use anything similar in your projects?
Do you see how this approach could be improved?
Let me know in the comments!
Cover picture by Pixabay
For more than a decade, I was focused only on the technical part of website building with Django. In the process, I have built a bunch of interesting cultural websites. But I always felt that those sleepless nights were not worthy of the impact.
They say, "Don’t work hard, work smart!" I agree with that phrase, and for me it's not about working less hours. For me, it's working as much as necessary, but on things that matter most.
So after years of collecting facts about life, I connected the dots and came up with make-impact.org – a social donation platform, which became one of the most important long-term projects. All my planning goes around this project.
And I believe I am not the only programmer who sometimes feels that they want to make a positive impact with their skills. So I brainstormed 17 Django project ideas. You can choose one and realize it as a hobby project, open-source platform, startup, or non-profit organization; alone, with a team of developers, or collaborating with some non-technical people.
The job market is pretty competitive, and not all people can keep up with the train. You could build a job search website for jobs that don't require high education or lots of working experience. It could be helpful for people with language barriers, harsh living conditions, or those who are very young or very old. You could build it for your city, region, or country.
Get inspired from Too Good To Go and build a progressive web app for your city about discounted restaurant meals and shop products whose expiration date is close to the end, but they are still good to eat.
Build a website for setting your personal health improvement goals and tracking the progress. For example, maybe one wants to start eating more particular vegetables every week, jogging daily, lose or gain weight, or get rid of unhealthy addictions. Let people choose their health goals and check in with each progressive step. Allow using the website anonymously.
Some people don't have access to schools in general or miss some classes because of illnesses. You could build a global and open wiki-based primary and elementary school education website for children and adults. It should be translatable and localizable. It would also be interesting to compare the same subject teachings in different countries side-by-side.
You could build a website with a video chat providing psychological support to discriminated or violently abused women. The help could be given by professionals or emphatic volunteers. The technical part can be implemented using django-channels, WebSockets, and WebRTC.
Rain harvesting is one of the available ways to solve the problem of the lack of drinking water. There could be a platform comparing rain-harvesting companies all around the world. What are the installation prices? What are the countries they are working with? How many people have they saved? This website would allow people to find the most optimal company to build a rain harvesting system for them.
Use the Open Charge Map API and create a progressive web app that shows the nearest electric car charging station and how to get there.
As remote jobs are getting more and more popular, there is still a matter of trust between the employees and employers. "Will the job taker complete their job in a good quality?" "Will the company pay the employee on time?" There are Escrow services to fix this issue. These are third parties that take and hold the money until the job is done. You could build a remote job search website promoting the usage of Escrow.com or another escrow service provider.
You could build a website listing coworking spaces and cafes with free wifi in your city. It should include the map, price ranges, details if registration is required, and other information necessary for remote workers.
There could be a social website listing the most admired companies to work for in your country. Companies could be rated by working conditions, salary equality, growth opportunities, work relations, and other criteria. Anyone could suggest such a company, and they would be rated by their current and former employees anonymously.
The cost of accommodation is a critical problem in many locations of the world. You could develop a website that lists examples of tiny houses and their building schemas and instructions.
You could work on a product catalog with links to online shops, selling things produced from collected plastic. For example, these sunglasses are made of plastic collected from the ocean. Where available, you could use affiliate marketing links.
You could work on a website for climate-change migrants with information about getting registered, housing, education, and jobs in a new city or country with better climate conditions.
Scrape parts of FishBase and create a website about fishes, fishing, and overfishing in your region or the world. Engage people about the marine world and inform them about the damage done by overfishing.
Create an E-commerce shop or Software as a Service and integrate RaaS (Reforestation as a Service). Let a tree be planted for every sale.
Create a progressive web app about positive parenting. For inspiration and information check this article.
Create a forum with topic voting and automatic hate speech detection and flagging. For example, maybe you could use a combination of Sentiment analysis from text-processing.com and usage of profanity words to find negativity in forum posts.
I hope this post inspired you. If you decided to start a startup with one of those ideas, don't forget to do your research at first. What are the competitors in your area? What would be your unique selling point? Etc.
Also, it would be interesting to hear your thoughts. Which of the projects would seem to you the most crucial? Which of them would you like to work on?
Cover photo by Joshua Fuller
The goal of this article is to discuss the caveats of the default Django user model implementation and also to give you some advice on how to address them. It is important to know the limitations of the current implementation so to avoid the most common pitfalls.
Something to keep in mind is that the Django user model is heavily based on its initial implementation that is at least 16 years old. Because user and authentication is a core part of the majority of the web applications using Django, most of its quirks persisted on the subsequent releases so to maintain backward compatibility.
The good news is that Django offers many ways to override and customize its default implementation so to fit your application needs. But some of those changes must be done right at the beginning of the project, otherwise it would be too much of a hassle to change the database structure after your application is in production.
Below, the topics that we are going to cover in this article:
First, let’s explore the caveats and next we discuss the options.
Even though the username
field is marked as unique, by default it is not case-sensitive. That means the username
john.doe
and John.doe
identifies two different users in your application.
This can be a security issue if your application has social aspects that builds around the username
providing a
public URL to a profile like Twitter, Instagram or GitHub for example.
It also delivers a poor user experience because people doesn’t expect that john.doe
is a different username than
John.Doe
, and if the user does not type the username exactly in the same way when they created their account, they
might be unable to log in to your application.
Possible Solutions:
CharField
with the CICharField
instead (which is case-insensitive)get_by_natural_key
from the UserManager
to query the database using iexact
ModelBackend
implementationThis is not necessarily an issue, but it is important for you to understand what that means and what are the effects.
By default the username field accepts letters, numbers and the characters: @
, .
, +
, -
, and _
.
The catch here is on which letters it accepts.
For example, joão
would be a valid username. Similarly, Джон
or 約翰
would also be a valid username.
Django ships with two username validators: ASCIIUsernameValidator
and UnicodeUsernameValidator
. If the intended
behavior is to only accept letters from A-Z, you may want to switch the username validator to use ASCII letters only
by using the ASCIIUsernameValidator
.
Possible Solutions:
ASCIIUsernameValidator
Multiple users can have the same email address associated with their account.
By default the email is used to recover a password. If there is more than one user with the same email address, the password reset will be initiated for all accounts and the user will receive an email for each active account.
It also may not be an issue but this will certainly make it impossible to offer the option to authenticate the user using the email address (like those sites that allow you to login with username or email address).
Possible Solutions:
AbstractBaseUser
to define the email field from scratchBy default the email field does not allow null
, however it allow blank
values, so it pretty much allows users to
not inform a email address.
Also, this may not be an issue for your application. But if you intend to allow users to log in with email it may be a good idea to enforce the registration of this field.
When using the built-in resources like user creation forms or when using model forms you need to pay attention to this detail if the desired behavior is to always have the user email.
Possible Solutions:
AbstractBaseUser
to define the email field from scratchThere is a small catch on the user creation process that if the set_password
method is called passing None
as a
parameter, it will produce an unusable password. And that also means that the user will be unable to start a password
reset to set the first password.
You can end up in that situation if you are using social networks like Facebook or Twitter to allow the user to create an account on your website.
Another way of ending up in this situation is simply by creating a user using the User.objects.create_user()
or
User.objects.create_superuser()
without providing an initial password.
Possible Solutions:
Changing the user model is something you want to do early on. After your database schema is generated and your database is populated it will be very tricky to swap the user model.
The reason why is that you are likely going to have some foreign key created referencing the user table, also Django internal tables will create hard references to the user table. And if you plan to change that later on you will need to change and migrate the database by yourself.
Possible Solutions:
AbstractUser
and change a single configuration on the
settings module. This will give you a tremendous freedom and it will make things way easier in the future should the
requirements change.To address the limitations we discussed in this article we have two options: (1) implement workarounds to fix the behavior of the default user model; (2) replace the default user model altogether and fix the issues for good.
What is going to dictate what approach you need to use is in what stage your project currently is.
django.contrib.auth.models.User
, go
with the first solution implementing the workarounds;First let’s have a look on a few workarounds that you can implement if you project is already in production. Keep in
mind that those solutions assume that you don’t have direct access to the User model, that is, you are currently using
the default User model importing it from django.contrib.auth.models
.
If you did replace the User model, then jump to the next section to get better tips on how to fix the issues.
Before making any changes you need to make sure you don’t have conflicting usernames on your database. For example,
if you have a User with the username maria
and another with the username Maria
you have to plan a data migration
first. It is difficult to tell you what to do because it really depends on how you want to handle it. One option is
to append some digits after the username, but that can disturb the user experience.
Now let’s say you checked your database and there are no conflicting usernames and you are good to go.
First thing you need to do is to protect your sign up forms to not allow conflicting usernames to create accounts.
Then on your user creation form, used to sign up, you could validate the username like this:
If you are handling user creation in a rest API using DRF, you can do something similar in your serializer:
In the previous example the mentioned ValidationError
is the one defined in the DRF.
The iexact
notation on the queryset parameter will query the database ignoring the case.
Now that the user creation is sanitized we can proceed to define a custom authentication backend.
Create a module named backends.py anywhere in your project and add the following snippet:
backends.py
Now switch the authentication backend in the settings.py module:
settings.py
Please note that 'mysite.core.backends.CaseInsensitiveModelBackend'
must be changed to the valid path, where you
created the backends.py module.
It is important to have handled all conflicting users before changing the authentication backend because otherwise it
could raise a 500 exception MultipleObjectsReturned
.
Here we can borrow the built-in UsernameField
and customize it to append the ASCIIUsernameValidator
to the list of
validators:
Then on the Meta
of your User creation form you can replace the form field class:
Here all you can do is to sanitize and handle the user input in all views where you user can modify its email address.
You have to include the email field on your sign up form/serializer as well.
Then just make it mandatory like this:
You can also check a complete and detailed example of this form on the project shared together with this post: userworkarounds
Now I’m going to show you how I usually like to extend and replace the default User model. It is a little bit verbose but that is the strategy that will allow you to access all the inner parts of the User model and make it better.
To replace the User model you have two options: extending the AbstractBaseUser
or extending the AbstractUser
.
To illustrate what that means I draw the following diagram of how the default Django model is implemented:
The green circle identified with the label User
is actually the one you import from django.contrib.auth.models
and
that is the implementation that we discussed in this article.
If you look at the source code, its implementation looks like this:
So basically it is just an implementation of the AbstractUser
. Meaning all the fields and logic are implemented in the
abstract class.
It is done that way so we can easily extend the User
model by creating a sub-class of the AbstractUser
and add other
features and fields you like.
But there is a limitation that you can’t override an existing model field. For example, you can re-define the email field to make it mandatory or to change its length.
So extending the AbstractUser
class is only useful when you want to modify its methods, add more fields or swap the
objects
manager.
If you want to remove a field or change how the field is defined, you have to extend the user model from the
AbstractBaseUser
.
The best strategy to have full control over the user model is creating a new concrete class from the PermissionsMixin
and the AbstractBaseUser
.
Note that the PermissionsMixin
is only necessary if you intend to use the Django admin or the built-in permissions
framework. If you are not planning to use it you can leave it out. And in the future if things change you can add
the mixin and migrate the model and you are ready to go.
So the implementation strategy looks like this:
Now I’m going to show you my go-to implementation. I always use PostgreSQL which, in my opinion, is the best database
to use with Django. At least it is the one with most support and features anyway. So I’m going to show an approach
that use the PostgreSQL’s CITextExtension
. Then I will show some options if you are using other database engines.
For this implementation I always create an app named accounts
:
Then before adding any code I like to create an empty migration to install the PostgreSQL extensions that we are going to use:
Inside the migrations
directory of the accounts
app you will find an empty migration called
0001_postgres_extensions.py
.
Modify the file to include the extension installation:
migrations/0001_postgres_extensions.py
Now let’s implement our model. Open the models.py
file inside the accounts
app.
I always grab the initial code directly from Django’s source on GitHub, copying the AbstractUser
implementation, and
modify it accordingly:
accounts/models.py
Let’s review what we changed here:
username_validator
to use ASCIIUsernameValidator
username
field now is using CICharField
which is not case-sensitiveemail
field is now mandatory, unique and is using CIEmailField
which is not case-sensitiveOn the settings module, add the following configuration:
settings.py
Now we are ready to create our migrations:
Apply the migrations:
And you should get a similar result if you are just creating your project and if there is no other models/apps:
If you check your database scheme you will see that there is no auth_user
table (which is the default one), and now
the user is stored on the table accounts_customuser
:
And all the Foreign Keys to the user model will be created pointing to this table. That’s why it is important to do it right in the beginning of your project, before you created the database scheme.
Now you have all the freedom. You can replace the first_name
and last_name
and use just one field called name
.
You could remove the username
field and identify your User model with the email
(then just make sure you change
the property USERNAME_FIELD
to email
).
You can grab the source code on GitHub: customuser
If you are not using PostgreSQL and want to implement case-insensitive authentication and you have direct access to the User model, a nice hack is to create a custom manager for the User model, like this:
accounts/models.py
Then you could also sanitize the username field on the clean()
method to always save it as lowercase so you don’t have
to bother having case variant/conflicting usernames:
In this tutorial we discussed a few caveats of the default User model implementation and presented a few options to address those issues.
The takeaway message here is: always replace the default User model.
If your project is already in production, don’t panic: there are ways to fix those issues following the recommendations in this post.
I also have two detailed blog posts on how to make the username field case-insensitive and other about how to extend the django user model:
You can also explore the source code presented in this post on GitHub:
In this tutorial I’m going to show you how I usually start and organize a new Django project nowadays. I’ve tried many different configurations and ways to organize the project, but for the past 4 years or so this has been consistently my go-to setup.
Please note that this is not intended to be a “best practice” guide or to fit every use case. It’s just the way I like to use Django and that’s also the way that I found that allow your project to grow in healthy way.
Index
Usually those are the premises I take into account when setting up a project:
Usually I work with three environment dimensions in my code: local, tests and production. I like to see it
as a “mode” how I run the project. What dictates which mode I’m running the project is which settings.py
I’m currently
using.
The local dimension always come first. It is the settings and setup that a developer will use on their local machine.
All the defaults and configurations must be done to attend the local development environment first.
The reason why I like to do it that way is that the project must be as simple as possible for a new hire to clone the repository, run the project and start coding.
The production environment usually will be configured and maintained by experienced developers and by those who are more familiar with the code base itself. And because the deployment should be automated, there is no reason for people being re-creating the production server over and over again. So it is perfectly fine for the production setup require a few extra steps and configuration.
The tests environment will be also available locally, so developers can test the code and run the static checks.
But the idea of the tests environment is to expose it to a CI environment like Travis CI, Circle CI, AWS Code Pipeline, etc.
It is a simple setup that you can install the project and run all the unit tests.
The production dimension is the real deal. This is the environment that goes live without the testing and debugging utilities.
I also use this “mode” or dimension to run the staging server.
A staging server is where you roll out new features and bug fixes before applying to the production server.
The idea here is that your staging server should run in production mode, and the only difference is going to be your static/media server and database server. And this can be achieved just by changing the configuration to tell what is the database connection string for example.
But the main thing is that you should not have any conditional in your code that checks if it is the production or staging server. The project should run exactly in the same way as in production.
Right from the beginning it is a good idea to setup a remote version control service. My go-to option is Git on GitHub. Usually I create the remote repository first then clone it on my local machine to get started.
Let’s say our project is called simple
, after creating the repository on GitHub I will create a directory named
simple
on my local machine, then within the simple
directory I will clone the repository, like shown on the
structure below:
Then I create the virtualenv
outside of the Git repository:
Then alongside the simple
and venv
directories I may place some other support files related to the project which I
do not plan to commit to the Git repository.
The reason I do that is because it is more convenient to destroy and re-create/re-clone both the virtual environment or the repository itself.
It is also good to store your virtual environment outside of the git repository/project root so you don’t need to bother ignoring its path when using libs like flake8, isort, black, tox, etc.
You can also use tools like virtualenvwrapper
to manage your virtual environments, but I prefer doing it that way
because everything is in one place. And if I no longer need to keep a given project on my local machine, I can delete
it completely without leaving behind anything related to the project on my machine.
The next step is installing Django inside the virtualenv so we can use the django-admin
commands.
Inside the simple
directory (where the git repository was cloned) start a new project:
Attention to the .
in the end of the command. It is necessary to not create yet another directory called simple
.
So now the structure should be something like this:
At this point I already complement the project package directory with three extra directories for templates
, static
and locale
.
Both templates
and static
we are going to manage at a project-level and app-level. Those are refer to the global
templates and static files.
The locale
is necessary in case you are using i18n
to translate your application to other languages. So here
is where you are going to store the .mo
and .po
files.
So the structure now should be something like this:
Inside the project root (2) I like to create a directory called requirements
with all the .txt
files, breaking down
the project dependencies like this:
base.txt
: Main dependencies, strictly necessary to make the project run. Common to all environmentstests.txt
: Inherits from base.txt
+ test utilitieslocal.txt
: Inherits from tests.txt
+ development utilitiesproduction.txt
: Inherits from base.txt
+ production only dependenciesNote that I do not have a staging.txt
requirements file, that’s because the staging environment is going to use the
production.txt
requirements so we have an exact copy of the production environment.
Now let’s have a look inside each of those requirements file and what are the python libraries that I always use no matter what type of Django project I’m developing.
base.txt
.env
files in a safe waysettings.py
module. It also helps with decoupling configuration from source codetests.txt
The -r base.txt
inherits all the requirements defined in the base.txt
file
local.txt
The -r tests.txt
inherits all the requirements defined in the base.txt
and tests.txt
file
production.txt
The -r base.txt
inherits all the requirements defined in the base.txt
file
Also following the environments and modes premise I like to setup multiple settings modules. Those are going to serve as the entry point to determine in which mode I’m running the project.
Inside the simple
project package, I create a new directory called settings
and break down the files like this:
Note that I removed the settings.py
that used to live inside the simple/ (3)
directory.
The majority of the code will live inside the base.py
settings module.
Everything that we can set only once in the base.py
and change its value using python-decouple
we should keep in the
base.py
and never repeat/override in the other settings modules.
After the removal of the main settings.py
a nice touch is to modify the manage.py
file to set the
local.py
as the default settings module so we can still run commands like python manage.py runserver
without any
further parameters:
manage.py
Now let’s have a look on each of those settings modules.
base.py
A few comments on the overall base settings file contents:
config()
are from the python-decouple
library. It is exposing the configuration to an environment variable and
retrieving its value accordingly to the expected data type. Read more about python-decouple
on this guide:
How to Use Python DecoupleSECRET_KEY
, DEBUG
and ALLOWED_HOSTS
defaults to local/development environment values.
That means a new developer won’t need to set a local .env
and provide some initial value to run locallydj_database_url
to translate this one line string to a Python
dictionary as Django expectsMEDIA_ROOT
we are navigating two directories up to create a media
directory outside the git
repository but inside our project workspace (inside the directory simple/ (1)
). So everything is handy and we won’t
be committing test uploads to our repositorybase.py
settings I reserve two blocks for third-party Django libraries that I may install, such
as Django Rest Framework or Django Crispy Forms. And the first-party settings refer to custom settings that I may create
exclusively for our project. Usually I will prefix them with the project name, like SIMPLE_XXX
local.py
Here is where I will setup Django Debug Toolbar for example. Or set the email backend to display the sent emails on console instead of having to setup a valid email server to work on the project.
All the code that is only relevant for the development process goes here.
You can use it to setup other libs like Django Silk to run profiling without exposing it to production.
tests.py
Here I add configurations that help us run the test cases faster. Sometimes disabling the migrations may not work if you have interdependencies between the apps models so Django may fail to create a database without the migrations.
In some projects it is better to keep the test database after the execution.
production.py
The most important part here on the production settings is to enable all the security settings Django offer. I like to do it that way because you can’t run the development server with most of those configurations on.
The other thing is the Sentry configuration.
Note the simple.__version__
on the release. Next we are going to explore how I usually manage the version of the
project.
I like to reuse Django’s get_version
utility for a simple and PEP 440 complaint version identification.
Inside the project’s __init__.py
module:
You can do something like this:
The only down side of using the get_version
directly from the Django module is that it won’t be able to resolve the
git hash for alpha versions.
A possible solution is making a copy of the django/utils/version.py
file to your project, and then you import it
locally, so it will be able to identify your git repository within the project folder.
But it also depends what kind of versioning you are using for your project. If the version of your project is not really relevant to the end user and you want to keep track of it for internal management like to identify the release on a Sentry issue, you could use a date-based release versioning.
A Django app is a Python package that you “install” using the INSTALLED_APPS
in your settings file. An app can live pretty
much anywhere: inside or outside the project package or even in a library that you installed using pip
.
Indeed, your Django apps may be reusable on other projects. But that doesn’t mean it should. Don’t let it destroy your project design or don’t get obsessed over it. Also, it shouldn’t necessarily represent a “part” of your website/web application.
It is perfectly fine for some apps to not have models, or other apps have only views. Some of your modules doesn’t even need to be a Django app at all. I like to see my Django projects as a big Python package and organize it in a way that makes sense, and not try to place everything inside reusable apps.
The general recommendation of the official Django documentation is to place your apps in the project root (alongside
the manage.py file, identified here in this tutorial by the simple/ (2)
folder).
But actually I prefer to create my apps inside the project package (identified in this tutorial by the simple/ (3)
folder). I create a module named apps
and then inside the apps
I create my Django apps. The main reason why is that
it creates a nice namespace for the app. It helps you easily identify that a particular import is part of your
project. Also this namespace helps when creating logging rules to handle events in a different way.
Here is an example of how I do it:
In the example above the folders accounts/
and core/
are Django apps created with the command django-admin startapp
.
Those two apps are also always in my project. The accounts
app is the one that I use the replace the default Django
User
model and also the place where I eventually create password reset, account activation, sign ups, etc.
The core
app I use for general/global implementations. For example to define a model that will be used across most
of the other apps. I try to keep it decoupled from other apps, not importing other apps resources. It usually is a good
place to implement general purpose or reusable views and mixins.
Something to pay attention when using this approach is that you need to change the name
of the apps configuration,
inside the apps.py
file of the Django app:
accounts/apps.py
You should rename it like this, to respect the namespace:
Then on your INSTALLED_APPS
you are going to create a reference to your models like this:
The namespace also helps to organize your INSTALLED_APPS
making your project apps easily recognizable.
This is what my app structure looks like:
The first thing I do is create a folder named tests
so I can break down my tests into several files. I always add a
factories.py
to create my model factories using the factory-boy
library.
For both static
and templates
always create first a directory with the same name as the app to avoid name collisions
when Django collect all static files and try to resolve the templates.
The admin.py
may be there or not depending if I’m using the Django Admin contrib app.
Other common modules that you may have is a utils.py
, forms.py
, managers.py
, services.py
etc.
Now I’m going to show you the configuration that I use for tools like isort
, black
, flake8
, coverage
and tox
.
The .editorconfig
file is a standard recognized by all major IDEs and code editors. It helps the editor understand
what is the file formatting rules used in the project.
It tells the editor if the project is indented with tabs or spaces. How many spaces/tabs. What’s the max length for a line of code.
I like to use Django’s .editorconfig
file. Here is what it looks like:
.editorconfig
Flake8 is a Python library that wraps PyFlakes, pycodestyle and Ned Batchelder’s McCabe script. It is a great toolkit for checking your code base against coding style (PEP8), programming errors (like “library imported but unused” and “Undefined name”) and to check cyclomatic complexity.
To learn more about flake8, check this tutorial I posted a while a go: How to Use Flake8.
setup.cfg
isort is a Python utility / library to sort imports alphabetically, and automatically separated into sections.
To learn more about isort, check this tutorial I posted a while a go: How to Use Python isort Library.
setup.cfg
Pay attention to the known_first_party
, it should be the name of your project so isort can group your project’s
imports.
Black is a life changing library to auto-format your Python applications. There is no way I’m coding with Python nowadays without using Black.
Here is the basic configuration that I use:
pyproject.toml
In this tutorial I described my go-to project setup when working with Django. That’s pretty much how I start all my projects nowadays.
Here is the final project structure for reference:
You can also explore the code on GitHub: django-production-template.
This is a guest post by Serhii Kushchenko, Python/Django developer and data analyst. He is skillful in SQL, Python, Django, RESTful APIs, statistics, and machine learning.
This post aims to demonstrate the creation and processing of the large and complex form in Django using django-crispy-forms
. The form contains several buttons that require executing different actions during the processing phase. Also, in the form, the number of rows and columns is not static but varies depending on some conditions. In our case, the set of rows and columns changes depending on the number of instances (columns) associated with the main object (data schema) in a many-to-one relationship.
The django-crispy-forms
documentation includes a page Updating layouts on the go. That page is quite helpful. However, it does not contain any detailed example of a working project. IMHO such an example is much needed. I hope my project serves as a helpful addition to the official Django Crispy Forms documentation. Feel free to copy-paste the pieces of code that you find applicable.
Please see the full codebase here. It is ready for Heroku deployment.
Suppose we have a database with information about people: name, surname, phone numbers, place of work, companies owned, etc. The task is not to process the data but to work with meta-information about that data.
Different users may need to extract varying information from the database. These users do not want to write and run SQL queries. They demand some simple and more visual solutions. The assignment is to make it possible for users to create the data schemas visually. In other words, to develop such a form and make it fully functioning.
Using their schemas, users will be able to CRUD data in the database. However, these operations are beyond the scope of the current project.
Different columns can have their specific parameters. For example, integer columns have lower and upper bounds. It is necessary to develop functionality for editing those parameters for all types of columns. For that editing, forms are used that arise after clicking the "Edit details" button on the main form.
Moreover, we have to develop a form "Create new schema" and a page with a list of all available schemas.
Described below:
The task described above can be better solved using JavaScript together with Django forms. It would reduce the number of requests to the server and increase the speed of the application. So the user experience would improve. However, the project aimed to create an advanced example of working with Django Crispy Forms.
Here you can learn the following tricks:
According to the task, the number of columns in the schemas can be different. The users add new columns and delete existing columns. Also, they can change the type and order of columns. The columns and schemas have a many-to-one relationship that is described using the Foreign Key in Django models.
The picture shows that every schema has its name, 'Column separator' field, and 'String character' field. Also, it would be nice to save the date of the last schema modification. The following code from schemas\models.py
file is pretty simple.
INTEGER_CH = "IntegerColumn"
FULLNAME_CH = "FullNameColumn"
JOB_CH = "JobColumn"
PHONE_CH = "PhoneColumn"
COMPANY_CH = "CompanyColumn"
COLUMN_TYPE_CHOICES = [
(INTEGER_CH, "Integer"),
(FULLNAME_CH, "Full Name"),
(JOB_CH, "Job"),
(PHONE_CH, "Phone"),
(COMPANY_CH, "Company"),
]
DOUBLE_QUOTE = '"'
SINGLE_QUOTE = "'"
STRING_CHARACTER_CHOICES = [
(DOUBLE_QUOTE, 'Double-quote(")'),
(SINGLE_QUOTE, "Single-quote(')"),
]
COMMA = ","
SEMICOLON = ";"
COLUMN_SEPARATOR_CHOICES = [(COMMA, "Comma(,)"), (SEMICOLON, "Semicolon(;)")]
class DataSchemas(models.Model):
name = models.CharField(max_length=100)
column_separator = models.CharField(
max_length=1,
choices=COLUMN_SEPARATOR_CHOICES,
default=COMMA,
)
string_character = models.CharField(
max_length=1,
choices=STRING_CHARACTER_CHOICES,
default=DOUBLE_QUOTE,
)
modif_date = models.DateField(auto_now=True)
def get_absolute_url(self):
return reverse("schema_add_update", args=[str(self.id)])
Each column has a name, type, and order. All of these fields are in the base SchemaColumn(models.Model)
class. This class cannot be abstract because in such a case, the code schema.schemacolumn_set.all()
would not work.
Columns of type integer, first and last name, job, company, and phone number are implemented as classes derived from the base class SchemaColumn.
class SchemaColumn(models.Model):
name = models.CharField(max_length=100)
schema = models.ForeignKey(DataSchemas, on_delete=models.CASCADE)
order = models.PositiveIntegerField()
class Meta:
unique_together = [["schema", "name"], ["schema", "order"]]
def save(self, *args, **kwargs):
self.validate_unique()
super(SchemaColumn, self).save(*args, **kwargs)
class IntegerColumn(SchemaColumn):
range_low = models.IntegerField(blank=True, null=True, default=-20)
range_high = models.IntegerField(blank=True, null=True, default=40)
class FullNameColumn(SchemaColumn):
first_name = models.CharField(max_length=10, blank=True, null=True)
last_name = models.CharField(max_length=15, blank=True, null=True)
class JobColumn(SchemaColumn):
job_name = models.CharField(max_length=100, blank=True, null=True)
class CompanyColumn(SchemaColumn):
company_name = models.CharField(max_length=100, blank=True, null=True)
class PhoneColumn(SchemaColumn):
phone_regex = RegexValidator(
regex=r"^\+?1?\d{9,15}$",
message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.",
)
phone_number = models.CharField(
validators=[phone_regex], max_length=17, blank=True, null=True
) # validators should be a list
The schema editing form is quite complex. We do not use the Django built-in ModelForm
class here because it is not flexible enough. Our class DataSchemaForm is a derivative of the forms.Form
class. Of course, django-crispy-forms
was very helpful and even essential.
from crispy_forms.layout import (
Layout,
Submit,
Row,
Column,
Fieldset,
Field,
Hidden,
ButtonHolder,
HTML,
)
The type of column in the form depends on the class of the column. How to determine that class? The problems can arise if we use the built-in isinstance()
function for derived classes such as our various column types. The following code demonstrates how the subclass check was implemented in the forms.py
file when generating the form.
INTEGER_CH = "IntegerColumn"
FULLNAME_CH = "FullNameColumn"
JOB_CH = "JobColumn"
PHONE_CH = "PhoneColumn"
COMPANY_CH = "CompanyColumn"
COLUMN_TYPE_CHOICES = [
(INTEGER_CH, "Integer"),
(FULLNAME_CH, "Full Name"),
(JOB_CH, "Job"),
(PHONE_CH, "Phone"),
(COMPANY_CH, "Company"),
]
subclasses = [
str(subclass).split(".")[-1][:-2].lower()
for subclass in SchemaColumn.__subclasses__()
]
# yes, somewhat redundant
column_type_switcher = {
"integercolumn": INTEGER_CH,
"fullnamecolumn": FULLNAME_CH,
"jobcolumn": JOB_CH,
"companycolumn": COMPANY_CH,
"phonecolumn": PHONE_CH,
}
column_type_field_name = "col_type_%s" % (column.pk,)
self.fields[column_type_field_name] = forms.ChoiceField(
label="Column type", choices=COLUMN_TYPE_CHOICES
)
for subclass in subclasses:
if hasattr(column, subclass):
self.fields[column_type_field_name].initial = [
column_type_switcher.get(subclass)
]
break
The function that generates the schema editing form may get the primary key of the existing schema. If such a key is not available, then the function creates a new schema and its first column. After that, the user can change the parameters of the schema, as well as add new columns.
if schema_pk:
schema = DataSchemas.objects.get(pk=schema_pk)
else:
# no existing schema primary key passed from the caller,
# so create new schema and its first column
# with default parameters
schema = DataSchemas.objects.create(name="New Schema")
int1 = IntegerColumn.objects.create(
name="First Column",
schema=schema,
order=1,
range_low=-20,
range_high=40,
)
self.fields["name"].initial = schema.name
self.fields["column_separator"].initial = schema.column_separator
self.fields["string_character"].initial = schema.string_character
In addition to the schema editing form, the application also contains a list of all created schemas.
There is nothing special about that page, so I will not describe it in detail here. Pleas see the full code and templates at https://github.com/s-kust/django-advanced-forms.
The picture shows that the schema editing form contains several types of buttons:
We need to determine which button the user pressed and perform the required action.
During the form creation, the required action is encoded in the names of all the buttons. Also, the column primary key or schema primary key is encoded there. For example, delete_btn = 'delete_col_%s' % (column.pk,)
or submit_form_btn = 'submit_form_%s' % (schema.pk,)
.
The name of the button can be found in the request.POST data only if the user has pressed that button. The following code from the views.py file searches for the button name in the request.POST data and calls the required function. A well-known method is used to implement the Python analog of switch-case
statement.
btn_functions = {
"add_new_col": process_btn_add_column,
"delete_col": process_btn_delete_column,
"edit_col": process_btn_edit_column_details,
"submit_form": process_btn_submit_form,
"save_column_chng": process_btn_save_chng_column,
}
btn_pressed = None
# source of key.startswith idea - https://stackoverflow.com/questions/13101853/select-post-get-parameters-with-regular-expression
for key in request.POST:
if key.startswith("delete_col_"):
btn_pressed = "delete_col"
if key.startswith("edit_col_"):
btn_pressed = "edit_col"
if key.startswith("add_column_btn_"):
btn_pressed = "add_new_col"
if key.startswith("submit_form_"):
btn_pressed = "submit_form"
if key.startswith("save_column_chng_btn_"):
btn_pressed = "save_column_chng"
if btn_pressed is not None:
func_to_call = self.btn_functions.get(btn_pressed)
self.pk, form = func_to_call(self, key, form_data=request.POST)
break
Different types of columns have their specific parameters. For example, integer columns have lower and upper bounds. Phone columns have the phone number field that requires validation before saving. The number of different types of columns can increase over time.
How to handle the Edit Details button click? The straightforward solution is to make individual ModelForm classes for each type of column. However, it would violate the DRY principle. Perhaps, in this case, the use of metaprogramming is justified.
def get_general_column_form(self, model_class, column_pk):
class ColumnFormGeneral(ModelForm):
def __init__(self, *args, **kwargs):
super(ColumnFormGeneral, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
save_chng_btn = "save_column_chng_btn_%s" % (column_pk,)
self.helper.layout.append(Submit(save_chng_btn, "Save changes"))
class Meta:
model = model_class
exclude = ["schema", "order"]
return ColumnFormGeneral
First, the type of column is determined using its primary key. After that, the get_general_column_form
function is called. It returns the customized ModelForm
class. Next, an instance of that class is created and used.
column = get_object_or_404(SchemaColumn, pk=column_pk)
for subclass in self.subclasses:
if hasattr(column, subclass):
column_model = apps.get_model("schemas", subclass)
column = get_object_or_404(column_model, pk=column_pk)
form_class = self.get_general_column_form(column_model, column_pk)
form = form_class(
initial=model_to_dict(
column, fields=[field.name for field in column._meta.fields]
)
)
break
The user may change the type of one or several columns. If it happens, it means that the class of these columns has changed. Here, it is not enough to change the value of some attribute of the object. We have to delete the old object and create a new object belonging to the new class instead. How do we handle it:
form.is_valid()
method explicitly.request.POST
type. If these types differ, the old column is deleted, and a new one is created instead.# elem is a 'Submit Form' button that the user pressed.
# schema primary key is encoded in its name
# first, let's decode it
self.pk = [int(s) for s in elem.split("_") if s.isdigit()][0]
# form_data is request.POST
form = DataSchemaForm(form_data, schema_pk=self.pk)
if form.is_valid():
schema = get_object_or_404(DataSchemas, pk=self.pk)
schema.name = form.cleaned_data["name"]
schema.column_separator = form.cleaned_data["column_separator"]
schema.string_character = form.cleaned_data["string_character"]
schema.save()
# the following code is in the save_schema_columns(self, schema, form) function
schema_columns = schema.schemacolumn_set.all()
for column in schema_columns:
column_name_field_name = "col_name_%s" % (column.pk,)
column_order_field_name = "col_order_%s" % (column.pk,)
column_type_field_name = "col_type_%s" % (column.pk,)
type_form = form.cleaned_data[column_type_field_name]
type_changed = False
for subclass in self.subclasses:
if hasattr(column, subclass):
type_db = self.column_type_switcher.get(subclass)
if type_db != type_form:
new_class = globals()[type_form]
new_column = new_class()
new_column.name = form.cleaned_data[column_name_field_name]
new_column.order = form.cleaned_data[column_order_field_name]
new_column.schema = schema
column.delete()
new_column.save()
type_changed = True
break
if not type_changed:
column.name = form.cleaned_data[column_name_field_name]
column.order = form.cleaned_data[column_order_field_name]
column.save()
I hope this post serves as a helpful addition to the official Django Crispy Forms documentation. Feel free to copy-paste the pieces of code that you find applicable. Also, write comments and share your experience of using Django Crispy Forms.
Cover photo by Juan Pablo Malo.
For quite some time, I have been building a SaaS product - strategic prioritizer 1st things 1st. It's using Django in the backend and ReactJS in the frontend and communicating between those ends by REST API. Every week I try to make progress with this project, be it a more prominent feature, some content changes, or small styling tweaks. In the past week, I implemented frontend testing with Selenium, and I want to share my journey with you.
1st things 1st allows you to evaluate a list of items by multiple criteria and calculates priorities for you to follow and take action. The service has 4 main steps:
Selenium is a testing tool that mimics user interaction in the browser: you can fill in fields, trigger events, or read out information from the HTML tags. To test the frontend of 1st things 1st with Selenium, I had to
Let's see how I did it.
In 2020, Chrome is the most popular browser, and it's my default browser, so I decided to develop tests using it.
I had to install Selenium with pip into my virtual environment:
(venv)$ pip install selenium
Also, I needed a binary chromedriver, which makes Selenium talk to your Chrome browser. I downloaded it and placed it under myproject/drivers/chromedriver
.
In the Django project configuration, I needed a couple of settings. I usually have separate settings-file for each of the environments, such as:
myproject.settings.local
for the local development,myproject.settings.staging
for the staging server,myproject.settings.test
for testing, andmyproject.settings.production
for production.All of them import defaults from a common base, and I have to set only the differences for each environment.
In the myproject.settings.test
I added these settings:
WEBSITE_URL = 'http://my.1st-things-1st.127.0.0.1.xip.io:8080' # no trailing slash
TESTS_SHOW_BROWSER = True
Here for the WEBSITE_URL
, I was using the xip.io service. It allows you to create domains dynamically pointing to the localhost or any other IP. The Selenium tests will use this URL.
The TEST_SHOW_BROWSER
was my custom setting, telling whether to show a browser while testing the frontend or just to run the tests in the background.
In one of my apps, myproject.apps.evaluations
, I created a tests
package, and there I placed a test case test_evaluations_frontend.py
with the following content:
import os
from time import sleep
from datetime import timedelta
from django.conf import settings
from django.test import LiveServerTestCase
from django.test import override_settings
from django.contrib.auth import get_user_model
from django.utils import timezone
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
User = get_user_model()
SHOW_BROWSER = getattr(settings, "TESTS_SHOW_BROWSER", False)
@override_settings(DEBUG=True)
class EvaluationTest(LiveServerTestCase):
host = settings.WEBSITE_URL.rsplit(":", 1)[0].replace(
"http://", ""
) # domain before port
port = int(settings.WEBSITE_URL.rsplit(":", 1)[1]) # port
USER1_USERNAME = "user1"
USER1_FIRST_NAME = "user1"
USER1_LAST_NAME = "user1"
USER1_EMAIL = "user1@example.com"
USER1_PASSWORD = "change-me"
@classmethod
def setUpClass(cls):
# …
@classmethod
def tearDownClass(cls):
# …
def wait_until_element_found(self, xpath):
# …
def wait_a_little(self, seconds=2):
# …
def test_evaluations(self):
# …
It's a live-server test case, which runs a Django development server under the specified IP and port and then runs the Chrome browser via Selenium and navigates through the DOM and fills in forms.
By default, the LiveServerTestCase
runs in non-debug mode, but I want to have the debug mode on so that I could see any causes of server errors. With the @override_settings
decorator, I could change the DEBUG
setting to True
.
The host
and port
attributes define on which host and port the test server will be running (instead of a 127.0.0.1 and a random port). I extracted those values from the WEBSITE_URL
setting.
The test case also had some attributes for the user who will be navigating through the web app.
Let's dig deeper into the code for each method.
Django test cases can have class-level setup and teardown, which run before and after all methods whose names start with test_
:
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user1 = User.objects.create_user(
cls.USER1_USERNAME, cls.USER1_EMAIL, cls.USER1_PASSWORD
)
# … add subscription for this new user …
driver_path = os.path.join(settings.BASE_DIR, "drivers", "chromedriver")
chrome_options = Options()
if not SHOW_BROWSER:
chrome_options.add_argument("--headless")
chrome_options.add_argument("--window-size=1200,800")
cls.browser = webdriver.Chrome(
executable_path=driver_path, options=chrome_options
)
cls.browser.delete_all_cookies()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.browser.quit()
# … delete subscription for the user …
cls.user1.delete()
In the setup, I created a new user, added a subscription to them, and prepared the Chrome browser to use.
If the TEST_SHOW_BROWSER
setting was False
, Chrome was running headless, that is, in the background without displaying a browser window.
When the tests were over, the browser closed, and the subscription, as well as the user, were deleted.
I created two utility methods for my Selenium test: wait_until_element_found()
and wait_a_little()
:
def wait_until_element_found(self, xpath):
WebDriverWait(self.browser, timeout=10).until(
lambda x: self.browser.find_element_by_xpath(xpath)
)
def wait_a_little(self, seconds=2):
if SHOW_BROWSER:
sleep(seconds)
I used the wait_until_element_found(xpath)
method to keep the test running while pages switched.
I used the wait_a_little(seconds)
method to stop the execution for 2 or more seconds so that I could follow what's on the screen, make some screenshots, or even inspect the DOM in the Web Developer Inspector.
Selenium allows to select DOM elements by ID, name, CSS class, tag name, and other ways, but the most flexible approach, in my opinion, is selecting elements by XPath (XML Path Language).
Contrary to jQuery, ReactJS doesn't use IDs or CSS classes in the markup to update the contents of specific widgets. So the straightforward Selenium's methods for finding elements by IDs or classes won't always work.
XPath is a very flexible and powerful tool. For example, you can:
"//input[@id='id_title']"
"//div[@aria-label='Blank']"
"//button[.='Save']"
"//button[contains(@class,'btn-primary')][.='Save']"
"(//button[.='yes'])[1]"
You can try out XPath syntax and capabilities in Web Developer Console in Chrome and Firefox, using the $x()
function, for example:
» $x("//h1[.='Projects']")
← Array [ h1.display-4.mb-4 ]
I started with opening a login page, dismissing cookie consent notification, filling in user credentials into the login form, creating a new project from a blank template, setting title and description, etc.
def test_evaluations(self):
self.browser.get(f"{self.live_server_url}/")
self.wait_until_element_found("//h1[.='Log in or Sign up']")
# Accept Cookie Consent
self.wait_until_element_found("//a[.='Got it!']")
self.browser.find_element_by_xpath("//a[.='Got it!']").click()
# Log in
self.browser.find_element_by_id("id_email").send_keys(self.USER1_EMAIL)
self.browser.find_element_by_id("id_password").send_keys(self.USER1_PASSWORD)
self.browser.find_element_by_xpath('//button[text()="Log in"]').send_keys(
"\n"
) # submit the form
self.wait_until_element_found("//h1[.='Projects']")
# Click on "Add new project"
self.wait_until_element_found("//a[.='Add new project']")
self.wait_a_little()
self.browser.find_element_by_xpath("//a[.='Add new project']").send_keys("\n")
self.wait_until_element_found("//div[@aria-label='Blank']")
# Create a project from the project template "Blank"
self.wait_a_little()
self.browser.find_element_by_xpath("//div[@aria-label='Blank']").send_keys("\n")
# Enter project title and description
self.wait_until_element_found("//input[@id='id_title']")
self.browser.find_element_by_xpath("//input[@id='id_title']").send_keys(
"Urgent and Important Activities"
)
self.browser.find_element_by_xpath(
"//textarea[@id='id_description']"
).send_keys("I want to find which things to do and which to skip.")
self.browser.find_element_by_xpath("//button[.='Next']").send_keys("\n")
# Keep the default verbose names for the criteria and initiatives
self.wait_until_element_found("//input[@id='id_initiative_verbose_name_plural']")
self.wait_a_little()
self.browser.find_element_by_xpath("//button[.='Next']").send_keys("\n")
If TESTS_SHOW_BROWSER
was set to True
, we would see all this workflow in an opened browser window.
I was creating the test by carefully inspecting the markup in Web Developer Inspector and creating appropriate DOM navigation with XPath. For most of the navigation, I was using send_keys()
method, which triggers keyboard events. During the testing, I also noticed that my cookie consent only worked with a mouse click, and I couldn't approve it by the keyboard. That's some room for improving accessibility.
I ran the test with the following command each time I added some more lines:
(venv)$ python manage.py test myproject.apps.evaluations --settings=myproject.settings.test
The test case failed if any command in the test failed. I didn't even need asserts.
Now it was time to add some criteria:
self.wait_until_element_found("//h2[.='Criteria']")
# Add new criterion "Urgent" with the evaluation type Yes/No/Maybe
self.wait_until_element_found("//a[.='Add new criterion']")
self.browser.find_element_by_xpath("//a[.='Add new criterion']").send_keys("\n")
self.wait_until_element_found("//input[@id='id_title']")
self.browser.find_element_by_xpath("//input[@id='id_title']").send_keys(
"Urgent"
)
self.browser.find_element_by_xpath("//input[@id='widget_y']").send_keys(" ")
self.browser.find_element_by_xpath("//button[.='Save']").send_keys("\n")
# Add new criterion "Important" with the evaluation type Yes/No/Maybe
self.wait_until_element_found("//a[.='Add new criterion']")
self.browser.find_element_by_xpath("//a[.='Add new criterion']").send_keys("\n")
self.wait_until_element_found("//input[@id='id_title']")
self.browser.find_element_by_xpath("//input[@id='id_title']").send_keys(
"Important"
)
self.browser.find_element_by_xpath("//input[@id='widget_y']").send_keys(" ")
self.browser.find_element_by_xpath("//button[.='Save']").send_keys("\n")
# Click on the button "Done"
self.wait_until_element_found("//a[.='Done']")
self.browser.find_element_by_xpath("//a[.='Done']").send_keys("\n")
I added two criteria, "Urgent" and "Important", with evaluation type "Yes/No/Maybe".
Then I created some activities to evaluate:
self.wait_until_element_found("//h2[.='Things']")
# Add new thing "Write a blog post"
self.wait_until_element_found("//a[.='Add new thing']")
self.browser.find_element_by_xpath("//a[.='Add new thing']").send_keys("\n")
self.wait_until_element_found("//input[@id='id_title']")
self.browser.find_element_by_xpath("//input[@id='id_title']").send_keys(
"Write a blog post"
)
self.browser.find_element_by_xpath("//textarea[@id='id_description']").send_keys(
"I have an idea of a blog post that I want to write."
)
self.browser.find_element_by_xpath("//button[.='Save']").send_keys("\n")
# Add new thing "Fix a bug"
self.wait_until_element_found("//a[.='Add new thing']")
self.browser.find_element_by_xpath("//a[.='Add new thing']").send_keys("\n")
self.wait_until_element_found("//input[@id='id_title']")
self.browser.find_element_by_xpath("//input[@id='id_title']").send_keys(
"Fix a bug"
)
self.browser.find_element_by_xpath("//textarea[@id='id_description']").send_keys(
"There is a critical bug that bothers our clients."
)
self.browser.find_element_by_xpath("//button[.='Save']").send_keys("\n")
# Add new thing "Binge-watch a series"
self.wait_until_element_found("//a[.='Add new thing']")
self.browser.find_element_by_xpath("//a[.='Add new thing']").send_keys("\n")
self.wait_until_element_found("//input[@id='id_title']")
self.browser.find_element_by_xpath("//input[@id='id_title']").send_keys(
"Binge-watch a series"
)
self.browser.find_element_by_xpath("//textarea[@id='id_description']").send_keys(
"There is an exciting series that I would like to watch."
)
self.browser.find_element_by_xpath("//button[.='Save']").send_keys("\n")
# Click on the button "Done"
self.wait_until_element_found("//a[.='Done']")
self.browser.find_element_by_xpath("//a[.='Done']").send_keys("\n")
These were three activities: "Write a blog post", "Fix a bug", and "Binge-watch a series" with their descriptions:
In this step, there was a list of widgets to evaluate each thing by each criterion with answers "No", "Maybe", or "Yes". The buttons for those answers had no specific id or CSS class, but I could target them by the text on the button using XPath like "//button[.='maybe']"
:
self.wait_until_element_found("//h2[.='Evaluations']")
self.wait_until_element_found("//button[.='maybe']")
# Evaluate all things by Urgency
self.browser.find_element_by_xpath("(//button[.='no'])[1]").send_keys("\n")
self.wait_until_element_found("//footer[.='Evaluation saved.']")
self.browser.find_element_by_xpath("(//button[.='yes'])[2]").send_keys("\n")
self.wait_until_element_found("//footer[.='Evaluation saved.']")
self.browser.find_element_by_xpath("(//button[.='no'])[3]").send_keys("\n")
self.wait_until_element_found("//footer[.='Evaluation saved.']")
# Evaluate all things by Importance
self.browser.find_element_by_xpath("(//button[.='yes'])[4]").send_keys("\n")
self.wait_until_element_found("//footer[.='Evaluation saved.']")
self.browser.find_element_by_xpath("(//button[.='yes'])[5]").send_keys("\n")
self.wait_until_element_found("//footer[.='Evaluation saved.']")
self.browser.find_element_by_xpath("(//button[.='maybe'])[6]").send_keys("\n")
self.wait_until_element_found("//footer[.='Evaluation saved.']")
# Click on the button "Done"
self.browser.find_element_by_xpath("//a[.='Done']").send_keys("\n")
These were my evaluations:
So in the last step, I got the calculated priorities:
self.wait_until_element_found("//h2[.='Priorities']")
self.wait_until_element_found("//h5[.='1. Fix a bug (100%)']")
self.wait_until_element_found("//h5[.='2. Write a blog post (50%)']")
self.wait_until_element_found("//h5[.='3. Binge-watch a series (25%)']")
self.wait_a_little()
The results looked correct:
LiveServerTestCase
.I hope that my journey was useful to you too.
Happy coding!
Thanks a lot to Adam Johnson for the review.
Cover photo by Science in HD.
When you develop a web app or a mobile app with Django, it is common to use the Django REST Framework for communication with the server-side. The client-side makes GET, POST, PUT, and DELETE requests to the REST API to read, create, update, or delete data there. The communication by Ajax is pretty uncomplicated, but how would you upload an image or another file to the server? I will show you that in this article by creating user avatar upload via REST API. Find the full code for this feature on Github.
We will start by installing Pillow for image handling to the virtual environment using the standard pip command:
(venv)$ pip install Pillow
Create accounts
app with a custom User
model:
# myproject/apps/accounts/models.py
import os
import sys
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
def upload_to(instance, filename):
now = timezone.now()
base, extension = os.path.splitext(filename.lower())
milliseconds = now.microsecond // 1000
return f"users/{instance.pk}/{now:%Y%m%d%H%M%S}{milliseconds}{extension}"
class User(AbstractUser):
# …
avatar = models.ImageField(_("Avatar"), upload_to=upload_to, blank=True)
You can add there as many fields as you need, but the noteworthy part there is the avatar
field.
Update the settings and add the accounts
app to INSTALLED_APPS
, set the AUTH_USER_MODEL
, and the configuration for the static and media directories:
# myproject/settings.py
INSTALLED_APPS = [
# …
"myproject.apps.accounts",
]
AUTH_USER_MODEL = "accounts.User"
STATICFILES_DIRS = [os.path.join(BASE_DIR, "myproject", "site_static")]
STATIC_ROOT = os.path.join(BASE_DIR, "myproject", "static")
STATIC_URL = "/static/"
MEDIA_ROOT = os.path.join(BASE_DIR, "myproject", "media")
MEDIA_URL = "/media/"
Next small steps:
makemigrations
and migrate
management commands.createsuperuser
management command.Install Django REST Framework for the REST APIs to your virtual environment, as always, using pip:
(venv)$ pip install djangorestframework
We'll be using authentication by tokens in this example. So add Django REST Framework to INSTALLED_APPS
in the settings and set TokenAuthentication
as the default authentication in the REST_FRAMEWORK
configuration:
# myproject/settings.py
INSTALLED_APPS = [
# …
"rest_framework",
"rest_framework.authtoken",
# …
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
]
}
In Django REST Framework, serializers are used for data validation, rendering, and saving. They are similar to Django forms. Prepare UserAvatarSerializer
for avatar uploads:
# myproject/apps/accounts/serializers.py
from django.contrib.auth import get_user_model
from rest_framework.serializers import ModelSerializer
User = get_user_model()
class UserAvatarSerializer(ModelSerializer):
class Meta:
model = User
fields = ["avatar"]
def save(self, *args, **kwargs):
if self.instance.avatar:
self.instance.avatar.delete()
return super().save(*args, **kwargs)
Now create an API view UserAvatarUpload
for avatar uploads.
# myproject/apps/accounts/views.py
from rest_framework import status
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import UserAvatarSerializer
class UserAvatarUpload(APIView):
parser_classes = [MultiPartParser, FormParser]
permission_classes = [IsAuthenticated]
def post(self, request, format=None):
serializer = UserAvatarSerializer(data=request.data, instance=request.user)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Make sure that the view uses MultiPartParser
as one of the parser classes. That's necessary for the file transfers.
In the URL configuration, we will need those URL rules:
TemplateView
.# myroject/urls.py
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from django.views.generic import TemplateView
from django.conf import settings
from myproject.accounts.views import UserAvatarUpload
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path("", TemplateView.as_view(template_name="index.html")),
path("api/auth-token/", obtain_auth_token, name="rest_auth_token"),
path("api/user-avatar/", UserAvatarUpload.as_view(), name="rest_user_avatar_upload"),
path("admin/", admin.site.urls),
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
I will illustrate the frontend using Bootstrap HTML and Vanilla JavaScript. Of course, you can implement the same using ReactJS, Vue, Angular, or other JavaScript framework and any other CSS framework.
The template for the index page has one login form with username and password or email and password fields (depending on your implementation), and one avatar upload form with a file selection field. Also, it includes a JavaScript file avatar.js
for Ajax communication.
{# myproject/templates/index.html #}
<!doctype html>
{% load static %}
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<title>Hello, World!</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8">
<p class="text-muted my-3"><small>Open Developer Console for information about responses.</small></p>
<h1 class="my-3">1. Log in</h1>
<form id="login_form">
<div class="form-group">
<label for="id_email">Email address</label>
<input type="email" class="form-control" id="id_email" aria-describedby="emailHelp"
placeholder="Enter email"/>
</div>
<div class="form-group">
<label for="id_password">Password</label>
<input type="password" class="form-control" id="id_password" placeholder="Password"/>
</div>
<button type="submit" class="btn btn-primary">Log in</button>
</form>
<h1 class="my-3">2. Upload an avatar</h1>
<form id="avatar_form">
<div class="form-group">
<label for="id_avatar">Choose an image for your avatar</label>
<input type="file" class="form-control-file" id="id_avatar"/>
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</form>
</div>
</div>
</div>
<script src="{% static 'site/js/avatar.js' %}"></script>
</body>
</html>
Last but not least, create the JavaScript file avatar.js
. It contains these things:
// myproject/site_static/site/js/avatar.js
let userToken;
document.getElementById('login_form').addEventListener('submit', function(event) {
event.preventDefault();
let email = document.getElementById('id_email').value;
let password = document.getElementById('id_password').value;
fetch('http://127.0.0.1:8000/api/auth-token/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"username": email,
"password": password,
})
}).then( response => {
return response.json();
}).then(data => {
console.log(data);
userToken = data.token;
console.log('Logged in. Got the token.');
}).catch((error) => {
console.error('Error:', error);
});
});
document.getElementById('avatar_form').addEventListener('submit', function(event) {
event.preventDefault();
let input = document.getElementById('id_avatar');
let data = new FormData();
data.append('avatar', input.files[0]);
fetch('http://127.0.0.1:8000/api/user-avatar/', {
method: 'POST',
headers: {
'Authorization': `Token ${userToken}`
},
body: data
}).then(response => {
return response.json();
}).then(data => {
console.log(data);
}).catch((error) => {
console.error('Error:', error);
});
});
In the JavaScript file, we are using fetch API for the REST API requests. The noteworthy part there is the FormData
class that we use to send the file to the server.
Now run the local development server and go to the http://127.0.0.1:8000
. There you will have something like this:
As more than a half Internet usage happens on mobile devices, there is a demand to switch from usual HTML websites and platforms to mobile apps. Whether you create a native mobile app, a hybrid app, or Progressive Web App, you will likely have to communicate with the server via REST API or GraphQL. It is pretty clear how to transfer textual data from and to a remote server. But after this exercise, we can also transfer binary files like images, PDF or Word documents, music, and videos.
Happy coding!
Cover Photo by Dan Silva
Google Analytics is a powerful tool that gives you useful insight about the traffic that arrives in your website. But together with Django, you will be able to retrieve data of your Analytics account and display it to the user, on demand.
In this post you’ll learn how to put together Django, the oauth2 protocol and the Google Analytics API.
Let’s Start!
First of all, you might want to check this post if you’re not familiar with the Google Analytics API with Python.
Moreover, it assumes that you are familiar with Python and pip, and that you know Django and have a working application. If you navigate into the Python and Django sections of this website you’ll learn more about those
I assume you have a working Django application, and that you want to extract data from your Google Analytics account using an existing Project. If you didn’t create a Project in the Google Developers Console, check this post and create one. Then, come back
But if you have a live Django project, then you need to create a Web application instead. You just need to select Web application as the Application type when you are creating a Client Id in the Credentials section:
In Authorized Javascript origins insert your website domain, and in authorized redirect uris, insert your domain plus /oauth2/oauth2callback. This endpoint will be used by django to catch the oatuh2 response.
After that, you should have the Oauth credentials in a json format.
You can obtain it from the Google Developers Console. Go to your project and then select APIs & Oauth credentials –> Oauth and click on Download JSON.
Next, we need to install the oaut2client python package. Activate your virtual environment (if you have one) and type:
$ pip install oauth2client
This will install oauth2client and the following packages: httplib2 (an http library), pyasn1 and pyasn1-modules (to use the ASN.1 protocol), and rsa to use the RSA cryptosystem).
Learn how to create one here, for Python 2.7 and for Python 3. It’s really useful, once you start using them you won’t stop!
Don’t forget to add these packages into the requirements file of your virtual environment.
And also, we will need the API Python Client library:
$ pip install python-gflags $ pip install -U google-api-python-client
Next, we will create a Django app to manage the Authorization process. Depending on where you want to create the app’s folder, you will run a slightly different command (this one will create the folder in the current directory):
$ python manage.py startapp oauth2_authentication
Take the client_secrets.json file and save it inside this app, and include this app into your settings.py file, in the INSTALLED_APPS.
Now that we have the app created, we are going to create two different models that will store the Flow and Credential objects.
Edit the models.py inside the oaut2_authentication app and write:
from django.db import models from django.contrib.auth.models import User from oauth2client.django_orm import FlowField, CredentialsField class FlowModel(models.Model): id = models.ForeignKey(User, primary_key=True) flow = FlowField() class CredentialsModel(models.Model): id = models.ForeignKey(User, primary_key=True) credential = CredentialsField()
Next, we’ll edit the urls of your app. First, open the main urls.py and add the following urlpattern:
url(r'^oauth2/', include('oauth2_authentication.urls', namespace="oauth2"))
And in the oauth2_authentication/urls.py write:
from django.conf.urls import patterns, url from . import views urlpatterns = patterns( '', url(r'^$', views.index, name='index'), url(r'oauth2callback', views.auth_return, name='return'), )
This way, /oauth2 will start the authorization process, and /oauth2/oaut2callback will wait for the authorization response.
But we need to write our views.py first:
import os import httplib2 from oauth2client import xsrfutil from oauth2client.client import flow_from_clientsecrets from oauth2client.django_orm import Storage from apiclient.discovery import build from django.contrib.auth.decorators import login_required from django.http import HttpResponseBadRequest from django.http import HttpResponseRedirect from django.shortcuts import render from django.conf import settings from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from django.contrib.sites.models import get_current_site from .models import CredentialsModel, FlowModel CLIENT_SECRETS = os.path.join( os.path.dirname(__file__), 'client_secrets.json') def get_accounts_ids(service): accounts = service.management().accounts().list().execute() ids = [] if accounts.get('items'): for account in accounts['items']: ids.append(account['id']) return ids @login_required def index(request): # use the first REDIRECT_URI if you are developing your app # locally, and the second in production # REDIRECT_URI = 'http://localhost:8000/oauth2/oauth2callback' REDIRECT_URI = "https://%s%s" % ( get_current_site(request).domain, reverse("oauth2:return")) FLOW = flow_from_clientsecrets( CLIENT_SECRETS, scope='https://www.googleapis.com/auth/analytics.readonly', redirect_uri=REDIRECT_URI ) user = request.user storage = Storage(CredentialsModel, 'id', user, 'credential') credential = storage.get() if credential is None or credential.invalid is True: FLOW.params['state'] = xsrfutil.generate_token( settings.SECRET_KEY, user) authorize_url = FLOW.step1_get_authorize_url() f = FlowModel(id=user, flow=FLOW) f.save() return HttpResponseRedirect(authorize_url) else: http = httplib2.Http() http = credential.authorize(http) service = build('analytics', 'v3', http=http) ids = get_account_ids(service) return render( request, 'oauth2_authentication/main.html', {'ids':ids}) @login_required def auth_return(request): user = request.user if not xsrfutil.validate_token( settings.SECRET_KEY, request.REQUEST['state'], user): return HttpResponseBadRequest() FLOW = FlowModel.objects.get(id=user).flow credential = FLOW.step2_exchange(request.REQUEST) storage = Storage(CredentialsModel, 'id', user, 'credential') storage.put(credential) return HttpResponseRedirect("/oauth2")
Note that you have to choose which REDIRECT_URI to use, depending on if you are developing your Django app locally or your app is live on a server.
Inside the FLOW object, we have used the readonly scope, but you could also choose:
https://www.googleapis.com/auth/analytics for writing permissions, and https://www.googleapis.com/auth/analytics.manage.users to view and manage permission of users in the Analytics accounts (more info).
And finally, write something in the oauth2_authentication/main.html template, like:
Your credentials are up to date! :-) {% if ids %} The List of Ids of your Google Analytics accounts are: {{ids}} {% endif %}
Now you just need to start the server and visit /oauth2 to start the authentication process. If everything worked correctly, you should see the list of ids of your Google Analytics account.
Note that once you have the Google Analytics service object you can retrieve all the data you want from your accounts. To see more examples, you can check this post: Google Analytics API with Python.
The post Use the Google Analytics API with Django appeared first on Marina Mele's site.
When you remove a Django Model or a full app in Django, you have to be careful in how you do it, and how the database will be affected.
Thus, in this short post I’ll explain what I do when deleting a model or an app.
Weather you’re removing a single model or a full app, you should first remove the desired models from the models.py file.
Moreover, you have to make sure that no other file imports these models or uses them (admin.py, views.py, etc).
Next, run South or migrate your database to properly delete these models from your database.
If you are using South, you should run something like
$ python manage.py schemamigration --auto your-app $ python manage.py migrate your-app
changing your-app by the name of the app to migrate (where your deleted models were).
Next, if you are removing an entire app, you can now remove all the files in that app (models.py, views.py, etc). Again, make sure that the other apps don’t make an import from these files.
And finally, remove this app from your Installed Apps in the settings.py file.
But after doing all this, you will still have remnants of your models in the ContentType table, like permissions related to the deleted models.
One way to see this is when creating a Group in the Auth app using the admin. When you have to choose the permissions of the group, the box will show permissions that refer to the deleted models.
To clean these tables, we will create the file clean_model_app_remove.py and write the following:
from django.contrib.contenttypes.models import ContentType # List of deleted apps DEL_APPS = ["app-you-deleted", "second-app-deleted"] # List of deleted models (that are not in the app deleted) In lowercase! DEL_MODELS = ["model-you-deleted", "second-model-deleted"] ct = ContentType.objects.all().order_by("app_label", "model") for c in ct: if (c.app_label in DEL_APPS) or (c.model in DEL_MODELS): print "Deleting Content Type %s %s" % (c.app_label, c.model) c.delete()
where you should write in DEL_APPS the apps that you deleted, and in DEL_MODELS the models that you delete and that do not belong to any of the deleted apps. Moreover, note that the models have to be written in lowercase.
Finally, we just need to run our script with
$ python manage.py shell >>>execfile("clean_model_app_remove.py")
Great! Now we cleanly removed our apps/models!
The post How to correctly remove a Model or an App in Django appeared first on Marina Mele's site.
Chart.js is a cool open source JavaScript library that helps you render HTML5 charts. It is responsive and counts with 8 different chart types.
In this tutorial we are going to explore a little bit of how to make Django talk with Chart.js and render some simple charts based on data extracted from our models.
For this tutorial all you are going to do is add the Chart.js lib to your HTML page:
You can download it from Chart.js official website and use it locally, or you can use it from a CDN using the URL above.
I’m going to use the same example I used for the tutorial How to Create Group By Queries With Django ORM which is a good complement to this tutorial because actually the tricky part of working with charts is to transform the data so it can fit in a bar chart / line chart / etc.
We are going to use the two models below, Country
and City
:
And the raw data stored in the database:
cities | |||
---|---|---|---|
id | name | country_id | population |
1 | Tokyo | 28 | 36,923,000 |
2 | Shanghai | 13 | 34,000,000 |
3 | Jakarta | 19 | 30,000,000 |
4 | Seoul | 21 | 25,514,000 |
5 | Guangzhou | 13 | 25,000,000 |
6 | Beijing | 13 | 24,900,000 |
7 | Karachi | 22 | 24,300,000 |
8 | Shenzhen | 13 | 23,300,000 |
9 | Delhi | 25 | 21,753,486 |
10 | Mexico City | 24 | 21,339,781 |
11 | Lagos | 9 | 21,000,000 |
12 | São Paulo | 1 | 20,935,204 |
13 | Mumbai | 25 | 20,748,395 |
14 | New York City | 20 | 20,092,883 |
15 | Osaka | 28 | 19,342,000 |
16 | Wuhan | 13 | 19,000,000 |
17 | Chengdu | 13 | 18,100,000 |
18 | Dhaka | 4 | 17,151,925 |
19 | Chongqing | 13 | 17,000,000 |
20 | Tianjin | 13 | 15,400,000 |
21 | Kolkata | 25 | 14,617,882 |
22 | Tehran | 11 | 14,595,904 |
23 | Istanbul | 2 | 14,377,018 |
24 | London | 26 | 14,031,830 |
25 | Hangzhou | 13 | 13,400,000 |
26 | Los Angeles | 20 | 13,262,220 |
27 | Buenos Aires | 8 | 13,074,000 |
28 | Xi'an | 13 | 12,900,000 |
29 | Paris | 6 | 12,405,426 |
30 | Changzhou | 13 | 12,400,000 |
31 | Shantou | 13 | 12,000,000 |
32 | Rio de Janeiro | 1 | 11,973,505 |
33 | Manila | 18 | 11,855,975 |
34 | Nanjing | 13 | 11,700,000 |
35 | Rhine-Ruhr | 16 | 11,470,000 |
36 | Jinan | 13 | 11,000,000 |
37 | Bangalore | 25 | 10,576,167 |
38 | Harbin | 13 | 10,500,000 |
39 | Lima | 7 | 9,886,647 |
40 | Zhengzhou | 13 | 9,700,000 |
41 | Qingdao | 13 | 9,600,000 |
42 | Chicago | 20 | 9,554,598 |
43 | Nagoya | 28 | 9,107,000 |
44 | Chennai | 25 | 8,917,749 |
45 | Bangkok | 15 | 8,305,218 |
46 | Bogotá | 27 | 7,878,783 |
47 | Hyderabad | 25 | 7,749,334 |
48 | Shenyang | 13 | 7,700,000 |
49 | Wenzhou | 13 | 7,600,000 |
50 | Nanchang | 13 | 7,400,000 |
51 | Hong Kong | 13 | 7,298,600 |
52 | Taipei | 29 | 7,045,488 |
53 | Dallas–Fort Worth | 20 | 6,954,330 |
54 | Santiago | 14 | 6,683,852 |
55 | Luanda | 23 | 6,542,944 |
56 | Houston | 20 | 6,490,180 |
57 | Madrid | 17 | 6,378,297 |
58 | Ahmedabad | 25 | 6,352,254 |
59 | Toronto | 5 | 6,055,724 |
60 | Philadelphia | 20 | 6,051,170 |
61 | Washington, D.C. | 20 | 6,033,737 |
62 | Miami | 20 | 5,929,819 |
63 | Belo Horizonte | 1 | 5,767,414 |
64 | Atlanta | 20 | 5,614,323 |
65 | Singapore | 12 | 5,535,000 |
66 | Barcelona | 17 | 5,445,616 |
67 | Munich | 16 | 5,203,738 |
68 | Stuttgart | 16 | 5,200,000 |
69 | Ankara | 2 | 5,150,072 |
70 | Hamburg | 16 | 5,100,000 |
71 | Pune | 25 | 5,049,968 |
72 | Berlin | 16 | 5,005,216 |
73 | Guadalajara | 24 | 4,796,050 |
74 | Boston | 20 | 4,732,161 |
75 | Sydney | 10 | 5,000,500 |
76 | San Francisco | 20 | 4,594,060 |
77 | Surat | 25 | 4,585,367 |
78 | Phoenix | 20 | 4,489,109 |
79 | Monterrey | 24 | 4,477,614 |
80 | Inland Empire | 20 | 4,441,890 |
81 | Rome | 3 | 4,321,244 |
82 | Detroit | 20 | 4,296,611 |
83 | Milan | 3 | 4,267,946 |
84 | Melbourne | 10 | 4,650,000 |
countries | |
---|---|
id | name |
1 | Brazil |
2 | Turkey |
3 | Italy |
4 | Bangladesh |
5 | Canada |
6 | France |
7 | Peru |
8 | Argentina |
9 | Nigeria |
10 | Australia |
11 | Iran |
12 | Singapore |
13 | China |
14 | Chile |
15 | Thailand |
16 | Germany |
17 | Spain |
18 | Philippines |
19 | Indonesia |
20 | United States |
21 | South Korea |
22 | Pakistan |
23 | Angola |
24 | Mexico |
25 | India |
26 | United Kingdom |
27 | Colombia |
28 | Japan |
29 | Taiwan |
For the first example we are only going to retrieve the top 5 most populous cities and render it as a pie chart. In this strategy we are going to return the chart data as part of the view context and inject the results in the JavaScript code using the Django Template language.
views.py
Basically in the view above we are iterating through the City
queryset and building a list of labels
and a list of
data
. Here in this case the data
is the population count saved in the City
model.
For the urls.py
just a simple routing:
urls.py
Now the template. I got a basic snippet from the Chart.js Pie Chart Documentation.
pie_chart.html
In the example above the base.html
template is not important but you can see it in the code example I shared in the
end of this post.
This strategy is not ideal but works fine. The bad thing is that we are using the Django Template Language to interfere
with the JavaScript logic. When we put {{ data|safe}}
we are injecting a variable that came from
the server directly in the JavaScript code.
The code above looks like this:
As the title says, we are now going to render a bar chart using an async call.
views.py
So here we are using two views. The home
view would be the main page where the chart would be loaded at. The other
view population_chart
would be the one with the sole responsibility to aggregate the data the return a JSON response
with the labels and data.
If you are wondering about what this queryset is doing, it is grouping the cities by the country and aggregating the total population of each country. The result is going to be a list of country + total population. To learn more about this kind of query have a look on this post: How to Create Group By Queries With Django ORM
urls.py
home.html
Now we have a better separation of concerns. Looking at the chart container:
We added a reference to the URL that holds the chart rendering logic. Later on we are using it to execute the Ajax call.
Inside the success
callback we then finally execute the Chart.js related code using the JsonResponse
data.
I hope this tutorial helped you to get started with working with charts using Chart.js. I published another tutorial on the same subject a while ago but using the Highcharts library. The approach is pretty much the same: How to Integrate Highcharts.js with Django.
If you want to grab the code I used in this tutorial you can find it here: github.com/sibtc/django-chartjs-example.
This year DjangoCon Europe happened in Copenhagen, Denmark, at a very creative and special place AFUK - Academy for Untamed Creativity. Surrounded by artistic souls, we learned more about web technologies, got to know each other at ecologic reusable disposable cups of coffee, enjoyed delicious authentic food, and socialized with Django-branded beverages. As always, there was also a party with people drinking IPA (not to be confused with API). And at the weekend Django developers were solving bugs for Django and related open-source software at the coding sprints.
Here I would like to present you with the top 5 talks that I liked most of all.
Adam Johnson (@AdamChainz) was talking about special response headers that tell browsers to treat data of the website more securely and which Django settings are responsible for those headers.
Mikey Ariel (@ThatDocsLady) was talking about the necessity of documentation and what to write there.
Sigurd Ljødal (@sigurdlj) was talking about advanced Django ORM use cases.
Markus Holtermann (@m_holtermann) was talking about logging structured data to log files instead of the traditional plain text messages.
Joachim Jablon (@Ewjoachim) and Stéphane Angel (@twidi) were talking about the best practices developing large-scale Django projects.
You can see all talk descriptions, video records and some slides at the official conference website.
Amazing conference photos were taken by Bartek Pawlik.
I was also really astonished how effective were the talk summaries written by rixx almost in real time.
Cover photo by Bartek Pawlik.
Django ORM (Object-relational mapping) makes querying the database so intuitive, that at some point you might forget that SQL is being used in the background.
This year at the DjangoCon Europe Katie McLaughlin was giving a talk and mentioned one thing that affects the SQL query generated by Django ORM, depending on how you call the QuerySet or manager methods. This particularity is especially relevant when you are creating your QuerySets dynamically. Here it is. When you have a many-to-many relationship, and you try to filter objects by the fields of the related model, every new filter()
method of a QuerySet creates a new INNER JOIN
clause. I won't discuss whether that's a Django bug or a feature, but these are my observations about it.
Let's create an app with books and authors, where each book can be written by multiple authors.
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Author(models.Model):
first_name = models.CharField(_("First name"), max_length=200)
last_name = models.CharField(_("Last name"), max_length=200)
author_name = models.CharField(_("Author name"), max_length=200)
class Meta:
verbose_name = _("Author")
verbose_name_plural = _("Authors")
ordering = ("author_name",)
def __str__(self):
return self.author_name
@python_2_unicode_compatible
class Book(models.Model):
title = models.CharField(_("Title"), max_length=200)
authors = models.ManyToManyField(Author, verbose_name=_("Authors"))
publishing_date = models.DateField(_("Publishing date"), blank=True, null=True)
class Meta:
verbose_name = _("Book")
verbose_name_plural = _("Books")
ordering = ("title",)
def __str__(self):
return self.title
The similar app with sample data can be found in this repository.
With the above models, you could define the following QuerySet to select books which author is me, Aidas Bendoraitis.
queryset = Book.objects.filter(
authors__first_name='Aidas',
).filter(
authors__last_name='Bendoraitis',
)
We can check what SQL query it would generate with str(queryset.query)
(or queryset.query.__str__()
).
The output would be something like this:
SELECT `libraryapp_book`.`id`, `libraryapp_book`.`title`, `libraryapp_book`.`publishing_date`
FROM `libraryapp_book`
INNER JOIN `libraryapp_book_authors` ON ( `libraryapp_book`.`id` = `libraryapp_book_authors`.`book_id` )
INNER JOIN `libraryapp_author` ON ( `libraryapp_book_authors`.`author_id` = `libraryapp_author`.`id` )
INNER JOIN `libraryapp_book_authors` T4 ON ( `libraryapp_book`.`id` = T4.`book_id` )
INNER JOIN `libraryapp_author` T5 ON ( T4.`author_id` = T5.`id` )
WHERE (`libraryapp_author`.`first_name` = 'Aidas' AND T5.`last_name` = 'Bendoraitis')
ORDER BY `libraryapp_book`.`title` ASC;
Did you notice, that the database table libraryapp_author
was attached through the libraryapp_book_authors
table to the libraryapp_book
table TWICE where just ONCE would be enough?
On the other hand, if you are defining query expressions in the same filter()
method like this:
queryset = Book.objects.filter(
authors__first_name='Aidas',
authors__last_name='Bendoraitis',
)
The generated SQL query will be much shorter and (theoretically) would perform faster:
SELECT `libraryapp_book`.`id`, `libraryapp_book`.`title`, `libraryapp_book`.`publishing_date`
FROM `libraryapp_book`
INNER JOIN `libraryapp_book_authors` ON ( `libraryapp_book`.`id` = `libraryapp_book_authors`.`book_id` )
INNER JOIN `libraryapp_author` ON ( `libraryapp_book_authors`.`author_id` = `libraryapp_author`.`id` )
WHERE (`libraryapp_author`.`first_name` = 'Aidas' AND `libraryapp_author`.`last_name` = 'Bendoraitis')
ORDER BY `libraryapp_book`.`title` ASC;
The same SQL query can be achieved using the Q()
objects:
queryset = Book.objects.filter(
models.Q(authors__first_name='Aidas') &
models.Q(authors__last_name='Bendoraitis')
)
The Q()
objects add a lot of flexibility to filters allowing to OR, AND, and negate query expressions.
So to have faster performance, when creating QuerySets dynamically, DON'T use filter()
multiple times:
queryset = Book.objects.all()
if first_name:
queryset = queryset.filter(
authors__first_name=first_name,
)
if last_name:
queryset = queryset.filter(
authors__last_name=last_name,
)
DO this instead:
filters = models.Q()
if first_name:
filters &= models.Q(
authors__first_name=first_name,
)
if last_name:
filters &= models.Q(
authors__last_name=last_name,
)
queryset = Book.objects.filter(filters)
Here the empty Q()
doesn't have any impact for the generated SQL query, so you don't need the complexity of creating a list of filters and then joining all of them with the bitwise AND operator, like this:
import operator
from django.utils.six.moves import reduce
filters = []
if first_name:
filters.append(models.Q(
authors__first_name=first_name,
))
if last_name:
filters.append(models.Q(
authors__last_name=last_name,
))
queryset = Book.objects.filter(reduce(operator.iand, filters))
In DEBUG mode, you can check how long the previously executed SQL queries took by checking django.db.connection.queries
:
>>> from django.db import connection
>>> connection.queries
[{'sql': 'SELECT …', 'time': '0.001'}, {'sql': 'SELECT …', 'time': '0.004'}]
filter()
methods, make use of Q()
objects instead.str(queryset.query)
.django.db.connection.queries
.Cover photo by Tobias Fischer.
"DjangoCon, why is everybody wearing this t-shirt?" wondered the security guys in the airport of Florence, Italy, in the beginning of April. The reason for that was DjangoCon Europe 2017 happening there for a week, full of interesting talks in an exciting location.
What I liked, was that the conference was not only about technical novelties in Django world, but also about human issues that programmers deal with in everyday life.
According to a manifest, the conference had a goal to strengthen the Django community and to shape responsible attitude towards the works done with Django.
We have to build stronger communities including everyone who wants to participate without discrimination. Although, at first, it might be difficult as people have biases, i.e. prejudices for or against one person or group; by being emphatic we can accept and include everyone no matter what is their gender identity or expression, sexual orientation, ethnicity, race, neurodiversity, age, religion, disabilities, geographical location, food diversities, body size, or family status.
Valuing diversity and individual differences is the key for a healthy, positive and successful community, that empowers its members and helps them grow stronger and happier.
Information technology companies (Apple, Alphabet, Microsoft, Amazon, and Facebook) are among the most traded companies in the world. IT connects people and their things, automates processes, stores and treats historical data. Usually you don't need many physical resources to start an IT business. Software developers have a power to shape the future, but should use this power responsibly:
With this, great responsibility is upon us: to make the future a better place, to make the future more evenly distributed, across gender gaps and discriminations, breaking economical, political and geographical barriers.
From the technical point of view, I liked several ideas mentioned in the conference:
startproject
has a parameter --template
for that where you can pass a URL to a zip file.EXPLAIN ANALYZE ...
SQL command to find the bottlenecks of your database queries. You can usually fix them by adding indexes.Finally, thanks to the organizers for making this conference as great as it was. The city was beautiful, the food and coffee was delicious, the location for the talks was impressive. Looking forward to the next DjangoCon Europe!
Cover photo by Aidas Bendoraitis
This week the post office delivered a package that made me very satisfied. It was a box with three paper versions of my "Web Development with Django Cookbook - Second Edition". The book was published at the end of January after months of hard, but fulfilling work in the late evenings and at weekends.
The first Django Cookbook was dealing with Django 1.6. Unfortunately, the support for that version is over. So it made sense to write an update for a newer Django version. The second edition was adapted for Django 1.8 which has a long-term support until April 2018 or later. This edition introduces new features added to Django 1.7 and Django 1.8, such as database migrations, QuerySet expressions, or System Check Framework. Most concepts in this new book should also be working with Django 1.9.
My top 5 favourite new recipes are these:
The book is worth reading for any Django developer, but will be best understood by those who already know the basics of web development with Django. You can learn more about the book and buy it at the Packt website or Amazon.
I thank the Packt Publishing very much for long cooperation in the development of this book. I am especially thankful to acquisition editor Nadeem N. Bagban, content development editors Arwa Manasawala and Sumeet Sawant, and technical editor Bharat Patil. Also I am grateful for insightful feedback from the reviewer Jake Kronika.
What 5 recipes do you find the most useful?
In this tutorial you are going to learn how to pass extra data to your serializer, before saving it to the database.
When using regular Django forms, there is this common pattern where we save the form with commit=False
and then pass
some extra data to the instance before saving it to the database, like this:
This is very useful because we can save the required information using only one database query and it also make it possible to handle not nullable columns that was not defined in the form.
To simulate this pattern using a Django REST Framework serializer you can do something like this:
You can also pass several parameters at once:
In this example I created an app named core
.
models.py
serializers.py
views.py
Very similar example, using the same models.py and serializers.py as in the previous example.
views.py
In this tutorial we are going to explore three date/datetime pickers options that you can easily use in a Django project. We are going to explore how to do it manually first, then how to set up a custom widget and finally how to use a third-party Django app with support to datetime pickers.
The implementation of a date picker is mostly done on the front-end.
The key part of the implementation is to assure Django will receive the date input value in the correct format, and also that Django will be able to reproduce the format when rendering a form with initial data.
We can also use custom widgets to provide a deeper integration between the front-end and back-end and also to promote better reuse throughout a project.
In the next sections we are going to explore following date pickers:
Tempus Dominus Bootstrap 4 Docs Source
XDSoft DateTimePicker Docs Source
Fengyuan Chen’s Datepicker Docs Source
This is a great JavaScript library and it integrate well with Bootstrap 4. The downside is that it requires moment.js and sort of need Font-Awesome for the icons.
It only make sense to use this library with you are already using Bootstrap 4 + jQuery, otherwise the list of CSS and JS may look a little bit overwhelming.
To install it you can use their CDN or download the latest release from their GitHub Releases page.
If you downloaded the code from the releases page, grab the processed code from the build/ folder.
Below, a static HTML example of the datepicker:
The challenge now is to have this input snippet integrated with a Django form.
forms.py
template
The script tag can be placed anywhere because the snippet $(function () { ... });
will run the datetimepicker
initialization when the page is ready. The only requirement is that this script tag is placed after the jQuery script
tag.
You can create the widget in any app you want, here I’m going to consider we have a Django app named core.
core/widgets.py
In the implementation above we generate a unique ID datetimepicker_id
and also include it in the widget context.
Then the front-end implementation is done inside the widget HTML snippet.
widgets/bootstrap_datetimepicker.html
Note how we make use of the built-in django/forms/widgets/input.html
template.
Now the usage:
core/forms.py
Now simply render the field:
template
The good thing about having the widget is that your form could have several date fields using the widget and you could simply render the whole form like:
The XDSoft DateTimePicker is a very versatile date picker and doesn’t rely on moment.js or Bootstrap, although it looks good in a Bootstrap website.
It is easy to use and it is very straightforward.
You can download the source from GitHub releases page.
Below, a static example so you can see the minimum requirements and how all the pieces come together:
A basic integration with Django would look like this:
forms.py
Simple form, default widget, nothing special.
Now using it on the template:
template
The id_date
is the default ID Django generates for the form fields (id_
+ name
).
core/widgets.py
widgets/xdsoft_datetimepicker.html
To have a more generic implementation, this time we are selecting the field to initialize the component using its name instead of its id, should the user change the id prefix.
Now the usage:
core/forms.py
template
This is a very beautiful and minimalist date picker. Unfortunately there is no time support. But if you only need dates this is a great choice.
To install this datepicker you can either use their CDN or download the sources from their GitHub releases page. Please note that they do not provide a compiled/processed JavaScript files. But you can download those to your local machine using the CDN.
A basic integration with Django (note that we are now using DateField
instead of DateTimeField
):
forms.py
template
core/widgets.py
widgets/fengyuanchen_datepicker.html
Usage:
core/forms.py
template
The implementation is very similar no matter what date/datetime picker you are using. Hopefully this tutorial provided some insights on how to integrate this kind of frontend library to a Django project.
As always, the best source of information about each of those libraries are their official documentation.
I also created an example project to show the usage and implementation of the widgets for each of the libraries presented in this tutorial. Grab the source code at github.com/sibtc/django-datetimepicker-example.
The Django forms API have two field types to work with multiple options: ChoiceField
and ModelChoiceField
.
Both use select input as the default widget and they work in a similar way, except that ModelChoiceField
is designed
to handle QuerySets and work with foreign key relationships.
A basic implementation using a ChoiceField
would be:
You can also organize the choices in groups to generate the <optgroup>
tags like this:
When you are using a ModelChoiceField
unfortunately there is no built-in solution.
Recently I found a nice solution on Django’s ticket tracker, where
someone proposed adding an opt_group
argument to the ModelChoiceField
.
While the discussion is still ongoing, Simon Charette proposed a really good solution.
Let’s see how we can integrate it in our project.
First consider the following models:
models.py
So now our category instead of being a regular choices field it is now a model and the Expense
model have a
relationship with it using a foreign key.
If we create a ModelForm
using this model, the result will be very similar to our first example.
To simulate a grouped categories you will need the code below. First create a new module named fields.py:
fields.py
And here is how you use it in your forms:
forms.py
Because in the example above I used a self-referencing relationship I had to add the exclude(parent=None)
to hide
the “group categories” from showing up in the select input as a valid option.
You can download the code used in this tutorial from GitHub: github.com/sibtc/django-grouped-choice-field-example
Credits to the solution Simon Charette on Django Ticket Track.
JWT stand for JSON Web Token and it is an authentication strategy used by client/server applications where the client is a Web application using JavaScript and some frontend framework like Angular, React or VueJS.
In this tutorial we are going to explore the specifics of JWT authentication. If you want to learn more about Token-based authentication using Django REST Framework (DRF), or if you want to know how to start a new DRF project you can read this tutorial: How to Implement Token Authentication using Django REST Framework. The concepts are the same, we are just going to switch the authentication backend.
The JWT is just an authorization token that should be included in all requests:
The JWT is acquired by exchanging an username + password for an access token and an refresh token.
The access token is usually short-lived (expires in 5 min or so, can be customized though).
The refresh token lives a little bit longer (expires in 24 hours, also customizable). It is comparable to an authentication session. After it expires, you need a full login with username + password again.
Why is that?
It’s a security feature and also it’s because the JWT holds a little bit more information. If you look closely the example I gave above, you will see the token is composed by three parts:
Those are three distinctive parts that compose a JWT:
So we have here:
This information is encoded using Base64. If we decode, we will see something like this:
header
payload
signature
The signature is issued by the JWT backend, using the header base64 + payload base64 + SECRET_KEY
. Upon each request
this signature is verified. If any information in the header or in the payload was changed by the client it will
invalidate the signature. The only way of checking and validating the signature is by using your application’s
SECRET_KEY
. Among other things, that’s why you should always keep your SECRET_KEY
secret!
For this tutorial we are going to use the djangorestframework_simplejwt
library, recommended by the DRF developers.
settings.py
urls.py
For this tutorial I will use the following route and API view:
views.py
urls.py
I will be using HTTPie to consume the API endpoints via the terminal. But you can also use cURL (readily available in many OS) to try things out locally.
Or alternatively, use the DRF web interface by accessing the endpoint URLs like this:
First step is to authenticate and obtain the token. The endpoint is /api/token/
and it only accepts POST requests.
So basically your response body is the two tokens:
After that you are going to store both the access token and the refresh token on the client side, usually in the localStorage.
In order to access the protected views on the backend (i.e., the API endpoints that require authentication), you should include the access token in the header of all requests, like this:
You can use this access token for the next five minutes.
After five min, the token will expire, and if you try to access the view again, you are going to get the following error:
To get a new access token, you should use the refresh token endpoint /api/token/refresh/
posting the
refresh token:
The return is a new access token that you should use in the subsequent requests.
The refresh token is valid for the next 24 hours. When it finally expires too, the user will need to perform a full authentication again using their username and password to get a new set of access token + refresh token.
At first glance the refresh token may look pointless, but in fact it is necessary to make sure the user still have the correct permissions. If your access token have a long expire time, it may take longer to update the information associated with the token. That’s because the authentication check is done by cryptographic means, instead of querying the database and verifying the data. So some information is sort of cached.
There is also a security aspect, in a sense that the refresh token only travel in the POST data. And the access token is sent via HTTP header, which may be logged along the way. So this also give a short window, should your access token be compromised.
This should cover the basics on the backend implementation. It’s worth checking the djangorestframework_simplejwt settings for further customization and to get a better idea of what the library offers.
The implementation on the frontend depends on what framework/library you are using. Some libraries and articles covering popular frontend frameworks like angular/react/vue.js:
The code used in this tutorial is available at github.com/sibtc/drf-jwt-example.
[Django 2.1.3 / Python 3.6.5 / Bootstrap 4.1.3]
In this tutorial we are going to explore some of the Django Crispy Forms features to handle advanced/custom forms rendering. This blog post started as a discussion in our community forum, so I decided to compile the insights and solutions in a blog post to benefit a wider audience.
Table of Contents
Throughout this tutorial we are going to implement the following Bootstrap 4 form using Django APIs:
This was taken from Bootstrap 4 official documentation as an example of how to use form rows.
NOTE!
The examples below refer to a base.html
template. Consider the code below:
base.html
Install it using pip:
Add it to your INSTALLED_APPS
and select which styles to use:
settings.py
For detailed instructions about how to install django-crispy-forms
, please refer to this tutorial:
How to Use Bootstrap 4 Forms With Django
The Python code required to represent the form above is the following:
In this case I’m using a regular Form
, but it could also be a ModelForm
based on a Django model with similar
fields. The state
field and the STATES
choices could be either a foreign key or anything else. Here I’m just using
a simple static example with three Brazilian states.
Template:
Rendered HTML:
Rendered HTML with validation state:
Same form code as in the example before.
Template:
Rendered HTML:
Rendered HTML with validation state:
Same form code as in the first example.
Template:
Rendered HTML:
Rendered HTML with validation state:
We could use the crispy forms layout helpers to achieve the same result as above. The implementation is done inside
the form __init__
method:
forms.py
The template implementation is very minimal:
The end result is the same.
Rendered HTML:
Rendered HTML with validation state:
You may also customize the field template and easily reuse throughout your application. Let’s say we want to use the custom Bootstrap 4 checkbox:
From the official documentation, the necessary HTML to output the input above:
Using the crispy forms API, we can create a new template for this custom field in our “templates” folder:
custom_checkbox.html
Now we can create a new crispy field, either in our forms.py module or in a new Python module named fields.py or something.
forms.py
We can use it now in our form definition:
forms.py
(PS: the AddressForm
was defined here and is the same as in the previous example.)
The end result:
There is much more Django Crispy Forms can do. Hopefully this tutorial gave you some extra insights on how to use the form helpers and layout classes. As always, the official documentation is the best source of information:
Django Crispy Forms layouts docs
Also, the code used in this tutorial is available on GitHub at github.com/sibtc/advanced-crispy-forms-examples.
In this tutorial you are going to learn how to implement Token-based authentication using Django REST Framework (DRF). The token authentication works by exchanging username and password for a token that will be used in all subsequent requests so to identify the user on the server side.
The specifics of how the authentication is handled on the client side vary a lot depending on the technology/language/framework you are working with. The client could be a mobile application using iOS or Android. It could be a desktop application using Python or C++. It could be a Web application using PHP or Ruby.
But once you understand the overall process, it’s easier to find the necessary resources and documentation for your specific use case.
Token authentication is suitable for client-server applications, where the token is safely stored. You should never expose your token, as it would be (sort of) equivalent of a handing out your username and password.
Table of Contents
So let’s start from the very beginning. Install Django and DRF:
Create a new Django project:
Navigate to the myapi folder:
Start a new app. I will call my app core:
Here is what your project structure should look like:
Add the core app (you created) and the rest_framework app (you installed) to the INSTALLED_APPS
, inside the
settings.py module:
myapi/settings.py
Return to the project root (the folder where the manage.py script is), and migrate the database:
Let’s create our first API view just to test things out:
myapi/core/views.py
Now register a path in the urls.py module:
myapi/urls.py
So now we have an API with just one endpoint /hello/
that we can perform GET
requests. We can use the browser to
consume this endpoint, just by accessing the URL http://127.0.0.1:8000/hello/
:
We can also ask to receive the response as plain JSON data by passing the format
parameter in the querystring like
http://127.0.0.1:8000/hello/?format=json
:
Both methods are fine to try out a DRF API, but sometimes a command line tool is more handy as we can play more easily with the requests headers. You can use cURL, which is widely available on all major Linux/macOS distributions:
But usually I prefer to use HTTPie, which is a pretty awesome Python command line tool:
Now let’s protect this API endpoint so we can implement the token authentication:
myapi/core/views.py
Try again to access the API endpoint:
And now we get an HTTP 403 Forbidden error. Now let’s implement the token authentication so we can access this endpoint.
We need to add two pieces of information in our settings.py module. First include rest_framework.authtoken to
your INSTALLED_APPS
and include the TokenAuthentication
to REST_FRAMEWORK
:
myapi/settings.py
Migrate the database to create the table that will store the authentication tokens:
Now we need a user account. Let’s just create one using the manage.py
command line utility:
The easiest way to generate a token, just for testing purpose, is using the command line utility again:
This piece of information, the random string 9054f7aa9305e012b3c2300408c3dfdf390fcddf
is what we are going to use
next to authenticate.
But now that we have the TokenAuthentication
in place, let’s try to make another request to our /hello/
endpoint:
Notice how our API is now providing some extra information to the client on the required authentication method.
So finally, let’s use our token!
And that’s pretty much it. For now on, on all subsequent request you should include the header Authorization: Token 9054f7aa9305e012b3c2300408c3dfdf390fcddf
.
The formatting looks weird and usually it is a point of confusion on how to set this header. It will depend on the client and how to set the HTTP request header.
For example, if we were using cURL, the command would be something like this:
Or if it was a Python requests call:
Or if we were using Angular, you could implement an HttpInterceptor
and set a header:
The DRF provide an endpoint for the users to request an authentication token using their username and password.
Include the following route to the urls.py module:
myapi/urls.py
So now we have a brand new API endpoint, which is /api-token-auth/
. Let’s first inspect it:
It doesn’t handle GET requests. Basically it’s just a view to receive a POST request with username and password.
Let’s try again:
The response body is the token associated with this particular user. After this point you store this token and apply it to the future requests.
Then, again, the way you are going to make the POST request to the API depends on the language/framework you are using.
If this was an Angular client, you could store the token in the localStorage
, if this was a Desktop CLI application
you could store in a text file in the user’s home directory in a dot file.
Hopefully this tutorial provided some insights on how the token authentication works. I will try to follow up this tutorial providing some concrete examples of Angular applications, command line applications and Web clients as well.
It is important to note that the default Token implementation has some limitations such as only one token per user, no built-in way to set an expiry date to the token.
You can grab the code used in this tutorial at github.com/sibtc/drf-token-auth-example.
This is a short post just to announce today I’m releasing a community forum for the simpleisbetterthancomplex.com readers! And I want you to be part of it.
I decided to create this community forum for a couple of reasons. First of all, I receive many emails with questions, asking for advice and asking my opinion about specific topics. I’m happy to answer those emails whenever I can, but unfortunately, I can’t answer them all. And when I’m able to answer those emails, the conversations and discussions have a high potential to be useful to others. So why not have some of those discussions in an open forum?
With this community forum, I also want to have a place for questions that are not suitable for StackOverflow. For example, “what’s the best database to use with Django?” or “Apache or NGINX?”. This kind of questions, where there is no right or wrong answer, but can serve as a starting point for a good discussion and exchange of experience.
Another reason is to have a single place to organize the readers’ requests, suggestions, and ideas for future tutorials and videos. There is a specific category for tutorials requests where you can share your ideas and upvote other’s requests to help me prioritize.
And really, I just want this forum to be a safe and respectful place where other tech enthusiasts can get together to talk about tech stuff, share experiences and help each other.
If you want to be part of this community, join us at community.simpleisbetterthancomplex.com!
See you there!
In this tutorial series, we are going to explore Django’s authentication system by implementing sign up, login, logout, password change, password reset and protected views from non-authenticated users. This tutorial is organized in 8 videos, one for each topic, ranging from 4 min to 15 min each.
Starting a Django project from scratch, creating a virtual environment and an initial Django app. After that, we are going to setup the templates and create an initial view to start working on the authentication.
If you are already familiar with Django, you can skip this video and jump to the Sign Up tutorial below.
First thing we are going to do is implement a sign up view using the built-in UserCreationForm
. In this video you
are also going to get some insights on basic Django form processing.
In this video tutorial we are going to first include the built-in Django auth URLs to our project and proceed to implement the login view.
In this tutorial we are going to include Django logout and also start playing with conditional templates, displaying different content depending if the user is authenticated or not.
Next The password change is a view where an authenticated user can change their password.
This tutorial is perhaps the most complicated one, because it involves several views and also sending emails. In this video tutorial you are going to learn how to use the default implementation of the password reset process and how to change the email messages.
After implementing the whole authentication system, this video gives you an overview on how to protect some views from
non authenticated users by using the @login_required
decorator and also using class-based views mixins.
Extra video showing how to integrate Django with Bootstrap 4 and how to use Django Crispy Forms to render Bootstrap forms properly. This video also include some general advices and tips about using Bootstrap 4.
If you want to learn more about Django authentication and some extra stuff related to it, like how to use Bootstrap to make your auth forms look good, or how to write unit tests for your auth-related views, you can read the forth part of my beginners guide to Django: A Complete Beginner’s Guide to Django - Part 4 - Authentication.
Of course the official documentation is the best source of information: Using the Django authentication system
The code used in this tutorial: github.com/sibtc/django-auth-tutorial-example
This was my first time recording this kind of content, so your feedback is highly appreciated. Please let me know what you think!
And don’t forget to subscribe to my YouTube channel! I will post exclusive Django tutorials there. So stay tuned! :-)
Django comes with a variety of command line utilities that can be either invoked using django-admin.py
or the
convenient manage.py
script. A nice thing about it is that you can also add your own commands. Those management
commands can be very handy when you need to interact with your application via command line using a terminal and it can
also serve as an interface to execute cron jobs. In this tutorial you are going to learn how to code your own commands.
Just before we get started, let’s take a moment to familiarize with Django’s command line interface. You are probably
already familiar with commands like startproject
, runserver
or collectstatic
. To see a complete list of commands
you can run the command below:
Output:
We can create our own commands for our apps and include them in the list by creating a management/commands directory inside an app directory, like below:
The name of the command file will be used to invoke using the command line utility. For example, if our command was
called my_custom_command.py
, then we will be able to execute it via:
Let’s explore next our first example.
Below, a basic example of what the custom command should look like:
management/commands/what_time_is_it.py
Basically a Django management command is composed by a class named Command
which inherits from BaseCommand
. The
command code should be defined inside the handle()
method.
See how we named our module what_time_is_it.py
. This command can be executed as:
Output:
You may be asking yourself, how is that different from a regular Python script, or what’s the benefit of it. Well, the main advantage is that all Django machinery is loaded and ready to be used. That means you can import models, execute queries to the database using Django’s ORM and interact with all your project’s resources.
Django make use of the argparse, which is part of Python’s standard
library. To handle arguments in our custom command we should define a method named add_arguments
.
The next example is a command that create random user instances. It takes a mandatory argument named total
, which
will define the number of users that will be created by the command.
management/commands/create_users.py
Here is how one would use it:
The optional (and named) arguments can be passed in any order. In the example below you will find the definition of an argument named “prefix”, which will be used to compose the username field:
management/commands/create_users.py
Usage:
or
If the prefix is used, the username field will be created as custom_user_oYwoxtt4vNHR
. If not prefix, it will be
created simply as oYwoxtt4vNHR
– a random string.
Another type of optional arguments are flags, which are used to handle boolean values. Let’s say we want to add an
--admin
flag, to instruct our command to create a super user or to create a regular user if the flag is not present.
management/commands/create_users.py
Usage:
Or
Let’s create a new command now named delete_users. In this new command we will be able to pass a list of user ids and the command should delete those users from the database.
management/commands/delete_users.py
Usage:
Output:
We can also pass a number of ids separated by spaces, so the command will delete the users in a single call:
Output:
We could improve the previous example a little big by setting an appropriate color to the output message:
management/commands/delete_users.py
Usage is the same as before, difference now is just the output:
Output:
Below a list of all available styles, in form of a management command:
If you have a task that must run periodically, like generating a report every Monday. Or let’s say you have a Web scrapper that collects data from some Website every 10 minutes. You can define this code as a management command and simply add it to your server’s crontab like this:
The example above will execute the my_custom_command
every day at 4 a.m.
The examples above should be enough to get you started. More advanced usage will boil down to knowing how to use the argparse features. And of course, Django’s official documentation on management commands is the best resource.
You can find all the code used in this tutorial on GitHub.
This is a quick tutorial to get you start with django-crispy-forms and never look back. Crispy-forms is a great application that gives you control over how you render Django forms, without breaking the default behavior. This tutorial is going to be tailored towards Bootstrap 4, but it can also be used with older Bootstrap versions as well as with the Foundation framework.
The main reason why I like to use it on my projects is because you can simply render a Django form using `` and it will be nicely rendered with Bootstrap 4, with very minimal setup. It’s a really life saver.
Install it using pip:
Add it to your INSTALLED_APPS
and select which styles to use:
settings.py
You can either download the latest Bootstrap 4 version at getbootstrap.com. In that case, go to download page and get the Compiled CSS and JS version.
Or you can use the hosted Bootstrap CDN:
For simplicity, I will be using the CDN version. Here is my base.html template that will be referenced in the following examples:
I only added the CSS file because we won’t be using any JavaScript feature.
Suppose we have a model named Person
as follows:
models.py
Let’s say we wanted to create a view to add new Person
objects. In that case we could use the built-in CreateView
:
views.py
Without any further change, Django will try to use a template named people/person_form.html
. In that case “people”
is the name of my Django app:
people/person_form.html
This is a very basic form rendering, and as it is, Django will render it like this, with no style, just plain form fields:
To render the same form using Bootstrap 4 CSS classes you can do the following:
people/person_form.html
Now the result, much better:
There are some cases where you may want more freedom to render your fields. You can do so by rendering the fields
manually and using the as_crispy_field
template filter:
And the result is something like the screen shot below:
The django-crispy-forms app have a special class named FormHelper
to make your life easier and to give you complete
control over how you want to render your forms.
Here is an example of an update view:
forms.py
The job is done inside the __init__()
method. The rest is just a regular Django model form. Here I’m defining that
this form should handle the request using the POST method and the form should have an submit button with label
“Save person”.
Now our view, just regular Django code:
views.py
Then in our template:
people/person_update_form.html
Here we can simply call the {% crispy %}
template tag and pass our form instance as parameter.
And that’s all you need to render the form:
That’s pretty much it for the basics. Honestly that’s about all that I use. Usually I don’t even go for the
FormHelper
objects. But there are much more about it. If you are interested, you can check their official
documentation: django-crispy-forms.readthedocs.io.
If you are not sure about where you should create a certain file, or want to explore the sample project I created for this tutorial, you can grab the source code on GitHub at github.com/sibtc/bootstrap-forms-example.
Highcharts is, in my opinion, one of the best JavaScript libraries to work with data visualization and charts out there. Even though Highcharts is open source, it’s a commercial library. It’s free for use in non-commercial applications though.
In this tutorial we are going to explore how to integrate it with a Django project to render dynamically generated charts. In relation to drawing the charts and rendering it to the client, all the hard work is done by Highcharts at the client side. The configuration and setup is pure JavaScript.
The main challenge here is on how to translate the data from your backend to a format that Highcharts will understand. This data may come from a database or an external API, and probably is represented as Python objects (like in a QuerySet), or simply be represented as Python dictionaries or lists.
Generally speaking, there are two ways to do it:
The first option is like a brute force and in many cases the easiest way. The second option requires a slightly complicated setup, but you will also benefit from the page loading speed and from the maintainability of the code.
Basically we just need to include the Highcharts library in our template and we are ready to go. You can either download and serve it locally or simply use their CDN:
Now we need some data. I thought that it would be fun to play with an existing dataset. The Titanic dataset is pretty famous one, and easy to access.
What I did here was loading the dataset (1300~ rows) into a model named Passenger
:
If you are familiar with data mining, data science, or machine learning probably you already know this data set. This dataset is usually used for learning purpose. It’s composed by the list of passengers of the famous RMS Titanic tragedy.
We won’t be doing anything smart with it, just querying the database and displaying the data using Highcharts.
I won’t dive into deep details about Highcharts. The goal is to understand how to make Django and Highcharts talk. For details about how to do this or that with Highcharts, best thing is to consult the official documentation.
Here is a working example of a column chart using Highcharts:
The code above generates the following chart:
The basic structure here is:
The most straightforward way to do it is by writing directly in the template (which is not recommended).
Let’s write an query to display the number of survivors and deaths organized by ticket class.
views.py
The queryset above generates a data in the following format:
Then we could just write it in the template, inside the JavaScript tags:
ticket_class.html
This kind of strategy is not really a good idea because the code is hard to read, hard to maintain and it is too easy
to shoot in the foot. Because we are using Python to generate JavaScript code, we have to format it properly.
For example, the code {% if not forloop.last %}, {% endif %}
is to not append a comma (,) after
the last item of the array (otherwise the result would be [200, 119, 181,]
). The newest JavaScript versions are
forgiving and accepts an extra comma (like Python does), but older versions don’t, so it might cause problem in old
browsers. Anyway, the point is you have to make sure your Python code is writing valid JavaScript code.
A slightly better way to do it would be processing the data a little bit more in the view:
views.py
ticket_class_2.html
Here’s what we are doing: first run through the queryset and create three separate lists, append the values and do the
formatting. After that, use the json
module and dump the Python lists into JSON format. The result are Python strings
properly formatted as JSON data.
We have to use the safe
template filter to properly render the categories
because Django automatically escape
characters like '
and "
for safety reason, so we have to instruct Django to trust and render it as it is.
We could also do all the configuration in the backend, like this:
views.py
ticket_class_3.html
As you can see, that way we move all the configuration to the server side. But we are still interacting with the JavaScript code directly.
Now this is how I usually like to work with Highcharts (or any other JavaScript library that interacts with the server).
The idea here is to render the chart using an asynchronous call, returning a JsonResponse
from the server.
This time, we are going to need two routes:
urls.py
The json_example
URL route is pointing to a regular view, which will render the template which will invoke the
chart_data
view. This call can be automatic upon page load, or it can be triggered by an action (a button click
for example).
views.py
Here we can see the json_example
is nothing special, just returning the json_example.html
template, which we are
going to explore in a minute.
The chart_data
is the one doing all the hard work. Here we have the database query and building the chart
dictionary. In the end we return the chart data as a JSON object.
json_example.html
Here is where the magic happens. The div with id container
is where the chart is going to be rendered. Now, observe
that I included a custom attribute named data-url
. Inside this attribute I stored the path to the view that will be
used to load the chart data.
Inside the ajax call, we make the request based on the URL provided in the data-url
and instruct the ajax request
that we are expecting a JSON object in return (defined by the dataType
). When the request completes, the JSON
response will be inside the data
parameter in the success function. Finally, inside the success
function, we
render the chart using the Highcharts API.
This is extremely useful because now we can decouple all the JavaScript from our template. In this example we used a single file for simplicity, but nothing stops us now from saving the script tag content in a separate file. This is great because we are no longer mixing the Django template language with JavaScript.
The result is the following screen shot:
In this tutorial we explored the basics on how to integrate Highcharts.js with Django. The implementation concepts used in this tutorial can be applied in other charts libraries such as Charts.js. The process should be very similar.
Whenever possible, try to avoid interacting with JavaScript code using the Django Template Language. Prefer returning the data as JSON objects already processed and ready to use.
Usually when working with charts and data visualization the most challenging part is to squeeze the data in the format required to render the chart. What I usually do is first create a static example hardcoding the data so I can have an idea about the data format. Next, I start creating the QuerySet using in the Python terminal. After I get it right, I finally write the view function.
If you want to learn more, the best way is to get your hands dirty. Here’s something you can do: