۱۰ نکته‌ی ضروری کدنویسی در Django که باید حتماً بلد باشی تا مهارت‌هات رو به سطح بالاتری برسونی


۰


عکس از Dan Meyers در Unsplash

۱۰ نکته‌ی ضروری کدنویسی در جنگو برای ارتقای مهارت‌هایتان

جنگو امکانات کلان زیادی دارد که زندگی ما را به عنوان توسعه‌دهنده‌ها آسان‌تر می‌کند، و من پیش‌تر مقالات زیادی در این زمینه نوشته‌ام. اما در این مقاله می‌خواهم روی آن نکات کوچک و مفید در کدنویسی تمرکز کنم که معمولاً کمتر مورد توجه قرار می‌گیرند ولی می‌توانند در برنامه‌نویسی روزمره به شدت کارآمد باشند.

در ادامه، فهرست ۱۰ نکته شخصی من که باید در جنگو بدانید را آورده‌ام — نکاتی که باعث می‌شوند کد شما ساده‌تر و زیباتر شود.


نکته شماره ۱: متدهای داخلی QuerySet

علاوه بر متدهای بسیار رایج filter(), exclude() و annotate()، جنگو متدهای کم‌تر شناخته شده‌ای مثل first() و last() را هم ارائه می‌دهد که به‌عنوان syntactic sugar (شکرگزاری نحوی برای خوانایی بهتر) کمک می‌کنند.

همانطور که از نامشان پیداست، first() اولین رکورد در QuerySet را برمی‌گرداند و last() آخرین رکورد را. شخصاً معمولاً این‌ها را در تست‌های واحد استفاده می‌کنم.

مثلاً کد زیر را در نظر بگیرید که اولین رکورد Fruit را می‌گیرد:

1>>> Fruit.objects.all()[:1][0]  
2<Fruit: Banana ($0.99)>

می‌توان همان را این‌گونه با first() نوشت:

1>>> Fruit.objects.first()  
2<Fruit: Banana ($0.99)>

می‌بینید که کد بسیار خواناتر شده است. مزیت دیگر این متدها این است که اگر QuerySet خالی باشد، به جای اندیس‌دهی که خطا ایجاد می‌کند، مقدار None برمی‌گردانند:

1# وقتی از برش استفاده می‌کنیم، باید خطاگیری کنیم چون ممکن است QuerySet خالی باشد:
2>>> Fruit.objects.filter(price__gt=5.00)[:1][0]  
3Traceback (most recent call last):  
4  File "<console>", line 1, in <module>  
5  File ".../site-packages/django/db/models/query.py", line 318, in __getitem__  
6    return qs._result_cache[0]  
7IndexError: list index out of range  
8    
9# اما با first() یا last() مقدار None برگردانده می‌شود:
10>>> Fruit.objects.filter(price__gt=5.00).first()  
11None

برای اطلاعات بیشتر و متدهای دیگر می‌توانید به مستندات QuerySets جنگو مراجعه کنید.


نکته شماره ۲: متدهای get_or_create و update_or_create

دو متد بسیار مفید دیگر که مرتب استفاده می‌کنم، get_or_create() و update_or_create() هستند.

این متدها حجم زیادی از کدهای تکراری را پاک می‌کنند و همزمان خوانایی کد را بهتر می‌کنند:

1# بدون استفاده از get_or_create:
2try:  
3    fantasy = Category.objects.get(name='Fantasy')  
4except Category.DoesNotExist:  
5    fantasy = Category.objects.create(name='Fantasy')  
6    created = True  
7    
8# با get_or_create:
9fantasy, created = Category.objects.get_or_create(name='Fantasy')

هر دو متد یک Tuple به شکل (رکورد، بولین) برمی‌گردانند که در آن عنصر اول رکورد مربوطه است و عنصر دوم True می‌شود اگر رکورد جدید ایجاد شده باشد.

مثال مشابه با update_or_create():

1# بدون استفاده از update_or_create:
2try:  
3    horror = Category.objects.get(name='Horror')  
4    horror.name = 'Scary'  
5    horror.save()  
6except Category.DoesNotExist:  
7    horror = Category.objects.create(name='Scary')  
8    created = True  
9    
10# با update_or_create:
11horror, created = Category.objects.update_or_create(name='Horror', defaults={'name': 'Scary'})

در اینجا هم کل try-except به یک فراخوانی متد تبدیل شده است.

متناظر در مستندات


نکته شماره ۳: استفاده از get_FOO_display در تمپلیت‌ها

وقتی در مدل جنگو فیلدی با گزینه choices تعریف می‌کنید، معمولاً می‌خواهیم مقدار قابل خواندن توسط کاربر را در قالب (template) چاپ کنیم.

مثلاً مدل زیر را در نظر بگیرید:

1class Book(models.Model):  
2    STATUSES = [  
3        (1, "Available"),  
4        (2, "Signed out"),  
5        (3, "On Backorder"),  
6        (4, "To Be Shelved"),  
7        (5, "Out of Circulation"),  
8    ]  
9    title = models.CharField(max_length=128)  
10    author = models.ForeignKey(Author, on_delete=models.CASCADE)  
11    category = models.ForeignKey(Category, on_delete=models.CASCADE)  
12    status = models.IntegerField(choices=STATUSES)

