docs
  • Overview
  • 🐍 PYTHON
    • Type Hints
    • PEP8 Style Guide for Python Code
    • 🏡Pipenv
    • Pathlib
  • 🕸Django
    • 🗄models
      • 🎯Best Practices
      • 🚦Django Signals
    • ⚙️ settings
    • DRF
      • Serializer
      • Authentication
      • Permissions
      • Viewsets
    • Testing
      • Faker and Factory Boy
    • 🧪Test Coverage
    • 💦Python-Decouple
    • Django Tips:
    • 💾Django ORM Queryset
    • Custom Exceptions
    • Celery
    • Resources
  • Deploy
    • 🚀Django Deployment
    • 🔒Setup SSL Certificate
  • 💾Database
    • MongoDB
  • 🛠️DevOps
    • 🖥Scripting
      • A First Script
      • Loops
      • Test
      • Variables
      • External programs
      • Functions
    • Command Line Shortcuts
    • Basic Linux Commands
    • 🎛Microservices
    • 🐳Docker
      • Docker Commands
      • Docker Compose
      • Django project
    • Kubernates
  • 📝Software IDE
    • EditorConfig
    • Linters
    • VsCode
Powered by GitBook
On this page
  • Model style
  • Naming Models
  • Relationship Field Naming
  • Attributes and Methods Order in a Model
  • Reverse Relationships
  • Correct Related-Name
  • Meta class
  • Field Duplication in ModelForm
  • Many flags in a model?
  • Redundant model name in a field name
  • Getting the earliest/latest object
  • Don't use null=true if you don't need it
  • Transparent fields list
  • Do not heap all files loaded by the user in the same folder
  • Use abstract models
  • Use custom Manager and QuerySet
  • Resources

Was this helpful?

  1. 🕸Django
  2. 🗄models

🎯Best Practices

A well-designed data model allows for a smooth development process and a source code that is easy to understand and maintain.

Model style

Models definition is one of the most important parts of our application. Something that makes all the difference in defining the field types properly.

Naming Models

The model definition is a class, so always use CapWords convention (no underscores). E.g. User, Permission, ContentType, etc

For the model’s attributes use snake_case. E.g. first_name, last_name, etc.

Always name your models using singular. Call it Subject instead of Subjects

from django.db import models

class Subject(models.Model):
    name = models.CharField(max_length=30)
    isbn_no = models.CharField(max_length=20)

Relationship Field Naming

For relationships such as ForeignKey, OneToOneKey, ManyToMany it is sometimes better to specify a name. Imagine there is a model called Article, - in which one of the relationships is ForeignKey for model User. If this field contains information about the author of the article, then author will be a more appropriate name than user.

Do not use ForeignKey with unique=True

There is no point in using ForeignKey with unique=Trueas there exists OneToOneField for such cases.

Attributes and Methods Order in a Model

The Django Coding Style suggests the following order of inner classes, methods, and attributes:

  • constants (for choices and others)

  • All database fields

  • Custom manager

  • class Meta

  • def __str__()

  • other special methods

  • def clean()

  • dev save()

  • def get_absolut_url()

  • other custom methods

If choices is defined for a given model field, define each choice as a list of tuples, with an all-uppercase name as a class attribute on the model.

Example:

from django.db import models
from django.urls import reverse

class Company(models.Model):
    # CHOICES
    PUBLIC_LIMITED_COMPANY = 'PLC'
    PRIVATE_COMPANY_LIMITED = 'LTD'
    LIMITED_LIABILITY_PARTNERSHIP = 'LLP'
    COMPANY_TYPE_CHOICES = (
        (PUBLIC_LIMITED_COMPANY, 'Public limited company'),
        (PRIVATE_COMPANY_LIMITED, 'Private company limited by shares'),
        (LIMITED_LIABILITY_PARTNERSHIP, 'Limited liability partnership'),
    )

    # DATABASE FIELDS
    name = models.CharField('name', max_length=30)
    vat_identification_number = models.CharField('VAT', max_length=20)
    company_type = models.CharField('type', max_length=3, choices=COMPANY_TYPE_CHOICES)

    # MANAGERS
    objects = models.Manager()
    limited_companies = LimitedCompanyManager()

    # META CLASS
    class Meta:
        verbose_name = 'company'
        verbose_name_plural = 'companies'

    # TO STRING METHOD
    def __str__(self):
        return self.name

    # SAVE METHOD
    def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs)  # Call the "real" save() method.
        do_something_else()

    # ABSOLUTE URL METHOD
    def get_absolute_url(self):
        return reverse('company_details', kwargs={'pk': self.id})

    # OTHER METHODS
    def process_invoices(self):
        do_something()
