Showing preview only (695K chars total). Download the full file or copy to clipboard to get everything.
Repository: ftg-iran/ddpabp-persian
Branch: main
Commit: 08b73c1e931e
Files: 21
Total size: 426.4 KB
Directory structure:
gitextract_sifllnh9/
├── .nojekyll
├── 01-Django-and-Patterns/
│ └── README.md
├── 02-ApplicationDesign/
│ └── README.md
├── 03-Models/
│ └── README.md
├── 04-Views-and-URLs/
│ └── README.md
├── 05-Templates/
│ └── README.md
├── 06-AdminInterface/
│ └── README.md
├── 07-Forms/
│ └── README.md
├── 08-WorkingAsynchronously/
│ └── README.md
├── 09-CreatingAPIs/
│ └── README.md
├── 10-Dealing-with-LegacyCode/
│ └── README.md
├── 11-Testing-and-Debugging/
│ └── README.md
├── 12-Security/
│ └── README.md
├── 13-Production-Ready/
│ └── README.md
├── Appendix-A-Python2-Versus-Python3/
│ └── README.md
├── CONTRIBUTING.md
├── README.md
├── _sidebar.md
├── custom.css
├── custom.js
└── index.html
================================================
FILE CONTENTS
================================================
================================================
FILE: .nojekyll
================================================
================================================
FILE: 01-Django-and-Patterns/README.md
================================================
# جنگو الگوها
در این فصل در مورد موضوعات زیر صحبت خواهیم کرد:
* [چرا جنگو؟](#چرا-جنگو)
* [داستان جنگو](#داستان-جنگو)
* [جنگو چگونه کار می کند؟](#جنگو-چگونه-کار-می-کند)
* [الگو چیست؟](#الگو-چیست)
* [مجموعه های الگوهای شناخته شده](#گروه-چهار-الگو-gang-of-four-patterns)
* [الگوها در جنگو](#الگوها-در-این-کتاب)
بر اساس گزارش جهانی استارتاپ بووی گای، در سال 2013 بیش از 136000 شرکت اینترنتی در سراسر جهان وجود داشت که بیش از 60000 شرکت فقط در آمریکا وجود داشت. از این تعداد، 87 شرکت آمریکایی بیش از یک میلیارد دلار ارزش دارند. مطالعه دیگری می گوید که از 12000 نفر بین 18 تا 30 سال در 27 کشور، بیش از دو سوم فرصت هایی را برای کارآفرین شدن می بینند.
این رونق کارآفرینی در استارت آپ های دیجیتال در درجه اول به دلیل ارزان شدن و فراگیر شدن ابزارها و فناوری های استارت آپ ها است. ایجاد یک برنامه وب کامل به لطف فریمورک های قدرتمند، زمان و مهارت بسیار کمتری نسبت به گذشته می طلبد.
فیزیکدانان، مربیان، هنرمندان و بسیاری دیگر بدون پیشینه مهندسی نرم افزار در حال ایجاد برنامه های کاربردی مفیدی هستند که به طور قابل توجهی در حال پیشرفت حوزه خود هستند. با این حال، آنها ممکن است از اصول طراحی مهندسی نرم افزار مورد نیاز برای ساخت نرم افزار بزرگ و قابل نگهداری آگاه نباشند.
مطالعه چهار پیادهسازی مختلف از یک برنامه کاربردی مبتنی بر وب در نروژ نشان داد که پیادهسازیهایی با بوهای کد شناخته شده و طرحهای ضد الگو مستقیماً با مشکلات نگهداری مرتبط هستند. نرم افزاری که طراحی ضعیفی دارد ممکن است به همان اندازه کار کند، اما انطباق با الزامات در حال تحول در دنیایی که به سرعت در حال تغییر است، دشوار است.
مبتدی ها اغلب مشکلات طراحی را در اواخر پروژه خود کشف می کنند. به زودی، آنها سعی می کنند همان مشکلاتی را که دیگران با آن مواجه شده اند، حل کنند. اینجاست که درک الگوها واقعاً می تواند به صرفه جویی در وقت آنها کمک کند.
### چرا جنگو؟
هر برنامه وب متفاوت است، مانند یک تکه مبلمان دست ساز. به ندرت یک مبل تولید انبوه پیدا می کنید که تمام نیازهای شما را به خوبی برآورده کند. حتی اگر با یک نیاز اساسی مانند وبلاگ یا شبکه اجتماعی شروع کنید، نیازهای شما به آرامی افزایش مییابد و به راحتی میتوانید با بسیاری از محلولهای نیمهپخت که با چسب نواری روی محلولهای کاتر کوکیها چسبانده شدهاند، خاتمه دهید.
به همین دلیل است که FrameWork های وب مانند جنگو یا ریل بسیار محبوب شده اند. FrameWorkها توسعه را سرعت میبخشند و بهترین روشها را در خود دارند. با این حال، آنها همچنین به اندازه کافی انعطافپذیر هستند تا به شما امکان دسترسی به لولهکشی کافی برای کار را بدهند. امروزه فریم ورک های وب در همه جا وجود دارند و اکثر زبان های برنامه نویسی حداقل یک فریم ورک انتها به انتها مشابه جنگو دارند.
احتمالا پایتون نسبت به بسیاری از زبان های برنامه نویسی FrameWork های وب بیشتری دارد. نگاهی گذرا به فهرست بسته پایتون (PyPI) 13045 بسته شگفتانگیز مربوط به محیطهای وب را نشان میدهد. برای جنگو، مجموع 9091 بسته است. ویکی پایتون بیش از 54 FrameWork وب فعال را فهرست میکند که محبوبترین آنها جنگو، فلاسک، پیرامید و زوپ است. پایتون همچنین دارای تنوع گسترده ای در فریمورک ها است. فریم ورک میکرو وب بطری فشرده تنها یک فایل پایتون است که هیچ وابستگی ندارد و به طرز شگفت انگیزی قادر به ایجاد یک برنامه وب ساده است.
علیرغم این گزینههای فراوان، جنگو با اختلاف زیادی به عنوان یک مورد علاقه بزرگ ظاهر شده است. Djangosites.org بیش از 5263 سایت نوشته شده در جنگو را فهرست می کند، از جمله داستان های موفقیت معروفی مانند اینستاگرام، پینترست، و Disqus. همانطور که توضیحات رسمی می گوید، جنگو (https://djangoproject.com) یک FrameWork وب سطح بالا پایتون است که توسعه سریع و طراحی تمیز و عملی را تشویق می کند. به عبارت دیگر، این یک FrameWork وب کامل با باتری هایی است که دقیقاً مانند پایتون در آن گنجانده شده است. رابط مدیریت خارج از جعبه، یکی از ویژگی های منحصر به فرد جنگو، برای ورود و مدیریت اولیه داده ها بسیار مفید است. مستندات جنگو به دلیل اینکه برای یک پروژه منبع باز بسیار خوب نوشته شده است مورد تحسین قرار گرفته است. در نهایت، جنگو در چندین وب سایت پر ترافیک آزمایش شده است. تمرکز فوقالعاده دقیقی بر امنیت با محافظت در برابر حملات رایج مانند اسکریپتهای متقاطع (XSS)، جعل درخواستهای متقابل سایت (CSRF) تا تهدیدات امنیتی در حال تکامل مانند الگوریتمهای هش رمز عبور ضعیف است
اگرچه میتوانید از جنگو برای ساخت هر نوع برنامه وب در تئوری استفاده کنید، اما ممکن است برای هر موردی بهترین نباشد. به عنوان مثال، برای نمونه سازی اولیه یک وب سرویس ساده در یک سیستم تعبیه شده با محدودیت های حافظه فشرده، ممکن است بخواهید از Flask استفاده کنید، در حالی که ممکن است در نهایت به دلیل استحکام و ویژگی های آن به جنگو بروید. ابزار مناسب برای کار را انتخاب کنید.
اگر به سایر FrameWork های وب عادت داشته باشید، برخی از ویژگی های داخلی، مانند رابط مدیر، ممکن است عجیب به نظر برسد. برای درک طراحی جنگو، بیایید دریابیم که چگونه به وجود آمد.
## داستان جنگو
وقتی به اهرام مصر نگاه می کنید، فکر می کنید که چنین طراحی ساده و مینیمال باید کاملاً آشکار باشد. در حقیقت، آنها محصول 4000 سال تکامل معماری هستند. اهرام پله ای، طرح اولیه (و درهم و برهم)، دارای شش بلوک مستطیل شکل در حال کاهش بود.
چندین بار اصلاحات معماری و مهندسی طول کشید تا اینکه سازههای آهکی مدرن، لعابدار و بادوام اختراع شدند.
با نگاه کردن به جنگو، ممکن است احساس مشابهی داشته باشید - آنقدر زیبا ساخته شده است که باید بی عیب و نقص تصور شده باشد. برعکس، نتیجه بازنویسیها و تکرارهای سریع در یکی از پرفشارترین محیطهای قابل تصور بود - اتاق خبر!
در پاییز 2003، دو برنامه نویس، *آدریان هولواتی* و *سایمون ویلیسون* که در روزنامه *لارنس ژورنال-ورلد* کار می کردند، مشغول ایجاد چندین وب سایت خبری محلی در کانزاس بودند. این سایتها، از جمله *LJWorld.com*، *Lawrence.com*، و *KUsports.com*، مانند بسیاری از سایتهای خبری، نه تنها درگاههای محتوا محور مملو از متن، عکس و ویدئو بودند، بلکه دائماً سعی میکردند نیازهای مردم را برآورده کنند. جامعه محلی لارنس با برنامههایی مانند فهرست راهنمای کسبوکار محلی، تقویم رویدادها و آگهیها.
### یک FrameWork متولد می شود
البته این به معنای کار زیاد برای سایمون، آدریان و بعدها جاکوب کاپلان ماس بود که به تیم آنها پیوسته بودند. با مهلت های بسیار کوتاه، گاهی اوقات تنها با چند ساعت اطلاع رسانی. از آنجایی که اولین روزهای توسعه وب در پایتون بود، آنها مجبور بودند برنامه های وب را عمدتاً از ابتدا بنویسند. بنابراین، برای صرفه جویی در زمان گرانبها، آنها به تدریج ماژول ها و ابزارهای رایج را به چیزی به نام *CMS* تغییر دادند.
در نهایت، بخشهای مدیریت محتوا به پروژهای جداگانه به نام Ellington CMS تبدیل شد که به یک محصول تجاری موفق CMS تبدیل شد. بقیه CMS یک FrameWork زیربنایی منظم بود که به اندازه کافی عمومی بود که برای ساخت برنامه های کاربردی وب از هر نوع استفاده شود.
تا جولای 2005، این FrameWork توسعه وب به عنوان جنگو (تلفظ Jang-Oh) تحت مجوز منبع باز توزیع نرم افزار برکلی (BSD) منتشر شد. این نام از گیتاریست افسانه ای جاز جنگو راینهارت گرفته شد. و بقیه، همانطور که می گویند، تاریخ است.
### حذف جادو
جنگو به دلیل خاستگاه فروتنانهاش به عنوان یک ابزار داخلی، دارای موارد عجیب و غریب مخصوص لارنس ژورنال جهان بود. برای اینکه جنگو واقعاً هدف کلی باشد، تلاشی با عنوان حذف لارنس قبلاً در حال انجام بود.
با این حال، مهم ترین تلاش برای بازسازی که توسعه دهندگان جنگو باید انجام می دادند Removing the Magic نام داشت. این پروژه بلندپروازانه شامل پاکسازی تمام زگیلهایی بود که جنگو در طول سالها جمعآوری کرده بود، از جمله جادوی زیادی (یک اصطلاح غیررسمی برای ویژگیهای ضمنی) و جایگزینی آنها با کد پایتونیک طبیعیتر و صریحتر. برای مثال، کلاسهای مدل به جای اینکه مستقیماً از ماژول `models.py` که در آن تعریف شده بودند وارد شوند، از یک ماژول جادویی به نام `django.models.*` وارد میشدند.
در آن زمان، جنگو حدود صد هزار خط کد داشت و بازنویسی قابل توجهی از API بود. در 1 می 2006، این تغییرات، تقریباً به اندازه یک کتاب کوچک، در تنه نسخه توسعهدهنده جنگو ادغام شدند و با نسخه 0.95 جنگو منتشر شدند. این یک گام مهم به سوی نقطه عطف جنگو 1.0 بود.
### جنگو مدام بهتر می شود
هر ساله کنفرانس هایی به نام DjangoCons در سرتاسر جهان برای توسعه دهندگان جنگو برگزار می شود تا بتوانند با یکدیگر ملاقات کنند و با یکدیگر تعامل داشته باشند. آنها یک سنت شایان ستایش در ارائه یک سخنرانی نیمه طنز در مورد اینکه چرا جنگو بد است، دارند. این می تواند عضوی از جامعه جنگو یا شخصی باشد که روی FrameWork های وب رقیب کار می کند یا هر شخصیت برجسته ای باشد. در طول سالها، شگفتانگیز است که چگونه توسعهدهندگان جنگو این انتقادات را مثبت میگرفتند و در نسخههای بعدی آن را کاهش میدادند.
در اینجا خلاصهای از پیشرفتهای مربوط به چیزی است که زمانی در جنگو یک کاستی بود و نسخهای که در آن برطرف شد:
* کتابخانه جدید مدیریت فرم (Django 0.96)
* جداسازی ادمین از مدل ها (Django 1.0)
* پشتیبانی از چندین پایگاه داده (Django 1.2)
* مدیریت بهتر فایل های استاتیک (Django 1.3)
* پشتیبانی بهتر از منطقه زمانی (Django 1.4)
* مدل کاربر قابل تنظیم (Django 1.5)
* مدیریت بهتر تراکنش (جنگو 1.6)
* انتقال پایگاه داده داخلی (جنگو 1.7)
* موتورهای قالب چندگانه (جنگو 1.8)
* نحو ساده شده مسیریابی URL (Django 2.0)
با گذشت زمان، جنگو به یکی از اصطلاحی ترین پایگاه های کد پایتون در حوزه عمومی تبدیل شده است. کد منبع جنگو همچنین مکانی عالی برای یادگیری معماری یک FrameWork وب بزرگ پایتون است.
### جنگو چگونه کار می کند؟
برای درک واقعی جنگو، باید زیر کاپوت را نگاه کنید و قسمت های متحرک مختلف داخل آن را ببینید. این می تواند هم روشنگر و هم طاقت فرسا باشد. اگر قبلاً با اطلاعات زیر آشنا هستید، ممکن است بخواهید از این بخش صرفنظر کنید:

*نحوه پردازش درخواست های وب در یک برنامه معمولی جنگو*
نمودار قبلی سفر ساده درخواست وب از مرورگر بازدیدکننده به برنامه جنگو و بازگشت را نشان می دهد. مسیرهای شماره گذاری شده به شرح زیر است:
1. مرورگر درخواست را (در اصل یک رشته بایت) به وب سرور شما ارسال می کند.
2. وب سرور شما (مثلاً Nginx) درخواست را به یک سرور رابط دروازه وب سرور (WSGI) (مثلا uWSGI) تحویل می دهد یا مستقیماً یک فایل (مثلاً یک فایل CSS) را از سیستم فایل ارائه می دهد.
3. برخلاف وب سرور، سرورهای WSGI می توانند برنامه های پایتون را اجرا کنند. این درخواست یک فرهنگ لغت پایتون به نام `environ` را پر می کند و به صورت اختیاری، از چندین لایه میان افزار عبور می کند و در نهایت به برنامه جنگو شما می رسد.
4. URLconf (پیکربندی URL) ماژول موجود در `urls.py` پروژه شما، یک نمای را برای رسیدگی به درخواست بر اساس URL درخواستی انتخاب می کند. درخواست به HttpRequest، یک شی پایتون تبدیل شده است.
5. نمای انتخاب شده معمولاً یک یا چند مورد از کارهای زیر را انجام می دهد:
a. از طریق مدل ها با پایگاه داده صحبت می کند.
b. HTML یا هر پاسخ فرمت شده دیگری را با استفاده از Template ارائه می دهد
c. یک متن ساده را بر میگرداند ( نشان داده نمیشود)
d. در صورت وجود، یک exception بر میگرداند
6. شیع `HttpResponse` به متن تبدیل (Render) میشود.
7. یک صفحه وب با رندر زیبا در مرورگر شما دیده می شود.
اگرچه جزئیات خاصی حذف شده است، این نمایش باید به شما در درک معماری سطح بالای جنگو کمک کند. همچنین نقشهایی را که اجزای کلیدی بازی میکنند، مانند مدلها، نماها و قالبها نشان میدهد. بسیاری از اجزای جنگو بر اساس چندین الگوی طراحی شناخته شده هستند.
### الگو چیست؟
چه چیزی بین کلمات **نقشه** ، **داربست** و **نگهداری** مشترک است؟ این اصطلاحات توسعه نرم افزار از دنیای ساخت و ساز ساختمان و معماری به عاریت گرفته شده است. با این حال، یکی از تأثیرگذارترین اصطلاحات از رساله ای در معماری و شهرسازی می آید که در سال 1977 توسط معمار برجسته اتریشی کریستوفر الکساندر و تیمش متشکل از موری سیلورستاین، سارا ایشیکاوا و چندین نفر دیگر نوشته شده است.
اصطلاح الگو پس از کار اصلی آنها، *A Pattern Language: Towns, Buildings, Construction* (جلد 2 در یک مجموعه پنج کتابی)، بر اساس بینش شگفت انگیزی که کاربران در مورد ساختمان های خود بیش از هر معمار دیگری می دانند، رایج شد. یک الگو به یک مشکل روزمره و راه حل پیشنهادی اما آزمایش شده آن اشاره دارد. کریستوفر الکساندر در این کتاب چنین می گوید:
<p align="center">
<i>"هر الگو یک مشکل را توصیف می کند که بارها و بارها در محیط ما رخ می دهد و سپس هسته راه حل آن مشکل را به گونه ای توصیف می کند که می توانید از این راه حل میلیون ها بار استفاده کنید، بدون اینکه هرگز آن را به همان روش انجام دهید. دو برابر."</i>
</p>
برای مثال، الگوی *بالهای نوری* او توضیح میدهد که چگونه مردم ساختمانهایی با نور طبیعیتر را ترجیح میدهند و پیشنهاد میکند که ساختمان را به گونهای تنظیم کنید که از بال تشکیل شده باشد. این بال ها باید بلند و باریک باشند و هرگز بیش از 25 فوت عرض نداشته باشند. دفعه بعد که از قدم زدن در راهروهای طولانی یک دانشگاه قدیمی لذت بردید، از این الگو سپاسگزار باشید.
کتاب آنها شامل 253 الگوی عملی از این قبیل بود، از طراحی یک اتاق گرفته تا طراحی کل شهر. مهمتر از همه، هر یک از این الگوها نامی برای یک مسئله انتزاعی گذاشتند و با هم یک *زبان الگو* را تشکیل دادند.
به یاد دارید که اولین بار با کلمه déjà vu برخورد کردید؟ احتمالاً فکر کرده اید: "وای، من هرگز نمی دانستم که کلمه ای برای آن تجربه وجود دارد." به طور مشابه، معماران نه تنها قادر به شناسایی الگوها در محیط خود بودند، بلکه میتوانستند در نهایت آنها را به گونهای نامگذاری کنند که همتایان خود بتوانند آن را درک کنند.
در دنیای نرم افزار، اصطلاح الگوی طراحی به یک راه حل کلی قابل تکرار برای یک مشکل رایج در طراحی نرم افزار اشاره دارد. این رسمی سازی بهترین شیوه هایی است که یک توسعه دهنده می تواند از آن استفاده کند. مانند دنیای معماری، زبان الگو ثابت کرده است که برای برقراری ارتباط روش خاصی برای حل یک مشکل طراحی با برنامه نویسان دیگر بسیار مفید است.
مجموعه های مختلفی از الگوهای طراحی وجود دارد، اما برخی از آنها به طور قابل توجهی تاثیرگذارتر از بقیه بوده اند.
#### گروه چهار الگو (Gang of four patterns)
یکی از اولین تلاشها برای مطالعه و مستندسازی الگوهای طراحی، کتابی با عنوان *الگوهای طراحی: عناصر نرمافزار شی گرا قابل استفاده مجدد (Design
Patterns: Elements of Reusable Object-Oriented Software) * توسط *اریش گاما*، *ریچارد هلم*، *رالف جانسون* و *جان ولیسیدز* بود که بعدها به گروه چهار نفر ( **Gang of Four «GoF»** ) معروف شد. ). این کتاب به قدری تأثیرگذار است که بسیاری 23 الگوی طراحی موجود در کتاب را برای خود مهندسی نرم افزار اساسی می دانند.
در واقع، الگوها عمدتاً برای زبانهای برنامهنویسی شی گرا ثابت نوشته شدهاند و نمونههایی از کد در C++ و Smalltalk داشتند. همانطور که به زودی خواهیم دید، برخی از این الگوها ممکن است حتی در سایر زبان های برنامه نویسی با انتزاعات بالاتر مانند پایتون مورد نیاز نباشند.
23 الگو به طور کلی بر اساس نوع خود به شرح زیر طبقه بندی شده اند:
* **الگوهای خلاقانه**: این الگوها شامل کارخانه انتزاعی، الگوی سازنده، روش کارخانه، الگوی نمونه اولیه و الگوی تکتنه است.
* **الگوهای ساختاری**: اینها شامل الگوی آداپتور، الگوی پل، الگوی ترکیبی، الگوی تزئینی، الگوی نما، الگوی وزن پرواز و الگوی پروکسی است.
* **الگوهای رفتاری**: این الگوها شامل زنجیره مسئولیت، الگوی فرمان، الگوی مفسر، الگوی تکرارکننده، الگوی میانجی، الگوی یادگاری، الگوی مشاهده گر، الگوی حالت، الگوی استراتژی، الگوی الگو، و الگوی بازدیدکننده است.
در حالی که توضیح دقیق هر الگو خارج از محدوده این کتاب است، شناسایی برخی از این الگوهای موجود در خود پیادهسازی جنگو جالب خواهد بود:
| **الگوی GoF** | **کامپوننت جنگو** | **توضیح**
| الگوی فرمان | HttpRequest | این یک درخواست در یک شی | | الگوی مشاهده گر | سیگنال ها | هنگامی که یک شی تغییر حالت می دهد، همه شنوندگان آن به طور خودکار مطلع و به روز می شوند | | روش قالب | نماهای عمومی مبتنی بر کلاس | مراحل یک الگوریتم را می توان با زیر طبقه بندی بدون تغییر ساختار الگوریتم دوباره تعریف کرد |
در حالی که این الگوها بیشتر مورد توجه کسانی است که درونیات جنگو را مطالعه می کنند، رایج ترین سؤالی که پرسیده می شود این است که خود جنگو تحت کدام الگو طبقه بندی می شود؟
#### آیا جنگو MVC است؟
**مدل-ویو-کنترلر (MVC)** یک الگوی معماری است که توسط Xerox PARC در دهه 70 اختراع شد. به عنوان چارچوبی که برای ساخت رابط های کاربری در Smalltalk استفاده می شود، در کتاب GoF در ابتدا به آن اشاره شد.
امروزه MVC یک الگوی بسیار محبوب در فریمورک های برنامه های وب است. یک نوع سوال رایج این است که آیا جنگو یک چارچوب MVC است یا خیر
پاسخ هم بله و نه است. الگوی MVC از جداسازی لایه ارائه از منطق برنامه حمایت می کند. به عنوان مثال، هنگام طراحی یک API وب سایت بازی آنلاین، ممکن است جدول امتیازات بالای یک بازی را به عنوان یک فایل HTML، XML یا **مقادیر جدا شده با کاما (CSV)** ارائه دهید. با این حال، کلاس مدل زیربنایی آن مستقل از نحوه ارائه نهایی داده ها طراحی می شود.
MVC در مورد کاری که مدل ها، نماها و کنترلرها انجام می دهند بسیار سخت است. با این حال، جنگو نگاه بسیار کاربردی تری به برنامه های کاربردی وب دارد. با توجه به ماهیت پروتکل HTTP، هر درخواست برای یک صفحه وب مستقل از هر درخواست دیگری است. چارچوب جنگو مانند یک خط لوله برای پردازش هر درخواست و آماده سازی پاسخ طراحی شده است.
جنگو این را معماری **مدل-تمپلیت-ویو (MTV)** می نامد. نگرانیها بین کلاسهای واسط پایگاه داده (مدل)، کلاسهای پردازش درخواست (نما) و زبان قالب برای ارائه نهایی (الگو) جدایی وجود دارد.
اگر این را با MVC کلاسیک مقایسه کنید، یک مدل با مدل های جنگو قابل مقایسه است. یک view معمولاً Templates جنگو است و کنترلر خود چارچوبی است که یک درخواست HTTP ورودی را پردازش می کند و آن را به تابع view صحیح هدایت می کند.
اگر این به اندازه کافی شما را گیج نکرده است، جنگو ترجیح میدهد که تابع callback را برای مدیریت هر URL یک تابع view نامگذاری کند. این، متأسفانه، به ایده الگوی MVC از دیدگاه مربوط نمی شود.
#### الگوهای فاولر (Fowler's)
در سال 2002، مارتین فاولر **الگوهای معماری برنامه های سازمانی** را نوشت که حدود 40 الگوی را توصیف کرد که او اغلب در هنگام ساخت برنامه های سازمانی با آنها مواجه می شد.
برخلاف کتاب GoF که الگوهای طراحی را توصیف می کرد، کتاب فاولر در مورد الگوهای معماری بود. از این رو، آنها الگوها را در سطح بسیار بالاتری از انتزاع توصیف می کنند و تا حد زیادی زبان برنامه نویسی آگنوستیک هستند.
الگوهای فاولر به صورت زیر سازماندهی می شوند:
* **الگوهای منطقی دامنه:** این الگوها شامل مدل دامنه، اسکریپت تراکنش، لایه سرویس و ماژول جدول است
* **الگوهای معماری منبع داده:** اینها شامل دروازه داده ردیف، دروازه داده جدول، نگاشت داده و رکورد فعال است.
* **الگوهای رفتاری شی-رابطه ای:** این الگوها شامل نقشه هویت، واحد کار و بار تنبل است.
* **الگوهای ساختاری شی-رابطه ای:** این الگوها عبارتند از نگاشت کلید خارجی، نگاشت، نگاشت وابسته، نگاشت جدول انجمن، فیلد هویت، LOB سریال، ارزش جاسازی شده، نگاشتهای ارثی، ارث بری جدول تک، ارث بری جدول بتن، و ارث بری جدول کلاس.
* **الگوهای نگاشت ابرداده شی رابطه ای:** این الگوها عبارتند از Query Object، Metadata Mapping و Repository.
* **الگوهای ارائه وب:** اینها عبارتند از کنترل کننده صفحه، کنترلر جلو، کنترلر نمای مدل، نمای تبدیل، نمای الگو، کنترل کننده برنامه، و نمای دو مرحله ای
* **الگوهای توزیع:** اینها شامل شی انتقال داده و نما از راه دور است
* **الگوهای همزمانی آفلاین:** این الگوهای شامل قفل درشت دانه، قفل ضمنی، قفل آفلاین خوش بینانه و قفل آفلاین بدبینانه است.
* **الگوهای وضعیت جلسه:** اینها شامل وضعیت جلسه پایگاه داده، وضعیت جلسه مشتری و وضعیت جلسه سرور است.
* **الگوهای پایه:** این الگوها عبارتند از Mapper، Gateway، Layer Supertype، Registry، Value Object، Separated Interface، Money، Plugin، Case Special، Service Stub و Record Set.
دانستن تقریباً همه این الگوها هنگام طراحی یک برنامه جنگو مفید است. در واقع، وبسایت فاولر به آدرس `http://martinfowler.com/eaaCatalog` یک کاتالوگ عالی از این الگوها به صورت آنلاین دارد. من به شدت توصیه می کنم آنها را بررسی کنید.
جنگو تعدادی از این الگوها را نیز پیاده سازی می کند. جدول زیر تعدادی از آنها را فهرست می کنددانستن تقریباً همه این الگوها هنگام طراحی یک برنامه جنگو مفید است. در واقع، وبسایت فاولر به آدرس `http://martinfowler.com/eaaCatalog` یک کاتالوگ عالی از این الگوها به صورت آنلاین دارد. من به شدت توصیه می کنم آنها را بررسی کنید.
جنگو تعدادی از این الگوها را نیز پیاده سازی می کند. جدول زیر تعدادی از آنها را فهرست می کند:
| **الگوهای فاولر** | **کامپوننت های جنگو** | **توضیح**
|:---------------------------:|:-------------------------:|:-----------------------------------------------------------------:|
| رکورد فعال | مدل های جنگو | دسترسی به پایگاه داده را کپسوله کنید و منطق دامنه را روی آن داده ها اضافه کنید |
| وراثت جدول کلاس | وراثت مدل ها | هر موجودیت در سلسله مراتب به یک جدول جداگانه نگاشت می شود |
| فیلد هویت | ID فیلد | برای حفظ هویت، یک فیلد ID پایگاه داده را در یک شی ذخیره می کند |
|Template view | قالب های جنگو | رندر خروجی HTML بوسلیه تبدیل علامت ها و حروف |
#### آیا الگوهای بیشتری وجود دارد؟
بله حتما. الگوها همیشه کشف می شوند. مانند موجودات زنده، برخی جهش می یابند و الگوهای جدیدی را تشکیل می دهند، به عنوان مثال، انواع MVC مانند Model-view-presenter (MVP)، **Hierarchical model-view-controller (HMVC)** ، یا **Model View ViewModel (MVVM)**.
الگوها نیز با گذشت زمان تکامل می یابند، زیرا راه حل های بهتری برای مشکلات شناخته شده شناسایی می شود. به عنوان مثال، الگوی Singleton زمانی به عنوان یک الگوی طراحی در نظر گرفته می شد، اما اکنون به دلیل حالت مشترکی که معرفی می کند، مشابه با استفاده از متغیرهای سراسری، به عنوان یک ضد الگو در نظر گرفته می شود. یک ضد الگو را می توان به عنوان راه حلی که معمولاً دوباره اختراع می شود، اما راه حلی بد برای یک مشکل تعریف کرد. برخی از کتابهای معروف دیگر که الگوهای فهرستنویسی دارند، معماری نرمافزار الگو محور (POSA) توسط Buschmann، Meunier، Rohnert، Sommerlad و Sta هستند. الگوهای ادغام سازمانی توسط هوپ و وولف. و طراحی سایت ها: الگوها، اصول و فرآیندها برای ایجاد یک تجربه وب مشتری محور توسط Duyne، Landay و Hong.
### الگوها در این کتاب
این کتاب الگوهای طراحی و معماری خاص جنگو را پوشش میدهد که برای توسعهدهنده جنگو مفید است. هر الگو به این صورت ارائه خواهد شد:
**نام الگو
عنوان نام الگو است. اگر یک الگوی شناخته شده باشد، از نام رایج استفاده می شود. در غیر این صورت، نام مختصر و خود توصیفی انتخاب شده است. نام ها مهم هستند، زیرا به ساخت واژگان الگو کمک می کنند. تمام الگوها دارای قسمت های زیر خواهند بود:
* مشکل: در اینجا به طور خلاصه به مشکل اشاره می شود
* راه حل: این راه حل(های) پیشنهادی را خلاصه می کند
* جزئیات مشکل: این موضوع زمینه مشکل را توضیح می دهد و احتمالاً مثالی را ارائه می دهد
* جزئیات راه حل: این راه حل (ها) را به طور کلی توضیح می دهد و نمونه ای از اجرای جنگو را ارائه می دهد
#### نقد الگوها
علیرغم کاربرد تقریباً جهانی آنها، الگوها نیز سهم خود را از انتقاد دارند. رایج ترین استدلال ها علیه آنها به شرح زیر است:
* **الگوها ویژگیهای زبان از دست رفته را جبران میکنند:** پیتر نورویگ دریافت که 16 الگو از 23 الگو در الگوهای طراحی در زبانهای پویا مانند Lisp یا Python نامرئی یا سادهتر هستند. برای مثال، از آنجایی که توابع قبلاً در پایتون اشیاء هستند، ایجاد کلاسهای جداگانه برای پیادهسازی الگوهای استراتژی غیرضروری است.
* **الگوها بهترین شیوهها را تکرار میکنند:** بسیاری از الگوها اساساً رسمیسازی بهترین شیوهها، مانند تفکیک نگرانیها هستند، و ممکن است زائد به نظر برسند.
الگوها می توانند منجر به مهندسی بیش از حد شوند: پیاده سازی الگو ممکن است در مقایسه با راه حل ساده تر کارآمدتر و بیش از حد باشد.
* **الگوها می توانند منجر به مهندسی بیش از حد شوند:** پیاده سازی الگو ممکن است در مقایسه با راه حل ساده تر کارآمدتر و بیش از حد باشد.
#### نحوه استفاده از الگوها
اگرچه برخی از انتقادات قبلی کاملاً معتبر است، اما بر اساس نحوه استفاده نادرست از الگوها است. در اینجا چند توصیه وجود دارد که می تواند به شما در درک بهترین روش استفاده از الگوهای طراحی کمک کند:
* بهتر است از الگوها برای بیان اینکه شما از یک رویکرد طراحی کاملاً درک شده پیروی می کنید استفاده می شود
* اگر زبان شما از راه حل مستقیم پشتیبانی می کند، الگو را پیاده سازی نکنید
* سعی نکنید همه چیز را از نظر الگوها بازسازی کنید
* از یک الگو فقط در صورتی استفاده کنید که ظریف ترین راه حل در زمینه شما باشد
* از ایجاد الگوهای جدید نترسید
#### فلسفه طراحی پایتون ذن و جنگو
به طور کلی، جامعه پایتون از اصطلاح *پایتونیک* برای توصیف یک کد اصطلاحی استفاده می کند. معمولاً به اصولی اشاره دارد که در *The Zen of Python* بیان شده است. که مانند یک شعر نوشته شده است، توصیف چنین مفهوم مبهمی بسیار مفید است.
** نکته: برای مشاهده *Zen of Python* سعی کنید این را در یک اعلان پایتون `وارد کنید`.
علاوه بر این، توسعهدهندگان جنگو فلسفههای طراحی خود را در حین طراحی چارچوب در `https://docs.djangoproject.com/en/dev/misc/design-philosophies/` به طور واضح مستند کردهاند.
در حالی که این سند فرآیند فکری پشت چگونگی طراحی جنگو را توضیح میدهد، اما برای توسعهدهندگانی که از جنگو برای ساخت برنامهها استفاده میکنند نیز مفید است.
برخی از اصول مانند:
**Don't Repeat Yourself (DRY)**, **loose coupling**, and **tight cohesion** می توانند به شما کمک کنند تا برنامه های جنگو قابل نگهداری و اصطلاحی بیشتری بنویسید.
بهترین شیوه های جنگو یا پایتون پیشنهاد شده توسط این کتاب به روش زیر قالب بندی می شوند:
** نکته: از `BASE_DIR` در `settings.py` استفاده کنید و از کدگذاری نام دایرکتوری ها خودداری کنید.
### خلاصه
در این فصل، ما به این موضوع پرداختیم که چرا مردم جنگو را نسبت به سایر چارچوب های وب، تاریخچه جالب آن و نحوه کارکرد آن انتخاب می کنند. ما همچنین الگوهای طراحی، مجموعه های الگوهای محبوب و بهترین شیوه ها را بررسی کردیم.
در فصل بعد، نگاهی به چند مرحله اولیه در آغاز پروژه جنگو خواهیم داشت، مانند جمع آوری نیازمندی ها، ساخت ماکت ها و راه اندازی پروژه.
================================================
FILE: 02-ApplicationDesign/README.md
================================================
# طراحی برنامه
در این بخش، موضوعات زیر را پوشش میدهیم:
- جمعآوری نیازمندیها
- ساخت یک سند کانسپت
- ماکتهای HTML
- چگونه یک پروژه را به اپهای مختلف تقسیم کنیم
- آیا یک اپ جدید بنویسیم یا یک اپ موجود را بازنویسی کنیم
- بهینهترین روشها قبل از شروع یک پروژه
- چرا پایتون ۳
- کدام نسخه جنگو را استفاده کنیم
- شروع پروژه SuperBook
بسیاری از توسعهدهندگان یک پروژه جدید را با نوشتن کد آغاز میکنند. معمولاً چنین روشی به برداشتهای غلط، امکانات بیاستفاده و اتلاف زمان منتهی میشود. صرف وقت برای فهمیدن نیازهای اصلی مشتری، حتی در یک پروژه کوچک از نظر زمانی، میتواند نتایج باورنکردنی داشته باشد. مدیریت نیازمندیها یک مهارت کلیدی است که ارزش یادگیری دارد.
## چگونه نیازمندیها را جمعآوری کنیم؟
_"خلاقیت، بله گفتن به هر چیزی نیست، بلکه نه گفتن به هرچیزی غیر از ویژگیهای بسیار مهم است."_
_–استیو جابز_
من پروژههای محکوم به شکست بسیاری را فقط با گوش کردن دقیق به نیازهای مشتری و تعیین انتظارات درست، نجات دادهام. تنها سلاح من قلم وکاغذ (یا معادل دیجیتال آن) بوده است. فرآیند به طرز باورنکردنی ساده اما تأثیرگذار است. در اینجا چند نکته کلیدی آمده است تا در زمان جمعآوری نیازمندیها به یاد داشته باشید:
1. مستقیماً با صاحبان برنامه صحبت کنید حتی اگر از نظر فنی توانایی نداشته باشند.
2. اطمینان پیدا کنید که کاملاً نیازهای آنها را شنیدهاید و یادداشت برداشتهاید.
3. از اصطلاحات تخصصی مانند _models_ استفاده نکنید. ساده صحبت کنید و از اصطلاحات آشنا برای کاربر مانند _پروفایل کاربری_ استفاده کنید.
4. انتظارات درست ایجاد کنید. اگر چیزی از نظر فنی سخت یا غیرممکن است، مطمئن شوید که درست به مشتری توضیح دادهاید.
5. تا میتوانید طراحی کنید. افراد به طور طبیعی با تصویر راحتتر هستند، وبسایتها هم تصویری هستند. از خطوط ساده و چسباندن عکس استفاده کنید. لازم نیست همه چیز عالی باشد.
6. فرآیندهایی مانند ثبتنام را به بخشهای کوچک تقسیم کنید. هر عملکرد چند مرحلهای، باید در مستطیلهایی که با خط و فلش به هم وصل شدهاند رسم شود.
7. حالا، ویژگیهای مورد نیاز برنامه را به صورتی لیستی از سناریوهای کاربری و در شکلی ساده و قابل خواندن جمعآوری کنید.
8. سعی کنید نقش مؤثری در تقسیم ویژگیها به سه اولویت بالا، متوسط و پایین، ایفا کنید.
9. در قبول ویژگی جدید بسیار، بسیار محافظهکارانه برخورد کنید.
10. بعد از جلسه، یادداشتهای خود را با بقیه به اشتراک بگذارید تا از سوءتفاهم جلوگیری شود.
جلسه اول طولانی خواهد بود (شاید یک کارگاه یک روزه یا یک جلسه چند ساعته). بعداً وقتی این جلسات تکرار شد میتوانید آنها را تبدیل به جلساتی نیم ساعته یا یک ساعته کنید.
نتایج این جلسات احتمالاً یک صفحه نوشته و چند صفحه طراحی ضعیف خواهد بود. بعضیها یک _wireframe_ که اسکلت اصلی وبسایت است نیز میسازند.
در این کتاب ما یک پروژه به نام SuperBook که یک شبکه اجتماعی برای ابرقهرمانان است میسازیم. یک وایرفریم ساده بر اساس صحبتهای ما با چندین ابرقهرمان که به صورت اتفاقی انتخاب شدهاند در اینجا آمده است:
 *یک وایرفریم از وبسایت سوپربوک به صورت ریسپانسیو - صفحه بندی دسکتاپ و موبایل*
## آیا شما یک قصهگو هستید؟
خب، خلاصه یک صفحهای چیست؟ یک سند ساده است که شرح میدهد استفاده از سایت چه حسی دارد. تقریباً در تمام پروژههایی که من با آنها کار کردهام وقتی فرد جدید وارد تیم میشود، اگر از آنها خواسته شود که تمام اسناد را مطالعه کنند سریعاً دلسرد میشوند اما اگر قرار باشد فقط یک صفحه را مطالعه کنند تا بفهمند که هدف سایت چیست بسیار هیجانزده میشوند.
شما میتوانید به سند هر نامی که دوست دارید بدهید؛ سند کانسپت، سند نیازمندیهای بازار، مستندات تجربه مشتری یا Epic Fragile StoryLog™ (در انتظار ثبت برند). واقعاً اهمیت خاصی ندارد.
این سند باید بر روی تجربه کاربر متمرکز باشد تا جزییات فنی یا نحوه پیادهسازی. این سند باید کوتاه و جذاب برای مطالعه کردن باشد. در واقع قانون شماره یک جوئل اسپاسکی برای سند نیازمندیها _بامزهبودن_ است.
اگر امکان دارد در مورد یک کاربر معمولی ( که در زبان بازاریابی پرسونا میگویند) بنویسید، مشکلاتی که آنها مواجه میشوند و روشی که وب اپلیکیشن مشکل آنها را برطرف میکند. تصور کنید که آنها چطور تجربه خود را برای دیگران شرح خواهند داد. سعی کنید این تصویر را بهدست آورید.
اینجا یک سند کانسپت برای پروژه سوپربوک داریم:
**_کانسپت سوپربوک_**
_این مصاحبه پس از راهاندازی وبسایت ما سوپربوک، در آینده انجام شده است. یک تست کاربری ۳۰ دقیقهای دقیقاً قبل از مصاحبه اجرا شده است._
**_لطفا خودتان را معرفی کنید_**
_ اسم من اَکسل است. من یک سنجاب خاکستری هستم که در مرکز نیویورک زندگی میکنم. همه من را اَکورن صدا میکنند.پدرم تی. بری که هنرمند شناختهشده هیپ هاپ بود من را به این نام صدا میکرد. فکر میکنم هیچ وقت اینقدر خوب نمیخواندم که کسب و کار خانوداگی را ادامه دهم. در واقع در ابتدا کمی عشق سرقت بودم. میدانید که، من به فندق و مانند آن حساسیت دارم اما بقیه رفیقهایم راحت میخورند. آنها میتوانند به سادگی در هر پارکی زندگی کنند. من مجبورم خلاق باشم، کافهها، سالنهای سینما یا پارکهای سرگرمی. من برچسبها را به دقت می خوانم._
***خب اکورن فکر میکنی که چرا برای تست کاربری انتخاب شدهای؟***
_احتمالاً برای اینکه در برنامه ویژه NY Star که در مورد ابرقهرمانان کمتر شناخته شدهبود شرکت کردم. به نظرم این برای مردم جالب بود که یک سنجاب میتواند از مکبوک استفاده کند (مصاحبه کننده: این مصاحبه از طریق چت انجام شده است). علاوه بر این، من دقت نظر یک سنجاب را دارم._
 ***بر اساس چیزی که دیدهاید نظر شما در مورد سوپربوک چیست؟***
_فکر میکنم که ایده فوقالعادهای است. منظورم این است که مردم معمولاً ابرقهرمانان را میبینند، با اینحال کسی به آنها توجهی ندارد. اکثر آنها تنها و غیر اجتماعی هستند. سوپربوک میتواند این وضعیت را تغییر دهد._
**_فکر میکنید که چه چیزی در سوپربوک متفاوت است؟_**
_این سایت از ابتدا برای افرادی مانند ما ساخته شده است. منظورم این است که وقتی قرار است از هویت مخفی خود استفاده کنید مجبور نیستید موارد بیمعنی مانند "سابقه کار و تحصیلات" را پر کنید. اگرچه که من چنین چیزی ندارم ولی میتوانم بفهمم که چرا ممکن است یکی بخواهد این کار را بکند._
**_ممکن است به طور خلاصه برخی ویژگیهایی را که برای شما مهم بودند نام ببرید؟_**
_بله حتماً، فکر میکنم این یک شبکه اجتماعی مناسب است که شما میتوانید:_
- _میتوان با هر نوع نام کاربری ثبت نام کرد (دیگر عبارت احمقانه "نام واقعی خود را وارد کنید" را نمیبینیم)_
- _طرفدارها میتوانندافراد را دنبال کنند بدون آنکه مجبور باشند آنها را به عنوان "دوست" اضافه کنند_
- _میتوان پست و کامنت ایجاد کرد و آنها را بازنشر کرد_
- _میتوان یک پست خصوصی را برای دیگری ارسال کرد_
\* همه چیز ساده است. لازم نیست ابرقهرمان باشید تا با آن کار کنید. **آکورن، از وقتی که گذاشتی تشکر میکنیم. \***
## ماکتهای HTML
در ابتدای ساخت وب اپلیکیشنها، ابزارهایی مانند فوتوشاپ و Flash به طور گستردهای استفاده میشدند تا ماکتهایی با کیفیت پیکسلی، ساخته شوند. الان به ندرت پیشنهاد میشوند یا مورد استفاده قرار میگیرند.
امروزه ارائه یک تجربه یکسان بین موبایل، تبلت، لپ تاپ و سایر پلتفرمها بسیار مهمتر از یک طراحی پیکسلی بسیار دقیق است. در واقع، اکثر طراحان وب مستقیماً صفحهبندی خود را به صورت HTML انجام میدهند.
ساختن یک ماکت HTML بسیار سریعتر و سادهتر از قبل است. اگر طراح وب شما در دسترس نیست، توسعهدهندگان میتوانند از یک فریمورک CSS مانند Bootsrap یا ZURB Foundation برای ساخت یک ماکت مناسب و زیبا استفاده کنند.
هدف از ساخت یک ماکت، ایجاد پیشنمایشی واقعی از وبسایت است. ماکت نباید بر جزییات متمرکز باشد بلکه باید به نسبت طراحیهای خطی اولیه، به محصول نهایی نزدیکتر باشد و علاوه بر این تعاملی باشد. HTML ایستای خود را با اضافه کردن لینک و برخی تکه کدهای جاواسکریپتی ساده، پویاتر کنید.
یک ماکت خوب میتواند تا ۸۰ درصد از تجربه کاربری نهایی را تنها با حدود ۱۰ درصد از توسعه اصلی، ایجاد کند.
## طراحی اپلیکیشن
وقتی که شما تصویر خوبی از آنچه لازم است بسازید، پیدا کردید، وقت آن است که به پیادهسازی آن در جنگو فکر کنید. الان شروع کدنویسی وسوسهانگیز است، با اینحال اگر چند دقیقهای برای طراحی وقت بگذارید راههای بسیار متفاوتی برای حل مشکلات طراحی پیدا خواهید کرد.
هنچنین شما میتوانید ابتدا تستها را طراحی کنید، همانطور که در متد **توسعه تست محور** (**Test-Driven Development**)، مطرح میشود. ما رویکرد TDD را در بخش ۱۱ _تست کردن و رفع مشکل_ بیشتر بررسی خواهیم کرد.
هر رویکردی را که انتخاب کنید، خوب است که کمی توقف کنید و به این موضوعات فکر کنید:
- راههای مختلف من برای پیادهسازی این اپلیکیشن چیست؟
- مزایا و معایب آن چیست؟
- در زمینه کار ما کدام فاکتورها مهمتر هستند؟
- در نهایت، کدام رویکرد بهترین است؟
بهترین طراحیها اغلب در مجموع ظریف و هماهنگ هستند.اینجاست که معمولاً الگوهای طراحی به شما کمک میکنند. کدهای خوب طراحی شده لزوماً برای خواندن، ساده نیستند اما برای توسعه و بهبود دادن سادهتر هستند.
توسعهدهندگان باتجربه جنگو، به کلیت پروژه از روشهای مختلفی نگاه میکنند. این توسعهدهندگان با وفاداری به اصول DRY (شاید به خاطر اینکه تنبل شدهاند)، همواره فکر میکنند آیا من این عملکرد را قبلاً دیدهام؟ مثلاً آیا این لاگین به کمک سوشیال را میتوان به کمک یک پکیج کمکی مانند django-all-auth، پیادهسازی کرد؟
اگر مجبور باشند که اپ را خودشان بنویسند، به امید پیدا کردن بهترین طراحی، به انواع الگوهای طراحی فکر میکنند. با اینحال، در ابتدا نیاز است که یک پروژه را به اپهای کوچک تقسیم کنند.
**تقسیم کردن پروژه به اپها**
اپلیکیشنهای جنگو، **پروژه** نامیده میشوند. یک پروژه از اپلیکیشنها یا اپهای مختلفی تشکیل شده است. یک اپ، یک پکیج پایتونی است که مجموعهای از ویژگیها را برای یک هدف مشخص مانند ثبتنام یا تامبنیل عکسها تأمین میکند.
به صورت ایدهال یک اپ باید چندبار مصرف باشد و بهسادگی بتواند با سایر اپها ارتباط برقرار کند. شما میتوانید به هر تعداد که بخواهید اپ بسازید. هرگز نگران اضافه کردن اپهای جدید یا بازنویسی اپهای موجود و تقسیم آنها به چند اپ جدید نباشید. یک پروژه معمول جنگو از ۱۵ تا ۲۰ اپ تشکیل شده است.
تصمیم مهم در این مقطع این است که از یک پکیج شخص ثالث استفاده کنید یا آن را از ابتدا بسازید. پکیجهای شخص ثالث، که توسط شما نوشته نشدهاند، آماده استفاده هستند. اکثر پکیجها بسیار سریع نصب و تنظیم میشوند. شما میتوانید ظرف چند دقیقه از آنها استفاده کنید.
در سوی دیگر، اگر اپ خود را بنویسید به این معنی است که باید مدلها، ویوها، تستها و سایر موارد را خودتان بنویسید. جنگو بین این دو نوع از اپ هیچ تفاوتی قائل نیست.
**استفاده مجدد یا نوشتن از اول؟**
یکی از بزرگترین ویژگیهای جنگو اکوسیستم اپهای شخص ثالث آن است. در زمان نوشتن این متن، [djangopackages.com](http://djangopackages.com/) بیش از ۳۵۰۰ پکیج را لیست کرده است. شما ممکن است در شرکتتان یا در کتابخانههای خصوصی حتی بیشتر از این هم پیدا کنید. زمانی که پروژه شما به اپهای کوچک تقسیم شود و بدانید که به چه نوع اپهایی نیاز دارید، وقت فراهم کردن اپها است، چه آنها را بنویسید یا اینکه از اپی موجود دوباره استفاده کنید.
ممکن است نصب و استفاده از یک اپ آماده سادهتر به نظر برسد، اما به همین سادگی هم نیست. بیایید برای پروژه خودمان نگاهی به چند اپ شخص ثالث اعتبارسنجی بیندازیم و دلایلی را که از آنها برای پروژه سوپربوک استفاده نمیکنیم، فهرست کنیم:
- **بیش از حد مهندسی شده برای نیازهای ما**: ما احساس میکنیم پکیج [python-social-auth](https://github.com/python-social-auth/social-app-django) با پشتیبانی لاگین به همه شبکههای اجتماعی، غیر ضروری است.
- **بیش از حد مشخص شده**: استفاده از [Django-Facebook ](http://django-facebook.readthedocs.io/en/latest/installation.html) به معنی آن است که اعتبارسنجی خودمان را به امکانات یک وبسایت مشخص گره بزنیم.
- **ایجاد خرابی در سایر اپها **: بعضی از اپها ممکن است تأثیرات ناخواستهای بر سایر اپها داشته باشند.
- **وابستگیهای پایتونی**: برخی از اپها وابستگیهایی دارند که به طور فعال بهروزرسانی نمیشوند یا مورد تأیید نیستند.
- **وابستگیهای غیر پایتونی**: برخی اپها وابستگیهای غیر پایتونی دارند مانند Redis و Node.js که سربارهایی برای انتشار وبسایت ایجاد میکنند.
- **چندبار مصرف نبودن**: بسیاری از اپهای خودمان قابل استفاده نیستند چرا که برای استفاده مجدد نوشته نشدهاند و استفاده چندباره از آنها سخت است.
هیچکدام از این پکیجها بد نیستند. اینها فقط نیاز فعلی ما را پاسخ نمیهند. این پکیجها ممکن است برای پروژه دیگری مورد استفاده قرار بگیرند. در مسأله ما اپ پیشساخته اعتبارسنجی جنگو، به خوبی کافی است.
به عبارت دیگر، شما ممکن است ترجیح دهید از یک اپ شخص ثالث به یکی از دلایل زیر استفاده کنید:
- **DRY**: دوباره چرخ را اختراع نکنید. از مزایای اپهای متن باز و به خوبی تستشده که احتمالاً بهتر از آن چیزی است که بخواهید از ابتدا بنویسید، استفاده کنید.
- **برای فهمیدن زیادی سخت است**: آیا نیاز است که مدلهای شما یک درخت را شکل دهند اما همزمان از نظر دیتابیس بهینه باشند؟ از django-mptt استفاده کنید.
- **بهترین یا توصیه شدهترین اپ برای یک هدف**: این توصیهها در طول زمان تغییر میکنند، اما پکیجی مانند django-debug-toolbar، توصیهشدهترین پکیج برای رفع عیب است.
- **باتری اضافه**: بسیاری احساس میکنند که پکیجهایی مانند django-model-utils و django-extensions باید بخشی از بدنه اصلی فریمورک جنگو باشد.
- **حداقل وابستگیها**: این همیشه نکته مثبتی است. اپهای کمتر به معنی تداخل ناخواسته کمتر بین سایر اپهاست و نگرانی کمتری ایجاد میکند.
خب، آیا باید از یک اپ استفاده دوباره کرد و زمان را صرفهجویی کرد یا اینکه باید از اول نوشت؟ من پیشنهاد میکنم که اول یک اپ شحص ثالث را در یک محیط آزمایشی، امتحان کنید. اگر یک توسعهدهنده متوسط جنگو هستید، بخش بعد به شما میگوید که چگونه اپ را در محیط آزمایشی بررسی کنید.
**محیط آزمایشی اپ من**
در هر موقعیتی، شما با لیست متفاوتی از اپهای به دردبخور برای جنگو در پستهای وبلاگی مواجه میشوید. با اینحال بهترین راه برای تصمیمگیری در مورد استفاده از یک پکیج در یک پروژه، **پروتوتایپ** کردن آن است.
حتی اگر شما یک محیط مجازی پایتون برای پروژه خود درست کرده باشید، نصب کردن و بررسی این اپها و بعد حذف آنها، ممکن است باز هم محیط مجازی شما را کثیف کند. بنابراین من معمولاً یک محیط مجازی جداگانه با پسوند _sandbox_ میسازم که به طور اختصاصی برای بررسی پکیجهاست. یک پروژه کوچک هم میسازم تا بهتر متوجه شوم که استفاده از این پکیج خاص چطور به ساده شدن کارها کمک میکند.
در نهایت اگر از بررسی این اپ راضی بودم به کمک یک ابزار کنترل نسخه مانند گیت یک شاخه جدید برای اضافه کردن این اپ به پروژه، میسازم. سپس، کدنویسی و نوشتن تستها را در این شاخه جدید ادامه میدهم تا ویژگیهای لازم به پروژه اضافه شوند. در پایان این شاخه بازبینی شده و به شاخه اصلی اضافه میشود.
**با کدام پکیجها پروژه را بسازیم؟**
برای روشن شدن فرآیند، پروژه سوپربوک ما به طور کلی به اپهای زیر تقسیم میشود (البته لیست کامل نیست):
- **اعتبارسنحی یا Authentication ** (اپ موجود django.auth) این اپ، ثبتنام، ورود و خروج کاربر را مدیریت میکند
- **اکانتها یا Accounts** (اپ اختصاصی) این اپ اطلاعات اضافی پروفایل کاربر را مدیریت میکند
- **پستها یا Posts** (اپ اختصاصی) این اپ پستها و کامنتها را مدیریت میکند
در این مرحله، هر اپی که قرار است از ابتدا ساخته شود (با برچسب اپ اختصاصی)، و یا از اپهای شخص ثالث استفاده شود (اپ موجود)، مشخص شده است. در مراحل پیشرفت پروژه، ممکن است این موارد تغییر کند. با اینحال این لیست برای الان کافی است.
## بهینهترین روشها قبل از شروع پروژه
موقعی که یک محیط توسعه را آماده میکنید، مطمئن شوید که این ابزارها را فراهم کردهاید:
- **یک محیط مجازی تازه پایتون**: پایتون ۳ به صورت پیشفرض دارای ماژول venv است یا اینکه میتوانید virtualenv را نصب کنید. هر دو اینها جلوی آلوده شدن محیط عمومی کتابخانههای پایتون شما را میگیرند. اما [pipenv ](https://docs.pipenv.org/) ابزار پیشنهادی ماست (در این کتاب هم از آن استفاده میکنیم) تا وابستگیها و محیط مجازی پروژه را کنترل کند.
- **کنترل نسخه**: همیشه از یک ابزار کنترل نسخه مانند گیت یا Mercurial استفاده کنید. این ابزارها نجاتدهنده هستند. به کمک آنها بدون نگرانی و ترس میتوانید تغییرات ایجاد کنید.
- **انتخاب یک قالب برای پروژه**: قالب پیشفرض جنگو تنها انتخاب نیست. بر اساس نیازهایتان میتوانید از قالبهای دیگر مانند [Edge ](https://github.com/pydanny/cookiecutter-django) یا [Cookiecutter](https://github.com/pydanny/cookiecutter-django) استفاده کنید.
- **پایپ لاینهای انتشار**: من معمولاً کمی دیرتر نگران این موضوع میشوم. اما یک فرآیند انتشار سریع میتواند توسعه را سرعت ببخشد. من Fabric (یک نسخه پایتون ۳ به نام fabric3 دارد) یا Ansible را ترجیح میدهم.
## سوپربوک، مأموریتی که باید بپذیرید
این کتاب به رویکرد عملی و کاربردی برای پیادهسازی الگوهای طراحی جنگو و روشهای بهینه آن، به کمک مثالها، باور دارد. برای هماهنگی بیشتر، تمام مثالها در مورد ساخت یک پروژه شبکه اجتماعی به نام سوپربوک است.
سوپربوک، به طور خاص بر روی بخش ویژه و اغلب نادیده گرفته شدهای از افرادی که دارای ابرقدرتهای استثنایی هستند، متمرکز است. شما یکی از توسعهدهندگان تیمی متشکل از سایر توسعهدهندگان، طراحان وب، یک مدیر بازاریابی و یک مدیر پروژه هستید.
پروژه روی آخرین نسخه پایتون (نسخه 3.6) و جنگو (نسخه 2.0) در زمان نوشتن کتاب، توسعه داده میشود. از آنجایی که انتخاب پایتون ۳ ممکن است موضوعی بحث برانگیز باشد، شایسته است که توضیح بیشتری داده شود.
**چرا پایتون ۳؟**
در حالی که توسعه پایتون 3.0 در سال ۲۰۰۶ شروع شد، اولین نسخه آن در ۳ دسامبر ۲۰۰۸ منتشر شد. دلیل اصلی برای توسعه دادن یک نسخه هماهنگ با نسخههای قبلی این موارد بود: هماهنگی تمام رشتهها با یونیکد، افزایش استفاده از iteratorها، کنار گذاشتن ویژگیهای منسوح شده مانند کلاسهای قدیمی، و برخی قواعد دستوری جدید مانند عبارتهای nonlocal.
بازخوردها به پایتون ۳ در جامعه جنگو کمی درهم آمیخته بود. اگرچه تغییرات زبان بین نسخه ۲ و ۳ کم بود (و در طول زمان هم کاهش یافت)، انتقال تمام کدهای جنگو، یک مهاجرت واقعاً بزرگ بود.
در ۱۳ فوریه، جنگو 1.5، اولین نسخه منتشر شدهای بود که پایتون ۳ را پشتیبانی میکرد. توسعهدهندگان اصلی اعلام کردهاند که در آینده، جنگو فقط برای پایتون ۳ نوشته خواهد شد.
به دلایل زیر، پایتون ۳ انتخاب ایدهآلی برای این کتاب است:
- **دستور زبان بهتر**: دستور زبان پایتون ۳ بسیاری از دستورات زشت مانند _izip_، _xrange_ و \_\_unicode\_\_ را با دستورات تمیزتری مانند zip، range و \_\_str\_\_ جابجا کردهاست.
- **حمایت شخص ثالث کافی**: بیش از ۹۰ درصد از ۲۰۰ کتابخانه مهم شخص ثالث، از پایتون ۳ پشتیبانی میکنند (به Python 3 Wall of Superpowers نگاهی بیندازید).
- **نداشتن کد قدیمی**: ما یک پروژه را از ابتدا شروع میکنیم و نیازی نداریم با کدهای باقی مانده از قبل سر و کار داشته باشیم.
- **زبان پیشفرض در پلتفرمهای جدید**: الان به صورت پیشفرض مفسر پایتون ۳ در Arch Linux وجود دارد و اوبونتو و فدورا هم در نسخههای بعدی آن را به صورت پیشفرض فعال خواهند کرد.
- **سادهتر است**: از دیدگاه توسعه بر اساس جنگو، تغییرات این دو بسیار کم هستند و میتوان ظرف چند دقیقه آنها را فرا گرفت.
نکته آخر مهم است. حتی اگر شما از پایتون ۲ استفاده کنید، این کتاب برای شما قابل استفاده است. ضمیمه A را بخوانید تا تفاوتها را بفهمید. لازم است تغییرات کمی بدهید تا کدها را برای پایتون ۲ آماده کنید.
**کدام نسخه جنگو را استفاده کنیم**
جنگو الان یک برنامه زمانی استاندارد شده برای توسعه در سه مدل مختلف دارد:
- **نسخه ویژگی (Feature)**: این نسخهها ویژگیهای جدید دارند یا ویژگیهای موجود را ارتقا دادهاند. این نسخهها هر ۸ ماه یکبار منتشر میشوند و یک پشتیبانی ۱۶ ماهه از زمان انتشار دارند. شمارههای آنها به صورت A.B است (دقت کنید که نسخه مینور ندارند)
- **نسخه پشتیبانی بلندمدت(Long-Term Support)**: اینها یک نوع خاص از نسخه ویژگی هستند که یک دوره پشتیبانی ۳ ساله از زمان انتشار دارند. این نسخهها هر دو سال یکبار منتشر میشوند. شماره این نسخهها به شکل A.2 است (هر سه نسخه ویژگی، یک نسخه LTS خواهد بود). نسخههای بلند مدت، چند ماه با هم همپوشانی دارند تا مهاجرت از یک نسخه به نسخه دیگر با آرامش انجام شود.
- **نسخه رفع عیب (Patch)**: این نسخهها برای رفع عیب یا اصلاح مشکلات امنیتی هستند. این نسخهها بلافاصه منتشر میشوند. چون به طور معمول تغییرات زیادی ندارند بهروزرسانی به این نسخهها معمولاً بدون مشکل است. این نسخهها شمارههایی به شکل A.B.C دارند.
این نقشه راه توسعه جنگو، تصویر شفافتری از رویکرد توسعه جنگو ارائه میدهد:

نقشه انتشار نسخههای جنگو
جنگو 1.11 LTS آخرین نسخهای خواهد بود که پایتون ۲ را پشتیبانی میکند و این نسخه تا آپریل ۲۰۲۰ پشتیبانی خواهد شد. نسخههای بعدی فقط از پایتون ۳ پشتیبانی خواهند کرد.
یک نسخه جنگو مناسب برای شما، بستگی به این دارد که هر چند وقت بکبار میتوانید جنگو را آپدیت کنید و به کدام ویژگیها نیاز دارید. اگر پروژه شما به صورت فعال در حال توسعه است و نسخه جنگو میتواند هر ۱۶ ماه یکبار آپدیت شود، پس شما میتوانید آخرین نسخه ویژگی را نصب کنید فارغ از اینکه LTS هست یا نه.
در غیر اینصورت، اگر پروژه شما فقط گاهی توسعه داده میشود، پس باید آخرین نسخه LTS را انتخاب کنید. به روزرسانی وابستگیهای پروژه جنگو از یک نسخه ویژگی به نسخه ویژگی دیگر ممکن است تلاشی غیر ضروری باشد. بنابراین توضیحات هر نسخه را مطالعه کنید و براساس آن برنامهریزی کنید.
این کتاب تا حد ممکن از امکانات جنگو 2.0 استفاده میکند.
**شروع پروژه**
این بخش دارای دستورالعملهای نصب پروژه سوپربوک است که شامل تمام کدهای استفاده شده در کتاب است. میتوانید برای دیدن آخرین یادداشتها در مورد نصب پروژه، فایل README.md را در [گیتهاب](https://github.com/DjangoPatternsBook/superbook2) ببینید. ما از ابزار pipenv برای ساخت یک محیط مجازی و نصب وابستگیها استفاده میکنیم.
 _برای هر پروژه جنگو یک محیط مجازی جداگانه بسازید_
ابتدا، پروژه را از گیتهاب کپی کنید:
**$ git clone https://github.com/DjangoPatternsBook/superbook2.git**
سپس، pipenv را بر اساس توصیه مستندات خودش، به صورت لوکال یا به صورت عمومی، اما خارج از virtualenv، به کمک دستورات زیر نصب کنید:
**$ pip install -U pip $ pip install pipenv**
حالا به پوشه پروژه بروید و وابستگیها را نصب کنید:
**$ cd superbook2!**
**$ pipenv install --dev**
سپس وارد شل بشوید تا از محیط مجازی تازه ساخته شده با تمام وابستگیها، استفاده کنید:
**$ pipenv shell**
در نهایت، پروژه را با دستورات مدیریتی جنگو، اجرا کنید:
**$ cd src**
**$ python manage.py migrate**
**$ python manage.py createsuperuser**
**$ python manage.py runserver**
میتوانید به آدرس http://127.0.0.1:8000 که در ترمینال نمایش دادهشده بروید و از وبسایت استفاده کنید.
## خلاصه
تازهکارها معمولاً یک فرآیند جمعآوری نیازمندیهای خوب را دستکم میگیرند. همزمان بسیار مهم است که بیش از حد درگیر جزییات نشد، چرا که برنامهنویسی ذاتاً یک فرآیند اکتشافی است. پروژههای موفق، پیش از توسعه، زمان مناسبی را برای برنامهریزی و آمادهسازی صرف میکنند در نتیجه بیشترین منافع را ایجاد میکنند.
ما درمورد جنبههای مختلفی از طراحی اپلیکیشن بحث کردیم مانند ساختن ماکت تعاملی یا تقسیم پروژه به بخشهای چندبار مصرف به نام اپ. همچنین برای پروژه مثالی سوپربوک، مراحل راهاندازی را مطرح کردیم.
در بخشهای بعدی به هر قسمت از جنگو با جزییات بیشتری نگاه خواهیم کرد و الگوهای طراحی و روشهای بهینه در مورد آنها را یاد خواهیم گرفت.
================================================
FILE: 03-Models/README.md
================================================
# مدلها
در این بخش به بحثهای زیر میپردازیم:
- اهمیت مدلها
- نمودار کلاسها
- الگویهای ساختاری مدل
- الگوهای رفتاری مدل
- مایگریشنها (مهاجرتها)
من یک بار به یک استارت آپ تحلیل داده، در مراحل اولیهشان مشاوره دادم. با وجود اینکه گرفتن دیتا به یک بازه زمانی اخیر محدود شده بود، آنها مشکلات کارایی(performance) داشتند. باز کردن برخی صفحات بعضی اوقات چند ثانیه طول میکشید. بعد از بررسی معماریشان، به تظر میآمد که مشکل از مدل دادهشان بود. در عین حال، مهاجرت کردن(migrating) و تبدیل پتابایتهایی از دیتای ساختاریافته و زنده، غیر ممکن به نظر میرسید.
> "فلوچارت خود را به من نشان بدهید و جداول خود را پنهان کنید و من همچنان مبهوت خواهم ماند.
جداول خود را به من نشان دهید و من معمولاً نیازی به فلوچارتها نخواهم داشت، آنها آشکار خواهند بود." (فرد بروکز، The Mythical Man-month)
به طور سنتی، طراحی کد بر اساس دادههای فکر شده همیشه توصیه میشود. اما در این عصر دادههای بزرگ، این توصیهها مرتبطتر هم شده است. اگر مدل داده شما ضعیف طراحی شده باشد، حجم دادهها در نهایت باعث مشکلات مقیاس پذیری و نگهداری میشود. توصیه میکنم از ضرب المثل زیر در مورد نحوه تعادل کد و داده استفاده کنید:
> **قانون بازنمایی** (Rule of Representation): دانش را در دیتا قرار بدهید تا منطق برنامه قدرتمند و احمق باشد.
فکر کنید که چطور میتوانید پیچیدگی را از کد به دیتا ببرید. همیشه فهمیدن منطق کد از فهمیدن منطق دیتا سختتر است. یونیکس از همین فلسفه به خوبی استفاده کرده است تا تعداد زیادی ابزار ساده ایجاد شود که میتوانند با هم ترکیب (پایپ) شوند و هر گونه تغییر روی دیتاهای متنی را انجام دهند.
در نهایت، دادهها طول عمر بیشتری نسبت به کد دارند. شرکتها ممکن است تصمیم بگیرند کل پایگاههای کد را بازنویسی کنند زیرا دیگر نیازهای آنها را برآورده نمیکنند، اما پایگاههای داده معمولاً نگهداری میشوند و حتی در بین برنامهها به اشتراک گذاشته میشوند.
پایگاههای دادهای که به خوبی طراحی شده اند بیشتر یک هنر هستند تا یک علم. این فصل برخی از اصول اساسی مانند نرمال سازی(Normalization) و بهترین شیوهها در مورد سازماندهی دادهها را به شما ارائه میدهد. اما قبل از آن، بیایید ببینیم مدلهای داده در برنامه جنگو کجا قرار میگیرند.
# ام بزرگ تر از وی و سی بزرگ تر از وی است
در جنگو، مدلها کلاسهایی هستند که روشی شیءگرا برای برخورد با پایگاههای داده ارائه میکنند. به طور معمول، هر کلاس به یک جدول پایگاه داده و هر ویژگی به یک ستون پایگاه داده اشاره دارد. میتوانید با استفاده از یک API که به طور خودکار تولید میشود، کوئریهایی را در این جداول ایجاد کنید.
مدلها میتوانند پایه بسیاری از اجزای دیگر باشند. هنگامی که یک مدل دارید، میتوانید به سرعت ادمینهای مدل، فرمهای مدل و انواع نماهای عمومیرا استخراج کنید. در هر مورد، باید یک یا دو خط کد بنویسید تا خیلی هم جادویی به نظر نرسد.
همچنین، مدلها در مکانهای بیشتری از آنچه انتظار دارید، استفاده میشوند. این به این دلیل است که جنگو را میتوان به روشهای مختلفی اجرا کرد. برخی از نقاط ورود جنگو به شرح زیر است:
- جریان آشتای درخواست-پاسخ وب
- شل اینترکتیو جنگو
- دستورات مدیریتی (management commands)
- اسکریپتهای تست
- صفهای وظایف ناهمزمان همانند سلری
تقریباً در همه این موارد، ماژولهای مدل وارد میشوند (به عنوان بخشی از **django.setup()**). از این رو، بهتر است مدلهای خود را از هر گونه وابستگی غیر ضروری یا هر جزء دیگر جنگو، مانند viewها دور نگه دارید.
به طور خلاصه، طراحی درست مدلهای شما، بسیار مهم است. حالا بیایید با طراحی مدل SuperBook شروع کنیم.
#### کیف قهوهای نهار:
یادداشت نویسنده: پیشروی این پروژه سوپرکتاب در یک بخش مثل این نمایش داده خواهد شد. شاید شما از جعبه عبور کنید, ولی بینشها و تجربههای زیاد و درامای کار کردن روی یک پروژه وب اپلیکیشن را از دست میدهید.
هفته اول استیو با مشتریش، هوش ابرقهرمانی و مانیتورینگ (شیم) به صورت کوتاه، خیلی قاطی پاتی بود. دفتر فوقالعاده آیندهنگر بود، اما انجام هر کاری به صدها تائید و امضا نیاز داشت.
استیو به عنوان توسعهدهنده اصلی جنگو، راهاندازی یک سرور توسعه متوسط را که میزبان چهار ماشین مجازی بود بعد از دو روز به پایان رسانده بود. صبح روز بعد، خود دستگاه ناپدید شده بود. یک ربات به اندازه ماشین لباسشویی در همان نزدیکی گفت که به دلیل نصب نرم افزار تایید نشده به بخش پزشکی قانونی منتقل شده است.
با این حال، مدیر ارشد فناوری، هارت، کمک بزرگی بود. او درخواست کرد دستگاه تا یک ساعت دیگر با تمام سیستمهای نصبشده سالم برگردانده شود. او همچنین پیش تأییدیههایی را برای پروژه سوپربوک ارسال کرده بود تا از چنین موانعی در آینده جلوگیری کند.
بعد از ظهر همان روز، استیو یک کیف قهوهای ناهار همراهش بود. یک کت بلیزر بژ و شلوار جین آبی روشن پوشیده بود. هارت به موقع رسید. علیرغم اینکه از بیشتر مردم بلندتر بود و سرش تراشیده بود، خونسرد و خوش برخورد به نظر میرسید. او پرسید که آیا استیو تلاش قبلی برای ساخت پایگاه داده ابرقهرمانی در دهه شصت را بررسی کرده است؟
استیو گفت: "اوه بله، پروژه Sentinel، درست است؟". "به نظر میرسد پایگاه داده به عنوان یک مدل *Entity-Attribute-Value* طراحی شده است، چیزی که من آن را یک ضد الگو میدانم. شاید آنها در آن روزها تصور بسیار کمی در مورد ویژگیهای یک ابرقهرمان داشتند".
هارت در زمان شنیدن آخرین جمله تقریباً خم شد. با صدای کمی آهسته تر گفت: "درست میگویی. من چنین تصوری ندارم. علاوه بر این، آنها فقط دو روز به من فرصت دادند تا همه چیز را طراحی کنم. من معتقدم به معنای واقعی کلمه یک بمب هستهای در جایی تیک تاک میکند."
دهان استیو کاملاً باز بود و ساندویچش در ورودی آن یخ زده بود. هارت لبخند زد. "مطمئنا بهترین کار من نیست. زمانی که از حدود یک میلیارد ورودی عبور کرد، روزها طول میکشد تا هر نوع تحلیلی را روی آن پایگاه داده لعنتی اجرا کنیم.
سوپر بوک در عرض چند ثانیه آن را انجام میدهد، درست است؟"
استیو به آرامی سر تکان داد. او هرگز تصور نمیکرد که در وهله اول حدود یک میلیارد ابرقهرمان وجود داشته باشد.
### شکار مدلها
در اینجا اولین برش از شناسایی مدلها در سوپربوک است. به عنوان نمونه برای یک تلاش اولیه، ما فقط مدلهای اساسی و روابط آنها را در قالب یک نمودار ساده کلاسها نشان دادهایم:

بیایید یک لحظه مدلها را فراموش کنیم و در مورد اشیایی که مدل سازی میکنیم صحبت کنیم. هر کاربر یک پروفایل دارد. یک کاربر میتواند چندین نظر یا چندین پست بگذارد. یک لایک میتواند مربوط به یک ترکیب کاربر/پست باشد.
توصیه میشود نموداری کلاسی مانند این از مدلهای خود ترسیم کنید. ممکن است در این مرحله ویژگیهای کلاس وجود نداشته باشد، اما میتوانید بعداً آنها را توضیح دهید. هنگامی که کل پروژه در نمودار نشان داده میشود، جداسازی برنامهها آسان تر میشود.
اینجا چند نکته وجود دارد تا این بازنمایی را انجام بدهیم:
* اسمها معمولاً تبدیل به هویت مدلها میشود.
* مستطیلها که هر موجودیت را نشان میدهند به مدلها تبدیل میشوند.
* خطهای متصل کننده که دو جهتی هستند و سه نوع از روابط را در جنگو تعریف میکنند:
یک-به-یک , یک-به-خیلی (با کلید خارجی یا Foreign Keys پیاده سازی میشوند) و خیلی-به-خیلی
* بخشی که رابطه یک-به-خیلی را تعریف میکند در سمت **Entity-relationship model (ER-model)** قرار دارد. به عبارتی دیگر، طرف n جایی هست که کلید خارجی تعریف میشود.
نمودار کلاسها میتوانند به کدهای جنگو مانند زیر ارتباط داده شوند. (که بین چندین اپ، پخش خواهند شد):
```
class Profile(models.Model):
user = models.OneToOneField(User)
class Post(models.Model):
posted_by = models.ForeignKey(User)
class Comment(models.Model):
commented_by = models.ForeignKey(User)
for_post = models.ForeignKey(Post)
class Like(models.Model):
liked_by = models.ForeignKey(User)
post = models.ForeignKey(Post)
```
بعداً مستقیماً به **User** ارجاع نخواهیم داد، بلکه از **settings.AUTH_USER_MODEL** استفاده میکنیم. همچنین در این مرحله نگران ویژگیهای فیلد مانند **on_delete** یا **primary_key** نیستیم. به زودی به این جزئیات خواهیم پرداخت.
# تقسیم کردن فایل models.py به چندین فایل
مانند بسیاری از اجزای جنگو، یک فایل models.py بزرگ را میتوان به چندین فایل در یک پکیج تقسیم کرد. یک پکیج به صورت دایرکتوری پیاده سازی میشود که میتواند حاوی چندین فایل باشد یکی از آنها باید فایلی با نام خاص به نام `__init__.py` باشد. این فایل میتواند خالی باشد، اما باید وجود داشته باشد.
همه تعاریفی که باید در سطح پکیج نمایش داده شوند باید در `__init__.py` به صورت عمومی (global scope) تعریف شوند. به عنوان مثال، اگر models.py را به کلاسهای جداگانه تقسیم کنیم، در فایلهای مربوطه در داخل زیرشاخه مدلها مانند postable.py، post.py، و comment.py، ساختار دایرکتوری به شکل زیر خواهد بود:
models/
- comment.py
- ــinitــ.py
- postable.py
- post.py
برای اطمینان از اینکه همه مدلها به درستی فراخوانی شده اند فایل ، `__init__.py` باید خطوط زیر را داشته باشد:
```
from postable import Postable
from post import Post
from comment import Comment
```
اکنون میتوانید models.Post را مانند قبل فراخوانی کنید. هر کد دیگری که در فایل `__init__.py` باشد هنگام فراخوانی پکیج، اجرا میشود. بنابراین، این فایل، محل ایدهآلی برای تعریف مقادیر اولیه در سطح پکیج است.
# الگوهای ساختاری
این بخش شامل چندین الگوی طراحی است که میتواند به شما در طراحی و ساختار مدلهای خود کمک کند. الگوهای ساختاری ذکر شده در اینجا به شما کمک میکند تا روابط بین مدلها را به طور موثرتری درک کنید.
## الگوها — مدلهای نرمال شده
**مشکل:** به صورت ساختاری, هر کپی از مدلها، شامل دادههای تکراری هستند که باعث ناسازگاری دادهها میشود
**راه حل** مدلهای خود را از طریق نرمال سازی به مدلهای کوچکتر تقسیم کنید. این مدلها را با روابط منطقی به هم وصل کنید.
## جزییات مشکل
تصور کنید کسی جدول پست ما را (با حذف ستونهای خاص) به روش زیر طراحی کند:

امیدوارم که به اسمهای ابرقهرمانها که به صورت ناسازگار در ستون اول( و کاپیتان تمپری که صبر ندارد) آمده توجه کردهباشید.
اگه به اولین ستون نگاه کنیم, ما مطمئن نیستیم که کدام روش هجی کردن درست است، **Captain Temper** یا **Capt. Temper**. این نوعی از افزونگی داده است که ما دوست داریم توسط نرمال سازی دیتا از بین ببریم.
## جزییات راه حل
قبل از اینکه نگاهی به راه حل کاملا نرمال شده بیندازیم، اجازه دهید یک توضیح مختصر در مورد نرمال سازی پایگاه داده در زمینه مدلهای جنگو داشته باشیم.
### سه قدم در نرمال سازی
عادی سازی به شما کمک میکند تا دادهها را به طور موثر ذخیره کنید. هنگامی که مدلهای شما به طور کامل نرمالسازی شدند، دادههای اضافی نخواهند داشت و هر مدل باید حاوی دادههایی باشد که فقط از نظر منطقی به آن مرتبط هستند.
برای ارائه یک مثال سریع، اگر میخواهیم جدول پست را عادی کنیم تا بتوانیم بدون ابهام به ابرقهرمانی که آن پیام را ارسال کرده است اشاره کنیم، باید جزئیات کاربر را در یک جدول جداگانه جدا کنیم. جنگو قبلاً جدول کاربر را به طور پیش فرض ایجاد میکند. بنابراین، همانطور که در جدول زیر نشان داده شده است، فقط باید به شناسه کاربری که پیام را در ستون اول ارسال کرده است مراجعه کنید:

اکنون نه تنها مشخص است که سه پیام توسط یک کاربر ارسال شده است (با یک شناسه کاربری دلخواه)، بلکه میتوانیم با جستجوی جدول کاربر نام صحیح آن کاربر را نیز پیدا کنیم.
به طور کلی، شما مدلهای خود را به گونهای طراحی میکنید که کاملاً نرمال شده باشند و سپس به دلیل بهبود عملکرد، به طور انتخابی برخی از آنها را از حالت نرمال خارج میکنید (برای اطلاع از علت آن، به بخش بعدی در مورد عملکرد مراجعه کنید). در پایگاههای داده، **فرمهای نرمال** مجموعهای از دستورالعملها هستند که میتوان آنها را برای اطمینان از نرمالسازی جدول به کار برد. فرمهای معمولی که معمولاً یافت میشوند، فرمهای نرمال نوع اول،نوع دوم و نوع سوم هستند، اگرچه میتوانند تا پنج مرحله هم، نرمال بشوند.
در مثال بعدی,ما یک جدول را نرمال سازی میکنیم و مدلهای جنگو متناظر را میسازیم. صفحه گستردهای به نام Sightings را تصور کنید که اولین باری که فردی یک ابرقهرمان را با استفاده از قدرت یا توانایی مافوق بشری میبیند، وی را در این صفحه، فهرست میکند. هر ورودی در این صفحه گسترده، به منشاء ابرقهرمان، نوع قدرت وی و محل اولین مشاهده که شامل از جمله طول و عرض جغرافیایی است، اشاره میکند:

دیتای جغرافیای زیر از [http://www.golombek.com/locations.html](http://www.golombek.com/locations.html) به دست آمده است.
## فرم نرمال نوع اول (1NF)
- هیچ خصوصیتی(سلول) با داده تکراری وجود نداشته باشد
- یک کلید اصلی(پرایمری) به صورت یک ستون یا چندین ستونی(کامپوزیت کی) تعریف شود.
بیایید سعی کنیم صفحه گسترده خود را به یک جدول پایگاه داده تبدیل کنیم. بدیهی است که ستون **Power** ما قانون اول را زیر پا میگذارد.
جدول به روز شده در اینجا اولین فرم نرمال بودن را برآورده میکند. کلید اصلی (با علامت *) ترکیبی از **Name** و **Power** است که باید برای هر ردیف منحصر به فرد باشد:


## فرم نرمال نوع دوم (2NF)
فرم نرمال نوع دوم باید تمام شرایط فرم نرمال اول را برآورده کند. علاوه بر این، باید این شرط را برآورده کند که تمام ستونهای کلید غیر اصلی، باید به کل کلید اصلی وابسته باشند.
در جدول قبلی توجه کنید که Origin فقط به ابرقهرمان یعنی Name بستگی دارد. مهم نیست در مورد کدام Power صحبت میکنیم. بنابراین، Origin کاملاً به کلید اولیه ترکیبی - Name و Power وابسته نیست.
بیایید فقط اطلاعات مبدا را در یک جدول جداگانه به نام Origin استخراج کنیم، همانطور که در اینجا نشان داده شده است:

حالا جدول Sightings را طوری تغییر میدهیم که با فرم نرمال نوع دوم هم تطابق داشته باشد.

## فرم نرمال نوع سوم (3NF)
در فرم نرمال نوع سوم، جداول باید فرم نرمال نوع دوم را برآورده کنند و علاوه بر این باید شرایطی را داشته باشند که تمام ستونهای کلید غیراصولی باید مستقیماً به کل کلید اصلی وابسته باشند و در ضمن باید مستقل از یکدیگر باشند.
برای لحظهای به ستون **Country** فکر کنید. با توجه به **Longitude** و **Latitude**، میتوانید به راحتی ستون **Country** را استخراج کنید. حتی اگر کشوری که در آن یک ابرقدرت دیده شده است به کلید اولیه ترکیبی Name-Power وابسته است، اما فقط به طور غیرمستقیم به آنها وابسته است.
بنابراین، اجازه دهید جزئیات مکان را در جدول جداگانه کشورها به صورت زیر، جدا کنیم:

حالا جدول Sightings ما در سومین نوع نرمال سازی قرار دارد:

مانند قبل، نام ابرقهرمان را با User ID مربوطه جایگزین کرده ایم که میتواند برای ارجاع به جدول کاربر استفاده شود.
# مدلهای جنگو
حالا میتوانیم نگاه کنیم که این حدولهای نرمال سازی شده چطور میتوانند به صورت مدلهای جنگو نمایش داده بشوند. کلیدهای ترکیبی یا کامپوزیت کیها به صورت مستقیم در جنگو پشتیبانی نمیشوند.راه حل استفاده شده در اینجا اعمال کلیدهای جایگزین و مشخص کردن ویژگی *unique_together* در کلاس *Meta* است:
```python
class Origin(models.Model):
superhero = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
origin = models.CharField(max_length=100)
def __str__(self):
return "{}'s orgin: {}".format(self.superhero, self.origin)
class Location(models.Model):
latitude = models.FloatField()
longitude = models.FloatField()
country = models.CharField(max_length=100)
def __str__(self):
return "{}: ({}, {})".format(
self.country,
self.latitude,
self.longitude
)
class Meta:
unique_together = ("latitude", "longitude")
class Sighting(models.Model):
superhero = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
power = models.CharField(max_length=100)
location = models.ForeignKey(Location, on_delete=models.CASCADE)
sighted_on = models.DateTimeField()
def __str__(self):
return "{}'s power {} sighted at: {} on {}".format(
self.superhero,
self.power,
self.location.country,
self.sighted_on
)
class Meta:
unique_together = ("superhero", "power")
```
# کارایی و نرمال سازی نکردن (denormalization)
نرمال سازی میتواند بر کارایی، تأثیر منفی بگذارد. با افزایش تعداد مدلها، تعداد پیوستهای مورد نیاز برای پاسخ به یک کوئری نیز افزایش مییابد. به عنوان مثال، برای یافتن تعداد ابرقهرمانان با قابلیت Freeze در ایالات متحده، باید به چهار جدول درخواست ارسال شود. قبل از نرمال سازی، میتوانستیم همه اطلاعات را با کوئری فرستادن به یک جدول، به دست بیاوریم.
شما باید مدلهای خود را طوری طراحی کنید که دادهها نرمال نگه داشته شوند. این کار، یکپارچگی دادهها را حفظ میکند. با این حال، اگر سایت شما با مشکلات مقیاس پذیری مواجه است، میتوانید به طور انتخابی دادهها را از آن مدلها استخراج کنید تا دادههای دینرمال ایجاد کنید.
##### بهترین الگوها
*در حال طراحی نرمالسازی کنید, ولی برای بهینه سازی دی نرمالایز کنید*
به عنوان مثال، اگر تعداد مشاهدات در یک کشور خاص بسیار زیاد است، آن را به عنوان یک فیلد اضافی به مدل *Location* اضافه کنید. اکنون، میتوانید کوئریهای دیگر را با استفاده از **object-relational mapping (ORM)**، در جنگو، بر خلاف مقدار ذخیره شده، اضافه کنید.
با این حال، هر بار که یک مشاهده را اضافه یا حذف میکنید، باید این تعداد را به روز کنید. شما یا باید این محاسبه تعداد را به متد *save* در مدل Sighting اضافه کنید یا یک سیگنال اضافه کنید یا با استفاده از یک روش انجام کار ناهمزمان، محاسبات را انجام دهید.
اگر کوئری پیچیدهای دارید که چندین جدول را در بر میگیرد، مانند تعداد ابرقدرتها بر اساس کشور، ایجاد یک جدول دینرمال شده جداگانه ممکن است عملکرد را بهبود بخشد.
به طور معمول، این جدول در یک پایگاه داده دررون-حافظه یا کش سریعتر، اجرا میشود. مانند قبل، هر بار که دادههای مدلهای نرمالشده شما تغییر میکند، باید این جدول دینرمال شده را بهروزرسانی کنیم (در غیر این صورت با مشکل دوستنداشتنی Cache-Invalidation مواجه خواهید شد).
دینرمال کردن بهطور شگفتانگیزی در وبسایتهای بزرگ متداول است، زیرا تعادلی بین سرعت و فضا است. امروزه فضا ارزان است، اما سرعت برای تجربه کاربر بسیار مهم است. بنابراین، اگر پاسخ کوئری شما بیش از حد طول میکشد، ممکن است بخواهید آن را در نظر بگیرید.
# آیا همیشه باید نرمالسازی کنیم؟
نرمالسازی بیش از حد لزوماً چیز خوبی نیست. گاهی اوقات، میتواند باعث به وجود آمدن جداول غیر ضروری شود که به روز رسانیها و جستجوها را پیچیده کند.
به عنوان مثال، مدل کاربری شما ممکن است چندین فیلد برای آدرس خانه آنها داشته باشد. به طور دقیق، میتوانید این فیلدها را به یک مدل آدرس نرمال کنید. با این حال، در بسیاری از موارد، معرفی یک جدول اضافی به پایگاه داده غیر ضروری خواهد بود.
بهجای هدف نرمالسازیشدهترین طرح، هر فرصتی را برای نرمالسازی با دقت بسنجید و قبل از ایجاد آن، فواید و مضراتش را در نظر بگیرید.
# الگو — مدلهای میکسین
**مشکل:** مدلهای متمایز دارای فیلدها و/یا روشهای مشابه هستند که اصل DRY را نقض میکنند.
**راه حل:** زمینهها و روشهای مشابه و تکراری را به مدلهای میکسین قابل استفاده مجدد، تقسیم کنید.
#### جزییات مشکل
هنگام طراحی مدلها، ممکن است ویژگیها یا رفتارهای مشترک مشخصی را پیدا کنید که در کلاسهای مدل به اشتراک گذاشته شده است. به عنوان مثال، یک مدل پست و نظر باید تاریخ ایجاد و تاریخ اصلاح آن را پیگیری کند. کپی و چسباندن دستی فیلدها و روش مرتبط با آنها یک رویکرد بسیار DRY نیست.
از آنجایی که مدلهای جنگو کلاس هستند، رویکردهای شی گرا مانند ترکیب و ارث راه حلهای ممکن هستند. با این حال، ترکیبات (با داشتن یک ویژگی که حاوی نمونهای از کلاس مشترک است) برای دسترسی به فیلدها به یک سطح غیرمستقیم اضافی نیاز دارند.
ارث میتواند مشکل ساز شود. ما میتوانیم از یک کلاس پایه مشترک برای پست و نظرات استفاده کنیم.
با این حال، سه نوع ارث در جنگو وجود دارد: عینی concrete، انتزاعی abstract و پروکسی proxy.
**وراثت عینی** با ارث بری از کلاس پایه درست مانند آنچه که معمولاً در کلاسهای پایتون انجام میدهید کار میکند. با این حال، در جنگو، این کلاس پایه در یک جدول جداگانه ثبت میشود.
هر بار که به فیلدهای پایه دسترسی پیدا میکنید، به یک عملیات join نیاز است. این اتفاق منجر به افت شدید کارایی میشود.
**وراثت پراکسی** فقط میتواند رفتار جدیدی را به کلاس والد اضافه کند. شما نمی توانید فیلدهای جدید اضافه کنید. از این رو برای این وضعیت چندان مفید نیست.
در نهایت، ما با وراثت Abstract باقی میمانیم.
#### جزییات راه حل
وراثت انتزاعی یک راه حل ظریف است که از کلاسهای پایه Abstract ویژه برای به اشتراک گذاشتن دادهها و رفتار بین مدلها استفاده میکند. وقتی یک کلاس پایه انتزاعی را در جنگو تعریف میکنید، که با کلاسهای پایه انتزاعی (ABC) در پایتون یکسان نیست، هیچ جدول مربوطه در پایگاه داده ایجاد نمی کند. در عوض، این فیلدها در کلاسهای غیر انتزاعی مشتق شده ایجاد میشوند.
دسترسی به فیلدهای کلاس پایه انتزاعی نیازی به دستور *JOIN* ندارد. جداول به دست آمده نیز دارای فیلدهای مدیریت شده هستند. با توجه به این مزایا، اکثر پروژههای جنگو از کلاسهای پایه انتزاعی برای پیاده سازی فیلدها یا روشهای مشابه و تکراری استفاده میکنند.
محدودیتهای مدلهای انتزاعی به شرح زیر است:
- نمی توانند کلید خارجی یا فیلد چند به چند از مدل دیگری داشته باشند
- از آنها نمی توان نمونه (instance) تهیه کرد یا آنها را ذخیره کرد
- آنها را نمی توان مستقیماً در کوئری استفاده کرد زیرا مدیری (class manager) ندارند
در اینجا نحوه طراحی کلاسهای پست و نظرات، در ابتدا با یک کلاس پایه انتزاعی، آمده است:
```
class Postable(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
message = models.TextField(max_length=500)
class Meta:
abstract = True
class Post(Postable):
...
class Comment(Postable):
...
```
برای تبدیل یک مدل به یک کلاس پایه انتزاعی، باید *abstract = True* را در کلاس *Meta* در داخل مدل اضافه کنید. در اینجا، *Postable* یک کلاس پایه انتزاعی است. با این حال، خیلی قابل استفاده مجدد نیست.
در واقع، اگر کلاسی وجود داشته باشد که فقط فیلد *created* و *modified* را داشته باشد، میتوانیم تقریباً در هر مدلی که به مهر زمانی نیاز دارد، از آن عملکرد مهر زمانی مجدداً استفاده کنیم. در چنین مواردی، ما معمولا یک مدل میکسین را تعریف میکنیم.
##### میکسینهای مدل
میکسینهای مدل، کلاسهای انتزاعی هستند که میتوانند به عنوان کلاس والد یک مدل اضافه شوند. پایتون بر خلاف زبانهای دیگر مانند جاوا از چندین وراثت پشتیبانی میکند. از این رو، میتوانید هر تعداد کلاس والد را برای یک مدل فهرست کنید.
میکسینها باید بسیار واضح باشند و به راحتی ترکیب شوند. اگر یک میکسین را به صورت کلاسهای پایه تعریف کنید باید به درستی کار کند. از این نظر رفتار آنها بیشتر به ترکیب شبیه است تا وراثت.
میکسینهای کوچکتر بهتر هستند. هر زمان که یک میکسین بزرگ شد و اصل مسئولیت واحد را نقض کرد، آن را در کلاسهای کوچکتر تقسیم کنید. اجازه دهید یک میکسین یک کار را انجام دهد و آن را به خوبی انجام دهد.
در مثال قبلی، مدل میکسین مورد استفاده برای بهروزرسانی زمان *created* و *modified* را میتوان به راحتی فاکتور گرفت، همانطور که در کد زیر نشان داده شده است:
```
class TimeStampedModel(models.Model):
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now =True)
class Meta:
abstract = True
class Postable(TimeStampedModel):
message = models.TextField(max_length=500)
...
class Meta:
abstract = True
class Post(Postable):
...
class Comment(Postable):
...
```
ما الان دو کلاس پایه داریم. با این حال، عملکردها به وضوح از هم جدا شده است. میکسین را میتوان به عنوان یک ماژول جدا تعریف کرد و در اپهای دیگر دوباره استفاده کرد.
# الگو — پروفایلهای کاربر
**مشکل:** هر وب سایت مجموعه متفاوتی از جزئیات را در پروفایل کاربر ذخیره میکند. با این حال، مدل پیشساخته کاربر در جنگو، برای جزئیات احراز هویت در نظر گرفته شده است.
**راه حل:** یک کلاس پروفایل کاربری با یک رابطه یک به یک با مدل کاربر ایجاد کنید.
#### جزییات مشکل
جنگو، یک مدل بسیار مناسب برای تعریف کردن کاربر، ارائه میدهد. شما میتوانید از آن در هنگام ایجاد یک کاربر super user یا ورود به رابط کاربری استفاده کنید. دارای چند فیلد اساسی مانند نام کامل، نام کاربری و ایمیل است.
با این حال، اکثر پروژههای دنیای واقعی، اطلاعات بسیار بیشتری را در مورد کاربران، مانند آدرس، فیلمهای مورد علاقه یا تواناییهای ابرقدرت آنها نگه میدارند. از جنگو 1.5 به بعد، مدل کاربر پیش فرض را میتوان گسترش داد یا جایگزین کرد. با این حال، اسناد رسمی اکیداً توصیه میکنند که فقط دادههای احراز هویت را حتی در یک مدل کاربر سفارشی ذخیره کنید (این بخش مربوط به اپلیکیشن `auth` است).
پروژههای خاص به چندین نوع کاربر نیاز دارند. به عنوان مثال، سوپربوک میتواند توسط ابرقهرمانان و غیر ابرقهرمانان استفاده شود. ممکن است فیلدهای مشترک و برخی فیلدهای متمایز بر اساس نوع کاربر وجود داشته باشد.
#### جزییات راه حل
راه حل رسمی توصیه شده، ایجاد یک مدل پروفایل کاربر است. این مدل باید با مدل کاربری شما رابطه یک به یک داشته باشد. تمام اطلاعات اضافی کاربر در این مدل ذخیره میشود:
```
class Profile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True
)
```
توصیه میشود برای جلوگیری از مشکلات همزمانی در برخی از پایگاههای پشتیبان مانند PostgreSQL، مقدار `primary_key` را به طور واضح روی `True` تنظیم کنید. بقیه مدل میتواند شامل هر گونه جزئیات دیگر کاربر مانند تاریخ تولد، رنگ مورد علاقه و غیره باشد.
هنگام طراحی مدل پروفایل، توصیه میشود که تمام فیلدهای جزئیات پروفایل باید *nullable* یا *حاوی مقادیر پیش فرض* باشند. به طور شهودی، ما میتوانیم درک کنیم که یک کاربر نمی تواند هنگام ثبت نام، تمام جزئیات نمایه خود را پر کند. علاوه بر این، ما اطمینان حاصل میکنیم که کنترل کننده سیگنال در هنگام ایجاد نمونه پروفایل، هیچ پارامتر اولیهای را پاس نمی کند.
###### سیگنالها
در حالت ایده آل، هر بار که یک نمونه مدل کاربر ایجاد میشود، یک نمونه پروفایل کاربر مربوطه نیز باید ایجاد شود. اینکار معمولاً با استفاده از سیگنال انجام میشود.
برای مثال، میتوانیم سیگنال `post_save` را از مدل کاربر، با استفاده از کنترلکننده سیگنال زیر در `profiles/signals.py` گوش کنیم:
```
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from . import models
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_handler(sender, instance, created, **kwargs):
if not created:
return
# Create the profile object, only if it is newly created
profile = models.Profile(user=instance)
profile.save()
```
مدل `Profile` هیچ پارامتر اولیه اضافی به جز `user=instance` را ارسال نکرده است.
قبلاً مکان خاصی برای مقداردهی اولیه کد سیگنال وجود نداشت. به طور معمول، آنها در `models.py` فراخوانی یا پیاده سازی میشدند (که قابل اعتماد نبود). با این حال، با ویژگی `app-loading refactor` در جنگو 1.7، مکان کدهای اولیه در برنامه به خوبی تعریف شده است.
ابتدا، متد `ProfileConfig` را در فایل `apps.py` در اپ پروفایل تغییر دهید و درون متد `ready`، سیگنال را تعریف کنید:
```
# apps.py
from django.apps import AppConfig
class ProfilesConfig(AppConfig):
name = "profiles"
verbose_name = 'User Profiles'
def ready(self):
from . import signals
```
سپس در بخش `INSTALLED_APPS`، خطی که مسیر اپ را تعریف میکند به کمک آدرس دهی نقطهای به `AppConfig` متصل میکنیم. فایل تنظیمات به شکل زیر خواهد شد:
```
INSTALLED_APPS = [
'profiles.apps.ProfilesConfig',
'posts',
...
```
با تنظیم سیگنالها، دسترسی به `user.profile` باید یک شی `Profile` را از طریق هر کاربر، حتی کاربران تازه ایجاد شده، برگرداند.
##### Admin
اکنون، جزئیات یک کاربر در دو مکان مختلف در داخل ادمین خواهد بود: جزئیات احراز هویت در صفحه مدیریت معمولی کاربر، و جزئیات اضافی پروفایل همان کاربر در یک صفحه مدیریت نمایه جداگانه. این خیلی دست و پا گیر میشود.
برای راحتی، ادمین پروفایل را میتوان با تعریف یک `UserAdmin` سفارشی در `profiles/admin.py`، به صورت زیر به ادمین پیش فرض کاربر، اضافه کرد:
```
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import Profile
from django.contrib.auth.models import User
class UserProfileInline(admin.StackedInline):
model = Profile
class NewUserAdmin(UserAdmin):
inlines = [UserProfileInline]
admin.site.unregister(User)
admin.site.register(User, NewUserAdmin)
```
# گونههای مختلف پروفایل
فرض کنید به چندین نوع کاربر و پروفایلهای مربوط به آنها در برنامه خود نیاز دارید - باید یک فیلد برای ردیابی نوع پروفایل کاربر وجود داشته باشد. خود دادههای `profile` باید در مدلهای جداگانه یا یک مدل یکپارچه ذخیره شوند.
یک رویکرد تجمیعی برای `Profile` توصیه میشود زیرا انعطاف پذیری برای تغییر انواع `Profile` بدون از دست دادن جزئیات آنها را میدهد و پیچیدگی را به حداقل میرساند. در این رویکرد، مدل `Profile` شامل یک ابرمجموعه از تمام فیلدها از همه انواع `Profile` است.
برای مثال، SuperBook به یک پروفایل نوع ابرقهرمانی و یک پروفایل معمولی (غیر ابرقهرمانی) نیاز دارد. میتوان آن را با استفاده از یک مدل پروفایل یکپارچه به صورت زیر پیاده سازی کرد:
```
class BaseProfile(models.Model):
USER_TYPES = (
(0, 'Ordinary'),
(1, 'SuperHero'),
)
user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True)
user_type = models.IntegerField(max_length=1, null=True, choices=USER_TYPES)
bio = models.CharField(max_length=200, blank=True, null=True)
def __str__(self):
return "{}: {:.20}". format(self.user, self.bio or "")]
class Meta:
abstract = True
class SuperHeroProfile(models.Model):
origin = models.CharField(max_length=100, blank=True, null=True)
class Meta:
abstract = True
class OrdinaryProfile(models.Model):
address = models.CharField(max_length=200, blank=True, null=True)
class Meta:
abstract = True
class Profile(SuperHeroProfile, OrdinaryProfile, BaseProfile):
pass
```
ما جزئیات پروفایل را در چندین کلاس پایه انتزاعی گروه بندی کردیم تا موضوعات را از هم جدا کنیم. کلاس `BaseProfile` شامل تمام جزئیات پروفایل رایج، صرف نظر از نوع کاربر است. همچنین دارای یک قسمت `user_type` است که پروفایل فعال کاربر را ردیابی میکند.
کلاس `SuperHeroProfile` و کلاس `OrdinaryProfile` به ترتیب حاوی جزئیات `Profile` مخصوص کاربران ابرقهرمانی و غیرقهرمانی هستند. در نهایت، کلاس `Profile` از تمام این کلاسهای پایه برای ایجاد یک ابرمجموعه از جزئیات پروفایل مشتق میشود.
برخی از جزئیاتی که در هنگام استفاده از این روش باید رعایت شود به شرح زیر است:
- تمام فیلدهای `Profile` که متعلق به کلاس یا کلاسهای پایه انتزاعی آن هستند باید nullable یا دارای مقدار پیش فرض باشند.
- این رویکرد ممکن است فضای پایگاه داده بیشتری را به ازای هر کاربر مصرف کند، اما انعطاف پذیری فوق العادهای میدهد.
- فیلدهای فعال و غیرفعال برای نوع `Pofile` باید خارج از مدل مدیریت شوند. برای مثال، فرمی برای ویرایش نمایه باید فیلدهای مناسب را بر اساس نوع کاربر فعال فعلی نشان دهد.
# Pattern – service objects
**مشکل**: مدلها میتوانند بزرگ و غیرقابل مدیریت شوند. تست و نگهداری آنها سخت تر میشود زیرا یک مدل بیش از یک کار را انجام میدهد.
**راهحل**: مجموعهای از متدهای مرتبط یا یک مدل را در یک شیء تخصصی خدماتی به نام *Service* جای دهید.
#### جزئیات مشکل
مدلهای چاق، ویوی لاغر ضربالمثلی است که معمولاً برای مبتدیان جنگو گفته میشود. در حالت ایده آل، ویوهای شما نباید حاوی چیزی غیر از منطق برنامه باشد.
با این حال، با گذشت زمان، کدهایی که نمی توانند در جای دیگری قرار گیرند، تمایل پیدا میکنند درون مدلها قرار گیرند. به زودی، مدلها تبدیل به محل تخلیه کد میشوند.
اگر مدل شما حاوی هر یک از موارد زیر است، یک شی *Service* برای آن نیاز دارد:
1. تعامل با سرویسهای خارجی، به عنوان مثال، بررسی اینکه آیا کاربر واجد شرایط دریافت پروفایل *SuperHeroProfile* هست یا نه، به کمک یک وبسرویس.
2. کارهای کمکی که با پایگاه داده سروکار ندارند، به عنوان مثال، ایجاد یک URL کوتاه یا کپچای تصادفی برای یک کاربر
3. ساختن یک شی با عمر کوتاه بدون نیاز به پایگاه داده، به عنوان مثال، ایجاد یک پاسخ JSON برای یک تماس AJAX
4. عملکردی که چندین نمونه مدل را در بر میگیرد اما به هیچکس تعلق ندارد
5. وظایف طولانی مدت مانند وظایف Celery
مدلها در جنگو از الگوی Active Record پیروی میکنند، یعنی هر نمونه از کلاس، مربوط به یک ردیف در جدول پایگاه داده است. در حالت ایدهآل، آنها هم دسترسی به پایگاه داده و هم منطق برنامه (یا دامنه) را محصور میکنند. با این حال، منطق برنامه را در حداقل ممکن، نگه دارید.
در حین آزمایش، اگر متوجه شدیم که پایگاه داده را حتی در حالی که از آن استفاده نمیکنیم، به کار میگیریم، باید کلاس مدل را تجزیه کنیم. استفاده از یک شیء Service در چنین شرایطی توصیه میشود.
#### جزییات راه حل
اشیاء سرویس **Plain Old Python Objects (POPO)** یا اشیاء ساده قدیمی پایتون، هستند که یک سرویس یا تعاملات با یک سیستم را محصور میکنند. آنها معمولاً در یک فایل جداگانه با نام *services.py* یا *utils.py* نگهداری میشوند.
به عنوان مثال، بررسی یک وب سرویس گاهی اوقات در یک متد مدل به شرح زیر قرار میگیرد:
```
class Profile(models.Model):
...
def is_superhero(self):
url = "<http://api.herocheck.com/?q={0}>".format(
self.user.usernam
)
return webclient.get(url)
```
این متد میتواند با تغییر به یک شیء سرویس به شکل زیر بازنویسی شود:
```
from .services import SuperHeroWebAPI
def is_superhero(self):
return SuperHeroWebAPI.is_hero(self.user.username)
```
آبژکتهای سرویس میتوانند در فایلی به نام *services.py* به شکل زیر جمعآوری شوند:
```
API_URL = "<http://api.herocheck.com/?q={0}>"
class SuperHeroWebAPI:
...
@staticmethod
def is_hero(username):
url = API_URL.format(username)
return webclient.get(url)
```
در بیشتر موارد، متدهای یک شیء سرویس بدون حالت هستند، یعنی عمل را صرفاً بر اساس آرگومانهای تابع بدون استفاده از ویژگیهای کلاس انجام میدهند. از این رو، بهتر است آنها را به صراحت به عنوان متدهای استاتیک (ایستا) تعریف کنیم (همانطور که برای *is_hero* انجام دادیم).
در نظر بگیرید که منطق کسب و کار یا منطق دامنه خود را از مدلها به اشیاء خدماتی تبدیل کنید. به این ترتیب، میتوانید از آنها در خارج از برنامه جنگو نیز استفاده کنید.
تصور کنید یک دلیل تجاری وجود دارد که برخی از کاربران را بر اساس نام کاربری خود از تبدیل شدن به ابرقهرمانان، در لیست ممنوعه قرار دهید. شی سرویس ما را میتوان به راحتی برای پشتیبانی از این موضوع، تغییر داد:
```
class SuperHeroWebAPI:
...
@staticmethod
def is_hero(username):
blacklist = set(["syndrome", "kcka$$", "superfake"])
url = API_URL.format(username)
return username not in blacklist and webclient.get(url)
```
در حالت ایده آل، اشیاء سرویس، مستقل هستند. این باعث میشود که آنها را بتوان بدون به کارگرفتن، مثلاً پایگاه داده، به سادگی آزمایش کرد. آنها همچنین میتوانند به راحتی مورد استفاده مجدد قرار گیرند.
در جنگو، سرویسهای وقت گیر به صورت ناهمزمان با استفاده از صفهای وظیفه مانند Celery اجرا میشوند. به طور معمول، اقدامات شیء سرویس به عنوان وظایف Celery اجرا میشوند. چنین کارهایی را میتوان به صورت دورهای یا با تاخیر اجرا کرد.
# الگوهای بازیابی
این بخش شامل الگوهای طراحی است که با دسترسی به ویژگیهای مدل یا انجام کوئری بر روی آنها سروکار دارد. این الگوهای بازیابی میتوانند به شما در طراحی راههای بهتر برای دسترسی به اطلاعاتی که اغلبزیاد مورد استفاده هستند، کمک کنند.
#### الگو - فیلد ویژگی یا property field
**مشکل:** مدلها دارای ویژگیهای مشتق شدهای هستند که به عنوان متد پیاده سازی میشوند. با این حال، این ویژگیها نباید در پایگاه داده حفظ شوند.
**راه حل:** از دکوراتور ویژگی در چنین روشهایی استفاده کنید.
#### جزئیات مشکل
فیلدهای یک مدل، ویژگیهای هر نمونه از آن مدل را، مانند نام، نام خانوادگی، تاریخ تولد و غیره، در خود ذخیره میکنند. آنها همچنین در پایگاه داده ذخیره میشوند. با این حال، ما باید به برخی از ویژگیهای مشتق شده مانند نام کامل یا سن دسترسی داشته باشیم.
آنها را میتوان به راحتی از فیلدهای پایگاه داده محاسبه کرد، بنابراین نیازی به ذخیره جداگانه نیست. در برخی موارد، آنها فقط میتوانند یک بررسی مشروط مانند واجد شرایط بودن برای پیشنهادات بر اساس سن، امتیاز عضویت و وضعیت فعال باشند.
یک راه ساده برای پیاده سازی این فیلد، تعریف توابعی مانند get_age به شکل زیر است:
```
class BaseProfile(models.Model):
birthdate = models.DateField()
#...
def get_age(self):
today = datetime.date.today()
return (today.year - self.birthdate.year) - int(
(today.month, today.day) <
(self.birthdate.month, self.birthdate.day))
```
فراخوانی *profile.get_age()* سن کاربر را بر اساس تفاوت بین سال تولد و تاریخ امروز بر اساس سال و ماه و روز، محاسبه میکند. (یعنی اگر تولد امسال هنوز فرا نرسیده باشد، امسال به عدد سن اضافه نمیشود).
این میتواند توسط یک فراخوانی تابع احضار شود. با این حال، بسیار خواناتر (و پایتونیک) است که آن را *profile.age* نامید.
#### جزئیات راه حل
کلاسهای پایتون میتوانند با استفاده از دکوراتور `property`، یک تابع را بهعنوان یک ویژگی در نظر بگیرند. مدلهای جنگو نیز میتوانند از آن استفاده کنند. در مثال قبلی، خط تعریف تابع را با زیر جایگزین کنید:
```
@property
def age(self):
```
اکنون میتوانیم با `profile.age` به سن کاربر دسترسی پیدا کنیم. توجه داشته باشید که نام تابع نیز کوتاه شده است.
یک نقص مهم یک property این است که برای ORM نامرئی است، درست مانند متدهای تعریف شده در مدل. شما نمیتوانید آن را در یک شی `QuerySet` استفاده کنید. به عنوان مثال، این دستور کار نمیکند، `Profile.objects.exclude(age__lt=18)`. با این حال، برای ویوها یا تمپلیتها قابل مشاهده است.
در صورت نیاز به استفاده از آن در یک شیء `QuerySet`، ممکن است بخواهید از عبارت `Query` استفاده کنید. از تابع `annotate` برای اضافه کردن یک عبارت کوئری، برای استخراج یک فیلد محاسبه شده از فیلدهای موجود خود استفاده کنید.
یک دلیل خوب برای تعریف یک `property`، پنهان کردن جزئیات کلاسهای داخلی است. این موضوع به طور رسمی به عنوان **Law of Demeter (LoD)** یا **قانون دیمیتر** شناخته میشود. به بیان ساده، این قانون میگوید که شما فقط باید به اعضای مستقیم خود دسترسی داشته باشید یا فقط از یک نقطه برای دسترسی به اعضا، استفاده کنید.
به عنوان مثال، به جای دسترسی به `profile.birthdate.year`، بهتر است ویژگی `profile.birthyear` را تعریف کنید. این کار به شما کمک میکند ساختار زیربنایی فیلد *birthdate* را از این طریق پنهان کنید.
##### Best Practice
*از LoD استفاده کنید تا وقتی به یک property دسترسی پیدا میکنید فقط با یک نقطه به آن برسید.*
یک عارضه جانبی نامطلوب این قانون این است که منجر به ایجاد چندین ویژگی پوششی (wrapped properties) در مدل میشود. این موضوع میتواند مدلها را متورم کند و نگهداری از آنها را سخت کند. از این قانون برای بهبود API مدل خود استفاده کنید و هر جا که منطقی است، اتصال را کاهش دهید.
#### ویژگیهای ذخیره شده در حافظه پنهان (Cached)
هر بار که یک *property* را فراخوانی میکنیم، یک تابع را دوباره محاسبه میکنیم. اگر محاسبه گرانی است، ممکن است بخواهیم نتیجه را در حافظه پنهان نگه داریم. به این ترتیب، دفعه بعد که به *property* دسترسی پیدا کرد، مقدار *cached* برگردانده میشود:
```
from django.utils.functional import cached_property
#...
@cached_property
def full_name(self):
# Expensive operation e.g. external service call
return "{0} {1}".format(self.firstname, self.lastname)
```
مقدار *cached* به عنوان بخشی از نمونه پایتون (python instance) در حافظه ذخیره میشود. تا زمانی که نمونه وجود دارد، همان مقدار برگردانده میشود.
بهعنوان یک مکانیسم ایمن، ممکن است بخواهید اجرای *Expensiveoperation* را مجبور کنید تا اطمینان حاصل کنید که مقادیر قدیمی برنمیگردند. در چنین مواردی، یک آرگومان کلمه کلیدی مانند *cached=False* تنظیم کنید تا از بازگرداندن مقدار *cached* جلوگیری کنید.
# الگو - مدیریت سفارشی مدل (custom managers)
**مشکل:** برخی از کوئریهای مربوط به مدلها به طور مکرر و بدون رعایت اصل DRY، در سراسر کد تعریف شده و مورد دسترسی قرار میگیرند.
**راهحل:** مدیریت سفارشی را تعریف کنید تا نامهای معنیداری به پرس و جوهای رایج بدهند.
#### جزئیات مشکل
هر مدل جنگو دارای یک مدیر پیش فرض به نام **objects** است. فراخوانی `objects.all()`، تمام ورودیهای آن مدل را در پایگاه داده برمی گرداند. معمولاً ما فقط به یک زیرمجموعه از همه ورودیها علاقهمند هستیم.
ما فیلترهای مختلفی را اعمال میکنیم تا مجموعه ورودیهای مورد نیاز خود را پیدا کنیم. معیار انتخاب آنها اغلب منطق اصلی کسب و کار ما است. به عنوان مثال، ما میتوانیم پستهای قابل دسترسی برای عموم را با کد زیر پیدا کنیم:
```
public = Posts.objects.filter(privacy="public")
```
این معیار ممکن است در آینده تغییر کند. برای مثال، ممکن است بخواهیم بررسی کنیم که آیا پست برای ویرایش علامتگذاری شده است یا خیر. این تغییر ممکن است به صورت زیر باشد:
```
public = Posts.objects.filter(privacy=POST_PRIVACY.Public, draft=False)
```
با این حال، این تغییر باید در هر جایی که به یک پست عمومی نیاز است انجام شود. این میتواند بسیار خسته کننده باشد. فقط باید یک مکان برای تعریف چنین کوئریهای پرکاربرد بدون نقض قانون *repeating oneself* وجود داشته باشد.
#### جزئیات راه حل
کلاس *QuerySet* یک کلاس انتزاعی بسیار قدرتمند است. آنها تنها در صورت نیاز با تنبلی (lazily) ارزیابی میشوند. از این رو، ساخت *QuerySet* طولانیتر با روش زنجیرهای (شکلی از رابط روان) بر عملکرد آنها تأثیر نمی گذارد.
در واقع، با اعمال فیلتر بیشتر، مجموعه داده نتیجه کوچک میشود. این کار معمولا مصرف حافظه برای بهدست آمدن نتیجه را کاهش میدهد.
مدیر یک مدل (model manager)، رابط مناسب برای یک مدل برای دریافت شیء *QuerySet* است. به عبارت دیگر، آنها به شما کمک میکنند از ORM جنگو برای دسترسی به پایگاه داده زیربنایی استفاده کنید. در واقع، مدیران به عنوان پوششهای بسیار نازک در اطراف یک شیء *QuerySet* پیاده سازی میشوند. به این دو رابط یکسان توجه کنید:
```
>>> Post.objects.filter(posted_by__username="a")
[<Post: a: Hello World>, <Post: a: This is Private!>]
>>> Post.objects.get_queryset().filter(posted_by__username="a")
[<Post: a: Hello World>, <Post: a: This is Private!>]
```
مدیر پیشفرض ایجاد شده توسط جنگو، *objects*، چندین متد دارد، مانند *all*، *filter* یا *exclude* که یک *QuerySet* را برمیگرداند. با این حال، آنها فقط یک API سطح پایین برای پایگاه داده شما تشکیل میدهند.
از مدیران سفارشی برای ایجاد یک API سطح بالاتر مخصوص دامنه استفاده میشود. این نه تنها قابل خواندنتر است، بلکه کمتر تحت تأثیر جزئیات پیاده سازی قرار میگیرد. بنابراین، شما میتوانید در سطح بالاتری از انتزاع که دقیقاً با دامنه خود شما مدل شده است، کار کنید.
مثال قبلی ما برای پستهای عمومی را میتوان به راحتی به یک مدیر سفارشی به شرح زیر تبدیل کرد:
```
# managers.py
from django.db.models.query import QuerySet
class PostQuerySet(QuerySet):
def public_posts(self):
return self.filter(privacy="public")
PostManager = PostQuerySet.as_manager
```
این میانبر مناسب برای ایجاد یک مدیر سفارشی از یک شی *QuerySet*، در جنگو 1.7 ظاهر شد. برخلاف سایر روشهای قبلی، این شیء *PostManager* مانند مدیر پیشفرض *objects* قابل اتصال به کمک زنجیره کوئریها است.
گاهی اوقات منطقی است که مدیر پیش فرض *objects* را با مدیر سفارشی خود جایگزین کنیم، همانطور که در کد زیر نشان داده شده است:
```
from .managers import PostManager
class Post(Postable):
...
objects = PostManager()
```
با انجام این کار، برای دسترسی به *public_posts*، کد ما به میزان قابل توجهی به شکل زیر ساده میشود:
```
public = Post.objects.public_posts()
```
از آنجایی که مقدار بازگشتی یک *QuerySet* است، میتوان آنها را بیشتر فیلتر کرد:
```
public_apology = Post.objects.public_posts().filter(message_startswith="Sorry")
```
شیء *QuerySet* چندین ویژگی جالب دارد. در چند بخش بعدی، میتوانیم به برخی از الگوهای رایج که شامل ترکیب *QuerySet*ها هستند نگاهی بیندازیم.
##### عملیات را در QuerySets تنظیم کنید
شیء *QuerySets* مطابق با نام خود (یا بهتر بگوییم نیمه دوم نام خود) از بسیاری از ویژگیهای مجموعه ریاضی، پشتیبانی میکند. برای مثال، دو *QuerySets* را در نظر بگیرید که شامل اشیاء کاربر است:
```
>>> q1 = User.objects.filter(username__in=["a", "b", "c"])
[<User: a>, <User: b>, <User: c>]
>>> q2 = User.objects.filter(username__in=["c", "d"])
[<User: c>, <User: d>]
```
برخی از عملیات مجموعهای که میتوانید بر روی آنها انجام دهید به شرح زیر است:
- **Union:** این عملیات موارد تکراری را ترکیب و حذف میکند. استفاده از `q1 | q2` برای دریافت `[<User: a>،
<User: b>، <User: c>، <User: d>]`.
- **Intersection:** این عملیات موارد مشترک را پیدا میکند. برای دریافت `[<User: c>]` از `q1` و `q2` استفاده کنید.
- **Difference:** این عملیات عنصرهای موجود در مجموعه دوم را از مجموعه اول حذف میکند. هیچ عملگر منطقی برای این کار وجود ندارد. در عوض از `q1.exclude(pk__in=q2)` برای دریافت `[<User: a>، <User: b>]` استفاده کنید.
همین عملیات را میتوان در *QuerySets* با استفاده از اشیاء *Q* انجام داد:
```
from django.db.models import Q
# Union
>>> User.objects.filter(Q(username__in=["a", "b", "c"]) |
Q(username__in=["c", "d"]))
[<User: a>, <User: b>, <User: c>, <User: d>]
# Intersection
>>> User.objects.filter(Q(username__in=["a", "b", "c"]) &
Q(username__in=["c", "d"]))
[<User: c>]
# Difference
>>> User.objects.filter(Q(username__in=["a", "b", "c"]) &
~Q(username__in=["c", "d"]))
[<User: a>, <User: b>]
```
*تفاوت با استفاده از & (and) و ~ (نفی) اجرا میشود. اشیاء *Q* بسیار قدرتمند هستند و میتوان از آنها برای ساخت کوئریهای بسیار پیچیده استفاده کرد.*
با این حال، رفتار **Set** و **QuerySets** کاملاً یکسان نیست، **QuerySets**ها بر خلاف مجموعههای ریاضی، مرتبشده هستند. بنابراین، آنها از این نظر به ساختار داده **لیست** در پایتون، نزدیکتر هستند.
# زنجیرهسازی چندین *QuerySets*
تاکنون، *QuerySets* از همان نوع متعلق به یک کلاس پایه را با هم ترکیب کرده ایم. با این حال، ممکن است لازم باشد *QuerySets* را از مدلهای مختلف ترکیب کنیم و عملیاتی را روی آنها انجام دهیم.
به عنوان مثال، جدول زمانی فعالیت یک کاربر شامل تمام پستها و نظرات آنها به ترتیب زمانی معکوس است. روشهای قبلی ترکیب *QuerySets* کار نمی کند. یک راه حل ساده لوحانه، تبدیل آنها به لیست، الحاق و مرتب کردن آنها به شرح زیر است:
```
>>>recent = list(posts)+list(comments)
>>>sorted(recent, key=lambda e: e.modified, reverse=True)[:3]
[<Post: user: Post1>, <Comment: user: Comment1>, <Post: user: Post0>]
```
متأسفانه، این عملیات هر دو شیء تنبل *QuerySet* را ارزیابی کرده است. استفاده از حافظه برای ترکیب این دو لیست میتواند بسیار زیاد باشد. علاوه بر این، تبدیل *QuerySets* بزرگ به لیست میتواند بسیار کند باشد.
یک راه حل بسیار بهتر استفاده از iteratorها برای کاهش مصرف حافظه است. از روش *itertools.chain* برای ترکیب چند *QuerySets* به صورت زیر استفاده کنید:
```
>>> from itertools import chain
>>> recent = chain(posts, comments)
>>> sorted(recent, key=lambda e: e.modified, reverse=True)[:3]
```
هنگامی که یک *QuerySet* را ارزیابی میکنید، هزینه ورود به پایگاه داده میتواند بسیار زیاد باشد. بنابراین، مهم است که آن را تا جایی که ممکن است با انجام عملیاتی که *QuerySets* را بدون ارزیابی باز میگرداند به تأخیر بیندازید.
*تا جایی که ممکن است *QuerySets* را بدون ارزیابی نگه دارید.*
# مهاجرتها (migrations)
مهاجرتها به شما کمک میکند تا با اطمینان در مدلهای خود تغییراتی ایجاد کنید. مهاجرت مدل، در جنگو 1.7 معرفی شد، مهاجرت برای یک گردش کار توسعه روشمند، ضروری است. روند کار جدید اساساً به شرح زیر است:
1. اولین باری که کلاس مدل خود را تعریف میکنید، باید موارد زیر را اجرا کنید:
```
python manager.py makemigrations <app_label>
```
2. با این کار اسکریپتهای مهاجرت در پوشه *app/migrations* ایجاد میشود
3. دستور زیر را در همان محیط (توسعه) اجرا کنید:
```
python manager.py migrate <app_label>
```
4. این کار تغییرات مدل را در پایگاه داده اعمال میکند. گاهی اوقات، سؤالاتی برای رسیدگی به مقادیر پیش فرض، تغییر نام و غیره پرسیده میشود.
5. اسکریپتهای مهاجرت را به محیطهای دیگر انتشار دهید. به طور معمول، ابزار کنترل نسخه شما، به عنوان مثال Git، این کار را انجام میدهد. همانطور که آخرین منبع بررسی میشود، اسکریپتهای مهاجرت جدید نیز ظاهر میشوند.
6. دستور زیر را در این محیطها اجرا کنید تا تغییرات مدل اعمال شود:
```
python manager.py migrate <app_label>
```
7. هر زمان که در کلاس مدلها تغییراتی ایجاد کردید، مرحله 1 تا مرحله 5 را تکرار کنید.
اگر *app_label* را در دستورات حذف کنید، جنگو تغییرات اعمال نشده را در هر برنامه پیدا میکند و آنها را *migrate* میکند.
# خلاصه
درست طراحی کردن مدل، سخت است. با این حال، برای توسعه با جنگو، این بخش، یک مرحله اساسی است. در این فصل به چندین الگوی رایج هنگام کار با مدلها نگاه کردیم. در هر مورد، ما به تاثیر راه حل پیشنهادی و مبادلات مختلف نگاه کردیم.
در فصل بعد، الگوهای طراحی رایجی را که هنگام کار با نماها و تنظیمات URL با آن مواجه میشویم، بررسی خواهیم کرد.
================================================
FILE: 04-Views-and-URLs/README.md
================================================
# ویوها و URLها
در این فصل ما در مورد مباحث زیر بحث خواهیم کرد:
- ویوهای مبتنی بر کلاس و مبتنی بر تابع
- میکسینها
- دکوراتورها
- الگوهای مرسوم ویو
- طراحی کردن URLها
- کار کردن با ریاکت و دیگر فرانتاندهای جاوا اسکریپت
## نگاهی به ویو از بالا
در جنگو، ویوها به عنوان فراخوانی کننده تعریف میشوند که درخواستها را میپذیرند و پاسخها را برمیگردانند. ویوها معمولاً یک تابع یا کلاسی به همراه متود کلاسی مخصوص مثل ```()as_view``` هستند.
در هر دو مورد ما یک تابع پایتون معمولی میسازیم که ```HTTPRequest``` را به عنوان آرگومان اول میگیرد و ```HTTPResponse``` را به عنوان پاسخ برمیگرداند.
یک ```پیکربندی URL یا (URLConf)``` نیز میتواند به عنوان آرگومان اضافه به این تابع فرستاده شود. این آرگومانها میتوانند از، بخشی از URL گرفته شوند و یا مقدار آن به صورت پیشفرض معین شده باشد.
نمونه یک ویوی ساده به شکل زیر است:
```python
# In views.py
from django.http import HttpResponse
def hello_fn(request, name="World"):
return HttpResponse("Hello {}!".format(name))
```
هر دو خط تابع ویو ما به قدری ساده است که راحت میشود آن را متوجه شد. در حال حاضر ما هیچ کاری با آرگومانهایی که با درخواست فرستاده شدهاند نداریم. برای بهتر فهمیدن کانتکس که در کدام ویو صدا زده شده است، میتوانیم درخواست را بررسی کنیم به طور مثال با نگاه کردن به پارامترهای ```GET/POST```، مسیر URI یا هدرهای HTTP مانند ```REMOTE_ADDR```.
تنظیم نقشه مسیرها در ```پیکربندی URL``` به صورت سنتی است که از عبارات منظم استفاده میشود و نمونه آن به صورت زیر است:
```python
# In urls.py
url(r'^hello-fn/(?P<name>\w+)/$', views.hello_fn),
url(r'^hello-fn/$', views.hello_fn),
```
برای پشتیبانی کردن از دو الگوی URL میتوانیم از همان ویو مجدداً استفاده کنیم. الگوی اول یک نام را به عنوان آرگومان میگیرد. الگوی دوم هیچ آرگومانی را از URL نمیگیرد و تابع ویو از مقدار پیشفرض معین شده «World» برای نام را استفاده میکند.
وقتی شما از سینتکس مسیریابی ساده شده که در جنگو 2.0 معرفی شد استفاده میکنید، ارسال پارامترها به طور یکسان کار میکند.پس شما نگاشت معادل آن را میتوانید در ```viewschapter/urls.py``` پیدا کنید:
```python
# In urls.py
path('hello-fn/<str:name>/', views.hello_fn),
path('hello-fn/', views.hello_fn),
```
ما در ادامه کتاب از سینتکس ساده شده استفاده میکنیم که خواندن آن راحتتر است.
### ویوهایی که کلاسیتر هستند
ویوهای مبتنی بر کلاس در جنگو 1.4 معرفی شدند. در اینجا ما معادل تابع ویو قبلی را که دیدیم برای ویو مبتنی بر کلاس بازنویسی کردهایم:
```python
from django.views.generic import View
class HelloView(View):
def get(self, request, name="World"):
return HttpResponse("Hello {}!".format(name))
```
در اینجا نیز متناظر با قبل در ```پیکربندی URL``` ما دو خط داریم که در زیر آمده است:
```python
# In urls.py
path('hello-cl/<str:name>/', views.HelloView.as_view()),
path('hello-cl/', views.HelloView.as_view()),
```
چندین تفاوت جالب بین ویوهای کلاسی و ویوهای تابعی وجود دارد. این که ما نیاز داریم اول کلاس را تعریف کنیم خیلی واضح است و بعد از آن باید صراحتاً فقط درخواستهای ```GET``` را مدیریت کنیم. در ویو تابعی قبلی برای متود ```POST ،GET``` یا دیگر عملکردهای HTTP همان پاسخ را دریافت میکنیم. همانطور که در دستورهای زیر از کلاینت در شل جنگو استفاده میکنیم:
```python
>>> from django.test import Client
>>> c = Client()
>>> c.get("http://0.0.0.0:8000/hello-fn/").content
b'Hello World!'
>>> c.post("http://0.0.0.0:8000/hello-fn/").content
b'Hello World!'
>>> c.get("http://0.0.0.0:8000/hello-cl/").content
b'Hello World!'
>>> c.post("http://0.0.0.0:8000/hello-cl/").content
Method Not Allowed (POST): /hello-cl/
b''
```
توجه کنید که متود ```POST``` به جای اینکه در سکوت نادیده گرفته شود دیگر غیرمجاز است. صریح بودن از نقطه نظر امنیت و نگهداری ویو خوب است.
مهمترین مزیت استفاده از کلاس این است که هنگام شخصی سازی ویو میتوانیم راحتتر آن را انجام دهیم. شما میتوانید یک کلاس عمومی ویو برای خوش آمدگویی بنویسید و خوش آمدگوییهای اختصصاصی خود را نیز از آن استخراج کنید مثل زیر:
```python
class GreetView(View):
greeting = "Hello {}!"
default_name = "World"
def get(self, request, **kwargs):
name = kwargs.pop("name", self.default_name)
return HttpResponse(self.greeting.format(name))
class SuperVillainView(GreetView):
greeting = "We are the future, {}. Not them. "
default_name = "my friend"
```
پس ```پیکربندی URL``` نشأت گرفته از کلاس به صورت زیر است:
```python
# In urls.py
path('hello-su/<str:name>/', views.SuperVillainView.as_view()),
path('hello-su/', views.SuperVillainView.as_view()),
```
در صورتی که شما بخواهید چند آرگومان کلمه کلیدی با مقادیر پیشفرضشان را به تابع ویو اضافه و شخصی سازی کنید در شیوه مشابه ممکن نیست و این میتواند غیر قابل مدیریت باشد.این دقیقاً همان دلیلی است که ویوهای عمومی، از تابعهای ویو به ویوهای مبتنی بر کلاس مهاجرت کردند.
**جنگو رها شده(داستان)**
استیو بعد از صرف دو هفته زمان برای شکار کردن یه توسعه دهنده خوب جنگو شروع به فکر کردن خلاقانه و متفاوت کرد. متوجه موفقیت بزرگشان در رویداد هکاتون شد، او و هارت یک مسابقه *جنگوی رها شده* را در S.H.I.M سازماندهی کردهاند. قوانین آنها بسیار ساده است: ساختن یک وب اپلیکیشن در یک روز. این ممکنه ساده باشد ولی شما نمیتوانی یک روز را رد کنی یا زنجیر را بشکنی. هر کسی که طولانیترین زنجیر را بسازد برنده است.
برنده، برد زانی(Brad Zanni) یک سوپرایز واقعی بود. اون یک طراح سنتی بود که هیچ سررشتهای از برنامه نویسی نداشت. اون فقط یک بار در کلاس آموزشی یک هفتهای جنگو فقط برای ضربه زدن شرکت کرده بود. اون یک زنجیره ناگسستنی از 21 سایت جنگو که همه از صفر ساخته شده بودند را مدیریت کرد.
استیو برای روز بعد از همان روز با او برای ساعت 10 در دفترش قرار ملاقاتی گذاشت. اگر چه برد نمیدانست که در فرآیند استخدام قرار گرفته است. در زمان مقرر شده ضربه آرامی شنیده شد و پسری لاغر و ریشو که اواخر بیست سالگی بود وارد شد. همانطور که آنها صحبت میکردند، برد هیچ تظاهری به واقعیت نکرد که یک برنامهنویس نیست. در واقع برای او هیچ تظاهر کردنی نبود. با چشمان آرام آبیش به عینک ضخیمش نگاهی انداخت و توضیح داد که رازش خیلی ساده بوده است؛ الهام بگیر و تمرکز کن.
او روزش را با یک وایرفریم(طرح اولیه) ساده شروع میکرد. بعد میخواست یک پروژه خام جنگو را با قالب بوت استرپ توئیتر بسازد. او ویوهای عمومی مبتنی بر کلاس جنگو را پیدا کرد که راهی عالی برای کد نوشتنی بدون سختی، برای ساختن ویوها بود. بعضی اوقات او از یک یا دوتا از میکسینهای جنگو استفاده میکرد و همچنین عاشق رابط پنل مدیریت جنگو برای اضافه کردن داده در هنگام کار کردن بود.
پروژه مورد علاقهاش Labyrinth بود؛ یک هانی پات که به فروم بیس بال مبدل شده بود. او حتی توانست چندین ربات نظارتی که در حال شکار سایتهای آسیب پذیر بودند را نیز به دام بیاندازد. وقتی که استیو درباره پروژه سوپر کتاب به او توضیح داد، اون خیلی بیشتر خوشحال شد که این پیشنهاد را قبول کند. ایده ساخت شبکه اجتماعی میان ستارهای واقعاً او را مجذوب خود کرد. با کمی گشت و گذار بیشتر، استیو میتوانست چند ده پروفایل جذاب مثل برد را در S.H.I.M پیدا کند. او یاد گرفت که در وهله اول به جای گشتن در خارج از مجموعه، بهتر است که اول، داخل سازمان را جستجو کند.
## ویوهای عمومی مبتنی بر کلاس
ویوهای عمومی مبتنی بر کلاس، ویوهای مرسومی برای پیاده سازی کردن به شیوه شئ گرا (مخصوصاً متود الگوی قالب) برای استفاده مجدد بهتر هستند. من از اصطلاح ویوهای عمومی متنفرم و ترجیح میدهم آنها را **ویوهای استاک** صدا بزنم، مثل عکسهای استوک. شما میتوانید با کمی تغییر و تحول برای اکثر کارهای مرسومی که نیاز دارید از آنها استفاده کنید.
ویوهای عمومی به این دلیل ساخته شدند که توسعه دهندههای جنگو احساس میکردند دارند همان نوع ویوها را در هر پروژهای دوباره میسازند. تقریباً هر پروژه نیاز به یک صفحه داشت که لیستی از اشیاء(```ListView```)، جزیئات یک شئ(```DetailView```) یا فرمی برای ساختن یک شئ(```CreateView```) را نشان دهند. به دلیل اصل DRY(خودت را تکرار نکن)، این ویوهای با قابلیت استفاده مجدد با جنگو همراه شدند.
جدول مناسبی از ویوهای عمومی در جنگو 2.0 در زیر آمده است:
| توضیحات | نام کلاس | نوع کلاس |
| :---: | :---: | ---: |
|این ویو پدر تمام ویوها است که درستی(سلامت عقل) و ارسال(اعزام) را بررسی میکند.|View|پایه(base)|
|این ویو از قالب رندر میگیرد و کلمات کلیدی ```پیکربندی URL``` را در درون کانتکس قرار میدهد.|TemplateView|پایه(base)|
|این ویو هر درخواست ```GET``` را ریدایرکت میکند.|RedirectView|پایه(base)|
|این ویو از آیتمهای قابل تکرار مثل ```queryset``` رندر میگیرد.|ListView|لیست(List)|
|این ویو، از آیتم بر اساس ```pk(کلید اصلی)``` یا ```slug(آدرس مخصوص آن آیتم)``` در ```پیکربندی URL``` رندر میگیرد.|DetailView|جزئیات(Detail)|
|این ویو از فرم رندر میگیرد و آن را پردازش میکند.|FormView|ویرایش(Edit)|
|این ویو از فرم رندر میگیرد و آن را برای ساخت یک شئ جدید پردازش میکند.|CreateView|ویرایش(Edit)|
|این ویو از فرم رندر میگیرد و آن را برای ویرایش کردن یک شئ پردازش میکند.|UpdateView|ویرایش(Edit)|
|این ویو از فرم رندر میگیرد و برای حذف کردن یک شئ آن را پردازش میکند.|DeleteView|ویرایش(Edit)|
|این ویو لیستی از اشیاء را با فیلد ```تاریخ``` رندر میگیرد که آخرین شئ در اول قرار میگیرد و به همین ترتیب. |ArchiveIndexView|تاریخ(Date)|
|این ویو لیستی از اشیاء را با فیلد ```سال``` که توسط ```پیکربندی URL``` به آن داده میشود، رندر میگیرد.|YearArchiveView|تاریخ(Date)|
|این ویو لیستی از اشیاء را با فیلد ```سال``` و ```ماه``` رندر میگیرد.|MonthArchiveView|تاریخ(Date)|
|این ویو لیستی از اشیاء را با فیلد ```سال``` و شماره ```هفته``` رندر میگیرد.|WeekArchiveView|تاریخ(Date)|
|این ویو لیستی از اشیاء را با فیلد ```سال، ماه``` و ```روز``` رندر میگیرد.|DayArchiveView|تاریخ(Date)|
|این ویو لیستی از اشیاء که تاریخ آنها، امروز است را رندر میگیرد.|TodayArchiveView|تاریخ(Date)|
|این ویو شئای را که با فیلدهای ```سال، ماه``` و ```روز``` که بر اساس ```pk(کلید اصلی)``` یا ```slug(آدرس مخصوص آن آیتم)``` مشخص شده است را رندر میگیرد.|DateDetailView|تاریخ(Date)|
|این ویو فرم ورود را رندر میگیرد و فرآیند وارد شدن را مدیریت میکند.|LoginView|احراز هویت(Auth)|
|این ویو کاربرانی که قبلاً وارد شده اند و هنوز از حساب خود خارج نشدهاند را خارج کرده و پیام **شما خارج شدید** را به آنها نشان میدهد.|LogoutView|احراز هویت(Auth)|
|این مجموعهای از شش ویو است که جریان کار فراموشی رمز عبور و تغییر آن را مدیریت میکند.|Password*View|احراز هویت(Auth)|
ما کلاسهای پایه مثل ```BaseDetailView``` یا میکسینها مانند ```SingleObjectMixin``` را اینجا ذکر نکردیم. آنها به عنوان کلاسهای پدر طراحی شدهاند و در بیشتر موارد، شما از آنها به صورت مستقیم استفاده نمیکنید.
من قویاً توصیه میکنم که مناسبترین ویوی عمومی را انتخاب کنید. به طور مثال به جای استفاده از ```ListView``` میتوانید همان ویو را با استفاده از ```TemplateView``` پیاده سازی کنید یا حتی ```View```. هر چند که اینطور شما اکثر مزیتهای استفاده کردن از ویوهای عمومی را از دست میدهید.
پس خودتان را با این جدول آشنا کنید و ویو عمومی که با توجه به نیازتان، بیشترین تطابق را دارد انتخاب کنید. بهترین منبع برای ویوهای عمومی مبتنی بر کلاس درجه یک به آدرس <http://ccbv.co.uk/> است(اکثر توسعه دهندگان جنگو این آدرس را بخاطر دارند). شما تمام ویژگیها و متودهای هر یک از ویوهایی که در اینجا ذکر شد را پیدا خواهید کرد.
### ویوهای مبتنی بر کلاس همیشه عمومی ویوهای مبتنی بر کلاس نیستند
اکثر افراد بین ویوهای مبتنی بر کلاس با ویوهای عمومی مبتنی بر کلاس گیج میشوند. اسمهای آنها بهم شبیه است ولی یکی نیستند. این منجر به برخی ```تصورات غلط(misconceptions)``` شده که در زیر آمده است:
- **فقط ویوهای عمومی هستند که با جنگو همراه شدهاند**: خوشبختانه این اشتباه است. هیچ جادوی خاصی در ویوهای عمومی مبتنی بر کلاس نیست که ارائه شده باشد.
شما آزادید که مجموعه ویوهای عمومی مبتنی بر کلاس خود را منتشر کنید همچنین میتوانید از کتابخانههای واسط مثل```django-vanilla-views```(<http://django-vanilla-views.org/>) فلان استفاده کنید که پیادهسازی سادهتری نسبت به ویوهای عمومی استاندارد دارند. به خاطر داشته باشید که استفاده از ویوهای عمومی شخصیسازی شده ممکن است که کد شما را برای بقیه ناآشنا کند.
- **ویوهای مبتنی بر کلاس همیشه باید از ویوهای عمومی استخراج شوند**: دوباره میگوییم، هیچ چیز جادویی برای ویوهای عمومی مبتنی بر کلاس وجود ندارد. اگر چه 90 درصد اوقات، شما کلاس عمومی مثل ```View``` را پیدا میکنید که برای استفاده به عنوان کلاس پایه ایدهآل است. شما آزادید که ویژگیهای مشابه خودتان را پیادهسازی کنید.
## میکسینهای ویو
میکسینها ذات کدهای DRY در ویوهای مبتنی بر کلاس هستند. میکسینهای ویو نیز مثل میکسینهای مدل، از مزیت ارثبری چندگانه پایتون برای استفاده مجدد تکههای عملکرد استفاده میکنند. آنها اغلب کلاسهای بدون پدر در پایتون 3 هستند(یا اگر چه اینها کلاسهای سبک جدید هستند از کلاس ```شئ(object)``` در پایتون 2 استخراج شدهاند).
میکسینها، پردازشهای ویو را که در جای خوب تعریف شده باشند پیگیری میکنند. به طور مثال اکثر ویوهای عمومی از ```get_context_data``` استفاده میکنند تا دیکشنری کانتکس را با آن تنظیم کنند. کلاس مشتق شده یا میکسینها میتوانند متغیر کانتکس اضافه را به آن اضافه کند. برای مثال ```فید(feed)``` حاوی فیدهای کاربران درباره پستها است. در زیر میکسین آن که ممکن است چگونه باشد آمده است:
```python
class FeedMixin:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["feed"] = models.Post.objects.viewable_posts(
self.request.user)
return context
```
متود ```get_context_data``` در ابتدا کانتکسهای همنام خود را در تمامی کلاسهای پایه فراخوانی کرده و تجمیع میکند. بعد مقدار دیکشنری کانتکس را با متغیر ```feed``` بروزرسانی میکند.
حالا با قرار گرفتن آن در لیست کلاس های پایه میتوان از این میکسین برای افزودن فید کاربر استفاده کرد. اگر سوپر کتاب به یک صفحه اصلی شبکه اجتماعی معمولی با یک فرم برای ساختن یک پست بر اساس فید شما نیاز دارد، میتواند از این میکسین که در زیر آمده است استفاده کند:
```python
class MyFeed(FeedMixin, generic.CreateView):
model = models.Post
template_name = "myfeed.html"
success_url = reverse_lazy("my_feed")
```
یک میکسین که خوب نوشته شده باشد الزامات خیلی کمی دارد. باید انقدر انعطاف پذیر باشد که در اکثر موقعیتها مفید واقع شود. در مثال قبل، ```FeedMixin``` متغیر کانتکس ```feed``` را در کلاس مشتق شده بازنویسی خواهد کرد. اگر یک کلاس پدر از متغیر کانتکس ```feed``` استفاده کند میتواند روی میکسین باعث ایجاد نقص شود. از این رو خیلی مفیدتر خواهد بود اگر یک متغیر کانتکس شخصی سازی شده جدید را بسازد مثل زیر:
```python
class FeedMixin(object):
feed_context_name = "feed"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context[self.feed_context_name] =
models.Post.objects.viewable_posts(self.request.user)
return context
```
توانانی ترکیب کردن میکسینها با بقیه کلاسها نیز برایشان هم بزرگترین مزیت است و هم بزرگترین عیب. اشتباه ترکیب کردن آنها میتواند به نتایج عجیب و غریب منجر شود. پس قبل از استفاده از میکسین نیاز دارید اطمینان حاصل کنید که سورس کد میکسین و بقیه کلاسها، درگیریای با متودها یا متغیرهای کانتکس نداشته باشند.
### ترتیبی از میکسینها
شما ممکن است با کدهایی مواجه شده باشید که چندین میکسین به صورت زیر داشتهاند:
```python
class ComplexView(MyMixin, YourMixin, AccessMixin, DetailView):
```
پی بردن به ترتیب لیست کلاسهای پایه میتواند بسیار مشکل باشد. مثل اکثر چیزها در پایتون، قوانین عادی پایتون اعمال میشود. **Method Resolution Order (MRO)** پایتون معین میکند که آنها چطور باید مرتب شوند.
مخلص کلام این است که میکسینها اول بیایند و کلاسهای پایه، آخر. هر چی کلاس پدر تخصصیتر باشه باید چپ تر قرار بگیرد. در عمل این تنها قانونی است که باید یادتان بماند.
برای اینکه متوجه شوید چرا اینطوری کار میکند به مثال ساده زیر توجه کنید:
```python
class A:
def do(self):
print("A")
class B:
def do(self):
print("B")
class BA(B, A):
pass
class AB(A, B):
pass
BA().do() # Prints B
AB().do() # Prints A
```
همانطور که شما انتظار دارید اگر در لیست کلاسهای پایه ```B``` قبل از ```A``` ذکر شده بود، متود ```B``` صدا زده میشد و بالعکس.
حالا تصور کنید که ```A``` کلاس پایهای مثل ```CreateView``` باشد و ```B``` میکسینی مثل ```FeedMixin```. یک میکسین عملکرد پایهای یک کلاس پایه را بیش از پیش افزایش میدهد. از این رو باید کد میکسین اول عمل کند و به نوبه خود اگر نیاز بود کلاس پایه هم صدا زده شود. پس ترتیب درست، ```BA```(میکسین اول، پایه آخر) است.
ترتیب اینکه مشخص کنیم چطور کلاسهای پایه را صدا بزنیم میتواند با چک کردن ویژگی ```__mro__``` کلاس بررسی شود:
```python
>>> AB.__mro__
(<class 'AB'>, <class 'A'>, <class 'B'>, <class 'object'>)
```
پس اگر ```AB``` متود ```()super``` را صدا بزند، اول ```A``` فراخوانی میشود؛ بعد متود ```()super``` کلاس ```A```، کلاس ```B``` را صدا خواهد زد و به همین ترتیب.
**نکته(TIP)**
معمولاً MRO پایتون به ترتیب در مرحله اول، از عمق شروع میکند و در مرحله دوم از چپ به راست را، برای انتخاب متود در سلسله مراتب کلاسها دنبال میکند. جزئیات بیشتر میتواند در <http://www.python.org/download/releases/2.3/mro/> پیدا شود.
## دکوراتورها
قبل از ویوهای مبتنی بر کلاس، دکوراتورها تنها راه برای تغییر رفتار ویوهای مبتنی بر تابع بودند. از آنجایی که اطراف یک تابع قرار دارند، نمیتوانستند عملکرد ویو را تغییر دهند و بنابراین به طور موثر با آن، مثل جعبهای که از درونش خبر ندارند رفتار میکردند.
یک ```دکوراتور(Decorator)``` تابعی است که تابعی را میگیرد و یک تابع دکوراتور شده را برمیگرداند. گیج شدید؟ یک سری کد وجود دارد که به شما کمک میکند این مسئله را بهتر متوجه شوید. استفاده از علامت ```@``` برای نشان دادن دکوراتور است، همانطور که در زیر دکوراتور ```login_required``` نشان داده شده است:
```python
@login_required
def simple_view(request):
return HttpResponse()
```
کدی که در ادامه آمده دقیقاً همان کد قبلی است:
```python
def simple_view(request):
return HttpResponse()
simple_view = login_required(simple_view)
```
از آنجایی که ```login_required``` اطراف ویو قرار گرفته است، تابع اطراف گیرنده(wrapper)، کنترل تابع را بدست میگیرید. اگر کاربری وارد نشده باشد به ```settings.LOGIN_URL``` ریدایرکت میشود. در غیر اینصورت تابع ```simple_view``` را اجرا میکند انگار که اصلاً وجود نداشته است.
دکوراتورها انعطاف کمتری نسبت به میکسینها دارند هرچند سادهترند. شما میتوانید از هر دو آنها یعنی دکوراتورها و میکسینها استفاده کنید. در حقیقت، تعداد زیاد از میکسینها با دکوراتورها پیادهسازی شدهاند.
## الگوهای ویو
بیاید چندتا از الگوهای طراحی دیده شده در طراحی ویو را ببینیم.
### الگو - دسترسی به ویوهای کنترل شده
**مسئله**: صفحات نیاز دارند با شروطی قابل دسترسی باشند چه کاربر وارد شده باشد، چه عضو باشد چه کارمند یا هر شرط دیگری.
**راه حل**: استفاده از میکسینها یا دکوراتورها برای کنترل دسترسی به ویو.
#### جزئیات مسئله
اکثر وب سایتها صفحاتی دارند که فقط اگر شما وارد شده باشید میتوانید به آن دسترسی پیدا کنید. مسلماً بقیه صفحات در دسترس افراد ناشناس یا بینندههای عمومی است. اگر بینندههای ناشناس بخواهند تلاش کنند که به صفحاتی که باید کاربر وارد شده باشد دسترسی پیدا کنند، به صفحه ورود منتقل میشوند. در حالت ایدهآل به این صورت است که بعد از وارد شدن دوباره به همان صفحه قبلی که میخواستند آن را ببینند منتقل شوند.
به طور مشابه صفحاتی هستند که مسلماً فقط نوع خاصی از کاربران میتوانند آن را ببینند. به طور مثال پنل مدیریت جنگو فقط برای کارمندان قابل دسترس است. اگر افراد غیر کارمند تلاش کنند که به صفحات مدیریت دسترسی پیدا کنند، به صفحه ورود منتقل میشوند.
در نهایت، صفحاتی هستند که اگر فقط دسترسی دیدن آنها به ما اعطا شده باشد میتوانیم آنها را ببینیم. برای مثال توانایی ویرایش کردن یک نوشته باید فقط برای نویسنده آن قابل دسترس باشد. هر کس دیگری که بخواهد به این صفحه دسترسی پیدا کند باید خطای **دسترسی غیرمجاز(Permission Denied)** را ببیند.
#### جزئیات راه حل
دو راه برای کنترل دسترسی ویو وجود دارد:
1. به وسیله استفاده از دکوراتورها روی ویوهای مبتنی بر تابع یا ویوهای مبتنی بر کلاس:
```python
@login_required(MyView.as_view())
```
2. به وسیله بازنویسی کردن متود ```dispatch``` ویوهای مبتنی بر کلاس از طریق میکسین:
```python
from django.utils.decorators import method_decorator
class LoginRequiredMixin:
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
```
3. ما واقعا اینجا نیازی به دکوراتورها نداریم. به شما توصیه میشود که از حالت خیلی صریحتر زیر استفاده کنید:
```python
class LoginRequiredMixin:
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
```
وقتی شما خطای ```دسترسی غیر مجاز(Permission Denied)``` را مطرح کنید. جنگو قالب ```403.html``` در دایرکتوری ریشه شما را نمایش میدهد یا در صورت نبود آن، صفحه استاندارد **403 ممنوع(403 Forbidden)** را نمایش میدهد.
البته شما به مجموعهای از میکسینهای قدرتمند و شخصی سازی شده برای پروژههای واقعی نیاز دارید. پکیج ```django-braces``` (<https://github.com/brack3t/django-braces>) مجموعه عالی از میکسینها مخصوصاً برای کنترل کردن دسترسی به ویوها دارد.
در اینجا مثالی از استفاده آن برای کنترل دسترسی به ویو برای کاربران وارد شده و ناشناس آورده شده است:
```python
from braces.views import LoginRequiredMixin, AnonymousRequiredMixin
class UserProfileView(LoginRequiredMixin, DetailView):
# This view will be seen only if you are logged-in
pass
class LoginFormView(AnonymousRequiredMixin, FormView):
# This view will NOT be seen if you are loggedin
authenticated_redirect_url = "/feed"
```
جنگو ```LoginRequiredMixin``` از آدرس ```django.contrib.auth.mixins``` را با پیاده سازی خودش آماده کرده است اما میکسینی برای محدود کردن ویو فقط برای کاربران ناشناس آماده نکرده است.
کاربران کارمند در جنگو فقط کاربرانی هستند که پرچم ```is_staff``` آنها در مدل کاربر تنظیم شده است. شما میتوانید میکسین پیش ساخته ```UserPassesTestMixin``` را صدا بزنید و استفاده کنید که در زیر مثال آن آمده است:
```python
from django.contrib.auth.mixins import UserPassesTestMixin
class SomeStaffView(UserPassesTestMixin, TemplateView):
def test_func(self, user):
return user.is_staff
```
شما همچنین میتوانید میکسینهای خودتان را بسازید که بررسیهای به خصوصی را انجام دهد مانند اینکه شئای توسط نویسندهاش ویرایش میشود یا خیر(به وسیله مقایسهاش با یوزرهای وارد شده):
```python
class CheckOwnerMixin:
# To be used with classes derived from SingleObjectMixin
def get_object(self, queryset=None):
obj = super().get_object(queryset)
if not obj.owner == self.request.user:
raise PermissionDenied
return obj
```
توصیه میشود تا حد امکان به کاربران کمترین امتیاز برای اشیاء داده شود. به این اصل، **اصل حداقل امتیاز(Principle of least privilege)** گفته میشود. به عنوان بهترین شیوه، حتما مطمئن شوید که کدام کاربران یا گروهها به جای دسترسی پیشفرضی که دارند، مطمئناً میتوانند چه کارهایی روی اشیاء انجام دهند.
### الگو - بهبود دهندههای کانتکس
**مسئله**: چندین ویو بر اساس ویوهای عمومی نیاز به همان متغیر کانتکس دارند.
**راه حل**: ساخت یک میکسین که مجموعههایی از متغیر کانتکس را به اشتراک بگذارد.
#### جزئیات مسئله
قالبهای جنگو فقط میتوانند متغیرهایی را نمایش دهند که در دیکشنری کانتکس حاضر باشند. هرچند سایتها نیاز دارند که همان اطلاعات در چندین صفحه باشد. برای نمونه، سایدباری که پستهای اخیر را در فید شما نمایش میدهد ممکن است در چندین ویو نیاز باشد.
هرچند اگر از ویو عمومی مبتنی بر کلاس استفاده کنیم، فقط میتوانیم مجموعه محدودی از متغیرهای کانتکس مربوط به آن مدل به خصوص استفاده کنیم. تنظیم کردن همان متغیر کانتکس در هر ویو پیروی کردن از اصل DRY نیست.
#### جزئیات راه حل
اکثر ویوهای عمومی مبتنی بر کلاس از ```ContextMixin``` مشتق شدهاند که متود ```get_context_data``` را آماده کرده است و اکثر کلاسها آن را بازنویسی میکنند و متغیرهای کانتکس خودشان را به آن اضافه میکنند. درحالی که بهترین شیوه بازنویسی این متود است؛ شما اول نیاز دارید ```get_context_data``` سوپر کلاس را صدا بزنید و بعد متغیرهای کانتکس خودتان را اضافه یا بازنویسی کنید.
ما میتوانیم این حالت از میکسینها را همانطور که در قبل دیدیم انتزاع کنیم:
```python
class FeedMixin(object):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["feed"] = models.Post.objects.viewable_posts(
self.request.user)
return context
```
ما میتوانیم این میکسین را به ویوهامان اضافه کرده و از متغیرهای کانتکس اضافه شده نیز در قالبمان استفاده کنیم. به خاطر داشته باشید که ما از مدیر مدل(model manager) تعریف شدۀ [فصل 3]('../../../03-%20Models/README.md) *مدلها*، برای فیلتر کردن پستها استفاده میکنیم.
راه حل خیلی عمومیتر استفاده از میکسین ```StaticContextMixin``` از پکیج ```django-braces``` برای متغیرهای کانتکس ایستا است. به طور مثال ما میتوانیم متغیر کانتکس ```latest_profile``` را که آخرین کاربری که به اضافه شده است، را اضافه کنیم:
```python
class CtxView(StaticContextMixin, generic.TemplateView):
template_name = "ctx.html"
static_context = {"latest_profile": Profile.objects.latest('pk')}
```
در اینجا ```static_context``` به معنای هرچیزی است که از یک درخواست تا به درخواست دیگر تغییری نمیکند. به این معنا که شما میتوانید مجموعههایی از پرس و جوها(```Querysets```) را به خوبی ذکر کنید. هر چند متغیر کانتکس فید ما به ```self.request.user``` نیاز دارد تا پستهای قابل دیدن کاربر را بازیابی کند. از این رو نمیتواند به عنوان کانتکس ایستا در اینجا استفاده شود.
متقابلاً اگر کانتکس اشتراکی مقداری ایستا باشد و ویوی عمومی از ```ContextMixin``` مشتق شده باشد(که اکثراً همینطور است) پس آنها هنگام صدا زدن ```as_view``` میتوانند ذکر شوند. برای نمونه:
```python
path('myfeed/', views.MyFeed.as_view(
extra_context={'title': 'My Feed'})),
```
### الگو - سرویسها
**مسئله**: اپلیکیشنها به یک رابط ماشینی برای یک قابلیت یا اطلاعات خاص وب سایت شما نیاز دارند. دریافت و خراشیدن داده از صفحات HTML رندر شده میتواند کاری سخت و پرزحمت باشد. برعکس API تمام عیار(که در [فصل 8]('../../../08-%20Working%20Asynchronously/README.md)، *کار کردن به صورت ناهمزمان* پوشش داده شده است) که نیاز به یک اندپوینت واحد برای یک هدف خاص یا یکبار استفاده اشاره دارد.
**راه حل**: یک سرویس سبک بسازید که داده را به حالت ماشین پسند مثل JSON یا XML برگرداند.
#### جزئیات مسئله
ما اغلب فراموش میکنیم که وب سایتها فقط توسط انسانها استفاده نمیشوند. درصد قابل توجهی از ترافیک وب از دیگر برنامهها مثل خزندهها، رباتها، خراش دهندهها میآید. گاهی اوقات شما نیاز دارید همچنین برنامههایی را برای خودتان بنویسید که اطلاعاتی از سایتهای دیگر را استخراج کند.
عموماً صفحات طراحی شده برای مصرف انسانها برای استخراج مکانیکی سنگین و دست و پاگیر هستند. صفحات HTML دارای اطلاعاتی هستند که توسط نشانه گذاری احاطه شده است و نیاز به پاک سازی گسترده دارند. گاهی اوقات اطلاعات پراکنده شده است و نیاز به گردآوری و تبدیل این دادههای پراکنده است.
یک رابط ماشینی برای این چنین موقعیتهایی ایدهآل است. شما فقط نمیتوانید دردسر استخراج اطلاعات را کاهش دهید اما میتوانید مخلوطی از آن بسازید. طول عمر اپلیکیشن اگر عملکرد آن به شیوهای ماشین پسند در معرض دید قرار گیرد به طور فزایندهای افزایش مییابد.
#### جزئیات راه حل
در جنگو شما میتوانید بدون استفاده پکیجهای واسط یک سرویس پایه بسازید. به جای رندر گرفتن HTML، شما میتوانید داده سریالایز شده را در حالت JSON برگردانید.
برای مثال، ما میتوانیم سرویس سادهای بسازیم که پنج پست عمومی اخیر از سوپر کتاب را برمیگرداند:
```python
from django.http import JsonResponse
class PublicPostJSONView(View):
def get(self, request, *args, **kwargs):
msgs = models.Post.objects.public_posts().values(
"posted_by_id", "message")[:5]
return JsonResponse(list(msgs), safe=False
```
اگر سعی کنیم که این ویو را بازیابی کنیم به جای پاسخ HTML، رشته JSON دریافت خواهیم کرد:
```python
>>> from django.test import Client
>>> Client().get("http://0.0.0.0:8000/public/").content
b'[{"posted_by_id": 23, "message": "Hello!"},
{"posted_by_id": 13, "message": "Feeling happy"},
...
```
توجه کنید که ما نمیتوانیم متود ```مجموعه پرس و جو(QuerySet)``` را مستقیماً برای رندر گرفتن پاسخ JSON قرار دهیم. آن باید لیست، دیکشنری یا هر نوع دادهای پیش ساخته پایتون باشد که بتواند توسط سریالایز JSON شناسایی شود. اگر شما هر نوع دادهای غیر از ```دیکشنری(dict)``` را سریالایز کنید، نیاز دارید که پارامتر کلید ```safe``` را برابر ```False``` قرار دهید.
البته اگر بخواهید هر چیز پیچیدهتری از یک API ساده بسازید نیاز به استفاده از پکیجهایی مثل فریمورک رست جنگو(Django REST framework) دارید. فریمورک رست جنگو از سریالایز کردن(و دیسریالایز کردن) ```مجموعه پرس و جو(QuerySet)```، احراز هویت، ایجاد API قابل مرور وب و بقیه ویژگیهای ضروری برای ساخت API قدرتمند و تمام عیار نیاز دارید مراقبت میکند. ما این را در [فصل 9]('../../../09-%20Creating%20APIs/README.md) *ساختن APIها* پوشش دادهایم.
## طراحی کردن URLها
جنگو یکی از منعطفترین طرحهای URL را در فریمورکهای وب را دارا است. اساساً هیچ طرح ضمنی URL وجود ندارد. شما به صراحت میتوانید هر طرح URL که برای کاربرانتان معنا پیدا میکند استفاده کنید.
هر چند به عنوان ابرقهرمان دوست دارم بگویم؛ *قدرت بزرگ با مسئولیتهای بزرگ همراه است*. شما دیگر نمیتوانید از طراحی درهم و برهم URL خلاص شوید.
URLها قبلاً زشت بودند چون توسط کاربران نادیده گرفته میشدند. برگردیم به دهه 90 میلادی زمانی که استفاده کردن از پورتالها محبوب بود. فرض بر این بود که کاربران شما از درب جلویی میآیند که صفحه اصلی است. آنها با کلیک کردن بر روی لینکها به دیگر قسمتهای سایتها میرفتند.
موتورهای جستجو همه این چیزها را تغییر دادند. بنا بر تحقیقی در سال 2013، نزدیک نصف(47 درصد) تمام بازدیدها از موتورهای جستجو نشأت گرفته است. این به معنای این است که هر صفحه در وب سایت شما بستگی به محبوبیت و جستجو ارتباط دارد که میتواند اولین صفحهای باشد که کاربران میبینند. هر URLای میتواند درب ورودی باشد.
مهمتر از همه، مرور کردن 101 باره آموختههای امنیتی به ما یاد داده است. ما به مبتدیها میگوییم، *روی لینکهای آبی کلیک نکنید*. میگوییم اول URL را بخوانید. آیا URL بانک شما است یا سایتی است که تلاش میکند، جزئیات حساب کاربری شما را بدزدد.
امروزه URLها به بخشی از رابط کاربری تبدیل شدهاند. آنها دیده میشوند، کپی میشوند، به اشتراک گذاشته میشوند و حتی ویرایش میشوند. آنها را طوری بسازید که در یک نگاه قابل فهم و خوب به نظر برسند نه چشم را زخم کنند. به طور مثال:
<http://example.com/gallery/default.asp?sid=9DF4BC0280DF12D3ACB60090271E26A8&command=commntform>
کوتاه و قابل فهم بودن نه تنها توسط کاربران استقبال میشود بلکه توسط موتورهای جستجو نیز. URLهای طولانی و کم ارتباط با محتوا، تأثیر منفی روی رتبه سایت شما در موتورهای جستجو میگذارد.
در نهایت به طور ضمنی به یاد داشته باشید که *URIهای خوب تغییر نمیکنند* و شما باید تلاش کنید که ساختار URLها را در طول زمان حفظ کنید حتی اگر وب سایت شما به صورت کامل باز طراحی شد، لینکهای قدیمی شما باید همچنان کار کند. جنگو شما را از این موضوع مطمئن میکند.
قبل از اینکه به دل جزئیات طراحی URLها برویم، نیاز داریم که ساختار URLها را متوجه شویم.
### ساختمان URL
به طور فنی، URLها متعلق به خانوادهای عمومی از شناسهها است که **Uniform Resource
Identifiers (URIs)** صدایشان میزنیم. از این رو URL نیز همان ساختار URI را دارا است.
یک URI از چندین بخش تشکیل شده است:
*URI = Scheme + Net Location + Path + Query + Fragment*
به طور مثال ساختار یک URI(<http://dev.example.com/gallery/videos?id=217#comments>) میتواند در پایتون به وسیله تابع ```urlparse``` شکسته شود.
```python
>>> from urllib.parse import urlparse
>>> urlparse("http://dev.example.com:80/gallery/videos?id=217#comments")
ParseResult(scheme='http', netloc='dev.example.com:80',
path='/gallery/videos', params='', query='id=217', fragment='comments')
```
قسمتهای URI میتواند به صورت گرافیکی به تصویر کشیده شود که به صورت زیر است:

حتی اگر چه مستندات جنگو ترجیح میدهد که از عبارت URL استفاده کند، ممکن است از لحاظ فنی درست باشد که بگوییم در اکثر اوقات دارید با URIها کار می کنید. ما در این کتاب از این عبارات به جای همدیگر استفاده خواهیم کرد.
الگوهای URL جنگو اکثراً در رابطه با قسمت **مسیر(path)**(در تصویر قبل به صورت پررنگ نشان داده شده است) URI است. تمام قسمتهای دیگر کنار گذاشته شده است.
#### چه اتفاقی در urls.py میافتد؟
از بسیاری جهات، ```urls.py``` نقطه ورود برای پروژه شما است. معمولاً این اولین فایلی است که من هنگام مطالعه یک پروژه جنگو آن را باز میکنم. این مثل خواندن یک نقشه قبل از اکتشاف آن است. اساساً ```urls.py``` حاوی تنظیمات URL ریشه یا ```پیکربندی URL``` در کل پروژه شما است.
یک لیست پایتونی از ```الگوها(patterns)``` است که به متغیری عمومی به نام ```urlpatterns``` اختصاص داده شده است. هر URL ورودی با این توالی، از بالا تا پایین با هر یک از این الگوها مطابقت داده میشود. در اولین مطابقت جستجو متوقف شده و درخواست به ویوی متناظر ارسال میشود.
این گزیدهی ```urls.py``` از [python.org](https://www.python.org/) است که در جنگو ساخته شده است:
```python
urlpatterns = [
# Homepage
url(r'^$', views.IndexView.as_view(), name='home'),
# About
url(r'^about/$',
TemplateView.as_view(template_name="python/about.html",
name='about'),
# Blog URLs
url(r'^blogs/', include('blogs.urls', namespace='blog')),
# Job archive
url(r'^jobs/(?P<pk>\d+)/$',
views.JobArchive.as_view(),
name='job_archive'),
# Admin URLs
url(r'^admin/', include(admin.site.urls)),
# ...
]
```
برخی از نکات جالب توجه در اینجا به شرح زیر است:
- همه الگوها در لیست معمولی پایتون قرار دارند.
- هر الگوی URL با استفاده از تابع URL ساخته شده است که پنج آرگومان میگیرد. اکثر الگوها سه آرگومان دارند: الگوی عبارت منظم، ویو صدا کننده و اسم ویو.
- الگوی URL درباره(About)، مستقیماً ویو را با نمونه سازی از ```TemplateView``` تعریف کرده است. این رویکرد زمانی استفاده میشود که شما میتوانید از ویوهای عمومی با کمی شخصی سازی استفاده کنید.
- URL بلاگ(Blog) در جای دیگری ذکر شده است، به طور مشخص در ```urls.py``` درون اپ blog. عموماً جدا کردن الگوی URL اپ درون فایل خودش، تمرین خوبی است.
- الگوی ```Job``` تنها مثال اینجا است که تحت عنوان عبارت منظم آمده است.
هر الگوی URL از دو تابع استفاده میکنند: تطابق دادن URLهایی که به شکل معینی ظاهر میشوند؛ و استخراج کردن اطلاعات جالب از URL و فرستادن آن به ویو صدا کننده است.
از جنگو 2.0 به بعد شما میتوانید از الگوی URL ساده شده بدون عبارت منظم استفاده کنید. از آنجایی که درک آن آسانتر است تقریباً تمام مستندات جنگو، حتی آموزشش نیز از این فرمت استفاده میکنند. اجازه دهید ما اول امتحان کنیم.
#### سینتکس الگوی URL ساده شده
اکثر مبتدیها در الگوهای URL جنگو عبارات منظم را پیدا میکنند که کاراکترهای خاصی مثل ```^``` یا ```$``` در آن استفاده شده است که چالش برانگیز است. عبارات منظم در خودشان، زبان کوچکی هستند. پس یک سینتکس سادهتر، تا حد زیادی بر اساس فلسک به عنوان راهی پیشفرض و جدید برای اختصاص الگوهای URL پذیرفته شد.
به جای استفاده از عبارات منظم، میتوانید ```مسیر(path)``` URL را مستقیماً در الگو درون تابع ```path```(که همان پارامترهای مشخص شده در تابع URL که قبلاً معرفی شد را دارد) مشخص کنید. همچنین شما میتوانید نام قسمتهای URL را بگیرید و در علامتهای کمتری و بیشتری(```<>```) قرار دهید و پیشوند اختیاری نوع دادهای را برای آن بگذارید.
چندین مثال که میتواند این موضوع را بهتر توضیح دهد. در جدول زیر سینتکسهای قدیمی و جدید با هم مقایسه شدهاند:
| قدیمی(الگوی عبارت منظم) | جدید(الگوی ساده شده) |
| :----------: | :----------: |
|# Homepage ```url(r'^$', IndexView.as_view(), name='home'),```| # Homepage ```path('', IndexView.as_view(), name='home'),``` |
|```url(r'^about/$', AboutView.as_view(), name='about'),```| ```path('about/', AboutView.as_view(), name='home'),``` |
|```url(r'^hello/(?P<name>\w+)/$', views.hello_fn),```| ```path('hello/<str:name>/', views.hello_fn),``` |
| ```url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/(?P<day>[0-9]+)/(?P<pk>[0-9]+)/$',``` | ```path('<int:year>/<int:month>/<int:day>/<int:pk>/',``` |
**(داستان)**
سینتکس نه تنها قابل خواندن است بلکه برای گرفتن انواع دادهای نیز بهتر است مثل دادههای عددی که نیازی به، به خاطر سپردن معادل متناظر عبارات منظم آن را ندارند. آنها بعد از اینکه به نوع دادهای موردنظر تبدیل شدند به ویو صدا کننده آن فرستاده میشوند. این را با عبارت منظم مقایسه کنید که به معنای واقعی کلمه فقط رشته برمیگرداند.
نوع و مسیر(path) که به صورت پیشفرض در دسترس و قابل تبدیل هستند در زیر آمده است هر چند شما نیز میتوانید نوع و مسیرهای خودتان را اضافه کنید:
- ```str```: هر رشتهای که حاوی جدا کننده مسیر ```/``` نباشد به جز رشتههای خالی. اگر نوعی مشخص نشود، این نوع پیشفرض است.
- ```int```: هر داده عددی حتی شامل صفر نیز میشود. در نهایت به صورت ```int``` به ویو فرستاده میشود.
- ```slug```: هر رشته ساخته شده که ترکیبی از حروف ASCII، اعداد، (خط تیره) - یا (خط فاصله) _ باشد.
- ```uuid```: هر نوع ```uuid``` که اساساً به صورت **12345678-1234-5678-1234-567812345678** است. به عنوان نمونه ```uuid``` فرستاده میشود.
- ```path```: هر رشتهای که *حاوی* جدا کننده مسیر ```/``` باشد به جز رشتههای خالی.
برای تطابقهای ملزوم پیچیده میتوانید از عبارات منظم استفاده کنید یا یک تبدیل کننده مسیر شخصی سازی شده را تعریف کنید(اگر میخواهید دادۀ غیر رشتهای استخراج کنید توصیه میشود).
**نکته(TIP)**
ما تمام آرگومانها را به عنوان کلید واژه میفرستیم. جایگاه آرگومانها در سینتکس ساده شده قابل استفاده نیست.
من توصیه میکنم از سینتکس ساده برای خوانایی بیشتر و بررسی نوع بیشتر استفاده کنید ولی برای درک بهتر کدهای پایهای موجود، شما نیاز خواهید داشت که سینتکس الگوی URL عبارات منظم را نیز به خوبی بدانید.
#### سینتکس الگوی URL با عبارت منظم
استفاده از الگوی عبارت منظم میتواند گاهی به عنوان تودهای گیج کننده از علائم نگارشی به نظر برسد. هرچند مثل اکثر چیزها در جنگو، این فقط پایتون عادی است.
این میتواند خیلی قابل فهم باشد اگه به عملکرد دو الگوی عبارت منظم نگاه کنیم: مطابقت دادن و استخراج کردن.
قسمت اول آسان است. اگر بخواهید مسیری مثل ```/year/1980/``` را مطابقت دهید فقط از عبارت منظم ```^year/\d+/``` استفاده میکنید(که در اینجا ```d\``` برای اعداد تنهای 0 تا 9 ایستاده است). اسلش اول را نادیده بگیرد چون خورده میشود.
قسمت دوم کار جالب است چون در مثال ما دو راه برای استخراج کردن سال وجود دارد(که اینجا ```1980``` است) که برای ویو لازم است.
سادهترین کار این است که دور هر گروه از مقادیر پرانتز قرار دهیم که گرفته شود و هر کدام از مقادیر به عنوان آرگومانهای جایگاهی به ویو ارسال میشوند. به عنوان مثال الگوی ```^year/(\d+)/```، مقدار ```1980``` را به عنوان آرگومان دوم(اولین مورد درخواست(request) است) به ویو ارسال میکند.
مسئله با آرگومانهای جایگاهی میتواند به راحتی ترتیبها را درهم و برهم کند. از این رو ما آرگومانهای بر اساس نام را داریم که بر اساس نام آنها مقادیرشان گرفته میشود. مثال حالا شبیه ```^year/(?P<year>\d+)/``` خواهد بود. این به این معنی خواهد بود که ویو با آرگومان کلید واژه که ```year``` با مقدار ```1980``` صدا زده میشود.
**نکته(TIP)**
از تولیدکنندههای عبارات منظم آنلاین مثل <http://pythex.org/> یا <https://www.debuggex.com/> برای مهارت و امتحان کردن عبارتهای منظمتان استفاده کنید.
اگر ویوهای مبتنی بر کلاس دارید میتوانید با ```self.args``` به آرگومانهای جایگاهی و با ```self.kwargs``` به آرگومانهای کلید واژهای دسترسی پیدا کنید. اکثر ویوهای عمومی انتظار دارند که آرگومانهایشان صرفاً آرگومان کلید واژهای باشد برای مثال ```self.kwargs["slug"]```.
##### آیا میتوانیم سینکس ساده شده را با عبارات منظم جایگزین کنیم؟
باور دارم که شما کاملاً میتوانید به سینتکس ساده شده سوئیچ کرده و از عبارت منظم برای مطابقت الگو دوری کنید. عبارات منظم ممکن است که قدرتمندتر به نظر برسند ولی آنها خوانایی را قربانی میکنند و همچنین محدودیتهای خود را دارند.
الگوی سال مثال قبل را در نظر بگیرید. بعضی از مردم باهوش ممکنه عبارت منظم را به صورت ```^year/(\d{4})/``` بنویسند اما در مورد سال 793 میلادی چطور(وقتی وایکینگها شروع به حمله ایرلند کردند) یا 11234 میلادی(شاید ورود واکینگهای فضایی به زمین؟) یا هر سال غیر 4 عددی دیگر؟
الگوی ساده شده ```<year/<int:year/``` میتواند تمام این حالات سال و بیشتر را تطابق دهد. شما میتوانید یک بررسی برای معتبر بودن سال در ویو اضافه کنید. به صورت زیر:
```python
class YearView(View):
def get(self, request, year):
try:
d = datetime(year=year, month=1, day=1)
reply = "First day of the year {} is {}!".format(
year, d.ctime())
except ValueError:
reply = "Error: Invalid year!"
return HttpResponse(reply)
```
دوباره، این نمیتواند سال 11234 میلادی را مدیریت کند از آن جا که اشیاء datetime پایتون فقط میتوانند سال را نهایتاً تا 9999 نمایش دهند. اگر برنامه داشته باشید که از اشیاء datetime استفاده کنید به هر صورت این محدودیت را دارید. اجازه دهید که حتی راجع به مدیریت سالهای قبل از میلاد بحث نکنیم.
مختصراً، بهتر است که اطلاعات استخراج شده از الگوی URL را درون ویو بررسی کنید. شما میتوانید از منطق بررسی بهتری یا حتی عبارت منظم بهتری برای اپلیکیشن استفاده کنید که پیام خطای خیلی بهتری به جای نمایش مرموزانه خطای **404: صفحه پیدا نشد(404: Page not found)** به شما میدهد.
در موارد نادر دو ویو ممکن است که مسیر URL شبیه به هم داشته باشند که نیاز به عبارات منظم دارد. حتی بعد شما میتوانید پیشوند مسیری طراحی کنید که بین آنها تمایز ایجاد کند.
#### نامها و فضاهای نام
همیشه برای الگوهایتان اسم بگذارید چون کمک میکند که کدتان را از مسیرهای مطلق URL جدا کند. برای نمونه در ```پیکربندی URL``` قبلی اگر شما میخواستید به صفحه ```درباره(About)``` ریدایرکت کنید، ممکن بود که وسوسه شوید و از ```redirect("/about")``` استفاده کنید به جای اینکه از ```redirect("about")``` استفاده کنید که از نام به جای ```مسیر(path)``` استفاده میکند.
در اینجا چند مثال بیشتر از واکشیهای معکوس(reverse lookup) آوردهایم:
```python
>>> from django.urls import reverse
>>> reverse("hello_fn")
/hello-fn/
>>> reverse("year_view", kwargs={"year":"793"})
/year/793/
```
نامها باید منحصر به فرد باشند. اگر دو الگو یک اسم داشته باشند کار نخواهند کرد. قبلتر از چندین پکیج جنگو برای اضافه کردن پیشوندهایی به الگوی نام استفاده میشد. برای مثال در اپلیکیشنی به نام ```Blog``` ممکن بود ویوی فید را با نام ```blog-feed``` صدا بزنید در صورتی که ```feed``` نامی مرسوم است و ممکن است سبب ناسازگاری با دیگر اپها گردد.
فضاهای نام ساخته شدهاند که چنین مشکلاتی را حل کنند. الگوهای نام نیاز دارند در فضاهای نام استفاده شوند و فقط در آنجا منحصر به فرد باشند و نه در کل پروژه. به شما توصیه میشه که به هر اپ فضای نام خودش را اختصاص دهید.
برای مثال ما میتوانیم فضای نام ```viewschapter``` را که فقط URLهای این فصل است را با اضافه کردن این خط به ```پیکربندی URL``` ریشه بسازیم:
```python
path('', include(viewschapter.urls, namespace='viewschapter')),
```
حالا میتوانیم از الگوهای نام استفاده کنیم مثل ```feed``` یا هر چیز دیگری که در ```فضای نام(namespace)``` اپ منحصر به فرد هستند. با این حال وقتی میخواهید به نامی درون یک ```فضای نام``` ارجاع دهید نیاز دارید که اول ```فضای نام``` را ذکر کنید و بعد از آن علامت : و بعد از آن نام الگو که در مثال ما به صورت ```"viewschapter:hello_fn"``` است.
```python
>>> from django.urls import reverse
>>> reverse("viewschapter:hello_fn")
/hello-fn/
```
همانطور که زِن(Zen) پایتون گفته است: *Namespaces are one honking great idea — let's do more of those(فضاهای نام یک ایده عالی برای سر و صدا کردن هستند - بیاید از آن استفاده بیشتری کنیم)*. شما میتوانید فضاهای نام تو در تو بسازید که الگوهای نام شما را تمیزتر خواهد کرد مثل ```blog:comment:edit```. خیلی توصیه میکنم که از فضاهای نام در پروژههایتان استفاده کنید.
#### ترتیب الگوها
ترتیب الگوهای شما از این مزیت بهره میبرد که جنگو چطور آنها را پردازش کند که پردازش آن به صورت بالا به پایین است. یک قانون سرانگشتی خوب این است که موارد خاص را در بالا نگه داریم. الگوهای گستردهتر و عمومیتر میتواند پایینتر ذکر شود. گستردهترین الگو که همه درخواستها را میگیرد میتواند به پایینترین نقطه برود.
برای مثال مسیر اپ پستهای ```بلاگ``` ممکن است مجموعهای از کاراکترهای معتبر باشد اما شما میخواهید صفحه ```درباره(About)``` را به صورت جدا مدیریت کنید. توالی درست این الگوها باید به صورت باشد:
```python
blog_patterns = [
path('about/', views.AboutView.as_view(), name='about'),
path('<slug:slug>/', views.ArticleView.as_view(), name='article'),
]
```
اگر ما ترتیب را برعکس کنیم بعد آن مورد خاص یعنی ```AboutView``` هیچوقت صدا زده نخواهد شد.
#### سبکهای الگو URL
طراحی URLهای سایت به راحتی ممکنه است نادیده گرفته شود. طراحی خوب URLها نه تنها میتواند به صورت منطقی سایت شما را سازمانی دهی کند بلکه میتواند حدس زدن مسیرها برای کاربران را نیز راحتتر کند. طراحی ضعیف حتی ممکنه که ریسکهای امنیتی به وجود بیاورد: برای مثال استفاده از ID دیتابیس(که در یک دنباله افزایشی یکنواخت از اعداد صحیح رخ میدهد) در الگوی URL میتواند ریسک دزدیده شدن اطلاعات خراب کردن سایت را افزایش دهد.
بیاید چندین سبک مرسوم طراحی کردن URL را امتحان کنیم.
##### URLهای به سبک دپارتمانهای فروش
بعضی از سایتها شبیه دپارتمانهای فروش گذاشته شدهاند. قسمتی برای غذا وجود دارد که در درونش راهرویی برای میوه وجود خواهد داشت که در درون این قسمت انواع مختلفی از سیبها کنار هم قرار گرفتهاند.
در مورد URLها، به این معناست که صفحات مرتب شده را میتوانید به صورت سلسله مراتبی پیدا کنید که به صورت زیر است:
```
http://site.com/ <section> / <sub-section> / <item>
```
زیبایی این لایه این است که میتوان به آسانی از آن بالا رفت و به قسمت پدر رسید. هر موقع که آخر URL بعد از هر اسلش را پاک کنید یک سطح بالاتر خواهید رفت.
به طور مثال شما میتوانید ساختار مشابهی را برای ```article``` بسازید که در زیر نشان داده شده است:
```python
blog_patterns = [
path('', views.BlogHomeView.as_view(), name='blog_home'),
path('<slug:slug>/', views.ArticleView.as_view(), name='article'),
]
```
به الگوی ```blog_home``` توجه کنید که اگر کاربری از یک مقاله خاص بالا برود یک فهرست مقاله را نشان خواهد داد.
##### URLهای RESTful
در سال 2000، روی فیلدینگ(Roy Fielding) در پایان نامه دکترای خود عبارت **Representational state transfer (REST)** را معرفی کرد. خواندن تز او (<http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm>) به شدت توصیه میشود تا معماری وب را بهتر متوجه شوید. به شما کمک خواهد کرد که وب اپلیکیشنهای بهتری بنویسید که محدودیتهای هستۀ معماری را نقض نخواهد کرد.
یکی از بینشهای کلیدی این است که URI شناسه یک منبع است. یک منبع میتواند هر چیزی باشد مانند یک مقاله، یک کاربر یا مجموعهای منابع مثل رخدادها. به طور کلی، منابع اسم هستند.
وب برخی از عملکردهای اساسی HTTP برای دستکاری منابع را در اختیار شما قرار میدهد: ```GET، POST، PUT، PATCH و DELETE```.
**نکته(TIP)**
اینها قسمتی از یک URL نیستند و از این رو تمرین بدی است اگر از این عملکردها در URL، برای دستکاری منابع استفاده کنیم.
به طور مثال، مثالی که در ادامه آمده است URL بدی در نظر گرفته میشود: <http://site.com/articles/submit/>
به جای آن شما باید عملکرد را از URL حذف کرده و از عمل ```POST``` استفاده کنید: <http://site.com/articles/>
توجه کنید که همیشه استفاده کردن از این عملکردها در URL اشتباه نیست. URL جستجوی سایت شما میتواند فعل search را داشته باشد تا زمانی که با یکی از منابع به عنوان REST مرتبط نیست: <http://site.com/search/?q=needle>
URLهای RESTful برای طراحی کردن رابطها خیلی مفید هستند. آنها تقریباً بین عملیاتهای پایگاه داده **Create(ساختن)، Read(خواندن)، Update(بروزرسانی کردن) و Delete(حذف کردن) (CRUD)** و عملکردهای HTTP نگاشت یک به یک انجام میدهند. ما APIهای RESTful را در [فصل 9]('../../../09-%20Creating%20APIs/README.md)، *ساختن APIها* با جزئیات بیشتر پوشش دادهایم.
توجه کنید که سبک URLهای RESTful مکمل سبک URLهای دپارتمان فروش است. اکثر سایتها هر دو سبک را با هم ترکیب میکنند. آنها برای وضوح و فهم بیشتر از هم جدا شدهاند.
## React.js، Vue.js و دیگر ویوهای جایگزین
در سال 2018 اکثر وب اپلیکیشنهای بزرگ از فریمورکهای فرانت اند جاوا اسکریپت از جمله انگولار یا ریاکت جی اس استفاده میکردند. بعضی از اینها مثل انگولار فریمورکهای تماماً MVC هستند ولی بعضی دیگر مثل ریاکت جایگزینهای ویو هستند.
از آنجا که ریاکت در حال حاضر محبوبترین انتخاب برای توسعه فرات اند است، ما به طور خلاصه نگاهی میکنیم به ریاکت و جنگو که چگونه با هم کار میکنند. از نظر معماری ریاکت لایههای **قالب(Template)** را به جای ویوهای اپلیکیشن جنگوی شما جایگذاری میکند. همانطور که در نمودار زیر نمایش داده شده است:
||
|:--:|
|چگونه اضافه کردن ریاکت معماری سنتی سایت جنگویی را تغییر میدهد. این یکی از چند راه موجود است که میتوان ریاکت و جنگو را با هم ادغام کرد.|
شما میتوانید از فریمورک رست جنگو یا سرویس ساده ویو برای ارسال داده JSON به ریاکت استفاده کنید. رندر گرفتن از قالب در مرورگر، سمت کلاینت انجام خواهد شد.
رابطهای ریاکت میتوانند بدون بارگذاری مجدد خیلی بیشتر ریسپانسیو و پویا باشند.
وب اپلیکیشنهای کاملی وجود دارند که میتوانند بدون بارگذاری مجدد صفحه ساخته شوند که به آن **Single Page
Application (SPA)** میگویند.
هرچند خزندههای موتورهای جستجو توانایی اجرای جاوا اسکریپت را ندارند که منجر به رتبه سئوی ضعیف چنین سایتهایی میشود. برای غلبه به این مشکل گاهی اوقات از سمت سرور جاوا اسکریپت برای رندر گرفتن HTML استفاده میشود.
با جاوا اسکریپت به عنوان گزینه قابل دوام در بک اند، جنگو و ریاکت به چندین شیوه مختلف میتوانند ترکیب شوند. بعضی از الگوهای مرسوم آن اینها است:
- **ریاکت بر اساس SPA و بک اند API رست جنگو(React based SPA and Django REST API backend)**: این تفکیک ایدهآل از نگرانیها است. شما API عمومی بک اند را برای کلاینتهای مختلفی از جمله اپهای موبایل میگیرید اما ممکن است مجبور شوید که چگونگی پشتیبانی فهرست بندی جستجو را کشف کنید.
- **ریاکت بر اساس SPA و اندپوینتهای API جنگو(React based SPA and Django API endpoints)**: به جای ساخت بک اند API به صورت کامل، این رویکرد هر صفحه را به عنوان یک اندپوینت API در دسترس قرار میدهد. این روش برای مهاجرت کردن سایتها به صورت تکه تکه راحتتر است اما فرانت اند و بک اند شما را محکم بهم نگه میدارد.
- **قالبهای جنگو به همراه کامپوننتهای ریاکت(Django templates and bundled React components)**: قالبهای جنگو میتوانند به وسیله تگ ```<script>``` به باندل ریاکت مراجعه کرده و داده را به پروپرتیهای ریاکت ارسال کنند. اینجا میتوانید مزیتهای ابزارهای ساخته شده با جاوا اسکریپت را ببینید مثل Webpack به transpile و minify. این روش به خوبی کار میکند اگر سایت شما هم به صفحات ایستا و هم به صفحات پویا نیاز داشته باشد.
همانطور که میبینید قالبهای سمت سرور همچنان برای سئو مهم هستند. یک صفحه جاوا اسکریپتی سنگین ممکن است روی کلاینتهای کم قدرت مثل دستگاههای اینترنت اشیاء امکان پذیر نباشند. در موارد مشابه نیز شما ممکن است بخواهید صفحاتتان توسط سیستم قوی قالب سمت سرور جنگو رندر گرفته شود.
## خلاصه
ویوها قسمتی فوق العاده قوی در معماری MVC جنگو هستند. در طول زمان ویوهای مبتنی بر کلاس انعطاف پذیرتر و بیشتر قابل استفاده مجدد شدهاند تا در مقایسه با ویوهای مبتنی بر تابع سنتی. میکسینها بهترین مثال برای ویژگی قابل استفاده مجدد بودن هستند.
جنگو دارای یک سیستم ارسال URL فوق العاده انعطاف پذیر است. ساخت یک URL خوب چندین وجه را در نظر میگیرد. کاربران نیز از URLهای خوب طراحی شده استقبال مینمایند.
در فصل بعدی ما به زبان قالب جنگو و بهترین روش استفاده از آن نگاه میکنیم.
================================================
FILE: 05-Templates/README.md
================================================
# ۵ تمپلیتها
در این بخش ما موضوعات زیر را بررسی خواهیم کرد:
- ویژگیهای زبان تمپلیت جنگو
- جینجا ۲ (Jinja2)
- سازماندهی تمپلیتها
- چگونه تمپلیتها کار میکنند
- بوتسترپ (Bootstrap)
- الگوی درختی وراثت در تمپلیت
- الگوی لینک فعال
وقت آن است که از سومین تفنگدار در سهگانه MTV صحبت کنیم؛ تمپلیتها. تیم شما ممکن است دیزاینری داشته باشد که مسؤول طراحی تمپلیتها باشد یا ممکن است خود شما آنها را طراحی کنید. در هر حالت لازم است که یا تمپلیتها کاملاً آشنا باشید. در نهایت تمپلیتها هستند که با کاربر واقعی روبرو میشوند.
جنگو زبانهای تمپلیتی زیادی را پشتیبانی میکند. در اینجا ما ابتدا به زبان تمپلیت خود جنگو نگاهی میاندازیم که به صورت پیشفرض در هر پروژه، وجود دارد.
## درک ویژگیهای زبان تمپلیت جنگو
بیایید ابتدا نگاهی سریع به ویژگیهای **DTL** یا **Django Template Language** داشته باشیم.
**متغیرها**
هر تمپلیت یک مجموعه از متغیرهای زمینه (context) را دریافت میکند. مانند متد format() در رشتههای پایتونی که با یک آکولاد {variable}، مشخص میشود، جنگو از دو آکولاد {{ variable }} استفاده میکند. ببینیم چطور با هم تفاوت دارند:
در پایتون روش نوشتن `<h1>{title}</h1>` است. برای مثال:
`>>> "<h1>{title}</h1>".format(title="SuperBook") '<h1>SuperBook</h1>'`
همین عبارت در تمپلیت جنگو به صورت `<h1>{{ title }}</h1>` نوشته میشود. رندر کردن این عبارت با متغیر زمینه قبلی نتیجه یکسانی خواهد داشت:
`>>> from django.template import Template, Context`
`>>> Template("<h1>{{ title }}</h1>").render(Context({"title": "SuperBook"}))`
`'<h1>SuperBook</h1>'`
**صفات یا Attributes**
نقطه، یک اپراتور چند منظوره در تمپلیتهای جنگو است. سه نوع عملیات مختلف وجود دارد: جستجوی صفت، جستجوی دیکشنری و یا جستجوی ایندکس لیست (به همین ترتیب).
ابتدا متغیرهای زمینه و کلاسها را مشخص کنیم. در پایتون:
```python
>>> class DrOct:
arms = 4
def speak(self):
return "You have a train to catch."
>>> mydict = {"key":"value"}
>>> mylist = [10, 20, 30]
```
حالا بیایید به دستور زبان پایتون برای این سه نوع جستجو نگاه کنیم:
```python
>>> "Dr. Oct has {0} arms and says: {1}".format(DrOct().arms,
DrOct().speak())
'Dr. Oct has 4 arms and says: You have a train to catch.'
>>> mydict["key"]
'value'
>>> mylist[1]
20
```
معادل آن در تمپلیت جنگو به این صورت خواهد بود:
**Dr. Oct has {{ s.arms }} arms and says: {{ s.speak }} {{ mydict.key }}**
**{{ mylist.1 }}**
[](../02-%20Application%20Design/images/1.png) نگاه کنید که متد speak، که هیچ ارگومانی به غیر از self نمیگیرد، در اینجا مانند یک صفت با آن رفتار شده است.
**فیلترها**
بعض مواقع لازم است که متغیرها تغییر کنند. ممکن است بخواهید فانکشنهای مختلفی را روی یک متغیر اعمال کنید. به جای صدا کردن فانکشنها به صورت زنجیروار پشت سر هم مانند var.method1().method2(arg)، جنگو از روش پایپ کردن استفاده میکند {{ var|method1|method2:"arg" }}، که شبیه به فیلترهای یونیکس است. با اینحال این روش فقط برای فیلترهای پیشفرض یا فیلترهای تعریف شده به صورت اختصاصی، کار میکند.
یک محدودیت دیگر این است که فیلترها نمیتوانند به متن تمپلیت دسترسی داشته باشند. این فیلترها فقط به کمک دیتا و آرگومانهایی که به آنها ارسال میشود کار میکنند. بنابراین در درجه اول، برای تغییر متغیرها در متن تمپلیت به کار میروند.
این دستور را در پایتون اجرا کنید:
```python
**>>> title="SuperBook"**
**>>> title.upper()[:5]**
**'SUPER'**
```
در ادامه، معادل آن را در تمپلیت جنگو میبینید:
``{{ title|upper|slice:':5' }}``
**تگها**
زبانهای برنامه نویسی، کاری بیش از نشان دادن متغیرها میتوانند انجام دهند. زبان تمپلیت جنگو فرمهای نوشتاری آشنای زیادی مانند for دارد. این دستورها باید در روش نوشتاری تگها مانند `{% if %}` نوشته شوند. بسیاری از فرمهای اختصاصی تمپلیت مانند include و block نیز باید به همین صورت نوشته شوند.
در شل پایتون:
```shell
>>> if 1==1:
... print(" Date is {0} ".format(time.strftime("%d-%m-%Y")))
Date is 30-05-2018
```
در ادامه، همین عبارت را در شکل تمپلیت جنگو میبینید:
`{% if 1 == 1 %} Date is {% now 'd-m-Y' %} {% endif %}`
**فلسفه - یک زبان برنامهنویسی اختراع نکنید**
یک سوال رایج بین تازهکارها این است که چطور محاسبات عددی مانند پیدا کردن درصدها را در تمپلیت انجام دهیم. بر اساس فلسفه طراحی، سیستم تمپلیت عمداً اجازه انجام این موارد را نمیدهد:
- نسبت دادن مقدار به متغیرها
- صدا زدن فانکشنها به همراه آرگومان
- بهکارگیری منطقهای پیشرفته
این تصمیم گرفته شده تا اجازه ندهد منطقهای کسب و کار را در تمپلیت قراردهید. بر اساس تجربه من با PHP یا زبانهای مشابه ASP، ترکیب منطق با سیستم ارائه، میتواند یک کابوس جدی برای نگهداری کد باشد. با اینحال شما میتوانید تگهای اختصاصی را (که به زودی به آن میپردازیم) برای انجام هر نوع محاسبهای، مخصوصاً اگر مربوط به ارائه باشد، بنویسید.
**بهترین روش**
منطق کار را از تمپلیت دور نگه دارید.
فارغ از این توصیه، برخی ممکن است موتور تمپلیت کمی قویتری را ترجیح بدهند. در اینصورت Jinja2 احتمالاً همان چیزی است که نیاز دارید.
## جینجا ۲
جینجا ۲ از نظر نگارش بسیار شبیه به DTL است. اما در بعضی موارد، فلسفه کمی متفاوتی دارد. برای مثال، در DTL صداکردن متدها مانند مثالهای زیر است:
```
{% for post in user.public\_posts %}
...
{% endfor %}
```
اما در جینجا ۲، ما فراخوانی متد public_posts را مانند فراخوانی تابع پایتونی انجام میدهیم:
```
{% for post in user.public\_posts() %}
...
{% endfor %}
```
این به آن معنی است که در جینجا ۲ بر خلاف DTL میتوانید توابع را با آرگومان فراخوانی کنید. برای دیدن چنین تفاوتهای ظریفی به [مستندات Jinja2](http://jinja2.pocoo.org/) مراجعه کنید.
جینجا ۲ معمولاً به دلایل زیر انتخاب میشود:
- **آشنایی**: ممکن است طراح شما از قبل با جینجا ۲ آشنا باشد.
- **کنترل فضای خالی**: جینجا ۲ کنترل بهتری روی فضاهای خالی بعد از رندر شدن تگها دارد.
- **قابلیت سفارشی سازی**: تمام جنبههای جینجا ۲ از تعریف مارکاپ برای رشتهها تا تعریف اکستنشنها به سادگی قابل انجام است.
- **عملکرد**: بعضی بررسیها نشان میدهد که جینجا ۲ از جنگو سریعتر است.
- **قابلیت Autoescape**: به صورت پیشفرض جینجا ۲ برای عملکرد بهتر، قابلیت Autoescape را در XML/HTML غیرفعال میکند.
در اکثر موارد، این مزایا فقط برای استفاده از جینجا ۲ نیست، بلکه میتواند دلیل خوبی برای استفاده از سایر موتورهای تمپلیت مانند Mako و Genshi باشد.
آشنایی با DTL منحنی یادگیری را برای هر فرد جدیدی که به پروژه شما وارد شود، کاهش میدهد. علاوه بر این DTL، هم خوب به کارگرفته شده و هم خوب تست شده است. در نهایت ممکن است لازم باشد تگهای تمپلیت جنگو مانند url یا static را در موتور تمپلیت جدید به همان شکل تمپلیت جنگو، تکرار کنید.
تا زمانی که دلیل خوبی برای تغییر زبان تمپلیت ندارید، پیشنهاد میکنم که با همان زبان تمپلیت جنگو پیش بروید. در ادامه این بخش ما از DTL استفاده میکنیم.
## سازماندهی تمپلیتها
ترکیببندی اولیه پروژه که به کمک دستور startproject انجام میشود، محلی برای تمپلیتهای شما تعریف نمیکند. اما این کار بسیار راحتی است.
یک دایرکتوری به نام templates در پوشه اصلی پروژه بسازید. مقدار DIRS را در متغیرهای TEMPLATES در فایل settings.py تعیین کنید (فایل تنظیمات در آدرس superbook/settings/base.py در پروژه سوپربوک قابل دیدن است):
```python
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
```
همین، حالا برای مثال میتوانید یک تمپلیت به نام about.html بسازید و مانند زیر، از طریق فایل urls.py به آن ارجاع دهید:
urlpatterns = [
` path('about/', TemplateView.as_view(template_name='about.html'), name='about'),`
اگر مقدار APP_DIRS برابر با True باشد، تمپلیتهای شما میتوانند درون پوشه اپ شما قرار داشته باشند. ساخت یک دایرکتوری جداگانه برای تمپلیتها روش ایدهآلی برای نگهداری تمپلیتهای هر اپ است.
در اینجا چند روش ارائه شده که روشهای خوبی برای سازماندهی تمپلیتهاست:
- تمپلیتهای هر اپ را در دایرکتوری تمپلیتها و در یک دایرکتوری جدا نگه دارید. مثلاً projroot/app/templates/app/template.html، توجه داشته باشید که app دوبار در آدرس دیده میشود.
- از پسوند .html برای تمپلیتها استفاده کنید.
- برای تمپلیتهایی که تکه کدهایی هستند که درون سایر تمپلیتها استفاده میشوند از پیشوند _ استفاده کنید، مانند \_navbar.html
ترتیب معرفی دایرکتوریهای تمپلیت اهمیت زیادی دارد. برای فهم بهتر آن، نیاز است که بدانید جنگو چطور تمپلیتها را رندر میکند.
## تمپلیتها چگونه کار میکنند
همانطور که نمودار زیر نشان میدهد، جنگو تمپلیتها را بدون آگاهی از موتور واقعی تمپلیت، رندر میکند:

تصویرسازی ساده از رندر تمپلیت در جنگو
هر تمپلیت به ترتیب با بکاند تمپلیتهایی که در متغیرهای TEMPLATES در فایل settings.py تعریف شده است، امتحان میشود.
یک شئ **Loader** مربوط به بکاند تمام تمپلیتها را جستجو خواهد کرد. بر اساس تنظیمات بکاند، لودکنندههای مختلفی استفاده میشوند. برای مثال filesystem.Loader، تمپلیتهای فایل سیستم را بر اساس DIRS، لود میکند و app_directories.Loader تمپلیتهای درون پوشه اپها را لود میکند.
اگر یک **Loader** موفق باشد، جستجو قطع میشود و همان موتور بکاند تمپلیت، برای رندر کردن انتخاب میشود. نتیجه این کار یک شئ **Template** خواهد بود که شامل تمپلیت تجزیه شده و کامپایل شده است.
بر ای رندر کردن **Template** باید آن را با یک شئ **Context** آماده کنید. **Context** دقیقاً مانند یک دیکشنری عمل میکند، اما مانند دستهای از دیکشنریها ساخته شده است. اگر یک **Template** شامل تعدادی متغیر باشد، **Context** مقادیری است که به این متغیرها نسبت داده میشود.
هنگامی که از **Context** های جنگو استفاده میکنید میبایست با RequestContext بیشتر آشنا شوید که زیرکلاسی از **Context** است. یک RequestContext با اجرای پردازشگر زمینه تمپلیت، زمینههای (context) بیشتری را به تمپلیت اضافه میکند. جینجا ۲ به پردازشگر زمینه نیاز ندارد چرا که از فراخوانی فانکشن به طور مستقیم، پشتیبانی میکند.
در نهایت، روش رندر کردن **Template**، یک زمینه را دریافت میکند و خروجی را رندر میکند. خروجی میتواند HTML، ایمیل ، CSS، XML یا هر خروجی متنی باشد.
اگر شما ترتیب جستجو کردن تمپلیتها را متوجه شوید، میتوانید با توجه به اهداف خود، تمپلیتهای لود شده را بازنویسی کنید. در زیر چند سناریو وجود دارد که میتواند مفید باشد:
- بازنویسی یک تمپلیت از اپ شخص ثالث با تمپلیتهای تعریف شده در پروژه خودتان
- از جینجا ۲ برای بخشهایی که عملکرد در آنها مطرح است استفاده کنید و برای سایر بخشها از DTL استفاده کنید
حالت اول به حاطر استفاده از فریمورکهایی مانند بوتسترپ، وضعیت رایجی است.
**مادام O**
برای اولین بار بعد از چند هفته، دفتر استیو در گوشه سالن پر از جنب و جوش بود. با استخدامهای جدید، تیم پنج نفره متشکل از برَد، اِوان، جیکوب، سو و استیو است. مانند یک تیم ابرقهرمانی، تواناییهای آنها به طرز شگفتانگیزی متعادل است.
برد و اوان، کدنویسی میکنند. در حالی که اوان روی جزییات متمرکز است برد مسول کنترل دورنمای کارهاست. جیکوب با توانایی بررسی گوشه و کنارها، فرد مناسبی برای تست کردن است. سو مسؤولیت بازاریابی و طراحی را بر عهده دارد.
در واقع، کل طراحی قرار بود توسط یک آژانس طراحی آوانگارد انجام شود. نزدیک دو ماه طول کشید تا یک طرح اولیه پر رنگ و لعاب که مورد علاقه مدیریت بود آماده شود. دو هفته دیگر هم طول کشید تا یک ماکت HTML از طراحی فوتوشاپی تهیه شود. با اینحال همانطور که انتظار میرفت به خاطر کند و دست و پاگیر بودن در موبایلها، این طراحی کنار گذاشته شد.
استیو از شکست طرحی، که الان به طور گسترده طرح **استفراغ تک شاخ** نامیده میشد، ناامید شده بود. هارت با او تماس گرفته بود و از عدم پیشرفت پروژه برای گزارش دادن به مدیریت، نگران بود.
هارت با لحنی تلخ به استیو یادآوری کرد «ما زمانهای ذخیره پروژه را مصرف کردهایم. دیگر نمیتوانیم منتظر شگفتیهای لحظه آخر باشیم».
همین موقع بود که سو، که به طرز غیر منتظرهای ساکت بود گفت به کمک بوتسترپ توییتر، روی یک ماکت پروژه کار میکرده است. سو یک هکر توسعه بود، یک کدنویس مشتاق و بازاریابی خلاق.
او اعتراف کرد که فقط مهارتهای ابتدایی HTML را دارد. با اینحال ماکت او به طرز شگفتانگیزی کامل و برای کاربران سایر شبکههای اجتماعی معاصر، آشنا به نظر میرسید. از همه مهمتر، طراحی او ریسپانسیو بود و روی هر دستگاه موبایل یا تبلت به درستی کار میکرد.
همه مدیران به غیر از شخصی به نام مادام O، به اتفاق با طرح سو موافقت کردند. یک روز جمعه بعد از ظهر، او به سرعت کنار میز سو آمد و در مورد همه چیز شروع به سؤال کردن کرد. از رنگ پسزمینه تا سایز کرسر موس. سو سعی کرد با متانت و آرامش قابل توجهی همه چیز را توضیح دهد.
یک ساعت بعد، وقتی استیو تصمیم گرفت که مداخله کند، مادام O داشت میپرسید که چرا تصویر پروفایل باید در یک دایره باشد به جای آنکه مربع باشد. استیو جواب داد «چنین تغییر گستردهای در سراسر سایت به هیچ وجه در زمان مورد نظر تمام نمیشود». مادام O نگاهش را به سمت او برد و لبخند حیلهگرانهای زد. ناگهان استیو موجی از شادی و امید را حس کرد. به شدت احساس آرامش میکرد و هیجانزده بود. صدای خودش را شنید که با خوشحالی با تمام درخواستهای مادام O موافقت میکند.
بعدها استیو یادگرفت که مادام Optimism یک ذهنگرای کوچک است که میتواند بر ذهنهای مستعد تاثیر بگذارد. تیم او دوست داشت که در هر موقعیتی این موضوع را پیش بکشد.
## استفاده از بوتسترپ
اینروزها به ندرت کسی تمام یک وبسایت را از ابتدا طراحی میکند. فریمورکهای CSS مانند Twitter's Bootstrap یا Zurb's Foundation با امکاناتی مانند سیستم گرید، تایپوگرافی عالی و استایلهای از قبل آماده شده، نقطه شروع آسانی هستند. اکثر آنها از طراحی وب ریسپانسیو استفاده میکنند که باعث میشود سایت شما برای موبایلها مناسب باشد.

یک وبسایت که از بوتسترپ ورژن 3.3 تغییریافته و با استفاده از اسکلتبندی پروژه Edge طراحی شده است
ما از بوتسترپ استفاده خواهیم کرد اما روش کار برای سایر فریمورکهای CSS هم شبیه به همین است. سه راه مختلف برای بهکارگیری بوتسترپ در پروژه وجود دارد:
- ** یک اسکلتبندی پروژه پیدا کنید**: اگر هنوز پروژه را شروع نکردهاید پس بهتر است یک اسکلتبندی پروژه که بوتسترپ هم داشته باشد پیدا کنید. یک اسکلتبندی پروژه مانند Edge (که واقعاً توسط شما ساخته شده) میتواند به عنوان ساختار اولیه در هنگام ساخت پروژه استفاده شود، مانند زیر:
`$ django-admin.py startproject -- template=https://github.com/arocks/edge/archive/master.zip -- extension=py,md,html myproj`
همچنین میتوانید از یکی از تمپلیتهای cookiecutter که دارای پشتیبانی بوتسترپ هستند استفاده کنید.
- **استفاده از پکیج**: اگر پروژه خود را شروع کردهاید، آسانترین گزینه استفاده از یک پکیج مانند [django-bootstrap4](https://github.com/zostera/django-bootstrap4) است.
- **کپی کردن دستی**: هیچکدام از گزینههای قبلی تضمین نمیکند که نسخه بوتسترپ آنها، آخرین نسخه منتشر شده باشد. نسخههای بوتسترپ چنان با سرعت منتشر میشوند که نویسندگان پکیجها کار بسیار سختی برای بهروز نگهداشتن بوتسترپ مورد استفاده در پکیجها دارند. بنابراین، اگر می خواهید با آخرین نسخه بوتسترپ کار کنید، بهترین گزینه آن است که خودتان آن را از [http://getbootstrap.com](http://getbootstrap.com) دانلود کنید. مطمئن شوید که یادداشتهای مربوط به انتشار نسخه را خوانده باشید تا ببینید که آیا نیاز است برای هماهنگی با نسخه جدید، تغییراتی در کد خود بدهید یا نه. پوشه dist را که شامل css، js و پوشه فونتها است، در پوشه static که درون پوشه اصلی پروژه است کپی کنید. مطمئن شوید که مسیر این پوشه در متغیر STATICFILES_DIRS در فایل settings.py تعریف شده باشد:
`STATICFILES_DIRS = [os.path.join(BASE\_DIR, "static")]`
اکنون میتوانید بخشهای بوتسترپ را به شکل زیر در پروژه خود اضافه کنید:
```html
{% load staticfiles %}
<head>
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
```
**اما همه آنها شبیه هم هستند!**
بوتسترپ ممکن است برای شروع سریع، فوقالعاده باشد. با اینحال بعضی مواقع توسعهدهندهها تنبل میشوند و حوصله ندارند که ظاهر پیشفرض آن را تغییر دهند. این کار باعث می شود که سایت تاثیر ضعیفی بر کاربران شما بگذارد و باعث شود آنها ظاهر سایت را زیادی آشنا و غیر جذاب بدانند.
نسخه [Bootstrap 4](https://getbootstrap.com/docs/4.0/) به همراه ویژگیهایی متشر شده است که جذابیت ظاهری را افزایش داده است. شما میتوانید یک فایل custom.scss بسازید و همه چیز را مانند رنگبندی و نقاط شکست گریدها را شخصیسازی کنید. مستندات این نسخه شرح داده است که چطور میتوانید این فایلها را کامپایل و تبدیل به استایلشیت کنید.
با تشکر از جامعه توسعهدهندگان اطراف بوتسترپ، وبسایتهای بسیار زیادی مانند [bootswatch.com](https://bootswatch.com/) وجود دارد که استایلشیتهای آمادهای دارند که فقط کافی است فایل bootstrap.min.css را در پروژه خود جایگزین کنید.
آخرین نکته، شما میتوانید کلاسهای CSS خود را به کمک تغییر دادن نام کلاسهایی مانند row یا col-lg-9 با تگهای معنایی مانند main یا article، با معناتر کنید. این کار را میتوانید با چند خط کد SASS و توسعه دادن کلاسهای بوتسترپ به شکل زیر انجام دهید:
```
@import "bootstrap";
body > main { @extend .row;`
article { @extend .col-lg-9; } }
```
این کار به کمک ویژگی به نام میکسین (به نظر آشنا نمیآید؟) امکانپذیر میشود. به کمک فایلهای اصلی SASS، بوتسترپ میتواند به صورت کامل برای نیازهای شما، شخصیسازی شود.
**جایگزینهای سبک**
مرورگرهای قدیمی در مورد اجرا کردن CSS بسیار غیر یکسان عمل میکردند. آنها نه تنها پیشوندهای مخصوص تأمینکننده مانند -WebKit-transition را داشتند بلکه ویژگیهای مخصوص به خود را هم داشتند. مروگرهای جدیدتر، استانداردهای مدرن را بهتر دنبال میکنند.
حالا ما علاوه بر این صفحهبندیهای قدرتمندی مانند flexbox را داریم که پیچیدگی کد را کمتر میکند. تمام اینها باعث بوجود آمدن فریموکهای سبک CSS شده است.
برای مثال، [Pure.css ](https://purecss.io/) به صورت minify شده و gzip شده ولی باویژگیهای قابل قبول، فقط ۳.۸ کیلوبایت است. مشابه آن [mini.css](https://minicss.org/) با تمرکز بر دستگاههای موبایل و مرورگرهای مدرن طراحی شده و به صورت gzip، زیر ۷ کیلوبایت است. برای مقایسه بوتسترپ با تمام ماژولها و به صورت gzip شده، ۲۵ کیلوبایت است.
با اینکه این فریمورکهای سبک میتواند سرعت لود شدن اولیه را بهبود بدهند مطمئن باشید که با تمام مرورگرهایی که ممکن است کاربران شما از آن استفاده کنند، اینها را تست کنید. ابزارهایی مانند [CanIUse.com ](https://caniuse.com/) میتواند با نشان دادن اینکه کدام ویژگی در کدام مرورگر و پلتفرم پشتیبانی میشود یا نه، کمک خوبی باشد. بوتسترپ، با طیف وسیع مشتریانش، تقریباً در پشتیبانی از نسخههای قدیمیتر خود، خوب است.
## الگوهای تمپلیت
زبان تمپلیت جنگو بسیار ساده است. با اینحال شما با پیروی از چند الگوی طراحی زیبا، میتوانید در زمان، به خوبی صرفهجویی کنید. بیایید به تعدادی از آنها نگاهی بکنیم.
** الگو — درخت وراثت تمپلیت**
**مشکل**: تمپلیت به تعداد زیادی markup در صفحههای مختلف نیاز دارد.
**راه حل**: هرجا که ممکن است از الگوی وراثت استفاده کنید و تکه کدهای مورد نظر را include کنید.
**جزییات مشکل**
کاربرها نیاز دارند که صفحات وبسایت از یک الگوی یکسان پیروی کند. برخی عناصر مانند منوی دسترسی، هدر و فوتر در تمام اپلیکیشن ها دیده میشوند و تکرار آنها در هر صفحه، دست و پا گیر خواهد بود.
اکثر زبانهای تمپلیت، یک مکانیزم include دارند. محتوای یک فایل دیگر، معمولاً یک تمپلیت، در محلی درون فایل تمپلیت دیگری فراخوانی میشود و در آنجا اضافه میشود. این موضوع میتواند در یک پروژه بزرگ خسته کننده باشد.
توالی قطعاتی که در یک تمپلیت فراخوانی میشوند معمولاً یکسان است. ترتیب فراخوانی بسیار مهم است و کنترل کردن آن برای یافتن خطاها دشوار است. به صورت ایدهآل، ما باید بتوانیم یک ساختار پایهای درست کنیم. صفحههای جدید باید به این ساختار پایه اضافه شوند و فقط تغییرات یا اضافات را در آن اعمال کنند.
**جزییات راه حل**
تمپلیتهای جنگو، یک مکانیزم توسعه قوی دارند. شبیه به ساختار کلاسها در زبانهای برنامهنویسی، یک تمپلیت میتواند از طریق وراثت، توسعه داده شود. اما برای اینکه این روش استفاده شود باید فایل پایه دارای ساختار بلوکی مانند زیر باشد.

تمپلیت ماژولار پایه میتواند توسط صفحات دیگر توسعه داده شود و ترکیببندی یکسان و منعطفی را ایجاد کند.
فایل base.html به طور مرسوم، ساختار پایه برای کل سایت است. این تمپلیت معمولاً به صورت یک HTML خوشفرم (یعنی با یک مقدمه و با باز و بسته شدن تگهای HTML) ساختاربندی شده و با چندین placeholder که به صورت تگهای {% block tags %} نمایش داده میشوند پر شده است. برای مثال، یک فایل base.html ساده شبیه به نمونه زیر خواهد بود:
```html
<html>
<body>
<h1>{% block heading %}Untitled{% endblock %}</h1> {% block content %}
{% endblock %}
</body>
</html>
```
در اینجا دو بلوک داریم؛ heading و content که میتوانند در فایلهای دیگر بازنویسی شوند. شما میتوانید صفحههای خاصی بسازید که این بلوکها را بازنویسی کنند و صفحه پایه را توسعه دهند. برای مثال در اینجا یک صفحه About داریم:
```html
{% extends "base.html" %}
{% block content %}
<p> This is a simple About page </p> {% endblock %}
{% block heading %}About{% endblock %}
```
مجبور نیستیم ساختار پایه را هر بار تکرار کنیم. همچنین میتوانیم بلوکها را به هر ترتیبی که میخواهیم بنویسیم. نتیجه رندر شده نهایی، هر بلوک را در جای صحیح خود که در فایل base.html مشخص شده، قرار خواهد داد.
اگر ساختار وراثتی تمپلیت، یک بلوک را بازنویسی نکرده باشد، محتوای موجود در والد، استفاده خواهد شد. در مثال قبل اگر بلوک heading بازنویسی نشود، هدینگ موجود در والد که **Untitled** است، استفاده خواهد شد. شما میتوانید به طور مشخص از محتوای تمپلیت والد، با استفاده از {{ block.super }}، بهره ببرید. این روش برای مواقعی خوب است که میخواهید به محتوای والد چیزی را اضافه کنید.
یک تمپلیت که از الگوی وراثت استفاده میکند میتواند دوباره به ارث برده شود و زنجیره ارثبری را ایجاد کند. این الگو میتواند باعث ایجاد چندین فایل پایه با ترکیببندیهای مختلف شود مثلاً یک الگوی صفحه تک ستونی درست کرد یا یک تمپلیت پایه هم برای section های مختلف مانند صفحه وبلاگ سایت درست کرد.
معمولاً تمامزنجیرهای وراثت، به فایل base.html میرسند برای همین به آن الگوی _درخت وراثت تمپلیت_ گفته میشود. البته لازم نیست همهجا از این الگو استفاده کرد. صفحات خطای **404.html** و **500.html** معمولاً به ارث نمیرسند و برای جلوگیری از خطاهای بیشتر اکثر تگهای تمپلیت در آنها حذف میشود.
راه دیگر رسیدن به این هدف ممکن است استفاده از پردازشگر زمینه باشد. شما میتوانید یک پردازشگر زمینه درست کنید که یک متغیر زمینه را برای تمام تمپلیتهای شما به صورت عمومی قابل استفاده کند. ولی اینکار برای بخشهایی مانند سایدبار توصیه نمیشود چرا که با خارج کردن ارائه محتوا از لایه تمپلیت، قاعده تفکیک لایهها (جدا بودن منطق برنامه از نمایش محتوا) را نقض میکند.
**الگو —لینک فعال**
**مشکل**: منوی دسترسی یک جزء تکراری در اکثر صفحههاست. با اینحال، لینک فعال باید نشان دهد که کاربر در کدام صفحه است.
**راه حل**: با مشروط کردن لینک فعال به کمک تعریف متغیرهای زمینه یا بر اساس مسیر درخواست شده
**جزییات مشکل**
راه ساده به کارگیری لینک فعال در منوی دسترسی، تنظیم دستی آن در هر صفحه است که نه ضد اشتباه است و نه بر اساس قوانین DRY (خودت رو تکرار نکن).
**جزییات راه حل**
راه حلهای زیادی به غیر از روشهای مبتنی بر جاوااسکریپت، وجود دارد که لینک فعال ایجاد کند. این راه حلها میتواند در دو گروه بر مبنای تمپلیت و بر مبنای تگ اختصاصی، تقسیم شوند.
**راه حل بر مبنای تمپلیت**
با تعریف کردن متغیر active_link هنگام include کردن تکه کد مربوط به منوی دسترسی. این راه حل هم ساده است و هم به راحتی پیادهسازی میشود.
در هر تمپلیت لازم است این تکه کد را اضافه کنید (یا آن را به ارث ببرید):
{% include "\_navbar.html" with active\_link='link2' %}
فایل \_navbar.html شامل یک منوی دسترسی با تعدادی متغیر برای تعریف لینکهای فعال است:
```html
{# \_navbar.html #}
<ul class="nav nav-pills">
` `<li{% if active\_link == "link1" %} class="active"{% endif %}><a href="{% url 'link1' %}">Link 1</a></li>
` `<li{% if active\_link == "link2" %} class="active"{% endif %}><a href="{% url 'link2' %}">Link 2</a></li>
` `<li{% if active\_link == "link3" %} class="active"{% endif %}><a href="{% url 'link3' %}">Link 3</a></li>
</ul>
```
**تگهای اختصاصی**
تمپلیتهای جنگو، مجموعه همهکارهای از تگهای داخلی را ارائه میکنند. بسیار ساده است که تگ اختصاصی خود را بنویسید. با توجه به اینکه هر تگ اختصاصی درون یک اپ تعریف میشود یک پوشه templatetags درون اپ بسازید. این پوشه باید یک پکیج باشد در نتیجه باید یک فایل خالی به نام \_\_init\_\_.py داشته باشد. سپس تگ اختصاصی خود را در یک فایل پایتون بنویسید. برای مثال برای این الگوی لینک فعال، ما میتوانیم یک فایل به نام nav.py، با محتویات زیر بسازید:
\# app/templatetags/nav.py
```python
from django.core.urlresolvers import resolve from django.template import Library
register = Library()
@register.simple_tag
def active_nav(request, url):
url_name = resolve(request.path).url_name
if url_name == url:
return "active"
return ""
```
این فایل یک تگ اختصاصی به نام active\_nav درست میکند. این فانکشن یک مسیر URL را از آرگومان ریکوئست میگیرد (مثلاً /about/، برای توضیحات بیشتر در مورد مسیرهای URL، بخش ۴: _ویوها و URLها_ را ببینید ). سپس از فانکشن resolve() استفاده میشود تا نام-الگوهای URL (در فایل urls.py) بر اساس آرگومان ورودی جستجو شود. در نهایت، رشته "active" در صورتی بازگردانده میشود که نام-الگو با نام-الگو مورد نظر یکی باشد.
نحوه صدا زدن این تگ اختصاصی در تمپلیت به شکل {% active\_nav request 'pattern\_name' %} است. توجه داشته باشید که ریکوئست باید به هر صفحهای که از این تگ استفاده شده، ارجاع داده شود.
استفاده از یک تگ در تعداد زیادی صفحه میتواند سنگین باشد. برای همین ما یک پردازشگر زمینه داخلی به نام TEMPLATE_CONTEXT_PROCESSORS در فایل settings.py اضافه میکنیم. در نتیجه ریکوئست، در متغیرهای ریکوئست، در کل سایت قابل دسترس خواهد بود. مانند زیر:
\# settings.py
```
[
'django.core.context_processors.request',
]
```
حالا تنها کاری که باقی مانده این است که از این تگ اختصاصی در تمپلیتها جهت تعیین لینک فعال استفاده کنید:
{# base.html #}
{% load nav %}
```html
<ul class="nav nav-pills">
<li class={% active\_nav request 'active1' %}><a href="{% url 'active1' %}">Active 1</a></li>
<li class={% active\_nav request 'active2' %}><a href="{% url 'active2' %}">Active 2</a></li>
<li class={% active\_nav request 'active3' %}><a href="{% url 'active3' %}">Active 3</a></li>
</ul>
```
## خلاصه
در این بخش، ما به ویژگیهای تمپلیتهای جنگو نگاه کردیم. هرچند که تغییر دادن ربان تمپلیت جنگو ساده است اما بسیاری از افراد در مورد تغییر دادن آن احتیاط میکنند. با اینحال مهم است که فلسفه طراحی زبان تمپلیت جنگو را قبل از آنکه آن را با نمونههای جایگزین تغییر دهیم، بدانیم.
در بخش بعد به یکی از ویژگیهای بسیار جذاب جنگو، یعنی بخش ادمین و نحوه عملکرد و تغییر آن نگاه میکنیم.
================================================
FILE: 06-AdminInterface/README.md
================================================
<div dir='rtl'>
<h1>فصل ۶ - رابط کاربری ادمین</h1>
در این بخش، موضوعات زیر را بررسی خواهیم کرد:
* شخصی سازی بخش ادمین
* بهینه سازی مدل ها در قسمت ادمین
* عرف و تنظیمات متداول برای ادمین
* تغییر ویژگی ها
ویژگی برجسته جنگو، رابط کاربری ادمین است،که برگ برنده آن در رقابت است. این بخش یک برنامه داخلی(built-in) بوده که به صورت خودکار یک رابط کاربری را برای افزودن یا ویرایش محتوای وبسایت، تولید می کند.برای خیلی از کاربران،بخش ادمین یک برنامه نجات دهنده است؛ که عملیات خسته کننده ی ساخت یک رابط کاربری برای ادمین و مدل های داخل پروژه را به دوش می کشد.
بخش ادمین این امکان را برای تیم شما فراهم می کند که به صورت همزمان محتوا را افزوده و توسعه کد را ادامه دهید. زمانی که مدل های شما آماده باشند و انتقال آن ها(migrations) صورت گیرد، تنها نیاز است تا یک یا دو خط کد به برنامه افزوده شود تا مدل شما به رابط کاربری ادمین اضافه گردد. نحوه انجام این عملیات را در ادامه خواهیم دید.
### استفاده از رابط کاربری ادمین
در یک پروژه ی جدید، رابط کاربری ادمین به صورت پیش فرض فعال شده است. پس از راه اندازی سرور توسعه برنامه، شما می توانید یک صفحه ورود به رابط ادمین را در آدرس http://127.0.0.1:8000/admin مشاهده کنید.
اگر مشخصات ادمین سایت را(superuser) تعریف کرده باشید( یا مشخصات هر کاربر دیگر - شامل نام کاربری و رمز عبور و...)، می توانید همانطور که در تصویر دیده می شود، به رابط ادمین وارد شوید.

تصویری از رابط کاربری ادمین در جنگو برای یک پروژه جدید
اگر قبلا از جنگو استفاده کرده اید،متوجه خواهید شد که ظاهر رابط ادمین بهتر از قبل شده است، بخصوص آیکن های svg در صفحه های نمایش با دی پی آی بالا (high-DPI) بهبود یافته اند.
گرچه مدل های شما در حال حاضر قابل مشاهده نیست، مگر آن که مدل ها را از طریق بخش ادمین ثبت کنید. بخش ادمین در برنامه شما در فایل admin.py در دسترس است. برای مثال، در مسیر sightings/admin.py، مدلی به نام Sighting را به شکل زیر ثبت کرده ایم:
> <p dir='ltr'>from django.contrib import admin from . import models
> <br>
> admin.site.register(models.Sighting)
> </p>
اولین آرگومان تابع register کلاس مدل را برای افزودن به بخش ادمین سایت مشخص می کند. در اینجا، آرگومان دوم تابع register؛ که مدل کلاس ادمین (ModelAdmin) بوده، حذف شده است. بنابراین رابط کاربری که به صورت پیش فرض تعریف شده است را خواهیم داشت. در ادامه خواهیم دید که چطور می توانیم مدل ادمین خود را شخصی سازی کنیم.
### فانوس دریایی
"داری قهوه میخوری؟" صدایی از گوشه ی ابدارخانه این را پرسید. «سو» نزدیک بود که قهوه اش را بریزد. مردی قد بلند که لباس قرمز و آبی پوشیده بود ایستاد و دست به کمر، لبخندی زد. نشانی که بر سینه او نقش بسته بود؛ به حروف بزرگ نوشته بود،کاپیتان آبویس (capitan obvious).
سو در حالی که لکه قهوه را پاک میکرد گفت "خدای من!".
کاپیتان گفت "ببخشید که ترسوندمت، چه مورد اضطراری پیش اومده؟"
زنی آرام از بالا گفت: "نمیبینی که دختره توی باغ نیست؟"
سو؛ سایه ای که به آرامی از سالن رو باز به پایین می آمد را دنبال می کرد. بخشی از صورت زن به وسیله موهای مشکی اش پوشیده شده بود.
کاپیتان گفت:"سلام هکسا" سپس ادامه داد،"آخرش پیام سایت SuperBook چی بود؟"
اندکی بعد، همه آن ها در دفتر کار «استیو»، در حالی که به مانیتور او نگاه میکردند؛ جمع شدند.
«ایوان» گفت: "میبینی! گفته بودم که هیچ فانوسی در صفحه ی اصلی سایت نیست. ما هنوز داریم روی این ویژگی کار می کنیم."
استیو گفت: "صبر کن، بذار با یک اکانت غیرپرسنلی وارد بشم".
طی چند ثانیه، صفحه سایت تازه سازی شد و فانوس قرمز به طور برجسته ای در بالای صفحه سایت قرار گرفته بود.
کاپیتان فریاد زد:"خودشه! همون فانوسی که بهت گفته بودم".
استیو گفت:"یک لحظه صبر کن!". او سورس فایلی که برای ویژگی های جدید سایت ثبت شده بود را بررسی کرد. با یک نگاه به ویژگی های فانوس متوجه شد که چه چیزی اشتباه شده است.
> <p dir='ltr'>
> if switch_is_active(request, 'beacon') and not request.user.is_staff():
> <br>
> beacon.activate()
> </p>
استیو گفت:"ببخشید، یه خطای منطقی اتفاق افتاده.بجای اینکه این ویژگی را فقط برای پرسنل فعال کنم، ناخواسته آن را برای کسانی که جز پرسنل نبودند فعال کردم.حالا درست شد. بابت این اشفتگی عذر میخوام".
کاپیتان با یک نگاه تاسف بار پرسید:"پس هیچ مورد اضطراری وجود نداشت؟". هکسا با آرنج به بازوی او زد و گفت:"فکر نکنم کاپیتان!"
سپس صدای مهیبی آمد. همه به سمت راهرو دویدند. ظاهرا مردی اشتباها از طریق یکی از دیوارهای شیشه ای، وارد اتاق شده بود. در حالی که تکه های شکسته شیشه را در دستش تکان میداد؛ ایستاد و گفت: "ببخشید،با نهایت سرعت خودم رو رسوندم،دیر رسیدم؟".
هکسا خندید و گفت: "نه «بیلتز» منتظرت بودیم که برسی".
### بهینه سازی مدل ها برای بخش ادمین:
در اینجا مثالی موجود است که مدل ادمین را برای کارایی و جلوه ای بهتر، بهینه سازی کرده است. می توانید به تفاوت های میان دو اسکرین شات زیر نگاهی بیاندازید تا ببینید که چطور چند خط کد، می تواند انقدر تفاوت ایجاد کند.

حالت پیش فرض نمایش لیستی بخش ادمین، برای مدل sightings
پس از پیاده سازی روش مذکور برای شخصی سازی بخش ادمین، همان اطلاعات مشابه با دسترسی آسان تر و بهتری به شکل زیر نمایش داده می شوند:

نمایش لیستی بهینه شده بخش ادمین برای مدل sightings
برنامه ادمین به اندازه کافی هوشمند است که موارد زیادی را از مدل شما به صورت خودکار درک و دریافت کند. اگرچه گاهی اطلاعات اشاره شده قابل بهبود است. این عمل معمولا به صورت اضافه کردن یک ویژگی یا یک متد به مدل خودتان(بجای مدل ادمین) انجام می شود.
در اینجا کد مربوط به مدل بهینه شده ی sightings را داریم:
<pre dir='ltr' style='background-color:#D3D3D3'>\# <span class="pl-s1">models</span>.<span class="pl-s1">py</span>
<span class="pl-k">class</span> <span class="pl-v">Sighting</span>(<span class="pl-s1">models</span>.<span class="pl-v">Model</span>):
<span class="pl-s1">superhero</span> <span class="pl-c1">=</span> <span class="pl-s1">models</span>.<span class="pl-v">ForeignKey</span>(
<span class="pl-s1">settings</span>.<span class="pl-v">AUTH_USER_MODEL</span>, <span class="pl-s1">on_delete</span><span class="pl-c1">=</span><span class="pl-s1">models</span>.<span class="pl-v">CASCADE</span>)
<span class="pl-s1">power</span> <span class="pl-c1">=</span> <span class="pl-s1">models</span>.<span class="pl-v">CharField</span>(<span class="pl-s1">max_length</span><span class="pl-c1">=</span><span class="pl-c1">100</span>)
<span class="pl-s1">location</span> <span class="pl-c1">=</span> <span class="pl-s1">models</span>.<span class="pl-v">ForeignKey</span>(<span class="pl-v">Location</span>, <span class="pl-s1">on_delete</span><span class="pl-c1">=</span><span class="pl-s1">models</span>.<span class="pl-v">CASCADE</span>)
<span class="pl-s1">sighted_on</span> <span class="pl-c1">=</span> <span class="pl-s1">models</span>.<span class="pl-v">DateTimeField</span>()
<span class="pl-k">def</span> <span class="pl-en">__str__</span>(<span class="pl-s1">self</span>):
<span class="pl-k">return</span> <span class="pl-s">"{}'s power {} sighted at: {} on {}"</span>.<span class="pl-en">format</span>(
<span class="pl-s1">self</span>.<span class="pl-s1">superhero</span>,
<span class="pl-s1">self</span>.<span class="pl-s1">power</span>,
<span class="pl-s1">self</span>.<span class="pl-s1">location</span>.<span class="pl-s1">country</span>,
<span class="pl-s1">self</span>.<span class="pl-s1">sighted_on</span>)
<span class="pl-k">def</span> <span class="pl-en">get_absolute_url</span>(<span class="pl-s1">self</span>):
<span class="pl-k">from</span> <span class="pl-s1">django</span>.<span class="pl-s1">urls</span> <span class="pl-k">import</span> <span class="pl-s1">reverse</span>
<span class="pl-k">return</span> <span class="pl-en">reverse</span>(<span class="pl-s">'sighting_details'</span>, <span class="pl-s1">kwargs</span><span class="pl-c1">=</span>{<span class="pl-s">'pk'</span>: <span class="pl-s1">self</span>.<span class="pl-s1">id</span>})
<span class="pl-k">class</span> <span class="pl-v">Meta</span>:
<span class="pl-s1">unique_together</span> <span class="pl-c1">=</span> (<span class="pl-s">"superhero"</span>, <span class="pl-s">"power"</span>)
<span class="pl-s1">ordering</span> <span class="pl-c1">=</span> [<span class="pl-s">"-sighted_on"</span>]
<span class="pl-s1">verbose_name</span> <span class="pl-c1">=</span> <span class="pl-s">"Sighting & Encounter"</span>
<span class="pl-s1">verbose_name_plural</span> <span class="pl-c1">=</span> <span class="pl-s">"Sightings & Encounters"</span>
</pre>
حال نحوه استفاده ادمین از این ویژگی ها را بررسی می کنیم:
<h4>__str__():</h4>
بدون این متد، عناوین فلیدهای مدل شما به صورتی کسالت بار و ناخوانا نمایش داده می شوند. تمام فیلدها با فرمتی به شکل
Sighting:Sighting object
نمایش داده می شوند. سعی کنید که تمام ویژگی های منحصر به فرد آبجکت را در نمایش str(یا در نمایش Unicode برای پایتون ورژن ۲)، همچون نام آبجکت و ورژن آن؛ نمایش دهید. هر آنچه که به نمایش بدون ابهام آبجکت در ادمین کمک کند، کاربردی است.
#### get_absolute_url():
این متد برای جابجا شدن بین صفحه سایت ادمین و نمایش جزئیات آبجکت متناظر روی سایت شما(صفحه اصلی) کارآمد است.اگر این متد تعریف شده باشد، یک دکمه با عنوان "نمایش بر روی سایت" در کنج بالا و دست راست صفحه ی ویرایش آبجکت در داخل ادمین؛ نشان داده می شود
- ordering:
بدون این گزینه ی متا،ورودی های شما با ترتیبی که از دیتابیس دریافت شده اند،نمایش داده می شوند. همانطور که تصور می کنید، اگر تعداد زیادی آبجکت داشته باشید؛ این شیوه مرتب کردن برای ادمین سایت جالب نخواهد بود. به طور معمول، ادمین ها ترجیح می دهند که ورودی های جدیدتر را در ابتدا مشاهده کنند؛ بنابراین مرتب سازی با ترتیب زمانی معکوس(با نشان منفی) رایج است.
- verbose_name:
اگر این ویژگی را حذف کنید، نام مدل شما از حالت کمل کیس با حروف اول بزرگ، به حالت ساده تبدیل می شود.در این مثال، از این ویژگی به صورت بیهوده و برای تبدیل Sighting به Sighting & Encounter استفاده شده است.
اما بعضی مواقع اسمی که به صورت خودکار برای مدل ساخته می شود، گیج کننده است؛ پس شما می توانید نامی که برای کاربر خوانا باشد را برای نمایش در بخش ادمین تعیین کنید.
- verbose_name_plural
مجددا، حذف این ویژگی نتایج جالبی خواهد داشت. از آنجا که جنگو برای اسامی مدل ها حرف جمع s را در انتها می آورد، لغت جمع به صورت"Sighting & Encounters"(در صفحه ادمین و نه جای دیگر)نمایش داده می شود. پس بهتر است با استفاده از این ویژگی، شکل صحیح آن را تعریف کنیم.
پیشنهاد می شود که تگ متا مذکور را نه تنها برای رابط ادمین، بلکه برای نمایش بهتر در قسمت های شل و فایل های لاگ نیز، تعریف کنید.اگرچه، شما می توانید از ویژگی های بسیار بیشتر ادمین برای شخصی سازی کلاس مدل ادمین بهره ببرید. در این مثال، شخصی سازی را به صورت زیر انجام می دهیم:
`# admin.py`
<pre dir='ltr' style='background-color:#D3D3D3'><span class="pl-k">class</span> <span class="pl-v">SightingAdmin</span>(<span class="pl-s1">admin</span>.<span class="pl-v">ModelAdmin</span>):
<span class="pl-s1">list_display</span> <span class="pl-c1">=</span> (<span class="pl-s">'superhero'</span>, <span class="pl-s">'power'</span>, <span class="pl-s">'location'</span>, <span class="pl-s">'sighted_on'</span>)
<span class="pl-s1">date_hierarchy</span> <span class="pl-c1">=</span> <span class="pl-s">'sighted_on'</span>
<span class="pl-s1">search_fields</span> <span class="pl-c1">=</span> [<span class="pl-s">'superhero'</span>]
<span class="pl-s1">ordering</span> <span class="pl-c1">=</span> [<span class="pl-s">'superhero'</span>]
<span class="pl-s1">admin</span>.<span class="pl-s1">site</span>.<span class="pl-en">register</span>(<span class="pl-s1">models</span>.<span class="pl-v">Sighting</span>, <span class="pl-v">SightingAdmin</span>)</pre>
بگذارید تا به این ویژگی ها نگاهی دقیق تر داشته باشیم.
- list_display:
این گزینه نمونه های ساخته شده ازمدل را به صورت جدولی نشان می دهد. بجای استفاده از \__str__ این گزینه تمام فیلدهای ذکر شده را به صورت ستونی قابل مرتب سازی نشان می دهد. این قابلیت برای مرتب سازی مدل با بیش از یک ویژگی ، ایده آل است.
- date_hierarchy:
تعیین هر فیلد تاریخ-زمان با این ویژگی، یک توضیح دقیق تر برای هر تاریخ ارائه خواهد داد.( به سال های قابل کلیک در زیر کادر جستجو توجه کنید)
- search_fields:
این گزینه یک کادر جستجو بالای لیست ایجاد می کند. هر عبارتی که جستجو شود در فیلدهای مشخص شده جستجو خواهد شد. بنابراین فقط فیلدهای کاراکتری و فیلدهای متنی می توانند در اینجا استفاده شوند.
- ordering:
این گزینه به ترتیب پیش فرض مدل شما تقدم می یابد. این مورد زمانی کاربردی است که شما نیاز به ترتیب نمایش متفاوتی در صفحه ادمین دارید؛ که در اینجا ترجیحا اعمال شده است.
ما فقط زیرمجموعه ای از پر استفاده ترین ویژگی های ادمین را ذکر کردیم. نوع خاصی از وبسایت ها، استفاده زیادی از رابط ادمین دارند؛در این موارد، شدیدا توصیه می شود که به مستندات جنگو مراجعه و بخش مربوط به ادمین را مطالعه کنید.
#### هر کسی نباید ادمین باشه
از آنجا که ساخت رابط ادمین بسیار آسان است، ممکن است که افراد به غلط از آن استفاده کنند. برخی با فعال کردن تیک staff(کارکنان) برای کاربران، به صورت بی رویه به آن ها دسترسی برای مدیریت این بخش می دهند.
متاسفانه، این چیزی نیست که رابط ادمین برای آن ساخته شده. همانطور که از معنی لغت staff پیدا است، ابزاری داخلی است برای کارکنان تا محتوا را وارد کنند. این یک ابزار فعال در محیط تولید است(زمانی که سایت بر روی سرور اپلود شده)، اما برای کاربران نهایی وبسایت شما در نظر گرفته نشده است.
بهتر است از رابط ادمین برای وارد کردن دیتا های ساده استفاده شود. برای مثال،در یک پروژه ی اینترانت در سطح یک مدرسه مشاهده کردم، هر یک از معلم ها به عنوان یک ادمین برای اپ جنگو در نظر گرفته شده بود. این تصمیم اشتباهی بود چرا که محیط کاربری ادمین، معلمان را گیج می کرد.
گردش کار برای برنامه ریزی کلاس ، شامل بررسی برنامه های معلمان و دانش آموزان دیگر است. استفاده از رابط ادمین به آن ها نمای واضحی از پایگاه داده می دهد. در این حالت ، مدیر کنترل کمی بر روند اعمال تغییرات بر داده ها دارد.
بنابراین، تعداد افرادی که به رابط ادمین دسترسی دارند را در حداقل ممکن نگه دارید. در اعمال تغییرات از طریق رابط ادمین زیاده روی نکنید، مگر اینکه وارد کردن یک داده ساده، مثل اضافه کردن محتوای یک مقاله؛ باشد.
#### الگوهای سرآمد:
`به کاربران نهایی دسترسی به رابط ادمین ندهید.
`
مطمئن شوید که تمام مدیران سایت شما از تناقض هایی که ممکن است در داده ها از طریق رابط ادمین ایجاد شود، آگاه هستند.اگر ممکن است به صورت دستی تغییرات را ذخیره کنید؛یا از برنامه هایی مثل django-audit-log، که می تواند گزارش تغییرات اعمال شده در قسمت ادمین را برای ارجاع در آینده ذخیره کند، استفاده کنید.
در یک نمونه مثال از یک دانشگاه، ما یک رابط کاربری مجزا،همانند یک رابط برنامه ریزی دوره ها؛ برای اساتید ساختیم. چنین ابزاری حاوی برنامه ای است که می تواند برای مقاصدی بسیار فراتر از عملکرد فیلد های داده ی رابط ادمین،مثل شناسایی تناقض بین تاریخ دوره ها؛ مورد استفاده باشد باشد .
اساسا، اصلاح اشکالات رابط ادمین مستلزم ایجاد ابزار های توانمندتری برای یک مجموعه مشخص از کاربران است. با این حال، مسیر آسان(و اشتباه) اعطای دسترسی مدیریت به آنها را انتخاب نکنید.
#### سفارشی سازی رابط مدیریت
پنل ادمین آماده جنگو برای شروع کار بسیار مناسب است. متاسفانه، اکثریت افراد تصور می کنند که تغییر دادن رابط مدیریت جنگو بسیار مشکل است و آن را به همان شکلی که هست رها می کنند. در واقع رابط مدیریت جنگو قابلیت شخصی سازی فوق العاده ای دارد و ظاهر آن را می توان با حداقل تلاش، شدیدا تغییر داد.
#### تغییر عنوان
بسیاری از کاربران رابط مدیریت ممکن است با عنوان مدیریت جنگو (django administration) دچار مشکل شوند. احتمالا بهتر باشد که این مورد را به عبارتی اختصاصی مثل، "مدیر سایت من"، یا عبارت جالب توجهی چون "محیط مهرمانه superbook" تغییر دهید.
ایجاد این تغییرات بسیار راحت است، فقط لازم است تا خط کد زیر را به صفحه urls.py سایت خود اضافه کنید.
<pre dir='ltr' style='background-color:#D3D3D3'><span class="pl-s1">admin</span>.<span class="pl-s1">site</span>.<span class="pl-s1">site_header</span> <span class="pl-c1">=</span> <span class="pl-s">"SuperBook Secret Area"</span></pre>
#### تغییر پایه و شیوه نامه
تقریبا هر صفحه مدیریتی، از یک الگوی پایه مشترک توسعه یافته است که به آن admin/base_site.html می گویند.
این بدان معنی است که شما با یک دانش حداقلی از html و css، می توانید به دلخواه ظاهر و حالت رابط کاربری ادمین را تغییر دهید.
یک فایل با نام ادمین در مسیری که الگوهایتان قرار دارد بسازید. سپس محتوای فایل base_site.html را از سورس کد جنگو کپی کرده و در فایل جدید، به دلخواه آن را ویرایش کنید. اگر نمی دانید که الگوها(templates) کجا قرار گرفته اند،کافی است که کد زیر را در شل (shell) جنگو اجرا نمایید.
<p dir='ltr' style='background-color:#ADD8E6'>
from os.path import join
<br>
from django.contrib import admin
<br>
print(join(admin.\__path__[0], "templates", "admin")) /home/arun/env/sbenv/lib/python3.6/site- packages/django/contrib/admin/templates/admin
</p>
اخرین خط، محل قرارگیری تمام فایل های الگوی ادمین شما است.شما می توانید هر کدام از این فایل ها را تغییر و یا تعمیم دهید.
برای مثال در تغییر پایه الگوی ادمین، شما می توانیدفونت تمام قسمت ادمین را به فونت Special Elite تغییر دهید.
برای این کار لازم است که محتویات فایل base_site.html را از مسیر الگوهای ادمین به مسیر admin/base_site.html؛ در یک پوشه از الگوهای خودتان، کپی کنید. سپس کد زیر را به انتهای آن اضافه کنید.
<pre dir='ltr' style='background-color:#D3D3D3'>{% block extrastyle %}
<span class="pl-kos"><</span><span class="pl-ent">link</span>
<span class="pl-c1">href</span>="<span class="pl-s">http://fonts.googleapis.com/css?family=Special+Elite</span>"
<span class="pl-c1">rel</span>="<span class="pl-s">stylesheet</span>"
<span class="pl-c1">type</span>="<span class="pl-s">text/css</span>"
/>
<span class="pl-kos"><</span><span class="pl-ent">style</span> <span class="pl-c1">type</span>="<span class="pl-s">text/css</span>"<span class="pl-kos">></span>
<span class="pl-ent">body</span><span class="pl-kos">,</span>
<span class="pl-ent">td</span><span class="pl-kos">,</span>
<span class="pl-ent">th</span><span class="pl-kos">,</span>
<span class="pl-ent">input</span> {
<span class="pl-c1">font-family</span><span class="pl-kos">:</span> <span class="pl-s">"Special Elite"</span><span class="pl-kos">,</span> cursive;
}
<span class="pl-kos"></</span><span class="pl-ent">style</span><span class="pl-kos">></span>
{% endblock %}</pre>
این کد، شیوه نامه جدیدی برای تغییر فونت مربوطه می افزاید که به تمام صفحات ادمین اعمال می شود.
#### اضافه کردن یک ویرایشگر متن غنی(rich-text) برای ویرایش WYSIWYG
گاهی اوقات نیاز دارید که کد جاوااسکریپتی در بخش ادمین داشته باشید. یک نیاز مشترک، استفاده از یک ویرایشگر HTML ؛ مثل CKEditor، برای فیلدهای متنی است.
راه های متعددی برای پیاده سازی این موضوع در جنگو ، همانند استفاده از کلاس داخلی Media در کلاس مدل ادمین(ModelAdmin)؛ وجود دارد.با این حال، من تعمیم الگوی change_form در ادمین را مناسب ترین روش تشخیص دادم.
برای مثال، اگر شما یک برنامه به اسم پست ها دارید، لازم است که فایلی با نام change_form.html در مسیر templates/admin/posts/ بسازید. اگر نیاز دارید که ویرایشگر CKEditor ( یا هر ویرایشگر جاوااسکریپت، اما این ویرایشگر مورد ترجیح من است)برای فیلد پیام یک مدل از این برنامه نمایش داده شود، محتوای فایل مربوطه می تواند به صورت زیر باشد:
<pre dir='ltr' style='background-color:#D3D3D3'>{% extends "admin/change\_form.html" %} {% block footer %} {{ block.super }}
<span class="pl-kos"><</span><span class="pl-ent">script</span> <span class="pl-c1">src</span>="<span class="pl-s">//cdn.ckeditor.com/4.4.4/standard/ckeditor.js</span>"<span class="pl-kos">></span><span class="pl-kos"></</span><span class="pl-ent">script</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">script</span><span class="pl-kos">></span>
<span class="pl-c1">CKEDITOR</span><span class="pl-kos">.</span><span class="pl-en">replace</span><span class="pl-kos">(</span><span class="pl-s">"**id_message**"</span><span class="pl-kos">,</span> <span class="pl-kos">{</span>
<span class="pl-c1">toolbar</span>: <span class="pl-kos">[</span><span class="pl-kos">[</span><span class="pl-s">"Bold"</span><span class="pl-kos">,</span> <span class="pl-s">"Italic"</span><span class="pl-kos">,</span> <span class="pl-s">"-"</span><span class="pl-kos">,</span> <span class="pl-s">"NumberedList"</span><span class="pl-kos">,</span> <span class="pl-s">"BulletedList"</span><span class="pl-kos">]</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c1">width</span>: <span class="pl-c1">600</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos"></</span><span class="pl-ent">script</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">style</span> <span class="pl-c1">type</span>="<span class="pl-s">text/css</span>"<span class="pl-kos">></span>
.<span class="pl-c1">cke</span> {
<span class="pl-c1">clear</span><span class="pl-kos">:</span> both;
}
<span class="pl-kos"></</span><span class="pl-ent">style</span><span class="pl-kos">></span>
{% endblock %}</pre>
قسمتی که بولد شده، شناسه ساخته شده به صورت خودکار برای المان فرمی است که می خواهیم از یک کادر متنی ساده به یک ویرایشگر متن غنی تبدیل کنیم. این تغییر سایر کادرهای متنی فیلدهای سایت ادمین را تحت تاثیر قرار نمی دهد. این اسکریپت ها به بلوک انتهایی (footer) افزوده شده، پس المان های فرم در DOM قبل از آن که تغییر کنند،ساخته شده اند.
شیوه های دیگر دستیابی به این موضوع ممکن است نیازمند نصب برنامه ها و تنظیمات دیگری باشد. برای تغییر فقط یک صفحه از سایت ادمین، این زیاده روی است. شیوه قبلی به شما این انعطاف پذیری را می دهد که بین ویرایشگر جاوااسکریپت یا موارد دلخواه خودتان، انتخاب کنید.
#### ادمین با تم بوت استرپ
جای تعجب نیست که یک درخواست برای سفارشی سازی بخش ادمین این است که آیا می توان بوت استرپ را با آن ادغام کرد یا خیر. بسته های(pacakges) زیادی؛ همانند Django-admin-bootstrapped و یا Django suit هستند؛ که می توانند این کار را انجام دهند.
بجای این که تمام الگوهای ادمین را خودتان تغییر دهید، این بسته ها تم های آماده به مصرفی را فراهم کرده اند.نصب و بارگذاری این تم ها آسان است. از آنجا که بر اساس بوت استرپ هستند، واکنشگرا(responsive) بوده و با مولفه ها و ویجت های متنوعی همراه هستند.
#### بازسازی کامل
تلاش های زیادی برای دوباره به تصویر کشیدن بخش ادمین انجام شده است. Grappelli یک پوسته محبوب است که رابط ادمین جنگو را با با ویژگی های جدیدی تعمیم می دهد؛ جستجوهای پیشنهاد دهنده(autocomplete lookups) و خطوط تاشو(collapsible inlines) نمونه هایی از این ویژگی ها هستند. با استفاده از ابزار django-admin-tools شما یک داشبورد و نوار منو با قابلیت سفارشی سازی خواهید داشت.
تلاش هایی برای بازنویسی مجدد رابط ادمین صورت گرفته، نظیر django-admin2 و nexus؛ که پیشرفت قابل توجهی نداشتند. همچنین یک ابزار رسمی به نام AdminNext برای اصلاح کامل رابط ادمین ساخته شد.
با در نظر گرفتن حجم، پیچیدگی و محبوبیت رابط ادمین موجود؛ هر تلاشی از این قبیل زمان بسیار زیادی خواهد برد.
#### حفاظت از ادمین
رابط ادمین سایت شما تقریبا به هر داده ای که در سایت ذخیره شده است دسترسی دارد،بنابراین دروازه این مخزن اطلاعات را بدون محافظ رها نکنید.در واقع یک نشانه که گویای این است که شخص از جنگو استفاده می کند،این است که وقتی به صفحه http://example.com/admin/ می روید، یک صفحه آبی برای ورود به بخش می بینید.
زمانی که سایت را بر روی سرور بالا می آورید، بهتر است که مسیر ادمین را به مسیری که کمتر مشخص باشد تغییر دهید. انجام این کار با سادگی با کد زیر در فایل urls.py انجام می شود.
<pre dir='ltr' style='background-color:#D3D3D3'><span class="pl-en">path</span>(<span class="pl-s">'secretarea/'</span>, <span class="pl-s1">admin</span>.<span class="pl-s1">site</span>.<span class="pl-s1">urls</span>),</pre>
راهکاری که مقداری پیچیده تر است آن است که یک صفحه ادمین فیک (رد گم کن!) در مسیر پیش فرض داشته باشید.(بسته ی django-admin-honeypot package را جستجو کنید).
با این حال بهترین راه حل استفاده از HTTPS در محیط ادمین(و هر جای دیگر از وبسایت) است.چرا که استفاده از HTTP ساده، موجب ارسال تمام داده ها به صورت یک متن ساده در شبکه می شود.
مستندات سرور وبسایت خود را در رابطه با نحوه ی تنظیم HTTPS برای درخواست های ادمین (یا اگر ممکن است، تمام وبسایتتان) بررسی کنید. بر روی Nginx، انجام این کار ساده است؛ که شامل تعیین محل گواهی SSL می شود. در نهایت،تمامی درخواست های HTTP دریافت شده برای ادمین را به صفحات HTTPS بازنشانی کنید، تا خیال راحت تری داشته باشید!
الگوی زیر تنها محدود به بخش رابط ادمین نیست، اما با این وجود در این فصل گنجانده شده؛ زیرا اغلب در این بخش کنترل می شود.
#### پرچم های(flags) الگو - ویژگی
مشکل: انتشار ویژگی های جدید برای کاربران باید مستقل از بارگذاری کد متناظر در تولید باشد.
راه حل: استفاده از پرچم های ویژگی برای فعال یا غیر فعال کردن ویژگی ها پس از بارگذاری به صورت انتخابی.
##### شرح مشکل
امروزه رفع مشکلات مکرر و مدیریت ویژگی های جدید در حین فعال بودن سایت بر روی سرور، مسئله ای رایج است.بسیاری از این تغییرات توسط کاربران مورد توجه قرار نمی گیرد. اگرچه ویژگی های جدیدی که تاثیر قابل توجی از نظر عملکرد و قابلیت های سایت دارند، باید به صورت مرحله ای عرضه شوند. به عبارت دیگر، بارگذاری باید از مرحله انتشار و فعال سازی جدا شود.
یک فرآیند ساده انتشار نسخه جدید، ویژگی های تازه را به محض بارگذاری شدن آن ها بر روی سایت، فعال می کند. این امر به طور بالقوه می تواند نتایج فاجعه باری داشته باشد؛ از مشکلات کابران ( پر شدن منابع پشتیبانی و نرم افزاری شما) گرفته، تا مشکلات عملکردی(خرابی و غیرفعال شدن سایت) را منجر می شود.
از این رو در سایت های بزرگ، جداسازی فرآیند بارگذاری بروزرسانی جدید در زمان تولید و فعال سازی آن ها؛ اهمیت زیادی دارد. حتی گاهی پس از فعال سازی ممکن است فقط برای عده خاصی از کاربران آن را قابل مشاهده کنیم. این گروه منتخب می توانند کارکنان یا مجموعه محدودی از مشتریان باشند که پیش نمایش اولیه را دریافت می کنند.
##### شرح راه حل:
بسیاری از سایت ها فعال سازی ویژگی های جدید را با استفاده از پرچم های ویژگی کنترل می کنند.به طور معمول این یک سوئیچ است که در هر مرحله کنترل می شود."فلیپر" سوئیچی در کد شما است که تعیین می کند آیا یک ویژگی باید در دسترس مشتریان خاص قرار گیرد یا خیر. اما ما در اینجا عبارت عام پرچم ویژگی(feature flag) را به کار می بریم.
پکیج های جنگو دارای پرچم های ویژگی مانند gargoyle و django-waffle هستند. این پکیج ها پرچم های سایت را در پایگاه داده ذخیره می کنند.آنها را می توان از طریق رابط ادمین یا از طریق دستورات مدیریت فعال یا غیرفعال کرد. از این رو هر محیطی(تولید،آزمایش،توسعه و ...) می تواند مجموعه ای از ویژگی های فعال شده خود را داشته باشد.
پرچم های ویژگی در اصل در سایت فلیکر ثبت شده بودند.(ادرس http://code.flickr.net/2009/12/02/flipping-out را بررسی کنید). آنها یک منبع کد را بدون هیچ شاخه ای مدیریت کردند- یعنی همه چیز در شاخه اصلی بررسی شد. آنها همچنین این کد را چندین بار در روز وارد محیط تولید کردند. اگر آنها متوجه می شدند که یک ویژگی جدید باعث خرابی چیزی در محیط تولید شده است یا بارگذاری روی پایگاه داده را افزایش داده است؛ به سادگی آن را با استفاده از پرچم ویژگی، غیر فعال می کردند.
پرچم های ویژگی می توانند برای موقعیت های دیگری نیز استفاده شوند(مثال زیر از Django Waffle استفاده می کند):
##### آزمایش:
یک پرچم ویژگی می تواند برای کاربران خاصی فعال باشد. این کاربران می توانند کارکنان شما و یا برخی پذیرندگان اولیه باشند که مورد هدف شما هستند؛ که به صورت زیر مشاهده می کنید:
<pre dir='ltr' style='background-color:#D3D3D3'><span class="pl-k">def</span> <span class="pl-s1">my_view</span>(<span class="pl-s1">request</span>):
<span class="pl-k">if</span> <span class="pl-en">flag_is_active</span>(<span class="pl-s1">request</span>, <span class="pl-s">'flag_name'</span>):
\#<span class="pl-v">Behavior</span> <span class="pl-k">if</span> <span class="pl-s1">flag</span> <span class="pl-c1">is</span> <span class="pl-s1">active</span>.</pre>
سایت ها می توانند چندین آزمایش از این دست را به صورت همزمان و موازی اجرا کنند، بنابراین مجموعه های مختلف کاربران ممکن است تجربیات متفاوتی داشته باشند. کمیت ها و بازخوردهای این آزمایش های کنترل شده،قبل از انتشار در سطح وسیع تر؛ جمع آوری و بررسی می شوند.
##### آزمایش A/B:
این آزمایش نیز مشابه با مورد پیشین است، با این تفاوت که کاربران به صورت تصادفی در یک ازمایش کنترل شده انتخاب می شوند. این روش در طراحی وبسایت بسیار مرسوم است و با این هدف تعیین آن که کدام تغییرات موجب افزایش نرخ تبدیل می شود، انجام می شوند. در ادامه مثالی از آن را میبینیم:
<pre dir='ltr' style='background-color:#D3D3D3'><span class="pl-k">def</span> <span class="pl-s1">my_view</span>(<span class="pl-s1">request</span>):
<span class="pl-k">if</span> <span class="pl-en">sample_is_active</span>(<span class="pl-s1">request</span>, <span class="pl-s">'new_design'</span>):
\#<span class="pl-v">Behavior</span> <span class="pl-s1">for</span> <span class="pl-s1">test</span> <span class="pl-s1">sample</span>.</pre>
##### تست عملکرد:
گاهی اوقات اندازه گیری تاثیر یک ویژگی بر عملکرد سرور دشوار است. در چنین شرایطی بهتر است از ابتدا پرچم را فقط برای درصد کمی از کابران فعال کنید. اگر عملکرد آن مورد رضایت بود، درصد فعال سازی را می توان به تدریج افزایش داد.
##### محدودیت های خارجی:
همچنین می توانیم از پرچم های ویژگی به عنوان سوئیچ ویژگی در سراسر سایت استفاده کنیم که در دسترس بودن آن خدمات را منعکس می کند. به عنوان مثال، از کار افتادن سرویس های خارجی مانند Amazon S3 می تواند باعث شود که کابران در حین انجام اقداماتی مانند آپلود تصویر، با پیام های خطا مواجه شوند. هنگامی که سرویس خارجی برای مدت طولانی خاموش است، یک پرچم ویژگی را می توان فعال مرد و دکمه آپلود را غیر فعال کرد و یا پیامی قابل فهم در خصوص زمان خرابی سرویس نشان داد. این ویژگی ساده در وقت کاربر صرفه جویی کرده و تجربه کاربری بهتری را ارائه می دهد:
<pre dir='ltr' style='background-color:#D3D3D3'><span class="pl-k">def</span> <span class="pl-en">my_view</span>(<span class="pl-s1">request</span>):
<span class="pl-k">if</span> <span class="pl-en">switch_is_active</span>(<span class="pl-s">'s3_down'</span>):
\#<span class="pl-v">Disable</span> <span class="pl-s1">uploads</span> <span class="pl-c1">and</span> <span class="pl-s1">show</span> <span class="pl-s1">it</span> <span class="pl-c1">is</span> <span class="pl-s1">downtime</span></pre>
عیب اصلی این روش آن است که کد با عبارت های شرطی شلوغ می شود. با این حال، این مسئله را می توان با پاکسازی و تصحیح دوره ای کدها و حذف شروط برای ویژگی هایی که کاملا تایید شده اند ، یا حذف ویژگی هایی که به طور دائمی غیرفعال شده؛ تا حدی برطرف کنید.
فعال سازی پرچم ها را می توان از سایت مدیریت با ساتفاده از سیستم های احراز هویت و مجوزهای کاربر داخلی کنترل کرد. همچنین می توانید درصد کاربران نمونه برای ازمایش را از رابط مدیریت کنترل کنید.
### خلاصه
در این فصل، برنامه مدیریت داخلی جنگو(ادمین) را بررسی کردیم. نه تنها این بخش به عنوان یک ابزار کمکی بسیار مفید است، بلکه می توان برای بهبود ظاهر و کارایی، آن را سفارشی سازی کرد.
در فصل بعدی با در نظر گرفتن الگوهای مختلف و موارد استفاده شده متداول، نگاهی به نحوه استفاده موثرتر از فرم ها در جنگو خواهیم داشت.
</div>
================================================
FILE: 07-Forms/README.md
================================================
# فرم ها :
در این فصل به مباحث زیر می پردازیم:<br>
• نحوه کارکرد فرم ها<br>
• ورودی غیر قابل اعتماد<br>
• پردازش فرم با (CBV)Class Based View<br>
• کار با ویوهای CRUD<br>
بیایید فرم های جنگو را کنار بگذاریم و به طور کلی در مورد فرم های وب صحبت کنیم. فرم ها فقط صفحات طولانی و خسته کننده با چندین فیلد نیستند که باید آنها را پر کنید. فرم ها همه جا هستند. ما هر روز از آنها استفاده می کنیم. فرمها همه چیز را از کادر جستجوی Google گرفته تا دکمه لایک فیسبوک را شامل می شوند .
جنگو کارهای پچیده رادر هنگام کار با فرم هایی مانند اعتبار سنجی یا نمایشی به صورت خلاصه انجام می دهد . همچنین بهترین شیوه های امنیتی مختلف را پیاده سازی می کند. با این حال، فرم ها می توانند یاعث سردرگمی شوند زیرا ممکن است چندین حالت مختلف باشند. بیایید آنها را دقیق تر بررسی کنیم.
## فرم ها چگونه کار می کنند
درک فرم ها ممکن است مشکل باشد زیرا تعامل با آنها بیش از یک چرخه درخواست-پاسخ طول می کشد. در ساده ترین حالت، شما باید یک فرم خالی ارائه دهید که کاربر آن را به درستی پر کرده و ارسال کند. برعکس، آنها ممکن است برخی از دادههای نامعتبر را وارد کنند، در این صورت، فرم باید دوباره ارسال شود تا کل فرم معتبر باشد.
در این سناریو می بینیم که یک فرم می تواند یکی از چندین حالت زیر باشد و بین آنها تغییر می کند:
• فرم خالی (فرم پر نشده): به این فرم در جنگو فرم بدون چهارچوب (unbound form) گفته می شود.<br>
• فرم ارسال شده با خطا: به این فرم فرم محدود(bound form) می گویند اما فرم معتبر نیست .<br>
• فرم ارسال شده بدون خطا: به این فرم فرم محدود(bound form) و معتبر(valid form) می گویند .<br>
نکته: کاربران هرگز فرمی که در وضعیت خطا نباشد را مجدد نخواهند دید . به طور معمول، ارسال یک فرم معتبر باید کاربران را به یک صفحه موفقیت آمیز هدایت کند.
<b>فرم ها در جنگو</b><br>
نمونه های کلاس فرم جنگو شامل وضعیت هر فیلد و با خلاصه کردن آنها در یک سطح، خود فرم است. فرم دارای دو ویژگی مهم است که به شرح زیر است:
محدود شده (is_bound): اگر این مقدار false را برگرداند، یک فرم بدون قید است، یعنی یک فرم تازه با مقادیر فیلد خالی یا پیش فرض. اگر مقدار true را برگرداند، فرم محدود است، یعنی حداقل یک فیلد با ورودی کاربر تنظیم شده است.<br>
معتبر (is_valid): اگر این مقدار true را برگرداند، هر فیلد در فرم محدود دارای داده معتبر است. اگر نادرست باشد، حداقل در یک فیلد تعدادی داده نامعتبر وجود دارد یا فرم محدود نشده است.
```python
from django import forms
class PersonDetailsForm(forms.Form):
name = forms.CharField(max_length=100)
age = forms.IntegerField()
```
این کلاس را می توان به صورت محدود یا بدون کران آغاز کرد، همانطور که در کد زیر نشان داده شده است:
```bash
>>> f = PersonDetailsForm()
>>> print(f.as_p())
<p><label for="id_name">Name:</label> <input type="text" name="name"
maxlength="100" required id="id_name" /></p>
<p><label for="id_age">Age:</label> <input type="number" name="age"
required id="id_age" /></p>
>>> f.is_bound
False
>>> g = PersonDetailsForm({"name": "Blitz", "age": "30"})
>>> print(g.as_p())
<p><label for="id_name">Name:</label> <input type="text" name="name"
value="Blitz" maxlength="100" required id="id_name" /></p>
<p><label for="id_age">Age:</label> <input type="number" name="age"
value="30" required id="id_age" /></p>
>>> g.is_bound
True
```
توجه
gitextract_sifllnh9/ ├── .nojekyll ├── 01-Django-and-Patterns/ │ └── README.md ├── 02-ApplicationDesign/ │ └── README.md ├── 03-Models/ │ └── README.md ├── 04-Views-and-URLs/ │ └── README.md ├── 05-Templates/ │ └── README.md ├── 06-AdminInterface/ │ └── README.md ├── 07-Forms/ │ └── README.md ├── 08-WorkingAsynchronously/ │ └── README.md ├── 09-CreatingAPIs/ │ └── README.md ├── 10-Dealing-with-LegacyCode/ │ └── README.md ├── 11-Testing-and-Debugging/ │ └── README.md ├── 12-Security/ │ └── README.md ├── 13-Production-Ready/ │ └── README.md ├── Appendix-A-Python2-Versus-Python3/ │ └── README.md ├── CONTRIBUTING.md ├── README.md ├── _sidebar.md ├── custom.css ├── custom.js └── index.html
SYMBOL INDEX (4 symbols across 1 files)
FILE: custom.js
function updateNavigationButtons (line 58) | function updateNavigationButtons() {
function addThemeToggle (line 112) | function addThemeToggle() {
function toggleTheme (line 120) | function toggleTheme() {
function setInitialTheme (line 130) | function setInitialTheme() {
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (701K chars).
[
{
"path": ".nojekyll",
"chars": 0,
"preview": ""
},
{
"path": "01-Django-and-Patterns/README.md",
"chars": 23861,
"preview": "# جنگو الگوها\n\nدر این فصل در مورد موضوعات زیر صحبت خواهیم کرد:\n* [چرا جنگو؟](#چرا-جنگو)\n* [داستان جنگو](#داستان-جنگو)\n* "
},
{
"path": "02-ApplicationDesign/README.md",
"chars": 21255,
"preview": "# طراحی برنامه \n\nدر این بخش، موضوعات زیر را پوشش میدهیم:\n\n- جمعآوری نیازمندیها\n- ساخت یک سند کانسپت\n- ماکتهای HTML \n"
},
{
"path": "03-Models/README.md",
"chars": 45876,
"preview": "# مدلها\n\nدر این بخش به بحثهای زیر میپردازیم:\n\n- اهمیت مدلها\n- نمودار کلاسها\n- الگویهای ساختاری مدل\n- الگوهای رفتار"
},
{
"path": "04-Views-and-URLs/README.md",
"chars": 49307,
"preview": "# ویوها و URLها\n\nدر این فصل ما در مورد مباحث زیر بحث خواهیم کرد:\n\n- ویوهای مبتنی بر کلاس و مبتنی بر تابع\n- میکسینها\n- د"
},
{
"path": "05-Templates/README.md",
"chars": 25464,
"preview": "# ۵ تمپلیتها\n\nدر این بخش ما موضوعات زیر را بررسی خواهیم کرد:\n\n- ویژگیهای زبان تمپلیت جنگو \n- جینجا ۲ (Jinja2)\n- سازما"
},
{
"path": "06-AdminInterface/README.md",
"chars": 30117,
"preview": "<div dir='rtl'>\n<h1>فصل ۶ - رابط کاربری ادمین</h1>\nدر این بخش، موضوعات زیر را بررسی خواهیم کرد:\n\n* شخصی سازی بخش ادمین\n*"
},
{
"path": "07-Forms/README.md",
"chars": 29585,
"preview": "# فرم ها :\nدر این فصل به مباحث زیر می پردازیم:<br>\n\n•\tنحوه کارکرد فرم ها<br> \n•\tورودی غیر قابل اعتماد<br>\n•\tپردازش فرم ب"
},
{
"path": "08-WorkingAsynchronously/README.md",
"chars": 40194,
"preview": "# فصل 8 کارکردن به صورت ناهمزمان\n\n- چرا به ناهمزمانی نیاز داریم؟\n- الگوهای ناهمزمانی\n- کار کردن با Celery\n- فهم asyncio\n"
},
{
"path": "09-CreatingAPIs/README.md",
"chars": 18376,
"preview": "# ایجاد APIها\n\nدر این فصل ما در مورد مباحث زیر بحث خواهیم کرد:\n\n- RESTful API\n- طراحی API\n- فریمورک رست جنگو\n- الگوهای A"
},
{
"path": "10-Dealing-with-LegacyCode/README.md",
"chars": 22965,
"preview": "# سرو کار داشتن با کد مورثی\n\nدر این فصل، ما در مورد موضوعات زیر صحبت خواهیم کرد:\n\n- یک کد اولیه جنگو را می خوانیم\n-\tبررس"
},
{
"path": "11-Testing-and-Debugging/README.md",
"chars": 31796,
"preview": "# تست کردن و رفع مشکل\n\nدر این بخش موضوعات زیر را بررسی خواهیم کرد:\n\nتوسعه تست محور TDD\n- بایدها و نبایدها در نوشتن تست\n-"
},
{
"path": "12-Security/README.md",
"chars": 24386,
"preview": "# امنیت\nدر این فصل ما در باره موضوعات زیر بحث میکنیم:\n\n- انواع حملات وب و اقدامات متقابل\n- کجا جنگو میتواند و کجا نمیتوا"
},
{
"path": "13-Production-Ready/README.md",
"chars": 36681,
"preview": "# آماده برای تولید\n\nدر این فصل به مباحث زیر می پردازیم:\n- انتخاب یک وب استک\n- رویکردهای میزبانی\n- بزارهای استقرار\n- مانی"
},
{
"path": "Appendix-A-Python2-Versus-Python3/README.md",
"chars": 10136,
"preview": " # پایتون 2 در مقابل پایتون 3\nتمام نمونه کدهای این کتاب برای پایتون 3.6 نوشته شده است. \nبه جز تغییرات بسیار جزیی، آنها ب"
},
{
"path": "CONTRIBUTING.md",
"chars": 258,
"preview": "<div dir=\"rtl\">\n\n# چطور در ترجمه میتوانم مشارکت داشته باشم؟\n\n- یک فصلی که در وضعیت \"رزرو نشده\" قرار دارد را انتخاب کنید."
},
{
"path": "README.md",
"chars": 18779,
"preview": " # ترجمه آزاد کتاب Django Design Patterns and Best Practices\n\n\n\nقبل از شروع فهرست کتاب، اگر مایل به "
},
{
"path": "_sidebar.md",
"chars": 792,
"preview": "- [Home](/)\n\n- [01 - Django and Patterns](01-Django-and-Patterns/README.md)\n- [02 - Application Design](02-ApplicationDe"
},
{
"path": "custom.css",
"chars": 1946,
"preview": ":root {\n --theme-color: #3f51b5;\n --accent-color: #ff4081;\n --border-color: #e0e0e0;\n}\n\nbody { \n font-family: \"Rubik"
},
{
"path": "custom.js",
"chars": 3776,
"preview": "\n\n// Docsify configuration\nwindow.$docsify = {\n name: 'Django Design Patterns and Best Practices',\n loadSidebar: true,"
},
{
"path": "index.html",
"chars": 1133,
"preview": "<!DOCTYPE html>\n<html lang=\"fa\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Django Design Patterns and Best Practices</tit"
}
]
About this extraction
This page contains the full source code of the ftg-iran/ddpabp-persian GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (426.4 KB), approximately 135.8k tokens, and a symbol index with 4 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.