Here we go:
It is generally recommended to use singular nouns for model naming, for example: User, Post, 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.
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.
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')
There is no point in using ForeignKey with unique=Trueas there exists OneToOneField for such cases.
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
meta
def unicode (python 2) or def str (python 3)
other special methods
def clean
def save
def get_absolut_url
other methods
Please note that the given order was taken from documentations and slightly expanded.
If you need to add a model, then, having created a class of a model, execute serially manage.py commands makemigrations and 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 null=True or blank=True for 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 NullBooleanField.
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.
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.
Using ModelName.DoesNotExist instead of ObjectDoesNotExist makes your exception intercepting more specialised, which is a positive practice.
While using 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. new, in_progress, completed).
you can use Choices from the model_utils library. 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)
…
Using ORM, do not add an extra method call all before filter(), count(), etc.
If it is justified, replace several BooleanFields with one field, status-like. e.g.
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 is_verified in 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.
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.
Always use 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,unique_together for logically unique data and never use required=False in every field.
You can use ModelName.objects.earliest('created'/'earliest') instead of order_by('created')[0] and you can also put get_latest_by in Meta model. You 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.
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(). With count, an easier query will be carried out in that database and fewer resources will be required for python code performance.
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.
Using model 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.
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.
null=True - Allows column to keep null value.
blank=True - Will be used only if Forms for validation and not related to the database.
In text-based fields, it's better to keep default value.
blank=True
default=''
This way you'll get only one possible value for columns without data.
Do not add _id suffix to ForeignKeyField and OneToOneField.
In all non abstract models, add methods unicode(python 2) or str(python 3). These methods must always return strings.
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.
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)
If you want to share some logic between models, you can use abstract models.
class CreatedatModel(models.Model):
created_at = models.DateTimeField(
verbose_name=u"Created at",
auto_now_add=True
)
class Meta:
abstract = True
class Post(CreatedatModel):
...
class Comment(CreatedatModel):
...
The bigger project you work on, the more you repeat the same code in different places.
To keep your code DRY and allocate business logic in models, you can use custom Managers and Queryset.
For example. If you need to get comments 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 you can use:
posts = Post.objects.with_comments_counter()
posts[0].comments_count
If you want to use this method in chain with others queryset methods,
you 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