اگر بخواهیم در قالب مقدار status را چاپ کنیم با {{ book.status }} فقط عدد نمایش داده می‌شود. برای گرفتن مقدار قابل فهم کاربری می‌توانیم از متد get_status_display استفاده کنیم:

1<h1>Book Entry: {{ book.title }}</h1>  
2<b>Author:</b> {{ book.author }}<br />  
3<b>Status:</b> {{ book.get_status_display }}<br />

نتیجه HTML به شکل زیر خواهد بود:

1<h1>Book Entry: Moby Dick</h1>  
2<b>Author:</b> Herman Melville<br />  
3<b>Status:</b> Signed Out<br />

مستندات مرتبط


نکته شماره ۴: عملیات bulk create و bulk update

عملیات ایجاد و به‌روزرسانی دسته‌ای (bulk) در جنگو فقط به خاطر آسان بودن و خوانایی کد قدرتمند نیست، بلکه کل فرآیند به صورت اتمی انجام می‌شود.

برای ایجاد دسته‌ای، کافی است لیستی از اشیاء مدل را بسازید و آن را به bulk_create() بدهید:

1new_fruits = [  
2    Fruit(name='Banana', price=0.80, description='Bunch of 4'),  
3    Fruit(name='Banana (organic)', price=0.99, description='Bunch of 4'),  
4    Fruit(name='Apple', price=0.75, description='Honeycrisp'),  
5    Fruit(name='Strawberry', price=0.10),  
6]  
7fruits = Fruit.objects.bulk_create(new_fruits)

برای به‌روزرسانی دسته‌ای نیز می‌توانید از متد update() روی QuerySet استفاده کنید:

1# به‌روزرسانی description برای تمام میوه‌هایی که قیمتشان بالای ۱ دلار است:
2>>> Fruit.objects.filter(price__gt=1.00).update(description='Too expensive!')  
35

عدد بازگشتی تعداد رکوردهایی است که آپدیت شدند.

مستندات bulk create


نکته شماره ۵: auto_now و auto_now_add

جنگو دو قابلیت بسیار کاربردی برای مدیریت تاریخ‌ها ارائه می‌دهد: auto_now و auto_now_add.

  • auto_now مقدار فیلد را در هر بار ذخیره شدن رکورد به زمان جاری به‌روزرسانی می‌کند.
  • auto_now_add فقط در زمان ایجاد اولیه رکورد مقدار‌دهی می‌شود.

مثلاً مدل زیر:

1class Article(models.Model):  
2    title = models.CharField(max_length=128)  
3    created_on = models.DateTimeField(auto_now_add=True)  
4    last_modified = models.DateTimeField(auto_now=True)

مشاهده می‌کنیم که فیلد last_modified هر بار که مدل ذخیره می‌شود به روز می‌شود ولی created_on ثابت می‌ماند:

1>>> a = Article.objects.create(title='This is an example')  
2>>> a.created_on  
3datetime.datetime(2024, 7, 15, 21, 12, 44, 580527, tzinfo=<UTC>)  
4>>> a.last_modified  
5datetime.datetime(2024, 7, 15, 21, 12, 44, 580628, tzinfo=<UTC>)  
6
7# اکنون شی را به‌روز می‌کنیم:
8>>> a.title = 'I have been updated'  
9>>> a.save()  
10
11# توجه کنید last_modified به‌روزرسانی شده ولی created_on ثابت مانده:
12>>> a.created_on  
13datetime.datetime(2024, 7, 15, 21, 12, 44, 580527, tzinfo=<UTC>)  
14>>> a.last_modified  
15datetime.datetime(2024, 7, 15, 21, 13, 01, 538356, tzinfo=<UTC>)

مستندات field options


نکته شماره ۶: متدهای assert مفید در تست‌های واحد

علاوه بر متدهای assert عمومی Python مثل assertEqual, assertTrue, assertIsNone, assertIn و … جنگو مجموعه‌ای از متدهای assert مخصوص و بسیار کاربردی دارد که در تست‌هایتان خیلی به درد می‌خورند. محبوب‌ترین‌ها:

  • assertContains — بررسی می‌کند رکورد مشخصی در یک QuerySet یا خروجی وجود دارد. عکس آن، assertNotContains.
  • assertURLEqual — برای مقایسه URL‌ها که گاهی ترتیب پارامتر GET فرق دارد ولی URL معادل است.
  • assertFormError — بررسی خطاهای فرم در ارسال‌ها، برای تست اعتبارسنجی‌ها خیلی مفید است.
  • assertTemplateUsed — بررسی می‌کند یک ویو هنگام رندر کردن از قالب خاصی استفاده کرده باشد.

فهرست کامل متدهای assert جنگو


نکته شماره ۷: دکوراتور tag برای تست‌ها

ادامه موضوع تست به یک قابلیت جالب می‌رسیم: دکوراتور @tag که به توسعه‌دهنده‌ها اجازه می‌دهد تست‌ها را دسته‌بندی کنند.

