A cheat-sheet to make your Django apps better.
This reposetory contains my notes on Django concepts which I learn from various sources.
-
Use singular model name. E.g
class Organization(models.Model)
and notclass Organizations(models.Model)
-
Use snake case for model field names. E.g.
first_name
and notFirstName
-
Many-to-one relationships: Use ForeignKey to define ManyToOne relationship. E.g. If you want to design models for team and player where Many Player can belong to One Team do:
class Team(models.Model): name = models.CharField(max_length=100) class Player(models.Model): name = models.CharField(max_length=100) # Many Player can belong to one team team = models.ForeignKey(Team, ...)
-
Reverse Relationships: Use
related_name
if you want to get all the players for a team.
class Player(models.Model):
team = models.ForeignKey(Team, related_name='players', ...)
# Now you can do:
team1 = Team.objects.get(name='team1')
# Get all the players for this team
players = team1.players.all()
- Use
related_query_name
if you want get all the teams that has a Player namedKrishna
class Player(models.Model):
team = models.ForeignKey(
Team,
related_name='players',
related_query_name='player',
...
)
# Now you can do:
teams = Team.objects.filter(player__name='Krishna')
-
One-to-one relationships Use OneToOne Field when defining One-To-One Relationships. E.g. Suppose that each Team has a Coach. And one Coach can only belong to one Team.
class Team(models.Model): name = models.CharField(max_length=100) class Coach(models.Model): name = models.CharField(max_length=100) ... ... team = OneToOneField(Coach, ...)
There is one confusion that you might think of is that in which Model you should add the OneToOneField. In that case one rule that I follow is "Don't disturbe the existing models if not needed.": Suppose that you added
coach
field toTeam
model instead of the above.-
It will create two migrations
-
You will have to update team exclusively while creating Coach.
# Before team1 = Team.objects.get(name='team1') Coach.objects.create(name='tt', team=team1) # After team1 = Team.objects.get(name='team1') coach = Coach.objects.create(name='tt') team1.coach = coach team1.save()
-
-
ForeignKey and OneToOneField accept one required parameter
on_delete
which decides what will happen to your model when the data it is linked to gets deleted. For example look at the following:class Team(models.Model): name = models.CharField(max_length=100) class Player(models.Model): name = models.CharField(max_length=100) # Many Player can belong to one team team = models.ForeignKey(Team, on_delete=models.CASCADE)
In the above example
on_delete
is set toCASCADE
which means that delete all the Players in the team, when the team they are linked to gets deleted. But if it is set toSET_NULL
it means that keep they players and set theplayer.team
toNone
when the team is getting deleted. -
Any changes in your models requires you to run
python manage.py makemigrations
andpython manage.py migrate
-
Models should include all relevant domain/business logic as it's property or methods. For example for a player you might want to check if he is a captain or not:
class Player(models.Model): name = models.CharField(max_length=100) def is_captain(self): if self is captain: return True return False
-
Blank and Null Fields:
-
Null
is database-related. Defines if a given database column will accept null values or not. -
Blank
is validation-related. It will be used during forms validation, when calling form.is_valid(). -
Do not use
null=True
for text/char based field. Otherwise, you will end up having two possible values for "no data" that is:None
andan empty
string. Having two possible values for "no data" is redundant The Django convention is to use the empty string, not NULL.class Player(models.Model): name = models.CharField(max_length=100) # null=True is not required as `empty string` will be saved to the database. bio = models.TextField(blank=True) # here you may add `null=True` birth_date = models.DateField(null=True, blank=True)
-
Do not use
null=True
orblank=True
forBooleanField
. It is better to specify default values for such fields. If you realise that the field can remain empty, you needNullBooleanField
.
-
-
-
can be used to set default order of a list of objects. E.g. if you want to list all players in alphabetical order this is how you do it:
class Player(models.Model): name = models.CharField(max_length=100) class Meta: ordering = ['-name']
Now
Player.objects.all()
will return queryset of players in an alphabetical order but ordering is expensive so you should not use it when you don't need it.You might want to add an index to your database which may help to improve ordering performance:
class Player(models.Model): name = models.CharField(max_length=100) class Meta: indexes = [models.Index(fields=['name'])] ordering = ['-name']
-
can also be used to set abstract model by setting
abstract=True
in themeta class
. This model will then not be used to create any database table. Instead, when it is used as a base class for other models, its fields will be added to those of the child class.
-
-
Use
QuerySet.explain()
to understand how specific QuerySets are executed by your database. Also checkout django-debug-toolbar -
Use indexes to fields that you frequently query by using
filter()
,exclude()
,order_by
etc. -
Use indexed field to query:
# Faster player = Player.objects.get(id=10) # Slower. Assuming the name field is not indexed. player = Player.objects.get(name='Krishna')
-
Use
filter()
andexclude()
when needed. In case you want to perform some operation on only certain number of Players. -
Use iterator when dealing with
QuerySet
with large amount of data that you only need to access once. -
Use
F expression
to update fields in the same model.# Don't for player in Player.objects.all(): player.rating += 1 player.save() # Do Player.objects.update(rating=F('rating') + 1)
-
Use
QuerySet.count()
if you only want the count, rather thanlen(queryset)
. -
Use
QuerySet.exists()
if you only want to find out if at least one result exists, rather thanif queryset
-
Use
delete()
andupdate()
when needed.:# bulk update Player.objects.filter(name='Krishna').update(rating=10) # bulk delete Player.objects.filter(name='Krishna').delete()
-
Use
ForeignKey
values directlyplayer = Player.objects.get(id=1) # Don't team_name = player.team.name # Do team_name = player.team_name
-
Don't order results if not needed.
-
Use Bulk methods:
-
bulk_create
Suppose that you have to create a lot of Players:# Creating all the players at once is better Player.objects.bulk_create([ Player(name='dummy player 1'), Player(headline='dummy player 2'), ... ]) # Than creating one by one Player.objects.create(name='dummy player 1') Player.objects.create(name='dummy player 2') ...
-
bulk_update
You can also update in bulk:players = Player.objects.bulk_create([ Player(name='dummy player 1'), Player(headline='dummy player 2'), ... ]) players[0].rating = 4 players[1].rating = 3 ... # Updating all the players rating at once is better than Player.objects.bulk_update(players, ['rating']) # Updating them one by one players[0].save()
-
-
Use
select_related
andpreftech_related
: These methods are used to improve speed by minimising the number of database query when dealing with related objects.select_related
is used when you're dealing with ForeignKey and OneToOne Relations. Use when you want to get detail of one related object.
# Each player will be related to a team so why not get the team information also players = Player.objects.all().select_releted('team')
prefetch_related
is used when you're dealing with ManyToMany Relations. Use when you want to get details of many related object.
# Each player can belong to multiple groups so why not get those groups also. players = Player.objects.all().prefecth_relted('groups')
-
Managers are the interface through which database query operations are provided to Django models.
-
At least one
Manager
should exist for every model in a Django application. -
objects
is the default manager that is added to every model. -
You can define a manager for a model by calling
models.Manager()
:from django.db import models class Player(models.Model): #... people = models.Manager()
Now you can do
Player.people.all()
instead ofPlayer.objects.all()
-
You can also have custom managers:
-
Helpful when you need extra methods to your manager:
class CaptainManager(models.Manager): def get_captain(self): return captain class Player(models.Model): ... objects = PollManager()
Now you can do
Player.objects.get_captain()
with all the other methods onobjects
(.all()
,filter()
..etc.) -
Helpful in modifying the intial queryset that manager returns:
class HighRatedPlayerManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(rating='5') class Player(models.Model): title = models.CharField(max_length=100) author = models.CharField(max_length=50) objects = models.Manager() five_stars = HighRatedPlayerManager()
With
Player.objects.all()
you can get all the players but withPlayer.five_starts.all()
you only get players with rating 5. -
-
Middleware are hooks to modify Django request and response object.
-
You can use middleware to modify the request object which is sent to the view. E.g. every time you do
request.user
you get the user object. This functionality is provided viaAuthenticationMiddleware
which modifies the request object to add user to the request before it is sent to the view. -
You can also use middleware to modify the response object which is returned from the view.
-
Ordering of middlewares in your settings is very important. While processing request object middlware works from top to bottom and while processing response object middleware works from bottom to top.
-
Think of it like an onion where your view is the core of the onion. So when request comes, it will be passed through each of middlware from top to bottom. And then the view will be executed. After that the response of the view will be passed through each of the middleware from bottom to top.
-
Here is how you define a custom middleware:
class SimpleMiddleware: def __init__(self, get_response): self.get_response = get_response # One-time configuration and initialization. def __call__(self, request): # Code to be executed for each request before # the view (and later middleware) are called. response = self.get_response(request) # Code to be executed for each request/response after # the view is called. return response
To modify the request do something before the line
response = self.get_response(request)
and then that request will be passed to the next middleware(from top to bottom) or if no middleware is there then it will be passed to your views.Similarly to modify the response do something after the line
response = self.get_response(request)
and then your modified response will be passed to the next middleware from bottom to top.-
To activate a custom middleware you need to add it in your settings file in middleware section.
-
To know more about ordering of a middlware checkout 1.
-
- The Django Signals is a strategy to allow decoupled applications to get notified when certain events occur.
-
Let's say you want to send some message to slack everytime instance of a model is updated, but there are several place in your code base that this model can be updated. Now you don't want to call the send_message method everytime you're updating the model. You can do that via a signal hook which will get triggered every time this specific model's save method is triggered.
-
Signals are often misused, here are few tips to verify that you really need a signal:
- Use when many pieces of the applications are interested in the same event.
- Use when you are working with decoupled applications:
- Django core model.
- A model defined by third party app.
- Don't use signals when you have both sender and reciever at one place.
- Don't use signals in your views to send email.
-
Here is how you can implement signals in your django application:
-
Let's say I want to send a message everytime my
User
model is getting updated.# Put this in your app's `signals.py` @receiver(post_save, sender=User) def send_message_to_slack(sender, **kwargs): send_message(sender) # Now you must ensure that the receiver code is imported when the projects is started # in your apps.py do class ProfileConfig(AppConfig): name = 'myproject.profile' def ready(self): import myproject.profile.signals # noqa # And in your __init__.py put: # Note that this is not required if you're already referring to your AppConfig # in the `INSTALLED_APPS` settings. default_app_config = 'myproject.profile.apps.ProfileConfig'
-
In the above example we used Django's built-in signal
post_save
, there are many more built-in signals that you can use and also define your own if needed.
-
-
-
Know your version and use a secure one
What version of Django are you using? The choice of version determines what known vulnerabilities are present, and potentially exploitable, in your application. Have a look at the archive of all the security issues and closely follow Django's security release cycle.
-
Throttle user authentications
Django provides a lot of security features baked in, but the authentication system does not inherently protect against brute force attacks. It is important to write your own code to fix this, or use one of many open source solutions. (Like django-defender)
-
Use raw queries and custom SQL with caution
While it may be tempting to write raw sql queries and custom SQL, doing so may open the door for an attack. A user attempting to perform an sql injection(execute arbitrary sql on a database) is going to find it much harder, if you always use the ORM.
-
Watch you headers
When the site is served via https, the referer request header is utilized by Django to help prevent cross site request forgery(CSRF) attacks. If you're too strict with your referer-policy header, you disable the functionality of Django's CSRF protection. In the end you need to weigh the privacy benefits of using a strict referer-policy header with the benefits of the CSRF protection.
-
Be careful with your cookies
Some cookies are more secure than others -- the default cookie behavior is to connect over http. However, since we already established that you need to use https, you want to make sure your cookies are only being sent over https as well. To prevent leaking cookies, be sure to set your SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE settings to True.
-
Carefully handle user uploads
If your web application allows users to upload files, you're opening yourself to an attack vector and the upload logic should therefore be handled carefully. It is important to validate all uploaded files to be sure they are what you expect.
-
Understand all your dependencies
Always keep checking at the release cycle of your dependencies and upgrading them accordingly. If one of your dependencies package is not maintained for a long time, look for alternatives or help maintaing the package.
-
Change the admin URL
Change the default admin URL from
/admin/
to something else. This is so common anyone will be looking foryoudomain/admin
and trying brute force attacks.urlpatterns = [ path('my-special-admin-login/', admin.site.urls), ]
-
Never run
debug=True
in Production.