#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:
mipel
2021-09-01 16:24:49 +02:00
parent 17d574e2a5
commit f64a11cb50
25 changed files with 437 additions and 109 deletions

View File

@@ -1,6 +1,7 @@
from django.contrib import admin
from intervention.models import Intervention, ResponsibilityData, LegalData, Revocation
from intervention.models import Intervention, ResponsibilityData, LegalData, Revocation, InterventionDocument
from konova.admin import AbstractDocumentAdmin
class InterventionAdmin(admin.ModelAdmin):
@@ -12,6 +13,8 @@ class InterventionAdmin(admin.ModelAdmin):
"deleted",
]
class InterventionDocumentAdmin(AbstractDocumentAdmin):
pass
class ResponsibilityAdmin(admin.ModelAdmin):
list_display = [
@@ -47,3 +50,4 @@ admin.site.register(Intervention, InterventionAdmin)
admin.site.register(ResponsibilityData, ResponsibilityAdmin)
admin.site.register(LegalData, LegalAdmin)
admin.site.register(Revocation, RevocationAdmin)
admin.site.register(InterventionDocument, InterventionDocumentAdmin)

View File

@@ -15,9 +15,8 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccountDeduction, EcoAccount
from intervention.models import Intervention, Revocation
from intervention.models import Intervention, Revocation, RevocationDocument
from konova.forms import BaseForm, BaseModalForm
from konova.models import Document
from konova.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM, ZB_GROUP, ETS_GROUP
from konova.utils.messenger import Messenger
from konova.utils.user_checks import in_group
@@ -372,19 +371,9 @@ class NewRevocationForm(BaseModalForm):
user=self.user,
action=UserAction.EDITED
)
if self.cleaned_data["file"]:
document = Document.objects.create(
title="revocation_of_{}".format(self.instance.identifier),
date_of_creation=self.cleaned_data["date"],
comment=self.cleaned_data["comment"],
file=self.cleaned_data["file"],
)
else:
document = None
revocation = Revocation.objects.create(
date=self.cleaned_data["date"],
comment=self.cleaned_data["comment"],
document=document,
created=created_action,
)
self.instance.modified = edited_action
@@ -392,6 +381,15 @@ class NewRevocationForm(BaseModalForm):
self.instance.log.add(edited_action)
self.instance.legal.revocation = revocation
self.instance.legal.save()
if self.cleaned_data["file"]:
RevocationDocument.objects.create(
title="revocation_of_{}".format(self.instance.identifier),
date_of_creation=self.cleaned_data["date"],
comment=self.cleaned_data["comment"],
file=self.cleaned_data["file"],
instance=revocation
)
return revocation

View File

