""" Author: Michel Peltriaux Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany Contact: michel.peltriaux@sgdnord.rlp.de Created on: 09.08.21 """ from django.contrib import messages from django.db.models import Sum from django.urls import reverse from django.utils.translation import gettext_lazy as _ from django.contrib.auth.decorators import login_required from django.core.exceptions import ObjectDoesNotExist from django.http import HttpRequest, Http404, JsonResponse from django.shortcuts import render, get_object_or_404, redirect from compensation.forms.forms import NewEcoAccountForm, EditEcoAccountForm from compensation.forms.modalForms import NewStateModalForm, NewActionModalForm, NewDeadlineModalForm from compensation.models import EcoAccount, EcoAccountDocument, CompensationState, CompensationAction from compensation.tables import EcoAccountTable from intervention.forms.modalForms import NewDeductionModalForm, ShareInterventionModalForm from konova.contexts import BaseContext from konova.decorators import any_group_check, default_group_required, conservation_office_group_required, \ shared_access_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.generators import generate_qr_code from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \ CANCEL_ACC_RECORDED_OR_DEDUCTED from konova.utils.user_checks import in_group @login_required @any_group_check def index_view(request: HttpRequest): """ Renders the index view for eco accounts Args: request (HttpRequest): The incoming request Returns: A rendered view """ template = "generic_index.html" eco_accounts = EcoAccount.objects.filter( deleted=None, ) table = EcoAccountTable( request=request, queryset=eco_accounts ) context = { "table": table, } context = BaseContext(request, context).context return render(request, template, context) @login_required @default_group_required def new_view(request: HttpRequest): """ Renders a view for a new eco account creation Args: request (HttpRequest): The incoming request Returns: """ template = "compensation/form/view.html" data_form = NewEcoAccountForm(request.POST or None) geom_form = SimpleGeomForm(request.POST or None, read_only=False) if request.method == "POST": if data_form.is_valid() and geom_form.is_valid(): generated_identifier = data_form.cleaned_data.get("identifier", None) acc = data_form.save(request.user, geom_form) if generated_identifier != acc.identifier: messages.info( request, IDENTIFIER_REPLACED.format( generated_identifier, acc.identifier ) ) messages.success(request, _("Eco-Account {} added").format(acc.identifier)) return redirect("compensation:acc-detail", id=acc.id) else: messages.error(request, FORM_INVALID, extra_tags="danger",) else: # For clarification: nothing in this case pass context = { "form": data_form, "geom_form": geom_form, } context = BaseContext(request, context).context return render(request, template, context) @login_required @default_group_required def new_id_view(request: HttpRequest): """ JSON endpoint Provides fetching of free identifiers for e.g. AJAX calls """ tmp = EcoAccount() identifier = tmp.generate_new_identifier() while EcoAccount.objects.filter(identifier=identifier).exists(): identifier = tmp.generate_new_identifier() return JsonResponse( data={ "identifier": identifier } ) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def edit_view(request: HttpRequest, id: str): """ Renders a view for editing compensations Args: request (HttpRequest): The incoming request Returns: """ template = "compensation/form/view.html" # Get object from db acc = get_object_or_404(EcoAccount, id=id) # Create forms, initialize with values from db/from POST request data_form = EditEcoAccountForm(request.POST or None, instance=acc) geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc) if request.method == "POST": if data_form.is_valid() and geom_form.is_valid(): # The data form takes the geom form for processing, as well as the performing user acc = data_form.save(request.user, geom_form) messages.success(request, _("Eco-Account {} edited").format(acc.identifier)) return redirect("compensation:acc-detail", id=acc.id) else: messages.error(request, FORM_INVALID, extra_tags="danger",) else: # For clarification: nothing in this case pass context = { "form": data_form, "geom_form": geom_form, } context = BaseContext(request, context).context return render(request, template, context) @login_required @any_group_check def detail_view(request: HttpRequest, id: str): """ Renders a detail view for a compensation Args: request (HttpRequest): The incoming request id (str): The compensation's id Returns: """ template = "compensation/detail/eco_account/view.html" acc = get_object_or_404( EcoAccount.objects.prefetch_related( "deadlines", ).select_related( 'geometry', 'responsible', ), id=id ) geom_form = SimpleGeomForm(instance=acc) _user = request.user is_data_shared = acc.is_shared_with(_user) # Order states according to surface before_states = acc.before_states.order_by("-surface") after_states = acc.after_states.order_by("-surface") # Precalculate logical errors between before- and after-states # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0 sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0 diff_states = abs(sum_before_states - sum_after_states) # Calculate rest of available surface for deductions available_total, available_relative = acc.get_available_rest() # Prefetch related data to decrease the amount of db connections deductions = acc.deductions.filter( intervention__deleted=None, ) actions = acc.actions.all() if not is_data_shared: messages.info(request, DATA_UNSHARED_EXPLANATION) context = { "obj": acc, "geom_form": geom_form, "has_access": is_data_shared, "before_states": before_states, "after_states": after_states, "sum_before_states": sum_before_states, "sum_after_states": sum_after_states, "diff_states": diff_states, "available": available_relative, "available_total": available_total, "is_default_member": in_group(_user, DEFAULT_GROUP), "is_zb_member": in_group(_user, ZB_GROUP), "is_ets_member": in_group(_user, ETS_GROUP), "LANIS_LINK": acc.get_LANIS_link(), "deductions": deductions, "actions": actions, } context = BaseContext(request, context).context return render(request, template, context) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def remove_view(request: HttpRequest, id: str): """ Renders a modal view for removing the eco account Args: request (HttpRequest): The incoming request id (str): The account's id Returns: """ acc = get_object_or_404(EcoAccount, id=id) # If the eco account has already been recorded OR there are already deductions, it can not be deleted by a regular # default group user if acc.recorded is not None or acc.deductions.exists(): user = request.user if not in_group(user, ETS_GROUP): messages.info(request, CANCEL_ACC_RECORDED_OR_DEDUCTED) return redirect("compensation:acc-detail", id=id) form = RemoveModalForm(request.POST or None, instance=acc, user=request.user) return form.process_request( request=request, msg_success=_("Eco-account removed"), redirect_url=reverse("compensation:acc-index"), ) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str): """ Renders a modal view for removing deductions Args: request (HttpRequest): The incoming request id (str): The eco account's id deduction_id (str): The deduction's id Returns: """ acc = get_object_or_404(EcoAccount, id=id) try: eco_deduction = acc.deductions.get(id=deduction_id) except ObjectDoesNotExist: raise Http404("Unknown deduction") form = RemoveModalForm(request.POST or None, instance=eco_deduction, user=request.user) return form.process_request( request=request, msg_success=_("Deduction removed") ) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def log_view(request: HttpRequest, id: str): """ Renders a log view using modal Args: request (HttpRequest): The incoming request id (str): The eco acount's id Returns: """ comp = get_object_or_404(EcoAccount, id=id) template = "modal/modal_generic.html" body_template = "log.html" context = { "modal_body_template": body_template, "log": comp.log.all(), "modal_title": _("Log"), } context = BaseContext(request, context).context return render(request, template, context) @login_required @conservation_office_group_required @shared_access_required(EcoAccount, "id") def record_view(request: HttpRequest, id:str): """ Renders a modal form for recording an eco account Args: request (HttpRequest): The incoming request id (str): The account's id Returns: """ acc = get_object_or_404(EcoAccount, id=id) form = RecordModalForm(request.POST or None, instance=acc, user=request.user) msg_succ = _("{} unrecorded") if acc.recorded else _("{} recorded") msg_succ = msg_succ.format(acc.identifier) return form.process_request( request, msg_succ ) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def state_new_view(request: HttpRequest, id: str): """ Renders a form for adding new states for an eco account Args: request (HttpRequest): The incoming request id (str): The account's id to which the new state will be related Returns: """ acc = get_object_or_404(EcoAccount, id=id) form = NewStateModalForm(request.POST or None, instance=acc, user=request.user) return form.process_request( request, msg_success=_("State added") ) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def action_new_view(request: HttpRequest, id: str): """ Renders a form for adding new actions for an eco account Args: request (HttpRequest): The incoming request id (str): The account's id to which the new state will be related Returns: """ acc = get_object_or_404(EcoAccount, id=id) form = NewActionModalForm(request.POST or None, instance=acc, user=request.user) return form.process_request( request, msg_success=_("Action added") ) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def state_remove_view(request: HttpRequest, id: str, state_id: str): """ Renders a form for removing a compensation state Args: request (HttpRequest): The incoming request id (str): The compensation's id state_id (str): The state's id Returns: """ state = get_object_or_404(CompensationState, id=state_id) form = RemoveModalForm(request.POST or None, instance=state, user=request.user) return form.process_request( request, msg_success=_("State removed") ) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def action_remove_view(request: HttpRequest, id: str, action_id: str): """ Renders a form for removing a compensation action Args: request (HttpRequest): The incoming request id (str): The compensation's id id (str): The action's id Returns: """ action = get_object_or_404(CompensationAction, id=action_id) form = RemoveModalForm(request.POST or None, instance=action, user=request.user) return form.process_request( request, msg_success=_("Action removed") ) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def deadline_new_view(request: HttpRequest, id: str): """ Renders a form for adding new states for an eco account Args: request (HttpRequest): The incoming request id (str): The account's id to which the new state will be related Returns: """ acc = get_object_or_404(EcoAccount, id=id) form = NewDeadlineModalForm(request.POST or None, instance=acc, user=request.user) return form.process_request( request, msg_success=_("Deadline added") ) @login_required @default_group_required @shared_access_required(EcoAccount, "id") def new_document_view(request: HttpRequest, id: str): """ Renders a form for uploading new documents Args: request (HttpRequest): The incoming request id (str): The account's id to which the new document will be related Returns: """ acc = get_object_or_404(EcoAccount, id=id) form = NewDocumentForm(request.POST or None, request.FILES or None, instance=acc, user=request.user) return form.process_request( request, msg_success=_("Document added") ) @login_required @default_group_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) user = request.user instance = doc.instance # File download only possible if related instance is shared with user if not instance.users.filter(id=user.id): messages.info( request, DATA_UNSHARED ) return redirect("compensation:acc-detail", id=instance.id) return get_document(doc) @login_required @default_group_required @shared_access_required(EcoAccount, "id") 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 @shared_access_required(EcoAccount, "id") def new_deduction_view(request: HttpRequest, id: str): """ Renders a modal form view for creating deductions Args: request (HttpRequest): THe incoming request id (str): The eco account's id Returns: """ acc = get_object_or_404(EcoAccount, id=id) form = NewDeductionModalForm(request.POST or None, instance=acc, user=request.user) return form.process_request( request, msg_success=_("Deduction added") ) def report_view(request:HttpRequest, id: str): """ Renders the public report view Args: request (HttpRequest): The incoming request id (str): The id of the intervention Returns: """ # Reuse the compensation report template since EcoAccounts are structurally identical template = "compensation/report/eco_account/report.html" acc = get_object_or_404(EcoAccount, id=id) # If intervention is not recorded (yet or currently) we need to render another template without any data if not acc.recorded: template = "report/unavailable.html" return render(request, template, {}) # Prepare data for map viewer geom_form = SimpleGeomForm( instance=acc ) qrcode_img = generate_qr_code( request.build_absolute_uri(reverse("ema:report", args=(id,))), 10 ) qrcode_img_lanis = generate_qr_code( acc.get_LANIS_link(), 7 ) # Order states by surface before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent") after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent") actions = acc.actions.all().select_related("action_type__parent") # Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier) deductions = acc.deductions.all()\ .distinct("intervention")\ .select_related("intervention")\ .values_list("intervention__id", "intervention__identifier", named=True) context = { "obj": acc, "qrcode": qrcode_img, "qrcode_lanis": qrcode_img_lanis, "has_access": False, # disables action buttons during rendering "before_states": before_states, "after_states": after_states, "geom_form": geom_form, "actions": actions, "deductions": deductions, } context = BaseContext(request, context).context return render(request, template, context) @login_required def share_view(request: HttpRequest, id: str, token: str): """ Performs sharing of an eco account If token given in url is not valid, the user will be redirected to the dashboard Args: request (HttpRequest): The incoming request id (str): EcoAccount's id token (str): Access token for EcoAccount Returns: """ user = request.user obj = get_object_or_404(EcoAccount, id=id) # Check tokens if obj.access_token == token: # Send different messages in case user has already been added to list of sharing users if obj.is_shared_with(user): messages.info( request, _("{} has already been shared with you").format(obj.identifier) ) else: messages.success( request, _("{} has been shared with you").format(obj.identifier) ) obj.users.add(user) return redirect("compensation:acc-detail", id=id) else: messages.error( request, _("Share link invalid"), extra_tags="danger", ) return redirect("home") @login_required @default_group_required @shared_access_required(EcoAccount, "id") def create_share_view(request: HttpRequest, id: str): """ Renders sharing form for an eco account Args: request (HttpRequest): The incoming request id (str): EcoAccount's id Returns: """ obj = get_object_or_404(EcoAccount, id=id) form = ShareInterventionModalForm(request.POST or None, instance=obj, request=request) return form.process_request( request, msg_success=_("Share settings updated") )