یک شرکت نرمافزاری میخواهد برای خود یک سیستم مدیریت پروژه بنویسد. بدین منظور سایتی با قابلیتهای زیر طراحی شده است:
+ در این سایت چندین پروژه میتواند وجود داشته باشد.
+ هر پروژه میتواند چندین عضو داشته باشد.
+ هر کاربر میتواند در چند پروژه در نقشهای مختلف عضویت داشته باشد.
## پروژه اولیه
پروژه اولیه را از [این لینک](/problemset/assignments/4367/download_problem_initial_project/21213/) دانلود کنید. ساختار این پروژه به شرح زیر است:
```
src
├── manage.py
├── ProjectManager
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── projects
│ ├── admin.py
│ ├── apps.py
│ ├── <mark class="yellow" title="شما مجاز به تغییر این فایل هستید."> > decorators.py < </mark>
│ ├── <mark class="yellow" title="شما مجاز به تغییر این فایل هستید."> > models.py < </mark>
│ ├── <mark class="yellow" title="شما مجاز به تغییر این فایل هستید."> > activation_view.py < </mark>
│ ├── templates
│ │ └── projects
│ │ └── project_home.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── requirements.txt
```
## جزئیات
مدلِ`ProjectMembership` که در فایل *models.py* نوشته شده است، عضویت یک فرد را در یک پروژه به همراه نقش او در پروژه نشان میدهد.
در فایل views.py نیز چندین *view* وجود دارد که اعمال مختلف قابل انجام روی پروژه را پیادهسازی کرده است.
این سایت بدین شکل کار میکند که همواره **فقط یکی** از پروژههای کاربر به عنوان "پروژه فعلی" او انتخاب شده است که از طریق صفحهی `index` نیز قادر به مشاهده این پروژه هستیم و تمامی اعمال مختلف مانند حذف پروژه و غیره، روی همین پروژه فعلی انجام میشوند.
نکته: یک کاربر در یک پروژه فقط یک بار و با یک نقش میتواند عضو باشد. بنابراین واژههای "پروژهی فعلی" و "عضویت فعلی" به یک معنا هستند.
حال از شما میخواهیم تغییرات زیر را در این پروژه انجام دهید.
<details class="green">
<summary> **۱. نوشتن** `activation_view` </summary>
این view شناسهی یک **پروژه** را به عنوان ورودی گرفته و آن را به عنوان پروژه فعلی انتخاب میکند. بدین منظور یک field در مدل `ProjectMembership` به نام `is_current` در نظر گرفته شده است که باید آن را مقداردهی کرده (`is_current=True`) و برای سایر عضویتها (ProjectMembership) نیز این field را `False` کند.
+ توجه کنید ممکن است به هر دلیلی مانند *race condition*، از قبل برای یک کاربر چندین عضویت با `is_current=True` وجود داشته باشد. بعد از اجرای این کد باید اطمینان حاصل شود که برای این user، فقط یک عضویت فعلی وجود دارد.
+ در صورتی که کاربر فعلی در پروژه با شناسه مورد نظر عضویت نداشت یا به کل پروژهای با شناسه مورد نظر وجود نداشت، باید یک پاسخ با استاتوسکد ۴۰۴ نشان دهید.
+ بعد از این که پروژه مورد نظر با موفقیت به عنوان پروژه فعلی برای کاربر فعلی انتخاب شد، این view باید به صفحه `index` (یعنی آدرس `/project`) redirect شود. در صفحهی index باید بتوانیم پروژه انتخاب شده را ببینیم.
</details>
<details class="green">
<summary> **۲. نوشتن تابع** `has_permission` </summary>
برای این بخش از سوال باید یک تابع به نام `has_permission(action)` در مدل `ProjectMembership` تعریف کنید. این تابع یک رشته را به عنوان ورودی دریافت میکند و بر اساس `role` یک *boolean* بازمیگراند. انواع roleها در این مدل مشخصاند. شما باید بر اساس جدول زیر به ازای هر رشته ورودی، خروجی درست را بازگردانید. توجه کنید که ورودی تابع به صورت [snake_case](https://en.wikipedia.org/wiki/Snake_case) میباشد.
جدول زیر دسترسیهای نقشهای مختلف در این پروژه را مشخص میکند.
| Action | Guest | Reporter | Developer | Master | Owner |
|:----- |:------:|:--------:|:---------:|:------:|:------------------:|
| create_new_issue | ✓ | ✓ | ✓ | ✓ | ✓ |
| leave_comments | ✓ | ✓ | ✓ | ✓ | ✓ |
| pull_project_code | | ✓ | ✓ | ✓ | ✓ |
| assign_issues_and_merge_requests | | ✓ | ✓ | ✓ | ✓ |
| see_a_list_of_merge_requests | | ✓ | ✓ | ✓ | ✓ |
| manage_merge_requests | | | ✓ | ✓ | ✓ |
| create_new_branches | | | ✓ | ✓ | ✓ |
| add_new_team_members | | | | ✓ | ✓ |
| push_to_protected_branches | | | | ✓ | ✓ |
| switch_visibility_level | | | | | ✓ |
| remove_project | | | | | ✓ |
| force_push_to_protected_branches | | | | | |
</details>
<details class="green">
<summary> **۳. نوشتن** `Decorator` </summary>
یک فایل به نام `decorators.py` نوشته شده است. شما باید در این فایل یک decorator بنویسید. این decorator در بالای همهی viewهای موجود در فایل `views.py` قرار داده شده است. این decorator به شکل `@projects_panel()` در بالای viewها استفاده میشود که یک آرگومان اختیاری به نام `permissions` دریافت میکند. در صورتی که این آرگومان به decorator پاس داده شود باید به صورت یک لیست باشد. برای مثال:
```python sample.py
@projects_panel(permissions=['remove_project'])
def remove_project(request):
request.project.delete()
return redirect('index')
```
در این decorator باید کارهای زیر را به ترتیب انجام دهید:
۱. ابتدا باید چک کنید که آیا برای کابر مورد نظر پروژهای وجود دارد یا خیر.
+ اگر پروژهای وجود نداشت یک خطای ۴۰۴ با متن `"No projects found"` بازگردانید.
+ در غیر این صورت، `request.memberships` را برابر `queryset` عضویتهای کاربر قرار دهید.
۲. سپس باید چک کنید که آیا یک پروژه به عنوان پروژه فعلی برای کاربر وجود دارد یا خیر.
+ اگر پروژهای به عنوان پروژه فعلی وجود نداشت، عضویتی با کمترین id را به عنوان عضویت فعلی او انتخاب کنید.
سپس `request.current_membership`را برابر عضویت فعلی کاربر قرار دهید.
۳. `request.project` را برابر پروژه فعلی کاربر قرار دهید.
۴. اگر آرگومان `permissions` وجود داشت باید بررسی کنید که آیا کاربر در عضویت فعلی خود، دسترسیهای لازم را دارد یا خیر. برای این کار میتوانید از تابع `has_permission` که در قسمت قبل نوشتید استفاده کنید.
+ اگر دسترسی وجود نداشت باید یک خطای ۴۰۳ (Forbidden) بازگردانید.
</details>
## تست نمونه
در فایلهای اولیهای که دانلود کردید یکسری داده اولیه به عنوان نمونه قرار داده شده است. میتوانید قبل از فرستادن سوال در سایت، این تستها را ببینید تا با نحوه داوری ما آشنا شوید و از پاسخ خود اطمینان حاصل نمایید.
تستها را میتوانید با دستور زیر اجرا کنید:
```shell terminal terminal
python manage.py test
```
## نکات
+ شما تنها مجاز به تغییر در `projects/decorators.py`، `projects/models.py` و `projects/activation_view.py`هستید.
اگر تغییری در سایر فایلها ایجاد کنید، این تغییرات نادیده گرفته خواهد شد.
+ در فایل `models.py` میتوانید به تعداد دلخواهتان، به هر مدلی تابع اضافه کنید. اما فیلدها و نامهای مدلهای موجود را تغییر ندهید.
+ فراموش نکنید که میتوانید با مطالعهی `testsample.py` با روش تست کردن آشنا شوید.
## نحوه ارسال
یک فایل _ZIP_ حاوی همهی فایلهای پروژه، آپلود کنید. نام فایل _ZIP_ اهمیتی ندارد.