Merge branch 'djangodav'

This commit is contained in:
2020-02-28 17:16:27 -08:00
5 changed files with 328 additions and 1 deletions

View File

@ -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<path:path>', MarkupDavView.as_view()),
]

319
markup/webdav.py Normal file
View File

@ -0,0 +1,319 @@
import datetime
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 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
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 .utils import clean_path, ensure_dir, set_file_perms, WORKDIR
log = logging.getLogger(__name__)
fs_encoding = getfilesystemencoding()
@method_decorator(http_basic_auth, name='dispatch')
class MarkupDavView(DavView):
def __init__(self):
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):
def __str__(self):
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(self):
return None
def get_children(self):
children = [CatalogFolderResource(CatalogFolderResource.PATH, self.user),
MarkupResource(MarkupResource.PATH, self.user)]
for child in children:
yield child
@property
def getcontentlength(self):
return 2
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 CatalogFolderResource(MarkupDavResource):
NAME = 'My Catalogs'
PATH = f'/{NAME}/'
MARKUP_SUBMIT_NAME = 'For Markup'
MARKUP_SUBMIT_PATH = f'/{MARKUP_SUBMIT_NAME}/'
def get_parent(self):
return RootFolderResource(user=self.user)
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):
return self.catalog.pdf_exists()
def read(self):
if self.catalog.pdf_exists():
return open(self.catalog.pdf_file(), 'rb')
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]
if len(parent_path):
return MarkupResource(path=self.construct_path(parent_path, True), user=self.user)
else:
return RootFolderResource(user=self.user)
def get_children(self):
path = self.get_abs_path()
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):
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):
super().write(request, temp_file=temp_file, range_start=range_start)
def get_acl(self):
return FullAcl()
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} 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
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

View File

@ -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)

View File

@ -118,6 +118,7 @@ INSTALLED_APPS = [
'account',
'lazysignup',
'webpack_loader',
'djangodav',
'procat2',
'dashboard',
'products',

View File

@ -1,17 +1,21 @@
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
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/arcli/djangodav.git#egg=DjangoDav
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