<!-- BAD template code. Avoid! -->
<a href="/company/{{ object.id }}/">{{ object.name }}</a>

<!-- Correct -->
<a href="{{ object.get_absolute_url }}/">{{ object.name }}</a>

For the "human-readable" value of a choice field, use get_FOO_display().

company.get_company_type_display()

# 'Private company limited by shares'

Reverse Relationships

Correct Related-Name

The related_name attribute in the ForeignKey fields are extremely useful. It lets us define a meaningful name for the reverse relationship.

Rule of thumb: if you are not sure what would be the related_name, use the plural of the model holding the ForeignKey.

class Company:
    name = models.CharField(max_length=30)

class Employee:
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    company = models.ForeignKey(
                    Company,
                    on_delete=models.CASCADE,
                    related_name='employees')

That means the Company model will have a special attribute named employees, which will return a QuerySet with all employees instances related to the company.

google = Company.objects.get(name='Google')
google.employees.all()

santosh = Employee.objects.get(first_name='Santosh')
google = Company.objects.get(name='Google')
google.employees.add(santosh)

related_query_name

This kind of relationship also applies to query filters. For example, if I wanted to list all companies that employ people named ‘Santosh’, I could do the following:

companies = Company.objects.filter(employee__first_name='Santosh')

If you want to customize the name of this relationship, here is how we do it:

class Employee:
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    company = models.ForeignKey(
        Company,
        on_delete=models.CASCADE,
        related_name='employees',
        related_query_name='person'
    )
    
# Then the usage would be:

companies = Company.objects.filter(person__first_name='Santosh')

In Student Models

from django.utils.translation import gettext as _


class Student(models.Model):
    first_name = models.CharField(_('first name'), max_length=30)
    last_name = models.CharField(_('last name'), max_length=30)
    university = models.ForeignKey(
        University,
        on_delete=models.CASCADE,
        related_name='students',
        related_query_name='person',
    )

    def __str__(self):
        return f'{self.first_name} {self.last_name}'

    def get_absolute_url(self):
        return reverse('student_detail', args=[str(self.id)])

To use it consistently, related_name goes as plural and related_query_name goes as singular.

Meta class

The Meta class is incredibly powerful and has a long list of features.

A good first step is to explicitly name your model too, not just your fields. This can be done with verbose_name and verbose_name_plural. Otherwise, Django would just add ansto make it pluraluniversitys which is wrong.

from django.utils.translation import gettext as _

class Meta:
    verbose_name = _('university')
    verbose_name_plural = _('universities')
    indexes = [models.Index(fields=['full_name'])]
    ordering = ['-full_name']

Be aware though that there can be a performance hit to ordering results so don't order results if you don't need to.

Do not use null=True or

blank=True for BooleanField

It's better to specify default values for such fields. Use NullBooleanField if the field can be empty

Business logic is in the model method and model manager.

If it is inconvenient or impossible to allocate logic in models, you need to replace its forms or serializers in tasks.

Field Duplication in ModelForm

Do not use objectDoesNotExist

UseModelName.DoesNotExist instead of ObjectDoesNotExist

Do not add an extra .all() before filter(), count() etc.

Using ORM, do not add an extra method call all before filter(), count(), etc.

Many flags in a model?

replace several BooleanFields with one field, status


class Article(models.Model):
    is_published = models.BooleanField(default=False)
    is_verified = models.BooleanField(default=False)

        
