Skip to content

Commit

Permalink
#288 feat: WIP - Update PDF upload and render logic
Browse files Browse the repository at this point in the history
  • Loading branch information
afonsobspinto committed Nov 29, 2023
1 parent 2bd4102 commit e08a98b
Show file tree
Hide file tree
Showing 22 changed files with 901 additions and 530 deletions.
52 changes: 9 additions & 43 deletions applications/portal/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1216,31 +1216,6 @@ paths:
description: ''
tags:
- api
/api/population/{id}/upload-pdf-files/:
post:
operationId: uploadPdfFilePopulation
description: This viewset automatically provides `list` actions.
parameters:
- name: id
in: path
required: true
description: A unique integer value identifying this population.
schema:
type: string
requestBody:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/PopulationPDFUpload'
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/PopulationPDFUpload'
description: ''
tags:
- api
/api/teams/{id}/add_members/:
post:
operationId: addMembersGroup
Expand Down Expand Up @@ -1579,7 +1554,7 @@ components:
readOnly: true
name:
type: string
maxLength: 40
maxLength: 100
category:
enum:
- ELECTROPHYSIOLOGY
Expand All @@ -1591,15 +1566,21 @@ components:
format: date-time
readOnly: true
created_by:
type: integer
type: string
readOnly: true
file:
type: string
format: binary
nullable: true
experiment:
type: string
readOnly: true
population:
type: string
readOnly: true
required:
- name
- category
- created_by
Tag:
type: object
properties:
Expand Down Expand Up @@ -1729,21 +1710,6 @@ components:
format: binary
required:
- file
PopulationPDFUpload:
type: object
properties:
pdf_file:
type: string
format: binary
category:
enum:
- ELECTROPHYSIOLOGY
- BEHAVIOUR
- IO_MAPPING
type: string
required:
- pdf_file
- category
Member:
type: object
properties:
Expand Down
7 changes: 6 additions & 1 deletion applications/portal/backend/api/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib import admin

from api.models import Collaborator, Experiment, Population, Tag, UserDetail
from api.models import Collaborator, Experiment, Population, Tag, UserDetail, Pdf


class PopulationInline(admin.TabularInline):
Expand Down Expand Up @@ -39,7 +39,12 @@ class TagAdmin(admin.ModelAdmin):
pass


class PDFAdmin(admin.ModelAdmin):
pass


admin.site.register(UserDetail, UserDetailAdmin)
admin.site.register(Experiment, ExperimentAdmin)
admin.site.register(Collaborator, CollaboratorAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Pdf, PDFAdmin)
10 changes: 9 additions & 1 deletion applications/portal/backend/api/migrations/0004_pdf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2 on 2023-11-28 14:41
# Generated by Django 4.2 on 2023-11-29 02:51

from django.conf import settings
from django.db import migrations, models
Expand Down Expand Up @@ -51,6 +51,14 @@ class Migration(migrations.Migration):
to=settings.AUTH_USER_MODEL,
),
),
(
"experiment",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="experiment_pdfs",
to="api.experiment",
),
),
(
"population",
models.ForeignKey(
Expand Down
50 changes: 28 additions & 22 deletions applications/portal/backend/api/models/pdf.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,56 @@

from django.db import models
from django.contrib.auth.models import User

from . import Experiment
from .population import Population


class PDFCategory(models.TextChoices):
ELECTROPHYSIOLOGY = 'ELECTROPHYSIOLOGY'
BEHAVIOUR = 'BEHAVIOUR'
IO_MAPPING = 'IO_MAPPING'
ELECTROPHYSIOLOGY = 'ELECTROPHYSIOLOGY'
BEHAVIOUR = 'BEHAVIOUR'
IO_MAPPING = 'IO_MAPPING'


'''
PopulationPDF associate to each population.
Population can have multiple PDFs of any of following categories:
- ELECTROPHYSIOLOGY, BEHAVIOUR, IO_MAPPING
'''


class Pdf(models.Model):
name = models.CharField(max_length=100)
category = models.CharField(max_length=40, choices=PDFCategory.choices)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
User,
related_name="pdf_created_by",
on_delete=models.CASCADE,
)
User,
related_name="pdf_created_by",
on_delete=models.CASCADE,
)
file = models.FileField(null=True, blank=True, max_length=255)
population = models.ForeignKey(
Population,
related_name="population_pdf",
on_delete=models.CASCADE,
)

Population,
related_name="population_pdf",
on_delete=models.CASCADE,
)
experiment = models.ForeignKey(
Experiment,
on_delete=models.CASCADE,
related_name="experiment_pdfs"
)

@staticmethod
def has_read_permission(request):
return True

@staticmethod
def has_list_permission(request):
return True

@staticmethod
def has_destroy_permission(request):
return True

def has_object_write_permission(self, request):
# return self.population.experiment and self.population.experiment.has_object_write_permission(request)
def has_write_permission(request):
return True

def has_object_read_permission(self, request):
if request.user == self.created_by:
return True
return not self.experiment.is_private

