سال‌ها پیش، باقر، ممجواد و مصطفی سه دانشجوی جوان مهندسی کامپیوتر دانشگاه صنعتی شریف، آغازگری بودند بر آغازگرانی که بعد‌ها کوئرا (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

باید دو مدل 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

در این فایل یک چارچوب عمومی برای حذف نرم (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

میکسین مالکیت (OwnerRequiredMixin)

در این فایل یک کلاس Mixin با نام OwnerRequiredMixin بسازید که فقط به مالک درسنامه اجازه‌ی دسترسی بدهد. این میکسین‌کلاس در ویوهایی که شیء (از نوع Lesson یا Release) را می‌خوانند استفاده شده‌است و باید عملیات مربوط به کنترل دسترسی کاربران را به‌درستی انجام دهد.

class OwnerRequiredMixin(UserPassesTestMixin):
    def test_func(self):
        pass
  • اگر شیء از نوع درسنامه (Lesson) باشد، فقط در صورتی اجازه‌ی دسترسی صادر شود که شناسه‌ی نویسنده‌ی درسنامه با شناسهٔ کاربر فعلی برابر باشد.
  • اگر شیء از نوع نسخه (Release) بود، فقط در صورتی اجازه‌ی دسترسی داده شود که نویسنده‌ی درسنامه‌ی مربوط به آن نسخه با کاربر فعلی برابر باشد.
  • اگر شیء ورودی از هیچ‌کدام از انواع بالا نبود، نیازی به انجام عملیات اعتبارسنجی نیست.

نکته: این میکسین باید با مکانیزم استاندارد مجوز‌ها در جنگو پیاده‌سازی شود تا در صورت صدق نکردن شرایط بالا، اجازه‌ی دسترسی داده نشود و ریسپانسی با کد وضعیت ۴۰۳ برگردانده شود.

فایل 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 کرده و آپلود کنید. همانند پروژه‌ی اولیه در فایل زیپ‌شده نباید کد در پوشه‌ی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمره‌ای دریافت نخواهید کرد.

  • توجه: تنها فایل‌هایی که در ساختار پروژه مشخص شده‌اند، در سیستم داوری مورد پذیرش قرار خواهد گرفت و سایر تغییرات در سایر فایل‌ها بی‌تأثیر خواهند بود.

  • توجه: متن‌های نمونه‌ در مدل‌ها و فرم‌ها باید دقیقاً برابر مقادیر گفته‌شده باشند؛ در غیر این صورت نمره‌ی کامل دریافت نخواهید کرد.

ارسال پاسخ برای این سؤال
فایلی انتخاب نشده است.