Best practices working with Django models in Python
Here we go:
1. Correct Model Naming
It is generally recommended to use singular nouns for model naming, for example:
Article. That is, the last component of the name should be a noun, e.g.: Some New Shiny Item. It is correct to use singular numbers when one unit of a model does not contain information about several objects.
2. Relationship Field Naming
For relationships such as
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
3. Correct Related-Name
It is reasonable to indicate a related-name in
plural as related-name addressing returns queryset. Please, do set adequate related-names. In the majority of cases, the name of the model in plural will be just right. For example:
class Owner(models.Model): pass class Item(models.Model): owner = models.ForeignKey(Owner, related_name='items')
4. Do not use
There is no point in using
unique=Trueas there exists
OneToOneField for such cases.
5. Attributes and Methods Order in a Model
Preferable attributes and methods order in a model (an empty string between the points).
- constants (for choices and other)
- fields of the model
- custom manager indication
def __unicode__(python 2) or
def __str__(python 3)
- other special methods
- other methods
Please note that the given order was taken from documentations and slightly expanded.
6. Adding a Model via Migration
If you need to add a model, then, having created a class of a model, execute serially
migrate (or use
South for Django 1.6 and below).
You should not allow thoughtless use of denormalization in relational databases. Always try to avoid it, except for the cases when you denormalise data consciously for whatever the reason may be (e.g. productivity). If at the stage of database designing you understand that you need to denormalise much of the data, a good option could be the use of NoSQL. However, if most of data does not require denormalisation, which cannot be avoided, think about a relational base with JsonField to store some data.
Do not use
BooleanField. It should also be pointed out that it is better to specify default values for such fields. If you realise that the field can remain empty, you need
9. Business Logic in Models
The best place to allocate business logic for your project is in models, namely method models and model manager. It is possible that method models can only provoke some methods/functions. If it is inconvenient or impossible to allocate logic in models, you need to replace its forms or serializers in tasks.
10. Field Duplication in ModelForm
Do not duplicate model fields in
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.
11. Do not use
ModelName.DoesNotExist instead of
ObjectDoesNotExist makes your exception intercepting more specialised, which is a positive practice.
12. Use of
choices, it is recommended to:
- keep strings instead of numbers in the database (although this is not the best option from the point of optional database use, it is more convenient in practise as strings are more demonstrable, which allows the use of clear filters with get options from the box in REST frameworks).
- variables for variants storage are constants. That is why they must be indicated in uppercase.
- indicate the variants before the fields lists.
- if it is a list of the statuses, indicate it in chronological order (e.g.
- you can use
model_utilslibrary. Take model
Article, for instance:
from model_utils import Choices class Article(models.Model): STATUSES = Choices( (0, 'draft', _('draft')), (1, 'published', _('published')) ) status = models.IntegerField(choices=STATUSES, default=STATUSES.draft) …
13. Why do you need an extra
Using ORM, do not add an extra method call
14. Many flags in a model?
If it is justified, replace several
BooleanFields with one field,
class Article(models.Model): is_published = models.BooleanField(default=False) is_verified = models.BooleanField(default=False) …
Assume the logic of our application presupposes that the article is not published and checked initially, then it is checked and marked
True and then it is published. You can notice that article cannot be published without being checked. So there are 3 conditions in total, but with 2 boolean fields we do not have 4 possible variants, and you should make sure there are no articles with wrong boolean fields conditions combinations. That is why using one status field instead of two boolean fields is a better option:
class Article(models.Model): STATUSES = Choices('new', 'verified', 'published') status = models.IntegerField(choices=STATUSES, default=STATUSES.draft) …
This example may not be very illustrative, but imagine that you have 3 or more such boolean fields in your model, and validation control for these field value combinations can be really tiresome.
15. 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 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.
16. Dirty data should not be found in a base
PositiveIntegerField instead of
IntegerField if it is not senseless, because “bad” data must not go to the base. For the same reason you should always use
unique_together for logically unique data and never use
required=False in every field.
17. Getting the earliest/latest object
You can use
ModelName.objects.earliest('created'/'earliest') instead of
order_by('created') and you can also put
Meta model. You should keep in mind that
earliest as well as
get can cause an exception
order_by('created').first() is the most useful variant.
18. Never make
Do not use
len to get queryset’s objects amount. The
count method can be used for this purpose. Like this:
len(ModelName.objects.all()), firstly the query for selecting all data from the table will be carried out, then this data will be transformed into a Python object, and the length of this object will be found with the help of
len. It is highly recommended not to use this method as
count will address to a corresponding SQL function
count, an easier query will be carried out in that database and fewer resources will be required for python code performance.
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. Remember, that querysets are lazy, and if you use queryset as a boolean value, an inappropriate query to a database will be carried out.
help_text as documentation
help_text in fields as a part of documentation will definitely facilitate the understanding of the data structure by you, your colleagues, and admin users.
21. Money Information Storage
Do not use
FloatField to store information about the quantity of money. Instead, use
DecimalField for this purpose. You can also keep this information in cents, units, etc.
Do not add
_id suffix to
In all non abstract models, add methods
__unicode__(python 2) or
__str__(python 3). These methods must always return strings.
24. 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 use
Meta.fields=”__all__” for the same reason.
25. Do not heap all files loaded by 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. To avoid such problems, you can do the following:
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)