Add: modelviewsets for all models

This commit is contained in:
Stepan Zhukovsky 2023-08-11 10:38:47 +09:00
parent 90d7e64db3
commit 3f37ed95ed
8 changed files with 147 additions and 22 deletions

View File

@ -0,0 +1,25 @@
# Generated by Django 4.2 on 2023-08-10 03:24
import django.core.validators
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('collector', '0007_rename_upload_ticket_attempts'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='attempts',
field=models.IntegerField(default=5, validators=[django.core.validators.MaxValueValidator(10), django.core.validators.MinValueValidator(0)]),
),
migrations.AlterField(
model_name='ticket',
name='token',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
]

View File

@ -41,7 +41,7 @@ class Archive(models.Model):
)
def save(self, *args, **kwargs):
# calculate sha 1 hash sum and write md5 field to db
# calculate md5 hash sum and write md5 field to db
with self.file.open('rb') as f:
md5 = hashlib.md5()
for byte_block in iter(lambda: f.read(4096), b""):
@ -72,7 +72,7 @@ class Ticket(models.Model):
number = models.IntegerField(unique=True, db_index=True)
resolved = models.BooleanField(default=False)
note = models.TextField(blank=True)
token = models.UUIDField(default=uuid.uuid4, editable=False)
token = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
attempts = models.IntegerField(default=5, validators=[
MaxValueValidator(10),
MinValueValidator(0)

View File

@ -0,0 +1,13 @@
from rest_framework import permissions
class IsGuestUpload(permissions.BasePermission):
"""
Special permission class for the ability to upload attachments
to an unauthorized user using a ticket token
"""
def has_permission(self, request, view):
if request.method in ('HEAD', 'OPTIONS', 'POST',):
return True
return request.user.is_authenticated

View File

@ -1,11 +1,59 @@
from rest_framework import serializers
from .models import Archive
from .models import Archive, Platform, Ticket
class TimestampField(serializers.Field):
def to_representation(self, value):
return value.timestamp()
class JsTimestampField(serializers.Field):
def to_representation(self, value):
return round(value.timestamp()*1000)
class PublicArchiveUploadSerializer(serializers.ModelSerializer):
ticket = serializers.ReadOnlyField(source='ticket.token')
class Meta:
model = Archive
fields = ['file', 'ticket']
class ArchiveSerializer(serializers.ModelSerializer):
time_create = JsTimestampField(read_only=True)
class Meta:
model = Archive
fields = ['id', 'file', 'ticket', 'time_create']
def to_representation(self, instance):
print(int(round(instance.time_create.timestamp())))
return super().to_representation(instance)
class PlatformSerializer(serializers.ModelSerializer):
class Meta:
model = Platform
fields = ['id', 'name', 'pretty_name']
class TicketSerializer(serializers.ModelSerializer):
time_create = JsTimestampField(read_only=True)
time_update = JsTimestampField(read_only=True)
token = serializers.UUIDField(read_only=True)
user = serializers.ReadOnlyField(source='user.username')
class Meta:
model = Ticket
fields = [
'id',
'number',
'resolved',
'token',
'attempts',
'platform',
'time_create',
'time_update',
'user'
]

View File

@ -7,7 +7,9 @@ from . import views
app_name = 'collector'
router = routers.DefaultRouter()
router.register(r'archives', views.PublicArchiveUploadViewSet)
router.register(r'archives', views.ArchiveViewSet)
router.register(r'platforms', views.PlatformViewSet)
router.register(r'tickets', views.TicketViewSet)
urlpatterns = [

View File

@ -3,8 +3,8 @@ import os
def logs_dir_path(instance, filename):
# file will be uploaded to
# MEDIA_ROOT_FOR_SENSITIVE_FILES/<ticket>/<filename>
return f'{instance.ticket}/{filename}'
# MEDIA_ROOT_FOR_SENSITIVE_FILES/<ticket-token>/<filename>
return f'{instance.ticket.number}/{filename}'
def get_file_size(file_path, unit='bytes'):

View File

@ -1,5 +1,5 @@
import json
from django.core.exceptions import ValidationError
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import FileResponse, JsonResponse
from django.views import generic
@ -8,17 +8,25 @@ from django.urls import reverse_lazy
from django.db.models import Q
from rest_framework import status
from rest_framework.response import Response
# from rest_framework.decorators import action
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
# from rest_framework import mixins
from rest_framework import viewsets
from .models import Archive, Ticket
from .models import Archive, Ticket, Platform
from .forms import TicketForm
from .utils import PageTitleViewMixin, is_ajax
from .permissions import IsGuestUpload
from .serializers import PublicArchiveUploadSerializer
from .serializers import (
PublicArchiveUploadSerializer,
ArchiveSerializer,
PlatformSerializer,
TicketSerializer
)
class ArchiveHandlerView(LoginRequiredMixin, SingleObjectMixin, generic.View):
@ -174,15 +182,16 @@ class DeleteTicketHandler(SingleObjectMixin, generic.View):
)
class PublicArchiveUploadViewSet(mixins.CreateModelMixin, GenericViewSet):
class ArchiveViewSet(viewsets.ModelViewSet):
queryset = Archive.objects.order_by('-time_create')
serializer_class = PublicArchiveUploadSerializer
serializer_class = ArchiveSerializer
parser_classes = (MultiPartParser, FormParser)
permission_classes = (IsGuestUpload, )
def create(self, request, *args, **kwargs):
# ! upload-token protection:
upload_token = request.headers.get('upload-token', '')
if upload_token:
if not request.user.is_authenticated and upload_token:
try:
bound_ticket = Ticket.objects.get(token=upload_token)
if bound_ticket.resolved:
@ -198,13 +207,15 @@ class PublicArchiveUploadViewSet(mixins.CreateModelMixin, GenericViewSet):
bound_ticket.attempts -= 1
bound_ticket.save()
# ? mixin bound ticket to request.data from user
request.data['ticket'] = bound_ticket
except ValidationError:
request.data['ticket'] = bound_ticket.number
# ? change serializer for guest user
self.serializer_class = PublicArchiveUploadSerializer
except (ValidationError, ObjectDoesNotExist,):
return Response(
{'error': f'token {upload_token} is not valid'},
status=status.HTTP_403_FORBIDDEN
)
else:
elif not request.user.is_authenticated:
return Response(
{'error': 'Header Upload-Token is required'},
status=status.HTTP_401_UNAUTHORIZED
@ -220,5 +231,16 @@ class PublicArchiveUploadViewSet(mixins.CreateModelMixin, GenericViewSet):
headers=headers
)
def perform_create(self, serializer):
serializer.save(ticket=self.request.data['ticket'])
class PlatformViewSet(viewsets.ModelViewSet):
queryset = Platform.objects.all()
lookup_field = 'name'
serializer_class = PlatformSerializer
permission_classes = (IsAuthenticated, )
class TicketViewSet(viewsets.ModelViewSet):
queryset = Ticket.objects.order_by('-time_create')
lookup_field = 'number'
serializer_class = TicketSerializer
permission_classes = (IsAuthenticated, )

View File

@ -135,3 +135,18 @@ MEDIA_URL_FOR_SENSITIVE_FILES = '/archives/'
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
# https://www.django-rest-framework.org/api-guide/settings/
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.renderers.BrowsableAPIRenderer',
'rest_framework.parsers.MultiPartParser'
],
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', # noqa:E501
# 'PAGE_SIZE': 3,
}