Skip to content

Commit

Permalink
Merge pull request #87 from volunteers-for-city-projects/incomes
Browse files Browse the repository at this point in the history
Incomes
  • Loading branch information
MrRuzal authored Oct 26, 2023
2 parents 492cf82 + d6b2886 commit 99c6273
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 48 deletions.
38 changes: 22 additions & 16 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,57 +152,57 @@ def validate_dates(self, start, end, application):
raise serializers.ValidationError(
'Дата окончания мероприятия должна быть позже даты начала.'
)
if not (start <= application <= end):
if not (application <= start <= end):
raise serializers.ValidationError(
'Дата подачи заявки должна быть позже или равна дате начала '
'мероприятия и позже даты начала и раньше даты окончания.'
)
return start, end, application

def validate_reception_status(
self, status_project, application_date, start_datatime, end_datatime
self, status_project, application_date, start_datetime, end_datetime
):
"""
Проверяет, что статус "Прием откликов окончен" можно устанавливать
только после указанной даты подачи заявки.
"""
now = timezone.now()

if status_project == Project.RECEPTION_OF_RESPONSES_CLOSED:
if application_date > now:
raise serializers.ValidationError(
'Статус проекта "Прием откликов окончен" можно установить'
'только после окончания подачи заявок.'
)
if status_project == Project.READY_FOR_FEEDBACK:
if not (start_datatime <= application_date <= end_datatime):
if now < start_datetime or now < application_date:
raise serializers.ValidationError(
'Статус проекта "Готов к откликам" можно установить '
'в период с даты начала мероприятия до даты подачи заявки.'
'Статус проекта "Готов к откликам" можно установить до '
'начала мероприятия и до даты подачи заявки.'
)
if status_project == Project.PROJECT_COMPLETED:
if now < end_datatime:
if now < end_datetime:
raise serializers.ValidationError(
'Статус проекта "Проект завершен" можно установить '
'только после окончания мероприятия.'
)

def validate(self, data):
start_datatime = data['start_datatime']
end_datatime = data['end_datatime']
start_datetime = data['start_datetime']
end_datetime = data['end_datetime']
application_date = data['application_date']
status_project = data.get('status_project')

self.validate_dates(start_datatime, end_datatime, application_date)
self.validate_dates(start_datetime, end_datetime, application_date)
self.validate_reception_status(
status_project, application_date, start_datatime, end_datatime
status_project, application_date, start_datetime, end_datetime
)

return data

def create(self, validated_data):
if validated_data.get('status_approve') not in (
Project.EDITING, Project.PENDING):
Project.EDITING,
Project.PENDING,
):
validated_data.pop('status_approve')
return super().create(validated_data)

Expand All @@ -212,8 +212,8 @@ class Meta:
'name',
'description',
'picture',
'start_datatime',
'end_datatime',
'start_datetime',
'end_datetime',
'application_date',
'event_purpose',
'project_tasks',
Expand Down Expand Up @@ -438,4 +438,10 @@ class VolunteerFavoriteGetSerializer(serializers.ModelSerializer):

class Meta:
model = Project
fields = ('id', 'name', 'picture', 'organization', 'status_project',)
fields = (
'id',
'name',
'picture',
'organization',
'status_project',
)
43 changes: 32 additions & 11 deletions backend/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
from rest_framework.routers import DefaultRouter

