diff --git a/lego/apps/lending/managers.py b/lego/apps/lending/managers.py index 375865db2..6b3761584 100644 --- a/lego/apps/lending/managers.py +++ b/lego/apps/lending/managers.py @@ -26,6 +26,20 @@ def create(self, *args, **kwargs): notification.notify() return lending_instance + + #TODO: We probably need another template for accepting instances + def update(self, *args, **kwargs): + from lego.apps.lending.notifications import LendingInstanceNotification + + lending_instance = super().update(*args, **kwargs) + notification = LendingInstanceNotification( + lending_instance=lending_instance, + user=lending_instance.created_by, + ) + notification.notify() + + return lending_instance + def get_queryset(self): return super().get_queryset().select_related("created_by") diff --git a/lego/apps/lending/models.py b/lego/apps/lending/models.py index d08ead6d3..4867851f8 100644 --- a/lego/apps/lending/models.py +++ b/lego/apps/lending/models.py @@ -3,8 +3,10 @@ from django.db import models from lego.apps.files.fields import ImageField from lego.apps.files.models import FileField +from lego.apps.lending.permissions import LendingInstancePermissionHandler from lego.apps.lending.validators import responsible_roles_validator from django.contrib.postgres.fields import ArrayField +from lego.apps.permissions.models import ObjectPermissionsModel from lego.apps.users import constants from lego.apps.lending.managers import LendingInstanceManager from lego.apps.users.models import User @@ -16,7 +18,7 @@ # Create your models here. -class LendableObject(BasisModel): +class LendableObject(BasisModel, ObjectPermissionsModel): title = models.CharField(max_length=128, null=False, blank=False) description = models.TextField(null=False, blank=True) has_contract = models.BooleanField(default=False, null=False, blank=False) @@ -39,8 +41,11 @@ class LendableObject(BasisModel): def get_furthest_booking_date(self): return timezone.now() + timedelta(days=14) + class Meta: + abstract = False -class LendingInstance(BasisModel): + +class LendingInstance(BasisModel, ObjectPermissionsModel): lendable_object = models.ForeignKey(LendableObject, on_delete=models.CASCADE) start_date = models.DateTimeField(null=True) end_date = models.DateTimeField(null=True) @@ -52,3 +57,7 @@ class LendingInstance(BasisModel): @property def active(self): return timezone.now() < self.end_date and timezone.now() > self.start_date + + class Meta: + abstract = False + permission_handler = LendingInstancePermissionHandler() diff --git a/lego/apps/lending/permissions.py b/lego/apps/lending/permissions.py new file mode 100644 index 000000000..7bf9e0cef --- /dev/null +++ b/lego/apps/lending/permissions.py @@ -0,0 +1,60 @@ +from lego.apps.permissions.constants import CREATE, DELETE, EDIT, LIST, VIEW +from lego.apps.permissions.permissions import PermissionHandler +from django.db.models import Q + + +class LendingInstancePermissionHandler(PermissionHandler): + force_object_permission_check = True + authentication_map = {LIST: False, VIEW: False} + default_require_auth = True + + def has_perm( + self, + user, + perm, + obj=None, + queryset=None, + check_keyword_permissions=True, + **kwargs + ): + if perm == LIST: + return True + if not user.is_authenticated: + return False + + # Check object permissions before keywork perms + if obj is not None: + if self.has_object_permissions(user, perm, obj): + return True + + if perm == EDIT and self.created_by(user, obj): + return True + + if perm == CREATE: + return True + + has_perm = super().has_perm( + user, perm, obj, queryset, check_keyword_permissions, **kwargs + ) + + if has_perm: + return True + + return False + + def has_object_permissions(self, user, perm, obj): + if perm == DELETE: + return False + if perm == EDIT and obj.created_by == user: + return True + if perm == CREATE: + return True + return not (perm == DELETE or perm == EDIT) + + def filter_queryset(self, user, queryset, **kwargs): + if user.is_authenticated: + return queryset.filter( + Q(created_by=user) | + Q(lendable_object__responsible_groups__in=user.abakus_groups) + ).distinct() + return queryset.none() diff --git a/lego/apps/lending/serializers.py b/lego/apps/lending/serializers.py index eeb167360..f7f87dad8 100644 --- a/lego/apps/lending/serializers.py +++ b/lego/apps/lending/serializers.py @@ -4,17 +4,27 @@ from lego.apps.lending.models import LendableObject, LendingInstance from lego.apps.users.serializers.users import PublicUserSerializer -from lego.utils.serializers import BasisModelSerializer +from lego.utils.serializers import BasisModelSerializer, ObjectPermissionsSerializerMixin -class LendableObjectSerializer(serializers.ModelSerializer): +class DetailedLendableObjectSerializer(BasisModelSerializer): image = ImageField(required=False, options={"height": 500}) class Meta: model = LendableObject fields = "__all__" +class DetailedAdminLendableObjectSerializer( + ObjectPermissionsSerializerMixin, DetailedLendableObjectSerializer +): + class Meta: + model = LendableObject + fields = ( + DetailedLendableObjectSerializer.Meta.fields + + ObjectPermissionsSerializerMixin.Meta.fields + ) + -class LendingInstanceSerializer(BasisModelSerializer): +class DetailedLendingInstanceSerializer(BasisModelSerializer): author = PublicUserSerializer(read_only=True, source="created_by") class Meta: @@ -52,3 +62,13 @@ def validate(self, data): ) return data + +class DetailedAdminLendingInstanceSerializer( + ObjectPermissionsSerializerMixin, DetailedLendingInstanceSerializer +): + class Meta: + model = LendingInstance + fields = ( + DetailedLendingInstanceSerializer.Meta.fields + + ObjectPermissionsSerializerMixin.Meta.fields + ) diff --git a/lego/apps/lending/views.py b/lego/apps/lending/views.py index 8675e4548..c1f0df2c9 100644 --- a/lego/apps/lending/views.py +++ b/lego/apps/lending/views.py @@ -13,33 +13,56 @@ from lego.apps.lending.filters import LendableObjectFilterSet, LendingInstanceFilterSet from lego.apps.lending.models import LendableObject, LendingInstance from lego.apps.lending.serializers import ( - LendableObjectSerializer, - LendingInstanceSerializer, + DetailedLendableObjectSerializer, + DetailedLendingInstanceSerializer, + DetailedAdminLendableObjectSerializer, + DetailedAdminLendingInstanceSerializer ) from lego.apps.permissions.api.views import AllowedPermissionsMixin +from lego.apps.permissions.utils import get_permission_handler class LendableObjectViewSet(AllowedPermissionsMixin, viewsets.ModelViewSet): queryset = LendableObject.objects.all() - serializer_class = LendableObjectSerializer + serializer_class = DetailedLendableObjectSerializer filterset_class = LendableObjectFilterSet http_method_names = ["get", "post", "patch", "delete", "head", "options"] permission_classes = [ permissions.IsAuthenticated, ] + def get_serializer_class(self): + if self.request and self.request.user.is_authenticated: + return DetailedAdminLendableObjectSerializer + return super().get_serializer_class() + class LendingInstanceViewSet(AllowedPermissionsMixin, viewsets.ModelViewSet): queryset = LendingInstance.objects.all() - serializer_class = LendingInstanceSerializer + serializer_class = DetailedLendingInstanceSerializer filterset_class = LendingInstanceFilterSet http_method_names = ["get", "post", "patch", "delete", "head", "options"] permission_classes = [ permissions.IsAuthenticated, ] + + def get_queryset(self): + if self.request is None: + return LendingInstance.objects.none() + + permission_handler = get_permission_handler(LendingInstance) + return permission_handler.filter_queryset( + self.request.user, + ) + + def get_serializer_class(self): + if self.request and self.request.user.is_authenticated: + return DetailedAdminLendingInstanceSerializer + return super().get_serializer_class() + def create(self, request): - serializer = LendingInstanceSerializer( + serializer = DetailedLendingInstanceSerializer( data=request.data, context={"request": request} ) if serializer.is_valid(raise_exception=True):