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 وب بزرگ پایتون است. ### جنگو چگونه کار می کند؟ برای درک واقعی جنگو، باید زیر کاپوت را نگاه کنید و قسمت های متحرک مختلف داخل آن را ببینید. این می تواند هم روشنگر و هم طاقت فرسا باشد. اگر قبلاً با اطلاعات زیر آشنا هستید، ممکن است بخواهید از این بخش صرفنظر کنید: ![How web requests are processed in a typical Django application](./images/image.MSTBH1.png) *نحوه پردازش درخواست های وب در یک برنامه معمولی جنگو* نمودار قبلی سفر ساده درخواست وب از مرورگر بازدیدکننده به برنامه جنگو و بازگشت را نشان می دهد. مسیرهای شماره گذاری شده به شرح زیر است: 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 در یک مجموعه پنج کتابی)، بر اساس بینش شگفت انگیزی که کاربران در مورد ساختمان های خود بیش از هر معمار دیگری می دانند، رایج شد. یک الگو به یک مشکل روزمره و راه حل پیشنهادی اما آزمایش شده آن اشاره دارد. کریستوفر الکساندر در این کتاب چنین می گوید:

"هر الگو یک مشکل را توصیف می کند که بارها و بارها در محیط ما رخ می دهد و سپس هسته راه حل آن مشکل را به گونه ای توصیف می کند که می توانید از این راه حل میلیون ها بار استفاده کنید، بدون اینکه هرگز آن را به همان روش انجام دهید. دو برابر."