from api.views import (
AcceptIncomesView,
CityViewSet,
FeedbackCreateView,
NewsViewSet,
OrganizationViewSet,
PlatformAboutView,
ProjectCategoryViewSet,
ProjectIncomesView,
ProjectViewSet,
RejectIncomesView,
SearchListView,
SkillsViewSet,
TagViewSet,
Expand All @@ -31,19 +34,37 @@

urlpatterns = [
path('', include(router.urls)),
path('auth/me/', UserViewSet.as_view({'get': 'me'}),),
path('auth/change_password/',
UserViewSet.as_view({'post': 'set_password'}),
name='set_password'),
path('auth/reset_password/',
UserViewSet.as_view({'post': 'reset_password'}),
name='password_reset'),
path('auth/reset_password_confirm/',
UserViewSet.as_view({'post': 'reset_password_confirm'}),
name='password_reset_confirm'),
path(
'auth/me/',
UserViewSet.as_view({'get': 'me'}),
),
path(
'auth/change_password/',
UserViewSet.as_view({'post': 'set_password'}),
name='set_password',
),
path(
'auth/reset_password/',
UserViewSet.as_view({'post': 'reset_password'}),
name='password_reset',
),
path(
'auth/reset_password_confirm/',
UserViewSet.as_view({'post': 'reset_password_confirm'}),
name='password_reset_confirm',
),
path('auth/', include('djoser.urls.authtoken')),
path('platform_about/', PlatformAboutView.as_view()),
path('feedback/', FeedbackCreateView.as_view()),
path('search/', SearchListView.as_view()),
path('volunteers/profile/<int:pk>/', VolunteerProfileView.as_view()),
path('volunteers/<int:pk>/profile/', VolunteerProfileView.as_view()),
path('projects/<int:pk>/incomes/', ProjectIncomesView.as_view()),
path(
'projects/<int:project_id>/incomes/<int:income_id>/accept/',
AcceptIncomesView.as_view(),
),
path(
'projects/<int:project_id>/incomes/<int:income_id>/reject/',
RejectIncomesView.as_view(),
),
]
138 changes: 128 additions & 10 deletions backend/api/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.db.models import Count
from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters, generics, status, viewsets
Expand All @@ -19,6 +20,8 @@
Category,
Organization,
Project,
ProjectIncomes,
ProjectParticipants,
Volunteer,
VolunteerFavorite,
)
Expand All @@ -41,6 +44,7 @@
PlatformAboutSerializer,
PreviewNewsSerializer,
ProjectCategorySerializer,
ProjectIncomesSerializer,
ProjectSerializer,
SkillsSerializer,
TagSerializer,
Expand Down Expand Up @@ -88,7 +92,7 @@ class FeedbackCreateView(generics.CreateAPIView):

class ProjectViewSet(viewsets.ModelViewSet):
"""
Представление для управления проектами.
Представление для проектов.
Позволяет создавать, просматривать, обновлять и удалять проекты.
Только авторизованные пользователи-организаторы, связанные с проектом
Expand Down Expand Up @@ -141,8 +145,7 @@ def add_to(self, volunteer, project, errors):
Добавить проект в избранное.
"""
_, created = VolunteerFavorite.objects.get_or_create(
volunteer=volunteer,
project=project
volunteer=volunteer, project=project
)
if not created:
return Response(
Expand All @@ -157,8 +160,7 @@ def delete_from(self, volunteer, project, errors):
Удалить проект из избранного.
"""
cnt_deleted, _ = VolunteerFavorite.objects.filter(
volunteer=volunteer,
project=project
volunteer=volunteer, project=project
).delete()

if cnt_deleted == 0:
Expand All @@ -171,7 +173,7 @@ def delete_from(self, volunteer, project, errors):
@action(
['POST', 'DELETE'],
detail=True,
permission_classes=(IsVolunteerPermission,)
permission_classes=(IsVolunteerPermission,),
)
def favorite(self, request, **kwargs):
"""
Expand All @@ -181,12 +183,10 @@ def favorite(self, request, **kwargs):
volunteer = get_object_or_404(Volunteer, user=request.user)
if request.method == 'POST':
return self.add_to(
volunteer, project,
'Данный проект уже есть в избранном!'
volunteer, project, 'Данный проект уже есть в избранном!'
)
return self.delete_from(
volunteer, project,
'Данного проекта нет в избранном!'
volunteer, project, 'Данного проекта нет в избранном!'
)


Expand Down Expand Up @@ -268,3 +268,121 @@ def retrieve(self, request, *args, **kwargs):
)
serializer = self.get_serializer(volunteer)
return Response(serializer.data, status=status.HTTP_200_OK)


class ProjectIncomesView(generics.RetrieveAPIView):
"""
Представление для отображения информации о проекте и его заявках.
Параметры запроса:
- pk: первичный ключ проекта.
Методы:
- GET: Получает информацию о проекте и его заявках в формате JSON.
Возвращает JSON объект с информацией о проекте, включая:
- Название проекта.
- Дату начала приема заявок.
- Город проекта.
- Дату начала и окончания проекта.
- Общее количество заявок на проекте.
- Статус первой заявки на проекте.
- Дату и время подачи первой заявки.
- Информацию о волонтере, включая:
- Имя, Фамилию и Отчество волонтера.
- Навыки волонтера.
"""

