From 4333bd58cfeec5c613a8b9b5d3a3b713964f5c8e Mon Sep 17 00:00:00 2001 From: Kaaybi Date: Sat, 12 Nov 2022 18:46:52 +0000 Subject: [PATCH] feat: add users and groups API routes --- src-ui/src/app/data/paperless-group.ts | 9 +++ src-ui/src/app/data/paperless-user.ts | 15 +++++ src-ui/src/app/services/rest/group.service.ts | 13 ++++ src-ui/src/app/services/rest/user.service.ts | 13 ++++ src/paperless/filters.py | 20 ++++++ src/paperless/serialisers.py | 64 +++++++++++++++++++ src/paperless/urls.py | 4 ++ src/paperless/views.py | 37 +++++++++++ 8 files changed, 175 insertions(+) create mode 100644 src-ui/src/app/data/paperless-group.ts create mode 100644 src-ui/src/app/data/paperless-user.ts create mode 100644 src-ui/src/app/services/rest/group.service.ts create mode 100644 src-ui/src/app/services/rest/user.service.ts create mode 100644 src/paperless/filters.py create mode 100644 src/paperless/serialisers.py diff --git a/src-ui/src/app/data/paperless-group.ts b/src-ui/src/app/data/paperless-group.ts new file mode 100644 index 000000000..b78efeb5a --- /dev/null +++ b/src-ui/src/app/data/paperless-group.ts @@ -0,0 +1,9 @@ +import { ObjectWithId } from './object-with-id' + +export interface PaperlessGroup extends ObjectWithId { + name?: string + + user_count?: number // not implemented yet + + permissions?: string[] +} diff --git a/src-ui/src/app/data/paperless-user.ts b/src-ui/src/app/data/paperless-user.ts new file mode 100644 index 000000000..e4d3e86da --- /dev/null +++ b/src-ui/src/app/data/paperless-user.ts @@ -0,0 +1,15 @@ +import { PaperlessGroup } from 'src/app/data/paperless-group' +import { ObjectWithId } from './object-with-id' + +export interface PaperlessUser extends ObjectWithId { + username?: string + first_name?: string + last_name?: string + date_joined?: Date + is_staff?: boolean + is_active?: boolean + is_superuser?: boolean + groups?: PaperlessGroup[] + permissions?: string[] + inherited_permissions?: string[] +} diff --git a/src-ui/src/app/services/rest/group.service.ts b/src-ui/src/app/services/rest/group.service.ts new file mode 100644 index 000000000..7c913b92c --- /dev/null +++ b/src-ui/src/app/services/rest/group.service.ts @@ -0,0 +1,13 @@ +import { HttpClient } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { PaperlessGroup } from 'src/app/data/paperless-group' +import { AbstractNameFilterService } from './abstract-name-filter-service' + +@Injectable({ + providedIn: 'root', +}) +export class GroupService extends AbstractNameFilterService { + constructor(http: HttpClient) { + super(http, 'groups') + } +} diff --git a/src-ui/src/app/services/rest/user.service.ts b/src-ui/src/app/services/rest/user.service.ts new file mode 100644 index 000000000..e8466a2a2 --- /dev/null +++ b/src-ui/src/app/services/rest/user.service.ts @@ -0,0 +1,13 @@ +import { HttpClient } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { PaperlessUser } from 'src/app/data/paperless-user' +import { AbstractNameFilterService } from './abstract-name-filter-service' + +@Injectable({ + providedIn: 'root', +}) +export class UserService extends AbstractNameFilterService { + constructor(http: HttpClient) { + super(http, 'users') + } +} diff --git a/src/paperless/filters.py b/src/paperless/filters.py new file mode 100644 index 000000000..e847f141e --- /dev/null +++ b/src/paperless/filters.py @@ -0,0 +1,20 @@ +from django.contrib.auth.models import Group +from django.contrib.auth.models import User +from django_filters.rest_framework import FilterSet + +CHAR_KWARGS = ["istartswith", "iendswith", "icontains", "iexact"] +ID_KWARGS = ["in", "exact"] +INT_KWARGS = ["exact", "gt", "gte", "lt", "lte", "isnull"] +DATE_KWARGS = ["year", "month", "day", "date__gt", "gt", "date__lt", "lt"] + + +class UserFilterSet(FilterSet): + class Meta: + model = User + fields = {"username": CHAR_KWARGS} + + +class GroupFilterSet(FilterSet): + class Meta: + model = Group + fields = {"name": CHAR_KWARGS} diff --git a/src/paperless/serialisers.py b/src/paperless/serialisers.py new file mode 100644 index 000000000..af0dbe8ad --- /dev/null +++ b/src/paperless/serialisers.py @@ -0,0 +1,64 @@ +from django.contrib.auth.models import Group +from django.contrib.auth.models import User +from rest_framework import serializers + + +class UserSerializer(serializers.ModelSerializer): + + groups = serializers.SerializerMethodField() + permissions = serializers.SerializerMethodField() + inherited_permissions = serializers.SerializerMethodField() + + class Meta: + model = User + fields = ( + "id", + "username", + "first_name", + "last_name", + "date_joined", + "is_staff", + "is_active", + "is_superuser", + "groups", + "permissions", + "inherited_permissions", + ) + + def get_groups(self, obj): + return list(obj.groups.values_list("name", flat=True)) + + def get_permissions(self, obj): + # obj.get_user_permissions() returns more permissions than desired + permission_natural_keys = [] + permissions = obj.user_permissions.all() + for permission in permissions: + permission_natural_keys.append( + permission.natural_key()[1] + "." + permission.natural_key()[0], + ) + return permission_natural_keys + + def get_inherited_permissions(self, obj): + return obj.get_group_permissions() + + +class GroupSerializer(serializers.ModelSerializer): + + permissions = serializers.SerializerMethodField() + + class Meta: + model = Group + fields = ( + "id", + "name", + "permissions", + ) + + def get_permissions(self, obj): + permission_natural_keys = [] + permissions = obj.permissions.all() + for permission in permissions: + permission_natural_keys.append( + permission.natural_key()[1] + "." + permission.natural_key()[0], + ) + return permission_natural_keys diff --git a/src/paperless/urls.py b/src/paperless/urls.py index 46309e1e6..a57573e7a 100644 --- a/src/paperless/urls.py +++ b/src/paperless/urls.py @@ -27,6 +27,8 @@ from documents.views import UiSettingsView from documents.views import UnifiedSearchViewSet from paperless.consumers import StatusConsumer from paperless.views import FaviconView +from paperless.views import GroupViewSet +from paperless.views import UserViewSet from rest_framework.authtoken import views from rest_framework.routers import DefaultRouter @@ -39,6 +41,8 @@ api_router.register(r"tags", TagViewSet) api_router.register(r"saved_views", SavedViewViewSet) api_router.register(r"storage_paths", StoragePathViewSet) api_router.register(r"tasks", TasksViewSet, basename="tasks") +api_router.register(r"users", UserViewSet, basename="users") +api_router.register(r"groups", GroupViewSet, basename="groups") urlpatterns = [ diff --git a/src/paperless/views.py b/src/paperless/views.py index 9f3d017a6..9894c3006 100644 --- a/src/paperless/views.py +++ b/src/paperless/views.py @@ -1,8 +1,19 @@ import os +from django.contrib.auth.models import Group +from django.contrib.auth.models import User +from django.db.models.functions import Lower from django.http import HttpResponse from django.views.generic import View +from django_filters.rest_framework import DjangoFilterBackend +from paperless.filters import GroupFilterSet +from paperless.filters import UserFilterSet +from paperless.serialisers import GroupSerializer +from paperless.serialisers import UserSerializer +from rest_framework.filters import OrderingFilter from rest_framework.pagination import PageNumberPagination +from rest_framework.permissions import IsAuthenticated +from rest_framework.viewsets import ModelViewSet class StandardPagination(PageNumberPagination): @@ -22,3 +33,29 @@ class FaviconView(View): ) with open(favicon, "rb") as f: return HttpResponse(f, content_type="image/x-icon") + + +class UserViewSet(ModelViewSet): + model = User + + queryset = User.objects.order_by(Lower("username")) + + serializer_class = UserSerializer + pagination_class = StandardPagination + permission_classes = (IsAuthenticated,) + filter_backends = (DjangoFilterBackend, OrderingFilter) + filterset_class = UserFilterSet + ordering_fields = ("username",) + + +class GroupViewSet(ModelViewSet): + model = Group + + queryset = Group.objects.order_by(Lower("name")) + + serializer_class = GroupSerializer + pagination_class = StandardPagination + permission_classes = (IsAuthenticated,) + filter_backends = (DjangoFilterBackend, OrderingFilter) + filterset_class = GroupFilterSet + ordering_fields = ("name",)