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): 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: with self.file.open('rb') as f:
md5 = hashlib.md5() md5 = hashlib.md5()
for byte_block in iter(lambda: f.read(4096), b""): 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) number = models.IntegerField(unique=True, db_index=True)
resolved = models.BooleanField(default=False) resolved = models.BooleanField(default=False)
note = models.TextField(blank=True) 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=[ attempts = models.IntegerField(default=5, validators=[
MaxValueValidator(10), MaxValueValidator(10),
MinValueValidator(0) 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 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): class PublicArchiveUploadSerializer(serializers.ModelSerializer):
ticket = serializers.ReadOnlyField(source='ticket.token')
class Meta: class Meta:
model = Archive model = Archive
fields = ['file', 'ticket'] 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' app_name = 'collector'
router = routers.DefaultRouter() 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 = [ urlpatterns = [

View File

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

View File

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