def has_object_destroy_permission(self, request):
return request.user == self.created_by
3 changes: 1 addition & 2 deletions applications/portal/backend/api/models/population.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,7 @@ def has_object_read_permission(self, request):
return self.experiment is None or self.experiment.has_object_read_permission(request)

def has_object_write_permission(self, request):
# return self.experiment and self.experiment.has_object_write_permission(request)
return True
return self.experiment and self.experiment.has_object_write_permission(request)

def update_color(self):
if not is_valid_hex_str(self.color):
Expand Down
35 changes: 20 additions & 15 deletions applications/portal/backend/api/serializers/pdf.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
from rest_framework import serializers
from api.models import Pdf, PDFCategory
import os

from api.models import Pdf


class PdfSerializer(serializers.ModelSerializer):
created_by = serializers.SerializerMethodField()
class Meta:
model = Pdf
fields = (
"id",
"name",
"category",
"created_at",
"created_by",
"file"
)
created_by = serializers.SerializerMethodField()
experiment = serializers.PrimaryKeyRelatedField(read_only=True)
population = serializers.PrimaryKeyRelatedField(read_only=True)

class Meta:
model = Pdf
fields = (
"id",
"name",
"category",
"created_at",
"created_by",
"file",
"experiment",
"population",
)

def get_created_by(self, obj):
return obj.created_by.first_name + " " + obj.created_by.last_name
def get_created_by(self, obj):
return obj.created_by.first_name + " " + obj.created_by.last_name
26 changes: 13 additions & 13 deletions applications/portal/backend/api/serializers/population.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework import serializers

from api.models import AtlasesChoice, Population, PDFCategory
from api.models import AtlasesChoice, Population, Pdf
from api.serializers.pdf import PdfSerializer


Expand All @@ -27,19 +27,19 @@ class Meta:
)

def get_pdfs(self, obj):
pdfs = obj.population_pdf.filter(
created_by=self.context["request"].user
).order_by("-created_at")
return PdfSerializer(pdfs, many=True).data
request = self.context.get("request")

# Check if there's an experiment ID in the request
experiment_id = request.query_params.get("experiment") if request else None

if experiment_id:
pdfs = obj.population_pdf.filter(
experiment_id=experiment_id,
).order_by("-created_at")
else:
pdfs = Pdf.objects.none()

return PdfSerializer(pdfs, many=True).data


class PopulationPDFUploadSerializer(serializers.Serializer):
pdf_file = serializers.FileField(required=True)
category = serializers.ChoiceField(
choices=PDFCategory.choices, required=True
)

class Meta:
model = Population
fields = ()
54 changes: 50 additions & 4 deletions applications/portal/backend/api/views/rest/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

from dry_rest_permissions.generics import DRYPermissions
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser
from rest_framework.response import Response

from api.models import Pdf
from api.helpers.exceptions import InvalidPDFFile
from api.models import Pdf, Population, Experiment
from api.serializers import PdfSerializer
from api.services.user_service import is_user_owner
from api.services.pdf_service import get_pdf_save_dir
from api.services.pdf_service import get_pdf_save_dir, save_pdf_and_get_path
import os

log = logging.getLogger("__name__")
Expand All @@ -22,17 +25,60 @@ class PDFViewSet(viewsets.ModelViewSet):
serializer_class = PdfSerializer
queryset = Pdf.objects.all()

def partial_update(self, request, *args, **kwargs):
# Disable PATCH
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

def update(self, request, *args, **kwargs):
# Disable PUT
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

def destroy(self, request, *args, **kwargs):
instance = self.get_object()
population_obj = instance.population

if population_obj.experiment and (not is_user_owner(request, population_obj)):
return Response(status=status.HTTP_403_FORBIDDEN)

pdf_storage_path = get_pdf_save_dir(
population_obj.storage_path, instance)
if os.path.exists(pdf_storage_path):
os.remove(pdf_storage_path)

instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

def create(self, request, **kwargs):
pdf_file = request.FILES.get("pdf_file")
category = request.data.get("category")
population_id = request.data.get("population_id")
experiment_id = request.data.get("experiment_id")

# Retrieve the Population and Experiment instances
try:
population = Population.objects.get(id=population_id)
experiment = Experiment.objects.get(id=experiment_id)
except (Population.DoesNotExist, Experiment.DoesNotExist):
return Response(status=status.HTTP_404_NOT_FOUND)

if experiment and (not is_user_owner(request, experiment)):
return Response(status=status.HTTP_403_FORBIDDEN)

try:
pdf_obj = Pdf.objects.create(
population=population, experiment=experiment,
created_by=request.user, category=category, name=pdf_file.name
)
pdf_path = save_pdf_and_get_path(
pdf_obj=pdf_obj,
population_storage_path=population.storage_path, pdf_file=pdf_file
)
pdf_obj.file = pdf_path
pdf_obj.save()

pdf_serializer = PdfSerializer(pdf_obj)
return Response(pdf_serializer.data, status=status.HTTP_201_CREATED)
except InvalidPDFFile as e:
return Response(data={'detail': str(e)}, status=status.HTTP_422_UNPROCESSABLE_ENTITY)
except Exception as e:
return Response(data={'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
Loading

0 comments on commit e08a98b

Please sign in to comment.