diff options
author | Sunil Shetye | 2024-06-12 11:57:49 +0530 |
---|---|---|
committer | Sunil Shetye | 2024-06-12 13:34:54 +0530 |
commit | e273a3245f9a52475dba75be909752d0e800c719 (patch) | |
tree | 49570cac9247533a47fba2c44b1347d5ef2c41c9 | |
parent | ad8b6ef26fe232346dfd223ed4c4c34bf939932e (diff) | |
download | Common-Interface-Project-e273a3245f9a52475dba75be909752d0e800c719.tar.gz Common-Interface-Project-e273a3245f9a52475dba75be909752d0e800c719.tar.bz2 Common-Interface-Project-e273a3245f9a52475dba75be909752d0e800c719.zip |
add save code
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | blocks/Makefile | 2 | ||||
-rw-r--r-- | blocks/saveAPI/__init__.py | 0 | ||||
-rw-r--r-- | blocks/saveAPI/admin.py | 22 | ||||
-rw-r--r-- | blocks/saveAPI/apps.py | 5 | ||||
-rw-r--r-- | blocks/saveAPI/dbrouters.py | 43 | ||||
-rw-r--r-- | blocks/saveAPI/models.py | 56 | ||||
-rw-r--r-- | blocks/saveAPI/serializers.py | 76 | ||||
-rw-r--r-- | blocks/saveAPI/tests.py | 1 | ||||
-rw-r--r-- | blocks/saveAPI/urls.py | 28 | ||||
-rw-r--r-- | blocks/saveAPI/views.py | 454 |
11 files changed, 687 insertions, 1 deletions
@@ -17,4 +17,5 @@ blocks/*.xml blocks/xcosblocks.py blocks/Xcos/xcosblocks.py blocks/blocks/xcosblocks/xcosblocks.py +blocks/eda-frontend/public/xcos2xml.xsl blocks/eda-frontend/src/static/xcos2xml.xsl diff --git a/blocks/Makefile b/blocks/Makefile index b9b2aab1..648ac1bb 100644 --- a/blocks/Makefile +++ b/blocks/Makefile @@ -16,7 +16,7 @@ XCOS2XMLFILES += $(sort $(wildcard xcos2xml/foot.xsl)) IMPORT := Xcos/xcosblocks.py TARGET := xcosblocks.py TARGET2 := $(BLOCKDIR)/$(TARGET) -TARGET3 := eda-frontend/src/static/xcos2xml.xsl +TARGET3 := eda-frontend/public/xcos2xml.xsl all : $(IMPORT) $(TARGET) $(TARGET2) $(TARGET3) diff --git a/blocks/saveAPI/__init__.py b/blocks/saveAPI/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/blocks/saveAPI/__init__.py diff --git a/blocks/saveAPI/admin.py b/blocks/saveAPI/admin.py new file mode 100644 index 00000000..0d75ce4e --- /dev/null +++ b/blocks/saveAPI/admin.py @@ -0,0 +1,22 @@ +from django.contrib import admin +from saveAPI.models import Gallery, StateSave +from django.forms import TextInput, Textarea +from django.db import models + + +@admin.register(StateSave) +class UserDiagrams(admin.ModelAdmin): + list_display = ('name', 'base64_image', + 'save_time', 'create_time') + list_filter = ('save_id',) + + +@admin.register(Gallery) +class GalleryDiagrams(admin.ModelAdmin): + list_display = ('name', 'image_tag', 'description', 'shared') + list_filter = ('save_time',) + search_fields = ('name', 'description') + formfield_overrides = { + models.CharField: {'widget': TextInput(attrs={'size': '50'})}, + models.TextField: {'widget': Textarea(attrs={'rows': 20, 'cols': 50})}, + } diff --git a/blocks/saveAPI/apps.py b/blocks/saveAPI/apps.py new file mode 100644 index 00000000..9bc55ff0 --- /dev/null +++ b/blocks/saveAPI/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class SaveapiConfig(AppConfig): + name = 'saveAPI' diff --git a/blocks/saveAPI/dbrouters.py b/blocks/saveAPI/dbrouters.py new file mode 100644 index 00000000..7a648da7 --- /dev/null +++ b/blocks/saveAPI/dbrouters.py @@ -0,0 +1,43 @@ +class mongoRouter: + """ + A router to control all database operations on models in the + auth and contenttypes applications. + """ + route_app_labels = {'saveAPI'} + + def db_for_read(self, model, **hints): + """ + Attempts to read auth and contenttypes models go to auth_db. + """ + if model._meta.app_label in self.route_app_labels: + return 'mongodb' + return None + + def db_for_write(self, model, **hints): + """ + Attempts to write auth and contenttypes models go to auth_db. + """ + if model._meta.app_label in self.route_app_labels: + return 'mongodb' + return None + + def allow_relation(self, obj1, obj2, **hints): + """ + Allow relations if a model in the auth or contenttypes apps is + involved. + """ + if ( + obj1._meta.app_label in self.route_app_labels or + obj2._meta.app_label in self.route_app_labels + ): + return True + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + """ + Make sure the auth and contenttypes apps only appear in the + 'auth_db' database. + """ + if app_label in self.route_app_labels: + return db == 'mongodb' + return None diff --git a/blocks/saveAPI/models.py b/blocks/saveAPI/models.py new file mode 100644 index 00000000..b2c399ac --- /dev/null +++ b/blocks/saveAPI/models.py @@ -0,0 +1,56 @@ +from django.conf import settings +from django.contrib.auth import get_user_model +from django.core.files.storage import FileSystemStorage +from django.db import models +from django.utils.safestring import mark_safe +import uuid + + +# For handling file uploads to a permenant direcrory +file_storage = FileSystemStorage( + location=settings.FILE_STORAGE_ROOT, base_url=settings.FILE_STORAGE_URL) + + +class StateSave(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=200, null=True) + description = models.CharField(max_length=400, null=True) + save_time = models.DateTimeField(auto_now=True, db_index=True) + create_time = models.DateTimeField(auto_now_add=True) + save_id = models.UUIDField(default=uuid.uuid4) + data_dump = models.TextField(null=False) + shared = models.BooleanField(default=False) + owner = models.ForeignKey( + get_user_model(), null=True, on_delete=models.CASCADE) + base64_image = models.ImageField( + upload_to='simulation_images', storage=file_storage, null=True) + + def save(self, *args, **kwargs): + super(StateSave, self).save(*args, **kwargs) + + def __str__(self): + return self.name + + +class Gallery(models.Model): + id = models.AutoField(primary_key=True) + save_id = models.CharField(unique=True, max_length=500, null=False) + data_dump = models.TextField(null=False) + name = models.CharField(max_length=100, default="Untitled") + description = models.CharField(max_length=1000) + media = models.ImageField( + upload_to='simulation_images', storage=file_storage, null=True) + shared = models.BooleanField(default=True) + save_time = models.DateTimeField(auto_now=True) + + # For Django Admin Panel + def image_tag(self): + print(file_storage) + if self.media: + return mark_safe('<img src="%s" style="width: 45px; height:45px;" />' % self.media.url) + else: + return 'No Image Found' + image_tag.short_description = 'Image' + + def __str__(self): + return self.name diff --git a/blocks/saveAPI/serializers.py b/blocks/saveAPI/serializers.py new file mode 100644 index 00000000..24f67c77 --- /dev/null +++ b/blocks/saveAPI/serializers.py @@ -0,0 +1,76 @@ +from rest_framework import serializers +from saveAPI.models import StateSave, Gallery +from django.core.files.base import ContentFile +import base64 +import six +import uuid +import imghdr + + +class Base64ImageField(serializers.ImageField): + + def to_internal_value(self, data): + _, data = self.update(data) + return super(Base64ImageField, self).to_internal_value(data) + + def update(self, data): + if isinstance(data, six.string_types): + if 'data:' in data and ';base64,' in data: + header, data = data.split(';base64,') + try: + decoded_file = base64.b64decode(data) + except TypeError: + self.fail('invalid_image') + file_name = str(uuid.uuid4()) + file_extension = imghdr.what(file_name, decoded_file) + complete_file_name = "%s.%s" % (file_name, file_extension,) + data = ContentFile(decoded_file, name=complete_file_name) + return complete_file_name, data + + +class StateSaveSerializer(serializers.ModelSerializer): + base64_image = Base64ImageField(max_length=None, use_url=True) + + class Meta: + model = StateSave + fields = ('id', + 'name', + 'description', + 'save_time', + 'create_time', + 'save_id', + 'data_dump', + 'shared', + 'owner', + 'base64_image', + ) + + +class SaveListSerializer(serializers.ModelSerializer): + base64_image = Base64ImageField(max_length=None, use_url=True) + + class Meta: + model = StateSave + fields = ('id', + 'name', + 'description', + 'save_time', + 'create_time', + 'save_id', + 'shared', + 'base64_image', + ) + + +class GallerySerializer(serializers.ModelSerializer): + media = Base64ImageField(max_length=None, use_url=True) + + class Meta: + model = Gallery + fields = ('save_id', + 'data_dump', + 'name', + 'description', + 'media', + 'shared', + ) diff --git a/blocks/saveAPI/tests.py b/blocks/saveAPI/tests.py new file mode 100644 index 00000000..a39b155a --- /dev/null +++ b/blocks/saveAPI/tests.py @@ -0,0 +1 @@ +# Create your tests here. diff --git a/blocks/saveAPI/urls.py b/blocks/saveAPI/urls.py new file mode 100644 index 00000000..5d5f3691 --- /dev/null +++ b/blocks/saveAPI/urls.py @@ -0,0 +1,28 @@ +from django.urls import path +from saveAPI import views as saveAPI_views +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register(r'save/search', saveAPI_views.SaveSearchViewSet, + basename='SaveSearch') + +urlpatterns = [ + path('save', saveAPI_views.StateSaveView.as_view(), + name='saveState'), + + path('save/list', saveAPI_views.UserSavesView.as_view(), + name='listSaves'), + + path("save/gallery", saveAPI_views.GalleryView.as_view(), + name="getGallery"), + + path('save/gallery/<str:save_id>', + saveAPI_views.GalleryFetchSaveDeleteView.as_view(), + name='fetchGallerySchematic'), + + path("save/<uuid:save_id>", saveAPI_views.DeleteDiagram.as_view(), + name="deleteDiagram"), + +] + +urlpatterns += router.urls diff --git a/blocks/saveAPI/views.py b/blocks/saveAPI/views.py new file mode 100644 index 00000000..e76d2497 --- /dev/null +++ b/blocks/saveAPI/views.py @@ -0,0 +1,454 @@ +from django.contrib.auth import get_user_model +import django_filters +from django_filters import rest_framework as filters +from drf_yasg.utils import swagger_auto_schema +import logging +from rest_framework import status, viewsets +from rest_framework.parsers import FormParser, JSONParser +from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView +import traceback +import uuid +from .models import Gallery, StateSave +from .serializers import Base64ImageField, GallerySerializer, \ + SaveListSerializer, StateSaveSerializer + +logger = logging.getLogger(__name__) + + +class StateSaveView(APIView): + ''' + API to save the state of project to db which can be loaded or shared later + Note: this is different from SnapshotSave which stores images + THIS WILL ESCAPE DOUBLE QUOTES + ''' + + # Permissions should be validated here + permission_classes = (AllowAny,) + # parser_classes = (FormParser,) + + @swagger_auto_schema(request_body=StateSaveSerializer) + def post(self, request, *args, **kwargs): + print("Getting Saved State") + + logger.info('Got POST for state save ') + try: + queryset = StateSave.objects.get( + save_id=request.data.get("save_id", None)) + serializer = StateSaveSerializer(data=request.data) + if serializer.is_valid(): + img = Base64ImageField(max_length=None, use_url=True) + filename, content = img.update(request.data['base64_image']) + queryset.data_dump = request.data.get("data_dump") + queryset.save() + queryset.base64_image.save(filename, content) + return Response(data=serializer.data, + status=status.HTTP_200_OK) + else: + return Response(data=serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + except StateSave.DoesNotExist: + try: + queryset = StateSave.objects.get( + save_id=request.data.get("save_id", None), + data_dump=request.data["data_dump"]) + serializer = StateSaveSerializer(data=request.data) + if serializer.is_valid(): + queryset.name = serializer.data["name"] + queryset.description = serializer.data["description"] + queryset.save() + response = serializer.data + response['duplicate'] = True + response['owner'] = queryset.owner.username + return Response(response) + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + except StateSave.DoesNotExist: + img = Base64ImageField(max_length=None, use_url=True) + filename, content = img.update(request.data['base64_image']) + try: + state_save = StateSave( + data_dump=request.data.get('data_dump'), + description=request.data.get('description'), + name=request.data.get('name'), + owner=request.user if request.user.is_authenticated else None, + shared=True, + ) + except Exception: + state_save = StateSave( + data_dump=request.data.get('data_dump'), + description=request.data.get('description'), + name=request.data.get('name'), + owner=request.user if request.user.is_authenticated else None, + ) + if request.data.get('save_id'): + state_save.save_id = request.data.get('save_id') + state_save.base64_image.save(filename, content) + try: + state_save.save() + return Response(StateSaveSerializer(state_save).data) + except Exception: + return Response(status=status.HTTP_400_BAD_REQUEST) + + +class CopyStateView(APIView): + permission_classes = (IsAuthenticated,) + parser_classes = (FormParser, JSONParser) + + def post(self, request, save_id): + if isinstance(save_id, uuid.UUID): + # Check for permissions and sharing settings here + try: + saved_state = StateSave.objects.get( + save_id=save_id) + except StateSave.DoesNotExist: + return Response({'error': 'Does not Exist'}, + status=status.HTTP_404_NOT_FOUND) + copy_state = StateSave(name=saved_state.name, + description=saved_state.description, + data_dump=saved_state.data_dump, + base64_image=saved_state.base64_image, + owner=self.request.user) + copy_state.save() + return Response( + {"save_id": copy_state.save_id}) + + +class StateFetchUpdateView(APIView): + """ + Returns Saved data for given save id , + Only user who saved the state can access / update it + THIS WILL ESCAPE DOUBLE QUOTES + + """ + permission_classes = (AllowAny,) + parser_classes = (FormParser, JSONParser) + methods = ['GET'] + + @swagger_auto_schema(responses={200: StateSaveSerializer}) + def get(self, request, save_id): + + if isinstance(save_id, uuid.UUID): + # Check for permissions and sharing settings here + try: + saved_state = StateSave.objects.get( + save_id=save_id) + except StateSave.DoesNotExist: + return Response({'error': 'Does not Exist'}, + status=status.HTTP_404_NOT_FOUND) + # Verifies owner + if self.request.user != saved_state.owner and not saved_state.shared: + print("Here") + return Response({'error': 'not the owner and not shared'}, + status=status.HTTP_401_UNAUTHORIZED) + try: + serialized = StateSaveSerializer( + saved_state, context={'request': request}) + User = get_user_model() + try: + owner_name = User.objects.get( + id=serialized.data.get('owner')) + data = {} + data.update(serialized.data) + data['owner'] = owner_name.username + except User.DoesNotExist: + data = {} + data.update(serialized.data) + data['owner'] = None + return Response(data) + except Exception: + traceback.print_exc() + return Response({'error': 'Not Able To Serialize'}, + status=status.HTTP_400_BAD_REQUEST) + else: + return Response({'error': 'Invalid sharing state'}, + status=status.HTTP_400_BAD_REQUEST) + + @swagger_auto_schema(responses={200: StateSaveSerializer}) + def post(self, request, save_id): + if isinstance(save_id, uuid.UUID): + # Check for permissions and sharing settings here + try: + saved_state = StateSave.objects.get(save_id=save_id) + except StateSave.DoesNotExist: + return Response({'error': 'Does not Exist'}, + status=status.HTTP_404_NOT_FOUND) + + # Verifies owner + if self.request.user != saved_state.owner: + return Response({'error': 'not the owner and not shared'}, + status=status.HTTP_401_UNAUTHORIZED) + + if not request.data['data_dump'] and not request.data['shared']: + return Response({'error': 'not a valid PUT request'}, + status=status.HTTP_406_NOT_ACCEPTABLE) + + try: + # if data dump, shared,name and description needs to be updated + if 'data_dump' in request.data: + saved_state.data_dump = request.data['data_dump'] + if 'shared' in request.data: + saved_state.shared = bool(request.data['shared']) + if 'name' in request.data: + saved_state.name = request.data['name'] + if 'description' in request.data: + saved_state.description = request.data['description'] + # if thumbnail needs to be updated + if 'base64_image' in request.data: + img = Base64ImageField(max_length=None, use_url=True) + filename, content = img.update( + request.data['base64_image']) + saved_state.base64_image.save(filename, content) + saved_state.save() + serialized = SaveListSerializer(saved_state) + return Response(serialized.data) + except Exception: + return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) + else: + return Response({'error': 'Invalid sharing state'}, + status=status.HTTP_400_BAD_REQUEST) + + @swagger_auto_schema(responses={200: StateSaveSerializer}) + def delete(self, request, save_id): + if isinstance(save_id, uuid.UUID): + try: + saved_state = StateSave.objects.get( + save_id=save_id, + owner=self.request.user) + except StateSave.DoesNotExist: + return Response({'error': 'Does not Exist'}, + status=status.HTTP_404_NOT_FOUND) + saved_state.delete() + return Response({'done': True}) + else: + return Response({'error': 'Invalid sharing state'}, + status=status.HTTP_400_BAD_REQUEST) + + +class StateShareView(APIView): + """ + Enables sharing for the given saved state + Note: Only authorized user can do this + + """ + permission_classes = (AllowAny,) + methods = ['GET'] + + @swagger_auto_schema(responses={200: StateSaveSerializer}) + def post(self, request, save_id, sharing): + + if isinstance(save_id, uuid.UUID): + # Check for permissions and sharing settings here + try: + saved_state = StateSave.objects.get( + save_id=save_id) + except StateSave.DoesNotExist: + return Response({'error': 'Does not Exist'}, + status=status.HTTP_404_NOT_FOUND) + + # Verifies owner + if self.request.user != saved_state.owner: + return Response({'error': 'Not the owner'}, + status=status.HTTP_401_UNAUTHORIZED) + try: + if sharing == 'on': + saved_state.shared = True + elif sharing == 'off': + saved_state.shared = False + else: + return Response({'error': 'Invalid sharing state'}, + status=status.HTTP_400_BAD_REQUEST) + saved_state.save() + serialized = StateSaveSerializer(saved_state) + return Response(serialized.data) + except Exception: + return Response(serialized.error) + else: + return Response({'error': 'Invalid sharing state'}, + status=status.HTTP_400_BAD_REQUEST) + + +class UserSavesView(APIView): + """ + Returns Saved data for given username, + Only user who saved the state can access it + THIS WILL ESCAPE DOUBLE QUOTES + + """ + permission_classes = (IsAuthenticated,) + parser_classes = (FormParser, JSONParser) + methods = ['GET'] + + @swagger_auto_schema(responses={200: StateSaveSerializer}) + def get(self, request): + saved_state = StateSave.objects.filter( + owner=self.request.user).order_by( + "save_id", "-save_time").distinct("save_id") + try: + serialized = StateSaveSerializer(saved_state, many=True) + return Response(serialized.data) + except Exception: + return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +class SaveSearchFilterSet(django_filters.FilterSet): + class Meta: + model = StateSave + fields = { + 'name': ['icontains'], + 'description': ['icontains'], + 'save_time': ['icontains'], + 'create_time': ['icontains'], + } + + +class SaveSearchViewSet(viewsets.ReadOnlyModelViewSet): + """ + Search Project + """ + + def get_queryset(self): + queryset = StateSave.objects.filter( + owner=self.request.user).order_by('-save_time') + return queryset + + serializer_class = SaveListSerializer + filter_backends = (filters.DjangoFilterBackend,) + filterset_class = SaveSearchFilterSet + + +class DeleteDiagram(APIView): + permission_classes = (IsAuthenticated,) + + def delete(self, request, save_id): + try: + queryset = StateSave.objects.filter( + save_id=save_id, + owner=self.request.user + ) + if queryset[0].project is None: + queryset.delete() + return Response(data=None, status=status.HTTP_204_NO_CONTENT) + else: + return Response(data=None, status=status.HTTP_400_BAD_REQUEST) + except StateSave.DoesNotExist: + return Response({"error": "Diagram not found"}, + status=status.HTTP_404_NOT_FOUND) + + +class GalleryView(APIView): + permission_classes = (AllowAny,) + parser_classes = (FormParser, JSONParser) + methods = ['GET'] + + @swagger_auto_schema(responses={200: GallerySerializer}) + def get(self, request): + galleryset = Gallery.objects.filter() + try: + serialized = GallerySerializer(galleryset, many=True) + return Response(serialized.data) + except Exception: + return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +class GalleryFetchSaveDeleteView(APIView): + + """ + Returns Saved data for given save id, + Only staff can add / delete it + THIS WILL ESCAPE DOUBLE QUOTES + + """ + # permission_classes = (AllowAny,) + parser_classes = (FormParser, JSONParser) + methods = ['GET'] + + @swagger_auto_schema(responses={200: GallerySerializer}) + def get(self, request, save_id): + + try: + saved_state = Gallery.objects.get( + save_id=save_id) + except Gallery.DoesNotExist: + return Response({'error': 'Does not Exist'}, + status=status.HTTP_404_NOT_FOUND) + try: + serialized = GallerySerializer( + saved_state, context={'request': request}) + data = {} + data.update(serialized.data) + return Response(data) + except Exception: + traceback.print_exc() + return Response({'error': 'Not Able To Serialize'}, + status=status.HTTP_400_BAD_REQUEST) + + @swagger_auto_schema(responses={200: GallerySerializer}) + def post(self, request, save_id): + + # Checking user roles + userRoles = self.request.user.groups.all() + staff = False + for userRole in userRoles: + if (self.request.user and self.request.user.is_authenticated and + userRole.customgroup and + userRole.customgroup.is_type_staff): + staff = True + if not staff: + return Response({'error': 'Not the owner'}, + status=status.HTTP_401_UNAUTHORIZED) + saved_state = Gallery() + if not (request.data['data_dump'] and request.data['media'] and + request.data['save_id']): + return Response({'error': 'not a valid POST request'}, + status=status.HTTP_406_NOT_ACCEPTABLE) + + # saves to gallery + try: + if 'save_id' in request.data: + saved_state.save_id = request.data['save_id'] + if 'data_dump' in request.data: + saved_state.data_dump = request.data['data_dump'] + if 'shared' in request.data: + saved_state.shared = bool(request.data['shared']) + if 'name' in request.data: + saved_state.name = request.data['name'] + if 'description' in request.data: + saved_state.description = request.data['description'] + if 'media' in request.data: + img = Base64ImageField(max_length=None, use_url=True) + filename, content = img.update( + request.data['media']) + saved_state.media.save(filename, content) + saved_state.save() + serialized = GallerySerializer(saved_state) + return Response(serialized.data) + except Exception: + return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + @swagger_auto_schema(responses={200: GallerySerializer}) + def delete(self, request, save_id): + try: + # Checking user roles + userRoles = self.request.user.groups.all() + staff = False + for userRole in userRoles: + if (self.request.user and self.request.user.is_authenticated + and userRole.customgroup and + userRole.customgroup.is_type_staff): + staff = True + if not staff: + return Response({'error': 'Not the owner'}, + status=status.HTTP_401_UNAUTHORIZED) + # Deltes from gallery + try: + saved_state = Gallery.objects.get( + save_id=save_id) + except Gallery.DoesNotExist: + return Response({'error': 'Does not Exist'}, + status=status.HTTP_404_NOT_FOUND) + saved_state.delete() + return Response({'done': True}) + except Exception: + return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) |