Resources
What & Link | Type |
---|---|
Django Project - Docs | Official Docs |
Django ORM Cookbook | Cheatsheet on ORM Syntax and Tricks |
You might also find my general Python cheatsheet useful.
Misc / To Sort
- How to return specific status code?
return HttpResponse(status=STATUS_CODE)
- Where can I find a list of included / bundled icons?
- There are a set of icons accessible under
/static/admin/img/{ICON}
, which you can find here
- There are a set of icons accessible under
- CommandError: Unable to serialize database: cursor "...sync...." does not exist
- Check for unapplied migrations, conflicts with models and database
- How to get the names of all the fields of a model
- You can use "concrete fields" ->
[f.name for f in my_model._meta.concrete_fields]
- You can use "concrete fields" ->
Dealing with Requests
Dealing with QueryDict
The QueryDict
class in Django (from django.http
) is an interesting class, with a lot of hidden complexity. There are two critical distinctions to note when dealing with them:
- They are (often) immutable by default. If you want to mutate them, you need to override the immutable flag (and/or clone first)
- Keys can be repeated - meaning that the same key could have mappings to two (or more) different values. This is unlike any other dictionary and adds a bunch of complexity when trying to retrieve or store values.
To expand on the multiple-values-per-key part of QueryDicts, here is an example of what this means:
query_dict = QueryDict("id=123&id=abc&color=red&color=blue")
print(query_dict["id"])
# > abc
# ^ Only returns a single value, which is deceiving...
print(query_dict.dict())
# > {'id': 'abc'}
# ^ Only returns a single value, which is deceiving...
print(query_dict.getlist("id"))
# > ['123', 'abc']
# ^ Ah! There are actually two mappings for this single key
print(list(query_dict.lists()))
# > [('id', ['123', 'abc']), ('color', ['red', 'blue'])]
# ^ Get all key->lists mappings
For some utilities that can help with working with QueryDicts, check out:
Useful Entrypoints
- For executing code only once, on startup
wsgi.py
Routing and URLs
- Dictionary to query string?
- This is not Django specific - just use something like
urlencode({'my_arg': my_val})
- If you need to combine it with resolving to a URL in your settings, you can do something like:
final_url = reverse(...) + urlencode(...)
- This is not Django specific - just use something like
- Capturing query parameters / querystring in URL patterns
- You don't - if you really want a value captured in a pattern, move it to be part of the path instead of the query string. For query params, instead use
request.GET.*
methods to access params inside request handler, without explicit capture syntax
- You don't - if you really want a value captured in a pattern, move it to be part of the path instead of the query string. For query params, instead use
Database, Models, and ORM
Models - Using Base Models
Django's model system is generally enjoyable to use, but there are a few common things that I almost always want my models to have that the default model omits:
- Timestamps, for creation and modification
- Validation of model fields before saving (
.save()
) completes
With this in mind, I often implement a base model that can be subclassed, with something like this:
import uuid
from django.db import models
class BaseModel(models.Model):
# Sometimes it is nice to have ID be a UUID,
# instead of using an auto-incrementing int
# as the PK
id = models.UUIDField(
primary_key=True, default=uuid.uuid4, unique=True, editable=False
)
# Django doesn't include timestamps by default
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
# Django' doesn't call `full_clean` on saves by default
def save(self, *args, **kwargs) -> None:
self.full_clean()
return super().save(*args, **kwargs)
class Meta:
abstract = True
ORM - Misc Tips
- Get and update in same query?
my_models.objects.filter(...).update()
(no need to callsave()
)
- Assigning relationship via FK ID instead of model instance
- Assign by
my_model.${RELATED}_id = fk_id
insteadmy_model.${RELATED} = ${INSTANCE}
- This also works for retrieval
- Assign by
- Only getting back certain fields (instead of
SELECT *
) - Delete all objects of a given model?
MyModel.objects.all().delete()
- Filter for not matching?
- Options:
.exclude(prop=val)
.filter(~Q(prop=val))
- Options:
- Check if an object exists matching filter?
MyModel.objects.filter(myprop=myval).exists()
- Get an object that might or might not exist, without throwing
- If you are unsure if a queryset will return a result, you can use
queryset.first()
, as it will returnNone
if there are no results, without throwing an exception. You can also combine this with filters - e.g.MyModel.objects.filter(pk=25).first()
- Beware: the
get
method -queryset.get()
- will raise aModel.DoesNotExist
exception if no object can be found. - Another option, if you want to return a
404
response instead of throwing an exception, is to use theget_object_or_404()
method instead
- If you are unsure if a queryset will return a result, you can use
- Beware that using
MyModel(param=val).save()
returnsNone
, not a model instance.- You can use
MyModel.objects.create(param=val)
instead, which does return an instance - Or, if you really want to use this approach but get the instance, you have to do it as two separation operations:
instance = MyModel(param=val) instance.save()
- You can use
- Error cannot assign <MyModel object (1)> ...
- Make sure the instance you are assigning to the model actually has been saved to the database and has a primary key before assigning. In some cases, you might need to call
.save()
first. - In some instances, you might need to use
(related_obj_id=my_obj.pk)
instead of(related_obj=my_obj)
when assigning on creation
- Make sure the instance you are assigning to the model actually has been saved to the database and has a primary key before assigning. In some cases, you might need to call
_get_target_ids
, etc. , TypeError: 'MyModel' instance expected, got <MyModel: my_instance> instead- If you are writing a migration, and passing in an object to a related field (e.g.
my_model.related_field.add(my_related_model))
, you either need to make sure that the related model instance was instantiated via a class created withapp.get_model()
(instead of animport
) or add the model instance by primary key (e.g.my_model.related_field.add(my_related_model.pk))
. See this StackOvrrflow for example.
- If you are writing a migration, and passing in an object to a related field (e.g.
Database - Django Model Relationship Fields
Django ORM Relations: One-to-One
Django ORM Relations: Many-to-Many
A many-to-many relationship in Django works with an intermediate join table. When writing the migration to add this linkage, you can specify the join table explicitly. For example:
class User(AbstractUser, BaseModel):
companies = models.ManyToManyField(
Company,
related_name="users",
through="my_app.CompanyMappings"
)
class CompanyMappings(BaseModel):
user = models.ForeignKey(User, on_delete=models.CASCADE)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
For retrieving items via a many-to-many relationship, you have a few different options
- For getting get the intermediate (
through
/ join) object, you can usemy_model.intermediate_name_set
. Example:my_memberships = my_user.companymappings_set.all()
acme_memberships = acme_company.companymappings_set.all()
- For getting the related items:
- For the model that defines the
ManyToManyField
, you can use the field name directly -my_model.related_thing.all()
my_companies = my_user.companies.all()
- For the reverse, you can access though the
related_name
propertyacme_users = acme_company.users.all()
support_users = Group.objects.get(name="Support").user_set.all()
- For the model that defines the
For adding new linkages, it depends on how you are storing the many-to-many. If it is a simple many-to-many join table, with no other data, you can use your_model.linked_thing.add(new_linked_thing)
. For example:
my_user.companies.add(acme_corp)
However, if you are using a many-to-many with extra metadata attached, you'll generally want to access the join model directly:
CompanyMappings.objects.create(
user=my_user,
company=acme_corp,
notes="Extra notes..."
)
Django ORM Relations: Many-to-One
Example: Author <-> Book
. Each author can have multiple books, but each book has only one author.
In the child model, which can only have one parent, use parent_field_name = models.ForeignKey(ParentModel, on_delete={YOUR_CHOSEN_DELETE_OPTION})
.
Continuing our example,
# In Book model author = models.ForeignField(Author, on_delete=models.CASCADE)
With a child model instance, the parent field should be automatically accessible as a model instance (e.g. my_book.author
). For setting or updating the parent of the child instance, you can set to None
(if nullable is allowed), set to an actual model instance, or use the magic *_id
property to set the related model by ID alone (e.g. my_book.author_id = 4
).
With a parent model instance, which can have multiple children, you can access the children with {PARENT_MODEL_INSTANCE}.{CHILD_MODEL_NAME}_set
. With our example, that would be my_authors_books = my_author.book_set.all()
.
While the relationship is hard-coded in the child model (you must add a ForeignKey
field), it is implicit on the parent; you don't actually have to add any code to the parent model. If you wish to, you could make the relationship more explicit in the parent by adding it to the serialization method, or as a dynamic property.
Django ORM Relations: Adding Relationship Items in Bulk
students = Student.objects.all()
for student in students:
student.degree_id = getDegree(student)
Student.objects.bulk_update(students, fields=['degree'])
ORM "Magic"
Both a pro and a con of the Django ORM is the amount of "magic" it tries to inject. I consider these things primarily exist only at runtime and are hard to statically type. For example:
${FIELD}_set
injected model members (viarelated_name
absence), for reverse lookups- Thankfully
django-stubs
supports typing for these
- Thankfully
- The
${FIELD}_id
for relationship fields - Many constraints aren't actually created at the DB level...
Migrations
Q&A:
- How do I roll back ALL migrations?
manage.py migrate my_app zero
- Or, assuming you have no data outside of Django, drop and rebuild the database before running the migrate command
- How do I create a new migration based on model changes?
manage.py makemigrations
Debug SQL
- For specific queries
- If you want to see the SQL generated:
str(myQuerySet.query)
- If you want the SQL explained
myQuerySet.explain()
- If you want to see the SQL generated:
- For
Q()
objects- Pass to a model first, then you can use the above methods
- E.g.:
q_obj = Q(name="Ozma") | Q(location="Oz") queryset = User.objects.filter(q_object) sql_str = str(queryset.query)
- For monitoring / debugging all queries made
from django.db import connection print(connection.queries)
How does get_or_create
work?
Use regular kwargs for the fields to evaluate for similarity, and use defaults=
for the fields that will combined with the filtering fields for the actual object creation if it doesn't already exist.
For example, if we want to create a new menu item if one doesn't already exist with the same name, but we don't care about the price matching:
bagel, had_to_create_bagel = MenuItem.objects.get_or_create(
name="bagel",
defaults={"price": 1.99}
)
Making Ad-Hoc Queries
Generally, you should stick with the Django ORM whenever possible. Django offers a lot of utilities to hand-tune queries while staying within the ORM ecosystem, so there aren't many good reasons to bypass this.
However, if you really do want to bypass the ORM and make queries that are more raw (e.g. bypassing models and just using table names), while reusing the database driver connection, you can do so in a few different ways.
https://docs.djangoproject.com/en/5.0/topics/db/sql/#executing-custom-sql-directly
If you have the right database driver / binary installed locally, for an interactive shell you can use:
python manage.py dbshell
If you don't have the DB driver installed, or prefer a code-based approach, you can use the connection
object from django.db
. A small example:
from django.db import connection
c = connection.cursor()
c.execute("SELECT * FROM my_table")
res = c.fetchall()
# Do something with results
c.close()
JSON
Django and rest_framework sending back escaped JSON
There are a few reasons why your API endpoint might be sending back JSON as an escaped (or even double escaped) string.
The first thing you should double check is that the model class has the field in question declared as a models.JSONField()
. E.g.:
from django.db import models
class Game(models.Model):
state = models.JSONField()
And when returning the model from the API endpoint as JSON, you probably want to use something like a rest_framework.serializers.ModelSerializer
based class. For example:
from .models import Game
from rest_framework import serializers
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
fields = ["state"]
... and return it with something like
return JsonResponse(GameSerializer(gameInstance).data, safe=False)
However, if you are still getting back escaped JSON as a string, especially if it is happening with a nested sub-field of the model, consider how the data is getting into the database before questioning the retrieval method. For example, I accidentally wrote a migration that introduced this issue, by using (WRONG) this:
# WRONG
seed_game = Game(
state="""{"id": 24}"""
)
The above code will permanently add an escaped JSON string into the DB. Don't do this.
...when I should have been using:
seed_game = Game(
state=json.loads("""{"id": 24}""")
)
StartProject
django-admin
must have the most confusing docs and instructions for startproject
...
By default, using the command django-admin startproject backend
will create a structure like:
./backend
- manage.py
- ./backend
- settings.py
- wsgi.py
- ... (etc.)
I usually prefer to rename the backend/backend
directory to something like backend/rootproject
, since that really is what that directory is - the root Django project that contains the shared settings, wsgi entrypoint, etc. I also usually like to name my core "app" with the name "common", since it usually contains a lot of shared business logic, and then specialized sub-app would get a unique name. So my final structure sometimes looks something like this:
./backend/
- manage.py
- rootproject/
- settings.py
- wsgi.py
- ... (etc.)
- common/
- migrations/
- views.py
- ... (etc.)
Another option, if you want to create without myproj/myproj
nesting, you need to use the .
trick, but so many tutorials leave out where to execute this command. If you already have a /myproj
directory, you don't cd
to it first and then execute, you stay in the root directory and run:
django-admin startproject myproj .
If you were to run that after first using cd myproj
, you would still end up with the double myproj/myproj
creation. What the above command really does is avoids the "directory already exists" type error.
manage.py
will still be placed in the current directory, instead of myproj
, and moving it into the same directory takes some changes:
- change lines that have
myproj.submodule
tosubmodule
(e.g. changemyproj.settings
tosettings
) - Change
BASE_DIR = Path(__file__).resolve().parent.parent
to justBASE_DIR = Path(__file__).resolve().parent
Note: With the above strategy, Django will allow the directory to already exist and contain files. This is is very beneficial, as it allows you to setup a
venv
within that directory before targeting it to install to
HTML Templating
- How to conditionally render an attribute?
- Wrap in
{% if %} ... {% endif %}
blocks
- Wrap in
Interacting from the Command Line
manage.py shell
will let you use the Python interpreter within your Django environment- To change a setting from the management shell and have it persist / apply to the live instance, ... well, the short answer is that you can't. You might find some instances in which this works, but most parts of Django won't play nice with this and it is strongly recommended against.
Django-Rest-Framework (DRF)
DRF Serializers
- How to represent a field that can be repeated multiple times (e.g. multiple files for a single upload)?
- Use
serializers.ListField
image_file = serializers.ListField(child=serializers.ImageField(), allow_empty=False)
- Use
- How to represent a nested model that can be repeated multiple times
- Use the existing serializer, but with
many=True
books = BookSerializer(many=True)
- Use the existing serializer, but with
- How to represent a one-to-many (reversed via FK)?
- One option is to add a property equal to a serializer of that linked item, with
source="{model_name}_set"
andmany=True
. Or, with `source="{}"- Example:
# In `models.py` class Article(): ... class Comment(): article = models.ForeignKey(Article, on_delete=models.CASCADE) # In your serializers: class Article(serializers.ModelSerializer[Article]): comments = CommentSerializer(source="comment_set", many=True)
- If you used an explicit
related_name=
on the linked item, use that as thesource
value. - Tip: You can use
required=False
if this serializer is used reused for input validation, and it isn't required at input time
- One option is to add a property equal to a serializer of that linked item, with
- How to represent a many-to-many?
- Use the
many=true
trick (see above), but you don't need thesource={}
argument if the field name matches, since the field should explicitly exist on the class you are serializing, as opposed to a reversed FK lookup
- Use the