Skip to content

Commit

Permalink
Merge branch 'dev' into feature/profile_picture
Browse files Browse the repository at this point in the history
  • Loading branch information
Creee9 authored Feb 17, 2024
2 parents 71c650e + ff81d50 commit 53a5cee
Show file tree
Hide file tree
Showing 64 changed files with 1,099 additions and 320 deletions.
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Используйте IntelliSense, чтобы узнать о возможных атрибутах.
// Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов.
// Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: текущий файл",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
}
]
}
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"."
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
20 changes: 15 additions & 5 deletions infra/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ services:
frontend:
container_name: frontend
image: zali1813/short_tracker_frontend:v1
restart: always
# restart: always
volumes:
- ../frontend/:/app/result_build/
# depends_on:
Expand Down Expand Up @@ -40,18 +40,28 @@ services:
- media_value:/var/html/media/
# depends_on:
# - backend

redis:
image: redis:alpine
restart: on-failure
ports:
- "6379:6379"
volumes:
- redis_data:/data
bot:
container_name: bot
image: zali1813/short_tracker_bot:v1
#image: zali1813/short_tracker_bot:v1
build:
context: ../short_tracker_bot
dockerfile: Dockerfile
restart: always
env_file:
- ./.env
# depends_on:
# - backend
depends_on:
- redis

