From 370f149acf7c5d31965e9543d9055d708874be76 Mon Sep 17 00:00:00 2001 From: Seth Ladygo Date: Fri, 21 Feb 2020 16:38:23 -0800 Subject: [PATCH 1/7] webdav: initial ugly but mostly working proof of concept --- procat2/resource.py | 369 ++++++++++++++++++++++++++++++++++++++++++++ procat2/settings.py | 2 + procat2/urls.py | 5 + requirements.txt | 5 + 4 files changed, 381 insertions(+) create mode 100644 procat2/resource.py diff --git a/procat2/resource.py b/procat2/resource.py new file mode 100644 index 0000000..c850386 --- /dev/null +++ b/procat2/resource.py @@ -0,0 +1,369 @@ +import datetime +import os +import shutil +from sys import getfilesystemencoding + +import logging + +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt + +from django.core.exceptions import PermissionDenied, ValidationError +from djangodav.utils import WEBDAV_NSMAP, D, url_join, get_property_tag_list, rfc1123_date, rfc5987_content_disposition +from django.utils.timezone import now + +from djangodav.responses import HttpResponsePreconditionFailed, HttpResponseCreated, HttpResponseNoContent, \ + HttpResponseConflict, HttpResponseMediatypeNotSupported, HttpResponseBadGateway, HttpResponseMultiStatus, \ + HttpResponseLocked, ResponseException + +#from djangodav.fs.resources import DummyFSDAVResource +from djangodav.acls import FullAcl +from djangodav.base.resources import BaseDavResource +from djangodav.base.resources import MetaEtagMixIn +from djangodav.locks import DummyLock +from djangodav.utils import url_join +from djangodav.views import DavView + +# import lxml xml parser +from lxml import etree +# use defusedxmls parse function +from defusedxml.lxml import parse + +from rest_framework.authentication import SessionAuthentication, BasicAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from .models import Catalog + +log = logging.getLogger(__name__) + +fs_encoding = getfilesystemencoding() + + +class ExampleView(APIView): + authentication_classes = [SessionAuthentication, BasicAuthentication] + permission_classes = [IsAuthenticated] + + def get(self, request, format=None): + content = { + 'user': str(request.user), # `django.contrib.auth.User` instance. + 'auth': str(request.auth), # None + } + return Response(content) + + +class AuthDavView(DavView, APIView): + authentication_classes = [BasicAuthentication] + #authentication_classes = [SessionAuthentication, BasicAuthentication] + permission_classes = [IsAuthenticated] + auth_user = None + path = "" + + def __init__(self): + log.info(f"AuthDavView init") + super(DavView, self).__init__(resource_class=MarkupDavResource, lock_class=DummyLock, acl_class=FullAcl) + + # def get(self, request, format=None): + def get(self, request, head=False, *args, **kwargs): + # content = { + # 'user': str(request.user), # `django.contrib.auth.User` instance. + # 'auth': str(request.auth), # None + # } + # return Response(content) + self.auth_user = request.user + log.info(f'AuthDavView GET auth user: {request.user}') + path = "" + return super(DavView, self).get(request, path, head=False, *args, **kwargs) + + def dispatch(self, request, path, *args, **kwargs): + log.info(f'AuthDavView dispatch 0') + if path: + self.path = path + self.base_url = request.META['PATH_INFO'][:-len(self.path)] + else: + self.path = '/' + self.base_url = request.META['PATH_INFO'] + + # log.info(f'AuthDavView dispatch 1') + # super(APIView, self).dispatch(request, *args, **kwargs) + # log.info(f'AuthDavView dispatch 2') + # super(DavView, self).dispatch(request, *args, **kwargs) + # log.info(f'AuthDavView dispatch 3') + + # FROM APIView + self.args = args + self.kwargs = kwargs + request = self.initialize_request(request, *args, **kwargs) + self.request = request + self.headers = self.default_response_headers # deprecate? + + try: + self.initial(request, *args, **kwargs) + + # TODO HERE? + + meta = request.META.get + self.xbody = kwargs['xbody'] = None + if (request.method.lower() != 'put' + and "/xml" in meta('CONTENT_TYPE', '') + and meta('CONTENT_LENGTH', 0) != '' + and int(meta('CONTENT_LENGTH', 0)) > 0): + + # parse XML using defusedxmls parse function + self.xbody = kwargs['xbody'] = etree.XPathDocumentEvaluator( + parse(request, etree.XMLParser(ns_clean=True, resolve_entities=True)), + namespaces=WEBDAV_NSMAP + ) + + if request.method.upper() in self._allowed_methods(): + handler = getattr(self, request.method.lower(), self.http_method_not_allowed) + else: + handler = self.http_method_not_allowed + #try: + log.info(f"AuthDavView DISPATCH {handler} for {path}") + resp = handler(request, self.path, *args, **kwargs) + except ResponseException as e: + print(e) + resp = e.response + except PermissionDenied as pe: + print(pe) + resp = HttpResponseForbidden() + except ValidationError as ve: + print(ve) + resp = HttpResponseBadRequest() + except Exception as exc: + resp = self.handle_exception(exc) + self.response = self.finalize_response(request, resp, *args, **kwargs) + return self.response + + if not 'Allow' in resp: + methods = self._allowed_methods() + if methods: + resp['Allow'] = ", ".join(methods) + if not 'Date' in resp: + resp['Date'] = rfc1123_date(now()) + if self.server_header: + resp['Server'] = self.server_header + return resp + + + # TODO END HERE + + # Get the appropriate handler method + # if request.method.lower() in self.http_method_names: + # handler = getattr(self, request.method.lower(), + # self.http_method_not_allowed) + # else: + # handler = self.http_method_not_allowed + + # response = handler(request, *args, **kwargs) + + # except Exception as exc: + # response = self.handle_exception(exc) + + # self.response = self.finalize_response(request, response, *args, **kwargs) + # return self.response + + + def options(self, request, path, *args, **kwargs): + """ + Handler method for HTTP 'OPTIONS' request. + """ + log.info('AuthDavView OPTIONS') + # FROM APIView + # if self.metadata_class is None: + # return self.http_method_not_allowed(request, *args, **kwargs) + # data = self.metadata_class().determine_metadata(request, self) + # return Response(data, status=status.HTTP_200_OK) + + # FROM DavView + if not self.has_access(self.resource, 'read'): + return self.no_access() + response = self.build_xml_response() + response['DAV'] = '1,2' + response['Content-Length'] = '0' + if self.path in ('/', '*'): + return response + response['Allow'] = ", ".join(self._allowed_methods()) + if self.resource.exists and self.resource.is_object: + response['Accept-Ranges'] = 'bytes' + return response + + # @method_decorator(csrf_exempt) + # def dispatch(self, request, path, *args, **kwargs): + # self.auth_user = request.user + # log.info(f'auth user: {request.user}') + # return super().dispatch(request, path, *args, **kwargs) + + +class MarkupDavResource(MetaEtagMixIn, BaseDavResource): + root = '/opt/imagebank/mkbeta/webdav' # TODO replace with settings var + + def __init__(self, path): + log.info(f"MarkupDavResource INIT {path}") + super().__init__(path) + + def __str__(self): + return f"" + + def get_children(self): + """Return an iterator of all direct children of this resource.""" + # make sure the current object is a directory + path = self.get_path() + log.info(f"get_children of {path}") + + children = [] + if path == '/': + #children = ['My Catalogs', 'For Markup'] + children = ['MyCatalogs'] + elif path == '/MyCatalogs': + # TODO return user's catalogs + # get User + children = None + elif path == '/For Markup': + children = None + # else: + # path = self.get_abs_path() + # if os.path.isdir(path): + # children = os.listdir(path) + + for child in children: + try: + is_unicode = isinstance(child, str) + except NameError: # Python 3 fix + is_unicode = isinstance(child, str) + if not is_unicode: + child = child.decode(fs_encoding) + log.info(f'returning kid {self.clone(url_join(*(self.path + [child])))}') + yield self.clone(url_join(*(self.path + [child]))) + + + def get_abs_path(self): + """Return the absolute path of the resource. Used internally to interface with + an actual file system. If you override all other methods, this one will not + be used.""" + return os.path.join(self.root, *self.path) + + @property + def getcontentlength(self): + """Return the size of the resource in bytes.""" + if self.is_collection: + return 0 + else: + return os.path.getsize(self.get_abs_path()) + + def get_created(self): + """Return the create time as datetime object.""" + if self.is_collection: + return datetime.datetime.now() # TODO last created time of object in there? + else: + return datetime.datetime.fromtimestamp(os.stat(self.get_abs_path()).st_ctime) + + def get_modified(self): + """Return the modified time as datetime object.""" + if self.is_collection: + return datetime.datetime.now() # TODO last modified time of object in there? + else: + return datetime.datetime.fromtimestamp(os.stat(self.get_abs_path()).st_mtime) + + @property + def is_collection(self): + """Return True if this resource is a directory (collection in WebDAV parlance).""" + path = '/'.join(self.path) + # log.info(f"is_collection {self.path}") + + if path == '': + return True + elif path == 'MyCatalogs': + return True + elif path == 'For Markup': + return True + + log.info(f"is_collection {self.path} FALLTHROUGH") + return False + # return os.path.isdir(self.get_abs_path()) + + @property + def is_object(self): + """Return True if this resource is a file (resource in WebDAV parlance).""" + #path = self.get_path() + path = '/'.join(self.path) + # log.info(f"is_object {path}") + + if path == '': + return False + elif path == 'MyCatalogs': + return False + elif path == 'For Markup': + return False + + return True + #return os.path.isfile(self.get_abs_path()) + + @property + def exists(self): + """Return True if this resource exists.""" + path = '/'.join(self.path) + log.info(f"exists {path}") + if path in ['', 'MyCatalogs', 'For Markup']: + return True + else: + return os.path.exists(self.get_abs_path()) + + # def get_children(self): + # """Return an iterator of all direct children of this resource.""" + # # make sure the current object is a directory + # path = self.get_abs_path() + # log.info(f"get_children {path}") + # if os.path.isdir(path): + # for child in os.listdir(path): + # try: + # is_unicode = isinstance(child, str) + # except NameError: # Python 3 fix + # is_unicode = isinstance(child, str) + # if not is_unicode: + # child = child.decode(fs_encoding) + # yield self.clone(url_join(*(self.path + [child]))) + + # def write(self, content, temp_file=None, range_start=None): + # raise NotImplementedError + + # def read(self): + # raise NotImplementedError + + # def delete(self): + # """Delete the resource, recursive is implied.""" + # if self.is_collection: + # for child in self.get_children(): + # child.delete() + # os.rmdir(self.get_abs_path()) + # elif self.is_object: + # os.remove(self.get_abs_path()) + + # def create_collection(self): + # """Create a directory in the location of this resource.""" + # os.mkdir(self.get_abs_path()) + + # def copy_object(self, destination, depth=0): + # shutil.copy(self.get_abs_path(), destination.get_abs_path()) + + # def move_object(self, destination): + # os.rename(self.get_abs_path(), destination.get_abs_path()) + + # def read(self): + # return open(self.get_abs_path(), 'rb') + + # def write(self, request, temp_file=None, range_start=None): + # if temp_file: + # # move temp file (e.g., coming from nginx) + # shutil.move(temp_file, self.get_abs_path()) + # elif range_start == None: + # # open binary file and write to disk + # with open(self.get_abs_path(), 'wb') as dst: + # shutil.copyfileobj(request, dst) + # else: + # # open binary file and write to disk + # with open(self.get_abs_path(), 'r+b') as dst: + # dst.seek(range_start) + # shutil.copyfileobj(request, dst) diff --git a/procat2/settings.py b/procat2/settings.py index 6908ac5..fb0ee68 100644 --- a/procat2/settings.py +++ b/procat2/settings.py @@ -118,6 +118,8 @@ INSTALLED_APPS = [ 'account', 'lazysignup', 'webpack_loader', + 'djangodav', + 'rest_framework', 'procat2', 'dashboard', 'products', diff --git a/procat2/urls.py b/procat2/urls.py index d4539d4..c546eb7 100644 --- a/procat2/urls.py +++ b/procat2/urls.py @@ -26,6 +26,7 @@ from products.views import search_products, all_models from .forms import UserCreationForm from .views import login_guest, lazy_convert_done +from .resource import AuthDavView, ExampleView urlpatterns = [ @@ -53,6 +54,10 @@ urlpatterns = [ path('quickinfo/', include('quickinfo.urls')), path('markup/', include('markup.urls')), + + #path('dav', DavView.as_view(resource_class=MarkupDavResource, lock_class=DummyLock, acl_class=FullAcl)), + path('dav', AuthDavView.as_view()), + path('example', ExampleView.as_view()), ] if settings.DJDT: diff --git a/requirements.txt b/requirements.txt index 9d2f11b..4d17982 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,10 @@ amqp==2.5.2 +asgiref==3.2.3 backcall==0.1.0 billiard==3.6.1.0 celery==4.4.0 decorator==4.4.1 +defusedxml==0.6.0 Django==2.2.9 django-appconf==1.0.3 django-celery-results==1.1.2 @@ -12,6 +14,8 @@ django-lazysignup==2.0.0 django-settings-export==1.2.1 django-user-accounts==2.1.0 django-webpack-loader==0.6.0 +git+https://github.com/abend/djangodav.git#egg=DjangoDav +djangorestframework==3.11.0 Dumper==1.2.0 et-xmlfile==1.0.1 humanize==0.5.1 @@ -23,6 +27,7 @@ ipython-genutils==0.2.0 jdcal==1.4.1 jedi==0.15.2 kombu==4.6.7 +lxml==4.5.0 more-itertools==8.1.0 numpy==1.18.1 opencv-python==4.1.2.30 From c66827452d02110460b130c69173cb1f00d0d457 Mon Sep 17 00:00:00 2001 From: Seth Ladygo Date: Mon, 24 Feb 2020 15:18:23 -0800 Subject: [PATCH 2/7] webdav: change auth package to django-http-auth --- procat2/resource.py | 197 ++------------------------------------------ procat2/settings.py | 1 - procat2/urls.py | 4 +- requirements.txt | 4 +- 4 files changed, 8 insertions(+), 198 deletions(-) diff --git a/procat2/resource.py b/procat2/resource.py index c850386..61be048 100644 --- a/procat2/resource.py +++ b/procat2/resource.py @@ -2,37 +2,18 @@ import datetime import os import shutil from sys import getfilesystemencoding - import logging from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_exempt -from django.core.exceptions import PermissionDenied, ValidationError -from djangodav.utils import WEBDAV_NSMAP, D, url_join, get_property_tag_list, rfc1123_date, rfc5987_content_disposition -from django.utils.timezone import now - -from djangodav.responses import HttpResponsePreconditionFailed, HttpResponseCreated, HttpResponseNoContent, \ - HttpResponseConflict, HttpResponseMediatypeNotSupported, HttpResponseBadGateway, HttpResponseMultiStatus, \ - HttpResponseLocked, ResponseException - -#from djangodav.fs.resources import DummyFSDAVResource from djangodav.acls import FullAcl -from djangodav.base.resources import BaseDavResource -from djangodav.base.resources import MetaEtagMixIn +from djangodav.base.resources import BaseDavResource, MetaEtagMixIn from djangodav.locks import DummyLock from djangodav.utils import url_join from djangodav.views import DavView -# import lxml xml parser -from lxml import etree -# use defusedxmls parse function -from defusedxml.lxml import parse - -from rest_framework.authentication import SessionAuthentication, BasicAuthentication -from rest_framework.permissions import IsAuthenticated -from rest_framework.response import Response -from rest_framework.views import APIView +from django.utils.decorators import method_decorator +from django_http_auth.decorators import http_basic_auth from .models import Catalog @@ -41,169 +22,17 @@ log = logging.getLogger(__name__) fs_encoding = getfilesystemencoding() -class ExampleView(APIView): - authentication_classes = [SessionAuthentication, BasicAuthentication] - permission_classes = [IsAuthenticated] +@method_decorator(http_basic_auth, name='dispatch') +class AuthDavView(DavView): - def get(self, request, format=None): - content = { - 'user': str(request.user), # `django.contrib.auth.User` instance. - 'auth': str(request.auth), # None - } - return Response(content) - - -class AuthDavView(DavView, APIView): - authentication_classes = [BasicAuthentication] - #authentication_classes = [SessionAuthentication, BasicAuthentication] - permission_classes = [IsAuthenticated] - auth_user = None - path = "" - def __init__(self): log.info(f"AuthDavView init") super(DavView, self).__init__(resource_class=MarkupDavResource, lock_class=DummyLock, acl_class=FullAcl) - # def get(self, request, format=None): - def get(self, request, head=False, *args, **kwargs): - # content = { - # 'user': str(request.user), # `django.contrib.auth.User` instance. - # 'auth': str(request.auth), # None - # } - # return Response(content) - self.auth_user = request.user - log.info(f'AuthDavView GET auth user: {request.user}') - path = "" - return super(DavView, self).get(request, path, head=False, *args, **kwargs) - - def dispatch(self, request, path, *args, **kwargs): - log.info(f'AuthDavView dispatch 0') - if path: - self.path = path - self.base_url = request.META['PATH_INFO'][:-len(self.path)] - else: - self.path = '/' - self.base_url = request.META['PATH_INFO'] - - # log.info(f'AuthDavView dispatch 1') - # super(APIView, self).dispatch(request, *args, **kwargs) - # log.info(f'AuthDavView dispatch 2') - # super(DavView, self).dispatch(request, *args, **kwargs) - # log.info(f'AuthDavView dispatch 3') - - # FROM APIView - self.args = args - self.kwargs = kwargs - request = self.initialize_request(request, *args, **kwargs) - self.request = request - self.headers = self.default_response_headers # deprecate? - - try: - self.initial(request, *args, **kwargs) - - # TODO HERE? - - meta = request.META.get - self.xbody = kwargs['xbody'] = None - if (request.method.lower() != 'put' - and "/xml" in meta('CONTENT_TYPE', '') - and meta('CONTENT_LENGTH', 0) != '' - and int(meta('CONTENT_LENGTH', 0)) > 0): - - # parse XML using defusedxmls parse function - self.xbody = kwargs['xbody'] = etree.XPathDocumentEvaluator( - parse(request, etree.XMLParser(ns_clean=True, resolve_entities=True)), - namespaces=WEBDAV_NSMAP - ) - - if request.method.upper() in self._allowed_methods(): - handler = getattr(self, request.method.lower(), self.http_method_not_allowed) - else: - handler = self.http_method_not_allowed - #try: - log.info(f"AuthDavView DISPATCH {handler} for {path}") - resp = handler(request, self.path, *args, **kwargs) - except ResponseException as e: - print(e) - resp = e.response - except PermissionDenied as pe: - print(pe) - resp = HttpResponseForbidden() - except ValidationError as ve: - print(ve) - resp = HttpResponseBadRequest() - except Exception as exc: - resp = self.handle_exception(exc) - self.response = self.finalize_response(request, resp, *args, **kwargs) - return self.response - - if not 'Allow' in resp: - methods = self._allowed_methods() - if methods: - resp['Allow'] = ", ".join(methods) - if not 'Date' in resp: - resp['Date'] = rfc1123_date(now()) - if self.server_header: - resp['Server'] = self.server_header - return resp - - - # TODO END HERE - - # Get the appropriate handler method - # if request.method.lower() in self.http_method_names: - # handler = getattr(self, request.method.lower(), - # self.http_method_not_allowed) - # else: - # handler = self.http_method_not_allowed - - # response = handler(request, *args, **kwargs) - - # except Exception as exc: - # response = self.handle_exception(exc) - - # self.response = self.finalize_response(request, response, *args, **kwargs) - # return self.response - - - def options(self, request, path, *args, **kwargs): - """ - Handler method for HTTP 'OPTIONS' request. - """ - log.info('AuthDavView OPTIONS') - # FROM APIView - # if self.metadata_class is None: - # return self.http_method_not_allowed(request, *args, **kwargs) - # data = self.metadata_class().determine_metadata(request, self) - # return Response(data, status=status.HTTP_200_OK) - - # FROM DavView - if not self.has_access(self.resource, 'read'): - return self.no_access() - response = self.build_xml_response() - response['DAV'] = '1,2' - response['Content-Length'] = '0' - if self.path in ('/', '*'): - return response - response['Allow'] = ", ".join(self._allowed_methods()) - if self.resource.exists and self.resource.is_object: - response['Accept-Ranges'] = 'bytes' - return response - - # @method_decorator(csrf_exempt) - # def dispatch(self, request, path, *args, **kwargs): - # self.auth_user = request.user - # log.info(f'auth user: {request.user}') - # return super().dispatch(request, path, *args, **kwargs) - class MarkupDavResource(MetaEtagMixIn, BaseDavResource): root = '/opt/imagebank/mkbeta/webdav' # TODO replace with settings var - def __init__(self, path): - log.info(f"MarkupDavResource INIT {path}") - super().__init__(path) - def __str__(self): return f"" @@ -271,7 +100,6 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource): def is_collection(self): """Return True if this resource is a directory (collection in WebDAV parlance).""" path = '/'.join(self.path) - # log.info(f"is_collection {self.path}") if path == '': return True @@ -311,21 +139,6 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource): else: return os.path.exists(self.get_abs_path()) - # def get_children(self): - # """Return an iterator of all direct children of this resource.""" - # # make sure the current object is a directory - # path = self.get_abs_path() - # log.info(f"get_children {path}") - # if os.path.isdir(path): - # for child in os.listdir(path): - # try: - # is_unicode = isinstance(child, str) - # except NameError: # Python 3 fix - # is_unicode = isinstance(child, str) - # if not is_unicode: - # child = child.decode(fs_encoding) - # yield self.clone(url_join(*(self.path + [child]))) - # def write(self, content, temp_file=None, range_start=None): # raise NotImplementedError diff --git a/procat2/settings.py b/procat2/settings.py index fb0ee68..aab03e7 100644 --- a/procat2/settings.py +++ b/procat2/settings.py @@ -119,7 +119,6 @@ INSTALLED_APPS = [ 'lazysignup', 'webpack_loader', 'djangodav', - 'rest_framework', 'procat2', 'dashboard', 'products', diff --git a/procat2/urls.py b/procat2/urls.py index c546eb7..4945479 100644 --- a/procat2/urls.py +++ b/procat2/urls.py @@ -26,7 +26,7 @@ from products.views import search_products, all_models from .forms import UserCreationForm from .views import login_guest, lazy_convert_done -from .resource import AuthDavView, ExampleView +from .resource import AuthDavView urlpatterns = [ @@ -55,9 +55,7 @@ urlpatterns = [ path('markup/', include('markup.urls')), - #path('dav', DavView.as_view(resource_class=MarkupDavResource, lock_class=DummyLock, acl_class=FullAcl)), path('dav', AuthDavView.as_view()), - path('example', ExampleView.as_view()), ] if settings.DJDT: diff --git a/requirements.txt b/requirements.txt index 4d17982..079282b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,12 +10,12 @@ django-appconf==1.0.3 django-celery-results==1.1.2 django-debug-toolbar==2.1 django-extensions==2.2.5 +git+https://github.com/arcli/django-http-auth.git#egg=django-http-auth django-lazysignup==2.0.0 django-settings-export==1.2.1 django-user-accounts==2.1.0 django-webpack-loader==0.6.0 -git+https://github.com/abend/djangodav.git#egg=DjangoDav -djangorestframework==3.11.0 +git+https://github.com/arcli/djangodav.git#egg=DjangoDav Dumper==1.2.0 et-xmlfile==1.0.1 humanize==0.5.1 From bdbbd449c470975257cd9e5cdbeb13c869c4eb8f Mon Sep 17 00:00:00 2001 From: Seth Ladygo Date: Mon, 24 Feb 2020 16:56:49 -0800 Subject: [PATCH 3/7] markup webdav: move code, clean up --- markup/urls.py | 2 + procat2/resource.py => markup/webdav.py | 79 ++++++++++++------------- procat2/urls.py | 3 - 3 files changed, 41 insertions(+), 43 deletions(-) rename procat2/resource.py => markup/webdav.py (75%) diff --git a/markup/urls.py b/markup/urls.py index b4dd0c3..9b722e8 100644 --- a/markup/urls.py +++ b/markup/urls.py @@ -1,8 +1,10 @@ from django.urls import path from . import views +from .webdav import MarkupDavView urlpatterns = [ path('submit', views.submit, name='markup_submit'), #path('fail', views.fail, name='markup_fail'), + path('dav', MarkupDavView.as_view()), ] diff --git a/procat2/resource.py b/markup/webdav.py similarity index 75% rename from procat2/resource.py rename to markup/webdav.py index 61be048..72b0843 100644 --- a/procat2/resource.py +++ b/markup/webdav.py @@ -1,21 +1,22 @@ import datetime import os import shutil -from sys import getfilesystemencoding import logging +from sys import getfilesystemencoding from django.utils.decorators import method_decorator +from django_http_auth.decorators import http_basic_auth + from djangodav.acls import FullAcl from djangodav.base.resources import BaseDavResource, MetaEtagMixIn from djangodav.locks import DummyLock from djangodav.utils import url_join from djangodav.views import DavView -from django.utils.decorators import method_decorator -from django_http_auth.decorators import http_basic_auth +from procat2.models import Catalog -from .models import Catalog +# from procat2.settings import ASSET_DIR log = logging.getLogger(__name__) @@ -23,34 +24,37 @@ fs_encoding = getfilesystemencoding() @method_decorator(http_basic_auth, name='dispatch') -class AuthDavView(DavView): +class MarkupDavView(DavView): def __init__(self): - log.info(f"AuthDavView init") super(DavView, self).__init__(resource_class=MarkupDavResource, lock_class=DummyLock, acl_class=FullAcl) class MarkupDavResource(MetaEtagMixIn, BaseDavResource): - root = '/opt/imagebank/mkbeta/webdav' # TODO replace with settings var + ROOT_FOLDER = '' + MY_CATS_FOLDER = 'My Catalogs' + MARKUP_SUBMIT_FOLDER = 'For Markup' + #root = '/opt/imagebank/mkbeta/webdav' # TODO replace with settings var + + def __init__(self, path=None, user=None): + super().__init__(path) + self.user = user def __str__(self): - return f"" + return f"" def get_children(self): """Return an iterator of all direct children of this resource.""" # make sure the current object is a directory path = self.get_path() - log.info(f"get_children of {path}") + log.info(f"get_children of '{path}'") children = [] if path == '/': - #children = ['My Catalogs', 'For Markup'] - children = ['MyCatalogs'] - elif path == '/MyCatalogs': - # TODO return user's catalogs - # get User - children = None - elif path == '/For Markup': + children = [self.MY_CATS_FOLDER, self.MARKUP_SUBMIT_FOLDER] + elif path == f'/{self.MY_CATS_FOLDER}/': + children = self.user_catalogs() + elif path == f'/{self.MARKUP_SUBMIT_FOLDER}/': children = None # else: # path = self.get_abs_path() @@ -58,15 +62,16 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource): # children = os.listdir(path) for child in children: - try: - is_unicode = isinstance(child, str) - except NameError: # Python 3 fix - is_unicode = isinstance(child, str) - if not is_unicode: - child = child.decode(fs_encoding) - log.info(f'returning kid {self.clone(url_join(*(self.path + [child])))}') - yield self.clone(url_join(*(self.path + [child]))) + is_unicode = isinstance(child, str) + if not is_unicode: + child = child.decode(fs_encoding) + child_resource = self.clone(path=url_join(*(self.path + [child])), user=self.user) + log.info(f'returning kid {child_resource}') + yield child_resource + def user_catalogs(self): + cats = Catalog.objects.filter(owner=self.user).order_by('-updated') + return [c.name for c in cats] def get_abs_path(self): """Return the absolute path of the resource. Used internally to interface with @@ -100,32 +105,24 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource): def is_collection(self): """Return True if this resource is a directory (collection in WebDAV parlance).""" path = '/'.join(self.path) + # log.info(f"is_collection {self.path}") - if path == '': - return True - elif path == 'MyCatalogs': - return True - elif path == 'For Markup': + if path in [self.ROOT_FOLDER, self.MY_CATS_FOLDER, self.MARKUP_SUBMIT_FOLDER]: return True - log.info(f"is_collection {self.path} FALLTHROUGH") return False # return os.path.isdir(self.get_abs_path()) @property def is_object(self): """Return True if this resource is a file (resource in WebDAV parlance).""" - #path = self.get_path() path = '/'.join(self.path) - # log.info(f"is_object {path}") + log.info(f"is_object {path}") - if path == '': - return False - elif path == 'MyCatalogs': - return False - elif path == 'For Markup': + if self.is_collection: return False + # TODO test catalog pdf return True #return os.path.isfile(self.get_abs_path()) @@ -134,10 +131,12 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource): """Return True if this resource exists.""" path = '/'.join(self.path) log.info(f"exists {path}") - if path in ['', 'MyCatalogs', 'For Markup']: + + if self.is_collection: return True - else: - return os.path.exists(self.get_abs_path()) + + # TODO test catalog pdf + return os.path.exists(self.get_abs_path()) # def write(self, content, temp_file=None, range_start=None): # raise NotImplementedError diff --git a/procat2/urls.py b/procat2/urls.py index 4945479..d4539d4 100644 --- a/procat2/urls.py +++ b/procat2/urls.py @@ -26,7 +26,6 @@ from products.views import search_products, all_models from .forms import UserCreationForm from .views import login_guest, lazy_convert_done -from .resource import AuthDavView urlpatterns = [ @@ -54,8 +53,6 @@ urlpatterns = [ path('quickinfo/', include('quickinfo.urls')), path('markup/', include('markup.urls')), - - path('dav', AuthDavView.as_view()), ] if settings.DJDT: From 012c65cc7c74fe2161ead10edc6abf20c7877459 Mon Sep 17 00:00:00 2001 From: Seth Ladygo Date: Wed, 26 Feb 2020 16:28:36 -0800 Subject: [PATCH 4/7] procat2/models.py: fix Catalog.PDF_DIR --- procat2/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/procat2/models.py b/procat2/models.py index 0e0fc3b..6d339b0 100644 --- a/procat2/models.py +++ b/procat2/models.py @@ -51,7 +51,7 @@ def unix_datetime(date): class Catalog(models.Model): - PDF_DIR = 'catalogs' + PDF_DIR = 'catalogs/user' PDF_URL = 'export/catalogs' owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) From 136f9808dfe00c524508bb2a9d60d075f4c86574 Mon Sep 17 00:00:00 2001 From: Seth Ladygo Date: Wed, 26 Feb 2020 16:30:02 -0800 Subject: [PATCH 5/7] markup/webdav.py: mostly functional --- markup/webdav.py | 324 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 230 insertions(+), 94 deletions(-) diff --git a/markup/webdav.py b/markup/webdav.py index 72b0843..0f6ad0a 100644 --- a/markup/webdav.py +++ b/markup/webdav.py @@ -1,22 +1,28 @@ import datetime -import os -import shutil import logging +import os +import re +import shutil + +from os.path import dirname +from pathlib import Path from sys import getfilesystemencoding +from django.core.exceptions import PermissionDenied from django.utils.decorators import method_decorator +from django.utils.functional import cached_property from django_http_auth.decorators import http_basic_auth -from djangodav.acls import FullAcl +from djangodav.acls import DavAcl, ReadOnlyAcl, FullAcl from djangodav.base.resources import BaseDavResource, MetaEtagMixIn +from djangodav.fs.resources import DummyFSDAVResource from djangodav.locks import DummyLock from djangodav.utils import url_join from djangodav.views import DavView from procat2.models import Catalog - -# from procat2.settings import ASSET_DIR +from procat2.settings import ASSET_DIR log = logging.getLogger(__name__) @@ -27,122 +33,174 @@ fs_encoding = getfilesystemencoding() class MarkupDavView(DavView): def __init__(self): - super(DavView, self).__init__(resource_class=MarkupDavResource, lock_class=DummyLock, acl_class=FullAcl) + super().__init__(resource_class=resource_factory, lock_class=DummyLock, acl_class=FullAcl) + + def get_access(self, resource): + return resource.get_acl() + + +def resource_factory(**kwargs): + # log.info(f"resource_factory '{kwargs}'") + path = kwargs['path'] + if path == RootFolderResource.PATH: + return RootFolderResource(**kwargs) + elif path == CatalogFolderResource.PATH: + return CatalogFolderResource(**kwargs) + elif is_my_cats_catalog(path): + return CatalogResource(**kwargs) + elif is_markup_catalog(path): + return MarkupResource(**kwargs) + else: + return NonsensicalResource(**kwargs) + +def is_my_cats_catalog(path): + return re.search(fr'^{CatalogFolderResource.PATH}.*-([0-9]+)\.pdf', path, re.I) + +def is_markup_catalog(path): + return re.search(fr'^{MarkupResource.PATH}', path, re.I) class MarkupDavResource(MetaEtagMixIn, BaseDavResource): - ROOT_FOLDER = '' - MY_CATS_FOLDER = 'My Catalogs' - MARKUP_SUBMIT_FOLDER = 'For Markup' - #root = '/opt/imagebank/mkbeta/webdav' # TODO replace with settings var - def __init__(self, path=None, user=None): - super().__init__(path) - self.user = user + # def __init__(self, path=None, user=None): + # super().__init__(path) + # self.user = user def __str__(self): - return f"" + return f"<{type(self).__name__} '{self.user}' '{self.path}'>" + + def get_acl(self): + return ReadOnlyAcl() + # return DavAcl(read=True, write=False, delete=False, full=None) + + +class RootFolderResource(MarkupDavResource): + NAME = '' + PATH = '/' + + # def get_parent_path(self): + # path = self.path[:-1] + # return "/" + "/".join(path) + "/" if path else "" + + def get_parent(self): + return None def get_children(self): - """Return an iterator of all direct children of this resource.""" - # make sure the current object is a directory - path = self.get_path() - log.info(f"get_children of '{path}'") - - children = [] - if path == '/': - children = [self.MY_CATS_FOLDER, self.MARKUP_SUBMIT_FOLDER] - elif path == f'/{self.MY_CATS_FOLDER}/': - children = self.user_catalogs() - elif path == f'/{self.MARKUP_SUBMIT_FOLDER}/': - children = None - # else: - # path = self.get_abs_path() - # if os.path.isdir(path): - # children = os.listdir(path) - + children = [CatalogFolderResource(CatalogFolderResource.PATH, self.user), + MarkupResource(MarkupResource.PATH, self.user)] for child in children: - is_unicode = isinstance(child, str) - if not is_unicode: - child = child.decode(fs_encoding) - child_resource = self.clone(path=url_join(*(self.path + [child])), user=self.user) - log.info(f'returning kid {child_resource}') - yield child_resource - - def user_catalogs(self): - cats = Catalog.objects.filter(owner=self.user).order_by('-updated') - return [c.name for c in cats] - - def get_abs_path(self): - """Return the absolute path of the resource. Used internally to interface with - an actual file system. If you override all other methods, this one will not - be used.""" - return os.path.join(self.root, *self.path) + yield child @property def getcontentlength(self): - """Return the size of the resource in bytes.""" - if self.is_collection: - return 0 - else: - return os.path.getsize(self.get_abs_path()) + return 2 def get_created(self): - """Return the create time as datetime object.""" - if self.is_collection: - return datetime.datetime.now() # TODO last created time of object in there? - else: - return datetime.datetime.fromtimestamp(os.stat(self.get_abs_path()).st_ctime) + return datetime.datetime.now() # TODO last created time of object in there? def get_modified(self): - """Return the modified time as datetime object.""" - if self.is_collection: - return datetime.datetime.now() # TODO last modified time of object in there? - else: - return datetime.datetime.fromtimestamp(os.stat(self.get_abs_path()).st_mtime) + return datetime.datetime.now() # TODO last modified time of object in there? @property def is_collection(self): - """Return True if this resource is a directory (collection in WebDAV parlance).""" - path = '/'.join(self.path) - # log.info(f"is_collection {self.path}") - - if path in [self.ROOT_FOLDER, self.MY_CATS_FOLDER, self.MARKUP_SUBMIT_FOLDER]: - return True - - return False - # return os.path.isdir(self.get_abs_path()) + return True @property def is_object(self): - """Return True if this resource is a file (resource in WebDAV parlance).""" - path = '/'.join(self.path) - log.info(f"is_object {path}") - - if self.is_collection: - return False - - # TODO test catalog pdf - return True - #return os.path.isfile(self.get_abs_path()) + return False @property def exists(self): - """Return True if this resource exists.""" - path = '/'.join(self.path) - log.info(f"exists {path}") + return True - if self.is_collection: - return True - # TODO test catalog pdf - return os.path.exists(self.get_abs_path()) +class CatalogFolderResource(MarkupDavResource): + NAME = 'My Catalogs' + PATH = f'/{NAME}/' + MARKUP_SUBMIT_NAME = 'For Markup' + MARKUP_SUBMIT_PATH = f'/{MARKUP_SUBMIT_NAME}/' - # def write(self, content, temp_file=None, range_start=None): - # raise NotImplementedError + def get_parent(self): + return RootFolderResource(user=self.user) - # def read(self): - # raise NotImplementedError + def get_children(self): + for child in self.user_catalogs: + yield CatalogResource(path=url_join(*(self.path + [child])), user=self.user) + + @cached_property + def user_catalogs(self): + cats = Catalog.objects.filter(owner=self.user).order_by('-updated') + return [c.pdf_name() for c in cats if c.pdf_exists()] + + @property + def getcontentlength(self): + return 2 # TODO based on contents + + def get_created(self): + return datetime.datetime.now() # TODO last created time of object in there? + + def get_modified(self): + return datetime.datetime.now() # TODO last modified time of object in there? + + @property + def is_collection(self): + return True + + @property + def is_object(self): + return False + + @property + def exists(self): + return True + + +class CatalogResource(MarkupDavResource): + + def get_parent(self): + return CatalogFolderResource(path=CatalogFolderResource.PATH, user=self.user) + + # def get_children(self): + # return + # yield + + @cached_property + def catalog(self): + id = self.id_from_pdf_name(self.path) + return Catalog.objects.get(id=id) + + def id_from_pdf_name(self, path): + result = re.search(r'-([0-9]+)\.pdf', path[-1], re.I) + if result and result.groups(): + return result.group(1) + else: + return None + + @property + def getcontentlength(self): + if self.catalog.pdf_exists(): + return os.stat(self.catalog.pdf_file()).st_size + else: + return 0 + + def get_created(self): + return self.catalog.created + + def get_modified(self): + return self.catalog.updated + + @property + def is_collection(self): + return False + + @property + def is_object(self): + return True + + @property + def exists(self): + log.info(f'exists? {self.catalog.pdf_exists()}') + return self.catalog.pdf_exists() # def delete(self): # """Delete the resource, recursive is implied.""" @@ -163,8 +221,11 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource): # def move_object(self, destination): # os.rename(self.get_abs_path(), destination.get_abs_path()) - # def read(self): - # return open(self.get_abs_path(), 'rb') + def read(self): + if self.catalog.pdf_exists(): + return open(self.catalog.pdf_file(), 'rb') + else: + return None # def write(self, request, temp_file=None, range_start=None): # if temp_file: @@ -179,3 +240,78 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource): # with open(self.get_abs_path(), 'r+b') as dst: # dst.seek(range_start) # shutil.copyfileobj(request, dst) + + +class MarkupResource(MarkupDavResource, DummyFSDAVResource): + NAME = 'For Markup' + PATH = f'/{NAME}/' + + def get_parent(self): + parent_path = self.path[:-1] + if len(parent_path): + log.info(f"markup folder get parent for {self.user} / {self.path}: {parent_path} - PARENT") + return MarkupResource(path=self.construct_path(parent_path, True), user=self.user) + else: + log.info(f"markup folder get parent for {self.user} / {self.path}: {parent_path} - ROOT") + return RootFolderResource(user=self.user) + + def get_children(self): + path = self.get_abs_path() + log.info(f"markup folder get_children '{path}' user: {self.user}") + if os.path.isdir(path): + for child in os.listdir(path): + is_unicode = isinstance(child, str) + if not is_unicode: + child = child.decode(fs_encoding) + yield MarkupResource(path=url_join(*(self.path + [child])), user=self.user) + + def get_abs_path(self): + path = os.path.join(self.get_markup_user_path(), *self.path[1:]) + # log.debug(f'markup folder user is {self.user} and path {self.path}: {path}') + return path + + def get_markup_user_path(self): + log.debug(f'get_markup_user_path for {self} has user {self.user}') + return os.path.join(f'{ASSET_DIR}/webdav/', 'markup', self.user.username) + + def write(self, request, temp_file=None, range_start=None): + # make sure dest dir exists first + dest_dir = dirname(self.get_abs_path()) + Path(dest_dir).mkdir(parents=True, exist_ok=True) + super().write(request, temp_file=temp_file, range_start=range_start) + + def get_acl(self): + return FullAcl() + + +class NonsensicalResource(MarkupDavResource): + + def get_parent(self): + log.debug(f'nonsense parent of {self.path}') + return RootFolderResource(path=RootFolderResource.PATH, user=self.user) + + def get_children(self): + return + yield + + @property + def getcontentlength(self): + return 0 + + def get_created(self): + return datetime.datetime.now() + + def get_modified(self): + return datetime.datetime.now() + + @property + def is_collection(self): + return False + + @property + def is_object(self): + return True + + @property + def exists(self): + return False From 7cf6c0d24f01d019c9b0cebebab39b34ffc37516 Mon Sep 17 00:00:00 2001 From: Seth Ladygo Date: Fri, 28 Feb 2020 15:40:59 -0800 Subject: [PATCH 6/7] markup/webdav.py: cleanup --- markup/webdav.py | 57 +++--------------------------------------------- 1 file changed, 3 insertions(+), 54 deletions(-) diff --git a/markup/webdav.py b/markup/webdav.py index 0f6ad0a..a8ce76b 100644 --- a/markup/webdav.py +++ b/markup/webdav.py @@ -62,10 +62,6 @@ def is_markup_catalog(path): class MarkupDavResource(MetaEtagMixIn, BaseDavResource): - # def __init__(self, path=None, user=None): - # super().__init__(path) - # self.user = user - def __str__(self): return f"<{type(self).__name__} '{self.user}' '{self.path}'>" @@ -78,10 +74,6 @@ class RootFolderResource(MarkupDavResource): NAME = '' PATH = '/' - # def get_parent_path(self): - # path = self.path[:-1] - # return "/" + "/".join(path) + "/" if path else "" - def get_parent(self): return None @@ -160,9 +152,9 @@ class CatalogResource(MarkupDavResource): def get_parent(self): return CatalogFolderResource(path=CatalogFolderResource.PATH, user=self.user) - # def get_children(self): - # return - # yield + def get_children(self): + return + yield @cached_property def catalog(self): @@ -199,47 +191,14 @@ class CatalogResource(MarkupDavResource): @property def exists(self): - log.info(f'exists? {self.catalog.pdf_exists()}') return self.catalog.pdf_exists() - # def delete(self): - # """Delete the resource, recursive is implied.""" - # if self.is_collection: - # for child in self.get_children(): - # child.delete() - # os.rmdir(self.get_abs_path()) - # elif self.is_object: - # os.remove(self.get_abs_path()) - - # def create_collection(self): - # """Create a directory in the location of this resource.""" - # os.mkdir(self.get_abs_path()) - - # def copy_object(self, destination, depth=0): - # shutil.copy(self.get_abs_path(), destination.get_abs_path()) - - # def move_object(self, destination): - # os.rename(self.get_abs_path(), destination.get_abs_path()) - def read(self): if self.catalog.pdf_exists(): return open(self.catalog.pdf_file(), 'rb') else: return None - # def write(self, request, temp_file=None, range_start=None): - # if temp_file: - # # move temp file (e.g., coming from nginx) - # shutil.move(temp_file, self.get_abs_path()) - # elif range_start == None: - # # open binary file and write to disk - # with open(self.get_abs_path(), 'wb') as dst: - # shutil.copyfileobj(request, dst) - # else: - # # open binary file and write to disk - # with open(self.get_abs_path(), 'r+b') as dst: - # dst.seek(range_start) - # shutil.copyfileobj(request, dst) class MarkupResource(MarkupDavResource, DummyFSDAVResource): @@ -249,15 +208,12 @@ class MarkupResource(MarkupDavResource, DummyFSDAVResource): def get_parent(self): parent_path = self.path[:-1] if len(parent_path): - log.info(f"markup folder get parent for {self.user} / {self.path}: {parent_path} - PARENT") return MarkupResource(path=self.construct_path(parent_path, True), user=self.user) else: - log.info(f"markup folder get parent for {self.user} / {self.path}: {parent_path} - ROOT") return RootFolderResource(user=self.user) def get_children(self): path = self.get_abs_path() - log.info(f"markup folder get_children '{path}' user: {self.user}") if os.path.isdir(path): for child in os.listdir(path): is_unicode = isinstance(child, str) @@ -270,14 +226,7 @@ class MarkupResource(MarkupDavResource, DummyFSDAVResource): # log.debug(f'markup folder user is {self.user} and path {self.path}: {path}') return path - def get_markup_user_path(self): - log.debug(f'get_markup_user_path for {self} has user {self.user}') - return os.path.join(f'{ASSET_DIR}/webdav/', 'markup', self.user.username) - def write(self, request, temp_file=None, range_start=None): - # make sure dest dir exists first - dest_dir = dirname(self.get_abs_path()) - Path(dest_dir).mkdir(parents=True, exist_ok=True) super().write(request, temp_file=temp_file, range_start=range_start) def get_acl(self): From 64ae5709e085acbe0bfad94bbb4d5bae6dfdf9dd Mon Sep 17 00:00:00 2001 From: Seth Ladygo Date: Fri, 28 Feb 2020 15:42:35 -0800 Subject: [PATCH 7/7] markup/webdav.py: more complete functionality --- markup/webdav.py | 59 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/markup/webdav.py b/markup/webdav.py index a8ce76b..82ff310 100644 --- a/markup/webdav.py +++ b/markup/webdav.py @@ -14,6 +14,9 @@ from django.utils.functional import cached_property from django_http_auth.decorators import http_basic_auth +from django.http import HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseBadRequest, \ + HttpResponseRedirect, Http404, HttpResponse, FileResponse + from djangodav.acls import DavAcl, ReadOnlyAcl, FullAcl from djangodav.base.resources import BaseDavResource, MetaEtagMixIn from djangodav.fs.resources import DummyFSDAVResource @@ -24,6 +27,8 @@ from djangodav.views import DavView from procat2.models import Catalog from procat2.settings import ASSET_DIR +from .utils import clean_path, ensure_dir, set_file_perms, WORKDIR + log = logging.getLogger(__name__) fs_encoding = getfilesystemencoding() @@ -199,11 +204,38 @@ class CatalogResource(MarkupDavResource): else: return None + def delete(self): + pass + + def copy_object(self, destination, depth=0): + dest = destination.get_abs_path() + dest_dir = dirname(dest) + Path(dest_dir).mkdir(parents=True, exist_ok=True) + shutil.copy(self.catalog.pdf_file(), dest) + + def move_object(self, destination): + os.rename(self.get_abs_path(), destination.get_abs_path()) + + def write(self, request, temp_file=None, range_start=None): + autosave_path = url_join(MarkupResource.PATH, 'Autosave', self.path[-1]) + autosave = MarkupResource(path=autosave_path, user=self.user) + autosave_dir = autosave.get_abs_path() + log.debug(f'autosave_dir: {autosave_dir}') + ensure_dir(dirname(autosave_dir)) + return autosave.write(request, temp_file=None, range_start=None) + + def get_acl(self): + return DavAcl(read=True, write=True, delete=False, full=None) + + +def get_markup_user_path(user): + return os.path.join(ASSET_DIR, 'markup', 'webdav', user.username if user else 'unknown_user') class MarkupResource(MarkupDavResource, DummyFSDAVResource): NAME = 'For Markup' PATH = f'/{NAME}/' + SUBDIR = 'markup' def get_parent(self): parent_path = self.path[:-1] @@ -222,8 +254,10 @@ class MarkupResource(MarkupDavResource, DummyFSDAVResource): yield MarkupResource(path=url_join(*(self.path + [child])), user=self.user) def get_abs_path(self): - path = os.path.join(self.get_markup_user_path(), *self.path[1:]) - # log.debug(f'markup folder user is {self.user} and path {self.path}: {path}') + base_dir = os.path.join(get_markup_user_path(self.user), self.SUBDIR) + ensure_dir(base_dir) + path = os.path.join(base_dir, *self.path[1:]) + # log.debug(f'markup: get abs path for {self.path}: base {base_dir}') return path def write(self, request, temp_file=None, range_start=None): @@ -234,15 +268,34 @@ class MarkupResource(MarkupDavResource, DummyFSDAVResource): class NonsensicalResource(MarkupDavResource): + NAME = 'Misc' + PATH = f'/{NAME}/' + SUBDIR = 'misc' + + def get_abs_path(self): + base_dir = os.path.join(get_markup_user_path(self.user), self.SUBDIR) + ensure_dir(base_dir) + log.debug(f'nonsense: get abs path for {self.path}: base {base_dir}') + path = os.path.join(base_dir, *self.path[1:]) + return path def get_parent(self): - log.debug(f'nonsense parent of {self.path}') + log.debug(f'nonsense parent of {self.path} is root') return RootFolderResource(path=RootFolderResource.PATH, user=self.user) def get_children(self): return yield + def delete(self): + return HttpResponseForbidden() + + def move(self): + return HttpResponseForbidden() + + def copy(self, destination, depth=-1): + return HttpResponseForbidden() + @property def getcontentlength(self): return 0