# Better Options

class Article(models.Model):
    STATUSES = Choices('new', 'verified', 'published')

    status = models.IntegerField(choices=STATUSES, default=STATUSES.draft)

Redundant model name in a field name

Do not add model names to fields if there is no need to do so, e.g. if the table User has a field user_status - you should rename the field into status, as long as there are no other statuses in this model.

Always use PositiveIntegerField instead of IntegerField if it is not senseless

Getting the earliest/latest object

we can use ModelName.objects.earliest('created'/'earliest') instead of order_by('created')[0] and we can also put get_latest_by in Meta model. We should keep in mind that latest/earliest as well as get can cause an exception DoesNotExist. Therefore, order_by('created').first() is the most useful variant.

Never use len(queryset)

Do not use len to get queryset’s objects amount. The countmethod can be used for this purpose

if queryset is a bad idea

Do not use queryset as a boolean value: instead of if queryset: do something use if queryset.exists(): do something.

Do not use FloatField to Store Money Information

Use DecimalField to store information of money.

Don't use null=true if you don't need it

Avoid using null on string-based fields such as CharField and TextField. If a string-based field has null=True, that means it has two possible values for “no data”: NULL, and the empty string. In most cases, it’s redundant to have two possible values for “no data;” the Django convention is to use the empty string, not NULL.

null=True - It is database-related. Defines if a given database column will accept null values or not. blank=True - It is validation-related. It will be used during forms validation when callingform.is_valid(). In TextField it's better to keep the default value. blank=True , default=''

Transparent fields list

Do not use Meta.exclude for a model’s fields list description in ModelForm. It is better to use Meta.fields for this as it makes the fields list transparent.

Do not heap all files loaded by the user in the same folder

Sometimes even a separate folder for each FileField will not be enough if a large amount of downloaded files is expected. Storing many files in one folder means the file system will search for the needed file more slowly.

def get_upload_path(instance, filename):
    return os.path.join('account/avatars/', now().date().strftime("%Y/%m/%d"), filename)

class User(AbstractUser):
    avatar = models.ImageField(blank=True, upload_to=get_upload_path)

Use abstract models

If we want to share some logic between models, we can use abstract models.

class CreatedatModel(models.Model):
    created_at = models.DateTimeField(verbose_name="Created at", auto_now_add=True)

    class Meta:
        abstract = True

 
class Post(CreatedatModel):
... 

class Comment(CreatedatModel):
...

Use custom Manager and QuerySet

The bigger the project we work on, the more we repeat the same code in different places.

To keep our code DRY and allocate business logic in models, we can use custom Managers and Queryset.

For example. If you need to get comments to count for posts, from the example above.

class CustomManager(models.Manager):

    def with_comments_counter(self):
        return self.get_queryset().annotate(comments_count=Count('comment_set'))

# Now we can use:

posts = Post.objects.with_comments_counter()

posts[0].comments_count

If we want to use this method in the chain with others queryset methods, we should use custom QuerySet:

class CustomQuerySet(models.query.QuerySet):
    """
    Substitution the QuerySet, and adding additional methods to QuerySet
    """

    def with_comments_counter(self):
        """
        Adds comments counter to queryset
        """
        return self.annotate(comments_count=Count('comment_set'))


# Now you can use:

posts = Post.objects.filter(...).with_comments_counter()

posts[0].comments_count 

Some Tips

  • ManyToManyField does not support validators.

  • null has no effect on ManyToManyField since there is no way to require a relationship at the database level.

  • django-extensions Custom extensions including shell_plus to automatically load models into the shell and runserver_plus among many others.

Resources

  • docs.djangoproject.com

  • simpleisbetterthancomplex.com

  • steelkiwi.com

Previous🗄modelsNextDjango Signals

Last updated 4 years ago

Was this helpful?

Do not duplicate model fields in ModelForm or ModelSerializer without need. If you want to specify that the form uses all model fields, use MetaFields. If you need to redefine a widget for a field with nothing else to be changed in this field, make use of Meta widgets to indicate widgets.

🚫