86_User_suggestions_and_feedback #111
@ -6,8 +6,11 @@ Created on: 27.09.21
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db.models.fields.files import FieldFile
|
||||||
|
|
||||||
from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED
|
from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \
|
||||||
|
REVOCATION_EDITED, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE
|
||||||
from user.models import User, UserActionLogEntry
|
from user.models import User, UserActionLogEntry
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django import forms
|
from django import forms
|
||||||
@ -15,7 +18,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from compensation.models import EcoAccount, EcoAccountDeduction
|
from compensation.models import EcoAccount, EcoAccountDeduction
|
||||||
from intervention.inputs import TextToClipboardInput
|
from intervention.inputs import TextToClipboardInput
|
||||||
from intervention.models import Intervention, InterventionDocument
|
from intervention.models import Intervention, InterventionDocument, RevocationDocument
|
||||||
from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
|
from konova.forms import BaseModalForm, NewDocumentForm, RemoveModalForm
|
||||||
from konova.utils.general import format_german_float
|
from konova.utils.general import format_german_float
|
||||||
from konova.utils.user_checks import is_default_group_only
|
from konova.utils.user_checks import is_default_group_only
|
||||||
@ -157,6 +160,7 @@ class NewRevocationModalForm(BaseModalForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
document_model = RevocationDocument
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -166,12 +170,61 @@ class NewRevocationModalForm(BaseModalForm):
|
|||||||
"enctype": "multipart/form-data", # important for file upload
|
"enctype": "multipart/form-data", # important for file upload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
super_valid = super().is_valid()
|
||||||
|
|
||||||
|
_file = self.cleaned_data.get("file", None)
|
||||||
|
|
||||||
|
if isinstance(_file, FieldFile):
|
||||||
|
# FieldFile declares that no new file has been uploaded and we do not need to check on the file again
|
||||||
|
return super_valid
|
||||||
|
|
||||||
|
mime_type_valid = self.document_model.is_mime_type_valid(_file)
|
||||||
|
if not mime_type_valid:
|
||||||
|
self.add_error(
|
||||||
|
"file",
|
||||||
|
FILE_TYPE_UNSUPPORTED
|
||||||
|
)
|
||||||
|
|
||||||
|
file_size_valid = self.document_model.is_file_size_valid(_file)
|
||||||
|
if not file_size_valid:
|
||||||
|
self.add_error(
|
||||||
|
"file",
|
||||||
|
FILE_SIZE_TOO_LARGE
|
||||||
|
)
|
||||||
|
|
||||||
|
file_valid = mime_type_valid and file_size_valid
|
||||||
|
return super_valid and file_valid
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
revocation = self.instance.add_revocation(self)
|
revocation = self.instance.add_revocation(self)
|
||||||
self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED)
|
self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED)
|
||||||
return revocation
|
return revocation
|
||||||
|
|
||||||
|
|
||||||
|
class EditRevocationModalForm(NewRevocationModalForm):
|
||||||
|
revocation = None
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.revocation = kwargs.pop("revocation", None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
try:
|
||||||
|
doc = self.revocation.document.file
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
doc = None
|
||||||
|
form_data = {
|
||||||
|
"date": str(self.revocation.date),
|
||||||
|
"file": doc,
|
||||||
|
"comment": self.revocation.comment,
|
||||||
|
}
|
||||||
|
self.load_initial_data(form_data)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
revocation = self.instance.edit_revocation(self)
|
||||||
|
self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_EDITED)
|
||||||
|
return revocation
|
||||||
|
|
||||||
|
|
||||||
class RemoveRevocationModalForm(RemoveModalForm):
|
class RemoveRevocationModalForm(RemoveModalForm):
|
||||||
""" Removing modal form for Revocation
|
""" Removing modal form for Revocation
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ Created on: 15.11.21
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db.models.fields.files import FieldFile
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
@ -202,6 +204,41 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
|
|||||||
)
|
)
|
||||||
return revocation
|
return revocation
|
||||||
|
|
||||||
|
def edit_revocation(self, form):
|
||||||
|
""" Updates a revocation of the intervention
|
||||||
|
|
||||||
|
Args:
|
||||||
|
form (EditRevocationModalForm): The form holding the data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
form_data = form.cleaned_data
|
||||||
|
file = form_data.get("file", None)
|
||||||
|
|
||||||
|
revocation = form.revocation
|
||||||
|
revocation.date = form_data.get("date", None)
|
||||||
|
revocation.comment = form_data.get("comment", None)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
try:
|
||||||
|
revocation.document.date_of_creation = revocation.date
|
||||||
|
revocation.document.comment = revocation.comment
|
||||||
|
if not isinstance(file, FieldFile):
|
||||||
|
revocation.document.replace_file(file)
|
||||||
|
revocation.document.save()
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
revocation.document = RevocationDocument.objects.create(
|
||||||
|
title="revocation_of_{}".format(self.identifier),
|
||||||
|
date_of_creation=revocation.date,
|
||||||
|
comment=revocation.comment,
|
||||||
|
file=file,
|
||||||
|
instance=revocation
|
||||||
|
)
|
||||||
|
revocation.save()
|
||||||
|
|
||||||
|
return revocation
|
||||||
|
|
||||||
def remove_revocation(self, form):
|
def remove_revocation(self, form):
|
||||||
""" Removes a revocation from the intervention
|
""" Removes a revocation from the intervention
|
||||||
|
|
||||||
|
@ -63,9 +63,12 @@
|
|||||||
{{ rev.comment }}
|
{{ rev.comment }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and has_access %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'intervention:remove-revocation' obj.id rev.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove revocation' %}">
|
<button data-form-url="{% url 'intervention:edit-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit revocation' %}">
|
||||||
|
{% fa5_icon 'edit' %}
|
||||||
|
</button>
|
||||||
|
<button data-form-url="{% url 'intervention:remove-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove revocation' %}">
|
||||||
{% fa5_icon 'trash' %}
|
{% fa5_icon 'trash' %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -10,7 +10,7 @@ from django.urls import path
|
|||||||
from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
|
from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
|
||||||
create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
|
create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
|
||||||
record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
|
record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view, \
|
||||||
remove_deduction_view, remove_compensation_view, edit_deduction_view
|
remove_deduction_view, remove_compensation_view, edit_deduction_view, edit_revocation_view
|
||||||
|
|
||||||
app_name = "intervention"
|
app_name = "intervention"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -42,6 +42,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# 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('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'),
|
||||||
path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
|
path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
|
||||||
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
|
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
|
||||||
]
|
]
|
@ -7,7 +7,7 @@ from django.shortcuts import render
|
|||||||
from intervention.forms.forms import NewInterventionForm, EditInterventionForm
|
from intervention.forms.forms import NewInterventionForm, EditInterventionForm
|
||||||
from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
|
from intervention.forms.modalForms import ShareModalForm, NewRevocationModalForm, \
|
||||||
CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \
|
CheckModalForm, NewDeductionModalForm, NewInterventionDocumentForm, RemoveEcoAccountDeductionModalForm, \
|
||||||
RemoveRevocationModalForm, EditEcoAccountDeductionModalForm
|
RemoveRevocationModalForm, EditEcoAccountDeductionModalForm, EditRevocationModalForm
|
||||||
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
|
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
|
||||||
@ -18,7 +18,7 @@ from konova.utils.documents import remove_document, get_document
|
|||||||
from konova.utils.generators import generate_qr_code
|
from konova.utils.generators import generate_qr_code
|
||||||
from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \
|
from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \
|
||||||
CHECKED_RECORDED_RESET, DEDUCTION_REMOVED, DEDUCTION_ADDED, REVOCATION_ADDED, REVOCATION_REMOVED, \
|
CHECKED_RECORDED_RESET, DEDUCTION_REMOVED, DEDUCTION_ADDED, REVOCATION_ADDED, REVOCATION_REMOVED, \
|
||||||
COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED
|
COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED
|
||||||
from konova.utils.user_checks import in_group
|
from konova.utils.user_checks import in_group
|
||||||
|
|
||||||
|
|
||||||
@ -331,6 +331,31 @@ def remove_view(request: HttpRequest, id: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
@shared_access_required(Intervention, "id")
|
||||||
|
def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str):
|
||||||
|
""" Renders a edit view for a revocation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention's id as string
|
||||||
|
revocation_id (str): The revocation's id as string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
|
revocation = get_object_or_404(Revocation, id=revocation_id)
|
||||||
|
|
||||||
|
form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, revocation=revocation, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
REVOCATION_EDITED,
|
||||||
|
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@default_group_required
|
@default_group_required
|
||||||
@shared_access_required(Intervention, "id")
|
@shared_access_required(Intervention, "id")
|
||||||
@ -339,7 +364,8 @@ def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
id (str): The revocation's id as string
|
id (str): The intervention's id as string
|
||||||
|
revocation_id (str): The revocation's id as string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ from konova.contexts import BaseContext
|
|||||||
from konova.models import BaseObject, Geometry, RecordableObjectMixin
|
from konova.models import BaseObject, Geometry, RecordableObjectMixin
|
||||||
from konova.settings import DEFAULT_SRID
|
from konova.settings import DEFAULT_SRID
|
||||||
from konova.tasks import celery_update_parcels
|
from konova.tasks import celery_update_parcels
|
||||||
from konova.utils.message_templates import FORM_INVALID
|
from konova.utils.message_templates import FORM_INVALID, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE
|
||||||
from user.models import UserActionLogEntry
|
from user.models import UserActionLogEntry
|
||||||
|
|
||||||
|
|
||||||
@ -424,14 +424,14 @@ class NewDocumentForm(BaseModalForm):
|
|||||||
if not mime_type_valid:
|
if not mime_type_valid:
|
||||||
self.add_error(
|
self.add_error(
|
||||||
"file",
|
"file",
|
||||||
_("Unsupported file type")
|
FILE_TYPE_UNSUPPORTED
|
||||||
)
|
)
|
||||||
|
|
||||||
file_size_valid = self.document_model.is_file_size_valid(_file)
|
file_size_valid = self.document_model.is_file_size_valid(_file)
|
||||||
if not file_size_valid:
|
if not file_size_valid:
|
||||||
self.add_error(
|
self.add_error(
|
||||||
"file",
|
"file",
|
||||||
_("File too large")
|
FILE_SIZE_TOO_LARGE
|
||||||
)
|
)
|
||||||
|
|
||||||
file_valid = mime_type_valid and file_size_valid
|
file_valid = mime_type_valid and file_size_valid
|
||||||
|
@ -101,3 +101,19 @@ class AbstractDocument(BaseResource):
|
|||||||
def is_file_size_valid(cls, _file):
|
def is_file_size_valid(cls, _file):
|
||||||
max_size = cls._maximum_file_size * pow(1000, 2)
|
max_size = cls._maximum_file_size * pow(1000, 2)
|
||||||
return _file.size <= max_size
|
return _file.size <= max_size
|
||||||
|
|
||||||
|
def replace_file(self, new_file):
|
||||||
|
""" Replaces the old file on the hard drive with the new one
|
||||||
|
|
||||||
|
Args:
|
||||||
|
new_file (File): The new file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
os.remove(self.file.file.name)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
self.file = new_file
|
||||||
|
self.save()
|
@ -18,6 +18,10 @@ MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
|
|||||||
|
|
||||||
CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
|
CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
|
||||||
|
|
||||||
|
# FILES
|
||||||
|
FILE_TYPE_UNSUPPORTED = _("Unsupported file type")
|
||||||
|
FILE_SIZE_TOO_LARGE = _("File too large")
|
||||||
|
|
||||||
# ECO ACCOUNT
|
# ECO ACCOUNT
|
||||||
CANCEL_ACC_RECORDED_OR_DEDUCTED = _("Action canceled. Eco account is recorded or deductions exist. Only conservation office member can perform this action.")
|
CANCEL_ACC_RECORDED_OR_DEDUCTED = _("Action canceled. Eco account is recorded or deductions exist. Only conservation office member can perform this action.")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user