{"id":1629,"date":"2024-03-16T02:55:19","date_gmt":"2024-03-15T23:25:19","guid":{"rendered":"https:\/\/mshaeri.com\/blog\/?p=1629"},"modified":"2026-07-06T11:15:00","modified_gmt":"2026-07-06T21:15:00","slug":"django-best-practices-part-1","status":"publish","type":"post","link":"https:\/\/mshaeri.com\/blog\/django-best-practices-part-1\/","title":{"rendered":"Django Best Practices &#8211; Part 1, Django Models"},"content":{"rendered":"\n<p>Django is famous for its simplicity, flexibility, and robust features that made it a &#8220;batteries-included&#8221; framework offering comprehensive tools for web development, database operations, URL routing, HTML templating, and security. In this post and later posts (should time allow), I try to cover best practices for developing Django applications, with an emphasis on building performant, maintainable APIs, by gathering insights from authoritative sources and hands-on experience across multiple projects.<\/p>\n\n\n\n<p>Let&#8217;s start with Django&#8217;s ORM, which serves as cornerstone of any application. As a developer, often, we hastily code the models without considering the long-term consequences. Our rushed decisions usually lead to future complications or even data corruption. So, it&#8217;s crucial to carefully plan and design Django models to ensure a robust foundation for your application.<\/p>\n\n\n\n<p>This post covers some best practice advices in these topics  :<\/p>\n\n\n\n<ol>\n<li><a href=\"#optimal-number-of-models-in-django-apps\" data-type=\"internal\" data-id=\"#optimal-number-of-models-in-django-apps\">Optimal number of models in Django apps<\/a><\/li>\n\n\n\n<li><a href=\"#django-model-inheritance\" data-type=\"internal\" data-id=\"#django-model-inheritance\">Django model inheritance<\/a><\/li>\n\n\n\n<li><a href=\"#denormalization-in-django-model\" data-type=\"internal\" data-id=\"#denormalization-in-django-model\">Denormalization in django model<\/a><\/li>\n\n\n\n<li><a href=\"#django-model-custom-manager\" data-type=\"internal\" data-id=\"#django-model-custom-manager\">Django Custom managers<\/a><\/li>\n\n\n\n<li><a href=\"#empty-values-in-django-char-based-fields\" data-type=\"internal\" data-id=\"#empty-values-in-django-char-based-fields\">Empty values in Django char based fields<\/a><\/li>\n\n\n\n<li><a href=\"#django-model-migrations-consolidation\" data-type=\"internal\" data-id=\"#django-model-migrations-consolidation\">Django model migrations consolidation<\/a><\/li>\n\n\n\n<li><a href=\"#unique-uuid-identifier-in-django-model\" data-type=\"internal\" data-id=\"#unique-uuid-identifier-in-django-model\">Unique uuid identifier in Django model<\/a><\/li>\n\n\n\n<li><a href=\"#django-orm-advanced-query-tools\" data-type=\"internal\" data-id=\"#django-orm-advanced-query-tools\">Django ORM advanced query tools<\/a><\/li>\n\n\n\n<li><a href=\"#django-pk-vs-id\" data-type=\"internal\" data-id=\"#django-pk-vs-id\">Django model pk property for primary key fields<\/a><\/li>\n\n\n\n<li><a href=\"#django-model-naming\" data-type=\"internal\" data-id=\"#django-model-naming\">Django model naming<\/a><\/li>\n\n\n\n<li><a href=\"#django-model-save-updated-fields\" data-type=\"internal\" data-id=\"#django-model-save-updated-fields\">Update existing record in DB using Model.save()<\/a><\/li>\n\n\n\n<li><a href=\"#django-exist-vs-count\" data-type=\"internal\" data-id=\"#django-exist-vs-count\">Avoid count() when it isn&#8217;t necessary <\/a><\/li>\n\n\n\n<li><a href=\"#avoid-unique-foreignKey-relation\">Avoid unique foreign Key relation<\/a><\/li>\n\n\n\n<li><a href=\"#order-model-at-model-level\" data-type=\"internal\" data-id=\"#order-model-at-model-level\">Order querysets in model level<\/a><\/li>\n\n\n\n<li><a href=\"#Use-select_for_update-Wisely\" data-type=\"internal\" data-id=\"#Use-select_for_update-Wisely\">Use select_for_update wisely<\/a><\/li>\n\n\n\n<li><a href=\"#Keep-Business-Logic-Inside-the-Model\" data-type=\"internal\" data-id=\"#Keep-Business-Logic-Inside-the-Model\">Keep Business Logic Inside the Model<\/a><\/li>\n\n\n\n<li><a href=\"#Enforce-Data-Consistency-at-the-Database-Level\">Enforce Data Consistency at the Database Level<\/a><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"optimal-number-of-models-in-django-apps\">1. Keep Number Of Models In Apps No MoreThan 10<\/h2>\n\n\n\n<p>If you&#8217;ve got 20 models in a single app , it&#8217;s time to slice and dice the app into smaller ones. To keep things manageable and avoid overloading, keep number of models no more than ten models per app. Using bounded context principles in Domain-Driven Design (DDD) can help in organizing separate apps and grouping related models.<\/p>\n\n\n<div class=\"wp-block-image is-resized\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"744\" src=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-1024x744.png\" alt=\"Break down the app to keep models number no more than 10\" class=\"wp-image-1632\" style=\"width:474px;height:auto\" title=\"Break down the app to keep models number no more than 10\" srcset=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-1024x744.png 1024w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-300x218.png 300w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-768x558.png 768w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image.png 1205w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Break down the app to keep models number no more than 10<\/figcaption><\/figure><\/div>\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"django-model-inheritance\">2. Don&#8217;t Use Multi Table Inheritance<\/h2>\n\n\n\n<p>It&#8217;s a good practice to have base model. Typically, fields such as &#8216;created_at&#8217; and &#8216;updated_at&#8217; are ideal for go into the BaseModel. But, you should avoid using&nbsp;<strong>multi-table inheritance<\/strong>, because it leads to confusion and significant overhead.  For each query on child it equires to join on parent model. Instead, opt for&nbsp;<strong>OneToOneFields<\/strong>&nbsp;and&nbsp;<strong>ForeignKeys<\/strong>. Multi-table inheritance does nothing but making troubles. In other word, in Django, subclassing creates new tables and involves numerous left joins, which can hamper performance, especially in high-demand environments like game backends. To avoid this, handle inheritance manually using techniques like <strong>null<\/strong>, <strong>OneToOne<\/strong>, or <strong>Foreign key<\/strong>.<\/p>\n\n\n<div class=\"wp-block-image is-resized\">\n<figure class=\"aligncenter size-medium\"><a href=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"215\" src=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-2-300x215.png\" alt=\"OneToOneFields\u00a0and\u00a0ForeignKeys are better than multi option table inheritance\" class=\"wp-image-1636\" style=\"width:340px;height:auto\" srcset=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-2-300x215.png 300w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-2-1024x734.png 1024w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-2-768x550.png 768w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-2-120x85.png 120w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-2.png 1079w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><figcaption class=\"wp-element-caption\">OneToOneFields&nbsp;and&nbsp;ForeignKeys in most cases are better option than multi table inheritance<\/figcaption><\/figure><\/div>\n\n\n<p>Bad practice:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.db import models         \n\n                    \nclass Employee(models.Model):                          \n    name= models.CharField(max_length=100)         \n                            \n\nclass RegularEmployee(Employee):              \n    salary= models.IntegerField()       \n    bonus= models.IntegerField()       \n\n<\/code><\/pre>\n\n\n\n<p>However, <strong>abstract models<\/strong> are still very useful. They allow you to eliminate redundant fields across multiple tables (e.g., <code>created<\/code>, <code>updated<\/code>, <code>soft_delete<\/code>). An abstract model doesn\u2019t exist as a table in the database and cannot be instantiated. It simply serves as a reusable template for defining shared structure and behavior across your models:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">class BaseModel(models.Model):\n     created = models.DateTimeField(auto_now_add=True)\n     updated = models.DateTimeField(auto_now=True)\n     soft_delete = models.BooleanField(default=False)\n     class Meta:\n         abstract = True\n\n class Product(BaseModel):\n     name= models.CharField(max_length=25)\n     company= models.CharField(max_length=25)\n     serial_number= models.CharField(max_length=25)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"denormalization-in-django-model\">3. Denormalization Should be The Last Solution<\/h2>\n\n\n\n<p>We often see the denormalization as a first solution for challenges in our projects, unaware that it can cause complexity and increase the risk of data loss. It&#8217;s strongly recommended to consider other techniques such as caching before denormalization. Consider denormalization only when other techniques didn&#8217;t meet your needs.<\/p>\n\n\n<div class=\"wp-block-image is-resized\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"575\" src=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-1-1024x575.png\" alt=\"Denormalization Should be The Last Solution\" class=\"wp-image-1635\" style=\"width:660px;height:auto\" srcset=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-1-1024x575.png 1024w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-1-300x169.png 300w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-1-768x432.png 768w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2024\/03\/image-1.png 1251w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Denormalization Should be The Last Solution<\/figcaption><\/figure><\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"django-model-custom-manager\">4. Use Managers For Custom DB Queries<\/h2>\n\n\n\n<p>Django model manager provides a convenient mechanism for encapsulating complex query logic related to a model. Try to place frequently used operations associated with a particular model in the custom manager to have reusable code as well as improved code organization and readability. In the following example the&nbsp;<code>get_published_posts()<\/code>&nbsp;method filters blog posts with a status of \u201cpublished\u201d. We can use this method wherever we need to retrieve published posts, keeping our code DRY.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.db import models\nclass PostManager(models.Manager):\n     def get_published_posts(self):\n         \"\"\"Retrieve all published blog posts.\"\"\"\n         return self.filter(status='published')\n\nclass BlogPost(models.Model):\n     title = models.CharField(max_length=200)\n     content = models.TextField()\n     status = models.CharField(max_length=10, choices=[('draft', 'Draft'), ('published', 'Published')])\n\n     objects = PostManager()\n\n # Usage:\n published_posts = BlogPost.objects.get_published_posts()<\/code><\/pre>\n\n\n\n<p>The Manager method should be dedicated to database-related tasks. Take, for example, the Manager method below, which filters product records based on their numeric serial number using their display serial number that starts with &#8216;SN&#8217;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">class DeviceManager(models.Manager):\n    def get_by_display_serial_number(display_serial_number:str):\n        numeric_serial_number = int(display_serial_number[2:])\n        products = Products.objects.filter(serial_number= numeric_serial_number)\n        return products<\/code><\/pre>\n\n\n\n<p>This approach falls short in terms of code clarity and single responsibility principle. It&#8217;s not a specialized database query or a complex operation; rather, it merely involves manipulating the input parameter, which is none of Manager method&#8217;s business. A more effective and cleaner approach would be creating a simple helper method to convert the display serial number to the numeric serial number, followed by querying it using Django&#8217;s model filter:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">def get_numeric_serial_number(display_serial_number:str) -&gt; int:\n    return int(display_serial_number[2:])\n\ndef some_service_or_view():\n   ...\n   numeric_serial_number = get_numeric_serial_number(display_serial_number)\n   products = Product.objects.filter(serial_number=numeric_serial_number)<\/code><\/pre>\n\n\n\n<p>By segregating the conversion logic into a separate function, the code becomes more modular and comprehensible. This way, the purpose of each function is distinct, adhering to the single responsibility principle, that improves maintainability and readability. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"empty-values-in-django-char-based-fields\">5. Avoid null=True For Char Fields<\/h2>\n\n\n\n<p>Allowing both <strong><code>NULL<\/code> <\/strong>and <strong>empty values <\/strong>in string-based fields is generally discouraged because it&#8217;s against data consistency. Allowing <strong>null <\/strong>values in such fields results in two possible representations for absence of data: <strong>NULL or an empty string<\/strong>. To maintain clarity and consistency, Django conventionally favors the use of an <strong>empty string<\/strong> to represent absence of data.<\/p>\n\n\n\n<p>Bad practice:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.db import models\n\n\nclass Person(models.Model):\n    name = models.CharField(max_length=150, null=True, blank=True)\n<\/code><\/pre>\n\n\n\n<p>Best Practice:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.db import models\n\n\nclass Person(models.Model):\n    name = models.CharField(max_length=150, blank=True)\n<\/code><\/pre>\n\n\n\n<p>However, there is an exception to this guideline when the field is declared with <strong>unique=True<\/strong>. In such cases, two <strong>empty strings<\/strong> is considered as duplication, so you should change it to <strong>null=True<\/strong> instead of <strong>blank=True<\/strong>.<\/p>\n\n\n\n<p>String-based fields includes:<\/p>\n\n\n\n<ul>\n<li>CharField<\/li>\n\n\n\n<li>TextField<\/li>\n\n\n\n<li>EmailField<\/li>\n\n\n\n<li>URLField<\/li>\n\n\n\n<li>SlugField<\/li>\n\n\n\n<li>FilePathField<\/li>\n\n\n\n<li>URLField (Repeated mention)<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"django-model-migrations-consolidation\">6. Consolidate Migrations for Cleaner Release<\/h2>\n\n\n\n<p>We may have more than on changes in models in a release. Consolidating all migrations into a single file for each app in each release simplifies the management and deployment of database schema changes. This approach bring a cleaner release process and reduces the risk of migration related issues. You can use Django <strong>squashmigrations <\/strong>to bring specific generated migrations to heel:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\"><code>python manage.py squashmigrations &lt;appname&gt; &lt;squashfrom&gt; &lt;squashto&gt;<\/code><\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"unique-uuid-identifier-in-django-model\">7. Use Two Unique Identifiers For Your Models\u200a :<\/h2>\n\n\n\n<p>In real world project, use two identifiers for records in a Django model, a private identifier, often the primary key (<code>id<\/code>), and a public ID, represented by a UUID (UID). This approach offers both security and convenience, as it prevents revealing sensitive information about the data while still allowing for unique identification of records. Maintaining enumerators as private is always advisable as they reveal sensitive information about our data, such as the number of records ( e.g. products or accounts) we have, which we prefer to keep confidential:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">import uuid\nfrom django.db import models\n\n class YourModel(models.Model):\n     id = models.AutoField(primary_key=True)\n     uid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) \n     name = models.CharField(max_length=100) \n     description = models.TextField() \n\n     def __str__(self):     \n         return f'{self.name} - {self.uid}'<\/code><\/pre>\n\n\n\n<p><strong><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">Don&#8217;t use UUID as primary key. <\/mark><\/strong><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-black-color\">The issue with UUID as primary key is the inefficiency in inserts due to the non-sequential nature of UUIDs. Because the primary key is often a clustered index by default in majority of databases. It means, databases need to resort physical storage when inserting new ID with lower ordinality. With UUID it will happen almost all the time. This can lead to significant delays in inserting new records, taking seconds or even minutes as the database grows.<\/mark><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"django-orm-advanced-query-tools\">8. Use Advanced Query Tools Instead Of Processing Records In Python<\/h2>\n\n\n\n<p>Instead of working with data in Python, let Django&#8217;s advanced query tools handle this task for you. By doing this, we can not only enhance performance but also achieve cleaner and more maintainable code.  Let&#8217;s elaborate this concept with an example. Assume we need to have a list of all students whose Math scores are more than their English scores. Without Django ORM query expressions, it can be done with following code :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from models.students import Student\nstudents = []\nfor student in Student.objects.iterator():\n    if student.math_score &gt; student.english_score:\n        students.append(student)<\/code><\/pre>\n\n\n\n<p>The above code iterating through every Student record in the database using Python, one by one. It&#8217;s slow, memory consuming, and potentially leading to race conditions. Race conditions may arise when the script is executed concurrently with other user interactions with the same data. A more efficient and race-condition free approach is to use Django query expressions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.db.models import F\nfrom models.students import Student\nstudents = Student.objects.filter(math_score__gt=F('english_score'))<\/code><\/pre>\n\n\n\n<p>This way, we leverage the database itself to perform the comparison, enhancing project performance and stability.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"django-pk-vs-id\">9. Use pk instead of id<\/h2>\n\n\n\n<p>In Django, the <strong><code>id<\/code> <\/strong>field is the default primary key, automatically generating a unique integer for each database record. The <strong><code>pk<\/code> <\/strong>property, however, refers to the model&#8217;s designated primary key field, whether it&#8217;s <strong><code>id<\/code><\/strong>, <strong><code>student_id<\/code><\/strong>, or something else. Using <strong><code>pk<\/code><\/strong> throughout your code offers flexibility, allowing you to change the primary key field without modifying your code. This makes your code more readable, self-explanatory, and consistent, regardless of the primary key&#8217;s name.<\/p>\n\n\n\n<p><strong>Example 1 \u2013 Using id:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">student = Student.objects.get(id=42)\nprint(student.id)<\/code><\/pre>\n\n\n\n<p><strong>Example 2 \u2013 Using pk:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">student = Student.objects.get(pk=42)\nprint(student.pk)<\/code><\/pre>\n\n\n\n<p>I compared the performance of <strong><code>pk<\/code><\/strong> against <strong><code>id<\/code> <\/strong>in a queryset filtering and retrieval operation with 100,000 sample records. The operation time increased by only 8% in 100,000 retrieve operations, a small trade-off for the benefits of readability and consistency.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"django-model-naming\">10. Django Model Naming<\/h2>\n\n\n\n<ul>\n<li>Model names should use singular nouns to represent individual entities. This helps clarify the relationships between models and minimizes potential confusion.<\/li>\n\n\n\n<li>Django models use CamelCase, a Python-based convention where each word in the name starts with an uppercase letter and no underscores are included.<\/li>\n\n\n\n<li>A <strong>ManyToManyField<\/strong> should be named with a plural noun that reflects the associated model. For example, if an <strong>Author<\/strong> model has a <strong>ManyToManyField <\/strong>linked to a <strong>Book<\/strong> model, the field might be called &#8220;books.&#8221; A <strong>OneToOneField<\/strong> should use a singular noun that mirrors the related model, showing a one-to-one relationship. For instance, a <strong>User<\/strong> model could have a one-to-one link with a <strong>Profile<\/strong> model, with the field named &#8220;profile.&#8221;<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"django-model-save-updated-fields\">11. Use <code>update_fields<\/code> with <code>save()<\/code><\/h3>\n\n\n\n<p>To update specific columns in a database record, it is better to use the <code>update_fields<\/code> parameter when calling the <code>save()<\/code> method. This approach allows you to specify precisely which fields should be updated.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">product = Product.objects.get(id=1)\nproduct.name = \"new product name\"\nproduct.save(update_fields=['name'])<\/code><\/pre>\n\n\n\n<p>The resulting SQL query will be:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"sql\" class=\"language-sql\">UPDATE \"product\"\nSET    \"name\" = 'new product name'\nWHERE  \"product\".\"id\" = 1<\/code><\/pre>\n\n\n\n<p>You can also update multiple fields at once by including additional field names in the <code>update_fields<\/code> list.<\/p>\n\n\n\n<p>Using this method is more efficient because it limits the database operation to only the specified fields, reducing unnecessary overhead and improving performance.  Another key reason to use <code>update_fields<\/code> is to prevent data conflicts during concurrent updates. Consider a scenario with the <code>Product<\/code> model: if one user sets the <code>is_deleted<\/code> flag to <code>True<\/code> while another user changes the product&#8217;s name, and these actions happen in separate processes, using the generic <code>save()<\/code> method can lead to issues. The second process might unintentionally overwrite the <code>is_deleted<\/code> value back to <code>False<\/code> because it retains the outdated value in memory.<\/p>\n\n\n\n<p>While projects with high levels of concurrent data modification may require more robust conflict management strategies, using <code>update_fields<\/code> to update only the necessary fields significantly reduces the risk of unintended side effects.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"django-exist-vs-count\">12. Avoid count() when it is not necessary<\/h2>\n\n\n\n<p>Database counts are slow. Avoid using count if it&#8217;s not necessary.  A common usage of count is cases where you only need to check if any results exist. In this case using <code>queryset.count()<\/code> is less efficient than <code>queryset.exists()<\/code>. The example below demonstrates a more efficient and readable approach:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from models import Hound  \n\nqueryset = Hound.objects.filter(pk=1)  \n\nif queryset.exists():  \n    return \"Run away!\"  \nelse:  \n    return \"The coast is clear\"  \n<\/code><\/pre>\n\n\n\n<p>In contrast, the following approach using <code>queryset.count()<\/code> is both harder to read and less efficient:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from models import Hound\n queryset = Hound.objects.filter(pk=1)\n if queryset.count() &gt; 0:  # Unnecessary counting of all rows\n     return \"Run away!\"\n else:\n     return \"The coast is clear\"<\/code><\/pre>\n\n\n\n<p>According to the Django documentation, you should use <code>queryset.count()<\/code> when you need the exact number of results, and <code>queryset.exists()<\/code> when you only need to determine if at least one result exists.<\/p>\n\n\n\n<p>This distinction is crucial because <code>queryset.count()<\/code> triggers an SQL query that scans all rows in the database table to calculate the total count. In contrast, <code>queryset.exists()<\/code> optimizes the query by only checking for the presence of a single record, avoiding unnecessary overhead. It achieves this by:<\/p>\n\n\n\n<ul>\n<li>Removing ordering.<\/li>\n\n\n\n<li>Removing grouping.<\/li>\n\n\n\n<li>Clearing any developer-defined <code>select_related<\/code> or <code>distinct<\/code> clauses in the queryset.<\/li>\n<\/ul>\n\n\n\n<p>Note that, worse than using <code>count()<\/code> method, is checking if a <strong>queryset <\/strong>contains data like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">if queryset:\n   # do smothing<\/code><\/pre>\n\n\n\n<p>QuerySets in Django are <strong>lazy<\/strong>, evaluating them in a boolean context triggers a database query, which can lead to <strong>performance overhead<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"avoid-unique-foreignKey-relation\">13. Avoid Unique Foreign Key relation<\/h2>\n\n\n\n<p>A <strong>one-to-one relationship<\/strong> links one record in a model to exactly one record in another. Instead of using <code>ForeignKey(unique=True)<\/code>, which is less idiomatic, Django provides a more appropriate solution: <code>OneToOneField<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Example:<\/strong><\/h3>\n\n\n\n<p>\u274c <strong>Avoid:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.contrib.auth.models import User\nfrom django.db import models\n\n class Profile(models.Model):\n<strong>     user = models.ForeignKey(User, unique=True)<\/strong>\n     phone_number = models.CharField(max_length=15, blank=True, null=True)\n     birth_date = models.DateField(blank=True, null=True)\n     address = models.TextField(blank=True, null=True)\n \n<\/code><\/pre>\n\n\n\n<p>\u2705 <strong>Use:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.contrib.auth.models import User\nfrom django.db import models\n\n class Profile(models.Model):\n<strong>     user = models.OneToOneField(User, on_delete=models.CASCADE)<\/strong>\n     phone_number = models.CharField(max_length=15, blank=True, null=True)\n     birth_date = models.DateField(blank=True, null=True)\n     address = models.TextField(blank=True, null=True)\n \n<\/code><\/pre>\n\n\n\n<p>The  <strong>OneToOneField<\/strong> field brings better readability, enforces the one-to-one constraint at the database level, and aligns with Django&#8217;s best practices.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"order-model-at-model-level\">14. Order Queryset at the Model Level<\/h3>\n\n\n\n<p>If a model is supposed to be sorted in the majority of cases \u2014 for example, in list views, API endpoints, or admin pages \u2014 it&#8217;s a good practice to define a default ordering in the model\u2019s <code>Meta<\/code> class:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">class Product(models.Model):\n    name = models.CharField(max_length=255)\n    created_at = models.DateTimeField(auto_now_add=True)\n\n    class Meta:\n        ordering = ['-created_at']\n<\/code><\/pre>\n\n\n\n<p>This ensures consistent ordering across your project without the need to explicitly add <code>.order_by()<\/code> in every query. In other word, it helps avoid situations where developers forget to add <code>.order_by()<\/code> in new queries leading to inconsistent or unexpected results especially during pagination.<\/p>\n\n\n\n<p>Note that, this should only be applied when it <em>really<\/em> makes sense \u2014 i.e., when most of your queries and views rely on a particular ordering.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"Use-select_for_update-Wisely\">15. Use <code>select_for_update<\/code> Wisely<\/h3>\n\n\n\n<p>When using <code><strong>select_for_update<\/strong><\/code>, it&#8217;s crucial to understand how it interacts with related models, especially when combined with <code><strong>select_related<\/strong><\/code>. While <strong><code>select_for_update<\/code> <\/strong>is an excellent way to prevent race conditions by locking rows for update, it can have unintended consequences if you&#8217;re not careful.<\/p>\n\n\n\n<p>If you use <strong><code>select_for_update<\/code> <\/strong>in conjunction with <code><strong>select_related<\/strong><\/code>, Django will lock not only the rows from the base queryset model but also the rows from the related models fetched through <code><strong>select_related<\/strong><\/code>. This behavior can unintentionally escalate the scope of database locks and potentially lead to contention or even deadlocks.<\/p>\n\n\n\n<p><strong>Best Practice<\/strong><br>Be explicit about which related models you include in <strong><code>select_related<\/code> <\/strong>when using <code><strong>select_for_update<\/strong><\/code>. Only include the models you truly intend to lock. Avoid doing <code><strong><code><strong>select_for_update<\/strong><\/code><\/strong>()<\/code> without arguments in such cases, as it may cause broader locking than needed.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\"># \u274c This may lock more than you expect\nBook.objects.select_related(\"author\",\"category\").select_for_update().get(pk=1)\n\n# \u2705 Better: explicitly state the relation you want to lock\nBook.objects.select_related(\"author\").select_for_update(of=('self',)).get(pk=1)\n# Note that self is a special keyword referring to queryset's model\n# which here is Book\n<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>The later queryset avoids locking other tables unnecessarily, leading to safer concurrency handling. It&#8217;s recommended to always explicitly mention the tables to lock even if you don&#8217;t have <code><strong>select_related <\/strong><\/code>at this moment, because you might have in the future.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Keep-Business-Logic-Inside-the-Model\">16. Keep Business Logic Inside the Model<\/h2>\n\n\n\n<p>One important principle when working with Django models is this: <strong>keep model-related business logic inside the model itself whenever possible.<\/strong> A common mistake is scattering model behavior across API views, serializers, forms, signals, or service functions. For example, generating a serial number for a new <code>Device<\/code> should not happen inside the API endpoint that creates the device. The model itself should own that responsibility.<\/p>\n\n\n\n<p>By placing this logic in the model layer, every piece of code that creates a <code>Device<\/code> automatically follows the same rules, whether it comes from the Django admin, a management command, a Celery task, an API endpoint, or a test case. This improves consistency and significantly reduces the chance of bugs caused by duplicated or missing logic.<\/p>\n\n\n\n<p>Following this rule aligns well with several software engineering principles:<\/p>\n\n\n\n<ul>\n<li><strong>DRY (Don\u2019t Repeat Yourself)<\/strong> : business rules live in one place.<\/li>\n\n\n\n<li><strong>Single Responsibility Principle<\/strong> : views and serializers should focus on input\/output handling, not domain behavior.<\/li>\n\n\n\n<li><strong>Encapsulation<\/strong> : the model protects and manages its own state.<\/li>\n\n\n\n<li><strong>Maintainability<\/strong> : future changes only need to happen in one location.<\/li>\n<\/ul>\n\n\n\n<p>Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">class Device(models.Model):\n    ...\n\n    def save(self, *args, **kwargs):\n        self.full_clean()\n\n        if not self.serial_number:\n            max_serial_number = (\n                self.__class__.objects.aggregate(\n                    models.Max(\"serial_number\")\n                )[\"serial_number__max\"]\n                or 0\n            )\n            self.serial_number = max_serial_number + 1\n\n        super().save(*args, **kwargs)\n<\/code><\/pre>\n\n\n\n<p>In this example, the <code>Device<\/code> model guarantees that every newly created instance receives a serial number if one is not provided explicitly. Consumers of the model do not need to know <em>how<\/em> serial numbers are generated, they simply create a <code>Device<\/code>, and the model handles the rest.<\/p>\n\n\n\n<p>Keeping domain rules close to the data they belonged usually results in cleaner architecture, thinner views, and more predictable behavior across the application.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Enforce-Data-Consistency-at-the-Database-Level\">17. Enforce Data Consistency at the Database Level<\/h2>\n\n\n\n<p>Not every rule should live only in Python code. Some rules describe the shape of valid data itself, and those rules should be protected as close to the data as possible. Django validations, serializers, forms, and model <code>clean()<\/code> methods are great for catching problems early and returning helpful messages to users. But they only run when that specific part of the application is used. Once data can be written from multiple places, relying only on application code becomes risky.<\/p>\n\n\n\n<p>There are many ways data can bypass application-level checks: direct database access, migrations, management commands, Celery tasks, other services, admin actions, or even future code paths that forget to call the right validation method. If a rule is truly required by the business domain, the database should enforce it too.<\/p>\n\n\n\n<p>Assume we have a booking system where <code>start_at<\/code> and <code>end_at<\/code> must always happen on the same calendar day. We can first add a model-level validation to provide a clear error message:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.core.exceptions import ValidationError\nfrom django.db import models\n\n\nclass Booking(models.Model):\n    start_at = models.DateTimeField()\n    end_at = models.DateTimeField()\n\n    def clean(self):\n        super().clean()\n\n        if self.start_at.date() != self.end_at.date():\n            raise ValidationError(\n                {\n                    \"end_at\": \"start_at and end_at must be on the same day\"\n                }\n            )\n<\/code><\/pre>\n\n\n\n<p>This validation is useful, especially when the model is used through Django forms, Django admin, or when <code>full_clean()<\/code> is called manually. However, it is still application-layer validation. It can be skipped. The stronger solution is to add a database-level check constraint:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.db import models\nfrom django.db.models import F, Q\n\n\nclass Booking(models.Model):\n    start_at = models.DateTimeField()\n    end_at = models.DateTimeField()\n\n    class Meta:\n        constraints = [\n            models.CheckConstraint(\n                condition=Q(start_at__date=F(\"end_at__date\")),\n                name=\"ck_booking_same_day\",\n            ),\n        ]\n<\/code><\/pre>\n\n\n\n<p>With this constraint, every write path must respect the rule. It does not matter whether the record is inserted from an API endpoint, Django admin, a background task, a migration, another service, or direct SQL access. The database itself rejects invalid data.<\/p>\n\n\n\n<p>You should still keep application-level validation when it improves user experience. The best approach is usually both:<\/p>\n\n\n\n<ul>\n<li>Use serializer, form, or model validation for friendly error messages.<\/li>\n\n\n\n<li>Use database constraints for real data protection.<\/li>\n<\/ul>\n\n\n\n<p>When the database constraint is violated, catch the database error at the appropriate layer and raise a domain-tailored exception:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">from django.db import IntegrityError, transaction\n\n\nclass InvalidBookingDateRange(Exception):\n    pass\n\n\ndef create_booking(*, start_at, end_at):\n    try:\n        with transaction.atomic():\n            return Booking.objects.create(\n                start_at=start_at,\n                end_at=end_at,\n            )\n    except IntegrityError as exc:\n        if \"ck_booking_same_day\" in str(exc):\n            raise InvalidBookingDateRange(\n                \"start_at and end_at must be on the same day\"\n            ) from exc\n\n        raise\n<\/code><\/pre>\n\n\n\n<p>Note that checking the constraint name from the error message can be database-dependent, so keep this logic close to your persistence or service layer and test it against your actual database engine. Database constraints are not only for uniqueness. They are one of the most reliable ways to protect important business invariants and prevent silent data corruption.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Final word<\/h2>\n\n\n\n<p>Finally, although these best practices offer pragmatic and tested solutions and focus on re-usability, readability and reliability of code in Django development, always there are alternative approaches for different scenarios and different situations that fits your requirement.<\/p>\n\n\n\n<p>Happy coding!\u270c<\/p>\n\n\n\n<p>References :<\/p>\n\n\n\n<ol>\n<li>Two scoop of Django 3.0 book<\/li>\n\n\n\n<li><a href=\"https:\/\/docs.djangoproject.com\/en\/3.0\/ref\/models\/fields\/#django.db.models.Field.null\">https:\/\/docs.djangoproject.com\/en\/3.0\/ref\/models\/fields<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/HackSoftware\/Django-Styleguide\">https:\/\/github.com\/HackSoftware\/Django-Styleguide<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/hakibenita.com\/bullet-proofing-django-models\">Bullet Proofing Django Models<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/studygyaan.com\/django\/django-queries-id-vs-pk-maximizing-efficiency\" data-type=\"link\" data-id=\"https:\/\/studygyaan.com\/django\/django-queries-id-vs-pk-maximizing-efficiency\">studygyaan.com<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/dev.to\/sankalpjonna\/save-your-django-models-using-updatefields-for-better-performance-50ig\">https:\/\/dev.to\/sankalpjonna\/save-your-django-models-using-updatefields-for-better-performance-50ig<\/a><\/li>\n<\/ol>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Django is famous for its simplicity, flexibility, and robust features that made it a &#8220;batteries-included&#8221; framework offering comprehensive tools for web development, database operations, URL &hellip; <\/p>\n","protected":false},"author":1,"featured_media":1642,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[217,35,41],"tags":[220,131,218,242,330,219,245,39,243,244],"_links":{"self":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/1629"}],"collection":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/comments?post=1629"}],"version-history":[{"count":4,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/1629\/revisions"}],"predecessor-version":[{"id":2600,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/1629\/revisions\/2600"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media\/1642"}],"wp:attachment":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media?parent=1629"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/categories?post=1629"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/tags?post=1629"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}