Merge pull request '18_File_upload_in_certain_folders' (#21) from 18_File_upload_in_certain_folders into master

Reviewed-on: SGD-Nord/konova#21
This commit is contained in:
Michel Peltriaux 2021-09-17 11:08:00 +02:00
commit 4f8d6e7036
25 changed files with 672 additions and 110 deletions

View File

@ -21,8 +21,9 @@ urlpatterns = [
path('<id>/deadline/new', deadline_new_view, name="acc-new-deadline"), path('<id>/deadline/new', deadline_new_view, name="acc-new-deadline"),
# Documents # Documents
# Document remove route can be found in konova/urls.py
path('<id>/document/new/', new_document_view, name='acc-new-doc'), path('<id>/document/new/', new_document_view, name='acc-new-doc'),
path('document/<doc_id>', get_document_view, name='acc-get-doc'),
path('document/<doc_id>/remove/', remove_document_view, name='acc-remove-doc'),
# Eco-account deductions # Eco-account deductions
path('<id>/remove/<deduction_id>', deduction_remove_view, name='deduction-remove'), path('<id>/remove/<deduction_id>', deduction_remove_view, name='deduction-remove'),

View File

@ -21,8 +21,9 @@ urlpatterns = [
path('<id>/deadline/new', deadline_new_view, name="new-deadline"), path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
# Documents # Documents
# Document remove route can be found in konova/urls.py
path('<id>/document/new/', new_document_view, name='new-doc'), 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'),
# Generic state routes # Generic state routes
path('state/<id>/remove', state_remove_view, name='state-remove'), path('state/<id>/remove', state_remove_view, name='state-remove'),

View File

@ -5,16 +5,19 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 17.11.20 Created on: 17.11.20
""" """
import shutil
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.gis.db import models from django.contrib.gis.db import models
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db.models import Sum from django.db.models import Sum, QuerySet
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from codelist.models import KonovaCode from codelist.models import KonovaCode
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID
from intervention.models import Intervention, ResponsibilityData from intervention.models import Intervention, ResponsibilityData
from konova.models import BaseObject, BaseResource, Geometry, UuidModel from konova.models import BaseObject, BaseResource, Geometry, UuidModel, AbstractDocument, \
generate_document_file_upload_path
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
@ -137,7 +140,6 @@ class AbstractCompensation(BaseObject):
deadlines = models.ManyToManyField("konova.Deadline", blank=True, related_name="+") deadlines = models.ManyToManyField("konova.Deadline", blank=True, related_name="+")
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL) geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
documents = models.ManyToManyField("konova.Document", blank=True)
class Meta: class Meta:
abstract = True abstract = True
@ -197,6 +199,65 @@ class Compensation(AbstractCompensation):
y, y,
) )
def get_documents(self) -> QuerySet:
""" Getter for all documents of a compensation
Returns:
docs (QuerySet): The queryset of all documents
"""
docs = CompensationDocument.objects.filter(
instance=self
)
return docs
class CompensationDocument(AbstractDocument):
"""
Specializes document upload for revocations with certain path
"""
instance = models.ForeignKey(
Compensation,
on_delete=models.CASCADE,
related_name="documents",
)
file = models.FileField(
upload_to=generate_document_file_upload_path,
max_length=1000,
)
def delete(self, *args, **kwargs):
"""
Custom delete functionality for CompensationDocuments.
Removes the folder from the file system if there are no further documents for this entry.
Args:
*args ():
**kwargs ():
Returns:
"""
comp_docs = self.instance.get_documents()
folder_path = None
if comp_docs.count() == 1:
# The only file left for this compensation is the one which is currently processed and will be deleted
# Make sure that the compensation folder itself is deleted as well, not only the file
# Therefore take the folder path from the file path
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
# Remove the file itself
super().delete(*args, **kwargs)
# If a folder path has been set, we need to delete the whole folder!
if folder_path is not None:
try:
shutil.rmtree(folder_path)
except FileNotFoundError:
# Folder seems to be missing already...
pass
class EcoAccount(AbstractCompensation): class EcoAccount(AbstractCompensation):
""" """
@ -298,6 +359,65 @@ class EcoAccount(AbstractCompensation):
return ret_msgs return ret_msgs
def get_documents(self) -> QuerySet:
""" Getter for all documents of an EcoAccount
Returns:
docs (QuerySet): The queryset of all documents
"""
docs = EcoAccountDocument.objects.filter(
instance=self
)
return docs
class EcoAccountDocument(AbstractDocument):
"""
Specializes document upload for revocations with certain path
"""
instance = models.ForeignKey(
EcoAccount,
on_delete=models.CASCADE,
related_name="documents",
)
file = models.FileField(
upload_to=generate_document_file_upload_path,
max_length=1000,
)
def delete(self, *args, **kwargs):
"""
Custom delete functionality for EcoAccountDocuments.
Removes the folder from the file system if there are no further documents for this entry.
Args:
*args ():
**kwargs ():
Returns:
"""
acc_docs = self.instance.get_documents()
folder_path = None
if acc_docs.count() == 1:
# The only file left for this eco account is the one which is currently processed and will be deleted
# Make sure that the compensation folder itself is deleted as well, not only the file
# Therefore take the folder path from the file path
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
# Remove the file itself
super().delete(*args, **kwargs)
# If a folder path has been set, we need to delete the whole folder!
if folder_path is not None:
try:
shutil.rmtree(folder_path)
except FileNotFoundError:
# Folder seems to be missing already...
pass
class EcoAccountDeduction(BaseResource): class EcoAccountDeduction(BaseResource):
""" """

