پر شدن فیلد اتوماتیک توی فرم

توی یک فرم یک فیلد داریم بنام full_name که بر اساس first_name و last_name توی view باید ساخته بشه. برای اینکه بشه توی فرم و view ازش استفاده کرد توی fields مربوط به فرم اونو آوردیم.

چون این فیلد اتوماتیک ساخته میشه نمیخایم توی وب نشون داده بشه بنابراین expert_form.fields['full_name'].widget = forms.HiddenInput() ولی مشکل اینجاست که قبل از اینکه فرم post بشه ارور میگیریم که این فیلد باید پر بشه از طرفی Hidden هست.

راه حل این شد که باید توی __init__ فرم این رو از حالت required خارج کنیم.

بهترین روش استفاده از فرم ها توی جنگو

فرم در جنگو قابلیت های زیادی داره و من توی پروژه های مختلف هر بار چالش هایی داشتم که به مرور متوجه شدم که این قابلیت ها خیلی میتونه کمک کنه.

1.فرض کنید که توی یک فرم از یک مدل ما میخایم که یک فیلد رو نشون کاربر نده و اون رو پنهان کنه برای اون کار باید از HiddenInput استفاده کرد link_form.fields['provider'].widget = forms.HiddenInput()

2.فرض کنید به همون فیلد فرم میخایم یه مقدار اولیه بدیم که دیگه نیاز نباشه کاربر فیلدی رو پر کنه. link_form.initial = {'provider': svc}

3.هر فیلد توی یک فرم یک queryset داره که متونید اون رو تغییر بدید و مثلا یه فیلدش رو کم کنید یا sort کنید. self.fields['provider'].queryset.order_by('name')

4.یکی از نکات مشکلسازی که وجود داره. اگر میخاید فیلدهای یک فرم رو توی view تغییراتی بهش بدید (از اون چیزی که کاربر وارد میکنه) باید اون فیلد رو save کنید و اون instance ساخته شده رو تغییر بدید و بعدش commit کنید. link_instance = link_form.save(commit=False)

5.اگر بخایم مقدار وارد شده توسط کاربر رو بگیریم باید از cleaned_data استفاده کنیم. link_form.cleaned_data['provider'].name

6.توی خود کلاس Form میتونید برای هر فیلد محدودیت هایی ایجاد کنید و در صورت تخطی از اون محدودیت ها ValidationError برگردونید.

Form و View ای که تمامی این موارد رو توش داره رو در کدهای زیر میبینید. هر بار نیاز به زدن چنین فرمهایی بودیم بد نیست یه سری به اینا بزنیم.

و فرم مربوطه

دسترسی به پارامترهای URL از توی Template Django

اگر نیاز شد که به متغیرهای توی URL دسترسی پیدا کنیم، میتونیم ببینیم که توی request چی پیدا میشه. طبیعیه که هر چیزی که توی یک فرم توی مرورگر میبینیم باید توی request پیدا بشه. برای این مورد از request.resolver_match استفاده میکنیم مثل:

چند فرم و یک view در جنگو

توی یه اپلیکیشن یه فایل HTML داریم که سه تا فرم توشه ولی فقط با یه فرم داره رندر میشه. مشکل اینجاست که اینجوری پر از باگه. حتی ممکنه فرم اول رو سابمیت کنی کد فرم بعدی هم اجرا شه در حالی که اصلا لازم نیست هم از نظر performance ای یه کد اضافی داره اجرا میشه هم ممکنه دیتای فرم بعدی رو خراب کنه. برای اینکار به سه تا input فرم ها هر کدوم یه اسم یکتا دادم مثلا:

حالا توی فرم چک میکنیم که این فیلد توی request هست یا نه:

این جوری اگر درخواست POST باشه فقط اون فرمی که سابمیت شده کدش اجرا میشه و لازم هم نیست که برای هر فرم یک view جدا هم بنویسیم.

Signal Dispatcher در جنگو

