#18 File upload in certain folders
* refactors documents and file upload to be distributed into different subfolders, depending on the type of document (InterventionDocument, RevocationDocument, ...) * refactors Document model into AbstractDocument * subclasses RevocationDocument, InterventionDocument, COmpensationDocument, EmaDocument and EcoAccountDocument from AbstractDocument to provide proper functionality for each * adds new specialized routes for each new document type (opening, removing) * drops generic get and remove routes for documents
This commit is contained in:
@@ -7,7 +7,7 @@ Created on: 22.07.21
|
||||
"""
|
||||
from django.contrib import admin
|
||||
|
||||
from konova.models import Geometry, Document, Deadline
|
||||
from konova.models import Geometry, Deadline
|
||||
|
||||
|
||||
class GeometryAdmin(admin.ModelAdmin):
|
||||
@@ -17,7 +17,7 @@ class GeometryAdmin(admin.ModelAdmin):
|
||||
]
|
||||
|
||||
|
||||
class DocumentAdmin(admin.ModelAdmin):
|
||||
class AbstractDocumentAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"title",
|
||||
@@ -36,5 +36,4 @@ class DeadlineAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
admin.site.register(Geometry, GeometryAdmin)
|
||||
admin.site.register(Document, DocumentAdmin)
|
||||
admin.site.register(Deadline, DeadlineAdmin)
|
||||
|
||||
@@ -21,11 +21,11 @@ from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.models import EcoAccount
|
||||
from ema.models import Ema
|
||||
from intervention.models import Intervention
|
||||
from compensation.models import EcoAccount, Compensation, EcoAccountDocument, CompensationDocument
|
||||
from ema.models import Ema, EmaDocument
|
||||
from intervention.models import Intervention, Revocation, RevocationDocument, InterventionDocument
|
||||
from konova.contexts import BaseContext
|
||||
from konova.models import Document, BaseObject
|
||||
from konova.models import BaseObject
|
||||
from konova.utils.message_templates import FORM_INVALID
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
|
||||
@@ -307,7 +307,7 @@ class NewDocumentForm(BaseModalForm):
|
||||
attrs={
|
||||
"class": "w-75"
|
||||
}
|
||||
)
|
||||
),
|
||||
)
|
||||
comment = forms.CharField(
|
||||
required=False,
|
||||
@@ -322,6 +322,13 @@ class NewDocumentForm(BaseModalForm):
|
||||
}
|
||||
)
|
||||
)
|
||||
document_instance_map = {
|
||||
Intervention: InterventionDocument,
|
||||
Compensation: CompensationDocument,
|
||||
EcoAccount: EcoAccountDocument,
|
||||
Revocation: RevocationDocument,
|
||||
Ema: EmaDocument,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -331,6 +338,12 @@ class NewDocumentForm(BaseModalForm):
|
||||
self.form_attrs = {
|
||||
"enctype": "multipart/form-data", # important for file upload
|
||||
}
|
||||
self.document_type = self.document_instance_map.get(
|
||||
self.instance.__class__,
|
||||
None
|
||||
)
|
||||
if not self.document_type:
|
||||
raise NotImplementedError("Unsupported document type for {}".format(self.instance.__class__))
|
||||
|
||||
def save(self):
|
||||
with transaction.atomic():
|
||||
@@ -338,14 +351,14 @@ class NewDocumentForm(BaseModalForm):
|
||||
user=self.user,
|
||||
action=UserAction.CREATED,
|
||||
)
|
||||
doc = Document.objects.create(
|
||||
doc = self.document_type.objects.create(
|
||||
created=action,
|
||||
title=self.cleaned_data["title"],
|
||||
comment=self.cleaned_data["comment"],
|
||||
file=self.cleaned_data["file"],
|
||||
date_of_creation=self.cleaned_data["creation_date"],
|
||||
instance=self.instance,
|
||||
)
|
||||
self.instance.documents.add(doc)
|
||||
|
||||
edited_action = UserActionLogEntry.objects.create(
|
||||
user=self.user,
|
||||
|
||||
@@ -18,6 +18,7 @@ from compensation.settings import COMPENSATION_IDENTIFIER_TEMPLATE, COMPENSATION
|
||||
ECO_ACCOUNT_IDENTIFIER_TEMPLATE, ECO_ACCOUNT_IDENTIFIER_LENGTH
|
||||
from ema.settings import EMA_ACCOUNT_IDENTIFIER_LENGTH, EMA_ACCOUNT_IDENTIFIER_TEMPLATE
|
||||
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE
|
||||
from konova.settings import INTERVENTION_REVOCATION_DOC_PATH
|
||||
from konova.utils.generators import generate_random_string
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
|
||||
@@ -220,7 +221,47 @@ class Deadline(BaseResource):
|
||||
return None
|
||||
|
||||
|
||||
class Document(BaseResource):
|
||||
def generate_document_file_upload_path(instance, filename):
|
||||
""" Generates the file upload path for certain document instances
|
||||
|
||||
Documents derived from AbstractDocument need specific upload paths for their related models.
|
||||
|
||||
Args:
|
||||
instance (): The document instance
|
||||
filename (): The filename
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
from compensation.models import CompensationDocument, EcoAccountDocument
|
||||
from ema.models import EmaDocument
|
||||
from intervention.models import InterventionDocument, RevocationDocument
|
||||
from konova.settings import ECO_ACCOUNT_DOC_PATH, EMA_DOC_PATH, \
|
||||
COMPENSATION_DOC_PATH, \
|
||||
INTERVENTION_DOC_PATH
|
||||
|
||||
# Map document types to paths on the hard drive
|
||||
path_map = {
|
||||
InterventionDocument: INTERVENTION_DOC_PATH,
|
||||
CompensationDocument: COMPENSATION_DOC_PATH,
|
||||
EmaDocument: EMA_DOC_PATH,
|
||||
RevocationDocument: INTERVENTION_REVOCATION_DOC_PATH,
|
||||
EcoAccountDocument: ECO_ACCOUNT_DOC_PATH,
|
||||
}
|
||||
path = path_map.get(instance.__class__, None)
|
||||
if path is None:
|
||||
raise NotImplementedError("Unidentified document type: {}".format(instance.__class__))
|
||||
|
||||
# RevocationDocument needs special treatment, since these files need to be stored in a subfolder of the related
|
||||
# instance's (Revocation) legaldata interventions folder
|
||||
if instance.__class__ is RevocationDocument:
|
||||
path = path.format(instance.intervention.id)
|
||||
else:
|
||||
path = path.format(instance.instance.id)
|
||||
return path + filename
|
||||
|
||||
|
||||
class AbstractDocument(BaseResource):
|
||||
"""
|
||||
Documents can be attached to compensation or intervention for uploading legal documents or pictures.
|
||||
"""
|
||||
@@ -229,6 +270,9 @@ class Document(BaseResource):
|
||||
file = models.FileField()
|
||||
comment = models.TextField()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
""" Custom delete function to remove the real file from the hard drive
|
||||
|
||||
@@ -239,7 +283,11 @@ class Document(BaseResource):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
os.remove(self.file.file.name)
|
||||
try:
|
||||
os.remove(self.file.file.name)
|
||||
except FileNotFoundError:
|
||||
# File seems to missing anyway - continue!
|
||||
pass
|
||||
super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
|
||||
|
||||
@@ -55,7 +55,20 @@ DEFAULT_GROUP = "Default"
|
||||
ZB_GROUP = "Registration office"
|
||||
ETS_GROUP = "Conservation office"
|
||||
|
||||
|
||||
# Needed to redirect to LANIS
|
||||
## Values to be inserted are [zoom_level, x_coord, y_coord]
|
||||
LANIS_LINK_TEMPLATE = "https://geodaten.naturschutz.rlp.de/kartendienste_naturschutz/index.php?lang=de&zl={}&x={}&y={}&bl=tk_rlp_tms_grau&bo=1&lo=0.8,0.8,0.8,0.6,0.8,0.8,0.8,0.8,0.8&layers=eiv_f,eiv_l,eiv_p,kom_f,kom_l,kom_p,oek_f,ema_f,mae&service=kartendienste_naturschutz"
|
||||
|
||||
# ALLOWED FILE UPLOAD DEFINITIONS
|
||||
# Default: Upload into upper project folder
|
||||
MEDIA_ROOT = BASE_DIR + "/.."
|
||||
|
||||
# DOCUMENT UPLOAD PATHS
|
||||
# Extends MEDIA_ROOT
|
||||
## {} is a placeholder for the object's uuid --> each object will have it's own folder
|
||||
BASE_DOC_PATH = "konova_uploaded_files/"
|
||||
INTERVENTION_DOC_PATH = BASE_DOC_PATH + "interventions/{}/"
|
||||
INTERVENTION_REVOCATION_DOC_PATH = BASE_DOC_PATH + "interventions/{}/revocation/"
|
||||
COMPENSATION_DOC_PATH = BASE_DOC_PATH + "compensations/{}/"
|
||||
ECO_ACCOUNT_DOC_PATH = BASE_DOC_PATH + "eco_account/{}/"
|
||||
EMA_DOC_PATH = BASE_DOC_PATH + "ema/{}/"
|
||||
@@ -22,7 +22,7 @@ from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisati
|
||||
RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete
|
||||
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||
from konova.sso.sso import KonovaSSOClient
|
||||
from konova.views import logout_view, home_view, get_document_view, remove_document_view, remove_deadline_view
|
||||
from konova.views import logout_view, home_view, remove_deadline_view
|
||||
|
||||
sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY)
|
||||
urlpatterns = [
|
||||
@@ -38,10 +38,6 @@ urlpatterns = [
|
||||
path('news/', include("news.urls")),
|
||||
path('news/', include("codelist.urls")),
|
||||
|
||||
# Generic documents routes
|
||||
path('document/<id>', get_document_view, name="doc-open"),
|
||||
path('document/<id>/remove', remove_document_view, name="doc-remove"),
|
||||
|
||||
# Generic deadline routes
|
||||
path('deadline/<id>/remove', remove_deadline_view, name="deadline-remove"),
|
||||
|
||||
|
||||
50
konova/utils/documents.py
Normal file
50
konova/utils/documents.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 01.09.21
|
||||
|
||||
"""
|
||||
from django.http import FileResponse, HttpRequest
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.forms import RemoveModalForm
|
||||
from konova.models import AbstractDocument
|
||||
|
||||
|
||||
def get_document(doc: AbstractDocument):
|
||||
""" Returns a document as downloadable attachment
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The document id
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return FileResponse(doc.file, as_attachment=True)
|
||||
|
||||
|
||||
def remove_document(request: HttpRequest, doc: AbstractDocument):
|
||||
""" Renders a form for uploading new documents
|
||||
|
||||
This function works using a modal. We are not using the regular way, the django bootstrap modal forms are
|
||||
intended to be used. Instead of View classes we work using the classic way of dealing with forms (see below).
|
||||
It is important to mention, that modal forms, which should reload the page afterwards, must provide a
|
||||
'reload_page' bool in the context. This way, the modal may reload the page or not.
|
||||
|
||||
For further details see the comments in templates/modal or
|
||||
https://github.com/trco/django-bootstrap-modal-forms
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
title = doc.title
|
||||
form = RemoveModalForm(request.POST or None, instance=doc, user=request.user)
|
||||
return form.process_request(
|
||||
request=request,
|
||||
msg_success=_("Document '{}' deleted").format(title)
|
||||
)
|
||||
@@ -17,7 +17,7 @@ from intervention.models import Intervention
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import any_group_check
|
||||
from konova.forms import RemoveModalForm
|
||||
from konova.models import Document, Deadline
|
||||
from konova.models import Deadline
|
||||
from news.models import ServerMessage
|
||||
from konova.settings import SSO_SERVER_BASE
|
||||
|
||||
@@ -97,48 +97,6 @@ def home_view(request: HttpRequest):
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def get_document_view(request: HttpRequest, id: str):
|
||||
""" Returns a document as downloadable attachment
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The document id
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
doc = get_object_or_404(Document, id=id)
|
||||
return FileResponse(doc.file, as_attachment=True)
|
||||
|
||||
|
||||
@login_required
|
||||
def remove_document_view(request: HttpRequest, id: str):
|
||||
""" Renders a form for uploading new documents
|
||||
|
||||
This function works using a modal. We are not using the regular way, the django bootstrap modal forms are
|
||||
intended to be used. Instead of View classes we work using the classic way of dealing with forms (see below).
|
||||
It is important to mention, that modal forms, which should reload the page afterwards, must provide a
|
||||
'reload_page' bool in the context. This way, the modal may reload the page or not.
|
||||
|
||||
For further details see the comments in templates/modal or
|
||||
https://github.com/trco/django-bootstrap-modal-forms
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
doc = get_object_or_404(Document, id=id)
|
||||
title = doc.title
|
||||
form = RemoveModalForm(request.POST or None, instance=doc, user=request.user)
|
||||
return form.process_request(
|
||||
request=request,
|
||||
msg_success=_("Document '{}' deleted").format(title)
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def remove_deadline_view(request: HttpRequest, id:str):
|
||||
""" Renders a modal form for removing a deadline object
|
||||
|
||||
Reference in New Issue
Block a user