@@ -13,11 +13,11 @@ from django.utils.translation import gettext_lazy as _
from codelist.models import KonovaCode
from codelist.settings import CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_LAW_ID, \
CODELIST_PROCESS_TYPE_ID
from konova.models import BaseObject, Geometry, UuidModel, BaseResource
from konova.models import BaseObject, Geometry, UuidModel, BaseResource, AbstractDocument, \
generate_document_file_upload_path
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT
from konova.utils import generators
from organisation.models import Organisation
from user.models import UserActionLogEntry
@@ -68,15 +68,32 @@ class Revocation(BaseResource):
"""
date = models.DateField(null=True, blank=True, help_text="Revocation from")
comment = models.TextField(null=True, blank=True)
document = models.ForeignKey("konova.Document", blank=True, null=True, on_delete=models.SET_NULL)
def delete(self):
def delete(self, *args, **kwargs):
# Make sure related objects are being removed as well
if self.document:
self.document.delete()
self.document.delete(*args, **kwargs)
super().delete()
class RevocationDocument(AbstractDocument):
"""
Specializes document upload for revocations with certain path
"""
instance = models.OneToOneField(
Revocation,
on_delete=models.CASCADE,
related_name="document",
)
file = models.FileField(
upload_to=generate_document_file_upload_path
)
@property
def intervention(self):
return self.instance.legaldata.intervention
class LegalData(UuidModel):
"""
Holds intervention legal data such as important dates, laws or responsible handler
@@ -112,7 +129,7 @@ class LegalData(UuidModel):
}
)
revocation = models.ForeignKey(Revocation, null=True, blank=True, help_text="Refers to 'Widerspruch am'", on_delete=models.SET_NULL)
revocation = models.OneToOneField(Revocation, null=True, blank=True, help_text="Refers to 'Widerspruch am'", on_delete=models.SET_NULL)
def __str__(self):
return "{} | {} | {}".format(
@@ -131,7 +148,6 @@ class Intervention(BaseObject):
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='+',
help_text="Holds data on responsible organizations ('Zulassungsbehörde', 'Eintragungsstelle')"
)
legal = models.OneToOneField(
@@ -139,11 +155,9 @@ class Intervention(BaseObject):
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='+',
help_text="Holds data on legal dates or law"
)
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
documents = models.ManyToManyField("konova.Document", blank=True)
# Checks - Refers to "Genehmigen" but optional
checked = models.OneToOneField(
@@ -307,4 +321,18 @@ class Intervention(BaseObject):
value = localtime(value)
on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
tooltip = _("Recorded on {} by {}").format(on, self.recorded.user)
return tooltip
return tooltip
class InterventionDocument(AbstractDocument):
"""
Specializes document upload for an intervention with certain path
"""
instance = models.ForeignKey(
Intervention,
on_delete=models.CASCADE,
related_name="documents",
)
file = models.FileField(
upload_to=generate_document_file_upload_path
)

View File

@@ -39,14 +39,14 @@
{% for doc in intervention.documents.all %}
<tr>
<td class="align-middle">
<a href="{% url 'doc-open' doc.id %}">
<a href="{% url 'intervention:get-doc' doc.id %}">
{{ doc.title }}
</a>
</td>
<td class="align-middle">{{ doc.comment }}</td>
<td>
{% if is_default_member and has_access %}
<button data-form-url="{% url 'doc-remove' doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
<button data-form-url="{% url 'intervention:remove-doc' doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}

View File

@@ -31,10 +31,10 @@
{% trans 'From' context 'Revocation' %}
</th>
<th scope="col">
{% trans 'Comment' %}
{% trans 'Document' %}
</th>
<th scope="col">
{% trans 'Document' %}
{% trans 'Comment' %}
</th>
<th scope="col">
{% trans 'Action' %}
@@ -48,14 +48,14 @@
<td class="align-middle">
{{ rev.date }}
</td>
<td class="align-middle">{{ rev.comment }}</td>
<td class="align-middle">
{% if rev.document %}
<a href="{% url 'doc-open' rev.document.id %}">
{{ rev.document.file }}
<a href="{% url 'intervention:get-doc-revocation' rev.document.id %}">
{% trans 'Revocation' %}
</a>
{% endif %}
</td>
<td class="align-middle">{{ rev.comment }}</td>
<td>
{% if is_default_member and has_access %}
<button data-form-url="{% url 'intervention:remove-revocation' rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove revocation' %}">

View File

@@ -9,13 +9,12 @@ from django.urls import path
from intervention.views import index_view, new_view, open_view, edit_view, remove_view, new_document_view, share_view, \
create_share_view, remove_revocation_view, new_revocation_view, run_check_view, log_view, new_deduction_view, \
record_view
record_view, remove_document_view, get_document_view, get_revocation_view
app_name = "intervention"
urlpatterns = [
path("", index_view, name="index"),
path('new/', new_view, name='new'),
path('<id>/document/new/', new_document_view, name='new-doc'),
path('<id>', open_view, name='open'),
path('<id>/log', log_view, name='log'),
path('<id>/edit', edit_view, name='edit'),
@@ -25,10 +24,16 @@ urlpatterns = [
path('<id>/check', run_check_view, name='run-check'),
path('<id>/record', record_view, name='record'),
# Documents
path('<id>/document/new/', new_document_view, name='new-doc'),
path('document/<doc_id>', get_document_view, name='get-doc'),
path('document/<doc_id>/remove/', remove_document_view, name='remove-doc'),
# Deductions
path('<id>/deduction/new', new_deduction_view, name='acc-new-deduction'),
# Revocation routes
path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
path('revocation/<id>/remove', remove_revocation_view, name='remove-revocation'),
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
]

View File

@@ -6,12 +6,13 @@ from django.shortcuts import render, get_object_or_404
from intervention.forms import NewInterventionForm, EditInterventionForm, ShareInterventionForm, NewRevocationForm, \
RunCheckForm, NewDeductionForm
from intervention.models import Intervention, Revocation
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
from intervention.tables import InterventionTable
from konova.contexts import BaseContext
from konova.decorators import *
from konova.forms import SimpleGeomForm, NewDocumentForm, RemoveModalForm, RecordModalForm
from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT
from konova.utils.documents import remove_document, get_document
from konova.utils.message_templates import FORM_INVALID, INTERVENTION_INVALID
from konova.utils.user_checks import in_group
@@ -94,6 +95,60 @@ def new_document_view(request: HttpRequest, id: str):
)
@login_required
def get_revocation_view(request: HttpRequest, doc_id: str):
""" Returns the revocation document as downloadable file
Wraps the generic document fetcher function from konova.utils.
Args:
request (HttpRequest): The incoming request
doc_id (str): The document id
Returns:
"""
doc = get_object_or_404(RevocationDocument, id=doc_id)
return get_document(doc)
@login_required
def get_document_view(request: HttpRequest, doc_id: str):
""" Returns the document as downloadable file
Wraps the generic document fetcher function from konova.utils.
Args:
request (HttpRequest): The incoming request
doc_id (str): The document id
Returns:
"""
doc = get_object_or_404(InterventionDocument, id=doc_id)
return get_document(doc)
@login_required
def remove_document_view(request: HttpRequest, doc_id: str):
""" Removes the document from the database and file system
Wraps the generic functionality from konova.utils.
Args:
request (HttpRequest): The incoming request
doc_id (str): The document id
Returns:
"""
doc = get_object_or_404(InterventionDocument, id=doc_id)
return remove_document(
request,
doc
)
@login_required
@any_group_check
def open_view(request: HttpRequest, id: str):