diff --git a/edivorce/apps/core/models.py b/edivorce/apps/core/models.py index 7397e5e3..a593bc60 100644 --- a/edivorce/apps/core/models.py +++ b/edivorce/apps/core/models.py @@ -143,6 +143,7 @@ class Document(models.Model): class Meta: unique_together = ("bceid_user", "doc_type", "party_code", "filename", "size") + ordering = ('sort_order',) def save(self, *args, **kwargs): if not self.filename: diff --git a/edivorce/apps/core/redis.py b/edivorce/apps/core/redis.py index c84c99ab..971ff93f 100644 --- a/edivorce/apps/core/redis.py +++ b/edivorce/apps/core/redis.py @@ -15,7 +15,7 @@ from django.core.files.base import File from django.core.files.storage import Storage from django.utils.deconstruct import deconstructible -EX_EXPIRY = 60*24*7 # 1 week expiry +EX_EXPIRY = 60*60*24*30 # 1 month expiry in seconds def generate_unique_filename(instance, filename): diff --git a/edivorce/apps/core/views/api.py b/edivorce/apps/core/views/api.py index 8215a636..d968e4d2 100644 --- a/edivorce/apps/core/views/api.py +++ b/edivorce/apps/core/views/api.py @@ -1,4 +1,7 @@ +import graphene +import graphene_django from django.http import HttpResponse, HttpResponseGone +from graphql import GraphQLError from rest_framework import permissions, status from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveUpdateDestroyAPIView from rest_framework.views import APIView @@ -86,3 +89,68 @@ class DocumentView(RetrieveUpdateDestroyAPIView): doc.delete() return HttpResponseGone('File no longer exists') return HttpResponse(file_contents, content_type=content_type) + + +class DocumentType(graphene_django.DjangoObjectType): + url = graphene.String(source='get_file_url') + + class Meta: + model = Document + + +class Query(graphene.ObjectType): + documents = graphene.List(DocumentType, doc_type=graphene.String(required=True), party_code=graphene.Int(required=True)) + + def resolve_documents(self, info, **kwargs): + if info.context.user.is_anonymous: + raise GraphQLError('Unauthorized') + q = Document.objects.filter(bceid_user=info.context.user, **kwargs) + return q + + +class DocumentInput(graphene.InputObjectType): + filename = graphene.String(required=True) + size = graphene.Int(required=True) + rotation = graphene.Int() + + +class DocumentMetaDataInput(graphene.InputObjectType): + files = graphene.List(DocumentInput, required=True) + doc_type = graphene.String(required=True) + party_code = graphene.Int(required=True) + + +class UpdateMetadata(graphene.Mutation): + class Arguments: + input = DocumentMetaDataInput(required=True) + + documents = graphene.List(DocumentType) + + def mutate(self, info, **kwargs): + if info.context.user.is_anonymous: + raise GraphQLError('Unauthorized') + input_ = kwargs['input'] + documents = Document.objects.filter(bceid_user=info.context.user, doc_type=input_['doc_type'], party_code=input_['party_code']) + + unique_files = [dict(s) for s in set(frozenset(d.items()) for d in input_['files'])] + if documents.count() != len(input_['files']) or documents.count() != len(unique_files): + raise GraphQLError("Invalid input: there must be the same number of files") + + for i, file in enumerate(input_['files']): + try: + doc = documents.get(filename=file['filename'], size=file['size']) + doc.sort_order = i + 1 + doc.rotation = file.get('rotation', file.rotation) + if doc.rotation not in [0, 90, 180, 270]: + raise GraphQLError(f"Invalid rotation {doc.rotation}, must be 0, 90, 180, 270") + doc.save() + except Document.DoesNotExist: + raise GraphQLError(f"Couldn't find document '{file['filename']}' with size '{file['size']}'") + return UpdateMetadata(documents=documents.all()) + + +class Mutations(graphene.ObjectType): + update_metadata = UpdateMetadata.Field() + + +graphql_schema = graphene.Schema(query=Query, mutation=Mutations) diff --git a/edivorce/settings/base.py b/edivorce/settings/base.py index 0395910a..f1660750 100644 --- a/edivorce/settings/base.py +++ b/edivorce/settings/base.py @@ -52,6 +52,7 @@ INSTALLED_APPS = ( 'compressor', 'crispy_forms', 'sass_processor', + 'graphene_django', ) # add the POC app only if applicable @@ -104,6 +105,11 @@ REST_FRAMEWORK = { ] } +GRAPHENE = { + 'SCHEMA': 'edivorce.apps.core.views.api.graphql_schema' +} + + LOGGING = { 'version': 1, 'disable_existing_loggers': False, diff --git a/edivorce/urls.py b/edivorce/urls.py index 24a9c6f3..6b75974c 100644 --- a/edivorce/urls.py +++ b/edivorce/urls.py @@ -1,6 +1,9 @@ from django.conf import settings from django.conf.urls import include, url from django.contrib import admin +from django.urls import path +from graphene_django.views import GraphQLView + from .apps.core.views import main urlpatterns = [] @@ -9,6 +12,9 @@ if settings.ENVIRONMENT in ['localdev', 'dev', 'test', 'minishift']: import debug_toolbar urlpatterns.append(url(r'^__debug__/', include(debug_toolbar.urls)),) urlpatterns.append(url(r'^poc/', include('edivorce.apps.poc.urls'))) + urlpatterns.append(path('api/graphql/', GraphQLView.as_view(graphiql=True))), +else: + urlpatterns.append(path('api/graphql/', GraphQLView.as_view(graphiql=False))), if settings.ENVIRONMENT in ['localdev', 'minishift']: urlpatterns.append(url(r'^admin/', admin.site.urls))