#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:
		
							parent
							
								
									8eb9d4f9ee
								
							
						
					
					
						commit
						3c9d73533f
					
				@ -21,8 +21,9 @@ urlpatterns = [
 | 
			
		||||
    path('<id>/deadline/new', deadline_new_view, name="acc-new-deadline"),
 | 
			
		||||
 | 
			
		||||
    # Documents
 | 
			
		||||
    # Document remove route can be found in konova/urls.py
 | 
			
		||||
    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
 | 
			
		||||
    path('<id>/remove/<deduction_id>', deduction_remove_view, name='deduction-remove'),
 | 
			
		||||
 | 
			
		||||
@ -21,8 +21,9 @@ urlpatterns = [
 | 
			
		||||
    path('<id>/deadline/new', deadline_new_view, name="new-deadline"),
 | 
			
		||||
 | 
			
		||||
    # Documents
 | 
			
		||||
    # Document remove route can be found in konova/urls.py
 | 
			
		||||
    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
 | 
			
		||||
    path('state/<id>/remove', state_remove_view, name='state-remove'),
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,8 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from codelist.models import KonovaCode
 | 
			
		||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID
 | 
			
		||||
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 user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
@ -137,7 +138,6 @@ class AbstractCompensation(BaseObject):
 | 
			
		||||
    deadlines = models.ManyToManyField("konova.Deadline", blank=True, related_name="+")
 | 
			
		||||
 | 
			
		||||
    geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
 | 
			
		||||
    documents = models.ManyToManyField("konova.Document", blank=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
@ -198,6 +198,20 @@ class Compensation(AbstractCompensation):
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccount(AbstractCompensation):
 | 
			
		||||
    """
 | 
			
		||||
    An eco account is a kind of 'prepaid' compensation. It can be compared to an account that already has been filled
 | 
			
		||||
@ -299,6 +313,20 @@ class EcoAccount(AbstractCompensation):
 | 
			
		||||
        return ret_msgs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccountDeduction(BaseResource):
 | 
			
		||||
    """
 | 
			
		||||
    A deduction object for eco accounts
 | 
			
		||||
 | 
			
		||||
@ -39,14 +39,14 @@
 | 
			
		||||
            {% for doc in obj.documents.all %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    <a href="{% url 'doc-open' doc.id %}">
 | 
			
		||||
                    <a href="{% url 'compensation: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 'compensation:remove-doc' doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -39,14 +39,14 @@
 | 
			
		||||
            {% for doc in obj.documents.all %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    <a href="{% url 'doc-open' doc.id %}">
 | 
			
		||||
                    <a href="{% url 'compensation:acc-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 'compensation:acc-remove-doc' doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -5,11 +5,12 @@ from django.shortcuts import render, get_object_or_404
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
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 konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import *
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm
 | 
			
		||||
from konova.utils.documents import get_document, remove_document
 | 
			
		||||
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
 | 
			
		||||
def state_new_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders a form for adding new states for a compensation
 | 
			
		||||
 | 
			
		||||
@ -14,13 +14,14 @@ from django.http import HttpRequest, Http404
 | 
			
		||||
from django.shortcuts import render, get_object_or_404
 | 
			
		||||
 | 
			
		||||
from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm
 | 
			
		||||
from compensation.models import EcoAccount
 | 
			
		||||
from compensation.models import EcoAccount, EcoAccountDocument
 | 
			
		||||
from compensation.tables import EcoAccountTable
 | 
			
		||||
from intervention.forms import NewDeductionForm
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import any_group_check, default_group_required, conservation_office_group_required
 | 
			
		||||
from konova.forms import RemoveModalForm, SimpleGeomForm, NewDocumentForm, RecordModalForm
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
@default_group_required
 | 
			
		||||
def new_deduction_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,8 @@ from django.contrib.auth.models import User
 | 
			
		||||
from django.db import models
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -83,4 +84,18 @@ class Ema(AbstractCompensation):
 | 
			
		||||
 | 
			
		||||
        # ToDo: Add check methods!
 | 
			
		||||
 | 
			
		||||
        return ret_msgs
 | 
			
		||||
        return ret_msgs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    )
 | 
			
		||||
@ -39,14 +39,14 @@
 | 
			
		||||
            {% for doc in obj.documents.all %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="align-middle">
 | 
			
		||||
                    <a href="{% url 'doc-open' doc.id %}">
 | 
			
		||||
                    <a href="{% url 'ema: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 'ema:remove-doc' doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove document' %}">
 | 
			
		||||
                        {% fa5_icon 'trash' %}
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,8 @@ urlpatterns = [
 | 
			
		||||
    # Documents
 | 
			
		||||
    # Document remove route can be found in konova/urls.py
 | 
			
		||||
    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
 | 
			
		||||
    path('state/<id>/remove', state_remove_view, name='state-remove'),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										40
									
								
								ema/views.py
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								ema/views.py
									
									
									
									
									
								
							@ -10,9 +10,10 @@ from compensation.forms import NewStateModalForm, NewActionModalForm, NewDeadlin
 | 
			
		||||
from ema.tables import EmaTable
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
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.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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
def state_remove_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders a form for removing an EMA state
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
    )
 | 
			
		||||
@ -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 %}
 | 
			
		||||
 | 
			
		||||
@ -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' %}">
 | 
			
		||||
 | 
			
		||||
@ -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'),
 | 
			
		||||
]
 | 
			
		||||
@ -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):
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user