volumes:
static_value:
media_value:
postgres_data:
redis_data:

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ exclude = [

[tool.ruff.isort]
combine-as-imports = true
known-local-folder = ["api", "users", "tasks"] # Здесь указывать название локального модуля, для корректной сортировки импортов ["my_module"]
known-local-folder = ["api", "users", "tasks", "messages", "config", "handlers"] # Здесь указывать название локального модуля, для корректной сортировки импортов ["my_module"]
7 changes: 7 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[pytest]
python_paths = short_tracker/
DJANGO_SETTINGS_MODULE = short_tracker.settings
norecursedirs = env/*
addopts = -vv -p no:cacheprovider --disable-warnings
testpaths = tests/
python_files = test_*.py
Binary file modified requirements.txt
Binary file not shown.
3 changes: 2 additions & 1 deletion short_tracker/api/v1/analytics/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class PerformerAnalyticsSerializer(serializers.Serializer):
avg_time_create_date_to_done_date = serializers.CharField()
avg_time_inprogress_date_to_done_date = serializers.CharField()


class TaskAnalyticsSerializer(serializers.Serializer):
total_tasks_on_time = serializers.IntegerField()
total_tasks_with_delay = serializers.IntegerField()
Expand All @@ -27,4 +28,4 @@ def create(self, validated_data):
'total_tasks_with_delay': total_tasks_with_delay,
'performers_analytics': performers_analytics_data
}
return task_analytics_instance
return task_analytics_instance
2 changes: 1 addition & 1 deletion short_tracker/api/v1/analytics/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ def list(self, request, *args, **kwargs):
)
)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.data, status=status.HTTP_200_OK)
10 changes: 9 additions & 1 deletion short_tracker/api/v1/bot/serializer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from bot.models import AllowNotification
from django.contrib.auth import get_user_model
from rest_framework.serializers import ModelSerializer

Expand All @@ -7,13 +8,20 @@
User = get_user_model()


class AllowNotificationSerializer(ModelSerializer):
class Meta:
model = AllowNotification
fields = '__all__'


class BotSerializer(ModelSerializer):
messages = MessageSerializer(many=True)
reply = ReplySerializer(many=True)
tasks_for_user = TaskShowSerializer(many=True)
allow = AllowNotificationSerializer(many=True)

class Meta:
model = User
fields = ['messages', 'reply', 'tasks_for_user']
fields = ['messages', 'reply', 'tasks_for_user', 'allow']


3 changes: 3 additions & 0 deletions short_tracker/api/v1/bot/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ def get_queryset(self):
return User.objects.filter(id=user)


#class WebhookAPIView


1 change: 1 addition & 0 deletions short_tracker/api/v1/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def filter_is_expired(self, queryset, _, value):
)
return queryset


class TaskAnalyticsFilter(django_filters.FilterSet):
performer_id = filters.NumberFilter(
field_name='performers', method='filter_by_performer')
Expand Down
31 changes: 21 additions & 10 deletions short_tracker/api/v1/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,26 @@ def has_permission(self, request, view):
class IsLeadOrPerformerHimselfOnly(permissions.BasePermission):

def has_permission(self, request, view):
is_lead = request.user.is_lead
return request.user.id == request.data.get('performers')[0] or is_lead

is_lead = request.user.is_authenticated and request.user.is_team_lead
return is_lead or (
request.user.is_authenticated
and request.user.id == request.data.get('performers')[0]
)


class IsCreatorAndLidOrPerformerOnly(permissions.BasePermission):
"""
Задачу может менять создатель задачи и Лидер.
Исполнитель может менять только статус задачи.
"""
def has_object_permission(self, request, view, obj):

class IsCreatorOnly(permissions.BasePermission):
is_lead = request.user.is_team_lead
perf = []
for performer in obj.performers.values():
perf.append(performer.get('id'))
a = (request.user.id in perf and len(request.data) == 1
and 'status' in request.data)
b = request.user.id == obj.creator.id

def has_permission(self, request, view):
is_lead = request.user.is_lead
return [request.user.id] == request.data.get('performers') or is_lead

def has_object_permission(self, request, view, obj):
return request.user.id == obj.creator.id
return request.user.is_authenticated and (is_lead or a or b)
55 changes: 48 additions & 7 deletions short_tracker/api/v1/tasks/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from django.contrib.auth import get_user_model
from django.utils import timezone
from message.models import Message
from rest_framework import serializers

from api.v1.message.serializers import MessageSerializer
from api.v1.users.serializers import ShortUserSerializer
from tasks.models import Task
from users.models import ROLES
Expand Down Expand Up @@ -48,24 +50,25 @@ class TaskShowSerializer(TaskSerializer):
"""Serializer for show tasks."""

creator = ShortUserSerializer(read_only=True)
performers = ShortUserSerializer(many=True)
performer = ShortUserSerializer()
is_expired = serializers.SerializerMethodField()
resolved_status = serializers.SerializerMethodField()
message = serializers.SerializerMethodField()

class Meta(TaskSerializer.Meta):
fields = TaskSerializer.Meta.fields + (
'creator', 'performers', 'is_expired', 'resolved_status',
'creator', 'performer', 'is_expired', 'resolved_status', 'message',
)
read_only_fields = TaskSerializer.Meta.read_only_fields + (
'is_expired', 'resolved_status',
'is_expired', 'resolved_status', 'message',
)

def get_is_expired(self, obj):
"""
Check if the task is expired or not.
"""
return (
obj.deadline_date < timezone.now().date()
obj.deadline_date < timezone.now()
and obj.status not in ('done', 'archived')
)

Expand All @@ -78,12 +81,50 @@ def get_resolved_status(self, obj):
return RESOLVED_STATUS.get(
role, ROLES.get('employee')).get(obj.status, ())

def get_message(self, obj):
"""
Get the messages of the task.
"""
messages = Message.objects.filter(task=obj)
return MessageSerializer(messages, many=True).data


class TaskCreateSerializer(TaskSerializer):
"""Serializer for create tasks."""
performers = serializers.PrimaryKeyRelatedField(
many=True, queryset=User.objects.all(), write_only=True
)

class Meta(TaskSerializer.Meta):
fields = TaskSerializer.Meta.fields + ('performers',)
fields = TaskSerializer.Meta.fields + ('performer', 'performers',)
read_only_fields = TaskSerializer.Meta.read_only_fields + (
'performer',)

def create(self, validated_data):

performers = validated_data.pop('performers')
if len(performers) == 1:
validated_data['performer'] = performers[0]
return [Task.objects.create(**validated_data)]

tasks = []
for performer in performers:
tasks.append(Task(performer=performer, **validated_data))

Task.objects.bulk_create(tasks)
return tasks

def to_representation(self, instance):
"""
Serialize objects.
"""
cn = {'request': self.context.get('request')}
tasks_data = {
'tasks': [
TaskShowSerializer(task, context=cn).data for task in instance
],
}
return tasks_data


class TaskUpdateSerializer(TaskCreateSerializer):
Expand All @@ -93,10 +134,10 @@ def update(self, instance, validated_data):
if 'status' in validated_data:
time = STATUS_TIME.get(validated_data.get('status'))
if time:
validated_data[time] = timezone.now().date()
validated_data[time] = timezone.now()
if (
validated_data.get('status') == task_status.DONE
and timezone.now().date() <= instance.deadline_date
and timezone.now() <= instance.deadline_date
):
validated_data['get_medals'] = True
return super().update(instance, validated_data)
41 changes: 36 additions & 5 deletions short_tracker/api/v1/tasks/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@
from django.db.models import F, Q
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.filters import SearchFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from .serializers import (
TaskCreateSerializer,
TaskShowSerializer,
TaskUpdateSerializer,
)
from api.v1.filters import TaskFilter
from api.v1.permissions import IsCreatorOnly, IsLeadOrPerformerHimselfOnly
from api.v1.permissions import (
IsCreatorAndLidOrPerformerOnly,
IsLeadOrPerformerHimselfOnly,
)
from tasks.models import Task

User = get_user_model()
Expand All @@ -20,24 +25,24 @@
class TaskViewSet(viewsets.ModelViewSet):
filter_backends = (DjangoFilterBackend, SearchFilter,)
filterset_class = TaskFilter
search_fields = ['performers__first_name', 'performers__last_name']
search_fields = ['performer__first_name', 'performer__last_name']
permission_classes = (IsAuthenticated,)

def get_permissions(self):
if self.action == 'create':
return (IsLeadOrPerformerHimselfOnly(),)
elif self.action == 'partial_update':
return (IsCreatorOnly(),)
return (IsCreatorAndLidOrPerformerOnly(),)
return super().get_permissions()

def get_queryset(self):
user = self.request.user
if user.is_lead:
queryset = Task.objects.exclude(
Q(creator=F('performers')) & ~Q(performers=user)
Q(creator=F('performer')) & ~Q(performer=user)
).distinct()
else:
queryset = Task.objects.filter(performers=user)
queryset = Task.objects.filter(performer=user)
return queryset

def get_serializer_class(self):
Expand All @@ -49,3 +54,29 @@ def get_serializer_class(self):

def perform_create(self, serializer):
serializer.save(creator=self.request.user)

@action(
methods=('GET',),
detail=False,
permission_classes=(IsAuthenticated,),
url_path='get-sorted-tasks',
)
def get_sorted_tasks(self, _):
queryset = self.get_queryset()
filter_queryset = {
Task.TaskStatus.TO_DO: [],
Task.TaskStatus.IN_PROGRESS: [],
Task.TaskStatus.DONE: [],
Task.TaskStatus.HOLD: [],
}
for value in queryset:
task = TaskShowSerializer(
value, context=self.get_serializer_context()
).data
if value.hold:
filter_queryset.get(Task.TaskStatus.HOLD).append(task)
elif value.status == Task.TaskStatus.ARCHIVED:
continue
else:
filter_queryset.get(value.status).append(task)
return Response(filter_queryset)
1 change: 1 addition & 0 deletions short_tracker/api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
photo
)


router_v1 = DefaultRouter()
router_v1.register('tasks', TaskViewSet, basename='tasks')
router_v1.register(
Expand Down
Empty file added short_tracker/bot/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions short_tracker/bot/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib import admin

from .models import AllowNotification

admin.site.register(AllowNotification)
6 changes: 6 additions & 0 deletions short_tracker/bot/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class BotConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "bot"
Loading

0 comments on commit 53a5cee

Please sign in to comment.