feat: integrate and migrate PostgreSQL in Django Rest; extract functions in task_app to services.py, enable adding/removing assignees and subtasks
This commit is contained in:
parent
81d1334013
commit
d3227e9a76
10 changed files with 132 additions and 52 deletions
|
|
@ -28,6 +28,9 @@ class LoginView(APIView):
|
|||
if created or not token.expires_at:
|
||||
token.expires_at = timezone.now() + TOKEN_EXPIRATION_TIME
|
||||
token.save()
|
||||
|
||||
user.last_login = timezone.now()
|
||||
user.save(update_fields=['last_login'])
|
||||
|
||||
return Response({'token': token.key, 'user_id': user.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
|
|
|||
|
|
@ -105,13 +105,25 @@ CACHES = {
|
|||
# Database
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||
|
||||
"""
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'join_db',
|
||||
'USER': 'chneemann',
|
||||
'PASSWORD': '91&yWMr6',
|
||||
'HOST': 'localhost',
|
||||
'PORT': '',
|
||||
}
|
||||
}
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
asgiref==3.8.1
|
||||
Django==5.1.7
|
||||
django-cors-headers==4.7.0
|
||||
django-environ==0.12.0
|
||||
django-redis==5.4.0
|
||||
djangorestframework==3.15.2
|
||||
djangorestframework-camel-case==1.4.2
|
||||
psycopg2==2.9.10
|
||||
redis==5.2.1
|
||||
sqlparse==0.5.3
|
||||
|
|
@ -7,7 +7,7 @@ class SubTaskInline(admin.TabularInline):
|
|||
|
||||
class AssignedTaskInline(admin.TabularInline):
|
||||
model = AssignedTask
|
||||
fields = ['user_id']
|
||||
fields = ['user']
|
||||
extra = 1
|
||||
|
||||
@admin.register(Task)
|
||||
|
|
|
|||
|
|
@ -2,18 +2,11 @@ import uuid
|
|||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from .choices import TaskCategory, TaskPriority, TaskStatus
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
def generate_uuid_without_dashes():
|
||||
return uuid.uuid4().hex
|
||||
|
||||
class Task(models.Model):
|
||||
id = models.CharField(primary_key=True, default=generate_uuid_without_dashes, max_length=32, editable=False, unique=True)
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
title = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
category = models.CharField(max_length=50, choices=TaskCategory.choices)
|
||||
|
|
@ -27,8 +20,8 @@ class Task(models.Model):
|
|||
return self.title
|
||||
|
||||
class SubTask(models.Model):
|
||||
id = models.CharField(primary_key=True, default=generate_uuid_without_dashes, max_length=32, editable=False, unique=True)
|
||||
task = models.ForeignKey(Task, related_name="subtasks_task", on_delete=models.CASCADE)
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
task = models.ForeignKey(Task, related_name="subtasks", on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=255)
|
||||
status = models.BooleanField(default=False)
|
||||
|
||||
|
|
@ -36,8 +29,8 @@ class SubTask(models.Model):
|
|||
return f"{self.task.title} - {self.title}"
|
||||
|
||||
class AssignedTask(models.Model):
|
||||
user_id = models.ForeignKey(User, related_name="assigned_task", on_delete=models.CASCADE)
|
||||
task = models.ForeignKey(Task, related_name="assigned_task", on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, related_name="assigned_tasks", on_delete=models.CASCADE)
|
||||
task = models.ForeignKey(Task, related_name="assigned_tasks", on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user_id} - {self.task}"
|
||||
return f"{self.user} - {self.task}"
|
||||
|
|
@ -14,16 +14,16 @@ class AssignedTaskSerializer(serializers.ModelSerializer):
|
|||
fields = ['user_id']
|
||||
|
||||
class TaskSerializer(serializers.ModelSerializer):
|
||||
assignees = serializers.SerializerMethodField()
|
||||
subtasks = SubTaskSerializer(many=True, read_only=True, source="subtasks_task")
|
||||
assignees = serializers.SerializerMethodField()
|
||||
subtasks = SubTaskSerializer(many=True, read_only=True)
|
||||
|
||||
def get_assignees(self, obj):
|
||||
return obj.assigned_task.all().values('user_id')
|
||||
return obj.assigned_tasks.all().values('user_id')
|
||||
|
||||
class Meta:
|
||||
model = Task
|
||||
fields = ['id', 'title', 'description', 'category', 'priority', 'status', 'date',
|
||||
fields = ['id', 'title', 'description', 'category', 'priority', 'status', 'date',
|
||||
'creator', 'created_at', 'subtasks', 'assignees']
|
||||
|
||||
renderer_classes = [CamelCaseJSONRenderer]
|
||||
renderer_classes = [CamelCaseJSONRenderer]
|
||||
parser_classes = [CamelCaseJSONParser]
|
||||
76
task_app/services.py
Normal file
76
task_app/services.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import uuid
|
||||
from rest_framework.exceptions import APIException
|
||||
from .models import SubTask, AssignedTask
|
||||
from user_app.models import User
|
||||
|
||||
def create_or_update_subtasks(subtask_data, parent_task):
|
||||
current_subtasks = SubTask.objects.filter(task=parent_task)
|
||||
current_subtask_ids = {str(sub.id): sub for sub in current_subtasks} # Speichere Objekte zur einfachen Aktualisierung
|
||||
|
||||
new_subtasks_to_create = []
|
||||
subtasks_to_update = []
|
||||
new_subtask_ids = set()
|
||||
|
||||
for sub_data in subtask_data:
|
||||
sub_id = sub_data.get('id')
|
||||
new_subtask_ids.add(str(sub_id))
|
||||
if sub_id and str(sub_id) in current_subtask_ids:
|
||||
subtask = current_subtask_ids[str(sub_id)]
|
||||
subtask.title = sub_data.get('title', subtask.title)
|
||||
subtask.status = sub_data.get('status', subtask.status)
|
||||
subtasks_to_update.append(subtask)
|
||||
else:
|
||||
new_subtasks_to_create.append(
|
||||
SubTask(
|
||||
title=sub_data.get('title'),
|
||||
status=sub_data.get('status'),
|
||||
task=parent_task
|
||||
)
|
||||
)
|
||||
|
||||
subtasks_to_delete = [
|
||||
sub_id for sub_id in current_subtask_ids if sub_id not in new_subtask_ids
|
||||
]
|
||||
SubTask.objects.filter(id__in=subtasks_to_delete, task=parent_task).delete()
|
||||
|
||||
SubTask.objects.bulk_create(new_subtasks_to_create)
|
||||
SubTask.objects.bulk_update(subtasks_to_update, ['title', 'status'])
|
||||
|
||||
def create_or_update_assignees(assignee_data, task):
|
||||
new_assignee_ids = {str(user["user_id"]) for user in assignee_data if "user_id" in user}
|
||||
|
||||
current_assignees = AssignedTask.objects.filter(task=task).values_list('user_id', flat=True)
|
||||
current_assignee_ids = {str(assignee_id) for assignee_id in current_assignees}
|
||||
|
||||
assignees_to_remove = current_assignee_ids - new_assignee_ids
|
||||
assignees_to_add = new_assignee_ids - current_assignee_ids
|
||||
|
||||
AssignedTask.objects.filter(task=task, user_id__in=assignees_to_remove).delete()
|
||||
|
||||
users_to_add = User.objects.filter(id__in=assignees_to_add)
|
||||
AssignedTask.objects.bulk_create(
|
||||
[AssignedTask(user=user, task=task) for user in users_to_add]
|
||||
)
|
||||
|
||||
def assign_users_to_task(user_ids, task):
|
||||
user_ids = [uuid.UUID(user["user_id"]) for user in user_ids if "user_id" in user]
|
||||
|
||||
users = User.objects.filter(id__in=user_ids)
|
||||
found_user_ids = set(users.values_list('id', flat=True))
|
||||
missing_users = set(user_ids) - found_user_ids
|
||||
|
||||
if missing_users:
|
||||
raise APIException(f"Users not found: {', '.join(map(str, missing_users))}") # Konvertiere UUIDs zurück zu Strings für die Fehlermeldung
|
||||
|
||||
AssignedTask.objects.bulk_create(
|
||||
[AssignedTask(user=user, task=task) for user in users]
|
||||
)
|
||||
|
||||
def create_subtasks(subtask_data, parent_task):
|
||||
if subtask_data:
|
||||
subtasks = [
|
||||
SubTask(title=sub['title'], status=sub['status'], task=parent_task)
|
||||
for sub in subtask_data
|
||||
]
|
||||
SubTask.objects.bulk_create(subtasks)
|
||||
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import uuid
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
|
@ -10,9 +11,9 @@ from .choices import TaskStatus
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from user_app.models import User
|
||||
from rest_framework.exceptions import APIException
|
||||
from .services import create_or_update_subtasks, create_or_update_assignees, assign_users_to_task, create_subtasks
|
||||
|
||||
class TaskViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
queryset = Task.objects.all()
|
||||
serializer_class = TaskSerializer
|
||||
|
||||
|
|
@ -29,16 +30,33 @@ class TaskViewSet(viewsets.ModelViewSet):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
task = serializer.save()
|
||||
self._assign_users_to_task(request.data.get('assignees', []), task)
|
||||
assign_users_to_task(request.data.get('assignees', []), task)
|
||||
|
||||
subtasks_data = request.data.get("subtasks", [])
|
||||
|
||||
if subtasks_data:
|
||||
self._create_subtasks(subtasks_data, task)
|
||||
create_subtasks(subtasks_data, task)
|
||||
|
||||
cache.delete(f"task_{task.id}")
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
task = self.get_object()
|
||||
serializer = self.get_serializer(task, data=request.data, partial=True)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
task = serializer.save()
|
||||
|
||||
assignees_data = request.data.get('assignees', [])
|
||||
create_or_update_assignees(assignees_data, task)
|
||||
|
||||
subtasks_data = request.data.get('subtasks', [])
|
||||
create_or_update_subtasks(subtasks_data, task)
|
||||
|
||||
cache.delete(f"task_{task.id}")
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(self, request, pk=None):
|
||||
try:
|
||||
|
|
@ -48,29 +66,7 @@ class TaskViewSet(viewsets.ModelViewSet):
|
|||
return Response({'message': 'Task deleted successfully.'}, status=status.HTTP_204_NO_CONTENT)
|
||||
except Task.DoesNotExist:
|
||||
return Response({'error': 'Task not found.'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def _create_subtasks(self, subtask_data, parent_task):
|
||||
if subtask_data:
|
||||
subtasks = [
|
||||
SubTask(title=sub['title'], status=sub['status'], task=parent_task)
|
||||
for sub in subtask_data
|
||||
]
|
||||
SubTask.objects.bulk_create(subtasks)
|
||||
|
||||
def _assign_users_to_task(self, user_ids, task):
|
||||
user_ids = [user["user_id"] for user in user_ids if "user_id" in user]
|
||||
|
||||
users = User.objects.filter(id__in=user_ids)
|
||||
found_user_ids = set(users.values_list('id', flat=True))
|
||||
missing_users = set(user_ids) - found_user_ids
|
||||
|
||||
if missing_users:
|
||||
raise APIException(f"Users not found: {', '.join(missing_users)}")
|
||||
|
||||
AssignedTask.objects.bulk_create(
|
||||
[AssignedTask(user_id=user, task=task) for user in users]
|
||||
)
|
||||
|
||||
|
||||
@action(detail=True, methods=['patch'])
|
||||
def update_status(self, request, pk=None):
|
||||
task = self.get_object()
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ import uuid
|
|||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin, Group, Permission
|
||||
from django.db import models
|
||||
|
||||
def generate_uuid_without_dashes():
|
||||
return uuid.uuid4().hex
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
def create_user(self, email, first_name, password=None):
|
||||
if not email:
|
||||
|
|
@ -27,8 +24,7 @@ class UserManager(BaseUserManager):
|
|||
|
||||
|
||||
class User(AbstractBaseUser, PermissionsMixin):
|
||||
uId = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
||||
id = models.CharField(primary_key=True, default=generate_uuid_without_dashes, max_length=32,editable=False, unique=True)
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
first_name = models.CharField(max_length=50)
|
||||
last_name = models.CharField(max_length=50, blank=True, null=True)
|
||||
email = models.EmailField(unique=True)
|
||||
|
|
|
|||
Loading…
Reference in a new issue