سالها پیش، باقر، ممجواد و مصطفی سه دانشجوی جوان مهندسی کامپیوتر دانشگاه صنعتی شریف، آغازگری بودند بر آغازگرانی که بعدها کوئرا (Quera) نام گرفت...
اکنون، بعد از سالها تجربه و پیشرفت و کوئراگری، این سه آغازگر، که آغازگرِ آغازگران کوئرا بودند، تصمیم گرفتند تا همزمان با شروع سری جدید #المپیکفناوری پردیس با آغازگری جدیدی، ویژگی جدید مدیریت نسخهی کوئرا ( Quera's Version Control) را برای درسنامهها و سوالات توسعه دهند. این ویژگی جدید در کوئرا، باعث میشود تا برخلاف گذشته، هر درسنامه بتواند چندین نسخهی مختلف داشته باشد و نسخهی فعالی (Active Version) برای کاربران نمایش داده شود، سیستم مدیریت نسخه کوئرا به گونهای طراحی میشود که هویت ایجادکنندهی هر نسخه نیز همانند طراحان و مربیان کالجهای کوئرا کالج، ثبت و نمایش داده شود تا مسیر تغییرات شفاف شود و سابقهی تمامی مشارکتکنندگان در توسعهی محتواها، سوالات و درسنامهها در کوئرا به خوبی حفظ شود.
نسخهبندی درسنامهها و سوالات، قرار است تا بر اساس استاندارد SemVer (Semantic Versioning یا نسخهبندی معنایی) انجام میشود و فرمت MAJOR.MINOR.PATCH برای مدیریت آنها وجود داشته باشد. اولین نسخهی هر درسنامه و سوال با شمارهٔ 0.1.0 آغاز میشود. هنگام ایجاد تغییرات جدید، مشارکتکنندگان میتوانند نوع افزایش نسخه را مشخص کند: افزایش MAJOR، MINOR یا PATCH. کاربارن بعدها هنگام خواندن این سوالات و درسنامهها میتوانند روند تغییرات و مشارکتکنندگان آنها را ببینند و همچنین نسخهی فعال را تغییر داده تا بتوانند نسخه مورد نظر خودشان از آن سوال یا درسنامه را مطالعه کنند. از آنجایی که این سه آغازگر قرار است به زودی و در مجموعه برنامههای استخدامی ترتیب داده شده در فینال مسابقات، سخنرانیهای آغازگرانه داشته باشند، در قالب این سوال از شما میخواهند تا بخشی از سیستم مدیریت نسخه کوئرا را آغازگری کنید!

