I've been writing Django apps since 2008. Here are some Django Model tips that I've collected, applied over the years.
Before you do anything, please visit and read this doc. Django documentation is your friend!
Doc tells that, there is a rule/order in the Model class:
- All field declarations
- Custom manager attributes
- You custom methods, properties etc...
Here is an example:
def get_group_creator_sentinel(): payload = dict( firstname.lastname@example.org', first_name='Sentinel', last_name='User', ) return get_user_model().objects.get_or_create(**payload, defaults=payload) class Group(models.Model): """ User.objects.filter(group__name=...) Permission.objects.filter(group__name=...) """ name = models.CharField( max_length=150, unique=True, verbose_name=_('name'), ) creator = models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.SET(get_group_creator_sentinel), related_name='creator_groups', related_query_name='group', verbose_name=_('creator'), ) permissions = models.ManyToManyField( to=Permission, related_name='permissions_groups', related_query_name='group', verbose_name=_('permissions'), blank=True, ) objects = GroupManager() class Meta: app_label = 'core' verbose_name = _('group') verbose_name_plural = _('groups') def __str__(self): return self.name def natural_key(self): return (self.name,)
Model stands for a single element in the database. Model name should be singular. In some cases, Model name can be plural if you are building intermediate relations table since this is a different situation.
Some good model name examples:
If possible, set the table name by hand in
Meta class. This helps you to have smaller table names and allows you to know database better.
class Customer(models.Model): : class Meta: app_label = 'core' db_table = 'customer' verbose_name = _('customer') verbose_name_plural = _('customers') : :
In my projects, I always use a
base.py and put an abstract class for common usage:
class MyBaseModel(models.Model): created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) class Meta: abstract = True
I sometimes add
is_active if required.
I mostly collect all my models under a single app. This helps me to use short hand reference and keeps me avoiding circular imports.
With this usage style, I don't need to import models all the time! I use
Let's say we have an
Article model and
Article model has a user field:
from django.conf import settings class Article(models.Model): user = models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, : : )
or I have a
City model and
Country model relation;
class City(models.Model): country = models.ForeignKey( to='Country', on_delete=models.CASCADE, related_name='cities', related_query_name='city', verbose_name=_('country'), )
If you don't want to collect all models in a same app, you use
to='app_name.Model' too. Such as
ManyToMany field, always set
class City(models.Model): country = models.ForeignKey( to='Country', on_delete=models.CASCADE, related_name='cities', related_query_name='city', verbose_name=_('country'), ) : :
Country instance, you can query cities via
country.cities.filter(). You can use in the lookups too:
Why? Because you have
OneToOneField for that!
: : try: creator = user_model.objects.get(email=email) except user_model.DoesNotExist as exc: raise CommandError('email (%s) does not exists' % email) from exc : :
ObjectDoesNotExist is useful when checking relational lookups over
If you are oldskool like me, use this convention.
from django.utils.translation import gettext_lazy as _ from django.db import models class Post(models.Model): STATUS_OFFLINE = 0 STATUS_ONLINE = 1 STATUS_DELETED = 2 STATUS_DRAFT = 3 STATUS_CHOICES = ( (STATUS_OFFLINE, _('offline')), (STATUS_ONLINE, _('online')), (STATUS_DELETED, _('deleted')), (STATUS_DRAFT, _('draft')), ) status = models.IntegerField( choices=STATUS_CHOICES, default=STATUS_ONLINE, verbose_name=_('status'), ) : :
Django has enumeration types now, you can take a look at it for different approach.
If you have a
User model, do not add
user_status field. Make it shorter, just
status. No need to repeat
When project grows,
models.py file becomes too long. I always put my models separately in a models package:
models/ __init__.py user.py post.py comment.py
from .user import User from .post import Post : :
In the model file, I add
__all__ = ['ModelName'];
from django.db import models __all__ = ['City'] class City(models.Model): : :
If you have
ManyToMany field in your model, Django handles everything for you. You have no control over that extra table, model, model's save method or migration.
What happens when you need to keep extra fields in that intermediate table?
through_fields is your friend:
class Customer(models.Model): name = models.CharField(max_length=100, verbose_name=_('name')) users = models.ManyToManyField( to=settings.AUTH_USER_MODEL, through='CustomerMembership', through_fields=('customer', 'user'), related_name='customers', related_query_name='customer', blank=True, verbose_name=_('users'), ) : : class CustomerMembership(models.Model): customer = models.ForeignKey( to='Customer', on_delete=models.CASCADE, ) user = models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, ) is_admin = models.BooleanField( default=False, verbose_name=_('customer admin status'), ) : :
Now we have full control over
CustomerMembership model. It's a regular model now! Django Admin also have great support for through operations:
class CustomerInlineAdmin(models.TabularInlineAdmin): model = Customer.users.through extra = 0 autocomplete_fields = ['customer', 'user'] : : class CustomerAdmin(admin.ModelAdmin): list_display = ['__str__'] autocomplete_fields = ['users'] ordering = ['name'] inlines = [CustomerInlineAdmin] :
Lastly, always consider/plan about your upcoming queries. Design your model against your future lookups. Maybe you need to filter/report your model for YEAR only. You need to make date lookup such as
created_at__year=2021 or against
day. Maybe it's better to add an integer field to hold year or month or day value? Querying an integer field will always be easy if you compare to date query...
I hope you enjoyed these tips & tricks! Happy programming!