هدف این است که هنگام اجرای تست‌ها بتوانیم فقط زیرمجموعه‌ای از آن‌ها را با توجه به دسته‌بندی‌شان اجرا کنیم.

استفاده آسان است، فقط دکوراتور @tag را روی کلاس یا تابع تست قرار می‌دهیم:

1from django.test import tag  
2  
3@tag("api", "core")  
4class ExampleAPITest(TestCase):  
5    def setUp(self):  
6        pass  
7  
8    def test_example(self):  
9        # منطق تست اینجا

و هنگام اجرای تست‌ها پارامتر --tag را می‌دهیم تا فقط تست‌های آن تگ اجرا شوند:

1python manage.py test --tag=api

مستندات مربوط به tagging


نکته شماره ۸: تگ‌های کمتر شناخته شده تمپلیت

جنگو تگ‌های داخلی بسیار مفیدی دارد که برخی کم‌تر استفاده می‌شوند ولی بهتر است به یاد داشته باشیم:

  • join مانند متد join پایتون برای رشته‌ها عمل می‌کند.
  • first و last اولین و آخرین مقدار موجود در لیست را می‌دهد.
  • floatformat تعداد ارقام بعد از اعشار را کنترل می‌کند، مثلاً برای قیمت‌ها مفید است:
    1{{ price|floatformat:2 }}
  • intcomma برای خوانایی اعداد بزرگ، ویرگول‌ها را اضافه می‌کند: 1000000 تبدیل می‌شود به 1,000,000.
  • linebreaksbr خط‌های جدید را به <br /> در HTML تبدیل می‌کند.
  • urlize خودکار متن URL را به تگ <a> لینک تبدیل می‌کند.
  • yesno تبدیل مقدار بولی به رشته کاربرپسند است، مثلاً:
    1{{ record.is_active|yesno:"Active,Disabled" }}
    مقدار True به Active و False به Disabled تبدیل می‌شود.

فهرست کامل template tags و filters


نکته شماره ۹: refresh_from_db

این نکته اختصاصی به این دلیل است که من دیرتر یاد گرفتمش و کلی زمان را ذخیره کرده است.

اگر یک نمونه مدل (instance) دارید و فکر می‌کنید داده‌اش کَهنه شده و می‌خواهید از دیتابیس تازه‌اش را بگیرید، کافی است این متد را صدا بزنید:

1fruit = Fruits.objects.last()
2
3# بدون refresh_from_db:
4fruit = Fruits.objects.get(pk=fruit.pk)
5
6# با refresh_from_db:
7fruit.refresh_from_db()

من در تست‌های REST API خیلی از این متد استفاده می‌کنم چون می‌خواهم از آخرین وضعیت رکورد اطمینان حاصل کنم.

مستندات refresh_from_db


نکته شماره ۱۰: دکوراتور register در Admin

آخرین نکته آسان است — جنگو یک دکوراتور @admin.register در اختیار گذاشته که باعث می‌شود کدهای مربوط به پنل مدیریت نزدیک‌تر به هم و خواناتر شوند:

1from django.contrib import admin
2
3# بدون دکوراتور:
4class FruitAdmin(admin.ModelAdmin):  
5    search_fields = ['name', 'description']  
6    list_filter = ['name']  
7    list_display = ('name', 'price', 'description')
8
9admin.site.register(Fruit, FruitAdmin)
10
11# با دکوراتور:
12@admin.register(Fruit)
13class FruitAdmin(admin.ModelAdmin):  
14    search_fields = ['name', 'description']  
15    list_filter = ['name']  
16    list_display = ('name', 'price', 'description')

اگرچه فقط یک میانبر است، ولی به شدت خوانایی را بهتر می‌کند و ثبت کلاس مدیریت را در کنار تعریف کلاس نگه می‌دارد.

مستندات دکوراتور register


انتخاب فقط ۱۰ نکته از بین انبوه قابلیت‌های جنگو سخت بود، ولی تمرکز کردم روی مواردی که خودم زیاد استفاده می‌کنم و بیشترین صرفه‌جویی را در کد دارند. اگر نکته یا ترفند خاصی دارید که اشاره نشده، حتماً در بخش نظرات به اشتراک بگذارید!

دوآپس
جنگو
پایتون

۰


نظرات


author
نویسنده مقاله: شادان ارشدی

کد با می متعهد است که بالاترین سطح کیفی آموزش را در اختیار شما بگذارد. هدف به اشتراک گذاشتن دانش فناوری اطلاعات و توسعه نرم افزار در بالاترین سطح ممکن برای درستیابی به جامعه ای توانمند و قدرتمند است. ما باور داریم هر کسی میتواند با استمرار در یادگیری برنامه نویسی چالش های خود و جهان پیرامون خود را بر طرف کند و به موفقیت های چشم گیر برسد. با ما در این مسیر همراه باشید. کد با می اجتماع حرفه ای برنامه نویسان ایرانی.

تمام حقوق این سایت متعلق به وبسایتcodebymeمیباشد.