برای مثال، الگوی *بال‌های نوری* او توضیح می‌دهد که چگونه مردم ساختمان‌هایی با نور طبیعی‌تر را ترجیح می‌دهند و پیشنهاد می‌کند که ساختمان را به گونه‌ای تنظیم کنید که از بال تشکیل شده باشد. این بال ها باید بلند و باریک باشند و هرگز بیش از 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 که یک شبکه اجتماعی برای ابرقهرمانان است می‌سازیم. یک وایرفریم ساده بر اساس صحبت‌های ما با چندین ابرقهرمان که به صورت اتفاقی انتخاب شده‌اند در اینجا آمده است: ![](/02-%20Application%20Design/images/0.jpg) *یک وایرفریم از وبسایت سوپربوک به صورت ریسپانسیو - صفحه بندی دسکتاپ و موبایل* ## آیا شما یک قصه‌گو هستید؟ خب، خلاصه یک صفحه‌ای چیست؟ یک سند ساده است که شرح می‌دهد استفاده از سایت چه حسی دارد. تقریباً در تمام پروژه‌هایی که من با آن‌ها کار کرده‌ام وقتی فرد جدید وارد تیم می‌شود، اگر از آن‌ها خواسته شود که تمام اسناد را مطالعه کنند سریعاً دلسرد می‌شوند اما اگر قرار باشد فقط یک صفحه را مطالعه کنند تا بفهمند که هدف سایت چیست بسیار هیجان‌زده می‌شوند. شما می‌توانید به سند هر نامی که دوست دارید بدهید؛ سند کانسپت، سند نیازمندی‌های بازار، مستندات تجربه مشتری یا Epic Fragile StoryLog™ (در انتظار ثبت برند). واقعاً اهمیت خاصی ندارد. این سند باید بر روی تجربه کاربر متمرکز باشد تا جزییات فنی یا نحوه پیاده‌سازی. این سند باید کوتاه و جذاب برای مطالعه کردن باشد. در واقع قانون شماره یک جوئل اسپاسکی برای سند نیازمندی‌ها _بامزه‌بودن_ است. اگر امکان دارد در مورد یک کاربر معمولی ( که در زبان بازاریابی پرسونا می‌گویند) بنویسید، مشکلاتی که آن‌‌ها مواجه می‌شوند و روشی که وب اپلیکیشن مشکل آن‌ها را برطرف می‌کند. تصور کنید که آن‌ها چطور تجربه خود را برای دیگران شرح خواهند داد. سعی کنید این تصویر را به‌دست آورید. اینجا یک سند کانسپت برای پروژه سوپربوک داریم: **_کانسپت سوپربوک_** _این مصاحبه پس از راه‌اندازی وب‌سایت ما سوپربوک، در آینده انجام شده است. یک تست کاربری ۳۰ دقیقه‌ای دقیقاً قبل از مصاحبه اجرا شده است._ **_لطفا خودتان را معرفی کنید_** _ اسم من اَکسل است. من یک سنجاب خاکستری هستم که در مرکز نیویورک زندگی می‌کنم. همه من را اَکورن صدا می‌کنند.پدرم تی. بری که هنرمند شناخته‌شده هیپ هاپ بود من را به این نام صدا می‌کرد. فکر می‌کنم هیچ وقت این‌قدر خوب نمی‌خواندم که کسب‌ و کار خانوداگی را ادامه دهم. در واقع در ابتدا کمی عشق سرقت بودم. می‌دانید که، من به فندق و مانند آن حساسیت دارم اما بقیه رفیق‌هایم راحت می‌خورند. آن‌ها می‌توانند به سادگی در هر پارکی زندگی کنند. من مجبورم خلاق باشم، کافه‌ها، سالن‌های سینما یا پارک‌های سرگرمی. من برچسب‌ها را به دقت می خوانم._ ***خب اکورن فکر می‌کنی که چرا برای تست کاربری انتخاب شده‌ای؟*** _احتمالاً برای اینکه در برنامه ویژه NY Star که در مورد ابرقهرمانان کمتر شناخته شده‌بود شرکت کردم. به نظرم این برای مردم جالب بود که یک سنجاب می‌تواند از مک‌بوک استفاده کند (مصاحبه کننده: این مصاحبه از طریق چت انجام شده است). علاوه بر این، من دقت نظر یک سنجاب را دارم._ ![](/02-%20Application%20Design/images/1.png) ***بر اساس چیزی که دیده‌اید نظر شما در مورد سوپربوک چیست؟*** _فکر می‌کنم که ایده فوق‌العاده‌ای است. منظورم این است که مردم معمولاً ابرقهرمانان را می‌بینند، با اینحال کسی به ‌آن‌ها توجهی ندارد. اکثر آن‌ها تنها و غیر اجتماعی هستند. سوپربوک می‌تواند این وضعیت را تغییر دهد._ **_فکر می‌کنید که چه چیزی در سوپربوک متفاوت است؟_** _این سایت از ابتدا برای افرادی مانند ما ساخته شده است. منظورم این است که وقتی قرار است از هویت مخفی خود استفاده کنید مجبور نیستید موارد بی‌معنی مانند "سابقه کار و تحصیلات" را پر کنید. اگرچه که من چنین چیزی ندارم ولی می‌توانم بفهمم که چرا ممکن است یکی بخواهد این‌ کار را بکند._ **_ممکن است به طور خلاصه برخی ویژگی‌هایی را که برای شما مهم بودند نام ببرید؟_** _بله حتماً، فکر می‌کنم این یک شبکه اجتماعی مناسب است که شما می‌توانید:_ - _می‌توان با هر نوع نام کاربری ثبت نام کرد (دیگر عبارت احمقانه "نام واقعی خود را وارد کنید" را نمی‌بینیم)_ - _طرفدارها می‌توانندافراد را دنبال کنند بدون آنکه مجبور باشند آن‌ها را به عنوان "دوست" اضافه کنند_ - _می‌توان پست و کامنت ایجاد کرد و آن‌ها را بازنشر کرد_ - _می‌توان یک پست خصوصی را برای دیگری ارسال کرد_ \* همه چیز ساده است. لازم نیست ابرقهرمان باشید تا با آن کار کنید. **آکورن، از وقتی که گذاشتی تشکر می‌کنیم. \*** ## ماکت‌های 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 دارند. این نقشه راه توسعه جنگو، تصویر شفاف‌تری از رویکرد توسعه جنگو ارائه می‌دهد: ![](/02-%20Application%20Design/images/2.png) نقشه انتشار نسخه‌های جنگو جنگو 1.11 LTS آخرین نسخه‌ای خواهد بود که پایتون ۲ را پشتیبانی می‌کند و این نسخه تا آپریل ۲۰۲۰ پشتیبانی خواهد شد. نسخه‌های بعدی فقط از پایتون ۳ پشتیبانی خواهند کرد. یک نسخه جنگو مناسب برای شما، بستگی به این دارد که هر چند وقت بک‌بار می‌توانید جنگو را آپدیت کنید و به کدام ویژگی‌ها نیاز دارید. اگر پروژه شما به صورت فعال در حال توسعه است و نسخه جنگو می‌تواند هر ۱۶ ماه یکبار آپدیت شود، پس شما می‌توانید آخرین نسخه ویژگی را نصب کنید فارغ از اینکه LTS هست یا نه. در غیر اینصورت، اگر پروژه شما فقط گاهی توسعه داده می‌شود، پس باید آخرین نسخه LTS را انتخاب کنید. به روزرسانی وابستگی‌های پروژه جنگو از یک نسخه ویژگی به نسخه ویژگی دیگر ممکن است تلاشی غیر ضروری باشد. بنابراین توضیحات هر نسخه را مطالعه کنید و براساس آن برنامه‌ریزی کنید. این کتاب تا حد ممکن از امکانات جنگو 2.0 استفاده می‌کند. **شروع پروژه** این بخش دارای دستورالعمل‌های نصب پروژه سوپربوک است که شامل تمام کدهای استفاده شده در کتاب است. می‌توانید برای دیدن آخرین یادداشت‌ها در مورد نصب پروژه، فایل README.md را در [گیتهاب](https://github.com/DjangoPatternsBook/superbook2) ببینید. ما از ابزار pipenv برای ساخت یک محیط مجازی و نصب وابستگی‌ها استفاده می‌کنیم. ![](images/3.png) _برای هر پروژه جنگو یک محیط مجازی جداگانه بسازید_ ابتدا، پروژه را از گیتهاب کپی کنید: **$ git clone https://github.com/DjangoPatternsBook/superbook2.git** سپس، pipenv را بر اساس توصیه مستندات خودش، به صورت لوکال یا به صورت عمومی، اما خارج از virtualenv، به کمک دستورات زیر نصب کنید: **$ pip install -U pip $ pip install pipenv![](gd2nxz3p.008.png)** حالا به پوشه پروژه بروید و وابستگی‌ها را نصب کنید: **$ 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* طراحی شده است، چیزی که من آن را یک ضد الگو می‌دانم. شاید آن‌ها در آن روزها تصور بسیار کمی در مورد ویژگی‌های یک ابرقهرمان داشتند". هارت در زمان شنیدن آخرین جمله تقریباً خم شد. با صدای کمی آهسته تر گفت: "درست می‌گویی. من چنین تصوری ندارم. علاوه بر این، آن‌ها فقط دو روز به من فرصت دادند تا همه چیز را طراحی کنم. من معتقدم به معنای واقعی کلمه یک بمب هسته‌ای در جایی تیک تاک می‌کند." دهان استیو کاملاً باز بود و ساندویچش در ورودی آن یخ زده بود. هارت لبخند زد. "مطمئنا بهترین کار من نیست. زمانی که از حدود یک میلیارد ورودی عبور کرد، روزها طول می‌کشد تا هر نوع تحلیلی را روی آن پایگاه داده لعنتی اجرا کنیم. سوپر بوک در عرض چند ثانیه آن را انجام می‌دهد، درست است؟" استیو به آرامی سر تکان داد. او هرگز تصور نمی‌کرد که در وهله اول حدود یک میلیارد ابرقهرمان وجود داشته باشد. ### شکار مدل‌ها در اینجا اولین برش از شناسایی مدل‌ها در سوپربوک است. به عنوان نمونه برای یک تلاش اولیه، ما فقط مدل‌های اساسی و روابط آن‌ها را در قالب یک نمودار ساده کلاس‌ها نشان داده‌ایم: ![./images/1.png](./images/1.png) بیایید یک لحظه مدل‌ها را فراموش کنیم و در مورد اشیایی که مدل سازی می‌کنیم صحبت کنیم. هر کاربر یک پروفایل دارد. یک کاربر می‌تواند چندین نظر یا چندین پست بگذارد. یک لایک می‌تواند مربوط به یک ترکیب کاربر/پست باشد. توصیه می‌شود نموداری کلاسی مانند این از مدل‌های خود ترسیم کنید. ممکن است در این مرحله ویژگی‌های کلاس وجود نداشته باشد، اما می‌توانید بعداً آن‌ها را توضیح دهید. هنگامی که کل پروژه در نمودار نشان داده می‌شود، جداسازی برنامه‌ها آسان تر می‌شود. اینجا چند نکته وجود دارد تا این بازنمایی را انجام بدهیم: * اسم‌ها معمولاً تبدیل به هویت مدل‌ها می‌شود. * مستطیل‌ها که هر موجودیت را نشان می‌دهند به مدل‌ها تبدیل می‌شوند. * خط‌های متصل کننده که دو جهتی هستند و سه نوع از روابط را در جنگو تعریف میکنند: یک-به-یک , یک-به-خیلی (با کلید خارجی یا 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` باشد هنگام فراخوانی پکیج، اجرا می‌شود. بنابراین، این فایل، محل ایده‌آلی برای تعریف مقادیر اولیه در سطح پکیج است. # الگوهای ساختاری این بخش شامل چندین الگوی طراحی است که می‌تواند به شما در طراحی و ساختار مدل‌های خود کمک کند. الگوهای ساختاری ذکر شده در اینجا به شما کمک می‌کند تا روابط بین مدل‌ها را به طور موثرتری درک کنید. ## الگو‌ها — مدل‌های نرمال شده **مشکل:** به صورت ساختاری, هر کپی از مدل‌ها، شامل داده‌های تکراری هستند که باعث ناسازگاری داده‌ها می‌شود **راه حل** مدل‌های خود را از طریق نرمال سازی به مدل‌های کوچکتر تقسیم کنید. این مدل‌ها را با روابط منطقی به هم وصل کنید. ## جزییات مشکل تصور کنید کسی جدول پست ما را (با حذف ستون‌های خاص) به روش زیر طراحی کند: ![./images/2.png](./images/2.png) امیدوارم که به اسم‌های ابرقهرمان‌ها که به صورت ناسازگار در ستون اول( و کاپیتان تمپری که صبر ندارد) آمده توجه کرده‌باشید. اگه به اولین ستون نگاه کنیم, ما مطمئن نیستیم که کدام روش هجی کردن درست است، **Captain Temper** یا **Capt. Temper**. این نوعی از افزونگی داده است که ما دوست داریم توسط نرمال سازی دیتا از بین ببریم. ## جزییات راه حل قبل از اینکه نگاهی به راه حل کاملا نرمال شده بیندازیم، اجازه دهید یک توضیح مختصر در مورد نرمال سازی پایگاه داده در زمینه مدل‌های جنگو داشته باشیم. ### سه قدم در نرمال سازی عادی سازی به شما کمک می‌کند تا داده‌ها را به طور موثر ذخیره کنید. هنگامی که مدل‌های شما به طور کامل نرمال‌سازی شدند، داده‌های اضافی نخواهند داشت و هر مدل باید حاوی داده‌هایی باشد که فقط از نظر منطقی به آن مرتبط هستند. برای ارائه یک مثال سریع، اگر می‌خواهیم جدول پست را عادی کنیم تا بتوانیم بدون ابهام به ابرقهرمانی که آن پیام را ارسال کرده است اشاره کنیم، باید جزئیات کاربر را در یک جدول جداگانه جدا کنیم. جنگو قبلاً جدول کاربر را به طور پیش فرض ایجاد می‌کند. بنابراین، همانطور که در جدول زیر نشان داده شده است، فقط باید به شناسه کاربری که پیام را در ستون اول ارسال کرده است مراجعه کنید: ![./images/3.png](./images/3.png) اکنون نه تنها مشخص است که سه پیام توسط یک کاربر ارسال شده است (با یک شناسه کاربری دلخواه)، بلکه می‌توانیم با جستجوی جدول کاربر نام صحیح آن کاربر را نیز پیدا کنیم. به طور کلی، شما مدل‌های خود را به گونه‌ای طراحی می‌کنید که کاملاً نرمال شده باشند و سپس به دلیل بهبود عملکرد، به طور انتخابی برخی از آن‌ها را از حالت نرمال خارج می‌کنید (برای اطلاع از علت آن، به بخش بعدی در مورد عملکرد مراجعه کنید). در پایگاه‌های داده، **فرم‌های نرمال** مجموعه‌ای از دستورالعمل‌ها هستند که می‌توان آن‌ها را برای اطمینان از نرمال‌سازی جدول به کار برد. فرم‌های معمولی که معمولاً یافت می‌شوند، فرم‌های نرمال نوع اول،نوع دوم و نوع سوم هستند، اگرچه می‌توانند تا پنج مرحله هم، نرمال بشوند. در مثال بعدی,ما یک جدول را نرمال سازی می‌کنیم و مدل‌های جنگو متناظر را میسازیم. صفحه گسترده‌ای به نام Sightings را تصور کنید که اولین باری که فردی یک ابرقهرمان را با استفاده از قدرت یا توانایی مافوق بشری می‌بیند، وی را در این صفحه، فهرست می‌کند. هر ورودی در این صفحه گسترده، به منشاء ابرقهرمان، نوع قدرت وی و محل اولین مشاهده که شامل از جمله طول و عرض جغرافیایی است، اشاره می‌کند: ![./images/4.png](./images/4.png) دیتای جغرافیای زیر از [http://www.golombek.com/locations.html](http://www.golombek.com/locations.html) به دست آمده است. ## فرم نرمال نوع اول (1NF) - هیچ خصوصیتی(سلول) با داده تکراری وجود نداشته باشد - یک کلید اصلی(پرایمری) به صورت یک ستون یا چندین ستونی(کامپوزیت کی) تعریف شود. بیایید سعی کنیم صفحه گسترده خود را به یک جدول پایگاه داده تبدیل کنیم. بدیهی است که ستون **Power** ما قانون اول را زیر پا می‌گذارد. جدول به روز شده در اینجا اولین فرم نرمال بودن را برآورده می‌کند. کلید اصلی (با علامت *) ترکیبی از **Name** و **Power** است که باید برای هر ردیف منحصر به فرد باشد: ![./images/5.png](./images/5.png) ![./images/5-2.png](./images/5-2.png) ## فرم نرمال نوع دوم (2NF) فرم نرمال نوع دوم باید تمام شرایط فرم نرمال اول را برآورده کند. علاوه بر این، باید این شرط را برآورده کند که تمام ستون‌های کلید غیر اصلی، باید به کل کلید اصلی وابسته باشند. در جدول قبلی توجه کنید که Origin فقط به ابرقهرمان یعنی Name بستگی دارد. مهم نیست در مورد کدام Power صحبت می‌کنیم. بنابراین، Origin کاملاً به کلید اولیه ترکیبی - Name و Power وابسته نیست. بیایید فقط اطلاعات مبدا را در یک جدول جداگانه به نام Origin استخراج کنیم، همانطور که در اینجا نشان داده شده است: ![./images/6.png](./images/6.png) حالا جدول Sightings را طوری تغییر می‌دهیم که با فرم نرمال نوع دوم هم تطابق داشته باشد. ![./images/7.png](./images/7.png) ## فرم نرمال نوع سوم (3NF) در فرم نرمال نوع سوم، جداول باید فرم نرمال نوع دوم را برآورده کنند و علاوه بر این باید شرایطی را داشته باشند که تمام ستون‌های کلید غیراصولی باید مستقیماً به کل کلید اصلی وابسته باشند و در ضمن باید مستقل از یکدیگر باشند. برای لحظه‌ای به ستون **Country** فکر کنید. با توجه به **Longitude** و **Latitude**، می‌توانید به راحتی ستون **Country** را استخراج کنید. حتی اگر کشوری که در آن یک ابرقدرت دیده شده است به کلید اولیه ترکیبی Name-Power وابسته است، اما فقط به طور غیرمستقیم به آن‌ها وابسته است. بنابراین، اجازه دهید جزئیات مکان را در جدول جداگانه کشورها به صورت زیر، جدا کنیم: ![./images/8.png](./images/8.png) حالا جدول Sightings ما در سومین نوع نرمال سازی قرار دارد: ![./images/9.png](./images/9.png) مانند قبل، نام ابرقهرمان را با 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 = "".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 = "" 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.objects.get_queryset().filter(posted_by__username="a") [, ] ``` مدیر پیش‌فرض ایجاد شده توسط جنگو، *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"]) [, , ] >>> q2 = User.objects.filter(username__in=["c", "d"]) [, ] ``` برخی از عملیات مجموعه‌ای که می‌توانید بر روی آن‌ها انجام دهید به شرح زیر است: - **Union:** این عملیات موارد تکراری را ترکیب و حذف می‌کند. استفاده از `q1 | q2` برای دریافت `[، ، ، ]`. - **Intersection:** این عملیات موارد مشترک را پیدا می‌کند. برای دریافت `[]` از `q1` و `q2` استفاده کنید. - **Difference:** این عملیات عنصرهای موجود در مجموعه دوم را از مجموعه اول حذف می‌کند. هیچ عملگر منطقی برای این کار وجود ندارد. در عوض از `q1.exclude(pk__in=q2)` برای دریافت `[، ]` استفاده کنید. همین عملیات را می‌توان در *QuerySets* با استفاده از اشیاء *Q* انجام داد: ``` from django.db.models import Q # Union >>> User.objects.filter(Q(username__in=["a", "b", "c"]) | Q(username__in=["c", "d"])) [, , , ] # Intersection >>> User.objects.filter(Q(username__in=["a", "b", "c"]) & Q(username__in=["c", "d"])) [] # Difference >>> User.objects.filter(Q(username__in=["a", "b", "c"]) & ~Q(username__in=["c", "d"])) [, ] ``` *تفاوت با استفاده از & (and) و ~ (نفی) اجرا می‌شود. اشیاء *Q* بسیار قدرتمند هستند و می‌توان از آن‌ها برای ساخت کوئری‌های بسیار پیچیده استفاده کرد.* با این حال، رفتار **Set** و **QuerySets** کاملاً یکسان نیست، **QuerySets**ها بر خلاف مجموعه‌های ریاضی، مرتب‌شده هستند. بنابراین، آن‌ها از این نظر به ساختار داده **لیست** در پایتون، نزدیکتر هستند. # زنجیره‌سازی چندین *QuerySets* تاکنون، *QuerySets* از همان نوع متعلق به یک کلاس پایه را با هم ترکیب کرده ایم. با این حال، ممکن است لازم باشد *QuerySets* را از مدل‌های مختلف ترکیب کنیم و عملیاتی را روی آن‌ها انجام دهیم. به عنوان مثال، جدول زمانی فعالیت یک کاربر شامل تمام پست‌ها و نظرات آن‌ها به ترتیب زمانی معکوس است. روش‌های قبلی ترکیب *QuerySets* کار نمی کند. یک راه حل ساده لوحانه، تبدیل آن‌ها به لیست، الحاق و مرتب کردن آن‌ها به شرح زیر است: ``` >>>recent = list(posts)+list(comments) >>>sorted(recent, key=lambda e: e.modified, reverse=True)[:3] [, , ] ``` متأسفانه، این عملیات هر دو شیء تنبل *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 ``` 2. با این کار اسکریپت‌های مهاجرت در پوشه *app/migrations* ایجاد می‌شود 3. دستور زیر را در همان محیط (توسعه) اجرا کنید: ``` python manager.py migrate ``` 4. این کار تغییرات مدل را در پایگاه داده اعمال می‌کند. گاهی اوقات، سؤالاتی برای رسیدگی به مقادیر پیش فرض، تغییر نام و غیره پرسیده می‌شود. 5. اسکریپت‌های مهاجرت را به محیط‌های دیگر انتشار دهید. به طور معمول، ابزار کنترل نسخه شما، به عنوان مثال Git، این کار را انجام می‌دهد. همانطور که آخرین منبع بررسی می‌شود، اسکریپت‌های مهاجرت جدید نیز ظاهر می‌شوند. 6. دستور زیر را در این محیط‌ها اجرا کنید تا تغییرات مدل اعمال شود: ``` python manager.py migrate ``` 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\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//', 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//', 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//', 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```. هر چند که اینطور شما اکثر مزیت‌های استفاده کردن از ویوهای عمومی را از دست می‌دهید. پس خودتان را با این جدول آشنا کنید و ویو عمومی که با توجه به نیازتان، بیشترین تطابق را دارد انتخاب کنید. بهترین منبع برای ویوهای عمومی مبتنی بر کلاس درجه یک به آدرس است(اکثر توسعه دهندگان جنگو این آدرس را بخاطر دارند). شما تمام ویژگی‌ها و متودهای هر یک از ویوهایی که در اینجا ذکر شد را پیدا خواهید کرد. ### ویوهای مبتنی بر کلاس همیشه عمومی ویوهای مبتنی بر کلاس نیستند اکثر افراد بین ویوهای مبتنی بر کلاس با ویوهای عمومی مبتنی بر کلاس گیج می‌شوند. اسم‌های آن‌ها بهم شبیه است ولی یکی نیستند. این منجر به برخی ```تصورات غلط(misconceptions)``` شده که در زیر آمده است: - **فقط ویوهای عمومی هستند که با جنگو همراه شده‌اند**: خوشبختانه این اشتباه است. هیچ جادوی خاصی در ویوهای عمومی مبتنی بر کلاس نیست که ارائه شده باشد. شما آزادید که مجموعه ویوهای عمومی مبتنی بر کلاس خود را منتشر کنید همچنین می‌توانید از کتابخانه‌های واسط مثل```django-vanilla-views```() فلان استفاده کنید که پیاده‌سازی ساده‌تری نسبت به ویوهای عمومی استاندارد دارند. به خاطر داشته باشید که استفاده از ویوهای عمومی شخصی‌سازی شده ممکن است که کد شما را برای بقیه ناآشنا کند. - **ویوهای مبتنی بر کلاس همیشه باید از ویوهای عمومی استخراج شوند**: دوباره می‌گوییم، هیچ چیز جادویی برای ویوهای عمومی مبتنی بر کلاس وجود ندارد. اگر چه 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__ (, , , ) ``` پس اگر ```AB``` متود ```()super``` را صدا بزند، اول ```A``` فراخوانی می‌شود؛ بعد متود ```()super``` کلاس ```A```، کلاس ```B``` را صدا خواهد زد و به همین ترتیب. **نکته(TIP)** معمولاً 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``` () مجموعه‌ عالی از میکسین‌ها مخصوصاً برای کنترل کردن دسترسی به ویوها دارد. در اینجا مثالی از استفاده آن برای کنترل دسترسی به ویو برای کاربران وارد شده و ناشناس آورده شده است: ```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ها به بخشی از رابط کاربری تبدیل شده‌اند. آن‌ها دیده می‌شوند، کپی می‌شوند، به اشتراک گذاشته می‌شوند و حتی ویرایش می‌شوند. آن‌ها را طوری بسازید که در یک نگاه قابل فهم و خوب به نظر برسند نه چشم را زخم کنند. به طور مثال: کوتاه و قابل فهم بودن نه تنها توسط کاربران استقبال می‌شود بلکه توسط موتورهای جستجو نیز. URLهای طولانی و کم ارتباط با محتوا، تأثیر منفی روی رتبه سایت شما در موتورهای جستجو می‌گذارد. در نهایت به طور ضمنی به یاد داشته باشید که *URIهای خوب تغییر نمی‌کنند* و شما باید تلاش کنید که ساختار URLها را در طول زمان حفظ کنید حتی اگر وب سایت شما به صورت کامل باز طراحی شد، لینک‌های قدیمی شما باید همچنان کار کند. جنگو شما را از این موضوع مطمئن می‌کند. قبل از اینکه به دل جزئیات طراحی URLها برویم، نیاز داریم که ساختار URLها را متوجه شویم. ### ساختمان URL به طور فنی، URLها متعلق به خانواده‌ای عمومی از شناسه‌ها است که **Uniform Resource Identifiers (URIs)** صدایشان می‌زنیم. از این رو URL نیز همان ساختار URI را دارا است. یک URI از چندین بخش تشکیل شده است: *URI = Scheme + Net Location + Path + Query + Fragment* به طور مثال ساختار یک URI() می‌تواند در پایتون به وسیله تابع ```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 می‌تواند به صورت گرافیکی به تصویر کشیده شود که به صورت زیر است: ![قسمت‌های مختلف یک URI](../04-%20Views%20and%20URLs/images/img1.jpg) حتی اگر چه مستندات جنگو ترجیح می‌دهد که از عبارت 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\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\w+)/$', views.hello_fn),```| ```path('hello//', views.hello_fn),``` | | ```url(r'^(?P[0-9]{4})/(?P[-\w]+)/(?P[0-9]+)/(?P[0-9]+)/$',``` | ```path('////',``` | **(داستان)** سینتکس نه تنها قابل خواندن است بلکه برای گرفتن انواع داده‌ای نیز بهتر است مثل داده‌های عددی که نیازی به، به خاطر سپردن معادل متناظر عبارات منظم آن را ندارند. آن‌ها بعد از اینکه به نوع داده‌ای موردنظر تبدیل شدند به ویو صدا کننده آن فرستاده می‌شوند. این را با عبارت منظم مقایسه کنید که به معنای واقعی کلمه فقط رشته برمیگرداند. نوع و مسیر(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\d+)/``` خواهد بود. این به این معنی خواهد بود که ویو با آرگومان کلید واژه که ```year``` با مقدار ```1980``` صدا زده می‌شود. **نکته(TIP)** از تولیدکننده‌های عبارات منظم آنلاین مثل یا برای مهارت و امتحان کردن عبارت‌های منظمتان استفاده کنید. اگر ویوهای مبتنی بر کلاس دارید می‌توانید با ```self.args``` به آرگومان‌های جایگاهی و با ```self.kwargs``` به آرگومان‌های کلید واژه‌ای دسترسی پیدا کنید. اکثر ویوهای عمومی انتظار دارند که آرگومان‌هایشان صرفاً آرگومان کلید واژه‌ای باشد برای مثال ```self.kwargs["slug"]```. ##### آیا میتوانیم سینکس ساده شده را با عبارات منظم جایگزین کنیم؟ باور دارم که شما کاملاً می‌توانید به سینتکس ساده شده سوئیچ کرده و از عبارت منظم برای مطابقت الگو دوری کنید. عبارات منظم ممکن است که قدرتمندتر به نظر برسند ولی آن‌ها خوانایی را قربانی می‌کنند و همچنین محدودیت‌های خود را دارند. الگوی سال مثال قبل را در نظر بگیرید. بعضی از مردم باهوش ممکنه عبارت منظم را به صورت ```^year/(\d{4})/``` بنویسند اما در مورد سال 793 میلادی چطور(وقتی وایکینگ‌ها شروع به حمله ایرلند کردند) یا 11234 میلادی(شاید ورود واکینگ‌های فضایی به زمین؟) یا هر سال غیر 4 عددی دیگر؟ الگوی ساده شده ```>> 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('/', views.ArticleView.as_view(), name='article'), ] ``` اگر ما ترتیب را برعکس کنیم بعد آن مورد خاص یعنی ```AboutView``` هیچوقت صدا زده نخواهد شد. #### سبک‌های الگو URL طراحی URLهای سایت به راحتی ممکنه است نادیده گرفته شود. طراحی خوب URLها نه تنها می‌تواند به صورت منطقی سایت شما را سازمانی دهی کند بلکه می‌تواند حدس زدن مسیرها برای کاربران را نیز راحت‌تر کند. طراحی ضعیف حتی ممکنه که ریسک‌های امنیتی به وجود بیاورد: برای مثال استفاده از ID دیتابیس(که در یک دنباله افزایشی یکنواخت از اعداد صحیح رخ می‌دهد) در الگوی URL می‌تواند ریسک دزدیده شدن اطلاعات خراب کردن سایت را افزایش دهد. بیاید چندین سبک مرسوم طراحی کردن URL را امتحان کنیم. ##### URLهای به سبک دپارتمان‌های فروش بعضی از سایت‌ها شبیه دپارتمان‌های فروش گذاشته شده‌اند. قسمتی برای غذا وجود دارد که در درونش راهرویی برای میوه وجود خواهد داشت که در درون این قسمت انواع مختلفی از سیب‌ها کنار هم قرار گرفته‌اند. در مورد URLها، به این معناست که صفحات مرتب شده را می‌توانید به صورت سلسله مراتبی پیدا کنید که به صورت زیر است: ``` http://site.com/
/ / ``` زیبایی این لایه این است که می‌توان به آسانی از آن بالا رفت و به قسمت پدر رسید. هر موقع که آخر URL بعد از هر اسلش را پاک کنید یک سطح بالاتر خواهید رفت. به طور مثال شما می‌توانید ساختار مشابهی را برای ```article``` بسازید که در زیر نشان داده شده است: ```python blog_patterns = [ path('', views.BlogHomeView.as_view(), name='blog_home'), path('/', views.ArticleView.as_view(), name='article'), ] ``` به الگوی ```blog_home``` توجه کنید که اگر کاربری از یک مقاله خاص بالا برود یک فهرست مقاله را نشان خواهد داد. ##### URLهای RESTful در سال 2000، روی فیلدینگ(Roy Fielding) در پایان نامه دکترای خود عبارت **Representational state transfer (REST)** را معرفی کرد. خواندن تز او () به شدت توصیه می‌شود تا معماری وب را بهتر متوجه شوید. به شما کمک خواهد کرد که وب اپلیکیشن‌های بهتری بنویسید که محدودیت‌های هستۀ معماری را نقض نخواهد کرد. یکی از بینش‌های کلیدی این است که URI شناسه یک منبع است. یک منبع می‌تواند هر چیزی باشد مانند یک مقاله، یک کاربر یا مجموعه‌ای منابع مثل رخداد‌ها. به طور کلی، منابع اسم هستند. وب برخی از عملکردهای اساسی HTTP برای دستکاری منابع را در اختیار شما قرار می‌دهد: ```GET، POST، PUT، PATCH و DELETE```. **نکته(TIP)** این‌ها قسمتی از یک URL نیستند و از این رو تمرین بدی است اگر از این عملکردها در URL، برای دستکاری منابع استفاده کنیم. به طور مثال، مثالی که در ادامه آمده است URL بدی در نظر گرفته می‌شود: به جای آن شما باید عملکرد را از URL حذف کرده و از عمل ```POST``` استفاده کنید: توجه کنید که همیشه استفاده کردن از این عملکردها در URL اشتباه نیست. URL جستجوی سایت شما می‌تواند فعل search را داشته باشد تا زمانی که با یکی از منابع به عنوان REST مرتبط نیست: 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)** را به جای ویوهای اپلیکیشن جنگوی شما جایگذاری می‌کند. همانطور که در نمودار زیر نمایش داده شده است: |![نمودار کارکرد جنگو با و بدون ری‌اکت](../04-%20Views%20and%20URLs/images/img2.jpg)| |:--:| |چگونه اضافه کردن ری‌اکت معماری سنتی سایت جنگویی را تغییر می‌دهد. این یکی از چند راه موجود است که می‌توان ری‌اکت و جنگو را با هم ادغام کرد.| شما می‌توانید از فریمورک رست جنگو یا سرویس ساده ویو برای ارسال داده 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)**: قالب‌های جنگو می‌توانند به وسیله تگ ``` ``` جای تعجب نیست که مرورگر یک جعبه هشدار با پیام شوم - pwned را نشان می دهد.
این حمله در مرورگرهای فعلی مانند آخرین کروم با شکست مواجه می‌شود، 
که پیام خطای زیر را در کنسول نشان می‌دهد: از اجرای اسکریپت جاوا اسکریپت خودداری کرد. 
. کد منبع اسکریپت در درخواست یافت شد.
اگر نمی‌دانید که یک پیام هشدار ساده چه آسیبی می‌تواند داشته باشد، به یاد داشته باشید که هر کد جاوا اسکریپت را می‌توان به همان شیوه اجرا کرد. در بدترین حالت، کوکی های کاربر را می توان با وارد کردن عبارت جستجوی زیر به سایتی که توسط مهاجم کنترل می شود ارسال کرد: ```js ``` پس از ارسال کوکی‌های شما، مهاجم ممکن است بتواند حمله جدی‌تری انجام دهد. ### چرا کوکی های شما ارزشمند است؟ شاید ارزش درک این را داشته باشد که چرا کوکی ها هدف چندین حمله هستند. به زبان ساده، دسترسی به کوکی‌ها به مهاجمان اجازه می‌دهد که شما را جعل کنند و حتی کنترل حساب وب شما را در دست بگیرند. برای درک دقیق این موضوع، باید مفهوم **sessions** را درک کنید.HTTP بدون تابعیت است این بدان معناست که هر درخواست HTTP که سرور دریافت می‌کند مستقل است و به درخواست‌هایی که قبل از آن ارائه شده‌اند مربوط نمی‌شود. جنگو چه یک کاربر ناشناس یا یک کاربر تأیید شده باشد، با مدیریت جلسات، فعالیت های آنها را برای مدت زمان مشخصی پیگیری می کند. یک جلسه شامل شناسه جلسه در انتهای کلاینت، یعنی مرورگر و یک شی دیکشنری مانند است که در انتهای سرور ذخیره می شود. شناسه جلسه یک رشته تصادفی 32 نویسه ای است که به عنوان یک کوکی در مرورگر ذخیره می شود. هر بار که یک کاربر از یک وب سایت درخواستی می کند، تمام کوکی های او، از جمله این شناسه جلسه، همراه با درخواست ارسال می شود. در انتهای سرور، جنگو یک ذخیره‌سازی جلسه دارد که این شناسه جلسه را به داده‌های جلسه نگاشت می‌کند. به طور پیش فرض، جنگو داده های جلسه را در جدول پایگاه داده **django_session** ذخیره می کند. هنگامی که کاربر با موفقیت وارد سیستم می شود، جلسه متوجه می شود که احراز هویت موفقیت آمیز بوده و کاربر را پیگیری می کند. بنابراین، کوکی به یک تأیید هویت موقت کاربر برای تراکنش‌های بعدی تبدیل می‌شود. هر کسی که این کوکی را بدست آورد می تواند از این برنامه وب به عنوان آن کاربر استفاده کند که به آن Session Hijacking می گویند. ### جنگو چگونه کمک میکند؟ شاید مشاهده کرده باشید که مثال من به دو دلیل روشی بسیار غیرمعمول برای پیاده سازی view در جنگو بود: از الگوها برای رندر استفاده نمی کرد و کلاس های فرم استفاده نمی شد. هر دوی آنها اقدامات پیشگیری XSS را دارند. به‌طور پیش‌فرض، جنگو الگوهای فرار خودکار نویسه‌های ویژه HTML را ایجاد می‌کند. بنابراین، اگر رشته جستجو را در قالب نمایش داده بودید، همه تگ ها با کد HTML بودند. این کار تزریق اسکریپت‌ها را غیرممکن می‌کند، مگر اینکه صریحاً با علامت‌گذاری محتوا به‌عنوان امن، آنها را خاموش کنید. استفاده از کلاس‌های فرم در جنگو برای اعتبارسنجی و سالم‌سازی ورودی نیز یک اقدام متقابل بسیار مؤثر است. برای مثال، اگر برنامه شما به شناسه کارمند عددی نیاز دارد، از کلاس **IntegerField** به جای کلاس **CharField** مجاز تر استفاده کنید. در مثال ما، می‌توانیم از یک کلاس **RegexValidator** در فیلد عبارت جستجوی خود استفاده کنیم تا کاربر را به کاراکترهای الفبایی محدود کنیم و اجازه دهیم نمادهای نقطه‌گذاری توسط ماژول جستجوی شما شناسایی شوند. محدوده قابل قبول ورودی کاربر را تا حد امکان محدود کنید. ### جایی که جنگو ممکن است کمکی نکند جنگو می تواند از طریق فرار خودکار در قالب ها از ۸۰ درصد حملات XSS جلوگیری کند. برای سناریوهای باقی مانده، باید مراقب باشید که وظایف زیر را انجام دهید: - Quote all HTML attributes, for example, replace `` with `` - فرار از داده های پویا در CSS یا جاوا اسکریپت با استفاده از روش های **سفارشی** - اعتبارسنجی همه URL ها، به ویژه در برابر پروتکل های ناامن مانند جاوا اسکریپت - اجتناب از XSS سمت کاربر (همچنین به عنوان XSS مبتنی بر DOM شناخته می شود) به عنوان یک قانون کلی در برابر XSS، من فیلتر در ورودی و معادل سازی خروجی(output escaping) را پیشنهاد می‌کنم. اطمینان حاصل کنید که هر داده‌ای را که وارد می‌شود کاملاً اعتبارسنجی و پاکسازی (فیلتر) شده باشد و بلافاصله قبل از ارسال آن به کاربر، آن‌ها را تبدیل (output escaping) می‌کنید—مخصوصاً، اگر نیاز به پشتیبانی از ورودی کاربر با قالب‌بندی HTML مانند نظرات دارید، از Markdown استفاده کنید.
فیلتر کردن ورودی ها و معادل سازی خروجی ها
### جعل های درخواست بین سایتی(Cross-site request forgery) **جعل های درخواست بین سایتی (CSRF)** حمله ای است که کاربر را فریب می دهد تا در حالی که از سایت دیگری بازدید می کند، اقدامات ناخواسته ای را در یک وب سایت انجام دهد، جایی که قبلاً احراز هویت شده اند. مثلاً در یک فرم، یک مهاجم می‌تواند یک تگ IMG یا IFRAME را در صفحه قرار دهد که درخواستی را که به دقت طراحی شده است به سایت تأیید شده ارسال می‌کند. به عنوان مثال، تصویر جعلی0\*0 زیر را می توان در یک کامنت جاسازی کرد: ```html ``` اگر قبلاً از برگه دیگری وارد SuperBook شده اید، و اگر سایت اقدامات متقابل CSRF نداشته باشد، پیام بسیار شرم آور پست می شود. به عبارت دیگر، CSRF به مهاجم اجازه می دهد تا با فرض هویت شما اقداماتی را انجام دهد. ### جنگو چگونه کمک میکند حفاظت اولیه در برابر CSRF استفاده از HTTP **POST** یا **PUT** و **DELETE**، (در صورت پشتیبانی) برای هر اقدامی است که دارای عوارض جانبی است. هر درخواست) GET یا( HEAD باید برای بازیابی اطلاعات استفاده شود، به عنوان مثال، فقط خواندنی. جنگو اقدامات متقابلی را علیه روش‌های **POST**، **PUT** یا **DELETE** با جاسازی یک توکن ارائه می‌کند. شما باید قبلاً با **{% csrf_token %}** ذکر شده در هر قالب فرم جنگو آشنا باشید. این به یک مقدار تصادفی تبدیل می شود که باید هنگام ارسال فرم وجود داشته باشد. روش کار به این صورت است که مهاجم نمی تواند توکن را در حین ایجاد درخواست در سایت تأیید شده شما حدس بزند . از آنجایی که توکن اجباری است و باید با مقدار ارائه شده در حین پخش فرم مطابقت داشته باشد، ارسال فرم با شکست مواجه می شود و حمله خنثی می شود. ### جایی که جنگو ممکن است کمکی نکند برخی از افراد چک‌های CSRF را در view با دکوراتور @csrf_exempt خاموش می‌کنند، مخصوصاً برای پست‌های فرم AJAX. این توصیه نمی شود مگر اینکه خطرات امنیتی مربوطه را به دقت در نظر گرفته باشید. ## تزریق SQL (SQL injection) تزریق SQL دومین آسیب پذیری رایج برنامه های کاربردی وب پس از XSS است. این حمله شامل وارد کردن کد SQL مخرب در یک کوئری است که در پایگاه داده اجرا می شود. این می تواند منجر به سرقت داده ها، با ریختن محتوای پایگاه داده، یا تخریب داده ها، مثلاً با استفاده از دستور **DROP TABLE** شود. اگر با SQL آشنا هستید، می توانید کد زیر را درک کنید. یک آدرس ایمیل را بر اساس **نام کاربری** داده شده جستجو می کند: ```python name = request.GET['user'] sql = "SELECT email FROM users WHERE username = '{}';".format(name) ``` در نگاه اول، ممکن است به نظر برسد که فقط آدرس ایمیل مربوط به نام کاربری ** که به عنوان پارامتر **GET\*\* ذکر شده است، بازگردانده خواهد شد. با این حال، تصور کنید اگر یک مهاجم 'OR'1'='1' را در قسمت فرم وارد کند، کد SQL به شکل زیر خواهد بود: ```sql SELECT email FROM users WHERE username = '' OR '1'='1'; ``` از آنجایی که این بند **WHERE** همیشه درست خواهد بود، ایمیل های همه کاربران برنامه شما برگردانده می شود. این می تواند یک نشت جدی اطلاعات محرمانه باشد. مجدداً، اگر مهاجم بخواهد، می تواند پرس و جوهای خطرناک تری مانند موارد زیر را اجرا کند: ```sql SELECT email FROM users WHERE username = ''; DELETE FROM users WHERE '1'='1'; ``` اکنون، تمام ورودی های کاربر از پایگاه داده شما پاک می شود! ### جنگو چگونه کمک میکند اقدام متقابل در برابر تزریق SQL نسبتاً ساده است. از Django ORM به جای ساخت دستورات SQL به صورت مستقیم استفاده کنید. مثال قبل باید به صورت زیر پیاده سازی شود ```python User.objects.get(username=name).email ``` در اینجا، درایورهای پایگاه داده جنگو به طور خودکار از پارامترها فرار می کنند. این اطمینان حاصل می کند که آنها به عنوان داده صرفاً در نظر گرفته می شوند و بنابراین بی ضرر هستند. با این حال، همانطور که به زودی خواهیم دید، حتی ORM دارای چندین مشکل است. ### جایی که جنگو ممکن است کمکی نکند مثلاً به دلیل محدودیت‌های ORM جنگو، ممکن است مواردی وجود داشته باشد که افراد نیاز به استفاده از SQL خام داشته باشند. برای مثال با استافاده از متد **extra()** میتوانید از کد های sql خام استفاده کنید ولی ابن کد sql در مقابل SQL injections مقاوم نیست اگر از API سطح پایین ORM، مانند متد **execute()** استفاده می کنید، ممکن است بخواهید به جای درون یابی رشته SQL، پارامترهای bind را ارسال کنید. حتی در این صورت، اکیداً توصیه می‌شود که بررسی کنید که که آیا هر شناسه به درستی حذف شده است یا خیر. در نهایت، اگر از API پایگاه داده شخص ثالث مانند MongoDB استفاده می کنید، باید به صورت دستی تزریق SQL را بررسی کنید. در حالت ایده‌آل، شما می‌خواهید فقط از داده‌های کاملاً تمیز شده با چنین رابط‌هایی استفاده کنید. ### دزدی کلیک (Clickjacking) **Clickjacking** وسیله ای برای گمراه کردن کاربر برای کلیک بر روی پیوند یا دکمه مخفی در مرورگر زمانی که قصد کلیک روی چیز دیگری را دارد. این معمولاً با استفاده از یک IFRAME نامرئی که حاوی وب سایت مورد نظر است، اجرا می شود روی یک صفحه وب ساختگی (در اینجا نشان داده شده است) که کاربر احتمالاً روی آن کلیک می کند: ![image 02](2.jpg) از آنجایی که دکمه عمل در قاب نامرئی دقیقاً در بالای دکمه در صفحه ساختگی تراز شده است، کلیک کاربر به جای آن اقدامی را در وب سایت مورد نظر انجام می دهد. ### جنگو چگونه کمک میکند جنگو با استفاده از میان افزارهایی (middleware)که می توانند با استفاده از چندین دکوراتور به خوبی تنظیم شوند، از سایت شما در برابر کلیک جک محافظت می کند. به طور پیش‌فرض، این میان‌افزار «django.middleware.clickjacking.XFrameOptionsMiddleware» در **MIDDLEWARE_CLASSES** شما در فایل تنظیمات شما گنجانده می‌شود. با تنظیم هدر XFrame-Options روی **SAMEORIGIN** برای هر **HttpResponse** خروجی کار می کند. اکثر مرورگرهای مدرن هدر را تشخیص می دهند، به این معنی که این صفحه نباید در یک قاب در دامنه های دیگر باشد. حفاظت را می توان برای نماهای خاصی با استفاده از دکوراتورها، مانند `@xframe_options_deny` و `@xframe_options_exempt` فعال و غیرفعال کرد. ### تزریق دستورات سیستم عامل (Shell injection) همانطور که از نام آن پیداست، تزریق shell یا تزریق فرمان به مهاجم اجازه می دهد تا کد مخرب را به shell سیستم مانند bash تزریق کند. حتی برنامه‌های کاربردی تحت وب نیز از برنامه‌های خط فرمان برای راحتی و عملکردشان استفاده می‌کنند. چنین فرآیندهایی معمولاً در یک shell اجرا می شوند. به عنوان مثال، اگر می خواهید تمام جزئیات فایلی را که نام آن توسط کاربر داده شده است را نشان دهید، یک پیاده سازی ساده به صورت زیر خواهد بود: ```python os.system("ls -l {}".format(filename)) ``` مهاجم می‌تواند نام فایل را به‌عنوان «manage.py» وارد کند. rm -rf \*` و تمام فایل های دایرکتوری خود را حذف کنید. به طور کلی استفاده از "os.system" توصیه نمی شود. ماژول subprocess جایگزین امن‌تری است (یا حتی بهتر است ، ، می‌توانید از «os.stat()» برای دریافت ویژگی‌های فایل استفاده کنید). از آنجایی که یک shell آرگومان های خط فرمان و متغیرهای محیطی را تفسیر می کند، تنظیم مقادیر مخرب در آنها می تواند به مهاجم اجازه دهد تا دستورات سیستم دلخواه را اجرا کند. ### جنگو چگونه کمک میکند جنگو در درجه اول برای استقرار به WSGI وابسته است. از آنجایی که WSGI، برخلاف CGI، بر روی متغیرهای محیطی (environment variables)بر اساس خود چارچوب در پیکربندی پیش‌فرض خود در برابر تزریق فرمان خط آسیب‌پذیر نیست. با این حال، اگر برنامه جنگو نیاز به اجرای سایر فایل های اجرایی داشته باشد، باید مراقب اجرای آن به صورت محدود، یعنی با حداقل مجوزها باشد. هر پارامتری که منشا خارجی دارد باید قبل از ارسال به چنین فایل های اجرایی آنیتیزه شود. علاوه بر این، از call() از ماژول subprocess برای اجرای برنامه‌های خط فرمان با پارامتر پیش‌فرض «shell=False» استفاده کنید تا اگر درون‌یابی فرمان خط لازم نباشد، آ آرگومان‌ها را به‌طور ایمن مدیریت کنید. ### و حملات وب پایان ناپذیر هستند صدها تکنیک حمله وجود دارد که ما در اینجا به آنها اشاره نکرده‌ایم، ، و با یافتن حملات جدید، فهرست هر روز بیشتر می‌شود. مهم است که خود را از آنها آگاه کنیم. وبلاگ رسمی جنگو (https://www.djangoproject.com/weblog/) مکانی عالی برای اطلاع از آخرین اکسپلویت های کشف شده است. نگهبانان جنگو به طور فعال سعی می کنند با انتشار نسخه های امنیتی آنها را حل کنند. به شدت توصیه می شود که آنها را در اسرع وقت نصب کنید زیرا معمولاً نیاز به تغییر بسیار کمی در کد منبع شما دارند یا هیچ تغییری ندارند. امنیت اپلیکیشن شما به اندازه ضعیف ترین لینک آن قوی است. حتی اگر کد جنگو شما کاملاً ایمن باشد، لایه‌ها و مؤلفه‌های زیادی در پشته شما وجود دارد، به غیر از عناصر انسانی، که می‌توان با تکنیک‌های مختلف مهندسی اجتماعی، مانند فیشینگ، آنها را فریب داد. آسیب پذیری ها در یک منطقه، مانند سیستم عامل، پایگاه داده یا وب سرور ، می توانند برای دسترسی به سایر بخش های سیستم مورد سوء استفاده قرار گیرند. از این رو، بهتر است به جای مشاهده جداگانه هر قسمت، یک دید کلی از پشته خود داشته باشید.
    **اتاق امن**
