86_User_suggestions_and_feedback #111

Merged
mpeltriaux merged 31 commits from 86_User_suggestions_and_feedback into master 2022-02-10 13:31:55 +01:00
8 changed files with 151 additions and 11 deletions
Showing only changes of commit 1b5cda648e - Show all commits

View File

@ -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

View File

@ -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

View File

@ -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 %}

View File

@ -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'),
] ]

View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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.")