پروژهی اولیه
برای دانلود پروژهی اولیه روی این لینک کلیک کنید.
ساختار فایلها
release-manager/
├── accounts
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── templates
│ │ └── accounts
│ │ ├── login.html
│ │ └── signup.html
│ ├── __init__.py
│ ├── apps.py
│ ├── forms.py
│ ├── models.py
│ ├── urls.py
│ └── views.py
├── core
│ ├── forms
│ │ ├── __init__.py
│ │ ├── boundfields.py
│ │ └── renderers.py
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── lms
│ ├── fixtures
│ │ └── data.json
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── templates
│ │ └── lms
│ │ ├── lesson_confirm_delete.html
│ │ ├── lesson_detail.html
│ │ ├── lesson_form.html
│ │ ├── lesson_list.html
│ │ ├── my_lessons.html
│ │ ├── release_detail.html
│ │ ├── release_form.html
│ │ └── release_list.html
│ ├── __init__.py
│ ├── apps.py
│ ├── forms.py
│ ├── mixins.py
│ ├── models.py
│ ├── urls.py
│ └── views.py
├── static
│ ├── css
│ │ └── markdown.css
│ └── imgs
│ └── quera.png
├── templates
│ └── base.html
├── tests
│ ├── __init__.py
│ └── sample_tests.py
├── utils
│ ├── markdown.py
│ ├── models.py
│ ├── semver.py
│ └── utility.py
├── db.sqlite3
├── manage.py
└── requirements.txt
راهاندازی پروژه
برای اجرای پروژه، باید پایتون و ابزار pip را از قبل نصب کرده باشید.
-
ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
-
در پوشهی اصلی پروژه، یک محیط مجازی پایتون (venv) ایجاد و فعال کنید:
python -m venv venv
source venv/bin/activate # در ویندوز: venv\Scripts\activate
- دستور زیر را برای نصب نیازمندیها در پوشهی اصلی پروژه اجرا کنید:
pip install -r requirements.txt
- برای اجرای پروژه Django دستور زیر را در مسیر پوشهی اصلی پروژه اجرا کنید:
python manage.py runserver
در صورت اجرای موفق، یک لینک در خروجی نمایش داده میشود که میتوانید آن را در مرورگر باز کنید.
- برای اجرای مایگریشنها و ایجاد جداول پایگاه داده، دستور زیر را اجرا کنید:
python manage.py migrate
- برای بارگذاری دادههای نمونه از فایل fixture، دستور زیر را اجرا کنید:
python manage.py loaddata data.json
جزئیات پروژه
فایل models.py
models.pyباید دو مدل Lesson (درسنامه) و Release (نسخه) را مطابق زیر پیادهسازی کنید. در ادامه توضیحات مربوط به نحوهی پیادهسازی هر یک از این مدلها آورده شدهاست.
مدل درسنامه (Lesson)
هر درسنامه شامل اطلاعاتی از جمله کلید خارجی به نویسندهی آن، رشتهای حاوی آخرین نسخهی فعال درسنامه و فیلدهای تاریخ-زمان برای نگهداری زمان ایجاد و آخرین زمان ویرایش میباشد.
| نام | نوع |
|---|---|
author |
کلید خارجی به مدل کاربر |
active_version |
رشته با طول حداکثر ۲۰ کاراکتر |
created_at |
تاریخ-زمان (ثبت خودکار زمان ایجاد شیء) |
updated_at |
تاریخ-زمان (ثبت خودکار زمان آخرین ویرایش) |
- با حذف کاربر باید تمام درسنامههای مربوط به آن حذف شوند.
- با استفاده از فیلد
lessonsاز طرف مدلUserباید به توان به درسنامههای مربوط به آن دسترسی پیدا کرد. - فیلد
active_versionرشتهای با فرمتMAJOR.MINOR.PATCH(سه عدد که با.از هم جدا شدند) است و مقدار پیشفرض آن برابر با رشتهی خالی است. - ترتیب پیشفرض این مدل بر اساس فیلد
updated_atبهصورت نزولی است و درسنامههایی که جدیدتر ویرایش شدهاند بالاتر قرار میگیرند. - رشتهی نمایشی پیشفرض این مدل به فرمت زیر است:
Lesson #ID by USERNAME
متد get_active_release
اگر مقدار فیلد active_version خالی بود، خروجی متد برابر None است؛ در غیر اینصورت سه جزء اصلی نسخه را از آن استخراج کرده و شیء مربوطه از مدل نسخه (Release) متناظر با آن را برمیگرداند.
def get_active_release(self):
pass
متد set_active_release
مقدار فیلد active_version را با توجه به شیء نسخهی دریافتشده به فرمت MAJOR.MINOR.PATCH بهروزرسانی کرده و ذخیره میکند؛ بهگونهای که زمان آخرین ویرایش (updated_at) نیز بهروزرسانی شود.
def set_active_release(self, release: 'Release'):
pass
مدل نسخه (Release)
کلید اصلی مدل نسخه باید یک کلید مرکب (Composite Primary Key) از چهار جزء باشد: (lesson, major, minor, patch).
| نام | نوع |
|---|---|
lesson |
ارجاع به Lesson |
major |
عدد صحیح نامنفی |
minor |
عدد صحیح نامنفی |
patch |
عدد صحیح نامنفی |
label |
رشتهای با حداکثر طول ۲۵۵ |
title |
رشتهای با حداکثر طول ۲۵۵ |
content |
متن طولانی |
color |
رشتهای با حداکثر طول ۷ |
created_at |
تاریخ-زمان (ثبت خودکار زمان ایجاد) |
- با حذف هر درسنامه (
Lesson) باید نسخههای (Release) مرتبط با آن نیز حذف شوند. - با استفاده از فیلد
releasesاز طرف مدلLessonمیتوان به نسخههای مربوط به آن درسنامه دسترسی پیدا کرد. - فیلد
contentاز محتوای Markdown پشتیبانی میکند؛ بنابراین یک متن راهنمای آن برابر"Markdown content"است: - فیلد
colorبهطور پیشفرض (در صورت خالی بودن) یک رنگ تصادفی هگز با استفاده از تابعrandom_hex_colorموجود در فایلutils/utility.pyمیسازد و در خود ذخیره میکند. - ترتیب پیشفرض این مدل از بزرگ به کوچک بر اساس (major, minor, patch) و سپس زمان ایجاد (
created_at) است. - رشتهی نمایشی پیشفرض این مدل به فرمت زیر است:
Title [MAJOR.MINOR.PATCH]
متدto_semver
این متد، یک نمونهٔ از دیتاکلاس SemVer (موجود در فایل از utils/semver.py) با دادههای معتبر ساخته و برگردانده میشود که حاوی (major, minor, patch) نسخهی فعلی است (از این متد در ویوها استفاده خواهد شد).
def to_semver(self) -> SemVer:
pass
متدversion_str
این متد، رشتهای به فرمت MAJOR.MINOR.PATCH و حاوی اطلاعات صحیح را برمیگرداند.
def version_str(self):
pass
فایل utils/models.py
utils/models.pyدر این فایل یک چارچوب عمومی برای حذف نرم (Soft Delete) پیادهسازی میکنیم تا بهجای حذف دائمی رکوردها، زمان حذف آنها را در فیلد deleted_at ذخیره کنیم. بدین ترتیب، کوئریهای عادی رکوردهای حذفشده را نمیبینند و در صورت نیاز میتوان آنها را بازیابی یا بهطور دائمی حذف کرد.
کلاس SoftDeleteQuerySet
یک QuerySet سفارشی که عملیاتهای حذف یا بازیابی دستهای و فیلترهای کمکی را فراهم میکند.
class SoftDeleteQuerySet(QuerySet):
pass
متد delete
بهجای حذف رکوردها، مقدار deleted_at را برای همهٔ نتایج روی timezone.now() بهروزرسانی میکند (حذف نرم کوئریست).
def delete(self):
pass
متد hard_delete
با فراخوانی این متد باید رکوردها از پایگاهداده به طور کامل با مکانیزم پیشفرض جنگو پاک شوند (حذف دائمی کوئریست).
def hard_delete(self):
pass
متد restore
فیلد deleted_at را برای همهی نتایج None میکند (بازیابی کوئریست).
def restore(self):
pass
متد alive
فقط رکوردهایی که حذف نشدهاند را برمیگرداند (deleted_at در آنها None است).
def alive(self):
pass
متد dead
فقط رکوردهای حذفشده را برمیگرداند (دارای مقدار در deleted_at).
def dead(self):
pass
کلاس SoftDeleteManager
یک Manager پیشفرض که همواره رکوردهای حذفنشده را برمیگرداند و متدهایی برای دسترسی به رکوردهای حذفشده، بازیابی و حذف دائمی فراهم میکند. این منیجر بهصورت پیشفرض با متد .all() فقط نتایج حذفنشده را برمیگرداند (deleted_at برابر None است).
class SoftDeleteManager(models.Manager):
pass
متد with_deleted
یک کوئریست شامل همه رکوردها (موجود و حذفشده) را برمیگرداند.
def with_deleted(self):
pass
متد dead
یک کوئریست حاوی تنها رکوردهای حذفشده را برمیگرداند.
def dead(self):
pass
متد restore
بازیابی دستهای همهی رکوردها (روی کوئریست برگرداندهشده توسط with_deleted() اعمال میشود).
def restore(self):
pass
متد hard_delete
با فراخوانی این متد باید رکوردهای انتخاب شده از پایگاهداده به طور کامل با مکانیزم پیشفرض جنگو پاک شوند.
def hard_delete(self):
pass
کلاس SoftDeleteBaseModel
کلاسی انتزاعی (Abstract) که فیلد و رفتارهای حذف نرم را به مدلهایی که از آن ارثبری میکنند اضافه میکند.
class SoftDeleteBaseModel(models.Model):
pass
فیلد deleted_at
این فیلد از نوع DateTimeField است و میتواند خالی باشد. وظیفهی آن نگهداری زمان حذف شدن رکورد است.
فیلد objects
این فیلد از یک شیء از کلاس SoftDeleteManager استفاده میکند و منیجر پیشفرض مدلها است.
متد delete
این متد وظیفهی حذف نرمِ شیء را بر عهده دارد. مقداردهی deleted_at با مقدار زمان کنونی آپدیت شود.
def delete(self):
pass
متد hard_delete
این متد وظیفهی حذف واقعی رکورد از پایگاهداده را بر عهده دارد و رکورد موردنظر را به صورت کامل حذف میکند.
def hard_delete(self):
pass
متد restore
این متد وظیفهی بازیابی شیء را برعهده دارد که با تنظیم deleted_at با مقدار None و ذخیره آن اینکار را انجام میدهد؛ در نهایت همان شیء بازیابیشده را برمیگرداند.
def restore(self):
pass
پس از پیادهسازی این کلاس، هر مدلی که میخواهید حذف نرم داشته باشد، کافی است از SoftDeleteBaseModel ارثبری کند. در این حالت، رفتارهای بالا بهصورت خودکار در دسترس خواهند بود.
فایل mixins.py
mixins.pyمیکسین مالکیت (OwnerRequiredMixin)
در این فایل یک کلاس Mixin با نام OwnerRequiredMixin بسازید که فقط به مالک درسنامه اجازهی دسترسی بدهد. این میکسینکلاس در ویوهایی که شیء (از نوع Lesson یا Release) را میخوانند استفاده شدهاست و باید عملیات مربوط به کنترل دسترسی کاربران را بهدرستی انجام دهد.
class OwnerRequiredMixin(UserPassesTestMixin):
def test_func(self):
pass
- اگر شیء از نوع درسنامه (Lesson) باشد، فقط در صورتی اجازهی دسترسی صادر شود که شناسهی نویسندهی درسنامه با شناسهٔ کاربر فعلی برابر باشد.
- اگر شیء از نوع نسخه (Release) بود، فقط در صورتی اجازهی دسترسی داده شود که نویسندهی درسنامهی مربوط به آن نسخه با کاربر فعلی برابر باشد.
- اگر شیء ورودی از هیچکدام از انواع بالا نبود، نیازی به انجام عملیات اعتبارسنجی نیست.
نکته: این میکسین باید با مکانیزم استاندارد مجوزها در جنگو پیادهسازی شود تا در صورت صدق نکردن شرایط بالا، اجازهی دسترسی داده نشود و ریسپانسی با کد وضعیت ۴۰۳ برگردانده شود.
فایل forms.py
forms.pyدر این فایل باید دو عدد فرم با استفاده از ModelForm پیادهسازی کنید. متد سازندهی فرمها در پروژهی اولیه قرار داده شده و نباید آن را تغییر دهید. سایر بخشهای این فرمها، از جمله تعریف فیلدها و شخصیسازی ویجتهای نمایشی بر عهدهی شماست.
فرم ReleaseForm
هدف از این فرم، ساخت نسخهی اولیه است و بدون دخالت کاربر است؛ نسخهی اولیه در ویوها همواره روی 0.1.0 تنظیم میشود. از طرفی تفاوت آن با فرم بعدی در همین مورد است؛ بهطوری که در این فرم نسخه به صورت خودکار مقداردهی میشود اما در فرم دوم، نحوهی افزایش نسخه را کاربر مشخص میکند. بنابراین برای پیادهسازی فرم بعدی، ابتدا باید این فرم را بهدرستی پیادهسازی کرده باشید.
فیلد title
- نوع فیلد: ورودی متنی با پاسخ کوتاه
- برچسب (label):
عنوان
- متن placeholder:
مثال: معرفی اولیهٔ درسنامه
- اجباری/اختیاری: اجباری
- پیام خطا:
پیام خطای خالی بودن:
وارد کردن عنوان الزامی است.
پیام خطای طول غیرمجاز:
عنوان بیش از حد بلند است.
فیلد content
- نوع فیلد/ویجت: ورودی متنی با پاسخ بلند (
Textarea) - برچسب (label):
محتوا (Markdown)
- متن راهنما (help text):
از Markdown برای قالببندی تیترها، کد و فهرستها استفاده کنید.
- متن placeholder:
محتوای نسخه را با Markdown بنویسید…
- اجباری/اختیاری: اجباری
- پیام خطا:
پیام خطای نبود مقدار:
محتوای نسخه نمیتواند خالی باشد.
فیلد color
- نوع فیلد/ویجت: رنگ به فرمت کد هگزادسیمال (
ColorInput) - برچسب (label):
رنگ
- متن راهنما (help text):
یک رنگ هگز مانند #22C55E انتخاب کنید.
- متن placeholder:
محتوای نسخه را با Markdown بنویسید…
- اجباری/اختیاری: اختیاری
- پیام خطا:
پیام خطای مقدار نامعتبر:
قالب رنگ نامعتبر است (مثلاً #22C55E).
فیلد label
- نوع فیلد/ویجت: ورودی متنی با پاسخ کوتاه
- برچسب (label):
برچسب
- متن راهنما (help text):
نوع تغییرات این نسخه را مشخص کنید (feature، fix یا breaking).
- متن placeholder:
مثال: feature / fix / breaking
- اجباری/اختیاری: اجباری
- پیام خطا: پیام خطای نبود مقدار:
برچسب را وارد کنید (مثلاً feature یا fix).
پیام طول زیاد:
برچسب بیش از حد بلند است.
فیلد make_active
فیلد جدیدی که خودتان باید به فرم موردنظر اضافه کنید و جزء فیلدهای مدل Release نیست.
- نوع فیلد/ویجت: چکباکس (
BooleanField) - برچسب (label):
فعالسازی پس از ذخیره؟
- متن راهنما (help text):
پس از ذخیره، این نسخه بهعنوان نسخهٔ فعال نمایش داده میشود.
- اجباری/اختیاری: اجباری
- پیشفرض: فعال (
True)
فرم NewVersionForm
این فرم، نصخهٔ جدید را همراه با نوع افزایش نسخه مشخص میکند؛ درواقع فیلدهای این فرم مانند فیلدهای ReleaseForm است بهعلاوهی یک انتخابگر برای نوع افزایش نسخه. برای پیادهسازی این فرم باید از فرم ReleaseForm ارثبری کنید.
فیلد bump
- نوع فیلد/ویجت: انتخابی (
ChoiceField) تک گزینه بههمراه ویجتSelectو کلاس"input" - برچسب (label):
نوع افزایش نسخه
- متن راهنما (help text):
نوع نسخهگذاری مطابق Semantic Versioning انتخاب شود.
- اجباری/اختیاری: اجباری
- پیام خطا: پیام خطای نبود مقدار:
انتخاب نوع افزایش نسخه الزامی است.
- گزینهها: گزینههای این فیلد بهصورت لیستی از تاپلهای دو عضوی به نام
BUMP_CHOICESدر فایلutils/utility.pyقرار داده شدهاند. که در آن- مقدار
patchبه معنای «تغییرات جزئی بدون شکستن سازگاری» است. - مقدار
minorبه معنای «افزودن قابلیت بدون شکستن سازگاری» است. - مقدار
majorبه معنای «تغییرات بزرگ/احتمالاً ناسازگار» است.
- مقدار


زیرمسئلهها
سیستم داوری برای این سوال به زیرمسئلههای زیر برای نمرهدهی تقسیمبندی شده است که میتوانید امتیاز مربوط به هر کدام را در جدول زیر مشاهده کنید. زیرمسئلههای این جدول ابتدا بر اساس اولویت و پیشنیازی پیادهسازی و سپس بر اساس امتیاز آنها مرتبسازی شدهاند. لذا پیشنهاد میشود در پیادهسازی از زیرمسئلهی ابتدایی آغاز کنید.
| زیرمسئله | امتیاز |
|---|---|
مدلهای Lesson و Release |
75 |
مکانیزم Soft Delete |
100 |
میکسین OwnerRequiredMixin |
25 |
فرم ReleaseForm |
70 |
فرم NewVersionForm |
30 |
آنچه باید آپلود کنید
-
توجه: پس از اعمال تغییرات، کل پروژه را Zip کرده و آپلود کنید. همانند پروژهی اولیه در فایل زیپشده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.
-
توجه: تنها فایلهایی که در ساختار پروژه مشخص شدهاند، در سیستم داوری مورد پذیرش قرار خواهد گرفت و سایر تغییرات در سایر فایلها بیتأثیر خواهند بود.
-
توجه: متنهای نمونه در مدلها و فرمها باید دقیقاً برابر مقادیر گفتهشده باشند؛ در غیر این صورت نمرهی کامل دریافت نخواهید کرد.
ارسال پاسخ برای این سؤال