Skip to content

Commit edf5561

Browse files
authored
Merge pull request #537 from PROCOLLAB-github/dev
Dev
2 parents bb803cf + 1897442 commit edf5561

19 files changed

Lines changed: 1681 additions & 145 deletions

news/permissions.py

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.contrib.auth import get_user_model
2+
from django.shortcuts import get_object_or_404
23
from rest_framework import permissions
3-
from rest_framework.exceptions import NotFound
44
from rest_framework.permissions import SAFE_METHODS
55

66
from partner_programs.models import PartnerProgram
@@ -10,47 +10,37 @@
1010

1111

1212
class IsNewsCreatorOrReadOnly(permissions.BasePermission):
13-
def has_object_permission(self, request, view, obj):
14-
"""
15-
read/update/delete permission
16-
currently can only be updated/deleted in admin panel
17-
"""
13+
def has_permission(self, request, view):
1814
if request.method in SAFE_METHODS:
1915
return True
20-
if (
21-
isinstance(obj.content_object, Project)
22-
and obj.content_object.leader == request.user
23-
):
24-
return True
25-
if isinstance(obj.content_object, User) and obj.content_object == request.user:
26-
return True
27-
if isinstance(obj.content_object, PartnerProgram):
28-
# TODO: implement
29-
pass
16+
17+
if view.kwargs.get("project_pk"):
18+
project = get_object_or_404(Project, pk=view.kwargs["project_pk"])
19+
return request.user == project.leader
20+
21+
if view.kwargs.get("user_pk"):
22+
user = get_object_or_404(User, pk=view.kwargs["user_pk"])
23+
return request.user == user
24+
25+
if view.kwargs.get("partnerprogram_pk"):
26+
program = get_object_or_404(
27+
PartnerProgram, pk=view.kwargs["partnerprogram_pk"]
28+
)
29+
return program.is_manager(request.user)
30+
3031
return False
3132

32-
def has_permission(self, request, view):
33-
"""
34-
Creation permission
35-
Currently can only be created via admin panel
36-
"""
33+
def has_object_permission(self, request, view, obj):
3734
if request.method in SAFE_METHODS:
3835
return True
3936

40-
if view.kwargs.get("project_pk"):
41-
try:
42-
project = Project.objects.get(pk=view.kwargs["project_pk"])
43-
if request.method in SAFE_METHODS or (request.user == project.leader):
44-
return True
45-
except Project.DoesNotExist:
46-
raise NotFound
37+
if isinstance(obj.content_object, Project):
38+
return obj.content_object.leader == request.user
4739

48-
if view.kwargs.get("user_pk"):
49-
try:
50-
user = User.objects.get(pk=view.kwargs["user_pk"])
51-
if request.method in SAFE_METHODS or (request.user == user):
52-
return True
53-
except User.DoesNotExist:
54-
raise NotFound
40+
if isinstance(obj.content_object, User):
41+
return obj.content_object == request.user
42+
43+
if isinstance(obj.content_object, PartnerProgram):
44+
return obj.content_object.is_manager(request.user)
5545

5646
return False

news/views.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@
22
from django.shortcuts import get_object_or_404
33
from rest_framework import generics, status
44
from rest_framework.permissions import IsAuthenticated
5-
from rest_framework.response import Response
65
from rest_framework.request import Request
6+
from rest_framework.response import Response
77

8-
from core.serializers import SetViewedSerializer, SetLikedSerializer
8+
from core.serializers import SetLikedSerializer, SetViewedSerializer
99
from core.services import add_view, set_like
1010
from news.mixins import NewsQuerysetMixin
1111
from news.models import News
1212
from news.pagination import NewsPagination
1313
from news.permissions import IsNewsCreatorOrReadOnly
1414
from news.serializers import (
15-
NewsListSerializer,
1615
NewsDetailSerializer,
1716
NewsListCreateSerializer,
17+
NewsListSerializer,
1818
)
19+
from partner_programs.models import PartnerProgram
1920
from projects.models import Project
2021

2122
User = get_user_model()
@@ -44,7 +45,12 @@ def post(self, request: Request, *args, **kwargs) -> Response:
4445
NewsDetailSerializer(news).data, status=status.HTTP_201_CREATED
4546
)
4647

47-
# creating partner program news, not implemented yet, return 400
48+
if kwargs.get("partnerprogram_pk"):
49+
program = get_object_or_404(PartnerProgram, pk=kwargs["partnerprogram_pk"])
50+
news = News.objects.add_news(program, **data)
51+
return Response(
52+
NewsDetailSerializer(news).data, status=status.HTTP_201_CREATED
53+
)
4854
return Response(status=status.HTTP_400_BAD_REQUEST)
4955