permission_classes = [IsOrganizerPermission]

def get(self, request, *args, **kwargs):
project = get_object_or_404(Project, pk=self.kwargs['pk'])
project_incomes = ProjectIncomes.objects.filter(project=project)
total_incomes = project_incomes.aggregate(total_incomes=Count('id'))[
'total_incomes'
]
project_serializer = ProjectSerializer(project)
volunteers = project_incomes.values('volunteer')
volunteer_serializer = VolunteerGetSerializer(
Volunteer.objects.filter(pk__in=volunteers), many=True
)
response_data = {
'project_name': project_serializer.data['name'],
'application_date': project_serializer.data['application_date'],
'city': project_serializer.data['city'],
'start_datetime': project_serializer.data['start_datetime'],
'end_datetime': project_serializer.data['end_datetime'],
'total_incomes': total_incomes,
'volunteers': volunteer_serializer.data,
}
return Response(response_data, status=status.HTTP_200_OK)


class AcceptIncomesView(generics.DestroyAPIView):
"""
Представление для принятия заявки на участие в проекте.
Запись удаляется из заявок, волонтер добавляется в участники проекта.
"""

lookup_field = 'project_id'
queryset = ProjectIncomes.objects.all()
serializer_class = ProjectIncomesSerializer

def delete(self, request, *args, **kwargs):
instance = self.get_object()
ProjectParticipants.objects.create(
project=instance.project, volunteer=instance.volunteer
)
instance.delete()
return Response(
{'status': 'Заявка принята'}, status=status.HTTP_200_OK
)


class RejectIncomesView(generics.UpdateAPIView):
"""
Представление для отклонения заявки на участие в проекте.
Меняется статус заявки на отклонен.
"""

lookup_field = 'project_id'
serializer_class = ProjectIncomesSerializer

def get_object(self):
project_id = self.kwargs['project_id']
return ProjectIncomes.objects.filter(project_id=project_id).first()

def put(self, request, *args, **kwargs):
project_id = self.kwargs['project_id']
income_id = self.kwargs['income_id']
project_exists = Project.objects.filter(id=project_id).exists()
if project_exists:
instance = self.get_object()
if instance:
if instance.project_id == int(
project_id
) and instance.id == int(income_id):
instance.status_incomes = ProjectIncomes.REJECTED
instance.save()
return Response(
{'status': 'Заявка отклонена'},
status=status.HTTP_200_OK,
)
else:
return Response(
{'status': 'Заявка не найдена'},
status=status.HTTP_404_NOT_FOUND,
)
else:
return Response(
{'status': 'Заявка не найдена'},
status=status.HTTP_404_NOT_FOUND,
)
else:
return Response(
{'status': 'Проект не найден'},
status=status.HTTP_404_NOT_FOUND,
)
10 changes: 5 additions & 5 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,11 @@
f'"Длина поля от {MIN_LENGTH_EMAIL} до {MAX_LENGTH_EMAIL} символов"'
)

ORGANIZATION = 'Название: {}> ОГРН: {}> Город: {}'
VOLUNTEER = 'Пользователь: {}> Город: {}> Навыки: {}'
PROJECT = 'Название: {}> Организатор: {}> Категория: {}> Город: {}'
PROJECTPARTICIPANTS = 'Проект: {}> Волонтер: {}'
PROJECTINCOMES = 'Проект: {}> Волонтер: {}> Стаутс заявки {}'
ORGANIZATION = ' {} ОГРН: {} {}'
VOLUNTEER = 'Пользователь: {} Город: {} Навыки: {}'
PROJECT = 'Название: {}, Организатор: {}, Категория: {}, Город: {}'
PROJECTPARTICIPANTS = 'Проект: {} Волонтер: {}'
PROJECTINCOMES = 'Проект: {} Волонтер: {} Стаутс заявки {}'

MIN_LEN_TEXT_FEEDBACK = 10
MAX_LEN_TEXT_FEEDBACK = 750
Expand Down
Loading

0 comments on commit 99c6273

Please sign in to comment.