View File

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

View File

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

View File

@ -5,11 +5,12 @@ from django.shortcuts import render, get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from compensation.forms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm from compensation.forms import NewStateModalForm, NewDeadlineModalForm, NewActionModalForm
from compensation.models import Compensation, CompensationState, CompensationAction from compensation.models import Compensation, CompensationState, CompensationAction, CompensationDocument
from compensation.tables import CompensationTable from compensation.tables import CompensationTable
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import * from konova.decorators import *
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm
from konova.utils.documents import get_document, remove_document
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -163,6 +164,43 @@ def new_document_view(request: HttpRequest, id: str):
) )
@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(CompensationDocument, 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(CompensationDocument, id=doc_id)
return remove_document(
request,
doc
)
@login_required @login_required
def state_new_view(request: HttpRequest, id: str): def state_new_view(request: HttpRequest, id: str):
""" Renders a form for adding new states for a compensation """ Renders a form for adding new states for a compensation

View File

@ -14,13 +14,14 @@ from django.http import HttpRequest, Http404
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
from compensation.models import EcoAccount from compensation.models import EcoAccount, EcoAccountDocument
from compensation.tables import EcoAccountTable from compensation.tables import EcoAccountTable
from intervention.forms import NewDeductionForm from intervention.forms import NewDeductionForm
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required from konova.decorators import any_group_check, default_group_required, conservation_office_group_required
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.utils.documents import get_document, remove_document
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -289,6 +290,43 @@ def new_document_view(request: HttpRequest, id: str):
) )
@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(EcoAccountDocument, 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(EcoAccountDocument, id=doc_id)
return remove_document(
request,
doc
)
@login_required @login_required
@default_group_required @default_group_required
def new_deduction_view(request: HttpRequest, id: str): def new_deduction_view(request: HttpRequest, id: str):

View File

@ -1,8 +1,12 @@
import shutil
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.db.models import QuerySet
from compensation.models import AbstractCompensation from compensation.models import AbstractCompensation
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE from konova.models import AbstractDocument, generate_document_file_upload_path
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE, EMA_DOC_PATH
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
@ -84,3 +88,62 @@ class Ema(AbstractCompensation):
# ToDo: Add check methods! # ToDo: Add check methods!
return ret_msgs return ret_msgs
def get_documents(self) -> QuerySet:
""" Getter for all documents of an EMA
Returns:
docs (QuerySet): The queryset of all documents
"""
docs = EmaDocument.objects.filter(
instance=self
)
return docs
class EmaDocument(AbstractDocument):
"""
Specializes document upload for ema with certain path
"""
instance = models.ForeignKey(
Ema,
on_delete=models.CASCADE,
related_name="documents",
)
file = models.FileField(
upload_to=generate_document_file_upload_path,
max_length=1000,
)
def delete(self, *args, **kwargs):
"""
Custom delete functionality for EcoAccountDocuments.
Removes the folder from the file system if there are no further documents for this entry.
Args:
*args ():
**kwargs ():
Returns:
"""
ema_docs = self.instance.get_documents()
folder_path = None
if ema_docs.count() == 1:
# The only file left for this EMA is the one which is currently processed and will be deleted
# Make sure that the compensation folder itself is deleted as well, not only the file
# Therefore take the folder path from the file path
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
# Remove the file itself
super().delete(*args, **kwargs)
# If a folder path has been set, we need to delete the whole folder!
if folder_path is not None:
try:
shutil.rmtree(folder_path)
except FileNotFoundError:
# Folder seems to be missing already...
pass

View File

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

View File

@ -24,6 +24,8 @@ urlpatterns = [
# Documents # Documents
# Document remove route can be found in konova/urls.py # Document remove route can be found in konova/urls.py
path('<id>/document/new/', document_new_view, name='new-doc'), path('<id>/document/new/', document_new_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'),
# Generic state routes # Generic state routes
path('state/<id>/remove', state_remove_view, name='state-remove'), path('state/<id>/remove', state_remove_view, name='state-remove'),

View File

@ -10,9 +10,10 @@ from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlin
from ema.tables import EmaTable from ema.tables import EmaTable
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import conservation_office_group_required from konova.decorators import conservation_office_group_required
from ema.models import Ema from ema.models import Ema, EmaDocument
from konova.forms import RemoveModalForm, NewDocumentForm, SimpleGeomForm, RecordModalForm from konova.forms import RemoveModalForm, NewDocumentForm, SimpleGeomForm, RecordModalForm
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
from konova.utils.documents import get_document, remove_document
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -250,6 +251,43 @@ def document_new_view(request: HttpRequest, id: str):
) )
@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(EmaDocument, 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(EmaDocument, id=doc_id)
return remove_document(
request,
doc
)
@login_required @login_required
def state_remove_view(request: HttpRequest, id: str): def state_remove_view(request: HttpRequest, id: str):
""" Renders a form for removing an EMA state """ Renders a form for removing an EMA state

View File

@ -1,6 +1,7 @@
from django.contrib import admin 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): class InterventionAdmin(admin.ModelAdmin):
@ -12,6 +13,8 @@ class InterventionAdmin(admin.ModelAdmin):
"deleted", "deleted",
] ]
class InterventionDocumentAdmin(AbstractDocumentAdmin):
pass
class ResponsibilityAdmin(admin.ModelAdmin): class ResponsibilityAdmin(admin.ModelAdmin):
list_display = [ list_display = [
@ -47,3 +50,4 @@ admin.site.register(Intervention, InterventionAdmin)
admin.site.register(ResponsibilityData, ResponsibilityAdmin) admin.site.register(ResponsibilityData, ResponsibilityAdmin)
admin.site.register(LegalData, LegalAdmin) admin.site.register(LegalData, LegalAdmin)
admin.site.register(Revocation, RevocationAdmin) 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 django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccountDeduction, EcoAccount 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.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.settings import DEFAULT_LAT, DEFAULT_LON, DEFAULT_ZOOM, ZB_GROUP, ETS_GROUP
from konova.utils.messenger import Messenger from konova.utils.messenger import Messenger
from konova.utils.user_checks import in_group from konova.utils.user_checks import in_group
@ -372,19 +371,9 @@ class NewRevocationForm(BaseModalForm):
user=self.user, user=self.user,
action=UserAction.EDITED 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( revocation = Revocation.objects.create(
date=self.cleaned_data["date"], date=self.cleaned_data["date"],
comment=self.cleaned_data["comment"], comment=self.cleaned_data["comment"],
document=document,
created=created_action, created=created_action,
) )
self.instance.modified = edited_action self.instance.modified = edited_action
@ -392,6 +381,15 @@ class NewRevocationForm(BaseModalForm):
self.instance.log.add(edited_action) self.instance.log.add(edited_action)
self.instance.legal.revocation = revocation self.instance.legal.revocation = revocation
self.instance.legal.save() 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 return revocation

View File

@ -5,19 +5,22 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 17.11.20 Created on: 17.11.20
""" """
import shutil
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.gis.db import models from django.contrib.gis.db import models
from django.db.models import QuerySet
from django.utils.timezone import localtime from django.utils.timezone import localtime
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from codelist.models import KonovaCode from codelist.models import KonovaCode
from codelist.settings import CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_LAW_ID, \ from codelist.settings import CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_LAW_ID, \
CODELIST_PROCESS_TYPE_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.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT
from konova.utils import generators from konova.utils import generators
from organisation.models import Organisation
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
@ -68,15 +71,71 @@ class Revocation(BaseResource):
""" """
date = models.DateField(null=True, blank=True, help_text="Revocation from") date = models.DateField(null=True, blank=True, help_text="Revocation from")
comment = models.TextField(null=True, blank=True) 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 # Make sure related objects are being removed as well
if self.document: if self.document:
self.document.delete() self.document.delete(*args, **kwargs)
super().delete() 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,
max_length=1000,
)
@property
def intervention(self):
"""
Shortcut for opening the related intervention
Returns:
intervention (Intervention)
"""
return self.instance.legaldata.intervention
def delete(self, *args, **kwargs):
"""
Custom delete functionality for RevocationDocuments.
Removes the folder from the file system if there are no further documents for this entry.
Args:
*args ():
**kwargs ():
Returns:
"""
revoc_docs, other_intervention_docs = self.intervention.get_documents()
# Remove the file itself
super().delete(*args, **kwargs)
# Always remove 'revocation' folder
folder_path = self.file.path.split("/")
try:
shutil.rmtree("/".join(folder_path[:-1]))
except FileNotFoundError:
# Revocation subfolder seems to be missing already
pass
if other_intervention_docs.count() == 0:
# If there are no further documents for the intervention, we can simply remove the whole folder as well!
try:
shutil.rmtree("/".join(folder_path[:-2]))
except FileNotFoundError:
# Folder seems to be missing already
pass
class LegalData(UuidModel): class LegalData(UuidModel):
""" """
Holds intervention legal data such as important dates, laws or responsible handler Holds intervention legal data such as important dates, laws or responsible handler
@ -112,7 +171,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): def __str__(self):
return "{} | {} | {}".format( return "{} | {} | {}".format(
@ -131,7 +190,6 @@ class Intervention(BaseObject):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True, null=True,
blank=True, blank=True,
related_name='+',
help_text="Holds data on responsible organizations ('Zulassungsbehörde', 'Eintragungsstelle')" help_text="Holds data on responsible organizations ('Zulassungsbehörde', 'Eintragungsstelle')"
) )
legal = models.OneToOneField( legal = models.OneToOneField(
@ -139,11 +197,9 @@ class Intervention(BaseObject):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True, null=True,
blank=True, blank=True,
related_name='+',
help_text="Holds data on legal dates or law" help_text="Holds data on legal dates or law"
) )
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL) 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 # Checks - Refers to "Genehmigen" but optional
checked = models.OneToOneField( checked = models.OneToOneField(
@ -308,3 +364,66 @@ class Intervention(BaseObject):
on = value.strftime(DEFAULT_DATE_TIME_FORMAT) on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
tooltip = _("Recorded on {} by {}").format(on, self.recorded.user) tooltip = _("Recorded on {} by {}").format(on, self.recorded.user)
return tooltip return tooltip
def get_documents(self) -> (QuerySet, QuerySet):
""" Getter for all documents of an intervention
Returns:
revoc_docs (QuerySet): The queryset of a revocation document
regular_docs (QuerySet): The queryset of regular other documents
"""
revoc_docs = RevocationDocument.objects.filter(
instance=self.legal.revocation
)
regular_docs = InterventionDocument.objects.filter(
instance=self
)
return revoc_docs, regular_docs
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,
max_length=1000,
)
def delete(self, *args, **kwargs):
"""
Custom delete functionality for InterventionDocuments.
Removes the folder from the file system if there are no further documents for this entry.
Args:
*args ():
**kwargs ():
Returns:
"""
revoc_docs, other_intervention_docs = self.instance.get_documents()
folder_path = None
if revoc_docs.count() == 0 and other_intervention_docs.count() == 1:
# The only file left for this intervention is the one which is currently processed and will be deleted
# Make sure that the intervention folder itself is deleted as well, not only the file
# Therefore take the folder path from the file path
folder_path = self.file.path.split("/")[:-1]
folder_path = "/".join(folder_path)
# Remove the file itself
super().delete(*args, **kwargs)
# If a folder path has been set, we need to delete the whole folder!
if folder_path is not None:
try:
shutil.rmtree(folder_path)
except FileNotFoundError:
# Folder seems to be missing already...
pass

View File

@ -39,14 +39,14 @@
{% for doc in intervention.documents.all %} {% for doc in intervention.documents.all %}
<tr> <tr>
<td class="align-middle"> <td class="align-middle">
<a href="{% url 'doc-open' doc.id %}"> <a href="{% url 'intervention:get-doc' doc.id %}">
{{ doc.title }} {{ doc.title }}
</a> </a>
</td> </td>
<td class="align-middle">{{ doc.comment }}</td> <td class="align-middle">{{ doc.comment }}</td>
<td> <td>
{% if is_default_member and has_access %} {% 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' %} {% fa5_icon 'trash' %}
</button> </button>
{% endif %} {% endif %}

View File

@ -31,10 +31,10 @@
{% trans 'From' context 'Revocation' %} {% trans 'From' context 'Revocation' %}
</th> </th>
<th scope="col"> <th scope="col">
{% trans 'Comment' %} {% trans 'Document' %}
</th> </th>
<th scope="col"> <th scope="col">
{% trans 'Document' %} {% trans 'Comment' %}
</th> </th>
<th scope="col"> <th scope="col">
{% trans 'Action' %} {% trans 'Action' %}
@ -48,14 +48,14 @@
<td class="align-middle"> <td class="align-middle">
{{ rev.date }} {{ rev.date }}
</td> </td>
<td class="align-middle">{{ rev.comment }}</td>
<td class="align-middle"> <td class="align-middle">
{% if rev.document %} {% if rev.document %}
<a href="{% url 'doc-open' rev.document.id %}"> <a href="{% url 'intervention:get-doc-revocation' rev.document.id %}">
{{ rev.document.file }} {% trans 'Revocation' %}
</a> </a>
{% endif %} {% endif %}
</td> </td>
<td class="align-middle">{{ rev.comment }}</td>
<td> <td>
{% if is_default_member and has_access %} {% 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' %}"> <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, \ 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, \ 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" app_name = "intervention"
urlpatterns = [ urlpatterns = [
path("", index_view, name="index"), path("", index_view, name="index"),
path('new/', new_view, name='new'), path('new/', new_view, name='new'),
path('<id>/document/new/', new_document_view, name='new-doc'),
path('<id>', open_view, name='open'), path('<id>', open_view, name='open'),
path('<id>/log', log_view, name='log'), path('<id>/log', log_view, name='log'),
path('<id>/edit', edit_view, name='edit'), path('<id>/edit', edit_view, name='edit'),
@ -25,10 +24,16 @@ urlpatterns = [
path('<id>/check', run_check_view, name='run-check'), path('<id>/check', run_check_view, name='run-check'),
path('<id>/record', record_view, name='record'), 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 # Deductions
path('<id>/deduction/new', new_deduction_view, name='acc-new-deduction'), path('<id>/deduction/new', new_deduction_view, name='acc-new-deduction'),
# Revocation routes # Revocation routes
path('<id>/revocation/new', new_revocation_view, name='new-revocation'), path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
path('revocation/<id>/remove', remove_revocation_view, name='remove-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, \ from intervention.forms import NewInterventionForm, EditInterventionForm, ShareInterventionForm, NewRevocationForm, \
RunCheckForm, NewDeductionForm RunCheckForm, NewDeductionForm
from intervention.models import Intervention, Revocation from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
from intervention.tables import InterventionTable from intervention.tables import InterventionTable
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import * from konova.decorators import *
from konova.forms import SimpleGeomForm, NewDocumentForm, RemoveModalForm, RecordModalForm from konova.forms import SimpleGeomForm, NewDocumentForm, RemoveModalForm, RecordModalForm
from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT 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.message_templates import FORM_INVALID, INTERVENTION_INVALID
from konova.utils.user_checks import in_group 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 @login_required
@any_group_check @any_group_check
def open_view(request: HttpRequest, id: str): def open_view(request: HttpRequest, id: str):

View File

@ -7,7 +7,7 @@ Created on: 22.07.21
""" """
from django.contrib import admin from django.contrib import admin
from konova.models import Geometry, Document, Deadline from konova.models import Geometry, Deadline
class GeometryAdmin(admin.ModelAdmin): class GeometryAdmin(admin.ModelAdmin):
@ -17,7 +17,7 @@ class GeometryAdmin(admin.ModelAdmin):
] ]
class DocumentAdmin(admin.ModelAdmin): class AbstractDocumentAdmin(admin.ModelAdmin):
list_display = [ list_display = [
"id", "id",
"title", "title",
@ -36,5 +36,4 @@ class DeadlineAdmin(admin.ModelAdmin):
admin.site.register(Geometry, GeometryAdmin) admin.site.register(Geometry, GeometryAdmin)
admin.site.register(Document, DocumentAdmin)
admin.site.register(Deadline, DeadlineAdmin) admin.site.register(Deadline, DeadlineAdmin)

View File

@ -21,11 +21,11 @@ from django.shortcuts import render
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccount from compensation.models import EcoAccount, Compensation, EcoAccountDocument, CompensationDocument
from ema.models import Ema from ema.models import Ema, EmaDocument
from intervention.models import Intervention from intervention.models import Intervention, Revocation, RevocationDocument, InterventionDocument
from konova.contexts import BaseContext 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 konova.utils.message_templates import FORM_INVALID
from user.models import UserActionLogEntry, UserAction from user.models import UserActionLogEntry, UserAction
@ -307,7 +307,7 @@ class NewDocumentForm(BaseModalForm):
attrs={ attrs={
"class": "w-75" "class": "w-75"
} }
) ),
) )
comment = forms.CharField( comment = forms.CharField(
required=False, 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): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -331,6 +338,12 @@ class NewDocumentForm(BaseModalForm):
self.form_attrs = { self.form_attrs = {
"enctype": "multipart/form-data", # important for file upload "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): def save(self):
with transaction.atomic(): with transaction.atomic():
@ -338,14 +351,14 @@ class NewDocumentForm(BaseModalForm):
user=self.user, user=self.user,
action=UserAction.CREATED, action=UserAction.CREATED,
) )
doc = Document.objects.create( doc = self.document_type.objects.create(
created=action, created=action,
title=self.cleaned_data["title"], title=self.cleaned_data["title"],
comment=self.cleaned_data["comment"], comment=self.cleaned_data["comment"],
file=self.cleaned_data["file"], file=self.cleaned_data["file"],
date_of_creation=self.cleaned_data["creation_date"], date_of_creation=self.cleaned_data["creation_date"],
instance=self.instance,
) )
self.instance.documents.add(doc)
edited_action = UserActionLogEntry.objects.create( edited_action = UserActionLogEntry.objects.create(
user=self.user, user=self.user,

View File

@ -18,6 +18,7 @@ from compensation.settings import COMPENSATION_IDENTIFIER_TEMPLATE, COMPENSATION
ECO_ACCOUNT_IDENTIFIER_TEMPLATE, ECO_ACCOUNT_IDENTIFIER_LENGTH ECO_ACCOUNT_IDENTIFIER_TEMPLATE, ECO_ACCOUNT_IDENTIFIER_LENGTH
from ema.settings import EMA_ACCOUNT_IDENTIFIER_LENGTH, EMA_ACCOUNT_IDENTIFIER_TEMPLATE from ema.settings import EMA_ACCOUNT_IDENTIFIER_LENGTH, EMA_ACCOUNT_IDENTIFIER_TEMPLATE
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_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 konova.utils.generators import generate_random_string
from user.models import UserActionLogEntry, UserAction from user.models import UserActionLogEntry, UserAction
@ -220,7 +221,47 @@ class Deadline(BaseResource):
return None 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. Documents can be attached to compensation or intervention for uploading legal documents or pictures.
""" """
@ -229,6 +270,9 @@ class Document(BaseResource):
file = models.FileField() file = models.FileField()
comment = models.TextField() comment = models.TextField()
class Meta:
abstract = True
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
""" Custom delete function to remove the real file from the hard drive """ Custom delete function to remove the real file from the hard drive
@ -239,7 +283,11 @@ class Document(BaseResource):
Returns: 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) super().delete(using=using, keep_parents=keep_parents)

View File

@ -55,7 +55,20 @@ DEFAULT_GROUP = "Default"
ZB_GROUP = "Registration office" ZB_GROUP = "Registration office"
ETS_GROUP = "Conservation office" ETS_GROUP = "Conservation office"
# Needed to redirect to LANIS # Needed to redirect to LANIS
## Values to be inserted are [zoom_level, x_coord, y_coord] ## 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" 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/{}/"

View File

@ -22,7 +22,7 @@ from konova.autocompletes import OrganisationAutocomplete, NonOfficialOrganisati
RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
from konova.sso.sso import KonovaSSOClient 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) sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY)
urlpatterns = [ urlpatterns = [
@ -38,10 +38,6 @@ urlpatterns = [
path('news/', include("news.urls")), path('news/', include("news.urls")),
path('news/', include("codelist.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 # Generic deadline routes
path('deadline/<id>/remove', remove_deadline_view, name="deadline-remove"), path('deadline/<id>/remove', remove_deadline_view, name="deadline-remove"),

53
konova/utils/documents.py Normal file
View File

@ -0,0 +1,53 @@
"""
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, HttpResponse, Http404
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:
"""
try:
return FileResponse(doc.file, as_attachment=True)
except FileNotFoundError:
raise Http404()
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)
)

View File

@ -17,7 +17,7 @@ from intervention.models import Intervention
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import any_group_check from konova.decorators import any_group_check
from konova.forms import RemoveModalForm from konova.forms import RemoveModalForm
from konova.models import Document, Deadline from konova.models import Deadline
from news.models import ServerMessage from news.models import ServerMessage
from konova.settings import SSO_SERVER_BASE from konova.settings import SSO_SERVER_BASE
@ -97,48 +97,6 @@ def home_view(request: HttpRequest):
return render(request, template, context) 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 @login_required
def remove_deadline_view(request: HttpRequest, id:str): def remove_deadline_view(request: HttpRequest, id:str):
""" Renders a modal form for removing a deadline object """ Renders a modal form for removing a deadline object