5056
def get(self, request: Request, *args, **kwargs) -> Response:

partner_programs/admin.py

Lines changed: 115 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,43 @@
1-
import tablib
21
import re
32
import urllib.parse
3+
4+
import tablib
45
from django.contrib import admin
56
from django.db.models import QuerySet
6-
from django.http import HttpResponse, HttpRequest
7+
from django.http import HttpRequest, HttpResponse
78
from django.urls import path
89
from django.utils import timezone
910

10-
from mailing.views import MailingTemplateRender
1111
from core.utils import XlsxFileToExport
12-
from partner_programs.models import PartnerProgram, PartnerProgramUserProfile
12+
from mailing.views import MailingTemplateRender
13+
from partner_programs.models import (
14+
PartnerProgram,
15+
PartnerProgramField,
16+
PartnerProgramFieldValue,
17+
PartnerProgramMaterial,
18+
PartnerProgramProject,
19+
PartnerProgramUserProfile,
20+
)
21+
from partner_programs.services import ProjectScoreDataPreparer
1322
from project_rates.models import Criteria, ProjectScore
1423
from projects.models import Project
15-
from partner_programs.services import ProjectScoreDataPreparer
24+
25+
26+
class PartnerProgramMaterialInline(admin.StackedInline):
27+
model = PartnerProgramMaterial
28+
extra = 1
29+
fields = ("title", "url", "file")
30+
readonly_fields = ("datetime_created", "datetime_updated")
31+
32+
33+
class PartnerProgramFieldInline(admin.TabularInline):
34+
model = PartnerProgramField
35+
extra = 0
1636

1737

1838
@admin.register(PartnerProgram)
1939
class PartnerProgramAdmin(admin.ModelAdmin):
40+
inlines = [PartnerProgramMaterialInline, PartnerProgramFieldInline]
2041
list_display = ("id", "name", "tag", "city", "datetime_created")
2142
list_display_links = (
2243
"id",
@@ -32,7 +53,7 @@ class PartnerProgramAdmin(admin.ModelAdmin):
3253
)
3354
list_filter = ("city",)
3455

35-
filter_horizontal = ("users",)
56+
filter_horizontal = ("users", "managers")
3657
date_hierarchy = "datetime_started"
3758

3859
def get_queryset(self, request: HttpRequest) -> QuerySet[PartnerProgram]:
@@ -54,7 +75,9 @@ def change_view(self, request, object_id, form_url="", extra_context=None):
5475
"partner_programs/admin/program_manager_change_form.html"
5576
)
5677
else:
57-
self.change_form_template = "partner_programs/admin/programs_change_form.html"
78+
self.change_form_template = (
79+
"partner_programs/admin/programs_change_form.html"
80+
)
5881

5982
return super().change_view(request, object_id, form_url, extra_context)
6083

@@ -145,7 +168,7 @@ def get_export_file(self, partner_program: PartnerProgram):
145168