به محض اینکه استیو از اتاق هیئت مدیره خارج شد، تلفن خود را بیرون آورد و 
یک ایمیل تک خطی واضح را به تیمش ارسال کرد: "این کار است!"

در 60 دقیقه اخر او توسط پرسش های جزعی رعیس درباره کوچک 
ترین نکات اجرای برنامه به چالش کشیده شد. 
خانم اُ، با ناراحتی استیو، در تمام مدت سکوت خود را حفظ کرد.
    
وارد کابینش شد و یک بار دیگر پرینت اسلایدهایش را باز کرد. پس از معرفی چک لیست ها، 
. پس از معرفی چک لیست ها، تعداد باگ های بی اهمیت به شدت کاهش یافت. ویژگی‌های اساسی که گنجاندن آنها 
در نسخه غیرممکن بود از طریق همکاری اولیه با سرورهای 
سرورهای مفیدی مانند Hexa و Aksel به کار گرفته شدند.

به لطف کمپین بازاریابی درخشان سو، تعداد ثبت نام‌ها برای سایت بتا از 9000 گذشت.  
استیو هرگز در دوران حرفه‌ای خود این همه علاقه برای راه اندازی ندیده بود
آن موقع بود که متوجه چیز عجیبی در مورد روزنامه روی میزش شد.
    
پانزده دقیقه بعد، او با عجله از راهرو در طبقه 21 پایین آمد. در انتهای آن، 
، دری با علامت 2109 وجود داشت. وقتی در را باز کرد، اوان را دید که
روی چیزی شبیه یک لپ‌تاپ اسباب‌بازی پلاستیکی سفید کار می‌کرد. 
استیو پرسید: "چرا سرنخ های جدول کلمات متقاطع را حلقه زدی؟
می توانستی با من تماس بگیری."    

    «او با پوزخند پاسخ داد: می خواهم چیزی به شما نشان دهم». لپ تاپش را گرفت و بیرون رفت.
    او بین اتاق 2110 و خروجی آتش متوقف شد. روی زانوهایش افتاد و با دست راست
    کاغذ دیواری رنگ و رو رفته را گرفت. او زمزمه کرد: "اینجا باید یک قفل وجود داشته باشد."
    
    
    سپس دستش ایستاد و دسته ای را که به سختی از دیوار بیرون زده بود چرخاند. 
    قسمتی از دیوار چرخید و متوقف شد. ورودی اتاقی را نشان داد که با چراغ قرمز 
    روشن شده بود. تابلویی در داخل که از پشت بام آویزان بود 
    روی آن نوشته شده بود: «اتاق امن »21B.
    
    وقتی وارد شدند، صفحات و چراغ‌های متعددی به خودی خود روشن شدند. 
    یک صفحه نمایش بزرگ روی دیوار نوشته بود "احراز هویت لازم است. کلید را وارد کنید." 
    ایوان برای مدت کوتاهی این را تحسین کرد و شروع به سیم کشی لپ تاپ خود کرد.
    
    ایوان، ما اینجا چه کار می کنیم؟" استیو با صدایی خاموش پرسید. 
    " ایوان ایستاد، "اوه، درست است. حدس می‌زنم تا پایان آزمایش‌ها کمی وقت داریم."
    او یک نفس عمیق کشید.
    
    "به یاد دارید زمانی که خانم O از من خواست به پایگاه کد سنتینل نگاه کنم؟ 
    من این کار را کردم. متوجه شدم که کد منبع سانسور شده به ما داده شده است. 
    منظورم این است که می توانم برخی از رمزهای عبور را اینجا و آنجا حذف کنم، 
    اما هزاران خط کد را درک کنم؟ مدام فکر می کردم - باید اتفاقی می افتاد."
    
    
    بنابراین، با دسترسی من به بایگانی، برخی از نسخه های پشتیبان قدیمی را برداشتم. 
    " احتمال پاک نشدن یک رسانه مغناطیسی به طرز شگفت آوری زیاد است. 
    به هر حال، من می توانم بیشتر کدهای پاک شده را بازیابی کنم. آنچه را دیدم باور نخواهید کرد. "
    
    
    Sentinel یک پروژه شبکه اجتماعی معمولی نبود. 
    این یک برنامه نظارتی بود. شاید بزرگترین شناخته شده برای بشر.
    
    پس از جنگ سرد، گروهی از کشورها برای ایجاد شبکه ای 
    برای اشتراک گذاری اطلاعات اطلاعاتی به یکدیگر پیوستند. شبکه ای از انسان ها
    و نگهبانان. انتینل ها کامپیوترهایی نیمه مستقل با قدرت محاسباتی باورنکردنی هستند.
    برخی معتقدند که آنها کامپیوترهای کوانتومی هستند.
    
    نگهبان‌ها در هزاران مکان استراتژیک در سراسر بسترهای اقیانوسی جهان 
    که کابل‌های اصلی فیبر نوری از آنجا عبور می‌کنند، قرار گرفتند.
    آنها با انرژی زمین گرمایی کار می‌کردند، و عملاً غیرقابل تخریب بودند. 
    آنها تقریباً به تمام ارتباطات اینترنتی در اکثر کشورها دسترسی داشتند.
    
    در مقطعی در دهه نود، شاید از ترس نظارت عمومی، برنامه سنتینل تعطیل شد. 
    اینجاست که واقعا جالب می شود. تاریخچه کد نشان می دهد که توسعه در Sentinels 
    توسط شخصی به نام Cerebos ادامه یافت. این کد به طرز چشمگیری از توانایی های نظارتی 
    خود برای ایجاد نوعی ابر رایانه موازی بسیار افزایش یافته است. یک جانور اعداد خرد کننده 
    که هیچ الگوریتم رمزگذاری برای او چالش مهمی ایجاد نمی کند.
    
    نقض را به یاد دارید؟ برای من سخت بود که باور کنم قبل از آمدن ابرقهرمانان 
    حتی یک حرکت تهاجمی وجود نداشت. بنابراین، من کمی تحقیق کردم. 
    امنیت سایبری SHIM به صورت پنج حلقه متحدالمرکز طراحی شده است. 
    ما، کارمندان، در بیرونی ترین، کم برخوردارترین حلقه ای هستیم که توسط سائورون 
    محافظت می شود. حلقه‌های داخلی با الگوریتم‌های رمزنگاری قوی‌تر طراحی می‌شوند. 
    این اتاق در طبقه 4 است.
    
    حدس من این است که مدت‌ها قبل از اینکه ما از این رخنه مطلع شویم، همه 
    سیستم‌های سائورون در معرض خطر قرار گرفته بودند. سیستم‌ها از کار افتاده بودند
    و ورود آن روبات‌ها به محوطه دانشگاه عملاً یک راهپیمایی بود. من فقط به سیاهه ها نگاه کردم. 
    این حمله بسیار هدفمند بود – همه چیز از آدرس های IP گرفته 
    تا ورود به سیستم از قبل شناخته شده بود.
    
    "داخلی?" استیو با وحشت پرسید.
    
    بله. با این حال، Sentinels فقط برای سطح 5 نیاز به کمک داشت. 
    " هنگامی که آنها کلیدهای عمومی برای سطح 4 را به دست آوردند،
    آنها شروع به حمله به سیستم های سطح 4 کردند. این دیوانه به نظر
    می رسد اما این استراتژی آنها بود."

    "چرا دیوانه است؟"
    
    "خب، بیشتر امنیت آنلاین جهان مبتنی بر رمزنگاری با کلید عمومی یا رمزنگاری
    نامتقارن است. این امنیت مبتنی بر دو کلید است: یکی عمومی و دیگری خصوصی. 
    اگرچه از نظر ریاضی مرتبط است، یافتن یک کلید در صورت داشتن
    این کلید از نظر محاسباتی غیرعملی است. دیگر"
    
    "آیا می گویید که شبکه سنتینل می تواند؟"
    
    "در واقع، آنها می توانند برای کلیدهای کوچکتر. بر اساس آزمایشاتی که در حال حاضر 
    انجام می دهم، قدرت آنها به طور قابل توجهی افزایش یافته است. 
    با این سرعت، آنها باید در کمتر از 24 ساعت برای حمله دیگری آماده شوند."
    
    "لعنتی، آن زمان است که SuperBook پخش می شود!"