Signal Dispatcher به ما این امکان رو میده که اگر توی یک اپلیکیشن اتفاقی افتاد اپلیکیشن های دیگه یا جاهای دیگه رو notify کنیم که اتفاقی انجام شه. مثلا فرض کنید که یک نفر به ما یک پیام میفرسته. اون وقت من باید یه نوتیفیکیشن دریافت کنم. روش کار این مدلیه که یک تکه کد میتونه sender باشه و چند تابع میتونن reciever باشن. در واقع وقتی یک چند تابع به یک event حساس هستن میتونن از این قابلیت استفاده کنن.

در پروژه ای وقتی مستندات در مدلی به نام Document ذخیره میشه و همزمان یک تاریخچه از این ذخیره شدن توی DocumentChangeHistory ذخیره میشه. مشکل اینه که میخام وقتی یک داکیومنت حذف میشه تاریخچه مرتبط با اون هم پاک شه.

برای این کار باید از Signal جنگو استفاده کرد. مثلا

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

باید توی app.py این رو تعریف کنیم. تا کار کنه.

لینک 1

لینک 2

استقرار پروژه جنگو با بهینگی بالا

در پروژه ای که در بستر داکر روی لینوکس پیاده سازی شده است نرم افزار از performance قابل قبولی برخوردار نیست. بخشی از نرم افزار با websocket کار میکند اما اکثرا در بستر http. دیتابیس، و بخشهای دیگر مثل celery, rabbitMQ و … هر کدام یک کانتینر هستند.

ایرادات استقرار یا deployment:

جنگو در یک کانتینر با دستور runserver اجرا شده است.

کل پروژه از Daphne به عنوان اپلیکیشن وب سرور ASGI استفاده میکند در حالی که بخش کوچکی از نرم افزار asyn است و با websocket کار میکند.

وب سروری مثل Nginx یا Apache وجود ندارد که فایلهای استاتیک را ارائه دهد.

بهتر بود در بخشهای Sync از اپلیکیشن سروری مثل uWSGI که یک WSGI هست استفاده میشد.

بنابراین معماری به شکل زیر تغییر یافت:

NGINX درخواست های websocket رو به Daphne و سایر درخواست ها را به uWSGI ارسال میکند. فایل های کانفیگ از قرار زیر است.

compose.yml

nginx.conf

DockerfileNginx

DockerfileNasim

لینک های مرتبط:

لینک 1

لینک 2

لینک 3

لینک 4

لینک 5

Signal و Middleware توی پروسه لاگین (جلوگیری از چندین لاگین همزمان با یک نام کاربری)

توی یه پروژه خواستیم یک نام کاربری دو تا session نداشته باشه یعنی یه کاربر توی دوتا مرورگر یا دو تا سیستم لاگین نکنه. برای این کار از Middleware و Signal توی پروژه استفاده کردم.

توی Middleware.py توی app مربط کد زیر کد زیر رو زدم:

حالا اینو توی MIDDLEWARE توی SETTING اضافه میکنیم.

Signal زیر رو اضافه میکنم که اگر کسی logtout کرد session اش پاک شه که بشه جای دیگه لاگین کنن:

افزودن فیلد به مدل با کمترین impact

یه مدل داریم تو جنگو که پروفایل شرکت های مختلف توی اون ذخیره میشه:

لازم شد که لوگو شرکت هم اضافه بشه اما از اونجایی که این مدل جاهای مختلف استفاده شده بود و توی فرمها و view های مختلف ازش استفاده شده اگه یه فیلد برای لوگو بهش اضافه کنیم قطعا باید خیلی از جاهای کد هم تغییر بدیم و احتمالا باگ هایی تولید بشن و از اونجایی که این سیستم براش تست نوشته نشده ممکن اطفاقات بدی بیوفته.

بنابراین رفتم دنبال اینکه چطور میشه بدون تغییر این مدل لوگو رو اضافه کنم. حداقل طوری که اگر جایی از این مدل داره استفاده میشه دیگه فیلدهاش عوض نشه. راه های مختلفی وجود داره ولی بهترینش بنظرم اینه که یه مدل جدید برای لوگو بسازیم و ارتباط OneToOne بینشون برقرار کنیم.

