mirror of
https://github.com/lemeow125/DocManagerBackend.git
synced 2025-01-19 01:23:02 +08:00
Add document requests app
This commit is contained in:
parent
bb9fcc3d7c
commit
ba19412d31
23 changed files with 484 additions and 53 deletions
|
@ -18,3 +18,6 @@ EMAIL_ADDRESS = 'noreply.dev@mehdns.06222001.xyz'
|
||||||
# Admin Credentials
|
# Admin Credentials
|
||||||
ADMIN_EMAIL = 'admin@test.com'
|
ADMIN_EMAIL = 'admin@test.com'
|
||||||
ADMIN_PASSWORD = ''
|
ADMIN_PASSWORD = ''
|
||||||
|
|
||||||
|
# To insert test data or not
|
||||||
|
TEST_DATA = "True"
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.1.3 on 2024-11-23 13:04
|
# Generated by Django 5.1.3 on 2024-11-23 17:01
|
||||||
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.contrib.auth.validators
|
import django.contrib.auth.validators
|
||||||
|
@ -69,12 +69,6 @@ class Migration(migrations.Migration):
|
||||||
blank=True, max_length=150, verbose_name="last name"
|
blank=True, max_length=150, verbose_name="last name"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
|
||||||
"email",
|
|
||||||
models.EmailField(
|
|
||||||
blank=True, max_length=254, verbose_name="email address"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
"is_staff",
|
"is_staff",
|
||||||
models.BooleanField(
|
models.BooleanField(
|
||||||
|
@ -91,6 +85,7 @@ class Migration(migrations.Migration):
|
||||||
verbose_name="active",
|
verbose_name="active",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
("email", models.EmailField(max_length=254, unique=True)),
|
||||||
(
|
(
|
||||||
"role",
|
"role",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.1.3 on 2024-11-23 13:36
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("accounts", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="customuser",
|
|
||||||
name="email",
|
|
||||||
field=models.EmailField(max_length=254, unique=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -32,8 +32,4 @@ class CustomUser(AbstractUser):
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
self.username = self.email
|
self.username = self.email
|
||||||
if self.is_staff:
|
|
||||||
self.role = "staff"
|
|
||||||
elif self.is_superuser:
|
|
||||||
self.role = "admin"
|
|
||||||
super().save(**kwargs)
|
super().save(**kwargs)
|
||||||
|
|
22
docmanager_backend/accounts/permissions.py
Normal file
22
docmanager_backend/accounts/permissions.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
from rest_framework.permissions import BasePermission
|
||||||
|
|
||||||
|
|
||||||
|
class IsStaff(BasePermission):
|
||||||
|
"""
|
||||||
|
Allows access only to users with staff role
|
||||||
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return bool(
|
||||||
|
request.user and request.user.role in ("head", "admin", "planning", "staff")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IsHead(BasePermission):
|
||||||
|
"""
|
||||||
|
Allows access only to users with staff role
|
||||||
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
print(request.user.role)
|
||||||
|
return bool(request.user and request.user.role == "head")
|
|
@ -12,6 +12,7 @@ from drf_spectacular.views import (
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("accounts/", include("accounts.urls")),
|
path("accounts/", include("accounts.urls")),
|
||||||
path("documents/", include("documents.urls")),
|
path("documents/", include("documents.urls")),
|
||||||
|
path("requests/", include("document_requests.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("schema/", SpectacularAPIView.as_view(), name="schema"),
|
path("schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||||
path(
|
path(
|
||||||
|
|
|
@ -45,7 +45,30 @@ SECRET_KEY = get_secret("SECRET_KEY")
|
||||||
# SECURITY WARNING: don"t run with debug turned on in production!
|
# SECURITY WARNING: don"t run with debug turned on in production!
|
||||||
DEBUG = get_secret("DEBUG")
|
DEBUG = get_secret("DEBUG")
|
||||||
|
|
||||||
|
# URL Prefixes
|
||||||
|
USE_HTTPS = get_secret("USE_HTTPS")
|
||||||
|
URL_SCHEME = "https" if USE_HTTPS else "http"
|
||||||
|
# Building Backend URL
|
||||||
|
BACKEND_ADDRESS = get_secret("BACKEND_ADDRESS")
|
||||||
|
BACKEND_PORT = get_secret("BACKEND_PORT")
|
||||||
|
# Building Frontend URL
|
||||||
|
FRONTEND_ADDRESS = get_secret("FRONTEND_ADDRESS")
|
||||||
|
FRONTEND_PORT = get_secret("FRONTEND_PORT")
|
||||||
|
# Full URLs
|
||||||
|
BACKEND_URL = f"{URL_SCHEME}://{BACKEND_ADDRESS}"
|
||||||
|
FRONTEND_URL = f"{URL_SCHEME}://{BACKEND_ADDRESS}"
|
||||||
|
|
||||||
|
# Append port to full URLs if deployed locally
|
||||||
|
if not USE_HTTPS:
|
||||||
|
BACKEND_URL += f":{BACKEND_PORT}"
|
||||||
|
FRONTEND_URL += f":{FRONTEND_PORT}"
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["*"]
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
|
FRONTEND_URL,
|
||||||
|
BACKEND_URL,
|
||||||
|
# You can also set up https://*.name.xyz for wildcards here
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
@ -67,12 +90,14 @@ INSTALLED_APPS = [
|
||||||
"drf_spectacular_sidecar",
|
"drf_spectacular_sidecar",
|
||||||
"accounts",
|
"accounts",
|
||||||
"documents",
|
"documents",
|
||||||
|
"document_requests",
|
||||||
"django_cleanup.apps.CleanupConfig",
|
"django_cleanup.apps.CleanupConfig",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
@ -163,7 +188,7 @@ USE_TZ = True
|
||||||
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||||
|
|
||||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||||
MEDIA_URL = "api/v1/media/"
|
MEDIA_URL = f"{BACKEND_URL}/api/v1/media/"
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||||
ROOT_URLCONF = "config.urls"
|
ROOT_URLCONF = "config.urls"
|
||||||
STATIC_URL = "static/"
|
STATIC_URL = "static/"
|
||||||
|
|
0
docmanager_backend/document_requests/__init__.py
Normal file
0
docmanager_backend/document_requests/__init__.py
Normal file
26
docmanager_backend/document_requests/admin.py
Normal file
26
docmanager_backend/document_requests/admin.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
from .models import DocumentRequestUnit, DocumentRequest
|
||||||
|
from unfold.contrib.filters.admin import RangeDateFilter
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(DocumentRequestUnit)
|
||||||
|
class DocumentRequestUnitAdmin(ModelAdmin):
|
||||||
|
search_fields = ["id"]
|
||||||
|
list_display = ["id", "get_document_title", "copies"]
|
||||||
|
|
||||||
|
def get_document_title(self, obj):
|
||||||
|
return obj.documents.title # Assuming the Document model has a 'title' field
|
||||||
|
|
||||||
|
get_document_title.short_description = "Document"
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(DocumentRequest)
|
||||||
|
class DocumentRequestAdmin(ModelAdmin):
|
||||||
|
list_filter = [
|
||||||
|
("date_requested", RangeDateFilter),
|
||||||
|
]
|
||||||
|
|
||||||
|
list_display = ["id", "date_requested", "status", "college"]
|
6
docmanager_backend/document_requests/apps.py
Normal file
6
docmanager_backend/document_requests/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestsConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "document_requests"
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Generated by Django 5.1.3 on 2024-11-23 17:01
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("documents", "0001_initial"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="DocumentRequest",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"date_requested",
|
||||||
|
models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now, editable=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("college", models.CharField(max_length=64)),
|
||||||
|
("purpose", models.TextField(max_length=512)),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("pending", "Pending"),
|
||||||
|
("approved", "Approved"),
|
||||||
|
("denied", "Denied"),
|
||||||
|
],
|
||||||
|
default="pending",
|
||||||
|
max_length=32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"requester",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="DocumentRequestUnit",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("copies", models.IntegerField(default=1)),
|
||||||
|
(
|
||||||
|
"document",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="documents.document",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"document_request",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="document_requests.documentrequest",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="documentrequest",
|
||||||
|
name="documents",
|
||||||
|
field=models.ManyToManyField(to="document_requests.documentrequestunit"),
|
||||||
|
),
|
||||||
|
]
|
28
docmanager_backend/document_requests/models.py
Normal file
28
docmanager_backend/document_requests/models.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestUnit(models.Model):
|
||||||
|
document_request = models.ForeignKey(
|
||||||
|
"document_requests.DocumentRequest", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
document = models.ForeignKey("documents.Document", on_delete=models.CASCADE)
|
||||||
|
copies = models.IntegerField(default=1, null=False, blank=False)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequest(models.Model):
|
||||||
|
requester = models.ForeignKey("accounts.CustomUser", on_delete=models.CASCADE)
|
||||||
|
documents = models.ManyToManyField("document_requests.DocumentRequestUnit")
|
||||||
|
date_requested = models.DateTimeField(default=now, editable=False)
|
||||||
|
college = models.CharField(max_length=64, blank=False, null=False)
|
||||||
|
purpose = models.TextField(max_length=512, blank=False, null=False)
|
||||||
|
|
||||||
|
STATUS_CHOICES = (
|
||||||
|
("pending", "Pending"),
|
||||||
|
("approved", "Approved"),
|
||||||
|
("denied", "Denied"),
|
||||||
|
)
|
||||||
|
|
||||||
|
status = models.CharField(max_length=32, choices=STATUS_CHOICES, default="pending")
|
||||||
|
|
||||||
|
# TODO: Add request type (Softcopy/Hardcopy)
|
119
docmanager_backend/document_requests/serializers.py
Normal file
119
docmanager_backend/document_requests/serializers.py
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from documents.models import Document
|
||||||
|
from documents.serializers import DocumentSerializer, DocumentFileSerializer
|
||||||
|
from accounts.models import CustomUser
|
||||||
|
from .models import DocumentRequest, DocumentRequestUnit
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestUnitCreationSerializer(serializers.ModelSerializer):
|
||||||
|
document = serializers.SlugRelatedField(
|
||||||
|
many=False, slug_field="id", queryset=Document.objects.all(), required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DocumentRequestUnit
|
||||||
|
fields = ["document", "copies"]
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestCreationSerializer(serializers.ModelSerializer):
|
||||||
|
requester = serializers.SlugRelatedField(
|
||||||
|
many=False, slug_field="id", queryset=CustomUser.objects.all(), required=False
|
||||||
|
)
|
||||||
|
documents = DocumentRequestUnitCreationSerializer(many=True, required=True)
|
||||||
|
college = serializers.CharField(allow_blank=False)
|
||||||
|
purpose = serializers.CharField(max_length=512, allow_blank=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DocumentRequest
|
||||||
|
fields = ["requester", "college", "purpose", "documents"]
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
user = self.context["request"].user
|
||||||
|
documents_data = validated_data.pop("documents")
|
||||||
|
# Set requester to user who sent HTTP request to prevent spoofing
|
||||||
|
validated_data["requester"] = user
|
||||||
|
|
||||||
|
DOCUMENT_REQUEST = DocumentRequest.objects.create(**validated_data)
|
||||||
|
|
||||||
|
DOCUMENT_REQUEST_UNITS = []
|
||||||
|
for DOCUMENT_REQUEST_UNIT in documents_data:
|
||||||
|
DOCUMENT_REQUEST_UNIT = DocumentRequestUnit.objects.create(
|
||||||
|
document_request=DOCUMENT_REQUEST,
|
||||||
|
document=DOCUMENT_REQUEST_UNIT["document"],
|
||||||
|
copies=DOCUMENT_REQUEST_UNIT["copies"],
|
||||||
|
)
|
||||||
|
DOCUMENT_REQUEST_UNITS.append(DOCUMENT_REQUEST_UNIT)
|
||||||
|
|
||||||
|
DOCUMENT_REQUEST.documents.set(DOCUMENT_REQUEST_UNITS)
|
||||||
|
DOCUMENT_REQUEST.save()
|
||||||
|
|
||||||
|
return DOCUMENT_REQUEST
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestUnitSerializer(serializers.ModelSerializer):
|
||||||
|
document = DocumentSerializer(many=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DocumentRequestUnit
|
||||||
|
fields = ["document", "copies"]
|
||||||
|
read_only_fields = ["document", "copies"]
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestUnitWithFileSerializer(serializers.ModelSerializer):
|
||||||
|
document = DocumentFileSerializer(many=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DocumentRequestUnit
|
||||||
|
fields = ["document", "copies"]
|
||||||
|
read_only_fields = ["document", "copies"]
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestSerializer(serializers.ModelSerializer):
|
||||||
|
documents = serializers.SerializerMethodField()
|
||||||
|
college = serializers.CharField(allow_blank=False)
|
||||||
|
purpose = serializers.CharField(max_length=512, allow_blank=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DocumentRequest
|
||||||
|
fields = ["id", "requester", "college",
|
||||||
|
"purpose", "documents", "status"]
|
||||||
|
read_only_fields = [
|
||||||
|
"id",
|
||||||
|
"requester",
|
||||||
|
"college",
|
||||||
|
"purpose",
|
||||||
|
"documents",
|
||||||
|
"status",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_documents(self, obj):
|
||||||
|
if obj.status != "approved":
|
||||||
|
serializer_class = DocumentRequestUnitSerializer
|
||||||
|
else:
|
||||||
|
serializer_class = DocumentRequestUnitWithFileSerializer
|
||||||
|
return serializer_class(obj.documents, many=True).data
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestUpdateSerializer(serializers.ModelSerializer):
|
||||||
|
status = serializers.ChoiceField(
|
||||||
|
choices=DocumentRequest.STATUS_CHOICES, required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DocumentRequest
|
||||||
|
fields = ["id", "status"]
|
||||||
|
read_only_fields = ["id", "status"]
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
if instance.status == "denied":
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
{
|
||||||
|
"error": "Denied requests cannot be updated. It is advised you create a new request and approve it from there"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif validated_data["status"] == instance.status:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
{"error": "Request form status provided is the same as current status"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return super().update(instance, validated_data)
|
12
docmanager_backend/document_requests/urls.py
Normal file
12
docmanager_backend/document_requests/urls.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from django.urls import path, include
|
||||||
|
from .views import (
|
||||||
|
DocumentRequestCreateView,
|
||||||
|
DocumentRequestListView,
|
||||||
|
DocumentRequestUpdateView,
|
||||||
|
)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("create/", DocumentRequestCreateView.as_view()),
|
||||||
|
path("list/", DocumentRequestListView.as_view()),
|
||||||
|
path("update/<int:pk>/", DocumentRequestUpdateView.as_view()),
|
||||||
|
]
|
53
docmanager_backend/document_requests/views.py
Normal file
53
docmanager_backend/document_requests/views.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
from accounts.permissions import IsHead
|
||||||
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
from .serializers import (
|
||||||
|
DocumentRequestCreationSerializer,
|
||||||
|
DocumentRequestSerializer,
|
||||||
|
DocumentRequestUpdateSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .models import DocumentRequest
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestCreateView(generics.CreateAPIView):
|
||||||
|
"""
|
||||||
|
Used by clients to create document requests. Requires passing in request information in addition to the documents themselves
|
||||||
|
"""
|
||||||
|
|
||||||
|
http_method_names = ["post"]
|
||||||
|
serializer_class = DocumentRequestCreationSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestListView(generics.ListAPIView):
|
||||||
|
"""
|
||||||
|
Returns document requests. If document requests are approved, also returns the link to download the document.
|
||||||
|
Staff are able to view all document requests here. Clients are only able to view their own requests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
http_method_names = ["get"]
|
||||||
|
serializer_class = DocumentRequestSerializer
|
||||||
|
pagination_class = PageNumberPagination
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
if user.role == "client":
|
||||||
|
queryset = DocumentRequest.objects.filter(requester=user)
|
||||||
|
else:
|
||||||
|
queryset = DocumentRequest.objects.all()
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentRequestUpdateView(generics.UpdateAPIView):
|
||||||
|
"""
|
||||||
|
Used by head approve or deny document requests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
http_method_names = ["patch"]
|
||||||
|
serializer_class = DocumentRequestUpdateSerializer
|
||||||
|
permission_classes = [IsAuthenticated, IsHead]
|
||||||
|
queryset = DocumentRequest.objects.all()
|
|
@ -1,6 +1,7 @@
|
||||||
# Generated by Django 5.1.3 on 2024-11-23 14:13
|
# Generated by Django 5.1.3 on 2024-11-23 17:01
|
||||||
|
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
import documents.models
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +39,11 @@ class Migration(migrations.Migration):
|
||||||
max_length=32,
|
max_length=32,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("file", models.FileField(upload_to="documents/")),
|
("number_pages", models.IntegerField()),
|
||||||
|
(
|
||||||
|
"file",
|
||||||
|
models.FileField(upload_to=documents.models.Document.upload_to),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"date_uploaded",
|
"date_uploaded",
|
||||||
models.DateTimeField(
|
models.DateTimeField(
|
||||||
|
|
|
@ -17,14 +17,15 @@ class Document(models.Model):
|
||||||
document_type = models.CharField(
|
document_type = models.CharField(
|
||||||
max_length=32, choices=DOCUMENT_TYPE_CHOICES, null=False, blank=False
|
max_length=32, choices=DOCUMENT_TYPE_CHOICES, null=False, blank=False
|
||||||
)
|
)
|
||||||
|
number_pages = models.IntegerField(null=False, blank=False)
|
||||||
|
|
||||||
def upload_to(instance, filename):
|
def upload_to(instance, filename):
|
||||||
_, extension = filename.split(".")
|
_, extension = filename.split(".")
|
||||||
return "documents/%s_%s.%s" % (now, str(uuid.uuid4()), extension)
|
return "documents/%s_%s.%s" % (now(), str(uuid.uuid4()), extension)
|
||||||
|
|
||||||
file = models.FileField(upload_to=upload_to)
|
file = models.FileField(upload_to=upload_to)
|
||||||
|
|
||||||
date_uploaded = models.DateTimeField(default=now, editable=False)
|
date_uploaded = models.DateTimeField(default=now, editable=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return f"{self.name} ({self.document_type})"
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
from rest_framework.permissions import BasePermission
|
|
||||||
|
|
||||||
|
|
||||||
class IsStaff(BasePermission):
|
|
||||||
"""
|
|
||||||
Allows access only to users with staff role
|
|
||||||
"""
|
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
|
||||||
return bool(request.user and request.user.role == "staff")
|
|
|
@ -1,4 +1,5 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from config import settings
|
||||||
from .models import Document
|
from .models import Document
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +12,14 @@ class DocumentUploadSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Document
|
model = Document
|
||||||
fields = ["id", "name", "file", "document_type", "date_uploaded"]
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"file",
|
||||||
|
"document_type",
|
||||||
|
"number_pages",
|
||||||
|
"date_uploaded",
|
||||||
|
]
|
||||||
read_only_fields = ["id", "date-uploaded"]
|
read_only_fields = ["id", "date-uploaded"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,5 +37,38 @@ class DocumentSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Document
|
model = Document
|
||||||
fields = ["id", "name", "document_type", "date_uploaded"]
|
fields = ["id", "name", "document_type", "number_pages", "date_uploaded"]
|
||||||
read_only_fields = ["id", "name", "document_type", "date_uploaded"]
|
read_only_fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"document_type",
|
||||||
|
"number_pages",
|
||||||
|
"date_uploaded",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentFileSerializer(serializers.ModelSerializer):
|
||||||
|
# Read-only serializer which includes the actual link to the file
|
||||||
|
date_uploaded = serializers.DateTimeField(
|
||||||
|
format="%m-%d-%Y %I:%M %p", read_only=True
|
||||||
|
)
|
||||||
|
file = serializers.FileField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Document
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"document_type",
|
||||||
|
"file",
|
||||||
|
"number_pages",
|
||||||
|
"date_uploaded",
|
||||||
|
]
|
||||||
|
read_only_fields = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"document_type",
|
||||||
|
"number_pages",
|
||||||
|
"date_uploaded",
|
||||||
|
"file",
|
||||||
|
]
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from .views import DocumentUploadView, DocumentDeleteView, DocumentListView
|
from .views import (
|
||||||
|
DocumentUploadView,
|
||||||
|
DocumentDeleteView,
|
||||||
|
DocumentListView,
|
||||||
|
DocumentStaffListView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("upload/", DocumentUploadView.as_view()),
|
path("upload/", DocumentUploadView.as_view()),
|
||||||
path("delete/<int:pk>/", DocumentDeleteView.as_view()),
|
path("delete/<int:pk>/", DocumentDeleteView.as_view()),
|
||||||
path("list/", DocumentListView.as_view()),
|
path("list/", DocumentListView.as_view()),
|
||||||
|
path("list/staff/", DocumentStaffListView.as_view()),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,31 +1,56 @@
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
DocumentSerializer,
|
DocumentSerializer,
|
||||||
|
DocumentFileSerializer,
|
||||||
DocumentUploadSerializer,
|
DocumentUploadSerializer,
|
||||||
DocumentDeleteSerializer,
|
DocumentDeleteSerializer,
|
||||||
)
|
)
|
||||||
from .permissions import IsStaff
|
|
||||||
from .models import Document
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.pagination import PageNumberPagination
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
from accounts.permissions import IsStaff
|
||||||
|
from .models import Document
|
||||||
|
|
||||||
|
|
||||||
class DocumentUploadView(generics.CreateAPIView):
|
class DocumentUploadView(generics.CreateAPIView):
|
||||||
|
"""
|
||||||
|
Used by staff to upload documents.
|
||||||
|
"""
|
||||||
|
|
||||||
http_method_names = ["post"]
|
http_method_names = ["post"]
|
||||||
serializer_class = DocumentUploadSerializer
|
serializer_class = DocumentUploadSerializer
|
||||||
# permission_classes = [IsAuthenticated, IsStaff]
|
permission_classes = [IsAuthenticated, IsStaff]
|
||||||
|
|
||||||
|
|
||||||
class DocumentDeleteView(generics.DestroyAPIView):
|
class DocumentDeleteView(generics.DestroyAPIView):
|
||||||
|
"""
|
||||||
|
Used by staff to delete documents. Accepts the document id as a URL parameter
|
||||||
|
"""
|
||||||
|
|
||||||
http_method_names = ["delete"]
|
http_method_names = ["delete"]
|
||||||
serializer_class = DocumentDeleteSerializer
|
serializer_class = DocumentDeleteSerializer
|
||||||
queryset = Document.objects.all()
|
queryset = Document.objects.all()
|
||||||
# permission_classes = [IsAuthenticated, IsStaff]
|
permission_classes = [IsAuthenticated, IsStaff]
|
||||||
|
|
||||||
|
|
||||||
class DocumentListView(generics.ListAPIView):
|
class DocumentListView(generics.ListAPIView):
|
||||||
|
"""
|
||||||
|
Used by clients to view documents. Does not include actual download links to documents
|
||||||
|
"""
|
||||||
|
|
||||||
http_method_names = ["get"]
|
http_method_names = ["get"]
|
||||||
serializer_class = DocumentSerializer
|
serializer_class = DocumentSerializer
|
||||||
queryset = Document.objects.all()
|
queryset = Document.objects.all()
|
||||||
pagination_class = PageNumberPagination
|
pagination_class = PageNumberPagination
|
||||||
# permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentStaffListView(generics.ListAPIView):
|
||||||
|
"""
|
||||||
|
Used by staff to view documents. Includes actual download links to documents
|
||||||
|
"""
|
||||||
|
|
||||||
|
http_method_names = ["get"]
|
||||||
|
serializer_class = DocumentFileSerializer
|
||||||
|
queryset = Document.objects.all()
|
||||||
|
pagination_class = PageNumberPagination
|
||||||
|
permission_classes = [IsAuthenticated, IsStaff]
|
||||||
|
|
|
@ -9,6 +9,7 @@ colorama==0.4.6
|
||||||
cryptography==43.0.3
|
cryptography==43.0.3
|
||||||
defusedxml==0.8.0rc2
|
defusedxml==0.8.0rc2
|
||||||
Django==5.1.3
|
Django==5.1.3
|
||||||
|
django-cleanup==9.0.0
|
||||||
django-cors-headers==4.6.0
|
django-cors-headers==4.6.0
|
||||||
django-rest-framework==0.1.0
|
django-rest-framework==0.1.0
|
||||||
django-unfold==0.41.0
|
django-unfold==0.41.0
|
||||||
|
|
Loading…
Reference in a new issue