146169
binary_data = response_data.export("xlsx")
147170
file_name = (
148-
f'{partner_program.name} {timezone.now().strftime("%d-%m-%Y %H:%M:%S")}'
171+
f"{partner_program.name} {timezone.now().strftime('%d-%m-%Y %H:%M:%S')}"
149172
)
150173
response = HttpResponse(
151174
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
@@ -155,22 +178,26 @@ def get_export_file(self, partner_program: PartnerProgram):
155178
return response
156179

157180
def get_export_rates_view(self, request, object_id):
158-
rates_data_to_write: list[dict] = self._get_prepared_rates_data_for_export(object_id)
181+
rates_data_to_write: list[dict] = self._get_prepared_rates_data_for_export(
182+
object_id
183+
)
159184

160185
xlsx_file_writer = XlsxFileToExport()
161186
xlsx_file_writer.write_data_to_xlsx(rates_data_to_write)
162187
binary_data_to_export: bytes = xlsx_file_writer.get_binary_data_from_self_file()
163188
xlsx_file_writer.delete_self_xlsx_file_from_local_machine()
164189

165190
encoded_file_name: str = urllib.parse.quote(
166-
f'{PartnerProgram.objects.get(pk=object_id).name}_оценки {timezone.now().strftime("%d-%m-%Y %H:%M:%S")}'
191+
f"{PartnerProgram.objects.get(pk=object_id).name}_оценки {timezone.now().strftime('%d-%m-%Y %H:%M:%S')}"
167192
f".xlsx"
168193
)
169194
response = HttpResponse(
170195
binary_data_to_export,
171196
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
172197
)
173-
response["Content-Disposition"] = f'attachment; filename*=UTF-8\'\'{encoded_file_name}'
198+
response["Content-Disposition"] = (
199+
f"attachment; filename*=UTF-8''{encoded_file_name}"
200+
)
174201
return response
175202

176203
def _get_prepared_rates_data_for_export(self, program_id: int) -> list[dict]:
@@ -179,19 +206,22 @@ def _get_prepared_rates_data_for_export(self, program_id: int) -> list[dict]:
179206
Columns example:
180207
ФИО|Email|Регион_РФ|Учебное_заведение|Название_учебного_заведения|Класс_курс|Фамилия эксперта|**criteria
181208
"""
182-
criterias = Criteria.objects.filter(partner_program__id=program_id).select_related("partner_program")
209+
criterias = Criteria.objects.filter(
210+
partner_program__id=program_id
211+
).select_related("partner_program")
183212
scores = (
184-
ProjectScore.objects
185-
.filter(criteria__in=criterias)
213+
ProjectScore.objects.filter(criteria__in=criterias)
186214
.select_related("user", "criteria", "project")
187215
.order_by("project", "criteria")
188216
)
189-
user_programm_profiles = (
190-
PartnerProgramUserProfile.objects
191-
.filter(partner_program__id=program_id)
192-
.select_related("user")
217+
user_programm_profiles = PartnerProgramUserProfile.objects.filter(
218+
partner_program__id=program_id
219+
).select_related("user")
220+
projects = (
221+
Project.objects.filter(scores__in=scores)
222+
.select_related("leader")
223+
.distinct()
193224
)
194-
projects = Project.objects.filter(scores__in=scores).select_related("leader").distinct()
195225

196226
# To reduce the number of DB requests.
197227
user_profiles_dict: dict[int, PartnerProgramUserProfile] = {
@@ -203,7 +233,9 @@ def _get_prepared_rates_data_for_export(self, program_id: int) -> list[dict]:
203233

204234
prepared_projects_rates_data: list[dict] = []
205235
for project in projects:
206-
project_data_preparer = ProjectScoreDataPreparer(user_profiles_dict, scores_dict, project.id, program_id)
236+
project_data_preparer = ProjectScoreDataPreparer(
237+
user_profiles_dict, scores_dict, project.id, program_id
238+
)
207239
full_project_rates_data: dict = {
208240
**project_data_preparer.get_project_user_info(),
209241
**project_data_preparer.get_project_expert_info(),
@@ -242,3 +274,66 @@ def get_form(self, request, obj=None, **kwargs):
242274
form = super().get_form(request, obj, **kwargs)
243275
form.base_fields["project"].required = False
244276
return form
277+
278+
279+
@admin.register(PartnerProgramMaterial)
280+
class PartnerProgramMaterialAdmin(admin.ModelAdmin):
281+
list_display = ("title", "program", "short_url", "has_file", "datetime_created")
282+
list_filter = ("program",)
283+
search_fields = ("title", "program__name")
284+
285+
readonly_fields = ("datetime_created", "datetime_updated")
286+
287+
def short_url(self, obj):
288+
return obj.url[:60] if obj.url else "—"
289+
290+
short_url.short_description = "Ссылка"
291+
292+
def has_file(self, obj):
293+
return bool(obj.file)
294+
295+
has_file.boolean = True
296+
has_file.short_description = "Файл"
297+
298+
299+
class PartnerProgramFieldValueInline(admin.TabularInline):
300+
model = PartnerProgramFieldValue
301+
extra = 0
302+
autocomplete_fields = ("field",)
303+
readonly_fields = ("get_display_value",)
304+
305+
def get_display_value(self, obj):
306+
return obj.value_text or "-"
307+
308+
get_display_value.short_description = "Значение"
309+
310+
311+
@admin.register(PartnerProgramProject)
312+
class PartnerProgramProjectAdmin(admin.ModelAdmin):
313+
list_display = (
314+
"id",
315+
"project",
316+
"partner_program",
317+
"datetime_created",
318+
"submitted",
319+
"datetime_submitted",
320+
)
321+
list_filter = ("partner_program",)
322+
search_fields = ("project__name", "partner_program__name")
323+
inlines = [PartnerProgramFieldValueInline]
324+
autocomplete_fields = ("project", "partner_program")
325+
326+
327+
@admin.register(PartnerProgramField)
328+
class PartnerProgramFieldAdmin(admin.ModelAdmin):
329+
list_display = (
330+
"id",
331+
"partner_program",
332+
"name",
333+
"label",
334+
"field_type",
335+
"is_required",
336+
"show_filter",
337+
)
338+
list_filter = ("partner_program",)
339+
search_fields = ("name", "label", "help_text")

0 commit comments

Comments
 (0)