### یک چک لیست امنیتی مفید امنیت یک فکر بعدی نیست، بلکه در نحوه نوشتن برنامه ها ضروری است. با این حال، از آنجایی که انسان هستید، داشتن یک چک لیست برای یادآوری موارد مشترک مفید است. نکات زیر حداقلی از بررسی های امنیتی است که باید قبل از عمومی کردن برنامه جنگو خود انجام دهید: - **به داده های یک مرورگر، API یا هر منبع خارجی اعتماد نکنید:** این یک قانون اساسی است. اطمینان حاصل کنید که هر گونه داده خارجی را تأیید و پاکسازی می کنید. - **نگه ندارید** کلید خصوصی را **در کنترل نسخه:** به عنوان بهترین تمرین، انتخاب کنید **کلید خصوصی** پکیج `django-environ` را ببینید. - **رمزهای عبور را در متن ساده ذخیره نکنید:** در عوض هش رمز عبور برنامه خود را ذخیره کنید. یک نمک تصادفی نیز اضافه کنید. - **هیچ داده حساسی را ثبت نکنید:** اطلاعات محرمانه مانند جزئیات کارت اعتباری یا کلیدهای API را قبل از ثبت آنها در فایل های گزارش خود فیلتر کنید. - **هر تراکنش امن یا ورود به سیستم باید از SSL استفاده کند:** آگاه باشید که استراق سمع کنندگان در همان شبکه ای که اگر در HTTPS نباشد، می توانید به ترافیک وب خود گوش دهید. در حالت ایده آل، شما باید از HTTPS برای کل سایت استفاده کنید. - **از استفاده از تغییر مسیر به URL های ارائه شده توسط کاربر خودداری کنید:** اگر ریدایرکت هایی مانند http://example.com/r?url=http://evil.com, سپس همیشه دامنه های در لیست سفید را بررسی کنید. - **بررسی مجوز حتی برای کاربران احراز هویت شده:** قبل از انجام هر کدام تغییر با عوارض جانبی، بررسی کنید که آیا کاربر وارد شده مجاز به انجام آن است یا خیر. - **از دقیق ترین عبارات منظم ممکن استفاده کنید:** چه «URLconf» یا اعتبار سنجی‌های فرم شما، باید از عبارات منظم تنبل و عمومی اجتناب کنید. - **کد پایتون خود را در ریشه وب نگه ندارید:** این می تواند منجر به نشت تصادفی کد منبع شود اگر به عنوان متن ساده ارائه شود. - **به جای ساختن رشته ها با دست از الگوهای جنگو استفاده کنید:** قالب ها در برابر حملات XSS محافظت می کنند. - **از Django ORM به جای دستورات SQL استفاده کنید:** ORM محافظت در برابر تزریق SQL را ارائه می دهد. - **از فرم های جنگو با ورودی POST برای هر اقدامی با عوارض جانبی استفاده کنید:** ممکن است استفاده از فرم ها برای یک دکمه ساده رای زیاده روی به نظر برسد، اما این کار را انجام دهید. - **CSRF باید فعال و استفاده شود:** اگر نماهای خاصی را با استفاده از دکوراتور @csrf_exempt معاف می کنید، بسیار مراقب باشید. - **اطمینان حاصل کنید که جنگو و همه بسته ها آخرین نسخه هستند:** برای به روز رسانی برنامه ریزی کنید. آنها ممکن است نیاز به تغییراتی در کد منبع شما داشته باشند. با این حال، آن‌ها ویژگی‌های جدید درخشان و اصلاحات امنیتی را نیز به ارمغان می‌آورند. - **اندازه و نوع فایل های آپلود شده توسط کاربر را محدود کنید:** بارگذاری فایل های اجرایی یا اسکریپت ها را رد کنید. بارگذاری فایل های اجرایی یا اسکریپت ها را رد کنید. - **یک برنامه پشتیبان و بازیابی داشته باشید:** به لطف مورفی، می توانید برای یک حمله اجتناب ناپذیر، فاجعه یا هر نوع خرابی دیگری برنامه ریزی کنید. اطمینان حاصل کنید که برای به حداقل رساندن از دست دادن داده ها، به طور مکرر نسخه پشتیبان تهیه می کنید. برخی از این موارد را می توان به طور خودکار با استفاده از Erik's Pony Checkup در http://ponycheckup.com/ بررسی کرد. با این حال، توصیه می کنم این چک لیست را پرینت یا کپی کنید و روی میز خود بچسبانید. به یاد داشته باشید که این لیست به هیچ وجه جامع نیست و جایگزینی برای ممیزی امنیتی مناسب توسط یک متخصص نیست. ### خلاصه در این فصل، ما به انواع رایج حملاتی که بر وب سایت ها و برنامه های کاربردی وب تأثیر می گذارند نگاه کردیم. در بسیاری از موارد، توضیح تکنیک ها برای وضوح و به قیمت جزئیات ساده شده است. با این حال، هنگامی که ما شدت حمله را درک کنیم، می توانیم از اقدامات متقابلی که جنگو ارائه می دهد قدردانی کنیم. در فصل پایانی خود، نگاهی به فعالیت های پیش از استقرار با جزئیات بیشتر خواهیم داشت. ما همچنین نگاهی به استراتژی های مختلف استقرار، مانند میزبانی مبتنی بر ابر برای استقرار یک برنامه جنگو خواهیم داشت. ================================================ FILE: 13-Production-Ready/README.md ================================================ # آماده برای تولید در این فصل به مباحث زیر می پردازیم: - انتخاب یک وب استک - رویکردهای میزبانی - بزارهای استقرار - مانیتورینگ - نکاتی برای کارایی خوب شما تاکنون یک برنامه وب کاملا کاربردی را در جنگو توسعه داده و آنرا مورد آزمایش قرار داده اید. استقرار این برنامه می‌تواند شامل مجموعه‌ای از فعالیت‌ها از انتخاب ارائه‌دهنده میزبانی شما تا اجرای نصب باشد. حتی چالش‌برانگیزتر می‌تواند اقدامات مربوط به حفظ یک سایت تولید شده باشد، تا بدون وقفه و مدیریت انفجارهای غیرمنتظره در ترافیک بالا کار ‌کند. قوانین مدیریت سیستم بسیار گسترده است. از این رو، این فصل زمینه های زیادی را پوشش خواهد داد. با این حال، با توجه به فضای محدود، سعی می کنیم شما را با جنبه های مختلف ساخت یک محیط تولید و پروداکشن آشنا کنیم. ### محیط تولید و پروداکشن اگرچه بسیاری از ما به طور شهودی درک می کنیم که یک محیط تولید چیست، مشخص نمودن معنای واقعی آن حائز اهمیت است. محیط تولید به سادگی محیطی است که کاربران نهایی از برنامه شما استفاده می کنند. باید در دسترس، انعطاف پذیر، ایمن، پاسخگو باشد و باید ظرفیت فراوانی برای نیازهای فعلی (و آینده) داشته باشد. بر خلاف یک محیط توسعه، احتمال آسیب واقعی کسب و کار به دلیل هر گونه مسائل در یک محیط تولید بالا است. از این رو، قبل از حرکت به سمت تولید، کد به محیط‌های مختلف آزمایش و پذیرش منتقل می‌شود تا تا حد امکان از شر اشکالات خلاص شود. برای ردیابی آسان، هر تغییری که در محیط تولید ایجاد می‌شود باید ردیابی، مستند شده و برای همه اعضای تیم قابل دسترسی باشد. در نتیجه، هیچ توسعه ای نباید به طور مستقیم در محیط تولید انجام شود. در واقع نیازی به نصب ابزارهای توسعه مانند کامپایلر یا دیباگر در محیط تولید نیست. وجود هر نرم افزار غیر ضروری سطح حمله سایت شما را افزایش می دهد و می تواند خطر امنیتی ایجاد کند. اکثر برنامه‌های کاربردی وب در سایت‌هایی با خرابی بسیار کم مستقر می‌شوند، به عنوان مثال، مراکز داده بزرگ در پنج 9، یعنی 99.999 درصد، آپتایم هستند. با طراحی برای خرابی، حتی اگر یک جزء داخلی خراب شود، افزونگی کافی برای جلوگیری از خرابی کل سیستم وجود دارد. مفهوم اجتناب از یک نقطه شکست (SPOF) را می توان در هر سطح، سخت افزار یا نرم افزار اعمال کرد. این امر درواقع مجموعه ای مهم از نرم افزارهایی است که شما انتخاب می کنید تا در محیط تولید خود اجرا شوند. ### انتخاب یک پشته وب(استک) تا کنون، ما در مورد استک یا پشته ای که برنامه شما روی آن اجرا می شود صحبت نکرده ایم. حتی اگر در انتهای این کتاب در مورد آن صحبت می کنیم، بهتر است چنین تصمیماتی را به مراحل بعدی چرخه حیات برنامه موکول نکنید. در حالت ایده‌آل، محیط توسعه شما باید تا حد امکان به محیط تولید نزدیک باشد تا از این جمله که می گوید اما در ماشین من کار می‌کند، جلوگیری شود. با یک پشته وب، به مجموعه فناوری هایی اشاره می کنیم که برای ساخت یک برنامه وب استفاده می شوند. معمولاً به صورت مجموعه‌ای از مؤلفه‌ها، مانند سیستم عامل، پایگاه داده و وب سرور، که همگی روی یکدیگر جمع شده‌اند، نشان داده می‌شود. از این رو، از آن به عنوان پشته یاد می شود. ما در اینجا عمدتاً روی راه حل های منبع باز تمرکز خواهیم کرد زیرا آنها به طور گسترده استفاده می شوند. با این حال، برنامه های تجاری مختلف نیز در صورتی که بیشتر با نیازهای شما مطابقت داشته باشند، می توانند مورد استفاده قرار گیرند. ### کامپوننت های یک پشته یک پشته وب جنگو با استفاده از چندین نوع برنامه (یا لایه ها، بسته به اصطلاح شما) ساخته می شود. هنگام ساخت پشته وب خود، برخی از انتخاب هایی که ممکن است لازم باشد به شرح زیر است: - کدام سیستم عامل و توزیع؟ به عنوان مثال، دبیان، رد هت یا OpenBSD - کدام سرور WSGI؟ به عنوان مثال، Gunicorn یا uWSGI. - کدام وب سرور؟ به عنوان مثال، Apache یا Nginx. - کدام دیتابیس؟ به عنوان مثال، PostgreSQL، MySQL یا Redis - کدام سیستم کش؟ مثلا Memcached یا Redis. - کدام سیستم کنترل فرآیند و مانیتورینگ؟ به عنوان مثال، Upstart، Systemd، یا Supervisord. - چگونه رسانه های استاتیک را ذخیره کنیم؟ به عنوان مثال، Amazon S3 یا CloudFront ممکن است چندین مورد دیگر وجود داشته باشد، و این انتخاب ها نیز متقابلاً منحصر به فرد نیستند. برخی از چندین مورد از این برنامه ها به صورت پشت سر هم استفاده می شوند. به عنوان مثال، در دسترس بودن نام کاربری ممکن است در Redis جستجو شود، در حالی که پایگاه داده اولیه ممکن است PostgreSQL باشد. وقتی نوبت به انتخاب پشته شما می رسد، هیچ پاسخی برای همه وجود ندارد. اجزای مختلف نقاط قوت و ضعف متفاوتی دارند. آنها را تنها پس از بررسی دقیق و آزمایش انتخاب کنید. به عنوان مثال، ممکن است شنیده باشید که Nginx یک انتخاب محبوب برای وب سرور است، اما ممکن است در واقع به اکوسیستم غنی ماژول ها یا گزینه های آپاچی نیاز داشته باشید. گاهی اوقات، انتخاب پشته بر اساس دلایل مختلف غیر فنی است. سازمان شما ممکن است روی یک سیستم عامل خاص مانند دبیان برای همه سرورهایش استاندارد شده باشد، یا ارائه دهنده هاست ابری شما ممکن است تنها مجموعه محدودی از پشته ها را پشتیبانی کند. از این رو، نحوه انتخاب میزبانی برنامه جنگو یکی از عوامل کلیدی در تعیین تنظیمات تولید شما است. ### ماشینهای مجازی یا داکر بسیاری از ما به استفاده از ماشین های مجازی چه در توسعه و چه در تولید آشنا هستیم. این ابزارها برنامه شما (ماشین مهمان) را از زیرساخت اصلی (ماشین میزبان) جدا می کنند. فناوری‌های کانتینری مانند Docker به طور فزاینده‌ای برای استقرار ابری، یا مکمل یا جایگزین ماشین‌های مجازی استفاده می‌شوند. کانتینرها ابزاری برای ایجاد چندین نمونه فضای کاربر بر روی یک هسته هستند. برخلاف ماشین‌های مجازی، کانتینرها از نیاز به راه‌اندازی اجتناب می‌کنند و سیستم‌عامل‌های مهمان مجزا را اجرا می‌کنند. به طور معمول، هر کانتینر یک برنامه کاربردی و وابستگی های آن را در یک نمونه فضای کاربری جدا از سایر کانتینرها بسته بندی می کند. برخلاف ماشین‌های مجازی، داکر نمونه جداگانه‌ای از سیستم عامل ندارد، که باعث می‌شود سبک‌تر و سریع‌تر شروع یا متوقف شود. داکر با اکوسیستم بزرگ و پشتیبانی گسترده در میان فروشندگان ابری، به فناوری انتخابی کانتینری تبدیل شده است. داکر ایمیج ها از یک ایمیج باینری به نام ایمیج پایه ایجاد می شوند یا به طور خودکار از یک اسکریپت به نام Dockerfile ساخته می شوند. این به شما کمک می کند تا همان محیط را در تولید برای اهداف توسعه یا آزمایش دوباره ایجاد کنید، بنابراین بهانه اما در دستگاه من کار کرد را پایان دهید. ### میکروسرویس ها رایج ترین الگوی طراحی با استفاده از Docker، تجزیه برنامه ها و سرویس ها به میکروسرویس ها است. مزیت این است که میکروسرویس‌های فردی می‌توانند به طور مستقل توسعه یافته و به کار گرفته شوند، در حالی که در موقعیت‌های سخت انعطاف‌پذیرتر هستند. از این رو، فن‌آوری‌های کانتینری‌سازی مانند Docker به دلیل حداقل سطح سربار و ایزوله در سطح کاربرد، یک تناسب طبیعی است. مثال زیر یک مثال ساده از یک برنامه وب جنگو است که به عنوان میکروسرویس با استفاده از کانتینرها اجرا شده است: ![Django application flow when deployed as distinct containers](1.jpg) این میکروسرویس واحد از سه کانتینر با اجزای منطقی مجزا تشکیل شده است: ظرف Nginx (وب سرور)، کانتینر Gunicorn/Django (برنامه وب) و کانتینر PostgreSQL (پایگاه داده). هر ظرف از یک داکر ایمیج که ممکن است با استفاده از یک Dockerfile ساخته شود، نمونه سازی شده است. کانتینرهای Docker یک سیستم فایل زودگذر دارند، بنابراین داده‌های پایدار با ایجاد صریح یک حجم مدیریت می‌شوند. از ولوم ها می توان برای اشتراک گذاری داده ها بین کانتینرها استفاده کرد. در این حالت، فایل‌های استاتیک پروژه جنگو را می‌توان در کانتینر Nginx به اشتراک گذاشت تا مستقیماً به آن‌ها سرویس داده شود. همانطور که می توانید تصور کنید، اکثر برنامه های کاربردی دنیای واقعی از چندین Microservice تشکیل شده اند و هر یک از آنها به چندین کانتینر نیاز دارند. اگر آنها را روی چندین سرور اجرا کنید، چگونه این کانتینرها را در آنها مستقر خواهید کرد؟ چگونه می‌توانید میکروسرویس‌های فردی را به سمت بالا یا پایین مقیاس کنید؟ Kubernetes گسترده ترین راه حل توصیه شده برای مدیریت چنین خوشه های کانتینری است. اگرچه ما در این بخش کانتینرها را در سطح بسیار بالایی پوشش داده‌ایم، اما جزئیات پیاده‌سازی زیادی مانند الگوهای استقرار وجود دارد که در اینجا نمی‌توان به آنها پرداخت، زیرا می‌توانند به تنهایی یک کتاب باشند. کانتینرها و ابزارهای ارکستراسیون با ایجاد آسان‌تر مدیریت محیط‌های برنامه به بخش مهمی از توسعه برنامه‌های کاربردی وب مدرن تبدیل شده‌اند. ### میزبانی و هاستینگ وقتی نوبت به هاستینگ می رسد، باید مطمئن شوید که آیا به دنبال بستر(پلتفرم) میزبانی مانند Heroku هستید یا خیر. اگر اطلاعات زیادی در مورد مدیریت سرور ندارید یا کسی با آن دانش در تیم خود ندارید، پلتفرم میزبانی گزینه مناسبی است. ### پلتفرم به عنوان سرویس **پلتفرم به عنوان سرویس** (PaaS) یک سرویس ابری است که در آن راه حل از قبل برای شما ارائه و مدیریت شده است. پلتفرم های محبوب برای میزبانی جنگو عبارتند از Heroku، PythonAnywhere و Google App Engine. در بیشتر موارد، استقرار یک برنامه جنگو باید به سادگی انتخاب سرویس‌ها یا اجزای پشته و خارج کردن کد منبع شما باشد. نیازی نیست خودتان هیچ گونه مدیریت یا راه اندازی سیستمی انجام دهید. پلتفرم به طور کامل مدیریت می شود. مانند بسیاری از سرویس های ابری، زیرساخت ها نیز می توانند بر اساس تقاضا مقیاس شوند. اگر به پایگاه داده یا رم بیشتر روی یک سرور نیاز دارید، می توان آن را به راحتی از یک رابط وب یا خط فرمان تهیه کنید. قیمت گذاری در درجه اول بر اساس استفاده شما است. نکته اصلی در مورد چنین پلتفرم های میزبانی این است که راه اندازی آنها بسیار آسان است و برای پروژه های کوچکتر ایده آل هستند. با افزایش تعداد کاربران، گران تر می شوند. نکته منفی دیگر این است که برنامه شما ممکن است به یک پلتفرم گره بخورد یا پورت کردن آن دشوار شود. به عنوان مثال، Google App Engine فقط برای پشتیبانی از یک پایگاه داده غیررابطه ای استفاده می شود، به این معنی که شما باید از django-nonrel، یک فورکی(کپی) از جنگو، استفاده کنید. این محدودیت اکنون با Google Cloud SQL تا حدودی کاهش یافته است. ### سرورهای مجازی اختصاصی سرور خصوصی مجازی (VPS) یک ماشین مجازی است که در یک محیط مشترک میزبانی می شود. از دیدگاه توسعه‌دهنده، به نظر می‌رسد یک ماشین اختصاصی (از این رو، کلمه خصوصی) با یک سیستم عامل از قبل بارگذاری شده است. شما باید خودتان کل پشته را نصب و راه اندازی کنید، اگرچه بسیاری از ارائه دهندگان VPS مانند WebFaction و DigitalOcean تنظیمات جنگو را آسان تر ارائه می دهند. اگر مبتدی هستید و می توانید کمی وقت بگذارید، این روش را به شدت توصیه می کنم. به شما دسترسی ریشه (روت) داده می شود و می توانید کل پشته را خودتان بسازید. شما نه تنها متوجه خواهید شد که چگونه قطعات مختلف پشته به هم می رسند، بلکه کنترل کاملی در تنظیم دقیق هر جزء خواهید داشت. در مقایسه با PaaS، VPS ممکن است ارزش بیشتری دربرابر پول پرداختی داشته باشد، به خصوص برای سایت های پربازدید. ممکن است بتوانید چندین سایت از یک سرور را نیز اجرا کنید. ### سرورلس تصور کنید که نیاز به میزبانی سرویسی دارید که به ندرت استفاده می شود، اما پرداخت هزینه برای یک سرور اختصاصی که همیشه در حال اجرا است، هزینه بر یا ناکارآمد از نظر نگهداری است. به نظر میرسد در این حالت معماری های بدون سرور همان چیزی باشد که شما به دنبال آن هستید. نام بدون سرور یک نام اشتباه است زیرا تمام درخواست‌های مشتری در واقع توسط سرورهایی انجام می‌شوند که به صورت پویا برای طول عمر درخواست ارائه می‌شوند. اصطلاح مناسب‌تر Function as a Service (FaaS) است، زیرا این پلتفرم‌ها از اجرای یک منطق برنامه مانند یک تابع کوچک پایتون پشتیبانی می‌کنند اما هیچ حالتی را ذخیره نمی‌کنند. ساخت یک برنامه کاربردی متشکل از چنین توابعی کاملاً شبیه به معماری میکروسرویس است که قبلاً مورد بحث قرار گرفت. به طور معمول، شما فقط برای میلی ثانیه زمان سروری که یک برنامه بدون سرور استفاده می کند، پرداخت می کنید، که آن را بسیار ارزان تر از سرورهای اختصاصی می کند. مقیاس‌بندی به‌طور خودکار انجام می‌شود، بنابراین هیچ تلاش اضافی برای رسیدگی به جهش‌های عظیم در ترافیک لازم نیست. نکته مهم دیگر اینکه هیچ دردسر راه اندازی و نگهداری زیرساخت سرور هم وجود ندارد. جنگو ممکن است به نظر در چنین محیطی کار نکند، اما Zappa استقرار برنامه های جنگو (در واقع، هر برنامه سازگار با WSGI) را بر روی یک پلتفرم بدون سرور مانند AWS Lambda با حداقل تغییرات آسان می کند. این امکان لذت بردن از تمام مزایای بدون سرور را در هنگام استفاده از جنگو باز می کند. ### رویکردهای دیگر برای هاستینگ اگرچه میزبانی بر روی یک پلتفرم یا VPS دو گزینه محبوب میزبانی هستند، گزینه های زیاد دیگری نیز وجود دارد. اگر به افزایش کارایی علاقه دارید، می توانید یک سرور فیزیکی (bare metal) با هماهنگی از ارائه دهندگان، مانند Rackspace انتخاب کنید. در انتهای سبک‌تر طیف میزبانی، می‌توانید با میزبانی چندین برنامه در کانتینرهای Docker در هزینه صرفه‌جویی کنید. داکر ابزاری برای بسته بندی برنامه ها و وابستگی های شما در یک کانتینر مجازی است. در مقایسه با ماشین‌های مجازی سنتی، کانتینر Docker سریع‌تر راه‌اندازی می‌شود و دارای کمترین میزان هزینه‌های سربار است (زیرا سیستم عامل یا هایپروایزر همراهی وجود ندارد). داکر برای میزبانی برنامه های برپایه میکروسرویس ایده آل است. تقریباً هر ارائه دهنده PaaS و VPS از آنها پشتیبانی می کند و در همه جا در حال فراگیر شدن است. همچنین یک پلتفرم توسعه عالی است زیرا کانتینرهای داکر کل حالت برنامه را در خود محصور می کنند و می توانند مستقیماً در تولید دیپلوی و مستقر شوند. ### ابزارهای استقرار سازی و دیپلوی هنگامی که راه حل میزبانی خود را به صفر رساندید، ممکن است چندین مرحله در فرآیند استقرار شما وجود داشته باشد، از اجرای تست های رگرسیون گرفته تا ایجاد خدمات پس زمینه. کلید یک فرآیند استقرار موفق، اتوماسیون است. از آنجایی که استقرار برنامه ها شامل یک سری مراحل کاملاً تعریف شده است، می توان به درستی به عنوان یک مشکل برنامه نویسی به آن نگاه کرد. هنگامی که یک استقرار اتوماسیون شده دارید، از ترس از دست دادن یک مرحله، لازم نیست نگران استقرار باشید. در واقع، استقرار باید بدون درد و به همان اندازه که لازم است انجام شود. به عنوان مثال، تیم فیس بوک می تواند چندین بار در روز کد را برای تولید منتشر کند. با در نظر گرفتن پایگاه عظیم کاربر و پایگاه کد فیس بوک، این یک شاهکار چشمگیر است، با این حال، ضروری است زیرا رفع اشکالات و وصله های اضطراری باید در اسرع وقت اجرا شوند. یک فرآیند استقرار خوب نیز ناتوان است. به عبارت دیگر، حتی اگر به طور تصادفی ابزار استقرار را دو بار اجرا کنید، اقدامات نباید دو بار اجرا شوند (یا بهتر است بگوییم باید آن را در همان حالت رها کنید). بیایید نگاهی به برخی از ابزارهای محبوب برای استقرار برنامه های جنگو بیندازیم. ### Fabric این مورد به دلیل سادگی و سهولت استفاده در بین توسعه دهندگان وب پایتون مورد علاقه است. فایلی به نام fabfile.py را در نظر دارد که تمام اقدامات (برای استقرار یا غیره) در پروژه شما را تعریف میکند. هر یک از این اقدامات می تواند یک shell command محلی یا راه دور باشد. میزبان راه دور از طریق SSH متصل می شود. نقطه قوت اصلی Fabric توانایی آن در اجرای دستورات روی مجموعه ای از میزبان های راه دور است. به عنوان مثال، می توانید یک گروه **وب**را تعریف کنید که شامل نام میزبان همه وب سرورهای در حال تولید است. **نکته:** با تعیین نام گروه وب در خط فرمان، می‌توانید یک اکشن Fabric را فقط در برابر این سرورهای وب اجرا کنید. برای نشان دادن وظایف مربوط به استقرار یک سایت با استفاده از Fabric، اجازه دهید نگاهی به یک سناریوی استقرار معمولی بیندازیم. ### گام های ساده برای استقرار تصور کنید که یک برنامه وب با اندازه متوسط دارید که بر روی یک وب سرور واحد مستقر شده است. Git به عنوان ابزار کنترل نسخه و همکاری انتخاب شده است. یک مخزن مرکزی که با همه کاربران به اشتراک گذاشته شده است به شکل درخت Git برهنه ایجاد شده است. فرض کنید سرور تولید شما به طور کامل راه اندازی شده است. هنگامی که فرمان استقرار Fabric خود را اجرا می کنید، مثلاً fab deploy، دنباله اسکریپت زیر از اقدامات انجام می شود: 1. تمام تست ها را به صورت محلی اجرا می کند 2. تمام تغییرات محلی Git را انجام می دهد 3. به یک مخزن مرکزی راه دور Git فشار می آورد 4. در صورت وجود، تضادهای ادغام را حل می کند 5. فایل های ثابت (CSS، تصاویر) را جمع آوری می کند. 6. فایل های استاتیک را در سرور فایل استاتیک کپی می کند 7. در میزبان راه دور، تغییرات را از یک مخزن مرکزی Git می کشد 8. در میزبان راه دور، مهاجرت (پایگاه داده) را اجرا می کند 9. در میزبان راه دور، app.wsgi را برای راه اندازی مجدد سرور WSGI لمس کنید کل فرآیند به صورت خودکار است و باید در چند ثانیه تکمیل شود. به طور پیش فرض، اگر هر مرحله ای با شکست مواجه شود، استقرار متوقف می شود. اگرچه به صراحت ذکر نشده است، اما بررسی هایی برای اطمینان از عدم توانمندی فرآیند وجود دارد. Fabric هنوز با پایتون 3 سازگار نیست، اگرچه توسعه دهندگان در حال انتقال آن هستند. در عین حال، می‌توانید Fabric را در محیط مجازی Python 2.x اجرا کنید یا ابزارهای مشابهی مانند PyInvoke را بررسی کنید. ### مدیریت پیکره بندی ها مدیریت چندین سرور در حالت های مختلف می تواند با Fabric سخت باشد. ابزارهای مدیریت پیکربندی مانند Chef، Puppet یا Ansible سعی می کنند یک سرور را به وضعیت مطلوبی برسانند. بر خلاف Fabric، که نیاز به تعیین فرآیند استقرار به شیوه ای ضروری دارد، این ابزارهای مدیریت پیکربندی بیانی هستند. شما فقط باید وضعیت نهایی را که می خواهید سرور در آن قرار داشته باشد، مشخص کنید و نحوه رسیدن به آنجا را مشخص می کند. برای مثال، اگر می‌خواهید مطمئن شوید که سرویس Nginx در هنگام راه‌اندازی در تمام سرورهای وب شما اجرا می‌شود، باید وضعیت سروری را تعریف کنید که سرویس Nginx هم در حال اجرا و هم در هنگام راه‌اندازی است. از طرف دیگر، با Fabric، باید مراحل دقیق نصب و پیکربندی Nginx را برای رسیدن به چنین حالتی مشخص کنید. یکی از مهمترین مزایای ابزارهای مدیریت پیکربندی این است که به طور پیش فرض فاقد قدرت هستند. سرورهای شما می توانند از یک حالت ناشناخته به یک وضعیت شناخته شده بروند و در نتیجه مدیریت پیکربندی سرور آسان تر و استقرار قابل اعتماد را به همراه داشته باشند. در میان ابزارهای مدیریت پیکربندی، Chef و Puppet از محبوبیت زیادی برخوردار هستند زیرا یکی از اولین ابزارها در این دسته بودند. با این حال، ریشه آنها در Ruby می تواند آنها را برای برنامه نویس پایتون کمی ناآشنا نشان دهد. برای چنین افرادی، ما Salt و Ansible را به عنوان جایگزین های عالی داریم. ابزارهای مدیریت پیکربندی در مقایسه با ابزارهای ساده تر، مانند Fabric، منحنی یادگیری قابل توجهی دارند. با این حال، آنها ابزار ضروری برای ایجاد محیط های تولید قابل اعتماد هستند و مطمئنا ارزش یادگیری را دارند. ### مانیتورینگ حتی یک وب سایت با اندازه متوسط می تواند بسیار پیچیده باشد. جنگو ممکن است یکی از صدها برنامه و سرویسی باشد که در حال اجرا و تعامل با یکدیگر هستند. همانطور که ضربان قلب و سایر علائم حیاتی را می توان به طور مداوم برای ارزیابی سلامت بدن انسان کنترل کرد، معیارهای مختلفی نیز در اکثر سیستم های تولیدی جمع آوری، تجزیه و تحلیل و ارائه می شوند. در حالی که ثبت رویدادهای مختلف مانند ورود یک درخواست وب یا یک استثنا را پیگیری می کند، نظارت معمولاً به جمع آوری اطلاعات کلیدی به صورت دوره ای مانند استفاده از حافظه یا تأخیر شبکه اشاره دارد. با این حال، تفاوت ها در سطح برنامه محو می شوند، به عنوان مثال، هنگام نظارت بر عملکرد پرس و جو پایگاه داده، که ممکن است به خوبی از گزارش ها جمع آوری شود. نظارت همچنین به تشخیص زودهنگام مشکلات کمک می کند. الگوهای غیرمعمول، مانند سنبله ها یا افزایش تدریجی بار، می تواند نشانه ای از مشکلات اساسی بزرگتر مانند نشت حافظه باشد. یک سیستم مانیتورینگ خوب می تواند صاحبان سایت را از مشکلات قبل از وقوع آنها آگاه کند. ابزارهای مانیتورینگ معمولاً به یک سرویس پشتیبان (گاهی اوقات به نام عوامل) برای جمع آوری آمار و سرویس ظاهری برای نمایش داشبوردها یا تولید گزارش نیاز دارند. پشتیبان های محبوب جمع آوری داده ها عبارتند از StatsD و Monit. این داده ها را می توان به ابزارهای frontend مانند Graphite منتقل کرد. چندین ابزار مانیتورینگ میزبان مانند New Relic و Status.io وجود دارد که راه‌اندازی و استفاده از آنها آسان‌تر است. اندازه گیری عملکرد یکی دیگر از نقش های مهم نظارت است. همانطور که به زودی در بخش بعدی خواهیم دید، هر بهینه سازی پیشنهادی باید قبل از اجرا به دقت اندازه گیری و نظارت شود. ### بهبود کارایی عملکرد یک ویژگی است. مطالعات نشان می‌دهد که سایت‌های کند چگونه تأثیر نامطلوبی بر کاربران و در نتیجه درآمد دارند. به عنوان مثال آزمایش هایی در آمازون در سال 2007 نشان داد که به ازای هر 100 میلی ثانیه افزایش زمان بارگذاری [amazon.com](https://www.amazon.com/) فروش 1 درصد کاهش می یابد. با اطمینان، چندین برنامه وب با کارایی بالا مانند Disqus و Instagram بر روی جنگو ساخته شده اند. در Disqus، در سال 2013، آنها می‌توانستند 1.5 میلیون کاربر متصل همزمان، 45000 اتصال جدید در ثانیه، 165000 پیام در ثانیه را با تأخیر سرتاسر کمتر از 0.2 ثانیه مدیریت کنند. کلید بهبود عملکرد، یافتن تنگناهاست. به جای تکیه بر حدس و گمان، همیشه توصیه می شود که برنامه خود را اندازه گیری و نمایه کنید تا این گلوگاه های عملکرد را شناسایی کنید. همانطور که لرد کلوین می گوید: - "اگر نتوانید آن را اندازه گیری کنید، نمی توانید آن را بهبود ببخشید" در بیشتر برنامه‌های کاربردی وب، گلوگاه‌ها احتمالاً در انتهای مرورگر یا پایگاه داده به جای جنگو هستند. با این حال، برای کاربر، کل برنامه باید پاسخگو باشد. بیایید به برخی از راه های بهبود عملکرد یک برنامه جنگو نگاهی بیندازیم. به دلیل تکنیک‌های بسیار متفاوت، نکات به دو بخش تقسیم می‌شوند: قسمت جلویی و پشتیبان. ### کارایی سمت فرانت برنامه نویسان جنگو ممکن است عملکرد frontend را نادیده بگیرند زیرا با درک نحوه عملکرد سمت کلاینت، معمولاً یک مرورگر، سروکار دارد. با این حال، بیایید از مطالعه استیو سادرز در مورد 10 وب سایت برتر رتبه بندی الکسا نقل قول کنیم: - "80 تا 90 درصد از زمان پاسخ‌دهی کاربر نهایی در قسمت فرانت صرف می‌شود. از آنجا شروع کنید." یک نقطه شروع خوب برای بهینه سازی frontend این است که سایت خود را با سرعت صفحه گوگل یا یاهو بررسی کنید! YSlow (معمولاً به عنوان پلاگین مرورگر استفاده می شود). این ابزارها سایت شما را رتبه بندی می کنند و بهترین روش ها را توصیه می کنند، مانند به حداقل رساندن تعداد درخواست های HTTP یا gzip نمودن محتوا. به عنوان بهترین روش، دارایی های استاتیک شما، مانند تصاویر، شیوه نامه ها و فایل های جاوا اسکریپت، نباید از طریق جنگو ارائه شوند. به جای یک فایل سرور استاتیک و ثابت، فضای ذخیره سازی ابری مانند آمازون S3 یا یک شبکه **تحویل محتوا** (CDN) باید برای عملکرد بهتر مورد استفاده قرار گیرد. حتی در این صورت، جنگو می‌تواند به چندین روش به شما در بهبود عملکرد frontend کمک کند: - کش بی نهایت با CachedStaticFilesStorage: سریع ترین راه برای بارگیری assets ایستا، استفاده از حافظه پنهان مرورگر است. با تنظیم زمان کش طولانی، می توانید از بارگیری مجدد همان دارایی دوباره و دوباره خودداری کنید. با این حال، چالش این است که بدانیم چه زمانی از حافظه پنهان هنگام تغییر محتوا استفاده نکنیم. - کلاس CachedStaticFilesStorage این مشکل را با افزودن هش MD5 asset به نام فایل حل می کند. به این ترتیب می توانید TTL کش را برای این فایل ها بی نهایت افزایش دهید. - برای استفاده از این، تنظیم CACHES با نام staticfiles را روی CachedStaticFilesStorage تنظیم کنید یا اگر فضای ذخیره سازی سفارشی دارید، از CachedFilesMixin ارث بری نمایید. همچنین، بهتر است کش های خود را به گونه ای پیکربندی کنید که از پشتیبان کش حافظه محلی برای انجام نام فایل استاتیک در جستجوی نام هش شده آن استفاده کند. - از یک asset manager ایستا استفاده کنید: یک asset manager می تواند دارایی های استاتیک شما را از قبل پردازش کند تا آنها را کوچک کند، فشرده یا به هم متصل کند، در نتیجه اندازه آنها را کاهش داده و درخواست ها را به حداقل برساند. همچنین می‌تواند آنها را از قبل پردازش کند و به شما امکان می‌دهد آن‌ها را به زبان‌های دیگر بنویسید، مانند CoffeeScript و stylesheets از لحاظ نحوی عالی (Sass). چندین بسته جنگو وجود دارد که asset manager ثابت مانند django-pipeline یا websets را ارائه می دهد. ### کارایی سمت بک اند دامنه کارایی سمت Backend تمامی وب استک سمت سرور شما را شامل می شود، از جمله پرس و جوهای پایگاه داده، رندر قالب، ذخیره سازی و کارهایی که در پس زمینه انجام می شوند. ممکن است بالاترین عملکرد را از آنها استخراج کنید زیرا کاملاً در کنترل شما است. برای نیازهای پروفایل سریع و آسان، django-debug-toolbar بسیار مفید است. همچنین می‌توانیم از ابزارهای پروفایل پایتون مانند ماژول hotshot برای تجزیه و تحلیل دقیق استفاده کنیم. در جنگو، می توانید از یکی از چندین قطعه میان افزار پروفایل برای نمایش خروجی هات شات در مرورگر استفاده کنید. راه‌حلی که اخیراً برای ایجاد نمایه زنده وجود دارد، django-silk است. تمام درخواست‌ها و پاسخ‌ها را در پایگاه داده پیکربندی شده ذخیره می‌کند و اجازه تجزیه و تحلیل انبوه در یک سشن کاربر را میدهد، که بدترین عملکردها را پیدا کند. همچنین می‌تواند با افزودن یک دکوراتور، هر قطعه کد پایتون را نمایه کند. مانند قبل، نگاهی به برخی از راه‌های بهبود عملکرد Backend خواهیم انداخت. با این حال، با توجه به اینکه آنها به خودی خود موضوعات گسترده ای هستند، در بخش هایی دسته بندی شده اند. بسیاری از این موارد قبلاً در فصل‌های قبلی پوشش داده شده‌اند، اما برای ارجاع آسان در اینجا خلاصه شده‌اند. ### تمپلیت ها همانطور که مستندات نشان می دهد، شما باید تمپلیت لودر ذخیره شده را در مرحله تولید فعال کنید. این کار باعث می‌شود که هر بار که قالب‌ها نیاز به رندر شدن داشته باشند، از کامپایل مجدد آن جلوگیری می‌شود. قالب ذخیره شده در حافظه پنهان اولین باری که نیاز است کامپایل می شود و سپس در حافظه ذخیره می شود. درخواست های بعدی برای همان الگو از حافظه ارائه می شود. اگر متوجه شدید که زبان قالب دیگری مانند Jinja2 صفحه شما را به طور قابل توجهی سریعتر ارائه می کند، جایگزین کردن زبان قالب داخلی جنگو بسیار آسان است. ### دیتابیس گاهی اوقات، Django ORM می تواند کد SQL ناکارآمد تولید کند. چندین الگوی بهینه سازی برای بهبود این امر وجود دارد که به شرح زیر است: - کاهش بازدیدهای پایگاه داده با select_related: اگر از یک رابطه یک به یک یا یک کلید خارجی، در جهت فوروارد، برای تعداد زیادی از اشیاء استفاده می کنید، select_related() می تواند یک جوین را انجام دهد و تعداد بازدیدهای پایگاه داده را کاهش دهد. - کاهش بازدید پایگاه داده با prefetch_related: برای دسترسی به متد ManyToManyField یا یک رابطه کلید خارجی در جهت معکوس، یا یک رابطه کلید خارجی در تعداد زیادی از اشیاء، استفاده از prefetch_related را برای کاهش تعداد بازدیدهای پایگاه داده در نظر بگیرید. - واکشی فیلدهای مورد نیاز با ولیوها یا values_list: می‌توانید با محدود کردن کوئری‌ها برای بازگشت فیلدهای مورد نیاز و چشم پوشی کردن از نمونه‌سازی مدل با استفاده از values() یا values_list در زمان و استفاده از حافظه صرفه‌جویی کنید. - مدل‌های دینرمال شده: denormalization انتخابی، کارایی را از طریق کاهش جوین ها برای افزایش سازگاری داده‌ها بهبود می‌بخشد. همچنین می توان از آن برای پیش محاسبه مقادیر، مانند مجموع فیلدها یا گزارش وضعیت فعال در یک ستون اضافی استفاده کرد. در مقایسه با استفاده از مقادیر مشروح در پرس و جوها، فیلدهای غیرعادی شده اغلب ساده تر و سریعتر هستند. - یک شاخص اضافه کنید: اگر یک کلید غیر اصلی در جستارهای شما زیاد جستجو می شود، db_index آن فیلد را در تعریف مدل خود روی True قرار دهید. - ایجاد، به روز رسانی و حذف چندین ردیف به طور همزمان: چندین شی را می توان در یک کوئری پایگاه داده واحد با متدهای bulk_create()، update() و delete() اجرا کرد. با این حال، آنها با چندین اخطار مهم مانند چشم پوشی از متد save() در آن مدل همراه هستند. بنابراین، قبل از استفاده از آنها، اسناد را به دقت بخوانید. به‌عنوان آخرین راه‌حل، همیشه می‌توانید عبارات SQL خام را با استفاده از تخصص کارایی پایگاه داده تنظیم کنید. با این حال، حفظ کد SQL می تواند در طول زمان دردناک باشد. ### ذخیره سازی هر محاسباتی که زمان می برد می تواند از مزیت ذخیره سازی استفاده کند و نتایج از پیش محاسبه شده را سریعتر بازگرداند. با این حال، مشکل داده‌های قدیمی است یا اغلب به عنوان یکی از سخت‌ترین چیزها در علوم کامپیوتر، عدم اعتبار کش ذکر می‌شود. این معمولاً زمانی دیده می‌شود که، با وجود به‌روزرسانی صفحه، تعداد بازدیدهای ویدیوی YouTube تغییر نمی‌کند. جنگو دارای یک سیستم کش انعطاف پذیر است که به شما امکان می دهد هر چیزی از یک قطعه قالب گرفته تا کل سایت را در حافظه پنهان ذخیره کنید. این انواع پشتیبان های قابل اتصال مانند ذخیره سازی مبتنی بر فایل یا مبتنی بر داده را شامل میشود. اکثر سیستم های تولیدی از یک سیستم کش مبتنی بر حافظه مانند Redis یا Memcached استفاده می کنند. این صرفاً به این دلیل است که حافظه فرار بسیار سریعتر از ذخیره سازی مبتنی بر دیسک است. چنین حافظه های کش برای ذخیره سازی داده های پرکاربرد اما زودگذر، مانند جلسات کاربر، ایده آل هستند.. ### بکند سشن ذخیره شده به طور پیش فرض، جنگو یوزر سشن خود را در پایگاه داده ذخیره می کند. این معمولاً برای هر درخواست بازیابی می شود. برای بهبود عملکرد، داده های سشن را می توان با تغییر تنظیمات SESSION_ENGINE در حافظه ذخیره کرد. به عنوان مثال، موارد زیر را در settings.py اضافه کنید تا داده‌های جلسه را در حافظه پنهان خود ذخیره کنید: - SESSION_ENGINE = "django.contrib.sessions.backends.cache" از آنجایی که برخی از حافظه پنهان می‌توانند داده‌های قدیمی را که منجر به از دست رفتن داده‌های جلسه می‌شود، از بین ببرد، ترجیح داده می‌شود از Redis یا Memcached به عنوان ذخیره‌گاه سشن با محدودیت‌های حافظه کافی برای پشتیبانی از حداکثر تعداد یوزر سشن های فعال استفاده شود. ### فریم ورک های ذخیره سازی برای استراتژی های ذخیره سازی اولیه، ممکن است استفاده از فریم ورک ذخیره سازی آسان تر باشد. از جمله محبوب ترین ها می توان به django-cache-machine و django-cachalot اشاره کرد. آنها می توانند سناریوهای رایج را مدیریت کنند، مانند ذخیره خودکار نتایج جستجوها برای جلوگیری از بازدید از پایگاه داده هر بار که خواندن انجام می دهید. ساده ترین آنها جنگو-کاچالوت، جانشین Johnny Cache است. به پیکربندی بسیار کمی نیاز دارد. این برای سایت‌هایی که دارای چندین خواندن و نوشتن نادر هستند (یعنی اکثریت قریب به اتفاق برنامه‌ها) ایده‌آل است، تمام پرس‌و‌جوهای خوانده‌شده جنگو ORM را به شیوه‌ای ثابت ذخیره می‌کند. ### الگوهای ذخیره سازی هنگامی که سایت شما شروع به دریافت ترافیک سنگین نمود، باید شروع به کاوش چندین استراتژی ذخیره سازی در سراسر پشته خود کنید. با استفاده از Varnish، یک سرور کش که بین کاربران شما و جنگو قرار می گیرد، بسیاری از درخواست های شما ممکن است حتی به سرور جنگو هم نرسند. وارنیش می تواند باعث شود صفحات بسیار سریع بارگذاری شوند (گاهی اوقات صدها برابر سریعتر از حالت عادی). با این حال، در صورت استفاده نادرست، ممکن است صفحات ایستا را به کاربران شما ارائه دهد. Varnish را می توان به راحتی برای تشخیص صفحات پویا یا بخش های پویا از یک صفحه مانند سبد خرید پیکربندی کرد. ذخیره سازی عروسک روسی (Russian doll caching) که در جامعه ریل محبوب است، یک الگوی جالب برای تمپلیت الگوی caching validation است. صفحه تایم لاین یک کاربر را با مجموعه‌ای از پست‌ها تصور کنید که هر کدام شامل فهرست تودرتویی از نظرات است. در واقع، کل صفحه را می توان به عنوان چندین لیست تو در تو از محتوا در نظر گرفت. در هر سطح، قطعه قالب رندر شده کش می شود. بنابراین، اگر یک نظر جدید به یک پست اضافه شود، تنها پست مرتبط و کش های جدول زمانی باطل می شوند. ابتدا محتوای کش را مستقیماً خارج از محتوای تغییر یافته باطل می کنیم و به تدریج حرکت می کنیم تا زمانی که به بیرونی ترین محتوا برسیم. وابستگی های بین مدل ها باید دنبال شود تا این الگو کار کند. یکی دیگر از الگوهای رایج حافظه پنهان، کش کردن برای همیشه است. حتی پس از تغییر محتوا، کاربر ممکن است داده های قدیمی را از حافظه پنهان دریافت کند. با این حال، یک کار ناهمزمان، مانند کار Celery، نیز برای به‌روزرسانی حافظه پنهان فعال می‌شود. همچنین می توانید به طور دوره ای کش را در یک بازه زمانی مشخص گرم کنید تا محتوا را تازه کنید. اساساً، یک استراتژی ذخیره سازی موفق، بخش های استاتیک و پویا یک سایت را شناسایی می کند. برای بسیاری از سایت‌ها، بخش‌های پویا، داده‌های خاص کاربر هستند زمانی که وارد سیستم میشوید. ذخیره سازی نهان را به عنوان بخش جدایی ناپذیر عملکرد سایت خود تلقی نکنید. حتی اگر سیستم کش خراب شود، سایت باید به حالت کندتر اما کارآمد برگردد. **Cranos**:
    It was six in the morning and the SHIM building was surrounded by a
    grey fog. Somewhere inside, a small conference room had been designated
    the war room. For the last three hours, the SuperBook team had been
    holed up here diligently executing their pre-go-live plan.

    More than 30 users had logged on the IRC chatroom #superbookgolive
    from various parts of the world. The chat log was projected on a giant
    whiteboard. When the last item was struck off, Evan glanced at Steve.
    Then, he pressed a key triggering the deployment process.
    
    The room fell silent as the script output kept scrolling off the wall. One
    error, Steve thought, just one error can potentially set them back by hours.
    Several seconds later, the command prompt reappeared. It was live! The
    team erupted in joy. Leaping from their chairs they gave high-fives to
    each other. Some were crying tears of happiness. After weeks of
    uncertainty and hard work, it all seemed surreal.

    However, the celebrations were short-lived. A loud explosion from above
    shook the entire building. Steve knew the second breach had begun. He
    shouted to Evan, "don't turn on the beacon until you get my message",
    and sprinted out of the room.
    
    As Steve hurried up the stairway to the rooftop, he heard the sound of
    footsteps above him. It was Madam O. She opened the door and flung
    herself in. He could hear her screaming "no!" and a deafening blast shortly
    after that.
    
    By the time he reached the rooftop, he saw Madam O sitting with her back
    against the wall. She was clutching her left arm and wincing in pain. Steve
    slowly peered around the wall. At a distance, a tall bald man seemed to be
    working on something with the help of two robots.
    
    "He looks like...." Steve broke off, unsure of himself.
    
    "Yes, it is Hart. Rather I should say he is Cranos now."
    
    "What?"
    
    "Yes, a split personality. A monster that laid hidden in Hart's mind for
    years. I tried to help him control it. Many years back, I thought I had
    stopped it from ever coming back. However, all this stress took a toll on
    him. Poor thing, if only I could get near him."
    
    Poor thing indeed, he nearly tried to kill her. Steve took out his mobile
    and sent out a message to turn on the beacon. He had to improvise.
    
    With his hands high in the air and fingers crossed, he stepped out. The
    two robots immediately aimed directly at him. Cranos motioned them to
    stop.

    "Well, who do we have here? Mr. SuperBook himself. Did I crash into
    your launch party, Steve?"
    
    "It was our launch, Hart."
    
    "Don't call me that", growled Cranos. "That guy was a fool. He wrote the
    Sentinel code but he never understood its potential. I mean, just look at
    what Sentinels can do, unravel every cryptographic algorithm known to
    man. What happens when it enters an intergalactic network?"
    
    The hint was not lost on Steve. "SuperBook?" he asked slowly.
    
    Cranos let out a malicious grin. Behind him, the robots were busy wiring
    into SHIM's core network. "While your SuperBook users will be busy
    playing SuperVille, the tentacles of Sentinel will spread into new
    unsuspecting worlds. Critical systems of every intelligent species will be
    sabotaged. The Supers will have to bow to a new intergalactic supervillain
    Cranos."
    
    As Cranos was delivering this extended monologue, Steve noticed a
    movement of the corner of his eye. It was Acorn, the super-intelligent
    squirrel, scurrying along the right edge of the rooftop. He also spotted
    Hexa hovering strategically on the other side. He nodded at them.
    
    Hexa levitated a garbage bin and flung it towards the robots. Acorn
    distracted them with high-pitched whistles. "Kill them all!" Cranos said
    irritably. As he turned to watch his intruders, Steve fished out his phone,
    dialed into FaceTime and held it towards Cranos.
    
    "Say hello to your old friend, Cranos," said Steve.
    
    Cranos turned to face the phone and the screen revealed Madam O's face.
    With a smile, she muttered under her breath, "Taradiddle Bumfuzzle!"
    
    The expression on Cranos's face changed instantly. The seething anger
    disappeared. He now looked like a man they had once known.
    
    "What happened?" asked Hart confused.
    
    "We thought we had lost you," said Madam O over the phone. "I had to
    use hypnotic trigger words to bring you back."
    
    Hart took a moment to survey the scene around him. Then, he slowly
    smiled and nodded at her.
    
    ----------------------------------------------------
    
    One Year Later
    
    Who would have guessed Acorn would turn into an intergalactic singing
    sensation in less than a year? His latest album Acorn Unplugged debuted
    at the top of Billboard's Top 20 chart. He threw a grand party in his new
    white mansion overlooking a lake.
    
    The guest list included superheroes, pop stars, actors, and celebrities of all
    sorts.
    
    "So, there was a singer in you after all," said Captain Obvious holding a
    martini.
    
    "I guess there was," replied Acorn. He looked dazzling in a golden tuxedo
    with all sorts of bling-bling.
    
    Steve appeared with Hexa in tow, who looked ravishing in a flowing
    silver gown.
    
    "Hey Steve, Hexa. It has been a while. Is SuperBook still keeping you late
    at work, Steve?"
    
    "Not so much these days. Knock on wood," replied Hexa with a smile.
    
    "Ah, you guys did a fantastic job. I owe a lot to SuperBook. My first single,
    'Warning: Contains Nuts', was a huge hit in the Tucana galaxy. They
    watched the video on SuperBook more than a billion times!"
    
    "I am sure every other superhero has a good thing to say about SuperBook
    too. Take Blitz. His AskMeAnything interview won back the hearts of his
    fans. They were thinking that he was on experimental drugs all this time.
    It was only when he revealed that his father was Hurricane that his
    powers made sense."
    
    "By the way, how is Hart doing these days?"
    
    "Much better," said Steve. "He got professional help. The sentinels were
    handed back to S.H.I.M. They are developing a new quantum
    cryptographic algorithm that will be much more secure."
    
    "So, I guess we are safe until the next supervillain shows up," said Captain
    Obvious hesitantly.
    
    "Hey, at least the beacon works," said Steve, and the crowd burst into
    laughter.