و اینطوری ازش استفاده کنم

Coroutine و Awaitables

Coroutine در واقع یک تابع هست که میتونیم اونو pause کنیم. فرض کنیم دو تا تابع داریم و میخایم کنترل اجرا بین این دو تا جابجا بشه مثل ping pong مثلا تابع اول چند خط رو اجرا کنه بعد کنترل رو به تابع دوم بده و اون چند خط اجرا کنه و … در واقع دو تا routine که اینجا بهشون میگیم تابع با هم cooperate کنن.

برای تریفش توی پایتون از async کلیدواژه استفاده میکنیم. که اون وقت اون تابع coroutine میشه. میتونیم هر جای کد coroutine ازش بخایم وایسه تا اجرای یه تابع دیگه تموم شه برای این کار await میکنیم.

مقایسه Celery و Channels و Singnal

در جدول زیر مقایسه Celery و Django Channels و Django Signals رو میبینیم

Here’s a comparison of Django Channels, Django Signals, and Celery, presented in a tabular format, focusing on their use cases, features, and suitable scenarios:

Feature/AspectDjango ChannelsDjango SignalsCelery
Primary Use CaseReal-time communication, WebSockets, and long-lived connectionsDecoupled event handling within Django applicationsAsynchronous task queue/job queue for running background tasks
CommunicationWebSockets, HTTP2, server-sent eventsIn-process communication within the same Django appDistributed communication, allowing task distribution across multiple workers
ConcurrencyHandles concurrent connections and async processingSynchronous, works within the Django request-response cycleHandles concurrent tasks, designed for parallel processing
ScalabilityScalable for handling many concurrent connectionsLimited by the Django processHighly scalable, can distribute tasks across multiple servers
Real-time UpdatesYes, supports real-time updatesNoNo, tasks are run asynchronously but not in real-time
Event HandlingHandles real-time eventsHandles events within the Django lifecycle (e.g., model save, user login)Handles events as background tasks
Persistent ConnectionsYes, supports long-lived connections (e.g., chat apps)No, operates within the request-response cycleNo, designed for discrete, short-lived tasks
IntegrationIntegrates with WebSockets, WebRTC, etc.Integrates with Django ORM and lifecycle hooksIntegrates with message brokers (RabbitMQ, Redis, etc.)
Typical Use CasesChat applications, live notifications, live dashboardsSending signals on model changes, user actionsSending emails, processing data, generating reports, scheduled tasks
ExampleLive chat app with instant messagingLogging changes to models when savedSending periodic email reports, processing user-uploaded files in the background
Fault ToleranceBasic fault tolerance, relies on infrastructureBasic, depends on Django’s fault toleranceHigh fault tolerance, can retry failed tasks, distributed task execution
DependenciesRedis, RabbitMQ (for channel layers)None beyond DjangoMessage broker (e.g., RabbitMQ, Redis), result backend (optional)
Ease of UseRequires understanding of asynchronous programmingSimple to use with DjangoRequires setup of a message broker and understanding of task queues

Detailed Use Case Examples

  1. Django Channels:
    • Chat Application: Users can send and receive messages in real-time, with updates being pushed to all connected clients instantly.
    • Live Notifications: Real-time notifications for events such as new comments, likes, or mentions in a social media application.
    • Live Dashboard: Real-time updates of metrics and analytics in a monitoring dashboard.
  2. Django Signals:
    • Model Change Notifications: Automatically trigger a function to log changes or send an email when a model instance is saved or deleted.
    • User Activity Tracking: Capture user login, logout, or profile update events to maintain an activity log.
    • Post-Processing: Perform additional actions after saving a model, such as updating related records or sending a confirmation email.
  3. Celery:
    • Email Sending: Send emails asynchronously to avoid blocking the main thread during the request-response cycle.
    • Data Processing: Handle large data processing tasks, such as generating reports or processing user-uploaded files, in the background.
    • Scheduled Tasks: Run periodic tasks like clearing expired sessions, generating nightly reports, or updating external APIs.

By comparing these tools, you can choose the most appropriate solution based on the specific requirements and characteristics of your project.