Add: modelviewsets for all models
This commit is contained in:
parent
90d7e64db3
commit
3f37ed95ed
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -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)
|
||||||
|
13
logs_collector/collector/permissions.py
Normal file
13
logs_collector/collector/permissions.py
Normal 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
|
@ -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'
|
||||||
|
]
|
||||||
|
@ -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 = [
|
||||||
|
|
||||||
|
@ -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'):
|
||||||
|
@ -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, )
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user