### خلاصه در این فصل آخر، ما به رویکردهای مختلف برای پایدار، قابل اعتماد و سریع کردن برنامه جنگو شما نگاه کردیم. به عبارت دیگر، آن را آماده تولید کنیم. اگرچه مدیریت سیستم ممکن است به خودی خود یک رشته کامل باشد، دانش منصفانه از وب استک ضروری است. چندین گزینه میزبانی از جمله PaaS، VPS و Serverless را بررسی کردیم. همچنین چندین ابزار استقرار خودکار و یک سناریوی استقرار معمولی را بررسی کردیم. در نهایت، چندین تکنیک را برای بهبود عملکرد frontend و backend پوشش دادیم. مهمترین نقطه عطف یک وب سایت تکمیل و رساندن آن به مرحله تولید است. با این حال، این به هیچ وجه پایان سفر توسعه شما نیست. ویژگی‌ها، تغییرات و بازنویسی‌های جدیدی وجود خواهد داشت. هر بار که کد را دوباره مرور می‌کنید، از این فرصت استفاده کنید تا یک قدم به عقب برگردید و طرحی تمیزتر پیدا کنید، یک الگوی پنهان را شناسایی کنید یا به پیاده‌سازی بهتری فکر کنید. توسعه دهندگان دیگر، و شاید خود آینده شما، از شما برای آن تشکر خواهند کرد. ================================================ FILE: Appendix-A-Python2-Versus-Python3/README.md ================================================ # پایتون 2 در مقابل پایتون 3 تمام نمونه کدهای این کتاب برای پایتون 3.6 نوشته شده است. به جز تغییرات بسیار جزیی، آنها باید در پایتون 2.7 نیز کار کنند. نویسنده بر این باور است که پایتون 3 از نقطه ی اوج برای انتخاب ترجیحی برای پروژه های جدید جنگو عبور کرده است. قرار بود توسعه ی پایتون 2.7 در سال 2015 به پایان برسد اما برای 5 سال دیگر تا سال 2020 تمدید شد. پایتون 2.8 وجود نخواهد داشت. همان طور که در **فصل 2**، طراحی برنامه ذکر شد، بیشتر توزیع های اصلی لینوکس و فروشندگان ابری به طور کامل به استفاده از Python 3 به عنوان پیش فرض یا پشتیبانی از آن روی آورده اند. این پیوست برای توسعه دهندگانی نوشته شده که با پایتون 3 آشنایی ندارند. پیشینه ی تاریخی مختصر و تغییرات ترکیبی در پایتون 3 مورد بحث قرار گرفته است. به جای ارائه ی پوشش جامع از ویژگی های Python 3، تنها موارد مربوط به توسعه دهندگان جنگو پوشش داده شده است. ### پایتون 3 پایتون 3 از سر ناچاری متولد شد. یکی از مزاحمت های اصلی پایتون 2، مدیریت ناسازگار آن با کاراکترهای غیرانگلیسی بود (که به طور معمول به عنوان خطای بدنام UnicodeDecode نشان داده می شود). Guido پروژه Python 3 را برای پاکسازی تعدادی از چنین مشکلات زبانی و در عین حال شکستن سازگاری با نسخه های پیشین، آغاز کرد. اولین نسخه ی آلفا پایتون 3.0 در آگوست 2007 ساخته شد. از آن زمان، پایتون 2 و پایتون 3 چندین سال به طور موازی توسط تیم توسعه ی اصلی توسعه داده شده اند. در نهایت، انتظار می رود پایتون 3 آینده ی این زبان باشد. ### پایتون 3 برای جنگویی ها این بخش مهم ترین تغییرات پایتون 3 را از دیدگاه توسعه دهندگان جنگو پوشش می دهد. برای درک لیست کامل تغییرات، به بخش خواندن توصیه شده در پایان مراجعه کنید. مثال‌ها در پایتون 2 و پایتون 3 ارائه شده‌اند. بسته به نوع نصب شما، ممکن است لازم باشد همه دستورات پایتون 3 از پایتون به پایتون 3 تغییر کنند. **همه متدهای «__unicode__» را به «__str__» تغییر دهید** در پایتون 3، متد «()__str__» برای نمایش رشته‌ای مدل‌های شما به جای متد «()__unicode__» با صدای نامناسب فراخوانی می‌شود. این یکی از واضح ترین راه های شناسایی کدهای پورت شده پایتون 3 است: **python 2** ```python class Person(models.Model): name = models.TextField() def __unicode__(self): return self.name ``` **python 3** ```python class Person(models.Model): name = models.TextField() def __str__(self): return self.name ``` این نشان دهنده ی تفاوت در نحوه ی برخورد پایتون 3 با رشته ها است. در Python 2، نمایش قابل خواندن توسط انسان یک کلاس را می توان با «()__str__» (بایت) یا «()__unicode__» (متن) برگرداند. با این حال، در پایتون 3، نمایش قابل خواندن به سادگی توسط «()__str__» (متن) برگردانده می شود. ### همه کلاس ها از شی ارث می برند پایتون 2 دارای دو نوع کلاس است: سبک قدیمی (کلاسیک) و سبک جدید. کلاس های سبک جدید کلاس هایی هستند که به طور مستقیم یا غیرمستقیم از شی به ارث می برند. فقط کلاس‌های سبک جدید می‌توانند از ویژگی‌های پیشرفته پایتون مانند اسلات‌ها، توصیف‌گرها و ویژگی‌ها استفاده کنند. بسیاری از این ها توسط جنگو استفاده می شود. با این حال، کلاس ها به دلایل سازگاری هنوز به طور پیش فرض قدیمی هستند. در پایتون 3، کلاس‌های قدیمی دیگر وجود ندارند. همانطور که در جدول زیر مشاهده می شود، حتی اگر به صراحت هیچ کلاس والد را ذکر نکنید، کلاس شی به عنوان پایه وجود خواهد داشت. بنابراین، همه کلاس ها به سبک جدید هستند: **python 2** ```python >>> class CoolMixin: ... pass >>> CoolMixin.__bases__ () ``` **python 3** ```python >>> class CoolMixin: ... pass >>> CoolMixin.bases (,) ``` **فراخوانی ()super آسانتر است** فراخوانی ساده‌تر ()super، بدون هیچ آرگومان، مقداری از تایپ کردن در پایتون 3 را برای شما ذخیره می‌کند: **python 2** ```python class CoolMixin(object): def do_it(self): return super(CoolMixin, self).do_it() ``` **python 3** ```python class CoolMixin: def do_it(self): return super().do_it() ``` تعیین نام و نمونه کلاس اختیاری است، بنابراین کد شما **DRY** و کمتر مستعد خطا در هنگام بازآفرینی است. ### ورودی های مرتبط بایستی صریح باشند ساختار دایرکتوری زیر را برای بسته ای به نام app1 تصور کنید: ``` /app1 /__init__.py /models.py /tests.py ``` اکنون، در پایتون 3، بیایید موارد زیر را در دایرکتوری والد app1 اجرا کنیم: ``` $ echo "import models" > app1/tests.py $ python -m app1.tests Traceback (most recent call last): ... omitted ... ImportError: No module named 'models' $ echo "from . import models" > app1/tests.py $ python -m app1.tests # Successfully imported ``` در یک بسته، هنگام مراجعه به یک ماژول خواهر و برادر، بایستی از ورودی های مرتبط صریح استفاده کنید. می‌توانید «__init__.py» را در پایتون 3 حذف کنید، اگرچه معمولاً برای شناسایی یک بسته استفاده می‌شود. در پایتون 2، می‌توانید از ورود مدل‌ها برای وارد کردن موفقیت آمیز ماژول «models.py» استفاده کنید. با این حال، مبهم است و می تواند به طور تصادفی هر «models.py» دیگری را در مسیر پایتون شما وارد کند. از این رو، این در پایتون 3 ممنوع است و در پایتون 2 نیز ممنوع است. ### اHttpRequest و HttpResponse دارای انواع str و bytes هستند ما مراقبیم که داده‌هایی را که از HTTP می‌آیند یا از آن طریق خارج می‌شوند، که بر حسب بایت هستند، در مقابل متن‌های درون چارچوب، که رشته های بومی (یونیکد) هستند، مخلوط نکنیم. اساساً برای اشیاء HttpRequest و HttpResponse موارد زیر را در نظر داشته باشید: - عنوان ها همیشه اشیاء «str» خواهند بود - جریان های ورودی و خروجی همیشه اشیاء بایتی خواهند بود برخلاف پایتون 2، رشته ها و بایت ها به طور ضمنی در حین انجام مقایسه یا الحاق با یکدیگر تبدیل نمی شوند. Strings به معنی فقط رشته های یونیکد است. ### رشته های f یا رشته های قالب بندی شده در پایتون 3، ممکن است لفظ های رشته ای را با پیشوند f ببینید. این رشته ها ممکن است حاوی عباراتی در داخل براکت های فرفری باشند، مشابه رشته های قالب پذیرفته شده توسط `()str.format`. آنها در زمان اجرا با استفاده از پروتکل `()format` ارزیابی خواهند شد. در اینجا چند نمونه آورده شده است: ``` >>> class Person: ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return f"name is {self.name}" ... >>> p = Person("Hexa") >>> str(p) 'name is Hexa' ``` اگرچه ممکن است این ترکیب در ابتدا بیگانه به نظر برسد، اما استفاده از آن راحت‌تر از جایگزین‌های قالب‌بندی رشته‌ای است. ### تغییرات و بهبود ترکیب استثنا ترکیب و عملکرد مدیریت استثنا در پایتون 3 به طور قابل توجهی بهبود یافته است. در پایتون 3، شما نمی توانید از نحو جدا شده با کاما برای عبارت `except` استفاده کنید. به جای آن از کلمه کلیدی `as` استفاده کنید: **python 2** ```python try: pass except e, BaseException: pass ``` **python 2 and 3** ```python try: pass except e as BaseException: pass ``` ترکیب جدید برای پایتون 2 نیز توصیه می شود. در پایتون 3، همه ی استثناها باید (مستقیم یا غیرمستقیم) از `BaseException` مشتق شوند. در عمل، شما استثناهای سفارشی خود را با استخراج از کلاس `Exception` ایجاد خواهید کرد. به عنوان یک پیشرفت عمده در گزارش خطا، اگر یک استثنا در هنگام مدیریت یک استثنا رخ دهد، همه ی زنجیره ی استثناها گزارش می شود: **python 2** ```python >>> try: ... print(undefined) ... except Exception: ... print(oops) ... Traceback (most recent call last): File "", line 4, in NameError: name 'oops' is not defined ``` **python 3** ```python >>> try: ... print(undefined) ... except Exception: ... print(oops) ... Traceback (most recent call last): File "", line 2, in NameError: name 'undefined' is not defined During handling of the above exception, another exception occurred: Traceback (most recent call last): File "", line 4, in NameError: name 'oops' is not defined ``` وقتی به این ویژگی عادت کردید، قطعا در پایتون 2 آن را از دست خواهید داد. ### کتابخانه ی استاندارد، دوباره سازماندهی شد توسعه دهندگان اصلی کتابخانه ی استاندارد پایتون را پاکسازی و بهتر سازماندهی کرده اند. به طور مثال، `SimpleHTTPServer` اکنون در ماژول `http.server` زندگی می کند: **python 2** ```python $ python -m SimpleHTTPServer Serving HTTP on 0.0.0.0 port 8000 ... ``` **python 3** ```python $python -m http.server Serving HTTP on 0.0.0.0 port 8000 ... ``` ### چیزهای خوب جدید پایتون 3 فقط در مورد اصلاح زبان نیست. همچنین جایی است که توسعه ی پایتون به صورت بی‌نظیر رخ می‌دهد. این به معنای بهبود زبان از نظر ترکیب، عملکرد و عملکرد داخلی است. برخی از ماژول های جدید قابل توجه اضافه شده به پایتون 3 به شرح زیر است: - asyncio: ورودی/خروجی ناهمزمان، حلقه رویداد، برنامه‌ها و وظایف - اسرار: اعداد تصادفی رمزنگاری قوی - unittest.mock: کتابخانه شی ساختگی برای آزمایش - pathlib: مسیرهای سیستم فایل شی گرا - آمار: توابع آمار ریاضی حتی اگر برخی از این ماژول ها به وسیله ی پایتون 2 پشتیبانی شوند، مهاجرت به پایتون 3 و استفاده از آنها به عنوان ماژول های داخلی جذاب تر است. #### اPyvenv و pip داخلی سازی شده اند بیشتر توسعه دهندگان جدی پایتون استفاده از محیط های مجازی را ترجیح می دهند. `virtualenv` برای جداسازی تنظیمات پروژه از نصب پایتون در سراسر سیستم بسیار محبوب است. خوشبختانه، پایتون 3.3 با یک عملکرد مشابه با استفاده از ماژول `venv` یکپارچه شده است. از پایتون 3.4، یک محیط مجازی جدید با `pip`، یک نصب کننده ی محبوب، از پیش نصب می شود: ```shell $ python -m venv djenv [djenv] $ source djenv/bin/activate [djenv] $ pip install django ``` ### سایر تغییرات ما نمی‌توانیم تمام تغییرات و بهبودهای Python 3 را در این ضمیمه قرار دهیم. با این حال، سایر تغییرات رایج به شرح زیر است: 1. اکنون `()print` یک تابع است: در گذشته یک دستور بود، یعنی آرگومان ها در پرانتز نبودند. 2. اعداد صحیح سرریز نمی شوند: `sys.maxint` قدیمی است. اعداد صحیح دقت نامحدودی خواهند داشت 3. نابرابری `operator <>` حذف شده است: به جای آن از != استفاده کنید 4. تقسیم عدد صحیح واقعی: در پایتون 2، 3/2 به 1 محاسبه می شود. در پایتون 3 به درستی به 1.5 محاسبه می شود. 5. از محدوده به جای xrange استفاده کنید: `()range` اکنون تکرارگرها را برمی گرداند، همانطور که `xrange()` در گذشته کار می کرد. 6. کلیدهای دیکشنری نماها هستند: کلاس‌های dict و مانند dict (مانند `QueryDict`) به جای فهرست‌هایی برای فراخوانی‌های متد `()keys` و `()items` و `()values` تکرارگرها را برمی‌گردانند. ### اطلاعات بیشتر - مطالب جدید در پایتون 3.0 توسط Guido را بخوانید - برای اطلاع از موارد جدید در هر نسخه از پایتون، موارد جدید در پایتون را در https://docs.python.org/3/whatsnew/ بخوانید. - For richly-detailed answers about Python 3, read Python 3 Q & A by Nick Coghlan at http://python-notes.curiousefficiency.org/en/latest/python3/questions_and_answers.html - برای پاسخ‌های کامل و مفصل درباره ی پایتون 3، پرسش و پاسخ پایتون 3 توسط نیک کوگلان را در آدرس مورد اشاره بخوانید. ================================================ FILE: CONTRIBUTING.md ================================================
# چطور در ترجمه میتوانم مشارکت داشته باشم؟ - یک فصلی که در وضعیت "رزرو نشده" قرار دارد را انتخاب کنید. - یک issue باز کنید و فصل مورد نظرتان را آنجا اعلام کنید. - این ریپازیتوری را Fork کنید. - ترجمه کنید و Pull request بفرستید.
================================================ FILE: README.md ================================================ # ترجمه آزاد کتاب Django Design Patterns and Best Practices ![Cover](cover.jpg) قبل از شروع فهرست کتاب، اگر مایل به مشارکت هستید، [نحوه مشارکت](https://github.com/ftg-iran/ddpabp-persian/blob/main/CONTRIBUTING.md) را حتما مطالعه کنید. شما می توانید برای کمک کردن و خشنود کردن و انگیزه دادن به تیم ما، اهدای مالی به خیریه‌ی محک داشته باشید. لینک درگاه خیریه محک در سمت راست صفحه درج شده است. لازم به ذکر است که اگر مبلغی را اهدا کرده اید چون ما بی خبر از آن کار هستیم، یک رسید از آن داخل [گروه تلگرامی ما](https://t.me/dfp_farsi) آپلود کنید. ### فهرست مطالب
جنگو و الگوها
- [چرا جنگو؟](/01-%20Django%20and%20Patterns/README.md#%DA%86%D8%B1%D8%A7-%D8%AC%D9%86%DA%AF%D9%88) - [داستان جنگو ](/01-%20Django%20and%20Patterns/README.md#%D8%AF%D8%A7%D8%B3%D8%AA%D8%A7%D9%86-%D8%AC%D9%86%DA%AF%D9%88) - [جنگو چگونه کار می‌کند؟ ](/01-%20Django%20and%20Patterns/README.md#%D8%AC%D9%86%DA%AF%D9%88-%DA%86%DA%AF%D9%88%D9%86%D9%87-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C-%DA%A9%D9%86%D8%AF) - [الگو چیست؟ ](/01-%20Django%20and%20Patterns/README.md#%D8%A7%D9%84%DA%AF%D9%88-%DA%86%DB%8C%D8%B3%D8%AA) - [الگوها در این کتاب ](/01-%20Django%20and%20Patterns/README.md#%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7-%D8%AF%D8%B1-%D8%A7%DB%8C%D9%86-%DA%A9%D8%AA%D8%A7%D8%A8) - [نتیجه‌گیری](/01-%20Django%20and%20Patterns/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
طراحی برنامه
- [چگونه نیازها را جمع‌آوری کنیم؟](/02-%20Application%20Design/README.md#%DA%86%DA%AF%D9%88%D9%86%D9%87-%D9%86%DB%8C%D8%A7%D8%B2%D9%85%D9%86%D8%AF%DB%8C%D9%87%D8%A7-%D8%B1%D8%A7-%D8%AC%D9%85%D8%B9%D8%A2%D9%88%D8%B1%DB%8C-%DA%A9%D9%86%DB%8C%D9%85) - [آیا شما یک داستان‌گو هستید؟ ](/02-%20Application%20Design/README.md#%D8%A2%DB%8C%D8%A7-%D8%B4%D9%85%D8%A7-%DB%8C%DA%A9-%D9%82%D8%B5%D9%87%DA%AF%D9%88-%D9%87%D8%B3%D8%AA%DB%8C%D8%AF) - [HTML mockups ](/02-%20Application%20Design/README.md#%D9%85%D8%A7%DA%A9%D8%AA%D9%87%D8%A7%DB%8C-html) - [طراحی برنامه ](/02-%20Application%20Design/README.md#%D8%B7%D8%B1%D8%A7%D8%AD%DB%8C-%D8%A7%D9%BE%D9%84%DB%8C%DA%A9%DB%8C%D8%B4%D9%86) - [Best Practice ها قبل از شروع یک پروژه ](/02-%20Application%20Design/README.md#%D8%A8%D9%87%DB%8C%D9%86%D9%87%D8%AA%D8%B1%DB%8C%D9%86-%D8%B1%D9%88%D8%B4%D9%87%D8%A7-%D9%82%D8%A8%D9%84-%D8%A7%D8%B2-%D8%B4%D8%B1%D9%88%D8%B9-%D9%BE%D8%B1%D9%88%DA%98%D9%87) - [SuperBook - ماموریت شما، اگر بخواهید آن را بپذیرید](/02-%20Application%20Design/README.md#%D8%B3%D9%88%D9%BE%D8%B1%D8%A8%D9%88%DA%A9-%D9%85%D8%A3%D9%85%D9%88%D8%B1%DB%8C%D8%AA%DB%8C-%DA%A9%D9%87-%D8%A8%D8%A7%DB%8C%D8%AF-%D8%A8%D9%BE%D8%B0%DB%8C%D8%B1%DB%8C%D8%AF) - [نتیجه‌گیری](/02-%20Application%20Design/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
مدل‌ها
- [M بزرگ‌تر از V و C است](/03-%20Models/README.md#%D8%A7%D9%85-%D8%A8%D8%B2%D8%B1%DA%AF-%D8%AA%D8%B1-%D8%A7%D8%B2-%D9%88%DB%8C-%D9%88-%D8%B3%DB%8C-%D8%A8%D8%B2%D8%B1%DA%AF-%D8%AA%D8%B1-%D8%A7%D8%B2-%D9%88%DB%8C-%D8%A7%D8%B3%D8%AA) - [شکار مدل ](/03-%20Models/README.md#%D8%B4%DA%A9%D8%A7%D8%B1-%D9%85%D8%AF%D9%84%D9%87%D8%A7) - [الگوهای ساختاری ](/03-%20Models/README.md#%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-%D8%B3%D8%A7%D8%AE%D8%AA%D8%A7%D8%B1%DB%8C) - [الگوهای بازیابی ](/03-%20Models/README.md#%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-%D8%A8%D8%A7%D8%B2%DB%8C%D8%A7%D8%A8%DB%8C) - [Migrations](/03-%20Models/README.md#%D9%85%D9%87%D8%A7%D8%AC%D8%B1%D8%AA%D9%87%D8%A7-migrations) - [نتیجه‌گیری](/03-%20Models/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
Views and URLs
- [یک ویو از بالا](/04-%20Views%20and%20URLs/README.md#%D9%86%DA%AF%D8%A7%D9%87%DB%8C-%D8%A8%D9%87-%D9%88%DB%8C%D9%88-%D8%A7%D8%B2-%D8%A8%D8%A7%D9%84%D8%A7) - [ویوهای عمومی مبتنی بر کلاس ](/04-%20Views%20and%20URLs/README.md#%D9%88%DB%8C%D9%88%D9%87%D8%A7%DB%8C-%D8%B9%D9%85%D9%88%D9%85%DB%8C-%D9%85%D8%A8%D8%AA%D9%86%DB%8C-%D8%A8%D8%B1-%DA%A9%D9%84%D8%A7%D8%B3) - [View mixin ها](/04-%20Views%20and%20URLs/README.md#%D9%85%DB%8C%DA%A9%D8%B3%DB%8C%D9%86%D9%87%D8%A7%DB%8C-%D9%88%DB%8C%D9%88) - [Decorator ها ](/04-%20Views%20and%20URLs/README.md#%D8%AF%DA%A9%D9%88%D8%B1%D8%A7%D8%AA%D9%88%D8%B1%D9%87%D8%A7) - [الگوهای ویو ](/04-%20Views%20and%20URLs/README.md#%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-%D9%88%DB%8C%D9%88) - [طراحی URLها](/04-%20Views%20and%20URLs/README.md#%D8%B7%D8%B1%D8%A7%D8%AD%DB%8C-%DA%A9%D8%B1%D8%AF%D9%86-url%D9%87%D8%A7) - [React.js, Vue.js, و دیگر جایگزین‌های ویو](/04-%20Views%20and%20URLs/README.md#reactjs-vuejs-%D9%88-%D8%AF%DB%8C%DA%AF%D8%B1-%D9%88%DB%8C%D9%88%D9%87%D8%A7%DB%8C-%D8%AC%D8%A7%DB%8C%DA%AF%D8%B2%DB%8C%D9%86) - [نتیجه‌گیری](/04-%20Views%20and%20URLs/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
قالب‌ها
- [فهمیدن ویژگی‌های زبان قالب جنگو](/05-%20Templates/README.md#%D8%AF%D8%B1%DA%A9-%D9%88%DB%8C%DA%98%DA%AF%DB%8C%D9%87%D8%A7%DB%8C-%D8%B2%D8%A8%D8%A7%D9%86-%D8%AA%D9%85%D9%BE%D9%84%DB%8C%D8%AA-%D8%AC%D9%86%DA%AF%D9%88) - [Jinja2](/05-%20Templates/README.md#%D8%AC%DB%8C%D9%86%D8%AC%D8%A7-%DB%B2) - [سازمان‌ دادن قالب‌ها](/05-%20Templates/README.md#%D8%B3%D8%A7%D8%B2%D9%85%D8%A7%D9%86%D8%AF%D9%87%DB%8C-%D8%AA%D9%85%D9%BE%D9%84%DB%8C%D8%AA%D9%87%D8%A7) - [قالب‌ها چگونه کار می‌کنند؟ ](/05-%20Templates/README.md#%D8%AA%D9%85%D9%BE%D9%84%DB%8C%D8%AA%D9%87%D8%A7-%DA%86%DA%AF%D9%88%D9%86%D9%87-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C%DA%A9%D9%86%D9%86%D8%AF) - [استفاده از Bootstrap](/05-%20Templates/README.md#%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-%D8%A8%D9%88%D8%AA%D8%B3%D8%AA%D8%B1%D9%BE) - [الگوهای قالب](/05-%20Templates/README.md#%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-%D8%AA%D9%85%D9%BE%D9%84%DB%8C%D8%AA) - [نتیجه‌گیری](/05-%20Templates/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
رابط ادمین
- [استفاده از رابط ادمین](/06-%20Admin%20Interface/README.md#%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-%D8%B1%D8%A7%D8%A8%D8%B7-%DA%A9%D8%A7%D8%B1%D8%A8%D8%B1%DB%8C-%D8%A7%D8%AF%D9%85%DB%8C%D9%86) - [گسترش دادن مدلها برای ادمین](/06-%20Admin%20Interface/README.md#%D8%A8%D9%87%DB%8C%D9%86%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-%D9%85%D8%AF%D9%84-%D9%87%D8%A7-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%A8%D8%AE%D8%B4-%D8%A7%D8%AF%D9%85%DB%8C%D9%86) - [سفارشی‌سازی‌های رابط ادمین](/06-%20Admin%20Interface/README.md#%D8%B3%D9%81%D8%A7%D8%B1%D8%B4%DB%8C-%D8%B3%D8%A7%D8%B2%DB%8C-%D8%B1%D8%A7%D8%A8%D8%B7-%D9%85%D8%AF%DB%8C%D8%B1%DB%8C%D8%AA) - [محافظت از ادمین](/06-%20Admin%20Interface/README.md#%D8%AD%D9%81%D8%A7%D8%B8%D8%AA-%D8%A7%D8%B2-%D8%A7%D8%AF%D9%85%DB%8C%D9%86) - [نتیجه‌گیری](/06-%20Admin%20Interface/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
فرم‌ها
- [فرم‌ها چگونه کار می‌کنند؟](/07-%20Forms/README.md#%D9%81%D8%B1%D9%85-%D9%87%D8%A7-%DA%86%DA%AF%D9%88%D9%86%D9%87-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C-%DA%A9%D9%86%D9%86%D8%AF) - [نمایش فرم‌ها](/07-%20Forms/README.md#%D9%86%D9%85%D8%A7%DB%8C%D8%B4-%D9%81%D8%B1%D9%85-%D9%87%D8%A7-) - [درک‌ کردن CSRF](/07-%20Forms/README.md#%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-csrf) - [پردازش فرم با ویوهای مبتنی بر کلاس](/07-%20Forms/README.md#%D9%BE%D8%B1%D8%AF%D8%A7%D8%B2%D8%B4-%D9%81%D8%B1%D9%85-%D9%87%D8%A7-%D8%A8%D8%A7-class-based-view--cbv-) - [الگوهای فرم](/07-%20Forms/README.md#%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-%D9%81%D8%B1%D9%85--) - [نتیجه‌گیری](/07-%20Forms/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
کار کردن به صورت ناهمزمان
- [چرا ناهمزمانی؟](/08-%20Working%20Asynchronously/README.md#%DA%86%D8%B1%D8%A7-%D9%86%D8%A7%D9%87%D9%85%D8%B2%D9%85%D8%A7%D9%86%DB%8C) - [الگوهای ناهمزمانی](/08-%20Working%20Asynchronously/README.md#%D9%BE%D8%AA%D8%B1%D9%86-%D9%87%D8%A7-%D9%88-%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-%D9%86%D8%A7%D9%87%D9%85%D8%B2%D9%85%D8%A7%D9%86%DB%8Casynchronous-patterns) - [راه‌حل‌های ناهمزمانی برای جنگو](/08-%20Working%20Asynchronously/README.md#%D8%B1%D8%A7%D9%87-%D8%AD%D9%84-%D9%87%D8%A7%DB%8C-%D9%86%D8%A7%D9%87%D9%85%D8%B2%D9%85%D8%A7%D9%86%DB%8C-%D8%AF%D8%B1-%D8%AC%D9%86%DA%AF%D9%88) - [نتیجه‌گیری](/08-%20Working%20Asynchronously/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
ایجاد APIها
- [RESTful API](/09-%20Creating%20APIs/README.md#restful-api) - [Django Rest Framework](/09-%20Creating%20APIs/README.md#%D9%81%D8%B1%DB%8C%D9%85%D9%88%D8%B1%DA%A9-%D8%B1%D8%B3%D8%AA-%D8%AC%D9%86%DA%AF%D9%88) - [الگوهای API](/09-%20Creating%20APIs/README.md#%D8%A7%D9%84%DA%AF%D9%88%D9%87%D8%A7%DB%8C-api) - [نتیجه‌گیری](/09-%20Creating%20APIs/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
سر و کار داشتن با کد میراثی
- [پیدا کردن ورژن جنگو](/10-%20Dealing%20with%20Legacy%20Code/README.md#%DB%8C%D8%A7%D9%81%D8%AA%D9%86-%D9%86%D8%B3%D8%AE%D9%87-%D8%AC%D9%86%DA%AF%D9%88) - [فایل‌ها کجا هستند؟ این PHP نیست](/10-%20Dealing%20with%20Legacy%20Code/README.md#%D9%81%D8%A7%DB%8C%D9%84-%D9%87%D8%A7-%DA%A9%D8%AC%D8%A7-%D9%87%D8%B3%D8%AA%D9%86%D8%AF-%D8%A7%DB%8C%D9%86-php-%D9%86%DB%8C%D8%B3%D8%AA) - [شروع با urls.py](/10-%20Dealing%20with%20Legacy%20Code/README.md#%D8%B4%D8%B1%D9%88%D8%B9-%D8%A8%D8%A7-urlspy) - [پرش در اطراف کد](/10-%20Dealing%20with%20Legacy%20Code/README.md#%D9%BE%D8%B1%D8%B4-%D8%AF%D8%B1-%D8%A7%D8%B7%D8%B1%D8%A7%D9%81-%DA%A9%D8%AF) - [درک کردن پایه‌ی کد](/10-%20Dealing%20with%20Legacy%20Code/README.md#%D8%AF%D8%B1%DA%A9-%DA%A9%D8%B1%D8%AF%D9%86-%D9%BE%D8%A7%DB%8C%D9%87%DB%8C-%DA%A9%D8%AF) - [تغییرات افزایشی یا نوشتن مجدد به صورت کامل؟](/10-%20Dealing%20with%20Legacy%20Code/README.md#%D8%AA%D8%BA%DB%8C%DB%8C%D8%B1%D8%A7%D8%AA-%D8%A7%D9%81%D8%B2%D8%A7%DB%8C%D8%B4%DB%8C-%DB%8C%D8%A7-%D9%86%D9%88%D8%B4%D8%AA%D9%86-%D9%85%D8%AC%D8%AF%D8%AF-%D8%A8%D9%87-%D8%B5%D9%88%D8%B1%D8%AA-%DA%A9%D8%A7%D9%85%D9%84) - [تست نوشتن قبل از ایجاد هرگونه تغییر](/10-%20Dealing%20with%20Legacy%20Code/README.md#%D8%AA%D8%B3%D8%AA-%D9%86%D9%88%D8%B4%D8%AA%D9%86-%D9%82%D8%A8%D9%84-%D8%A7%D8%B2-%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF-%D9%87%D8%B1%DA%AF%D9%88%D9%86%D9%87-%D8%AA%D8%BA%DB%8C%DB%8C%D8%B1) - [یکپارچگی دیتابیس میراثی](/10-%20Dealing%20with%20Legacy%20Code/README.md#%DB%8C%DA%A9%D9%BE%D8%A7%D8%B1%DA%86%DA%AF%DB%8C-%D8%AF%DB%8C%D8%AA%D8%A7%D8%A8%DB%8C%D8%B3-%D9%85%DB%8C%D8%B1%D8%A7%D8%AB%DB%8C) - [تصحیح آینده](/10-%20Dealing%20with%20Legacy%20Code/README.md#%D8%A2%DB%8C%D9%86%D8%AF%D9%87-%D9%86%DA%AF%D8%B1%DB%8C3--future-proof) - [نتیجه‌گیری](/10-%20Dealing%20with%20Legacy%20Code/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
تست کردن و دیباگ کردن
- [چرا تست بنویسیم؟](/11-%20Testing%20and%20Debugging/README.md#%DA%86%D8%B1%D8%A7-%D8%AA%D8%B3%D8%AA-%D8%A8%D9%86%D9%88%DB%8C%D8%B3%DB%8C%D9%85) - [TDD](/11-%20Testing%20and%20Debugging/README.md#%D8%B1%D9%88%DB%8C%DA%A9%D8%B1%D8%AF-tdd) - [یک نمونه تست نوشتن](/11-%20Testing%20and%20Debugging/README.md#%D9%86%D9%88%D8%B4%D8%AA%D9%86-%DB%8C%DA%A9-%D8%AA%D8%B3%D8%AA-%D9%85%D9%88%D8%B6%D9%88%D8%B9%DB%8C) - [Mocking](/11-%20Testing%20and%20Debugging/README.md#%D8%AA%D9%82%D9%84%DB%8C%D8%AF-%DA%A9%D8%B1%D8%AF%D9%86) - [Pattern - Test fixtures and factories](/11-%20Testing%20and%20Debugging/README.md#%D8%A7%D9%84%DA%AF%D9%88--%D8%AA%D8%B3%D8%AA-%DA%A9%D8%B1%D8%AF%D9%86-%D8%A8%D8%A7-%D8%AA%D8%AC%D9%87%DB%8C%D8%B2%D8%A7%D8%AA-fixture-%D9%88-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87%D9%87%D8%A7-factory) - [آموختن بیشتر درباره‌ی تست کردن](/11-%20Testing%20and%20Debugging/README.md#%D9%BE%DB%8C%D8%B4%D8%A8%DB%8C%D9%86%DB%8C%D9%87%D8%A7%DB%8C-%D9%88%D8%AD%D8%B4%D8%AA%D9%86%D8%A7%DA%A9) - [دیباگ کردن](/11-%20Testing%20and%20Debugging/README.md#%D8%B1%D9%81%D8%B9-%D8%A7%D8%B4%DA%A9%D8%A7%D9%84) - [تابع پرینت](/11-%20Testing%20and%20Debugging/README.md#%D9%81%D8%A7%D9%86%DA%A9%D8%B4%D9%86-%D9%BE%D8%B1%DB%8C%D9%86%D8%AA) - [Logging](/11-%20Testing%20and%20Debugging/README.md#%D9%84%D8%A7%DA%AF-%DA%A9%D8%B1%D8%AF%D9%86) - [نوار ابزار دیباگ جنگو](/11-%20Testing%20and%20Debugging/README.md#%D9%BE%DA%A9%DB%8C%D8%AC-django-debug-toolbar) - [The Python debugger pdb ](/11-%20Testing%20and%20Debugging/README.md#%D8%B9%DB%8C%D8%A8%DB%8C%D8%A7%D8%A8-%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86%DB%8C-pdb) - [بقیه‌ی دیباگرها](/11-%20Testing%20and%20Debugging/README.md#%D8%B3%D8%A7%DB%8C%D8%B1-%D8%B9%DB%8C%D8%A8%DB%8C%D8%A7%D8%A8%D9%87%D8%A7) - [دیباگ کردن قالب‌های جنگو](/11-%20Testing%20and%20Debugging/README.md#%D8%B9%DB%8C%D8%A8%DB%8C%D8%A7%D8%A8%DB%8C-%D8%AF%D8%B1-%D8%AA%D9%85%D9%BE%D9%84%DB%8C%D8%AA%D9%87%D8%A7%DB%8C-%D8%AC%D9%86%DA%AF%D9%88) - [نتیجه‌گیری](/11-%20Testing%20and%20Debugging/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
امنیت
- [Cross-site scripting](/12-%20Security/README.md#cross-site-scripting) - [Cross-site request forgery](/12-%20Security/README.md#%D8%AC%D8%B9%D9%84-%D9%87%D8%A7%DB%8C-%D8%AF%D8%B1%D8%AE%D9%88%D8%A7%D8%B3%D8%AA-%D8%A8%DB%8C%D9%86-%D8%B3%D8%A7%DB%8C%D8%AA%DB%8Ccross-site-request-forgery) - [SQL injection](/12-%20Security/README.md#%D8%AA%D8%B2%D8%B1%DB%8C%D9%82-sql-sql-injection) - [Clickjacking](/12-%20Security/README.md#%D8%AF%D8%B2%D8%AF%DB%8C-%DA%A9%D9%84%DB%8C%DA%A9-clickjacking) - [Shell injection](/12-%20Security/README.md#%D8%AA%D8%B2%D8%B1%DB%8C%D9%82-%D8%AF%D8%B3%D8%AA%D9%88%D8%B1%D8%A7%D8%AA-%D8%B3%DB%8C%D8%B3%D8%AA%D9%85-%D8%B9%D8%A7%D9%85%D9%84-shell-injection) - [یک چک‌لیست دم‌دستی امنیت](/12-%20Security/README.md#%DB%8C%DA%A9-%DA%86%DA%A9-%D9%84%DB%8C%D8%B3%D8%AA-%D8%A7%D9%85%D9%86%DB%8C%D8%AA%DB%8C-%D9%85%D9%81%DB%8C%D8%AF) - [نتیجه‌گیری](/12-%20Security/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
آمادگی برای محیط پروداکشن
- [محیط پروداکشن](/13-%20Production-Ready/README.md#%D9%85%D8%AD%DB%8C%D8%B7-%D8%AA%D9%88%D9%84%DB%8C%D8%AF-%D9%88-%D9%BE%D8%B1%D9%88%D8%AF%D8%A7%DA%A9%D8%B4%D9%86) - [ماشین‌های مجازی یا داکر](/13-%20Production-Ready/README.md#%D9%85%D8%A7%D8%B4%DB%8C%D9%86%D9%87%D8%A7%DB%8C-%D9%85%D8%AC%D8%A7%D8%B2%DB%8C-%DB%8C%D8%A7-%D8%AF%D8%A7%DA%A9%D8%B1) - [میزبانی](/13-%20Production-Ready/README.md#%D9%85%DB%8C%D8%B2%D8%A8%D8%A7%D9%86%DB%8C-%D9%88-%D9%87%D8%A7%D8%B3%D8%AA%DB%8C%D9%86%DA%AF) - [ابزارهای استقرار](/13-%20Production-Ready/README.md#%D8%A7%D8%A8%D8%B2%D8%A7%D8%B1%D9%87%D8%A7%DB%8C-%D8%A7%D8%B3%D8%AA%D9%82%D8%B1%D8%A7%D8%B1-%D8%B3%D8%A7%D8%B2%DB%8C-%D9%88-%D8%AF%DB%8C%D9%BE%D9%84%D9%88%DB%8C) - [نظارت](/13-%20Production-Ready/README.md#%D9%85%D8%A7%D9%86%DB%8C%D8%AA%D9%88%D8%B1%DB%8C%D9%86%DA%AF) - [افزایش کارایی](/13-%20Production-Ready/README.md#%D8%A8%D9%87%D8%A8%D9%88%D8%AF-%DA%A9%D8%A7%D8%B1%D8%A7%DB%8C%DB%8C) - [نتیجه‌گیری](/13-%20Production-Ready/README.md#%D8%AE%D9%84%D8%A7%D8%B5%D9%87)
ضمیمه‌ی A: پایتون ۲ در برابر پایتون ۳
- [پایتون ۳](/Appendix%20A%20%20Python%202%20Versus%20Python%203/README.md#%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86-3) - [اطلاعات بیشتر](/Appendix%20A%20%20Python%202%20Versus%20Python%203/README.md#%D8%A7%D8%B7%D9%84%D8%A7%D8%B9%D8%A7%D8%AA-%D8%A8%DB%8C%D8%B4%D8%AA%D8%B1)
| تاریخ اتمام ترجمه | مترجم | فصل | |:-----------------:|:----------------:|:----------------------------------:| | اتمام | محمدامیر لطفی پور |01- Django and Patterns | | اتمام | Rahimz |02- Application Design | | اتمام | Rahimz |03- Models | | اتمام | Hamed Daneshvar |04- Views and URLs | | اتمام | Rahimz |05- Templates | | اتمام | sajjad ebrahimi moghaddam |06- Admin Interface | | اتمام | amirajoodani |07- Forms | | اتمام | Khalil Farashiani |08- Working Asynchronously | | اتمام | Hamed Daneshvar |09- Creating APIs | | اتمام | Fereydoon jafari babookani |10- Dealing with Legacy Code | | اتمام | Rahimz |11- Testing and Debugging | | اتمام | Mohammad Amin Orojloo |12- Security | | اتمام | mokarramis |13- Production-Ready | | اتمام | Hamed Alizade |Appendix A: Python 2 Versus Python 3|
## ممنون از افرادی که در ترجمه این کتاب مشارکت داشتند :heart: [![People](https://contrib.rocks/image?repo=ftg-iran/ddpabp-persian)](https://github.com/ftg-iran/ddpabp-persian/graphs/contributors) ================================================ FILE: _sidebar.md ================================================ - [Home](/) - [01 - Django and Patterns](01-Django-and-Patterns/README.md) - [02 - Application Design](02-ApplicationDesign/README.md) - [03 - Models](03-Models/README.md) - [04 - Views and URLs](04-Views-and-URLs/README.md) - [05 - Templates](05-Templates/README.md) - [06 - Admin Interface](06-AdminInterface/README.md) - [07 - Forms](07-Forms/README.md) - [08 - Working Asynchronously](08-WorkingAsynchronously/README.md) - [09 - Creating APIs](09-CreatingAPIs/README.md) - [10 - Dealing with Legacy Code](10-Dealing-with-LegacyCode/README.md) - [11 - Testing and Debugging](11-Testing-and-Debugging/README.md) - [12 - Security](12-Security/README.md) - [13 - Production-Ready](13-Production-Ready/README.md) - [Appendix: Python 2 vs Python 3](Appendix-A-Python2-Versus-Python3/README.md) ================================================ FILE: custom.css ================================================ :root { --theme-color: #3f51b5; --accent-color: #ff4081; --border-color: #e0e0e0; } body { font-family: "Rubik", sans-serif; font-optical-sizing: auto; font-weight: 400; font-style: normal; transition: background-color 0.3s ease; } /* Theme toggle button */ #theme-toggle { position: fixed; bottom: 100px; right: 20px; width: 40px; height: 40px; border-radius: 50%; background: var(--theme-color); color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 999; box-shadow: 0 2px 5px rgba(0,0,0,0.2); font-size: 1.2rem; transition: all 0.3s ease; } #theme-toggle:hover { transform: scale(1.1); } /* Navigation buttons */ .nav-buttons { margin: 3rem 0; padding: 1.5rem 1rem; display: flex; justify-content: space-between; } .nav-button { padding: 0.6rem 1.2rem; border-radius: 4px; background: var(--theme-color); color: white !important; text-decoration: none !important; transition: all 0.3s ease; display: inline-block; } .nav-button:hover { background: var(--accent-color); transform: translateY(-2px); box-shadow: 0 2px 8px rgba(0,0,0,0.1); } /* Dark mode specific styles */ .dark { --border-color: #444; } .dark .nav-button { background: var(--accent-color); } .dark .nav-button:hover { background: #ff2674; } /* Existing sidebar styles (keep these) */ .sidebar { width: 280px !important; padding: 20px !important; } .sidebar-nav { padding: 0 !important; } .sidebar-nav li { margin: 0.5rem 0; } .sidebar-nav a { border-radius: 4px; padding: 6px 10px !important; transition: all 0.2s ease; } .sidebar-nav a:hover { background: rgba(var(--theme-color), 0.1); } .markdown-section { max-width: 900px; padding: 2rem 3rem; direction: rtl; } .dark .sidebar { background-color: #1e1e1e !important; } .dark .sidebar-nav a { color: #e0e0e0 !important; } code { direction: ltr; } ================================================ FILE: custom.js ================================================ // Docsify configuration window.$docsify = { name: 'Django Design Patterns and Best Practices', loadSidebar: true, themeColor: '#3f51b5', search: { paths: 'auto', placeholder: 'Search...' }, alias: { '/.*/_sidebar.md': '/_sidebar.md' }, markdown: { tables: true // Explicitly enable tables }, plugins: [ function(hook) { hook.doneEach(function() { if (!buttonsExist) { updateNavigationButtons(); } if (!document.getElementById('theme-toggle')) { addThemeToggle(); } }); } ] }; // Book structure - update this when adding new chapters const bookStructure = [ '#/01-Django-and-Patterns/README', '#/02-ApplicationDesign/README', '#/03-Models/README', '#/04-Views-and-URLs/README', '#/05-Templates/README', '#/06-AdminInterface/README', '#/07-Forms/README', '#/08-WorkingAsynchronously/README', '#/09-CreatingAPIs/README', '#/10-Dealing-with-LegacyCode/README', '#/11-Testing-and-Debugging/README', '#/12-Security/README', '#/13-Production-Ready/README', '#/Appendix-A-Python2-Versus-Python3/README', ]; // Theme initialization on load document.addEventListener('DOMContentLoaded', setInitialTheme); // Global variable to track if buttons exist let buttonsExist = false; // Updated navigation function function updateNavigationButtons() { const currentHash = window.location.hash.split('?')[0]; const currentIndex = bookStructure.findIndex(path => currentHash === path); // Remove existing buttons if they exist const oldButtons = document.querySelector('.nav-buttons'); if (oldButtons) { oldButtons.remove(); buttonsExist = false; } if (currentIndex >= 0) { const navHtml = ` `; const content = document.querySelector('.content'); if (content) { content.insertAdjacentHTML('beforeend', navHtml); buttonsExist = true; // Add click handlers after buttons are inserted document.querySelector('.prev-button')?.addEventListener('click', function(e) { e.preventDefault(); window.location.hash = bookStructure[currentIndex-1]; setTimeout(updateNavigationButtons, 100); }); document.querySelector('.next-button')?.addEventListener('click', function(e) { e.preventDefault(); window.location.hash = bookStructure[currentIndex+1]; setTimeout(updateNavigationButtons, 100); }); } } } // Handle hash changes window.addEventListener('hashchange', function() { buttonsExist = false; updateNavigationButtons(); }); // Theme functions (keep these the same) function addThemeToggle() { const toggle = document.createElement('div'); toggle.id = 'theme-toggle'; toggle.innerHTML = '🌓'; document.body.appendChild(toggle); toggle.addEventListener('click', toggleTheme); } function toggleTheme() { const lightTheme = document.getElementById('light-theme'); const darkTheme = document.getElementById('dark-theme'); const isDark = lightTheme.disabled; lightTheme.disabled = !isDark; darkTheme.disabled = isDark; localStorage.setItem('docsify-theme', isDark ? 'light' : 'dark'); } function setInitialTheme() { if (localStorage.getItem('docsify-theme') === 'dark') { document.getElementById('light-theme').disabled = true; document.getElementById('dark-theme').disabled = false; } } ================================================ FILE: index.html ================================================ Django Design Patterns and Best Practices