جلوگیری از لاگین همزمان با یک نام کاربری

اگر بخایم یک نام کاربری نتونه همزمان توی دو تا سیستم لاگین کنه باید چیکار کنیم؟ باید یک Middleware بنویسیم که اگر سشن کاربر با سشن ذخیره شده قبلی برابر نبود اون رو پاک کنه و جدید رو بنویسه.

حالا این میدلویر رو باید اضافه کنیم

اگر اون سشنی که پاک شده بعدش بیاد لاگین بزنه به ارور میخوره بنابراین باید این تابع رو یه تغییراتی بدیم

اینکه این کد به درستی کار میکنه کافیه؟

آیا اینکه کد زیر به درستی کار میکنه کافیه؟

تابع بالا به درستی کار میکنه اما با تابع زیر جایگزینش کردم:

توی کد اول از دو فرم استفاده شده اما صرفا جهت رندر شدن توی HTML و از هیچ کدوم از قابلیت های دیگه فرم استفاده نشده. توی فرم هر صحت دیتای هر فیلد بر اساس دیتابیس بررسی میشه و صحت سنجی میشه. هر ارور مرتبطی با کل فرم یا هر کدوم از فیلدها در صورت وجود به کاربر نشون داده میشه. تمامی validation های لازم بعد از صدا زدن تابع is_valid انجام میشه. تمامی این موارد توی کد اول انجام نشده و در کل از فرم جنگو اصلا استفاده نشده. بخاطر همین کد دوم قابل اعتمادتره و قطعا باگهای کمتری دیده خواهد داشت. از طرفی واقعا کد دوم خواناتر و زیباتر هست.

چطور برای دو مدل که با هم ارتباط دارند Form و View بنویسیم

دو فرم که با مدل های آنها با هم ارتباط دارند رو میشه توی یک فرم به شکل زیر ساخت:

view باید به شکل زیر باشه

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

توی یک فرم یک فیلد داریم بنام 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

یک نمونه AJAX

در یک پروژه پرتکرار از AJAX استفاده کردیم و یه نمونه پیچیده شو اینجا میزارم.

در کد بالا route زیر که مربوط به یک view توی جنگو هست صدا زده شده

این یک route مربوطه به جنگو هست که یک پارامتر به اسم dep_id هم میگیره. قسمت جالب این کد اینه که همین کد جاوااسکریپت با django template engine داره render میشه و بعد توسط .replcace اصلاح میشه

نمونه route و view رو هم در ادامه میبینید.