From e7454e558d163732a98e529fb5010e195225bf14 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Thu, 10 Feb 2022 14:01:59 +0100 Subject: [PATCH 01/26] # 110 Biotope codelists * adds codelist 654 to settings * adds codelist to update_codelist command --- codelist/management/commands/update_codelist.py | 3 ++- codelist/settings.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/codelist/management/commands/update_codelist.py b/codelist/management/commands/update_codelist.py index 85c9032..3fd4c6c 100644 --- a/codelist/management/commands/update_codelist.py +++ b/codelist/management/commands/update_codelist.py @@ -14,7 +14,7 @@ from codelist.settings import CODELIST_INTERVENTION_HANDLER_ID, CODELIST_CONSERV CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, \ CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, \ CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \ - CODELIST_COMPENSATION_ACTION_DETAIL_ID + CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_AFTER_STATE_BIOTOPES__ID from konova.management.commands.setup import BaseKonovaCommand from konova.settings import PROXIES @@ -34,6 +34,7 @@ class Command(BaseKonovaCommand): CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, + CODELIST_AFTER_STATE_BIOTOPES__ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, diff --git a/codelist/settings.py b/codelist/settings.py index 598a3a6..53cef73 100644 --- a/codelist/settings.py +++ b/codelist/settings.py @@ -13,7 +13,8 @@ CODELIST_BASE_URL = "https://codelisten.naturschutz.rlp.de/repository/referenzli CODELIST_INTERVENTION_HANDLER_ID = 903 # CLMassnahmeträger CODELIST_CONSERVATION_OFFICE_ID = 907 # CLNaturschutzbehörden CODELIST_REGISTRATION_OFFICE_ID = 1053 # CLZulassungsbehörden -CODELIST_BIOTOPES_ID = 974 # CL_EIV_Biotoptypen +CODELIST_BIOTOPES_ID = 654 # CL_Biotoptypen +CODELIST_AFTER_STATE_BIOTOPES__ID = 974 # CL-KSP_ZielBiotoptypen CODELIST_BIOTOPES_EXTRA_CODES_ID = 975 # CLZusatzbezeichnung CODELIST_LAW_ID = 1048 # CLVerfahrensrecht CODELIST_PROCESS_TYPE_ID = 44382 # CLVerfahrenstyp -- 2.38.5 From 2b66189590e32a9faeb113446c4883d4b64333ed Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Thu, 10 Feb 2022 14:45:00 +0100 Subject: [PATCH 02/26] #112 WIP: Restructure CompensationAction * changes action_type from ForeignKey into M2M * adds migration * changes form widget * WIP: changes rendering on detail view of compensation * TEST NOT CHECKED YET! --- compensation/forms/modalForms.py | 8 ++-- compensation/managers.py | 11 ------ .../migrations/0004_auto_20220210_1402.py | 39 +++++++++++++++++++ compensation/models/action.py | 10 +---- compensation/models/compensation.py | 2 +- .../detail/compensation/includes/actions.html | 6 ++- .../detail/eco_account/includes/actions.html | 6 ++- .../ema/detail/includes/actions.html | 6 ++- 8 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 compensation/migrations/0004_auto_20220210_1402.py diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py index 804ebad..90b0b64 100644 --- a/compensation/forms/modalForms.py +++ b/compensation/forms/modalForms.py @@ -405,7 +405,7 @@ class NewActionModalForm(BaseModalForm): """ from compensation.models import UnitChoices - action_type = forms.ModelChoiceField( + action_type = forms.ModelMultipleChoiceField( label=_("Action Type"), label_suffix="", required=True, @@ -415,7 +415,7 @@ class NewActionModalForm(BaseModalForm): is_leaf=True, code_lists__in=[CODELIST_COMPENSATION_ACTION_ID], ), - widget=autocomplete.ModelSelect2( + widget=autocomplete.ModelSelect2Multiple( url="codes-compensation-action-autocomplete", attrs={ "data-placeholder": _("Action"), @@ -496,7 +496,7 @@ class EditCompensationActionModalForm(NewActionModalForm): self.action = kwargs.pop("action", None) super().__init__(*args, **kwargs) form_data = { - "action_type": self.action.action_type, + "action_type": self.action.action_type.all(), "action_type_details": self.action.action_type_details.all(), "amount": self.action.amount, "unit": self.action.unit, @@ -506,7 +506,7 @@ class EditCompensationActionModalForm(NewActionModalForm): def save(self): action = self.action - action.action_type = self.cleaned_data.get("action_type", None) + action.action_type.set(self.cleaned_data.get("action_type", [])) action.action_type_details.set(self.cleaned_data.get("action_type_details", [])) action.amount = self.cleaned_data.get("amount", None) action.unit = self.cleaned_data.get("unit", None) diff --git a/compensation/managers.py b/compensation/managers.py index c97cd51..b496eb1 100644 --- a/compensation/managers.py +++ b/compensation/managers.py @@ -8,17 +8,6 @@ Created on: 14.10.21 from django.db import models -class CompensationActionManager(models.Manager): - """ Holds default db fetch setting for this model type - - """ - def get_queryset(self): - return super().get_queryset().select_related( - "action_type", - "action_type__parent" - ) - - class CompensationStateManager(models.Manager): """ Holds default db fetch setting for this model type diff --git a/compensation/migrations/0004_auto_20220210_1402.py b/compensation/migrations/0004_auto_20220210_1402.py new file mode 100644 index 0000000..44d43cb --- /dev/null +++ b/compensation/migrations/0004_auto_20220210_1402.py @@ -0,0 +1,39 @@ +# Generated by Django 3.1.3 on 2022-02-10 13:02 + +from django.db import migrations, models + + +def migrate_actions(apps, schema_editor): + CompensationAction = apps.get_model('compensation', 'CompensationAction') + actions = CompensationAction.objects.all() + + for action in actions: + action_type = action.action_type or [] + action.action_type_tmp.set(action_type) + action.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('codelist', '0001_initial'), + ('compensation', '0003_auto_20220202_0846'), + ] + + operations = [ + migrations.AddField( + model_name='compensationaction', + name='action_type_tmp', + field=models.ManyToManyField(blank=True, limit_choices_to={'code_lists__in': [1026], 'is_archived': False, 'is_selectable': True}, related_name='_compensationaction_action_type_+', to='codelist.KonovaCode'), + ), + migrations.RunPython(migrate_actions), + migrations.RemoveField( + model_name='compensationaction', + name='action_type', + ), + migrations.RenameField( + model_name='compensationaction', + old_name='action_type_tmp', + new_name='action_type', + ) + ] diff --git a/compensation/models/action.py b/compensation/models/action.py index a557915..e54f53d 100644 --- a/compensation/models/action.py +++ b/compensation/models/action.py @@ -10,9 +10,7 @@ from django.utils.translation import gettext_lazy as _ from codelist.models import KonovaCode from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID -from compensation.managers import CompensationActionManager from konova.models import BaseResource -from konova.utils.message_templates import COMPENSATION_ACTION_REMOVED class UnitChoices(models.TextChoices): @@ -31,10 +29,8 @@ class CompensationAction(BaseResource): """ Compensations include actions like planting trees, refreshing rivers and so on. """ - action_type = models.ForeignKey( + action_type = models.ManyToManyField( KonovaCode, - on_delete=models.SET_NULL, - null=True, blank=True, limit_choices_to={ "code_lists__in": [CODELIST_COMPENSATION_ACTION_ID], @@ -57,10 +53,8 @@ class CompensationAction(BaseResource): unit = models.CharField(max_length=100, null=True, blank=True, choices=UnitChoices.choices) comment = models.TextField(blank=True, null=True, help_text="Additional comment") - objects = CompensationActionManager() - def __str__(self): - return f"{self.action_type} | {self.amount} {self.unit}" + return f"{self.action_type.all()} | {self.amount} {self.unit}" @property def unit_humanize(self): diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py index 4dd2b4c..7a10aa0 100644 --- a/compensation/models/compensation.py +++ b/compensation/models/compensation.py @@ -104,12 +104,12 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin): with transaction.atomic(): user_action = UserActionLogEntry.get_created_action(user) comp_action = CompensationAction.objects.create( - action_type=form_data["action_type"], amount=form_data["amount"], unit=form_data["unit"], comment=form_data["comment"], created=user_action, ) + comp_action.action_type.set(form_data.get("action_type", [])) comp_action_details = form_data["action_type_details"] comp_action.action_type_details.set(comp_action_details) self.actions.add(comp_action) diff --git a/compensation/templates/compensation/detail/compensation/includes/actions.html b/compensation/templates/compensation/detail/compensation/includes/actions.html index 33037ec..18e9304 100644 --- a/compensation/templates/compensation/detail/compensation/includes/actions.html +++ b/compensation/templates/compensation/detail/compensation/includes/actions.html @@ -47,7 +47,11 @@ {% for action in actions %} - {{ action.action_type }} + {% if action.action_type_details.count > 0 %}
{% for detail in action.action_type_details.all %} diff --git a/compensation/templates/compensation/detail/eco_account/includes/actions.html b/compensation/templates/compensation/detail/eco_account/includes/actions.html index add698e..092c5e8 100644 --- a/compensation/templates/compensation/detail/eco_account/includes/actions.html +++ b/compensation/templates/compensation/detail/eco_account/includes/actions.html @@ -46,7 +46,11 @@ {% for action in actions %} - {{ action.action_type }} + {% if action.action_type_details.count > 0 %}
{% for detail in action.action_type_details.all %} diff --git a/ema/templates/ema/detail/includes/actions.html b/ema/templates/ema/detail/includes/actions.html index 02772b3..9d91caf 100644 --- a/ema/templates/ema/detail/includes/actions.html +++ b/ema/templates/ema/detail/includes/actions.html @@ -44,7 +44,11 @@ {% for action in obj.actions.all %} - {{ action.action_type }} + {% if action.action_type_details.count > 0 %}
{% for detail in action.action_type_details.all %} -- 2.38.5 From afb78fa670303834637e0769620a1e4f369929f3 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Fri, 11 Feb 2022 08:43:21 +0100 Subject: [PATCH 03/26] #112 Migration enhance * fixes bug in migration * adds check to migration which raises error before data loss might happen --- compensation/migrations/0004_auto_20220210_1402.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compensation/migrations/0004_auto_20220210_1402.py b/compensation/migrations/0004_auto_20220210_1402.py index 44d43cb..7cc8126 100644 --- a/compensation/migrations/0004_auto_20220210_1402.py +++ b/compensation/migrations/0004_auto_20220210_1402.py @@ -9,9 +9,12 @@ def migrate_actions(apps, schema_editor): for action in actions: action_type = action.action_type or [] - action.action_type_tmp.set(action_type) + action.action_type_tmp.set([action_type]) action.save() + if not action.action_type_tmp.count() > 0: + raise ValueError("Migration of actions did not work! Stoped before data loss!") + class Migration(migrations.Migration): -- 2.38.5 From 62e452c62567064f047e81940487e80131567094 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Fri, 11 Feb 2022 14:13:42 +0100 Subject: [PATCH 04/26] WIP: CompensationAction using jstree --- compensation/forms/modalForms.py | 20 ++++++--- .../detail/compensation/view.html | 5 +++ .../compensation/detail/eco_account/view.html | 5 +++ ema/templates/ema/detail/view.html | 5 +++ intervention/inputs.py | 15 +++++++ .../widgets/checkbox-tree-select-base.html | 45 +++++++++++++++++++ .../widgets/checkbox-tree-select-content.html | 9 ++++ konova/urls.py | 4 +- konova/views.py | 24 ++++++++++ templates/form/scripts/jstree-scripts.html | 2 + 10 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 konova/templates/konova/widgets/checkbox-tree-select-base.html create mode 100644 konova/templates/konova/widgets/checkbox-tree-select-content.html create mode 100644 templates/form/scripts/jstree-scripts.html diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py index 90b0b64..20ff5dd 100644 --- a/compensation/forms/modalForms.py +++ b/compensation/forms/modalForms.py @@ -11,12 +11,14 @@ from django import forms from django.contrib import messages from django.http import HttpRequest, HttpResponseRedirect from django.shortcuts import render +from django.urls import reverse from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _ from codelist.models import KonovaCode from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \ CODELIST_COMPENSATION_ACTION_DETAIL_ID from compensation.models import CompensationDocument, EcoAccountDocument +from intervention.inputs import TreeSelectMultiple from konova.contexts import BaseContext from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm from konova.models import DeadlineType @@ -411,15 +413,11 @@ class NewActionModalForm(BaseModalForm): required=True, help_text=_("Select the action type"), queryset=KonovaCode.objects.filter( - is_archived=False, - is_leaf=True, code_lists__in=[CODELIST_COMPENSATION_ACTION_ID], + is_archived=False, ), - widget=autocomplete.ModelSelect2Multiple( - url="codes-compensation-action-autocomplete", - attrs={ - "data-placeholder": _("Action"), - } + widget=TreeSelectMultiple( + url=None, ), ) action_type_details = forms.ModelMultipleChoiceField( @@ -482,6 +480,14 @@ class NewActionModalForm(BaseModalForm): super().__init__(*args, **kwargs) self.form_title = _("New action") self.form_caption = _("Insert data for the new action") + url = reverse("codes-action-children") + self.fields["action_type"].widget.attrs = { + "url": url, + } + + def is_valid(self): + super_valid = super().is_valid() + return super_valid def save(self): action = self.instance.add_action(self) diff --git a/compensation/templates/compensation/detail/compensation/view.html b/compensation/templates/compensation/detail/compensation/view.html index 3f843c0..17f79bc 100644 --- a/compensation/templates/compensation/detail/compensation/view.html +++ b/compensation/templates/compensation/detail/compensation/view.html @@ -2,6 +2,11 @@ {% load i18n l10n static fontawesome_5 humanize ksp_filters %} {% block head %} + {% comment %} + Needed for custom Checkbox Tree Select Widget + {% endcomment %} + {% include 'form/scripts/jstree-scripts.html' %} + {% comment %} dal documentation (django-autocomplete-light) states using form.media for adding needed scripts. This does not work properly with modal forms, as the scripts are not loaded properly inside the modal. diff --git a/compensation/templates/compensation/detail/eco_account/view.html b/compensation/templates/compensation/detail/eco_account/view.html index f276f93..132e0b6 100644 --- a/compensation/templates/compensation/detail/eco_account/view.html +++ b/compensation/templates/compensation/detail/eco_account/view.html @@ -2,6 +2,11 @@ {% load i18n l10n static fontawesome_5 humanize %} {% block head %} + {% comment %} + Needed for custom Checkbox Tree Select Widget + {% endcomment %} + {% include 'form/scripts/jstree-scripts.html' %} + {% comment %} dal documentation (django-autocomplete-light) states using form.media for adding needed scripts. This does not work properly with modal forms, as the scripts are not loaded properly inside the modal. diff --git a/ema/templates/ema/detail/view.html b/ema/templates/ema/detail/view.html index 32ddd66..cdab570 100644 --- a/ema/templates/ema/detail/view.html +++ b/ema/templates/ema/detail/view.html @@ -2,6 +2,11 @@ {% load i18n l10n static fontawesome_5 humanize %} {% block head %} + {% comment %} + Needed for custom Checkbox Tree Select Widget + {% endcomment %} + {% include 'form/scripts/jstree-scripts.html' %} + {% comment %} dal documentation (django-autocomplete-light) states using form.media for adding needed scripts. This does not work properly with modal forms, as the scripts are not loaded properly inside the modal. diff --git a/intervention/inputs.py b/intervention/inputs.py index 2e84edc..9cf8e5a 100644 --- a/intervention/inputs.py +++ b/intervention/inputs.py @@ -1,4 +1,5 @@ from django import forms +from django.urls import reverse class DummyFilterInput(forms.HiddenInput): @@ -30,3 +31,17 @@ class GenerateInput(forms.TextInput): """ template_name = "konova/widgets/generate-content-input.html" + + +class TreeSelectMultiple(forms.SelectMultiple): + """ Provides multiple selection of parent-child data + + """ + template_name = "konova/widgets/checkbox-tree-select-base.html" + url = None + + def __init__(self, *args, **kwargs): + self.url = kwargs.pop("url", None) + if self.url: + self.url = reverse(self.url) + super().__init__(*args, **kwargs) diff --git a/konova/templates/konova/widgets/checkbox-tree-select-base.html b/konova/templates/konova/widgets/checkbox-tree-select-base.html new file mode 100644 index 0000000..793ab83 --- /dev/null +++ b/konova/templates/konova/widgets/checkbox-tree-select-base.html @@ -0,0 +1,45 @@ + +
+
+ + + + \ No newline at end of file diff --git a/konova/templates/konova/widgets/checkbox-tree-select-content.html b/konova/templates/konova/widgets/checkbox-tree-select-content.html new file mode 100644 index 0000000..13b3a34 --- /dev/null +++ b/konova/templates/konova/widgets/checkbox-tree-select-content.html @@ -0,0 +1,9 @@ +{% load l10n %} + +
    + {% for code in codes %} +
  • + {{code.long_name}} +
  • + {% endfor %} +
\ No newline at end of file diff --git a/konova/urls.py b/konova/urls.py index 68256e7..734be3c 100644 --- a/konova/urls.py +++ b/konova/urls.py @@ -23,7 +23,7 @@ from konova.autocompletes import EcoAccountAutocomplete, \ ShareUserAutocomplete, BiotopeExtraCodeAutocomplete, CompensationActionDetailCodeAutocomplete 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 +from konova.views import logout_view, home_view, get_konova_code_action_children sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY) urlpatterns = [ @@ -40,6 +40,8 @@ urlpatterns = [ path('analysis/', include("analysis.urls")), path('api/', include("api.urls")), + path("codes/comp/action/children", get_konova_code_action_children, name="codes-action-children"), + # Autocomplete paths for all apps path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"), path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="interventions-autocomplete"), diff --git a/konova/views.py b/konova/views.py index 2a4c8ad..2eac8b2 100644 --- a/konova/views.py +++ b/konova/views.py @@ -9,9 +9,12 @@ from django.contrib.auth import logout from django.contrib.auth.decorators import login_required from django.http import HttpRequest, FileResponse from django.shortcuts import redirect, render, get_object_or_404 +from django.template.loader import render_to_string from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from codelist.models import KonovaCode +from codelist.settings import CODELIST_COMPENSATION_ACTION_ID from compensation.models import Compensation, EcoAccount from intervention.models import Intervention from konova.contexts import BaseContext @@ -124,3 +127,24 @@ def get_500_view(request: HttpRequest): """ context = BaseContext.context return render(request, "500.html", context, status=500) + + +@login_required +def get_konova_code_action_children(request: HttpRequest): + template = "konova/widgets/checkbox-tree-select-content.html" + _id = request.GET.get("id", None) + if _id == "#": + # Return all! + codes = KonovaCode.objects.filter( + code_lists__in=[CODELIST_COMPENSATION_ACTION_ID], + parent=None, + ) + else: + codes = KonovaCode.objects.filter( + code_lists__in=[CODELIST_COMPENSATION_ACTION_ID], + parent__id=_id, + ) + context = { + "codes": codes + } + return render(request, template, context) \ No newline at end of file diff --git a/templates/form/scripts/jstree-scripts.html b/templates/form/scripts/jstree-scripts.html new file mode 100644 index 0000000..c936760 --- /dev/null +++ b/templates/form/scripts/jstree-scripts.html @@ -0,0 +1,2 @@ + + \ No newline at end of file -- 2.38.5 From d04d02380f6ec763ff24fe61babd2aa2e6c9344b Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Fri, 11 Feb 2022 16:21:44 +0100 Subject: [PATCH 05/26] WIP: #112 Restructure CompensationAction --- compensation/forms/modalForms.py | 2 +- .../widgets/checkbox-tree-select-base.html | 26 ++++--------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py index 20ff5dd..6dc0c98 100644 --- a/compensation/forms/modalForms.py +++ b/compensation/forms/modalForms.py @@ -22,7 +22,7 @@ from intervention.inputs import TreeSelectMultiple from konova.contexts import BaseContext from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm from konova.models import DeadlineType -from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \ +from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, \ ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED diff --git a/konova/templates/konova/widgets/checkbox-tree-select-base.html b/konova/templates/konova/widgets/checkbox-tree-select-base.html index 793ab83..6a10ff0 100644 --- a/konova/templates/konova/widgets/checkbox-tree-select-base.html +++ b/konova/templates/konova/widgets/checkbox-tree-select-base.html @@ -1,18 +1,14 @@ -
-
- - +
+ \ No newline at end of file -- 2.38.5 From 95856c9ee94cdb999b48d245f5cea0026081d88e Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Tue, 15 Feb 2022 10:48:01 +0100 Subject: [PATCH 06/26] #112 CompensationAction Tree * implements generic HTML based and Django compatible TreeView * enhances listing of CompensationActions on DetailView --- codelist/models.py | 20 ++ compensation/forms/modalForms.py | 34 ++- .../detail/compensation/includes/actions.html | 20 +- .../detail/compensation/view.html | 4 - .../detail/eco_account/includes/actions.html | 20 +- .../compensation/detail/eco_account/view.html | 4 - .../ema/detail/includes/actions.html | 20 +- ema/templates/ema/detail/view.html | 5 - intervention/inputs.py | 49 +++- .../widgets/checkbox-tree-select-base.html | 29 --- .../widgets/checkbox-tree-select-content.html | 9 - .../konova/widgets/checkbox-tree-select.html | 23 ++ konova/urls.py | 4 +- konova/views.py | 21 -- locale/de/LC_MESSAGES/django.mo | Bin 37463 -> 37523 bytes locale/de/LC_MESSAGES/django.po | 235 +++++++++--------- templates/form/scripts/jstree-scripts.html | 2 - 17 files changed, 246 insertions(+), 253 deletions(-) delete mode 100644 konova/templates/konova/widgets/checkbox-tree-select-base.html delete mode 100644 konova/templates/konova/widgets/checkbox-tree-select-content.html create mode 100644 konova/templates/konova/widgets/checkbox-tree-select.html delete mode 100644 templates/form/scripts/jstree-scripts.html diff --git a/codelist/models.py b/codelist/models.py index d5be5b1..de7a210 100644 --- a/codelist/models.py +++ b/codelist/models.py @@ -65,6 +65,26 @@ class KonovaCode(models.Model): ret_val += ", " + self.parent.long_name return ret_val + def add_children(self): + """ Adds all children (resurcively until leaf) as .children to the KonovaCode + + Returns: + code (KonovaCode): The manipulated KonovaCode instance + """ + if self.is_leaf: + return None + + children = KonovaCode.objects.filter( + code_lists__in=self.code_lists.all(), + parent=self + ).order_by( + "long_name" + ) + self.children = children + for child in children: + child.add_children() + return self + class KonovaCodeList(models.Model): """ diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py index 6dc0c98..a8d38aa 100644 --- a/compensation/forms/modalForms.py +++ b/compensation/forms/modalForms.py @@ -11,14 +11,13 @@ from django import forms from django.contrib import messages from django.http import HttpRequest, HttpResponseRedirect from django.shortcuts import render -from django.urls import reverse from django.utils.translation import pgettext_lazy as _con, gettext_lazy as _ from codelist.models import KonovaCode from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \ CODELIST_COMPENSATION_ACTION_DETAIL_ID from compensation.models import CompensationDocument, EcoAccountDocument -from intervention.inputs import TreeSelectMultiple +from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple from konova.contexts import BaseContext from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm from konova.models import DeadlineType @@ -407,18 +406,13 @@ class NewActionModalForm(BaseModalForm): """ from compensation.models import UnitChoices - action_type = forms.ModelMultipleChoiceField( + action_type = forms.MultipleChoiceField( label=_("Action Type"), label_suffix="", required=True, help_text=_("Select the action type"), - queryset=KonovaCode.objects.filter( - code_lists__in=[CODELIST_COMPENSATION_ACTION_ID], - is_archived=False, - ), - widget=TreeSelectMultiple( - url=None, - ), + choices=[], + widget=CompensationActionTreeCheckboxSelectMultiple(), ) action_type_details = forms.ModelMultipleChoiceField( label=_("Action Type detail"), @@ -480,14 +474,16 @@ class NewActionModalForm(BaseModalForm): super().__init__(*args, **kwargs) self.form_title = _("New action") self.form_caption = _("Insert data for the new action") - url = reverse("codes-action-children") - self.fields["action_type"].widget.attrs = { - "url": url, - } - - def is_valid(self): - super_valid = super().is_valid() - return super_valid + choices =KonovaCode.objects.filter( + code_lists__in=[CODELIST_COMPENSATION_ACTION_ID], + is_archived=False, + is_leaf=True, + ).values_list("id", flat=True) + choices = [ + (choice, choice) + for choice in choices + ] + self.fields["action_type"].choices = choices def save(self): action = self.instance.add_action(self) @@ -502,7 +498,7 @@ class EditCompensationActionModalForm(NewActionModalForm): self.action = kwargs.pop("action", None) super().__init__(*args, **kwargs) form_data = { - "action_type": self.action.action_type.all(), + "action_type": list(self.action.action_type.values_list("id", flat=True)), "action_type_details": self.action.action_type_details.all(), "amount": self.action.amount, "unit": self.action.unit, diff --git a/compensation/templates/compensation/detail/compensation/includes/actions.html b/compensation/templates/compensation/detail/compensation/includes/actions.html index 18e9304..87f4914 100644 --- a/compensation/templates/compensation/detail/compensation/includes/actions.html +++ b/compensation/templates/compensation/detail/compensation/includes/actions.html @@ -47,17 +47,15 @@ {% for action in actions %} -
    - {% for type in action.action_type.all %} -
  • {{type}}
  • - {% endfor %} -
- {% if action.action_type_details.count > 0 %} -
- {% for detail in action.action_type_details.all %} - {{detail.long_name}} - {% endfor %} - {% endif %} + {% for type in action.action_type.all %} +
{{type.parent.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.long_name}}
+
+ {% endfor %} + {% for detail in action.action_type_details.all %} + {{detail.long_name}} + {% empty %} + {% trans 'No action type details' %} + {% endfor %} {{ action.amount|floatformat:2|intcomma }} {{ action.unit_humanize }} diff --git a/compensation/templates/compensation/detail/compensation/view.html b/compensation/templates/compensation/detail/compensation/view.html index 17f79bc..c5ff41d 100644 --- a/compensation/templates/compensation/detail/compensation/view.html +++ b/compensation/templates/compensation/detail/compensation/view.html @@ -2,10 +2,6 @@ {% load i18n l10n static fontawesome_5 humanize ksp_filters %} {% block head %} - {% comment %} - Needed for custom Checkbox Tree Select Widget - {% endcomment %} - {% include 'form/scripts/jstree-scripts.html' %} {% comment %} dal documentation (django-autocomplete-light) states using form.media for adding needed scripts. diff --git a/compensation/templates/compensation/detail/eco_account/includes/actions.html b/compensation/templates/compensation/detail/eco_account/includes/actions.html index 092c5e8..8ae7c8f 100644 --- a/compensation/templates/compensation/detail/eco_account/includes/actions.html +++ b/compensation/templates/compensation/detail/eco_account/includes/actions.html @@ -46,17 +46,15 @@ {% for action in actions %} -
    - {% for type in action.action_type.all%} -
  • {{type.long_name}}
  • - {% endfor %} -
- {% if action.action_type_details.count > 0 %} -
- {% for detail in action.action_type_details.all %} - {{detail.long_name}} - {% endfor %} - {% endif %} + {% for type in action.action_type.all %} +
{{type.parent.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.long_name}}
+
+ {% endfor %} + {% for detail in action.action_type_details.all %} + {{detail.long_name}} + {% empty %} + {% trans 'No action type details' %} + {% endfor %} {{ action.amount|floatformat:2|intcomma }} {{ action.unit_humanize }} diff --git a/compensation/templates/compensation/detail/eco_account/view.html b/compensation/templates/compensation/detail/eco_account/view.html index 132e0b6..288b971 100644 --- a/compensation/templates/compensation/detail/eco_account/view.html +++ b/compensation/templates/compensation/detail/eco_account/view.html @@ -2,10 +2,6 @@ {% load i18n l10n static fontawesome_5 humanize %} {% block head %} - {% comment %} - Needed for custom Checkbox Tree Select Widget - {% endcomment %} - {% include 'form/scripts/jstree-scripts.html' %} {% comment %} dal documentation (django-autocomplete-light) states using form.media for adding needed scripts. diff --git a/ema/templates/ema/detail/includes/actions.html b/ema/templates/ema/detail/includes/actions.html index 9d91caf..0c352c1 100644 --- a/ema/templates/ema/detail/includes/actions.html +++ b/ema/templates/ema/detail/includes/actions.html @@ -44,17 +44,15 @@ {% for action in obj.actions.all %} -
    - {% for type in action.action_type.all%} -
  • {{type}}
  • - {% endfor %} -
- {% if action.action_type_details.count > 0 %} -
- {% for detail in action.action_type_details.all %} - {{detail.long_name}} - {% endfor %} - {% endif %} + {% for type in action.action_type.all %} +
{{type.parent.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.long_name}}
+
+ {% endfor %} + {% for detail in action.action_type_details.all %} + {{detail.long_name}} + {% empty %} + {% trans 'No action type details' %} + {% endfor %} {{ action.amount|floatformat:2|intcomma }} {{ action.unit_humanize }} diff --git a/ema/templates/ema/detail/view.html b/ema/templates/ema/detail/view.html index cdab570..32ddd66 100644 --- a/ema/templates/ema/detail/view.html +++ b/ema/templates/ema/detail/view.html @@ -2,11 +2,6 @@ {% load i18n l10n static fontawesome_5 humanize %} {% block head %} - {% comment %} - Needed for custom Checkbox Tree Select Widget - {% endcomment %} - {% include 'form/scripts/jstree-scripts.html' %} - {% comment %} dal documentation (django-autocomplete-light) states using form.media for adding needed scripts. This does not work properly with modal forms, as the scripts are not loaded properly inside the modal. diff --git a/intervention/inputs.py b/intervention/inputs.py index 9cf8e5a..34fe043 100644 --- a/intervention/inputs.py +++ b/intervention/inputs.py @@ -1,5 +1,6 @@ from django import forms -from django.urls import reverse +from codelist.models import KonovaCode +from codelist.settings import CODELIST_COMPENSATION_ACTION_ID class DummyFilterInput(forms.HiddenInput): @@ -33,15 +34,49 @@ class GenerateInput(forms.TextInput): template_name = "konova/widgets/generate-content-input.html" -class TreeSelectMultiple(forms.SelectMultiple): +class TreeCheckboxSelectMultiple(forms.CheckboxSelectMultiple): """ Provides multiple selection of parent-child data """ - template_name = "konova/widgets/checkbox-tree-select-base.html" - url = None + template_name = "konova/widgets/checkbox-tree-select.html" + + class meta: + abstract = True + + +class KonovaCodeTreeCheckboxSelectMultiple(TreeCheckboxSelectMultiple): + """ Provides multiple selection of KonovaCodes + + """ + filter = None + + def __init__(self, *args, **kwargs): + self.code_list = kwargs.pop("code_list", None) + self.filter = kwargs.pop("filter", {}) + super().__init__(*args, **kwargs) + + def get_context(self, name, value, attrs): + context = super().get_context(name, value, attrs) + codes = KonovaCode.objects.filter( + **self.filter, + ) + codes = [ + parent_code.add_children() + for parent_code in codes + ] + context["codes"] = codes + return context + + +class CompensationActionTreeCheckboxSelectMultiple(KonovaCodeTreeCheckboxSelectMultiple): + """ Provides multiple selection of CompensationActions + + """ + filter = None def __init__(self, *args, **kwargs): - self.url = kwargs.pop("url", None) - if self.url: - self.url = reverse(self.url) super().__init__(*args, **kwargs) + self.filter = { + "code_lists__in": [CODELIST_COMPENSATION_ACTION_ID], + "parent": None, + } \ No newline at end of file diff --git a/konova/templates/konova/widgets/checkbox-tree-select-base.html b/konova/templates/konova/widgets/checkbox-tree-select-base.html deleted file mode 100644 index 6a10ff0..0000000 --- a/konova/templates/konova/widgets/checkbox-tree-select-base.html +++ /dev/null @@ -1,29 +0,0 @@ - -
- - - \ No newline at end of file diff --git a/konova/templates/konova/widgets/checkbox-tree-select-content.html b/konova/templates/konova/widgets/checkbox-tree-select-content.html deleted file mode 100644 index 13b3a34..0000000 --- a/konova/templates/konova/widgets/checkbox-tree-select-content.html +++ /dev/null @@ -1,9 +0,0 @@ -{% load l10n %} - -
    - {% for code in codes %} -
  • - {{code.long_name}} -
  • - {% endfor %} -
\ No newline at end of file diff --git a/konova/templates/konova/widgets/checkbox-tree-select.html b/konova/templates/konova/widgets/checkbox-tree-select.html new file mode 100644 index 0000000..5b40d3f --- /dev/null +++ b/konova/templates/konova/widgets/checkbox-tree-select.html @@ -0,0 +1,23 @@ +{% load l10n fontawesome_5 %} + +
+ {% for code in codes %} +
+ + {% if not code.is_leaf %} +
+ {% with code.children as codes %} + {% include 'konova/widgets/checkbox-tree-select.html' %} + {% endwith %} +
+ {% endif %} +
+ {% endfor %} +
\ No newline at end of file diff --git a/konova/urls.py b/konova/urls.py index 734be3c..68256e7 100644 --- a/konova/urls.py +++ b/konova/urls.py @@ -23,7 +23,7 @@ from konova.autocompletes import EcoAccountAutocomplete, \ ShareUserAutocomplete, BiotopeExtraCodeAutocomplete, CompensationActionDetailCodeAutocomplete 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_konova_code_action_children +from konova.views import logout_view, home_view sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY) urlpatterns = [ @@ -40,8 +40,6 @@ urlpatterns = [ path('analysis/', include("analysis.urls")), path('api/', include("api.urls")), - path("codes/comp/action/children", get_konova_code_action_children, name="codes-action-children"), - # Autocomplete paths for all apps path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"), path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="interventions-autocomplete"), diff --git a/konova/views.py b/konova/views.py index 2eac8b2..f3c53f0 100644 --- a/konova/views.py +++ b/konova/views.py @@ -127,24 +127,3 @@ def get_500_view(request: HttpRequest): """ context = BaseContext.context return render(request, "500.html", context, status=500) - - -@login_required -def get_konova_code_action_children(request: HttpRequest): - template = "konova/widgets/checkbox-tree-select-content.html" - _id = request.GET.get("id", None) - if _id == "#": - # Return all! - codes = KonovaCode.objects.filter( - code_lists__in=[CODELIST_COMPENSATION_ACTION_ID], - parent=None, - ) - else: - codes = KonovaCode.objects.filter( - code_lists__in=[CODELIST_COMPENSATION_ACTION_ID], - parent__id=_id, - ) - context = { - "codes": codes - } - return render(request, template, context) \ No newline at end of file diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index 99058358253ae1eb76a6a5f21466ae886d82ccaf..e9a94ccb374f1fcb49ae6b60cf0df3131a61819d 100644 GIT binary patch delta 10418 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 HR_Ff!*^K44 delta 10343 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, YEAR. # -#: compensation/filters.py:122 compensation/forms/modalForms.py:35 -#: compensation/forms/modalForms.py:46 compensation/forms/modalForms.py:62 -#: compensation/forms/modalForms.py:355 compensation/forms/modalForms.py:471 +#: compensation/filters.py:122 compensation/forms/modalForms.py:37 +#: compensation/forms/modalForms.py:48 compensation/forms/modalForms.py:64 +#: compensation/forms/modalForms.py:357 compensation/forms/modalForms.py:469 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156 #: intervention/forms/forms.py:168 intervention/forms/modalForms.py:127 #: intervention/forms/modalForms.py:140 intervention/forms/modalForms.py:153 @@ -26,7 +26,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-10 13:31+0100\n" +"POT-Creation-Date: 2022-02-15 10:08+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -45,7 +45,7 @@ msgid "To" msgstr "Bis" #: analysis/forms.py:47 compensation/forms/forms.py:77 -#: compensation/templates/compensation/detail/eco_account/view.html:58 +#: compensation/templates/compensation/detail/eco_account/view.html:59 #: compensation/templates/compensation/report/eco_account/report.html:16 #: compensation/utils/quality.py:100 ema/templates/ema/detail/view.html:49 #: ema/templates/ema/report/report.html:16 ema/utils/quality.py:26 @@ -95,7 +95,7 @@ msgstr "" #: analysis/templates/analysis/reports/includes/eco_account/amount.html:3 #: analysis/templates/analysis/reports/includes/intervention/amount.html:3 #: analysis/templates/analysis/reports/includes/old_data/amount.html:3 -#: compensation/forms/modalForms.py:455 +#: compensation/forms/modalForms.py:453 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:34 #: intervention/templates/intervention/detail/includes/deductions.html:31 msgid "Amount" @@ -137,7 +137,7 @@ msgstr "Zuständigkeitsbereich" #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:8 #: analysis/templates/analysis/reports/includes/intervention/laws.html:17 #: compensation/tables.py:40 -#: compensation/templates/compensation/detail/compensation/view.html:63 +#: compensation/templates/compensation/detail/compensation/view.html:64 #: intervention/tables.py:39 #: intervention/templates/intervention/detail/view.html:68 #: user/models/user_action.py:20 @@ -153,9 +153,9 @@ msgstr "Geprüft" #: analysis/templates/analysis/reports/includes/intervention/laws.html:20 #: analysis/templates/analysis/reports/includes/old_data/amount.html:18 #: compensation/tables.py:46 compensation/tables.py:222 -#: compensation/templates/compensation/detail/compensation/view.html:77 +#: compensation/templates/compensation/detail/compensation/view.html:78 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31 -#: compensation/templates/compensation/detail/eco_account/view.html:44 +#: compensation/templates/compensation/detail/eco_account/view.html:45 #: ema/tables.py:44 ema/templates/ema/detail/view.html:35 #: intervention/tables.py:45 #: intervention/templates/intervention/detail/view.html:82 @@ -213,7 +213,7 @@ msgstr "Abbuchungen" #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:9 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:11 -#: compensation/forms/modalForms.py:193 +#: compensation/forms/modalForms.py:195 #: compensation/templates/compensation/detail/compensation/includes/states-after.html:36 #: compensation/templates/compensation/detail/compensation/includes/states-before.html:36 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36 @@ -239,14 +239,14 @@ msgstr "Kompensationsart" #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:15 #: analysis/templates/analysis/reports/includes/old_data/amount.html:29 -#: compensation/templates/compensation/detail/compensation/view.html:19 +#: compensation/templates/compensation/detail/compensation/view.html:20 #: konova/templates/konova/includes/quickstart/compensations.html:4 #: templates/navbars/navbar.html:28 msgid "Compensation" msgstr "Kompensation" #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:21 -#: compensation/forms/modalForms.py:75 +#: compensation/forms/modalForms.py:77 msgid "Payment" msgstr "Zahlung" @@ -293,7 +293,7 @@ msgstr "Eingriff" #: analysis/templates/analysis/reports/includes/old_data/amount.html:34 #: compensation/tables.py:266 -#: compensation/templates/compensation/detail/eco_account/view.html:19 +#: compensation/templates/compensation/detail/eco_account/view.html:20 #: intervention/forms/modalForms.py:322 intervention/forms/modalForms.py:329 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4 #: templates/navbars/navbar.html:34 @@ -327,9 +327,9 @@ msgstr "Automatisch generiert" #: compensation/forms/forms.py:44 compensation/tables.py:30 #: compensation/tables.py:202 #: compensation/templates/compensation/detail/compensation/includes/documents.html:28 -#: compensation/templates/compensation/detail/compensation/view.html:31 +#: compensation/templates/compensation/detail/compensation/view.html:32 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:28 -#: compensation/templates/compensation/detail/eco_account/view.html:31 +#: compensation/templates/compensation/detail/eco_account/view.html:32 #: compensation/templates/compensation/report/compensation/report.html:12 #: compensation/templates/compensation/report/eco_account/report.html:12 #: ema/tables.py:34 ema/templates/ema/detail/includes/documents.html:28 @@ -352,8 +352,8 @@ msgstr "Aussagekräftiger Titel" msgid "Compensation XY; Location ABC" msgstr "Kompensation XY; Flur ABC" -#: compensation/forms/forms.py:57 compensation/forms/modalForms.py:61 -#: compensation/forms/modalForms.py:354 compensation/forms/modalForms.py:470 +#: compensation/forms/forms.py:57 compensation/forms/modalForms.py:63 +#: compensation/forms/modalForms.py:356 compensation/forms/modalForms.py:468 #: compensation/templates/compensation/detail/compensation/includes/actions.html:35 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34 #: compensation/templates/compensation/detail/compensation/includes/documents.html:34 @@ -371,13 +371,13 @@ msgstr "Kompensation XY; Flur ABC" msgid "Comment" msgstr "Kommentar" -#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:472 +#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:470 #: intervention/forms/forms.py:182 msgid "Additional comment" msgstr "Zusätzlicher Kommentar" #: compensation/forms/forms.py:93 -#: compensation/templates/compensation/detail/eco_account/view.html:62 +#: compensation/templates/compensation/detail/eco_account/view.html:63 #: compensation/templates/compensation/report/eco_account/report.html:20 #: compensation/utils/quality.py:102 ema/templates/ema/detail/view.html:53 #: ema/templates/ema/report/report.html:20 ema/utils/quality.py:28 @@ -422,7 +422,7 @@ msgid "" msgstr "Optional: Handelt es sich um eine Kohärenzsicherungsmaßnahme?" #: compensation/forms/forms.py:156 -#: compensation/templates/compensation/detail/compensation/view.html:35 +#: compensation/templates/compensation/detail/compensation/view.html:36 #: compensation/templates/compensation/report/compensation/report.html:16 msgid "compensates intervention" msgstr "kompensiert Eingriff" @@ -448,7 +448,7 @@ msgid "The amount that can be used for deductions" msgstr "Die für Abbuchungen zur Verfügung stehende Menge" #: compensation/forms/forms.py:328 -#: compensation/templates/compensation/detail/eco_account/view.html:66 +#: compensation/templates/compensation/detail/eco_account/view.html:67 #: compensation/utils/quality.py:72 msgid "Agreement date" msgstr "Vereinbarungsdatum" @@ -469,73 +469,73 @@ msgstr "Ökokonto XY; Flur ABC" msgid "Edit Eco-Account" msgstr "Ökokonto bearbeiten" -#: compensation/forms/modalForms.py:36 +#: compensation/forms/modalForms.py:38 msgid "in Euro" msgstr "in Euro" -#: compensation/forms/modalForms.py:45 +#: compensation/forms/modalForms.py:47 #: intervention/templates/intervention/detail/includes/payments.html:31 msgid "Due on" msgstr "Fällig am" -#: compensation/forms/modalForms.py:48 +#: compensation/forms/modalForms.py:50 msgid "Due on which date" msgstr "Zahlung wird an diesem Datum erwartet" -#: compensation/forms/modalForms.py:63 compensation/forms/modalForms.py:356 +#: compensation/forms/modalForms.py:65 compensation/forms/modalForms.py:358 #: intervention/forms/modalForms.py:154 konova/forms.py:395 msgid "Additional comment, maximum {} letters" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" -#: compensation/forms/modalForms.py:76 +#: compensation/forms/modalForms.py:78 msgid "Add a payment for intervention '{}'" msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen" -#: compensation/forms/modalForms.py:96 +#: compensation/forms/modalForms.py:98 msgid "If there is no date you can enter, please explain why." msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb." -#: compensation/forms/modalForms.py:157 compensation/forms/modalForms.py:169 +#: compensation/forms/modalForms.py:159 compensation/forms/modalForms.py:171 msgid "Biotope Type" msgstr "Biotoptyp" -#: compensation/forms/modalForms.py:160 +#: compensation/forms/modalForms.py:162 msgid "Select the biotope type" msgstr "Biotoptyp wählen" -#: compensation/forms/modalForms.py:174 compensation/forms/modalForms.py:186 +#: compensation/forms/modalForms.py:176 compensation/forms/modalForms.py:188 msgid "Biotope additional type" msgstr "Zusatzbezeichnung" -#: compensation/forms/modalForms.py:177 +#: compensation/forms/modalForms.py:179 msgid "Select an additional biotope type" msgstr "Zusatzbezeichnung wählen" -#: compensation/forms/modalForms.py:196 intervention/forms/modalForms.py:340 +#: compensation/forms/modalForms.py:198 intervention/forms/modalForms.py:340 msgid "in m²" msgstr "" -#: compensation/forms/modalForms.py:207 +#: compensation/forms/modalForms.py:209 msgid "New state" msgstr "Neuer Zustand" -#: compensation/forms/modalForms.py:208 +#: compensation/forms/modalForms.py:210 msgid "Insert data for the new state" msgstr "Geben Sie die Daten des neuen Zustandes ein" -#: compensation/forms/modalForms.py:215 konova/forms.py:193 +#: compensation/forms/modalForms.py:217 konova/forms.py:193 msgid "Object removed" msgstr "Objekt entfernt" -#: compensation/forms/modalForms.py:326 +#: compensation/forms/modalForms.py:328 msgid "Deadline Type" msgstr "Fristart" -#: compensation/forms/modalForms.py:329 +#: compensation/forms/modalForms.py:331 msgid "Select the deadline type" msgstr "Fristart wählen" -#: compensation/forms/modalForms.py:338 +#: compensation/forms/modalForms.py:340 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31 #: ema/templates/ema/detail/includes/deadlines.html:31 @@ -543,101 +543,75 @@ msgstr "Fristart wählen" msgid "Date" msgstr "Datum" -#: compensation/forms/modalForms.py:341 +#: compensation/forms/modalForms.py:343 msgid "Select date" msgstr "Datum wählen" -#: compensation/forms/modalForms.py:368 +#: compensation/forms/modalForms.py:370 msgid "New deadline" msgstr "Neue Frist" -#: compensation/forms/modalForms.py:369 +#: compensation/forms/modalForms.py:371 msgid "Insert data for the new deadline" msgstr "Geben Sie die Daten der neuen Frist ein" -#: compensation/forms/modalForms.py:409 +#: compensation/forms/modalForms.py:411 msgid "Action Type" msgstr "Maßnahmentyp" -#: compensation/forms/modalForms.py:412 +#: compensation/forms/modalForms.py:414 msgid "Select the action type" msgstr "Maßnahmentyp wählen" -#: compensation/forms/modalForms.py:421 -#: compensation/templates/compensation/detail/compensation/includes/actions.html:40 -#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:39 -#: compensation/templates/compensation/detail/compensation/includes/documents.html:39 -#: compensation/templates/compensation/detail/compensation/includes/states-after.html:41 -#: compensation/templates/compensation/detail/compensation/includes/states-before.html:41 -#: compensation/templates/compensation/detail/eco_account/includes/actions.html:39 -#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:38 -#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:41 -#: compensation/templates/compensation/detail/eco_account/includes/documents.html:38 -#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:41 -#: compensation/templates/compensation/detail/eco_account/includes/states-before.html:41 -#: ema/templates/ema/detail/includes/actions.html:38 -#: ema/templates/ema/detail/includes/deadlines.html:38 -#: ema/templates/ema/detail/includes/documents.html:38 -#: ema/templates/ema/detail/includes/states-after.html:40 -#: ema/templates/ema/detail/includes/states-before.html:40 -#: intervention/templates/intervention/detail/includes/compensations.html:38 -#: intervention/templates/intervention/detail/includes/deductions.html:39 -#: intervention/templates/intervention/detail/includes/documents.html:39 -#: intervention/templates/intervention/detail/includes/payments.html:39 -#: intervention/templates/intervention/detail/includes/revocation.html:43 -#: templates/log.html:10 -msgid "Action" -msgstr "Aktionen" - -#: compensation/forms/modalForms.py:426 compensation/forms/modalForms.py:438 +#: compensation/forms/modalForms.py:424 compensation/forms/modalForms.py:436 msgid "Action Type detail" msgstr "Zusatzmerkmal" -#: compensation/forms/modalForms.py:429 +#: compensation/forms/modalForms.py:427 msgid "Select the action type detail" msgstr "Zusatzmerkmal wählen" -#: compensation/forms/modalForms.py:443 +#: compensation/forms/modalForms.py:441 msgid "Unit" msgstr "Einheit" -#: compensation/forms/modalForms.py:446 +#: compensation/forms/modalForms.py:444 msgid "Select the unit" msgstr "Einheit wählen" -#: compensation/forms/modalForms.py:458 +#: compensation/forms/modalForms.py:456 msgid "Insert the amount" msgstr "Menge eingeben" -#: compensation/forms/modalForms.py:483 +#: compensation/forms/modalForms.py:481 msgid "New action" msgstr "Neue Maßnahme" -#: compensation/forms/modalForms.py:484 +#: compensation/forms/modalForms.py:482 msgid "Insert data for the new action" msgstr "Geben Sie die Daten der neuen Maßnahme ein" -#: compensation/models/action.py:22 +#: compensation/models/action.py:20 msgid "cm" msgstr "" -#: compensation/models/action.py:23 +#: compensation/models/action.py:21 msgid "m" msgstr "" -#: compensation/models/action.py:24 +#: compensation/models/action.py:22 msgid "km" msgstr "" -#: compensation/models/action.py:25 +#: compensation/models/action.py:23 msgid "m²" msgstr "" -#: compensation/models/action.py:26 +#: compensation/models/action.py:24 msgid "ha" msgstr "" -#: compensation/models/action.py:27 +#: compensation/models/action.py:25 msgid "Pieces" msgstr "Stück" @@ -685,9 +659,9 @@ msgid "Checked on {} by {}" msgstr "Am {} von {} geprüft worden" #: compensation/tables.py:160 -#: compensation/templates/compensation/detail/compensation/view.html:80 +#: compensation/templates/compensation/detail/compensation/view.html:81 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58 -#: compensation/templates/compensation/detail/eco_account/view.html:47 +#: compensation/templates/compensation/detail/eco_account/view.html:48 #: ema/tables.py:131 ema/templates/ema/detail/view.html:38 #: intervention/tables.py:157 #: intervention/templates/intervention/detail/view.html:85 @@ -710,7 +684,7 @@ msgid "Access not granted" msgstr "Nicht freigegeben - Datensatz nur lesbar" #: compensation/tables.py:212 -#: compensation/templates/compensation/detail/eco_account/view.html:35 +#: compensation/templates/compensation/detail/eco_account/view.html:36 #: konova/templates/konova/widgets/progressbar.html:3 msgid "Available" msgstr "Verfügbar" @@ -750,15 +724,45 @@ msgctxt "Compensation" msgid "Amount" msgstr "Menge" -#: compensation/templates/compensation/detail/compensation/includes/actions.html:66 -#: compensation/templates/compensation/detail/eco_account/includes/actions.html:65 -#: ema/templates/ema/detail/includes/actions.html:63 +#: compensation/templates/compensation/detail/compensation/includes/actions.html:40 +#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:39 +#: compensation/templates/compensation/detail/compensation/includes/documents.html:39 +#: compensation/templates/compensation/detail/compensation/includes/states-after.html:41 +#: compensation/templates/compensation/detail/compensation/includes/states-before.html:41 +#: compensation/templates/compensation/detail/eco_account/includes/actions.html:39 +#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:38 +#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:41 +#: compensation/templates/compensation/detail/eco_account/includes/documents.html:38 +#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:41 +#: compensation/templates/compensation/detail/eco_account/includes/states-before.html:41 +#: ema/templates/ema/detail/includes/actions.html:38 +#: ema/templates/ema/detail/includes/deadlines.html:38 +#: ema/templates/ema/detail/includes/documents.html:38 +#: ema/templates/ema/detail/includes/states-after.html:40 +#: ema/templates/ema/detail/includes/states-before.html:40 +#: intervention/templates/intervention/detail/includes/compensations.html:38 +#: intervention/templates/intervention/detail/includes/deductions.html:39 +#: intervention/templates/intervention/detail/includes/documents.html:39 +#: intervention/templates/intervention/detail/includes/payments.html:39 +#: intervention/templates/intervention/detail/includes/revocation.html:43 +#: templates/log.html:10 +msgid "Action" +msgstr "Aktionen" + +#: compensation/templates/compensation/detail/compensation/includes/actions.html:57 +#: compensation/templates/compensation/detail/eco_account/includes/actions.html:56 +msgid "No action type details" +msgstr "Keine Zusatzmerkmale" + +#: compensation/templates/compensation/detail/compensation/includes/actions.html:68 +#: compensation/templates/compensation/detail/eco_account/includes/actions.html:67 +#: ema/templates/ema/detail/includes/actions.html:67 msgid "Edit action" msgstr "Maßnahme bearbeiten" -#: compensation/templates/compensation/detail/compensation/includes/actions.html:69 -#: compensation/templates/compensation/detail/eco_account/includes/actions.html:68 -#: ema/templates/ema/detail/includes/actions.html:66 +#: compensation/templates/compensation/detail/compensation/includes/actions.html:71 +#: compensation/templates/compensation/detail/eco_account/includes/actions.html:70 +#: ema/templates/ema/detail/includes/actions.html:70 msgid "Remove action" msgstr "Maßnahme entfernen" @@ -924,50 +928,50 @@ msgstr "Neuen Ausgangszustand hinzufügen" msgid "Missing surfaces according to states after: " msgstr "Fehlende Flächenmengen laut Zielzustand: " -#: compensation/templates/compensation/detail/compensation/view.html:43 +#: compensation/templates/compensation/detail/compensation/view.html:44 msgid "Is CEF compensation" msgstr "Ist CEF Maßnahme" -#: compensation/templates/compensation/detail/compensation/view.html:46 -#: compensation/templates/compensation/detail/compensation/view.html:56 +#: compensation/templates/compensation/detail/compensation/view.html:47 +#: compensation/templates/compensation/detail/compensation/view.html:57 #: venv/lib/python3.7/site-packages/django/forms/widgets.py:710 msgid "Yes" msgstr "Ja" -#: compensation/templates/compensation/detail/compensation/view.html:48 -#: compensation/templates/compensation/detail/compensation/view.html:58 +#: compensation/templates/compensation/detail/compensation/view.html:49 +#: compensation/templates/compensation/detail/compensation/view.html:59 #: venv/lib/python3.7/site-packages/django/forms/widgets.py:711 msgid "No" msgstr "Nein" -#: compensation/templates/compensation/detail/compensation/view.html:53 +#: compensation/templates/compensation/detail/compensation/view.html:54 msgid "Is Coherence keeping compensation" msgstr "Ist Kohärenzsicherungsmaßnahme" -#: compensation/templates/compensation/detail/compensation/view.html:70 +#: compensation/templates/compensation/detail/compensation/view.html:71 #: intervention/templates/intervention/detail/view.html:75 msgid "Checked on " msgstr "Geprüft am " -#: compensation/templates/compensation/detail/compensation/view.html:70 -#: compensation/templates/compensation/detail/compensation/view.html:84 +#: compensation/templates/compensation/detail/compensation/view.html:71 +#: compensation/templates/compensation/detail/compensation/view.html:85 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:56 -#: compensation/templates/compensation/detail/eco_account/view.html:51 +#: compensation/templates/compensation/detail/eco_account/view.html:52 #: ema/templates/ema/detail/view.html:42 #: intervention/templates/intervention/detail/view.html:75 #: intervention/templates/intervention/detail/view.html:89 msgid "by" msgstr "von" -#: compensation/templates/compensation/detail/compensation/view.html:84 -#: compensation/templates/compensation/detail/eco_account/view.html:51 +#: compensation/templates/compensation/detail/compensation/view.html:85 +#: compensation/templates/compensation/detail/eco_account/view.html:52 #: ema/templates/ema/detail/view.html:42 #: intervention/templates/intervention/detail/view.html:89 msgid "Recorded on " msgstr "Verzeichnet am" -#: compensation/templates/compensation/detail/compensation/view.html:91 -#: compensation/templates/compensation/detail/eco_account/view.html:74 +#: compensation/templates/compensation/detail/compensation/view.html:92 +#: compensation/templates/compensation/detail/eco_account/view.html:75 #: compensation/templates/compensation/report/compensation/report.html:24 #: compensation/templates/compensation/report/eco_account/report.html:41 #: ema/templates/ema/detail/view.html:61 @@ -977,8 +981,8 @@ msgstr "Verzeichnet am" msgid "Last modified" msgstr "Zuletzt bearbeitet" -#: compensation/templates/compensation/detail/compensation/view.html:99 -#: compensation/templates/compensation/detail/eco_account/view.html:82 +#: compensation/templates/compensation/detail/compensation/view.html:100 +#: compensation/templates/compensation/detail/eco_account/view.html:83 #: ema/templates/ema/detail/view.html:76 intervention/forms/modalForms.py:56 #: intervention/templates/intervention/detail/view.html:116 msgid "Shared with" @@ -1037,14 +1041,14 @@ msgstr "Abbuchung bearbeiten" msgid "Remove Deduction" msgstr "Abbuchung entfernen" -#: compensation/templates/compensation/detail/eco_account/view.html:34 +#: compensation/templates/compensation/detail/eco_account/view.html:35 msgid "No surface deductable" msgstr "Keine Flächenmenge für Abbuchungen eingegeben. Bitte bearbeiten." -#: compensation/templates/compensation/detail/eco_account/view.html:57 -#: compensation/templates/compensation/detail/eco_account/view.html:61 -#: compensation/templates/compensation/detail/eco_account/view.html:65 -#: compensation/templates/compensation/detail/eco_account/view.html:69 +#: compensation/templates/compensation/detail/eco_account/view.html:58 +#: compensation/templates/compensation/detail/eco_account/view.html:62 +#: compensation/templates/compensation/detail/eco_account/view.html:66 +#: compensation/templates/compensation/detail/eco_account/view.html:70 #: ema/templates/ema/detail/view.html:48 ema/templates/ema/detail/view.html:52 #: ema/templates/ema/detail/view.html:56 #: intervention/templates/intervention/detail/view.html:30 @@ -1060,7 +1064,7 @@ msgstr "Keine Flächenmenge für Abbuchungen eingegeben. Bitte bearbeiten." msgid "Missing" msgstr "fehlt" -#: compensation/templates/compensation/detail/eco_account/view.html:70 +#: compensation/templates/compensation/detail/eco_account/view.html:71 #: compensation/templates/compensation/report/eco_account/report.html:24 #: ema/templates/ema/detail/view.html:57 #: ema/templates/ema/report/report.html:24 @@ -1976,7 +1980,7 @@ msgstr "{} wurde erfolgreich vom Nutzer {} geprüft! {}" msgid "missing" msgstr "fehlt" -#: konova/views.py:96 templates/navbars/navbar.html:16 +#: konova/views.py:99 templates/navbars/navbar.html:16 msgid "Home" msgstr "Home" @@ -3993,9 +3997,6 @@ msgstr "" #~ msgid "General data edited" #~ msgstr "Allgemeine Daten bearbeitet" -#~ msgid "Action type details" -#~ msgstr "Zusatzmerkmale" - #~ msgid "On registered data edited" #~ msgstr "Wenn meine freigegebenen Daten bearbeitet wurden" diff --git a/templates/form/scripts/jstree-scripts.html b/templates/form/scripts/jstree-scripts.html deleted file mode 100644 index c936760..0000000 --- a/templates/form/scripts/jstree-scripts.html +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file -- 2.38.5 From c0de1ae28d5331b692b0816eca501c1fb22b184a Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Tue, 15 Feb 2022 10:56:49 +0100 Subject: [PATCH 07/26] #112 AbstractCompensation rendering enhancements * minor changes to detail view rendering of EMA, Compensation and EcoAccount --- .../compensation/includes/states-after.html | 14 +-- .../compensation/includes/states-before.html | 14 +-- .../eco_account/includes/states-after.html | 14 +-- .../eco_account/includes/states-before.html | 14 +-- .../ema/detail/includes/states-after.html | 14 +-- .../ema/detail/includes/states-before.html | 14 +-- locale/de/LC_MESSAGES/django.mo | Bin 37523 -> 37589 bytes locale/de/LC_MESSAGES/django.po | 91 +++++++++--------- 8 files changed, 90 insertions(+), 85 deletions(-) diff --git a/compensation/templates/compensation/detail/compensation/includes/states-after.html b/compensation/templates/compensation/detail/compensation/includes/states-after.html index 2c95ca1..7faa0f1 100644 --- a/compensation/templates/compensation/detail/compensation/includes/states-after.html +++ b/compensation/templates/compensation/detail/compensation/includes/states-after.html @@ -48,13 +48,13 @@ {% for state in after_states %} - {{ state.biotope_type }} - {% if state.biotope_type_details.count > 0 %} -
- {% for detail in state.biotope_type_details.all %} - {{detail.long_name}} - {% endfor %} - {% endif %} + {{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}}) +
+ {% for detail in state.biotope_type_details.all %} + {{detail.long_name}} + {% empty %} + {% trans 'No biotope type details' %} + {% endfor %} {{ state.surface|floatformat:2 }} m² diff --git a/compensation/templates/compensation/detail/compensation/includes/states-before.html b/compensation/templates/compensation/detail/compensation/includes/states-before.html index d2ba369..23faed4 100644 --- a/compensation/templates/compensation/detail/compensation/includes/states-before.html +++ b/compensation/templates/compensation/detail/compensation/includes/states-before.html @@ -48,13 +48,13 @@ {% for state in before_states %} - {{ state.biotope_type }} - {% if state.biotope_type_details.count > 0 %} -
- {% for detail in state.biotope_type_details.all %} - {{detail.long_name}} - {% endfor %} - {% endif %} + {{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}}) +
+ {% for detail in state.biotope_type_details.all %} + {{detail.long_name}} + {% empty %} + {% trans 'No biotope type details' %} + {% endfor %} {{ state.surface|floatformat:2 }} m² diff --git a/compensation/templates/compensation/detail/eco_account/includes/states-after.html b/compensation/templates/compensation/detail/eco_account/includes/states-after.html index bead6f2..4fce2f0 100644 --- a/compensation/templates/compensation/detail/eco_account/includes/states-after.html +++ b/compensation/templates/compensation/detail/eco_account/includes/states-after.html @@ -48,13 +48,13 @@ {% for state in after_states %} - {{ state.biotope_type }} - {% if state.biotope_type_details.count > 0 %} -
- {% for detail in state.biotope_type_details.all %} - {{detail.long_name}} - {% endfor %} - {% endif %} + {{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}}) +
+ {% for detail in state.biotope_type_details.all %} + {{detail.long_name}} + {% empty %} + {% trans 'No biotope type details' %} + {% endfor %} {{ state.surface|floatformat:2 }} m² diff --git a/compensation/templates/compensation/detail/eco_account/includes/states-before.html b/compensation/templates/compensation/detail/eco_account/includes/states-before.html index c19b404..1c6311c 100644 --- a/compensation/templates/compensation/detail/eco_account/includes/states-before.html +++ b/compensation/templates/compensation/detail/eco_account/includes/states-before.html @@ -48,13 +48,13 @@ {% for state in before_states %} - {{ state.biotope_type }} - {% if state.biotope_type_details.count > 0 %} -
- {% for detail in state.biotope_type_details.all %} - {{detail.long_name}} - {% endfor %} - {% endif %} + {{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}}) +
+ {% for detail in state.biotope_type_details.all %} + {{detail.long_name}} + {% empty %} + {% trans 'No biotope type details' %} + {% endfor %} {{ state.surface|floatformat:2 }} m² diff --git a/ema/templates/ema/detail/includes/states-after.html b/ema/templates/ema/detail/includes/states-after.html index e09f4ba..56e87be 100644 --- a/ema/templates/ema/detail/includes/states-after.html +++ b/ema/templates/ema/detail/includes/states-after.html @@ -46,13 +46,13 @@ {% for state in after_states %} - {{ state.biotope_type }} - {% if state.biotope_type_details.count > 0 %} -
- {% for detail in state.biotope_type_details.all %} - {{detail.long_name}} - {% endfor %} - {% endif %} + {{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}}) +
+ {% for detail in state.biotope_type_details.all %} + {{detail.long_name}} + {% empty %} + {% trans 'No biotope type details' %} + {% endfor %} {{ state.surface|floatformat:2 }} m² diff --git a/ema/templates/ema/detail/includes/states-before.html b/ema/templates/ema/detail/includes/states-before.html index 1369829..2fd7c35 100644 --- a/ema/templates/ema/detail/includes/states-before.html +++ b/ema/templates/ema/detail/includes/states-before.html @@ -46,13 +46,13 @@ {% for state in before_states %} - {{ state.biotope_type }} - {% if state.biotope_type_details.count > 0 %} -
- {% for detail in state.biotope_type_details.all %} - {{detail.long_name}} - {% endfor %} - {% endif %} + {{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}}) +
+ {% for detail in state.biotope_type_details.all %} + {{detail.long_name}} + {% empty %} + {% trans 'No biotope type details' %} + {% endfor %} {{ state.surface|floatformat:2 }} m² diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index e9a94ccb374f1fcb49ae6b60cf0df3131a61819d..cbbff8e8beb3e75746f17607296ed15d29eb4fd1 100644 GIT binary patch delta 10433 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 Ul~NnT?H, YEAR. # -#: compensation/filters.py:122 compensation/forms/modalForms.py:37 -#: compensation/forms/modalForms.py:48 compensation/forms/modalForms.py:64 -#: compensation/forms/modalForms.py:357 compensation/forms/modalForms.py:469 +#: compensation/filters.py:122 compensation/forms/modalForms.py:36 +#: compensation/forms/modalForms.py:47 compensation/forms/modalForms.py:63 +#: compensation/forms/modalForms.py:356 compensation/forms/modalForms.py:463 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156 #: intervention/forms/forms.py:168 intervention/forms/modalForms.py:127 #: intervention/forms/modalForms.py:140 intervention/forms/modalForms.py:153 @@ -26,7 +26,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-15 10:08+0100\n" +"POT-Creation-Date: 2022-02-15 10:53+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -95,7 +95,7 @@ msgstr "" #: analysis/templates/analysis/reports/includes/eco_account/amount.html:3 #: analysis/templates/analysis/reports/includes/intervention/amount.html:3 #: analysis/templates/analysis/reports/includes/old_data/amount.html:3 -#: compensation/forms/modalForms.py:453 +#: compensation/forms/modalForms.py:447 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:34 #: intervention/templates/intervention/detail/includes/deductions.html:31 msgid "Amount" @@ -213,7 +213,7 @@ msgstr "Abbuchungen" #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:9 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:11 -#: compensation/forms/modalForms.py:195 +#: compensation/forms/modalForms.py:194 #: compensation/templates/compensation/detail/compensation/includes/states-after.html:36 #: compensation/templates/compensation/detail/compensation/includes/states-before.html:36 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36 @@ -246,7 +246,7 @@ msgid "Compensation" msgstr "Kompensation" #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:21 -#: compensation/forms/modalForms.py:77 +#: compensation/forms/modalForms.py:76 msgid "Payment" msgstr "Zahlung" @@ -352,8 +352,8 @@ msgstr "Aussagekräftiger Titel" msgid "Compensation XY; Location ABC" msgstr "Kompensation XY; Flur ABC" -#: compensation/forms/forms.py:57 compensation/forms/modalForms.py:63 -#: compensation/forms/modalForms.py:356 compensation/forms/modalForms.py:468 +#: compensation/forms/forms.py:57 compensation/forms/modalForms.py:62 +#: compensation/forms/modalForms.py:355 compensation/forms/modalForms.py:462 #: compensation/templates/compensation/detail/compensation/includes/actions.html:35 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:34 #: compensation/templates/compensation/detail/compensation/includes/documents.html:34 @@ -371,7 +371,7 @@ msgstr "Kompensation XY; Flur ABC" msgid "Comment" msgstr "Kommentar" -#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:470 +#: compensation/forms/forms.py:59 compensation/forms/modalForms.py:464 #: intervention/forms/forms.py:182 msgid "Additional comment" msgstr "Zusätzlicher Kommentar" @@ -469,73 +469,73 @@ msgstr "Ökokonto XY; Flur ABC" msgid "Edit Eco-Account" msgstr "Ökokonto bearbeiten" -#: compensation/forms/modalForms.py:38 +#: compensation/forms/modalForms.py:37 msgid "in Euro" msgstr "in Euro" -#: compensation/forms/modalForms.py:47 +#: compensation/forms/modalForms.py:46 #: intervention/templates/intervention/detail/includes/payments.html:31 msgid "Due on" msgstr "Fällig am" -#: compensation/forms/modalForms.py:50 +#: compensation/forms/modalForms.py:49 msgid "Due on which date" msgstr "Zahlung wird an diesem Datum erwartet" -#: compensation/forms/modalForms.py:65 compensation/forms/modalForms.py:358 +#: compensation/forms/modalForms.py:64 compensation/forms/modalForms.py:357 #: intervention/forms/modalForms.py:154 konova/forms.py:395 msgid "Additional comment, maximum {} letters" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" -#: compensation/forms/modalForms.py:78 +#: compensation/forms/modalForms.py:77 msgid "Add a payment for intervention '{}'" msgstr "Neue Ersatzzahlung zu Eingriff '{}' hinzufügen" -#: compensation/forms/modalForms.py:98 +#: compensation/forms/modalForms.py:97 msgid "If there is no date you can enter, please explain why." msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb." -#: compensation/forms/modalForms.py:159 compensation/forms/modalForms.py:171 +#: compensation/forms/modalForms.py:158 compensation/forms/modalForms.py:170 msgid "Biotope Type" msgstr "Biotoptyp" -#: compensation/forms/modalForms.py:162 +#: compensation/forms/modalForms.py:161 msgid "Select the biotope type" msgstr "Biotoptyp wählen" -#: compensation/forms/modalForms.py:176 compensation/forms/modalForms.py:188 +#: compensation/forms/modalForms.py:175 compensation/forms/modalForms.py:187 msgid "Biotope additional type" msgstr "Zusatzbezeichnung" -#: compensation/forms/modalForms.py:179 +#: compensation/forms/modalForms.py:178 msgid "Select an additional biotope type" msgstr "Zusatzbezeichnung wählen" -#: compensation/forms/modalForms.py:198 intervention/forms/modalForms.py:340 +#: compensation/forms/modalForms.py:197 intervention/forms/modalForms.py:340 msgid "in m²" msgstr "" -#: compensation/forms/modalForms.py:209 +#: compensation/forms/modalForms.py:208 msgid "New state" msgstr "Neuer Zustand" -#: compensation/forms/modalForms.py:210 +#: compensation/forms/modalForms.py:209 msgid "Insert data for the new state" msgstr "Geben Sie die Daten des neuen Zustandes ein" -#: compensation/forms/modalForms.py:217 konova/forms.py:193 +#: compensation/forms/modalForms.py:216 konova/forms.py:193 msgid "Object removed" msgstr "Objekt entfernt" -#: compensation/forms/modalForms.py:328 +#: compensation/forms/modalForms.py:327 msgid "Deadline Type" msgstr "Fristart" -#: compensation/forms/modalForms.py:331 +#: compensation/forms/modalForms.py:330 msgid "Select the deadline type" msgstr "Fristart wählen" -#: compensation/forms/modalForms.py:340 +#: compensation/forms/modalForms.py:339 #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31 #: ema/templates/ema/detail/includes/deadlines.html:31 @@ -543,51 +543,51 @@ msgstr "Fristart wählen" msgid "Date" msgstr "Datum" -#: compensation/forms/modalForms.py:343 +#: compensation/forms/modalForms.py:342 msgid "Select date" msgstr "Datum wählen" -#: compensation/forms/modalForms.py:370 +#: compensation/forms/modalForms.py:369 msgid "New deadline" msgstr "Neue Frist" -#: compensation/forms/modalForms.py:371 +#: compensation/forms/modalForms.py:370 msgid "Insert data for the new deadline" msgstr "Geben Sie die Daten der neuen Frist ein" -#: compensation/forms/modalForms.py:411 +#: compensation/forms/modalForms.py:410 msgid "Action Type" msgstr "Maßnahmentyp" -#: compensation/forms/modalForms.py:414 +#: compensation/forms/modalForms.py:413 msgid "Select the action type" msgstr "Maßnahmentyp wählen" -#: compensation/forms/modalForms.py:424 compensation/forms/modalForms.py:436 +#: compensation/forms/modalForms.py:418 compensation/forms/modalForms.py:430 msgid "Action Type detail" msgstr "Zusatzmerkmal" -#: compensation/forms/modalForms.py:427 +#: compensation/forms/modalForms.py:421 msgid "Select the action type detail" msgstr "Zusatzmerkmal wählen" -#: compensation/forms/modalForms.py:441 +#: compensation/forms/modalForms.py:435 msgid "Unit" msgstr "Einheit" -#: compensation/forms/modalForms.py:444 +#: compensation/forms/modalForms.py:438 msgid "Select the unit" msgstr "Einheit wählen" -#: compensation/forms/modalForms.py:456 +#: compensation/forms/modalForms.py:450 msgid "Insert the amount" msgstr "Menge eingeben" -#: compensation/forms/modalForms.py:481 +#: compensation/forms/modalForms.py:475 msgid "New action" msgstr "Neue Maßnahme" -#: compensation/forms/modalForms.py:482 +#: compensation/forms/modalForms.py:476 msgid "Insert data for the new action" msgstr "Geben Sie die Daten der neuen Maßnahme ein" @@ -751,18 +751,19 @@ msgstr "Aktionen" #: compensation/templates/compensation/detail/compensation/includes/actions.html:57 #: compensation/templates/compensation/detail/eco_account/includes/actions.html:56 +#: ema/templates/ema/detail/includes/actions.html:54 msgid "No action type details" msgstr "Keine Zusatzmerkmale" #: compensation/templates/compensation/detail/compensation/includes/actions.html:68 #: compensation/templates/compensation/detail/eco_account/includes/actions.html:67 -#: ema/templates/ema/detail/includes/actions.html:67 +#: ema/templates/ema/detail/includes/actions.html:65 msgid "Edit action" msgstr "Maßnahme bearbeiten" #: compensation/templates/compensation/detail/compensation/includes/actions.html:71 #: compensation/templates/compensation/detail/eco_account/includes/actions.html:70 -#: ema/templates/ema/detail/includes/actions.html:70 +#: ema/templates/ema/detail/includes/actions.html:68 msgid "Remove action" msgstr "Maßnahme entfernen" @@ -892,7 +893,7 @@ msgid "Biotope type" msgstr "Biotoptyp" #: compensation/templates/compensation/detail/compensation/includes/states-after.html:62 -#: compensation/templates/compensation/detail/compensation/includes/states-before.html:62 +#: compensation/templates/compensation/detail/compensation/includes/states-before.html:64 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:62 #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:62 #: ema/templates/ema/detail/includes/states-after.html:60 @@ -901,7 +902,7 @@ msgid "Edit state" msgstr "Zustand bearbeiten" #: compensation/templates/compensation/detail/compensation/includes/states-after.html:65 -#: compensation/templates/compensation/detail/compensation/includes/states-before.html:65 +#: compensation/templates/compensation/detail/compensation/includes/states-before.html:67 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:65 #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:65 #: ema/templates/ema/detail/includes/states-after.html:63 @@ -928,6 +929,10 @@ msgstr "Neuen Ausgangszustand hinzufügen" msgid "Missing surfaces according to states after: " msgstr "Fehlende Flächenmengen laut Zielzustand: " +#: compensation/templates/compensation/detail/compensation/includes/states-before.html:57 +msgid "No biotope type details" +msgstr "Keine Zusatzbezeichnungen" + #: compensation/templates/compensation/detail/compensation/view.html:44 msgid "Is CEF compensation" msgstr "Ist CEF Maßnahme" -- 2.38.5 From 4be26fbc226d005f0171fbb79800efb2f6140799 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Tue, 15 Feb 2022 11:32:20 +0100 Subject: [PATCH 08/26] #112 CompensationAction explanation * updates the help_text for action_type on NewActionModalForm to give a better explanation --- compensation/forms/modalForms.py | 2 +- locale/de/LC_MESSAGES/django.mo | Bin 37589 -> 37895 bytes locale/de/LC_MESSAGES/django.po | 31 ++++++++++++++++++++++--------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py index a8d38aa..cdcc26e 100644 --- a/compensation/forms/modalForms.py +++ b/compensation/forms/modalForms.py @@ -410,7 +410,7 @@ class NewActionModalForm(BaseModalForm): label=_("Action Type"), label_suffix="", required=True, - help_text=_("Select the action type"), + help_text=_("An action can consist of multiple different action types. All the selected action types are expected to be performed according to the amount and unit below on this form."), choices=[], widget=CompensationActionTreeCheckboxSelectMultiple(), ) diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index cbbff8e8beb3e75746f17607296ed15d29eb4fd1..87e1e03a5d6b5139726bf84ca2cdc849f3fe98fe 100644 GIT binary patch delta 10588 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\n" "Language-Team: LANGUAGE \n" @@ -560,8 +560,13 @@ msgid "Action Type" msgstr "Maßnahmentyp" #: compensation/forms/modalForms.py:413 -msgid "Select the action type" -msgstr "Maßnahmentyp wählen" +msgid "" +"An action can consist of multiple different action types. All the selected " +"action types are expected to be performed according to the amount and unit " +"below on this form." +msgstr "" +"Eine Maßnahme kann aus mehreren verschiedenen Maßnahmentypen bestehen. Alle hier gewählten " +"Einträge sollen mit der weiter unten angegebenen Einheit und Menge umgesetzt werden. " #: compensation/forms/modalForms.py:418 compensation/forms/modalForms.py:430 msgid "Action Type detail" @@ -892,8 +897,17 @@ msgstr "Fehlende Flächenmengen laut Ausgangszustand: " msgid "Biotope type" msgstr "Biotoptyp" +#: compensation/templates/compensation/detail/compensation/includes/states-after.html:56 +#: compensation/templates/compensation/detail/compensation/includes/states-before.html:56 +#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:56 +#: compensation/templates/compensation/detail/eco_account/includes/states-before.html:56 +#: ema/templates/ema/detail/includes/states-after.html:54 +#: ema/templates/ema/detail/includes/states-before.html:54 +msgid "No biotope type details" +msgstr "Keine Zusatzbezeichnungen" + #: compensation/templates/compensation/detail/compensation/includes/states-after.html:62 -#: compensation/templates/compensation/detail/compensation/includes/states-before.html:64 +#: compensation/templates/compensation/detail/compensation/includes/states-before.html:62 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:62 #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:62 #: ema/templates/ema/detail/includes/states-after.html:60 @@ -902,7 +916,7 @@ msgid "Edit state" msgstr "Zustand bearbeiten" #: compensation/templates/compensation/detail/compensation/includes/states-after.html:65 -#: compensation/templates/compensation/detail/compensation/includes/states-before.html:67 +#: compensation/templates/compensation/detail/compensation/includes/states-before.html:65 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:65 #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:65 #: ema/templates/ema/detail/includes/states-after.html:63 @@ -929,10 +943,6 @@ msgstr "Neuen Ausgangszustand hinzufügen" msgid "Missing surfaces according to states after: " msgstr "Fehlende Flächenmengen laut Zielzustand: " -#: compensation/templates/compensation/detail/compensation/includes/states-before.html:57 -msgid "No biotope type details" -msgstr "Keine Zusatzbezeichnungen" - #: compensation/templates/compensation/detail/compensation/view.html:44 msgid "Is CEF compensation" msgstr "Ist CEF Maßnahme" @@ -3993,6 +4003,9 @@ msgstr "" msgid "Unable to connect to qpid with SASL mechanism %s" msgstr "" +#~ msgid "Select the action type" +#~ msgstr "Maßnahmentyp wählen" + #~ msgid "No revocation" #~ msgstr "Kein Widerspruch" -- 2.38.5 From 6d5e2b8d1508bf753f934eb23016aaa0f96414d9 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Tue, 15 Feb 2022 13:23:15 +0100 Subject: [PATCH 09/26] #112 TreeWidget JS * adds visual support on (de-)selecting checkboxes * adds same support on initialization of checked checkboxes e.g. on edit forms --- .../widgets/checkbox-tree-select-content.html | 21 ++++++ .../konova/widgets/checkbox-tree-select.html | 65 ++++++++++++------- 2 files changed, 63 insertions(+), 23 deletions(-) create mode 100644 konova/templates/konova/widgets/checkbox-tree-select-content.html diff --git a/konova/templates/konova/widgets/checkbox-tree-select-content.html b/konova/templates/konova/widgets/checkbox-tree-select-content.html new file mode 100644 index 0000000..9bf5725 --- /dev/null +++ b/konova/templates/konova/widgets/checkbox-tree-select-content.html @@ -0,0 +1,21 @@ +{% load l10n fontawesome_5 %} + +{% for code in codes %} +
+ + {% if not code.is_leaf %} +
+ {% with code.children as codes %} + {% include 'konova/widgets/checkbox-tree-select-content.html' %} + {% endwith %} +
+ {% endif %} +
+{% endfor %} \ No newline at end of file diff --git a/konova/templates/konova/widgets/checkbox-tree-select.html b/konova/templates/konova/widgets/checkbox-tree-select.html index 5b40d3f..ddcc60c 100644 --- a/konova/templates/konova/widgets/checkbox-tree-select.html +++ b/konova/templates/konova/widgets/checkbox-tree-select.html @@ -1,23 +1,42 @@ -{% load l10n fontawesome_5 %} - -
- {% for code in codes %} -
- - {% if not code.is_leaf %} -
- {% with code.children as codes %} - {% include 'konova/widgets/checkbox-tree-select.html' %} - {% endwith %} -
- {% endif %} -
- {% endfor %} -
\ No newline at end of file +
+ {% include 'konova/widgets/checkbox-tree-select-content.html' %} +
+ + \ No newline at end of file -- 2.38.5 From 1ea5b4fc39b33a5b6a503cd5e70833c3e957c663 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Tue, 15 Feb 2022 14:35:49 +0100 Subject: [PATCH 10/26] # 112 Search input for TreeWidget * adds search input field for js-filtering by input --- .../konova/widgets/checkbox-tree-select.html | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/konova/templates/konova/widgets/checkbox-tree-select.html b/konova/templates/konova/widgets/checkbox-tree-select.html index ddcc60c..3129be8 100644 --- a/konova/templates/konova/widgets/checkbox-tree-select.html +++ b/konova/templates/konova/widgets/checkbox-tree-select.html @@ -1,3 +1,9 @@ +{% load i18n %} + +
+ +
+
{% include 'konova/widgets/checkbox-tree-select-content.html' %}
@@ -31,6 +37,22 @@ toggleSelectedCssClass(this); } + function searchInputHandler(event){ + var elem = $(this); + var val = elem.val() + var allTreeElements = $(".tree-element") + var allTreeElementsContain = $(".tree-element:contains(" + val + ")") + if(val.length > 0){ + allTreeElements.hide() + allTreeElementsContain.show() + }else{ + allTreeElements.show() + } + } + + // Add event listener on search input + $("#tree-search-input").keyup(searchInputHandler) + // Add event listener on changed checkboxes $(".tree-input").change(changeHandler); -- 2.38.5 From a160f3fe6ca1caeb4082d7c526ec03c04403b883 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Tue, 15 Feb 2022 15:33:25 +0100 Subject: [PATCH 11/26] #112 Autocomplete enhancements * enhances filtering for Autocomplete -> parent_parent will be searched for match as well * adds rendering of parent_parent group for BiotopeAutocomplete * enhances ordering of registration office autocomplete --- konova/autocompletes.py | 49 +++++++++++++++++++++++++++++- konova/static/css/konova.css | 13 ++++++-- konova/utils/message_templates.py | 2 +- locale/de/LC_MESSAGES/django.mo | Bin 37895 -> 37936 bytes locale/de/LC_MESSAGES/django.po | 20 +++++++----- 5 files changed, 73 insertions(+), 11 deletions(-) diff --git a/konova/autocompletes.py b/konova/autocompletes.py index 3a79ab6..b07e208 100644 --- a/konova/autocompletes.py +++ b/konova/autocompletes.py @@ -5,7 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 07.12.20 """ +import collections + from dal_select2.views import Select2QuerySetView, Select2GroupQuerySetView +from django.core.exceptions import ImproperlyConfigured + +from konova.utils.message_templates import UNGROUPED from user.models import User from django.db.models import Q @@ -139,6 +144,8 @@ class KonovaCodeAutocomplete(Select2GroupQuerySetView): q_or |= Q(short_name__icontains=keyword) q_or |= Q(parent__long_name__icontains=keyword) q_or |= Q(parent__short_name__icontains=keyword) + q_or |= Q(parent__parent__long_name__icontains=keyword) + q_or |= Q(parent__parent__short_name__icontains=keyword) _filter.add(q_or, Q.AND) qs = qs.filter(_filter).distinct() return qs @@ -214,6 +221,41 @@ class BiotopeCodeAutocomplete(KonovaCodeAutocomplete): def get_result_label(self, result): return f"{result.long_name} ({result.short_name})" + def get_results(self, context): + """Return the options grouped by a common related model. + + Raises ImproperlyConfigured if self.group_by_name is not configured + """ + if not self.group_by_related: + raise ImproperlyConfigured("Missing group_by_related.") + + super_groups = collections.OrderedDict() + + object_list = context['object_list'] + + for result in object_list: + group = result.parent if result.parent else None + group_name = f"{group.long_name} ({group.short_name})" if group else UNGROUPED + super_group = result.parent.parent if result.parent else None + super_group_name = f"{super_group.long_name} ({super_group.short_name})" if super_group else UNGROUPED + super_groups.setdefault(super_group_name, {}) + super_groups[super_group_name].setdefault(group_name, []) + super_groups[super_group_name][group_name].append(result) + + return [{ + 'id': None, + 'text': super_group, + 'children': [{ + "id": None, + "text": group, + "children": [{ + 'id': self.get_result_value(result), + 'text': self.get_result_label(result), + 'selected_text': self.get_selected_result_label(result), + } for result in results] + } for group, results in groups.items()] + } for super_group, groups in super_groups.items()] + class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete): """ @@ -284,6 +326,11 @@ class RegistrationOfficeCodeAutocomplete(KonovaCodeAutocomplete): self.c = CODELIST_REGISTRATION_OFFICE_ID super().__init__(*args, **kwargs) + def order_by(self, qs): + return qs.order_by( + "parent__long_name" + ) + class ConservationOfficeCodeAutocomplete(KonovaCodeAutocomplete): """ @@ -297,4 +344,4 @@ class ConservationOfficeCodeAutocomplete(KonovaCodeAutocomplete): super().__init__(*args, **kwargs) def get_result_label(self, result): - return f"{result.long_name} ({result.short_name})" \ No newline at end of file + return f"{result.long_name} ({result.short_name})" diff --git a/konova/static/css/konova.css b/konova/static/css/konova.css index 709d3eb..7e7f3fd 100644 --- a/konova/static/css/konova.css +++ b/konova/static/css/konova.css @@ -242,15 +242,24 @@ Similar to bootstraps 'shadow-lg' .select2-results__option--highlighted{ background-color: var(--rlp-red) !important; } +/* .select2-container--default .select2-results__group{ background-color: var(--rlp-gray-light); } .select2-container--default .select2-results__option .select2-results__option{ - padding-left: 2em !important; + padding-left: 1em !important; +} + + */ +.select2-results__options--nested{ + padding-left: 1em !important; } .select2-container--default .select2-results > .select2-results__options{ max-height: 500px !important; } +/* .select2-container--default .select2-results__option .select2-results__option{ padding-left: 2em; -} \ No newline at end of file +} + + */ \ No newline at end of file diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py index c7a2be0..5809b3b 100644 --- a/konova/utils/message_templates.py +++ b/konova/utils/message_templates.py @@ -7,7 +7,7 @@ Created on: 02.08.21 """ from django.utils.translation import gettext_lazy as _ - +UNGROUPED = _("Ungrouped") FORM_INVALID = _("There was an error on this form.") PARAMS_INVALID = _("Invalid parameters") INTERVENTION_INVALID = _("There are errors in this intervention.") diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index 87e1e03a5d6b5139726bf84ca2cdc849f3fe98fe..022e9e1a728d186a0e8be95eee8ae29ea5928bee 100644 GIT binary patch delta 8795 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 A4*&oF delta 8770 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\n" "Language-Team: LANGUAGE \n" @@ -565,8 +565,9 @@ msgid "" "action types are expected to be performed according to the amount and unit " "below on this form." msgstr "" -"Eine Maßnahme kann aus mehreren verschiedenen Maßnahmentypen bestehen. Alle hier gewählten " -"Einträge sollen mit der weiter unten angegebenen Einheit und Menge umgesetzt werden. " +"Eine Maßnahme kann aus mehreren verschiedenen Maßnahmentypen bestehen. Alle " +"hier gewählten Einträge sollen mit der weiter unten angegebenen Einheit und " +"Menge umgesetzt werden. " #: compensation/forms/modalForms.py:418 compensation/forms/modalForms.py:430 msgid "Action Type detail" @@ -1765,6 +1766,11 @@ msgstr "Neu" msgid "Show" msgstr "Anzeigen" +#: konova/templates/konova/widgets/checkbox-tree-select.html:4 +#: templates/generic_index.html:56 +msgid "Search" +msgstr "Suchen" + #: konova/templates/konova/widgets/generate-content-input.html:6 msgid "Generate new" msgstr "Neu generieren" @@ -1805,6 +1811,10 @@ msgstr "{} - Freigegebene Daten geprüft" msgid "Request for new API token" msgstr "Anfrage für neuen API Token" +#: konova/utils/message_templates.py:10 +msgid "Ungrouped" +msgstr "Ohne Zuordnung" + #: konova/utils/message_templates.py:11 msgid "There was an error on this form." msgstr "Es gab einen Fehler im Formular." @@ -2270,10 +2280,6 @@ msgstr "Neu" msgid "Search for keywords" msgstr "Nach Schlagwörtern suchen" -#: templates/generic_index.html:56 -msgid "Search" -msgstr "Suchen" - #: templates/generic_index.html:57 msgid "Start search" msgstr "Starte Suche" -- 2.38.5 From ed27d8d589fd298be47bf835c56a605a96048753 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 16 Feb 2022 08:26:24 +0100 Subject: [PATCH 12/26] #112 Order enhancement * enhances ordering for action details and biotope details --- konova/autocompletes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/konova/autocompletes.py b/konova/autocompletes.py index b07e208..432df61 100644 --- a/konova/autocompletes.py +++ b/konova/autocompletes.py @@ -188,7 +188,7 @@ class CompensationActionDetailCodeAutocomplete(KonovaCodeAutocomplete): def order_by(self, qs): return qs.order_by( - "parent__long_name" + "long_name" ) @@ -281,7 +281,7 @@ class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete): qs (QuerySet): The ordered queryset """ return qs.order_by( - "parent__long_name", + "long_name", ) def get_result_label(self, result): -- 2.38.5 From 91185ef847c02d68bd9f2f91248747bdd429d07f Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 16 Feb 2022 08:31:18 +0100 Subject: [PATCH 13/26] #112 Tree filter case insensitive search * adds case insensitive search for TreeWidget --- konova/templates/konova/widgets/checkbox-tree-select.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/konova/templates/konova/widgets/checkbox-tree-select.html b/konova/templates/konova/widgets/checkbox-tree-select.html index 3129be8..c2b107c 100644 --- a/konova/templates/konova/widgets/checkbox-tree-select.html +++ b/konova/templates/konova/widgets/checkbox-tree-select.html @@ -41,7 +41,11 @@ var elem = $(this); var val = elem.val() var allTreeElements = $(".tree-element") - var allTreeElementsContain = $(".tree-element:contains(" + val + ")") + var allTreeElementsContain = $(".tree-element").filter(function(){ + var reg = new RegExp(val, "i"); + return reg.test($(this).text()); + } + ); if(val.length > 0){ allTreeElements.hide() allTreeElementsContain.show() -- 2.38.5 From 8224f4c8c5bc45bb95e815e87d718166ccef089a Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 16 Feb 2022 09:08:11 +0100 Subject: [PATCH 14/26] # 110 Biotope codes * removes list 974 from update_codelist.py command * adds migration for existing biotope states to be changed into proper list --- .../management/commands/update_codelist.py | 3 +- codelist/settings.py | 2 +- konova/migrations/0005_auto_20220216_0856.py | 40 +++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 konova/migrations/0005_auto_20220216_0856.py diff --git a/codelist/management/commands/update_codelist.py b/codelist/management/commands/update_codelist.py index 3fd4c6c..85c9032 100644 --- a/codelist/management/commands/update_codelist.py +++ b/codelist/management/commands/update_codelist.py @@ -14,7 +14,7 @@ from codelist.settings import CODELIST_INTERVENTION_HANDLER_ID, CODELIST_CONSERV CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, \ CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, \ CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \ - CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_AFTER_STATE_BIOTOPES__ID + CODELIST_COMPENSATION_ACTION_DETAIL_ID from konova.management.commands.setup import BaseKonovaCommand from konova.settings import PROXIES @@ -34,7 +34,6 @@ class Command(BaseKonovaCommand): CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, - CODELIST_AFTER_STATE_BIOTOPES__ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, diff --git a/codelist/settings.py b/codelist/settings.py index 53cef73..b4226b5 100644 --- a/codelist/settings.py +++ b/codelist/settings.py @@ -14,7 +14,7 @@ CODELIST_INTERVENTION_HANDLER_ID = 903 # CLMassnahmeträger CODELIST_CONSERVATION_OFFICE_ID = 907 # CLNaturschutzbehörden CODELIST_REGISTRATION_OFFICE_ID = 1053 # CLZulassungsbehörden CODELIST_BIOTOPES_ID = 654 # CL_Biotoptypen -CODELIST_AFTER_STATE_BIOTOPES__ID = 974 # CL-KSP_ZielBiotoptypen +CODELIST_AFTER_STATE_BIOTOPES__ID = 974 # CL-KSP_ZielBiotoptypen - USAGE HAS BEEN DROPPED IN 2022 IN FAVOR OF 654 CODELIST_BIOTOPES_EXTRA_CODES_ID = 975 # CLZusatzbezeichnung CODELIST_LAW_ID = 1048 # CLVerfahrensrecht CODELIST_PROCESS_TYPE_ID = 44382 # CLVerfahrenstyp diff --git a/konova/migrations/0005_auto_20220216_0856.py b/konova/migrations/0005_auto_20220216_0856.py new file mode 100644 index 0000000..43c518a --- /dev/null +++ b/konova/migrations/0005_auto_20220216_0856.py @@ -0,0 +1,40 @@ +# Generated by Django 3.1.3 on 2022-02-16 07:56 + +from django.db import migrations, transaction + +from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_AFTER_STATE_BIOTOPES__ID + + +def migrate_biotopes_from_974_to_654(apps, schema_editor): + KonovaCode = apps.get_model("codelist", "KonovaCode") + CompensationState = apps.get_model("compensation", "CompensationState") + all_states = CompensationState.objects.all() + + with transaction.atomic(): + for state in all_states: + new_biotope_code = KonovaCode.objects.get( + code_lists__in=[CODELIST_BIOTOPES_ID], + is_selectable=True, + is_archived=False, + short_name=state.biotope_type.short_name, + ) + state.biotope_type = new_biotope_code + state.save() + + all_states = CompensationState.objects.all() + after_state_list_elements = all_states.filter( + biotope_type__code_lists__in=[CODELIST_AFTER_STATE_BIOTOPES__ID] + ) + if after_state_list_elements.count() > 0: + raise Exception("Still states with wrong codelist entries!") + + +class Migration(migrations.Migration): + + dependencies = [ + ('konova', '0004_auto_20220209_0839'), + ] + + operations = [ + migrations.RunPython(migrate_biotopes_from_974_to_654), + ] -- 2.38.5 From 0677cec709a5d68e66b4e58c6cb874707fee0e8b Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 16 Feb 2022 09:44:56 +0100 Subject: [PATCH 15/26] #118 API ActionTypes * adds support for multiple action_type entries on one CompensationAction --- api/utils/serializer/v1/serializer.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/api/utils/serializer/v1/serializer.py b/api/utils/serializer/v1/serializer.py index f56db81..caae2de 100644 --- a/api/utils/serializer/v1/serializer.py +++ b/api/utils/serializer/v1/serializer.py @@ -367,7 +367,9 @@ class AbstractCompensationAPISerializerV1Mixin: """ actions = [] for entry in actions_data: - action = entry["action"] + action_types = [ + self._konova_code_from_json(e, CODELIST_COMPENSATION_ACTION_ID) for e in entry["action_types"] + ] action_details = [ self._konova_code_from_json(e, CODELIST_COMPENSATION_ACTION_DETAIL_ID) for e in entry["action_details"] ] @@ -384,7 +386,7 @@ class AbstractCompensationAPISerializerV1Mixin: # If this exact data is already existing, we do not create it new. Instead put it's id in the list of # entries, we will use to set the new actions action_entry = obj.actions.filter( - action_type__atom_id=action, + action_type__in=action_types, amount=amount, unit=unit, comment=comment, @@ -396,13 +398,13 @@ class AbstractCompensationAPISerializerV1Mixin: else: # Create and add id to list action_entry = CompensationAction.objects.create( - action_type=self._konova_code_from_json(action, CODELIST_COMPENSATION_ACTION_ID), amount=amount, unit=unit, comment=comment, ) actions.append(action_entry.id) + action_entry.action_type.set(action_types) action_entry.action_type_details.set(action_details) obj.actions.set(actions) return obj @@ -438,7 +440,9 @@ class AbstractCompensationAPISerializerV1Mixin: """ return [ { - "action": self._konova_code_to_json(entry.action_type), + "action_types": [ + self._konova_code_to_json(action) for action in entry.action_type.all() + ], "action_details": [ self._konova_code_to_json(detail) for detail in entry.action_type_details.all() ], -- 2.38.5 From c7382f1e54f35e82bfa49f38865725f9af0955c3 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 16 Feb 2022 11:38:24 +0100 Subject: [PATCH 16/26] #118 API pagination * adds pagination and related parameters to GET apis * updates api GET test --- api/tests/v1/get/test_api_get.py | 7 ++++- api/utils/serializer/serializer.py | 12 +++++-- api/views/v1/views.py | 17 ++++++---- api/views/views.py | 50 ++++++++++++++++++++++++++---- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/api/tests/v1/get/test_api_get.py b/api/tests/v1/get/test_api_get.py index 38af864..3c46454 100644 --- a/api/tests/v1/get/test_api_get.py +++ b/api/tests/v1/get/test_api_get.py @@ -36,7 +36,12 @@ class APIV1GetTestCase(BaseAPIV1TestCase): """ response = self._run_get_request(url) content = json.loads(response.content) - geojson = content[str(obj.id)] + self.assertIn("rpp", content) + self.assertIn("p", content) + self.assertIn("next", content) + self.assertIn("results", content) + paginated_content = content["results"] + geojson = paginated_content[str(obj.id)] self.assertEqual(response.status_code, 200, msg=response.content) return geojson diff --git a/api/utils/serializer/serializer.py b/api/utils/serializer/serializer.py index 443e10c..8d618e4 100644 --- a/api/utils/serializer/serializer.py +++ b/api/utils/serializer/serializer.py @@ -10,6 +10,7 @@ from abc import abstractmethod from django.contrib.gis import geos from django.contrib.gis.geos import GEOSGeometry +from django.core.paginator import Paginator from konova.utils.message_templates import DATA_UNSHARED @@ -19,6 +20,10 @@ class AbstractModelAPISerializer: lookup = None properties_data = None + rpp = None + page_number = None + paginator = None + class Meta: abstract = True @@ -80,9 +85,12 @@ class AbstractModelAPISerializer: Returns: serialized_data (dict) """ - entries = self.model.objects.filter(**self.lookup) + entries = self.model.objects.filter(**self.lookup).order_by("id") + self.paginator = Paginator(entries, self.rpp) + requested_entries = self.paginator.page(self.page_number) + serialized_data = {} - for entry in entries: + for entry in requested_entries.object_list: serialized_data[str(entry.id)] = self._model_to_geo_json(entry) return serialized_data diff --git a/api/views/v1/views.py b/api/views/v1/views.py index 7789680..8da5d49 100644 --- a/api/views/v1/views.py +++ b/api/views/v1/views.py @@ -21,7 +21,6 @@ class AbstractAPIViewV1(AbstractAPIView): """ Holds general serialization functions for API v1 """ - serializer = None def __init__(self, *args, **kwargs): self.lookup = { @@ -45,11 +44,17 @@ class AbstractAPIViewV1(AbstractAPIView): response (JsonResponse) """ try: + self.rpp = int(request.GET.get("rpp", self.rpp)) + self.page_number = int(request.GET.get("p", self.page_number)) + + self.serializer.rpp = self.rpp + self.serializer.page_number = self.page_number + self.serializer.prepare_lookup(id, self.user) data = self.serializer.fetch_and_serialize() except Exception as e: - return self.return_error_response(e, 500) - return JsonResponse(data) + return self._return_error_response(e, 500) + return self._return_response(request, data) def post(self, request: HttpRequest): """ Handles the POST request @@ -67,7 +72,7 @@ class AbstractAPIViewV1(AbstractAPIView): body = json.loads(body) created_id = self.serializer.create_model_from_json(body, self.user) except Exception as e: - return self.return_error_response(e, 500) + return self._return_error_response(e, 500) return JsonResponse({"id": created_id}) def put(self, request: HttpRequest, id=None): @@ -87,7 +92,7 @@ class AbstractAPIViewV1(AbstractAPIView): body = json.loads(body) updated_id = self.serializer.update_model_from_json(id, body, self.user) except Exception as e: - return self.return_error_response(e, 500) + return self._return_error_response(e, 500) return JsonResponse({"id": updated_id}) def delete(self, request: HttpRequest, id=None): @@ -104,7 +109,7 @@ class AbstractAPIViewV1(AbstractAPIView): try: success = self.serializer.delete_entry(id, self.user) except Exception as e: - return self.return_error_response(e, 500) + return self._return_error_response(e, 500) return JsonResponse( { "success": success, diff --git a/api/views/views.py b/api/views/views.py index fb4f6df..35c54fe 100644 --- a/api/views/views.py +++ b/api/views/views.py @@ -31,10 +31,22 @@ class AbstractAPIView(View): """ user = None + serializer = None + rpp = 5 # Results per page default + page_number = 1 # Page number default class Meta: abstract = True + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.response_body_base = { + "rpp": None, + "p": None, + "next": None, + "results": None + } + @csrf_exempt def dispatch(self, request, *args, **kwargs): try: @@ -42,13 +54,14 @@ class AbstractAPIView(View): ksp_token = request.headers.get(KSP_TOKEN_HEADER_IDENTIFIER, None) ksp_user = request.headers.get(KSP_USER_HEADER_IDENTIFIER, None) self.user = APIUserToken.get_user_from_token(ksp_token, ksp_user) + request.user = self.user if not self.user.is_default_user(): raise PermissionError("Default permissions required") except PermissionError as e: - return self.return_error_response(e, 403) + return self._return_error_response(e, 403) return super().dispatch(request, *args, **kwargs) - def return_error_response(self, error, status_code=500): + def _return_error_response(self, error, status_code=500): """ Returns an error as JsonReponse Args: @@ -68,6 +81,31 @@ class AbstractAPIView(View): status=status_code ) + def _return_response(self, request: HttpRequest, data): + """ Returns all important data into a response object + + Args: + request (HttpRequest): The incoming request + data (dict): The serialized data + + Returns: + response (JsonResponse): The response to be returned + """ + response = self.response_body_base + next_page = self.page_number + 1 + next_page = next_page if next_page in self.serializer.paginator.page_range else None + if next_page is not None: + next_url = request.build_absolute_uri( + request.path + f"?rpp={self.rpp}&p={next_page}" + ) + else: + next_url = None + response["rpp"] = self.rpp + response["p"] = self.page_number + response["next"] = next_url + response["results"] = data + return JsonResponse(response) + class InterventionCheckAPIView(AbstractAPIView): @@ -82,14 +120,14 @@ class InterventionCheckAPIView(AbstractAPIView): response (JsonResponse) """ if not self.user.is_zb_user(): - return self.return_error_response("Permission not granted", 403) + return self._return_error_response("Permission not granted", 403) try: obj = Intervention.objects.get( id=id, users__in=[self.user] ) except Exception as e: - return self.return_error_response(e) + return self._return_error_response(e) all_valid, check_details = self.run_quality_checks(obj) @@ -161,7 +199,7 @@ class AbstractModelShareAPIView(AbstractAPIView): try: users = self._get_shared_users_of_object(id) except Exception as e: - return self.return_error_response(e) + return self._return_error_response(e) data = { "users": [ @@ -185,7 +223,7 @@ class AbstractModelShareAPIView(AbstractAPIView): try: success = self._process_put_body(request.body, id) except Exception as e: - return self.return_error_response(e) + return self._return_error_response(e) data = { "success": success, } -- 2.38.5 From 5ccb63d27b18fa3d06e335421d88b88f70767728 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 16 Feb 2022 12:35:19 +0100 Subject: [PATCH 17/26] #114 Unshared Account Deductions * enables deducting from unshared eco accounts * account must be recorded and not deleted, so users can use it for deductions --- .../detail/eco_account/includes/deductions.html | 4 ++-- compensation/tests/ecoaccount/test_workflow.py | 2 ++ compensation/views/eco_account.py | 9 ++++++--- intervention/tests/test_workflow.py | 15 ++------------- konova/autocompletes.py | 3 +-- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/compensation/templates/compensation/detail/eco_account/includes/deductions.html b/compensation/templates/compensation/detail/eco_account/includes/deductions.html index 10f177e..b91fb21 100644 --- a/compensation/templates/compensation/detail/eco_account/includes/deductions.html +++ b/compensation/templates/compensation/detail/eco_account/includes/deductions.html @@ -10,7 +10,7 @@
- {% if is_default_member and has_access %} + {% if is_default_member and obj.recorded %} diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py index 1cdb030..afc4111 100644 --- a/compensation/tests/ecoaccount/test_workflow.py +++ b/compensation/tests/ecoaccount/test_workflow.py @@ -231,7 +231,9 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): def test_edit_deduction(self): test_surface = self.eco_account.get_available_rest()[0] self.eco_account.set_recorded(self.superuser) + self.intervention.share_with(self.superuser) self.eco_account.refresh_from_db() + self.assertIn(self.superuser, self.intervention.is_shared_with(self.superuser)) deduction = EcoAccountDeduction.objects.create( intervention=self.intervention, diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py index cf5f328..d291218 100644 --- a/compensation/views/eco_account.py +++ b/compensation/views/eco_account.py @@ -272,7 +272,6 @@ def remove_view(request: HttpRequest, id: str): @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 @@ -287,6 +286,8 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str): acc = get_object_or_404(EcoAccount, id=id) try: eco_deduction = acc.deductions.get(id=deduction_id) + if not eco_deduction.intervention.is_shared_with(request.user): + raise ObjectDoesNotExist() except ObjectDoesNotExist: raise Http404("Unknown deduction") @@ -300,7 +301,6 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str): @login_required @default_group_required -@shared_access_required(EcoAccount, "id") def deduction_edit_view(request: HttpRequest, id: str, deduction_id: str): """ Renders a modal view for editing deductions @@ -315,6 +315,8 @@ def deduction_edit_view(request: HttpRequest, id: str, deduction_id: str): acc = get_object_or_404(EcoAccount, id=id) try: eco_deduction = acc.deductions.get(id=deduction_id) + if not eco_deduction.intervention.is_shared_with(request.user): + raise ObjectDoesNotExist except ObjectDoesNotExist: raise Http404("Unknown deduction") @@ -679,7 +681,6 @@ def remove_document_view(request: HttpRequest, id: str, doc_id: str): @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 @@ -691,6 +692,8 @@ def new_deduction_view(request: HttpRequest, id: str): """ acc = get_object_or_404(EcoAccount, id=id) + if not acc.recorded: + raise Http404() form = NewDeductionModalForm(request.POST or None, instance=acc, request=request) return form.process_request( request, diff --git a/intervention/tests/test_workflow.py b/intervention/tests/test_workflow.py index 69f606f..1a66245 100644 --- a/intervention/tests/test_workflow.py +++ b/intervention/tests/test_workflow.py @@ -303,7 +303,6 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): Reasons for failing are: * EcoAccount does not provide enough 'deductable_surface' * EcoAccount is not recorded (not "approved"), yet - * EcoAccount is not shared with performing user Args: new_url (str): The url to send the post data to @@ -315,7 +314,6 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): # Before running fail positive tests, we need to have an account in a (normally) fine working state self.assertIsNotNone(self.eco_account.recorded) # -> is recorded self.assertGreater(self.eco_account.deductable_surface, test_surface) # -> has more deductable surface than we need - self.assertIn(self.superuser, self.eco_account.users.all()) # -> is shared with the performing user # Count the number of already existing deductions in total and for the account for later comparison num_deductions = self.eco_account.deductions.count() @@ -333,20 +331,11 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): self.assertEqual(num_deductions, self.eco_account.deductions.count()) self.assertEqual(num_deductions_total, EcoAccountDeduction.objects.count()) - # Now restore the deductable surface to a valid size back again but remove the user from the shared list + # Now restore the deductable surface to a valid size back again self.eco_account.deductable_surface = test_surface + 100.00 - self.eco_account.share_with_list([]) self.eco_account.save() - # Now perform the (expected) failing request (again) - self.client_user.post(new_url, post_data) - - # Expect no changes at all, since the account is not shared - self.assertEqual(num_deductions, self.eco_account.deductions.count()) - self.assertEqual(num_deductions_total, EcoAccountDeduction.objects.count()) - - # Restore the sharing but remove the recording state - self.eco_account.share_with_list([self.superuser]) + # Remove the recording state self.eco_account.recorded.delete() self.eco_account.refresh_from_db() self.eco_account.save() diff --git a/konova/autocompletes.py b/konova/autocompletes.py index 432df61..f1775b5 100644 --- a/konova/autocompletes.py +++ b/konova/autocompletes.py @@ -25,7 +25,7 @@ from intervention.models import Intervention class EcoAccountAutocomplete(Select2QuerySetView): """ Autocomplete for ecoAccount entries - Only returns entries that are accessible for the requesting user and already are recorded + Only returns entries that are already recorded and not deleted """ def get_queryset(self): @@ -34,7 +34,6 @@ class EcoAccountAutocomplete(Select2QuerySetView): qs = EcoAccount.objects.filter( deleted=None, recorded__isnull=False, - users__in=[self.request.user], ).order_by( "identifier" ) -- 2.38.5 From 75c70ff8dcf7d7a6bbb093871dd73a51d5f36e09 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Thu, 17 Feb 2022 13:13:32 +0100 Subject: [PATCH 18/26] #101 Team settings * adds first implementation for team managing --- intervention/forms/modalForms.py | 6 +- konova/models/object.py | 41 ++++++----- user/forms.py | 110 +++++++++++++++++++++++++++- user/models/__init__.py | 7 +- user/models/team.py | 16 ++++ user/templates/user/index.html | 8 ++ user/templates/user/team/index.html | 67 +++++++++++++++++ user/urls.py | 4 + user/views.py | 58 ++++++++++++++- 9 files changed, 285 insertions(+), 32 deletions(-) create mode 100644 user/models/team.py create mode 100644 user/templates/user/team/index.html diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index a8fa98d..0b72ae8 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -7,11 +7,11 @@ Created on: 27.09.21 """ 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, \ - REVOCATION_EDITED, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE -from user.models import User, UserActionLogEntry + REVOCATION_EDITED +from user.models import User +from user.models import UserActionLogEntry from django.db import transaction from django import forms from django.utils.translation import gettext_lazy as _ diff --git a/konova/models/object.py b/konova/models/object.py index a6164f5..69a0a2e 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -16,7 +16,6 @@ from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP, LANIS_ZOOM_LUT, from konova.tasks import celery_send_mail_shared_access_removed, celery_send_mail_shared_access_given, \ celery_send_mail_shared_data_recorded, celery_send_mail_shared_data_unrecorded, \ celery_send_mail_shared_data_deleted, celery_send_mail_shared_data_checked -from user.models import User from django.core.exceptions import ObjectDoesNotExist from django.http import HttpRequest from django.utils.timezone import now @@ -28,7 +27,6 @@ from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_I from konova.utils import generators from konova.utils.generators import generate_random_string from konova.utils.message_templates import CHECKED_RECORDED_RESET, GEOMETRY_CONFLICT_WITH_TEMPLATE -from user.models import UserActionLogEntry, UserAction class UuidModel(models.Model): @@ -50,14 +48,14 @@ class BaseResource(UuidModel): A basic resource model, which defines attributes for every derived model """ created = models.ForeignKey( - UserActionLogEntry, + "user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, related_name='+' ) modified = models.ForeignKey( - UserActionLogEntry, + "user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, @@ -94,9 +92,9 @@ class BaseObject(BaseResource): """ identifier = models.CharField(max_length=1000, null=True, blank=True) title = models.CharField(max_length=1000, null=True, blank=True) - deleted = models.ForeignKey(UserActionLogEntry, on_delete=models.SET_NULL, null=True, blank=True, related_name='+') + deleted = models.ForeignKey("user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, related_name='+') comment = models.TextField(null=True, blank=True) - log = models.ManyToManyField(UserActionLogEntry, blank=True, help_text="Keeps all user actions of an object", editable=False) + log = models.ManyToManyField("user.UserActionLogEntry", blank=True, help_text="Keeps all user actions of an object", editable=False) class Meta: abstract = True @@ -105,7 +103,7 @@ class BaseObject(BaseResource): def set_status_messages(self, request: HttpRequest): raise NotImplementedError - def mark_as_deleted(self, user: User, send_mail: bool = True): + def mark_as_deleted(self, user, send_mail: bool = True): """ Mark an entry as deleted Does not delete from database but sets a timestamp for being deleted on and which user deleted the object @@ -116,6 +114,7 @@ class BaseObject(BaseResource): Returns: """ + from user.models import UserActionLogEntry if self.deleted: # Nothing to do here return @@ -133,7 +132,7 @@ class BaseObject(BaseResource): self.save() - def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None): + def mark_as_edited(self, performing_user, request: HttpRequest = None, edit_comment: str = None): """ In case the object or a related object changed the log history needs to be updated Args: @@ -144,13 +143,14 @@ class BaseObject(BaseResource): Returns: """ + from user.models import UserActionLogEntry edit_action = UserActionLogEntry.get_edited_action(performing_user, edit_comment) self.modified = edit_action self.log.add(edit_action) self.save() return edit_action - def add_log_entry(self, action: UserAction, user: User, comment: str): + def add_log_entry(self, action, user, comment: str): """ Wraps adding of UserActionLogEntry to log Args: @@ -161,6 +161,7 @@ class BaseObject(BaseResource): Returns: """ + from user.models import UserActionLogEntry user_action = UserActionLogEntry.objects.create( user=user, action=action, @@ -229,7 +230,7 @@ class RecordableObjectMixin(models.Model): """ # Refers to "verzeichnen" recorded = models.OneToOneField( - UserActionLogEntry, + "user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, @@ -240,7 +241,7 @@ class RecordableObjectMixin(models.Model): class Meta: abstract = True - def set_unrecorded(self, user: User): + def set_unrecorded(self, user): """ Perform unrecording Args: @@ -249,6 +250,7 @@ class RecordableObjectMixin(models.Model): Returns: """ + from user.models import UserActionLogEntry if not self.recorded: return None action = UserActionLogEntry.get_unrecorded_action(user) @@ -262,7 +264,7 @@ class RecordableObjectMixin(models.Model): return action - def set_recorded(self, user: User): + def set_recorded(self, user): """ Perform recording Args: @@ -271,6 +273,7 @@ class RecordableObjectMixin(models.Model): Returns: """ + from user.models import UserActionLogEntry if self.recorded: return None action = UserActionLogEntry.get_recorded_action(user) @@ -284,7 +287,7 @@ class RecordableObjectMixin(models.Model): return action - def unrecord(self, performing_user: User, request: HttpRequest = None): + def unrecord(self, performing_user, request: HttpRequest = None): """ Unrecords a dataset Args: @@ -318,7 +321,7 @@ class RecordableObjectMixin(models.Model): class CheckableObjectMixin(models.Model): # Checks - Refers to "Genehmigen" but optional checked = models.OneToOneField( - UserActionLogEntry, + "user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, @@ -346,7 +349,7 @@ class CheckableObjectMixin(models.Model): self.save() return None - def set_checked(self, user: User) -> UserActionLogEntry: + def set_checked(self, user): """ Perform checking Args: @@ -355,6 +358,7 @@ class CheckableObjectMixin(models.Model): Returns: """ + from user.models import UserActionLogEntry if self.checked: # Nothing to do return @@ -373,7 +377,7 @@ class CheckableObjectMixin(models.Model): class ShareableObjectMixin(models.Model): # Users having access on this object - users = models.ManyToManyField(User, help_text="Users having access (data shared with)") + users = models.ManyToManyField("user.User", help_text="Users having access (data shared with)") access_token = models.CharField( max_length=255, null=True, @@ -420,7 +424,7 @@ class ShareableObjectMixin(models.Model): self.access_token = token self.save() - def is_shared_with(self, user: User): + def is_shared_with(self, user): """ Access check Checks whether a given user has access to this object @@ -433,7 +437,7 @@ class ShareableObjectMixin(models.Model): """ return self.users.filter(id=user.id) - def share_with(self, user: User): + def share_with(self, user): """ Adds user to list of shared access users Args: @@ -465,6 +469,7 @@ class ShareableObjectMixin(models.Model): Returns: """ + from user.models import User form_data = form.cleaned_data keep_accessing_users = form_data["users"] diff --git a/user/forms.py b/user/forms.py index fd66a91..1b509ba 100644 --- a/user/forms.py +++ b/user/forms.py @@ -5,17 +5,17 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 08.07.21 """ +from dal import autocomplete from django import forms -from django.db import IntegrityError +from django.db import IntegrityError, transaction from django.urls import reverse, reverse_lazy from django.utils.translation import gettext_lazy as _ from api.models import APIUserToken from intervention.inputs import GenerateInput -from user.models import User +from user.models import User, UserNotification, Team -from konova.forms import BaseForm, BaseModalForm -from user.models import UserNotification +from konova.forms import BaseForm, BaseModalForm, RemoveModalForm class UserNotificationForm(BaseForm): @@ -160,3 +160,105 @@ class UserAPITokenForm(BaseForm): user.api_token = new_token user.save() return new_token + + +class NewTeamModalForm(BaseModalForm): + name = forms.CharField( + label_suffix="", + label=_("Team name"), + max_length=500, + widget=forms.TextInput( + attrs={ + "placeholder": _("Team name"), + "class": "form-control", + } + ) + ) + description = forms.CharField( + label_suffix="", + label=_("Description"), + widget=forms.Textarea( + attrs={ + "rows": 5, + "class": "form-control" + } + ) + ) + members = forms.ModelMultipleChoiceField( + label=_("Manage team members"), + label_suffix="", + help_text=_("Multiple selection possible - You can only select users which are not already a team member. Enter the full username or e-mail."), + required=True, + queryset=User.objects.all(), + widget=autocomplete.ModelSelect2Multiple( + url="share-user-autocomplete", + attrs={ + "data-placeholder": _("Click for selection"), + "data-minimum-input-length": 3, + }, + ), + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.form_title = _("Create new team") + self.form_caption = _("You will become the administrator for this group by default. You do not need to add yourself to the list of members.") + self.action_url = reverse("user:team-new") + self.cancel_redirect = reverse("user:team-index") + + def save(self): + with transaction.atomic(): + team = Team.objects.create( + name=self.cleaned_data.get("name", None), + description=self.cleaned_data.get("description", None), + admin=self.user, + ) + members = self.cleaned_data.get("members", User.objects.none()) + if self.user.id not in members: + members = members.union( + User.objects.filter( + id=self.user.id + ) + ) + team.users.set(members) + return team + + +class EditTeamModalForm(NewTeamModalForm): + admin = forms.ModelChoiceField( + label_suffix="", + label=_("Admin"), + help_text=_("Administrators manage team details and members"), + queryset=User.objects.none(), + empty_label=None, + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.form_title = _("Edit team") + self.form_caption = None + self.action_url = reverse("user:team-edit", args=(self.instance.id,)) + self.cancel_redirect = reverse("user:team-index") + + members = self.instance.users.all() + self.fields["admin"].queryset = members + + form_data = { + "members": members, + "name": self.instance.name, + "description": self.instance.description, + "admin": self.instance.admin, + } + self.load_initial_data(form_data) + + def save(self): + with transaction.atomic(): + self.instance.name = self.cleaned_data.get("name", None) + self.instance.description = self.cleaned_data.get("description", None) + self.instance.save() + self.instance.users.set(self.cleaned_data.get("members", [])) + return self.instance + + +class RemoveTeamModalForm(RemoveModalForm): + pass diff --git a/user/models/__init__.py b/user/models/__init__.py index 7788d8e..06dbe73 100644 --- a/user/models/__init__.py +++ b/user/models/__init__.py @@ -5,6 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 15.11.21 """ -from .user_action import * -from .user import * -from .notification import * +from .user_action import UserActionLogEntry, UserAction +from .user import User +from .notification import UserNotification, UserNotificationEnum +from .team import Team diff --git a/user/models/team.py b/user/models/team.py new file mode 100644 index 0000000..c26af3c --- /dev/null +++ b/user/models/team.py @@ -0,0 +1,16 @@ +from django.db import models + +from konova.models import UuidModel + + +class Team(UuidModel): + """ Groups users in self managed teams. Can be used for multi-sharing of data + + """ + name = models.CharField(max_length=500, null=True, blank=True) + description = models.TextField(null=True, blank=True) + users = models.ManyToManyField("user.User", blank=True, related_name="teams") + admin = models.ForeignKey("user.User", blank=True, null=True, related_name="+", on_delete=models.SET_NULL) + + def __str__(self): + return self.name diff --git a/user/templates/user/index.html b/user/templates/user/index.html index 1193340..c31de94 100644 --- a/user/templates/user/index.html +++ b/user/templates/user/index.html @@ -62,6 +62,14 @@
+
diff --git a/user/templates/user/team/index.html b/user/templates/user/team/index.html new file mode 100644 index 0000000..c8ce113 --- /dev/null +++ b/user/templates/user/team/index.html @@ -0,0 +1,67 @@ +{% extends 'base.html' %} +{% load i18n fontawesome_5 %} + +{% block head %} + + {% comment %} + dal documentation (django-autocomplete-light) states using form.media for adding needed scripts. + This does not work properly with modal forms, as the scripts are not loaded properly inside the modal. + Therefore the script linkages from form.media have been extracted and put inside dal/scripts.html to ensure + these scripts are loaded when needed. + {% endcomment %} + {% include 'dal/scripts.html' %} +{% endblock %} + +{% block body %} +

{% trans 'Teams' %}

+
+ +
+
+ + + + + + + + + + + {% for team in teams %} + + + + + + + {% endfor %} + +
{% trans 'Name' %}{% trans 'Description' %}{% trans 'Members' %}{% trans 'Actions' %}
{{team.name}} +
+ {{team.description}} +
+
+ {% for member in team.users.all %} + {{member.username}} + {% endfor %} + + {% if team.admin == user %} + + + {% endif %} +
+
+ +{% with 'btn-modal' as btn_class %} + {% include 'modal/modal_form_script.html' %} +{% endwith %} + +{% endblock %} \ No newline at end of file diff --git a/user/urls.py b/user/urls.py index e312a63..76a8662 100644 --- a/user/urls.py +++ b/user/urls.py @@ -15,5 +15,9 @@ urlpatterns = [ path("notifications/", notifications_view, name="notifications"), path("token/api", api_token_view, name="api-token"), path("contact/", contact_view, name="contact"), + path("team/", index_team_view, name="team-index"), + path("team/new", new_team_view, name="team-new"), + path("team//edit", edit_team_view, name="team-edit"), + path("team//remove", remove_team_view, name="team-remove"), ] \ No newline at end of file diff --git a/user/views.py b/user/views.py index ee6aee5..afed572 100644 --- a/user/views.py +++ b/user/views.py @@ -1,17 +1,19 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required +from django.urls import reverse from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER from konova.utils.mailer import Mailer from konova.utils.message_templates import FORM_INVALID -from user.models import User -from django.http import HttpRequest +from user.models import User, Team +from django.http import HttpRequest, Http404 from django.shortcuts import render, redirect, get_object_or_404 from django.utils.translation import gettext_lazy as _ from konova.contexts import BaseContext from konova.decorators import any_group_check, default_group_required -from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm +from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm, NewTeamModalForm, EditTeamModalForm, \ + RemoveTeamModalForm @login_required @@ -128,4 +130,52 @@ def contact_view(request: HttpRequest, id: str): request, template, context - ) \ No newline at end of file + ) + + +@login_required +def index_team_view(request: HttpRequest): + template = "user/team/index.html" + user = request.user + context = { + "teams": user.teams.all(), + "tab_title": _("Teams"), + } + context = BaseContext(request, context).context + return render(request, template, context) + + +@login_required +def new_team_view(request: HttpRequest): + form = NewTeamModalForm(request.POST or None, request=request) + return form.process_request( + request, + _("New team added"), + redirect_url=reverse("user:team-index") + ) + + +@login_required +def edit_team_view(request: HttpRequest, id: str): + team = get_object_or_404(Team, id=id) + if request.user != team.admin: + raise Http404() + form = EditTeamModalForm(request.POST or None, instance=team, request=request) + return form.process_request( + request, + _("Team edited"), + redirect_url=reverse("user:team-index") + ) + + +@login_required +def remove_team_view(request: HttpRequest, id: str): + team = get_object_or_404(Team, id=id) + if request.user != team.admin: + raise Http404() + form = RemoveTeamModalForm(request.POST or None, instance=team, request=request) + return form.process_request( + request, + _("Team removed"), + redirect_url=reverse("user:team-index") + ) -- 2.38.5 From 9fec85b68804be115e8de66cd90da33e38a86db6 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Thu, 17 Feb 2022 13:44:32 +0100 Subject: [PATCH 19/26] #101 Team enhancements * visual enhancement for team index rendering * adds validity check for admin-membership of a team --- locale/de/LC_MESSAGES/django.mo | Bin 37936 -> 39382 bytes locale/de/LC_MESSAGES/django.po | 116 ++++++++++++++++++++++++---- user/forms.py | 20 ++++- user/templates/user/team/index.html | 88 ++++++++++----------- 4 files changed, 163 insertions(+), 61 deletions(-) diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index 022e9e1a728d186a0e8be95eee8ae29ea5928bee..ca93bc886faec14fea82f27aa2689b03373d5b42 100644 GIT binary patch delta 12271 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 delta 11048 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\n" "Language-Team: LANGUAGE \n" @@ -64,6 +64,7 @@ msgstr "Verantwortliche Stelle" #: compensation/forms/forms.py:165 intervention/forms/forms.py:64 #: intervention/forms/forms.py:81 intervention/forms/forms.py:97 #: intervention/forms/forms.py:113 intervention/forms/modalForms.py:49 +#: user/forms.py:196 msgid "Click for selection" msgstr "Auswählen..." @@ -751,7 +752,7 @@ msgstr "Menge" #: intervention/templates/intervention/detail/includes/documents.html:39 #: intervention/templates/intervention/detail/includes/payments.html:39 #: intervention/templates/intervention/detail/includes/revocation.html:43 -#: templates/log.html:10 +#: templates/log.html:10 user/templates/user/team/index.html:33 msgid "Action" msgstr "Aktionen" @@ -1150,12 +1151,12 @@ msgstr "Kompensation {} bearbeitet" msgid "Edit {}" msgstr "Bearbeite {}" -#: compensation/views/compensation.py:240 compensation/views/eco_account.py:349 +#: compensation/views/compensation.py:240 compensation/views/eco_account.py:351 #: ema/views.py:194 intervention/views.py:531 msgid "Log" msgstr "Log" -#: compensation/views/compensation.py:584 compensation/views/eco_account.py:716 +#: compensation/views/compensation.py:584 compensation/views/eco_account.py:719 #: ema/views.py:551 intervention/views.py:677 msgid "Report {}" msgstr "Bericht {}" @@ -1176,32 +1177,32 @@ msgstr "Ökokonto {} bearbeitet" msgid "Eco-account removed" msgstr "Ökokonto entfernt" -#: compensation/views/eco_account.py:370 ema/views.py:275 +#: compensation/views/eco_account.py:372 ema/views.py:275 #: intervention/views.py:630 msgid "{} unrecorded" msgstr "{} entzeichnet" -#: compensation/views/eco_account.py:370 ema/views.py:275 +#: compensation/views/eco_account.py:372 ema/views.py:275 #: intervention/views.py:630 msgid "{} recorded" msgstr "{} verzeichnet" -#: compensation/views/eco_account.py:789 ema/views.py:617 +#: compensation/views/eco_account.py:792 ema/views.py:617 #: intervention/views.py:428 msgid "{} has already been shared with you" msgstr "{} wurde bereits für Sie freigegeben" -#: compensation/views/eco_account.py:794 ema/views.py:622 +#: compensation/views/eco_account.py:797 ema/views.py:622 #: intervention/views.py:433 msgid "{} has been shared with you" msgstr "{} ist nun für Sie freigegeben" -#: compensation/views/eco_account.py:801 ema/views.py:629 +#: compensation/views/eco_account.py:804 ema/views.py:629 #: intervention/views.py:440 msgid "Share link invalid" msgstr "Freigabelink ungültig" -#: compensation/views/eco_account.py:824 ema/views.py:652 +#: compensation/views/eco_account.py:827 ema/views.py:652 #: intervention/views.py:463 msgid "Share settings updated" msgstr "Freigabe Einstellungen aktualisiert" @@ -2272,7 +2273,7 @@ msgstr "* sind Pflichtfelder." msgid "New entry" msgstr "Neuer Eintrag" -#: templates/generic_index.html:41 +#: templates/generic_index.html:41 user/templates/user/team/index.html:23 msgid "New" msgstr "Neu" @@ -2401,6 +2402,54 @@ msgstr "Neuen Token generieren" msgid "A new token needs to be validated by an administrator!" msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!" +#: user/forms.py:168 user/forms.py:172 +msgid "Team name" +msgstr "Team Name" + +#: user/forms.py:179 user/templates/user/team/index.html:31 +msgid "Description" +msgstr "Beschreibung" + +#: user/forms.py:188 +msgid "Manage team members" +msgstr "Mitglieder verwalten" + +#: user/forms.py:190 +msgid "" +"Multiple selection possible - You can only select users which are not " +"already a team member. Enter the full username or e-mail." +msgstr "" +"Mehrfachauswahl möglich - Sie können nur Nutzer wählen, die noch nicht " +"Mitglieder dieses Teams sind. Geben Sie den ganzen Nutzernamen an." + +#: user/forms.py:204 +msgid "Create new team" +msgstr "Neues Team anlegen" + +#: user/forms.py:205 +msgid "" +"You will become the administrator for this group by default. You do not need " +"to add yourself to the list of members." +msgstr "" +"Sie werden standardmäßig der Administrator dieses Teams. Sie müssen sich " +"selbst nicht zur Liste der Mitglieder hinzufügen." + +#: user/forms.py:230 +msgid "Admin" +msgstr "Administrator" + +#: user/forms.py:231 +msgid "Administrators manage team details and members" +msgstr "Administratoren verwalten die Teamdaten und Mitglieder" + +#: user/forms.py:244 +msgid "Selected admin ({}) needs to be a member of this team." +msgstr "Gewählter Administrator ({}) muss ein Mitglied des Teams sein." + +#: user/forms.py:256 user/templates/user/team/index.html:52 +msgid "Edit team" +msgstr "Team bearbeiten" + #: user/models/user_action.py:22 msgid "Unrecorded" msgstr "Entzeichnet" @@ -2417,7 +2466,7 @@ msgstr "Gelöscht" msgid "Show contact data" msgstr "Zeige Kontaktdaten" -#: user/templates/user/index.html:13 +#: user/templates/user/index.html:13 user/templates/user/team/index.html:30 msgid "Name" msgstr "" @@ -2462,6 +2511,27 @@ msgstr "API token einsehen oder neu generieren" msgid "API" msgstr "" +#: user/templates/user/index.html:66 +msgid "Manage teams" +msgstr "" + +#: user/templates/user/index.html:69 user/templates/user/team/index.html:19 +#: user/views.py:142 +msgid "Teams" +msgstr "" + +#: user/templates/user/team/index.html:21 +msgid "Add new team" +msgstr "Neues Team hinzufügen" + +#: user/templates/user/team/index.html:32 +msgid "Members" +msgstr "Mitglieder" + +#: user/templates/user/team/index.html:55 +msgid "Remove team" +msgstr "Team entfernen" + #: user/templates/user/token.html:6 msgid "API settings" msgstr "API Einstellungen" @@ -2486,26 +2556,38 @@ msgstr "Token noch nicht freigeschaltet" msgid "Valid until" msgstr "Läuft ab am" -#: user/views.py:31 +#: user/views.py:33 msgid "User settings" msgstr "Einstellungen" -#: user/views.py:57 +#: user/views.py:59 msgid "Notifications edited" msgstr "Benachrichtigungen bearbeitet" -#: user/views.py:69 +#: user/views.py:71 msgid "User notifications" msgstr "Benachrichtigungen" -#: user/views.py:92 +#: user/views.py:94 msgid "New token generated. Administrators need to validate." msgstr "Neuer Token generiert. Administratoren sind informiert." -#: user/views.py:103 +#: user/views.py:105 msgid "User API token" msgstr "API Nutzer Token" +#: user/views.py:153 +msgid "New team added" +msgstr "Neues Team hinzugefügt" + +#: user/views.py:166 +msgid "Team edited" +msgstr "Team bearbeitet" + +#: user/views.py:179 +msgid "Team removed" +msgstr "Team gelöscht" + #: venv/lib/python3.7/site-packages/bootstrap4/components.py:17 #: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/form_errors.html:3 #: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/messages.html:4 diff --git a/user/forms.py b/user/forms.py index 1b509ba..35c1e82 100644 --- a/user/forms.py +++ b/user/forms.py @@ -233,10 +233,27 @@ class EditTeamModalForm(NewTeamModalForm): empty_label=None, ) + def __is_admin_valid(self): + admin = self.cleaned_data.get("admin", None) + members = self.cleaned_data.get("members", None) + _is_valid = admin in members + + if not _is_valid: + self.add_error( + "members", + _("Selected admin ({}) needs to be a member of this team.").format(admin.username) + ) + + return _is_valid + + def is_valid(self): + super_valid = super().is_valid() + admin_valid = self.__is_admin_valid() + return super_valid and admin_valid + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.form_title = _("Edit team") - self.form_caption = None self.action_url = reverse("user:team-edit", args=(self.instance.id,)) self.cancel_redirect = reverse("user:team-index") @@ -255,6 +272,7 @@ class EditTeamModalForm(NewTeamModalForm): with transaction.atomic(): self.instance.name = self.cleaned_data.get("name", None) self.instance.description = self.cleaned_data.get("description", None) + self.instance.admin = self.cleaned_data.get("admin", None) self.instance.save() self.instance.users.set(self.cleaned_data.get("members", [])) return self.instance diff --git a/user/templates/user/team/index.html b/user/templates/user/team/index.html index c8ce113..d2040a3 100644 --- a/user/templates/user/team/index.html +++ b/user/templates/user/team/index.html @@ -13,51 +13,53 @@ {% endblock %} {% block body %} -

{% trans 'Teams' %}

-
- -
-
- - - - - - - - - - - {% for team in teams %} +
+

{% trans 'Teams' %}

+
+ +
+
+
{% trans 'Name' %}{% trans 'Description' %}{% trans 'Members' %}{% trans 'Actions' %}
+ - - - - + + + + - {% endfor %} - -
{{team.name}} -
- {{team.description}} -
-
- {% for member in team.users.all %} - {{member.username}} - {% endfor %} - - {% if team.admin == user %} - - - {% endif %} - {% trans 'Name' %}{% trans 'Description' %}{% trans 'Members' %}{% trans 'Action' %}
+ + + {% for team in teams %} + + {{team.name}} + +
+ {{team.description}} +
+ + + {% for member in team.users.all %} + {{member.username}} + {% endfor %} + + + {% if team.admin == user %} + + + {% endif %} + + + {% endfor %} + + +
{% with 'btn-modal' as btn_class %} -- 2.38.5 From 3878b5dbdb3144aae778cc8298f136ecabaa9745 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Thu, 17 Feb 2022 15:07:25 +0100 Subject: [PATCH 20/26] WIP: #101 Team sharing form * adds form for sharing via team --- intervention/forms/modalForms.py | 23 ++++++++++++++++++++++- konova/autocompletes.py | 22 +++++++++++++++++++--- konova/urls.py | 3 ++- user/migrations/0003_team.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 user/migrations/0003_team.py diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index 0b72ae8..978360d 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -10,7 +10,7 @@ from django.core.exceptions import ObjectDoesNotExist from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \ REVOCATION_EDITED -from user.models import User +from user.models import User, Team from user.models import UserActionLogEntry from django.db import transaction from django import forms @@ -37,6 +37,20 @@ class ShareModalForm(BaseModalForm): } ) ) + team_select = forms.ModelMultipleChoiceField( + label=_("Add team to share with"), + label_suffix="", + help_text=_("Multiple selection possible - You can only select teams which do not already have access."), + required=False, + queryset=Team.objects.all(), + widget=autocomplete.ModelSelect2Multiple( + url="share-team-autocomplete", + attrs={ + "data-placeholder": _("Click for selection"), + "data-minimum-input-length": 3, + }, + ), + ) user_select = forms.ModelMultipleChoiceField( label=_("Add user to share with"), label_suffix="", @@ -97,6 +111,13 @@ class ShareModalForm(BaseModalForm): self.disable_form_field("users") self._add_user_choices_to_field() + self._add_teams_to_field() + + def _add_teams_to_field(self): + form_data = { + "teams": [] + } + self.load_initial_data(form_data) def _add_user_choices_to_field(self): """ Transforms the instance's sharing users into a list for the form field diff --git a/konova/autocompletes.py b/konova/autocompletes.py index f1775b5..9b1be2d 100644 --- a/konova/autocompletes.py +++ b/konova/autocompletes.py @@ -11,7 +11,7 @@ from dal_select2.views import Select2QuerySetView, Select2GroupQuerySetView from django.core.exceptions import ImproperlyConfigured from konova.utils.message_templates import UNGROUPED -from user.models import User +from user.models import User, Team from django.db.models import Q from codelist.models import KonovaCode @@ -69,9 +69,8 @@ class InterventionAutocomplete(Select2QuerySetView): class ShareUserAutocomplete(Select2QuerySetView): - """ Autocomplete for intervention entries + """ Autocomplete for share with single users - Only returns entries that are accessible for the requesting user """ def get_queryset(self): @@ -93,6 +92,23 @@ class ShareUserAutocomplete(Select2QuerySetView): return qs +class ShareTeamAutocomplete(Select2QuerySetView): + """ Autocomplete for share with teams + + """ + def get_queryset(self): + if self.request.user.is_anonymous: + return Team.objects.none() + if self.q: + # Due to privacy concerns only a full username match will return the proper user entry + qs = Team.objects.filter( + Q(name__icontains=self.q) + ).order_by( + "name" + ) + return qs + + class KonovaCodeAutocomplete(Select2GroupQuerySetView): """ Provides simple autocomplete functionality for codes diff --git a/konova/urls.py b/konova/urls.py index 68256e7..1cd8851 100644 --- a/konova/urls.py +++ b/konova/urls.py @@ -20,7 +20,7 @@ from django.urls import path, include from konova.autocompletes import EcoAccountAutocomplete, \ InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \ RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete, ProcessTypeCodeAutocomplete, \ - ShareUserAutocomplete, BiotopeExtraCodeAutocomplete, CompensationActionDetailCodeAutocomplete + ShareUserAutocomplete, BiotopeExtraCodeAutocomplete, CompensationActionDetailCodeAutocomplete, ShareTeamAutocomplete 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 @@ -52,6 +52,7 @@ urlpatterns = [ path("atcmplt/codes/reg-off", RegistrationOfficeCodeAutocomplete.as_view(), name="codes-registration-office-autocomplete"), path("atcmplt/codes/cons-off", ConservationOfficeCodeAutocomplete.as_view(), name="codes-conservation-office-autocomplete"), path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"), + path("atcmplt/share/t", ShareTeamAutocomplete.as_view(), name="share-team-autocomplete"), ] if DEBUG: diff --git a/user/migrations/0003_team.py b/user/migrations/0003_team.py new file mode 100644 index 0000000..6f7ac90 --- /dev/null +++ b/user/migrations/0003_team.py @@ -0,0 +1,29 @@ +# Generated by Django 3.1.3 on 2022-02-17 10:22 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0002_user_api_token'), + ] + + operations = [ + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=500, null=True)), + ('description', models.TextField(blank=True, null=True)), + ('admin', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('users', models.ManyToManyField(blank=True, related_name='teams', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] -- 2.38.5 From 31f4369236aece7372028923255d8fbea95a1c90 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Fri, 18 Feb 2022 09:26:29 +0100 Subject: [PATCH 21/26] Missing migration * adds a migration which has not been checked in from another branch --- .../migrations/0005_auto_20220218_0917.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 compensation/migrations/0005_auto_20220218_0917.py diff --git a/compensation/migrations/0005_auto_20220218_0917.py b/compensation/migrations/0005_auto_20220218_0917.py new file mode 100644 index 0000000..43e7db9 --- /dev/null +++ b/compensation/migrations/0005_auto_20220218_0917.py @@ -0,0 +1,46 @@ +# Generated by Django 3.1.3 on 2022-02-18 08:17 + +from django.db import migrations, models, transaction +import django.db.models.deletion + +from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_AFTER_STATE_BIOTOPES__ID + + +def migrate_entries_974_to_654(apps, schema_editor): + CompensationState = apps.get_model("compensation", "CompensationState") + KonovaCode = apps.get_model("codelist", "KonovaCode") + all_states = CompensationState.objects.all() + + with transaction.atomic(): + for state in all_states: + code_from_654 = KonovaCode.objects.get( + short_name=state.biotope_type.short_name, + code_lists__in=[CODELIST_BIOTOPES_ID], + is_archived=False, + is_leaf=True, + ) + state.biotope_type = code_from_654 + state.save() + + old_list_states = CompensationState.objects.filter( + biotope_type__code_lists__in=[CODELIST_AFTER_STATE_BIOTOPES__ID] + ) + if old_list_states.count() > 0: + raise Exception("Still unmigrated values!") + + +class Migration(migrations.Migration): + + dependencies = [ + ('codelist', '0001_initial'), + ('compensation', '0004_auto_20220210_1402'), + ] + + operations = [ + migrations.RunPython(migrate_entries_974_to_654), + migrations.AlterField( + model_name='compensationstate', + name='biotope_type', + field=models.ForeignKey(blank=True, limit_choices_to={'code_lists__in': [654], 'is_archived': False, 'is_selectable': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='codelist.konovacode'), + ), + ] -- 2.38.5 From e152dfd4d70b08cdd983a42d102bcd54794693ad Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Fri, 18 Feb 2022 11:02:40 +0100 Subject: [PATCH 22/26] #101 Team sharing form * adds team sharing field to share form * splits sharing logic into user based and teams based * adds TeamAdmin for admin backend * adds validity check on Team name -> only unused names shall be valid --- api/tests/v1/create/test_api_create.py | 4 +- api/tests/v1/delete/test_api_delete.py | 10 +- api/tests/v1/get/test_api_get.py | 10 +- api/tests/v1/update/test_api_update.py | 12 +-- api/views/views.py | 2 +- compensation/forms/forms.py | 2 +- compensation/tests/compensation/test_views.py | 8 +- .../tests/compensation/test_workflow.py | 2 +- compensation/tests/ecoaccount/test_views.py | 8 +- .../tests/ecoaccount/test_workflow.py | 14 +-- compensation/tests/payment/test_views.py | 8 +- compensation/tests/payment/test_workflow.py | 2 +- compensation/views/eco_account.py | 2 +- ema/forms.py | 2 +- ema/tests/test_views.py | 8 +- ema/views.py | 2 +- intervention/forms/forms.py | 2 +- intervention/forms/modalForms.py | 2 +- intervention/tests/test_views.py | 12 +-- intervention/tests/test_workflow.py | 4 +- intervention/views.py | 2 +- konova/models/object.py | 56 ++++++++-- locale/de/LC_MESSAGES/django.mo | Bin 39382 -> 39754 bytes locale/de/LC_MESSAGES/django.po | 102 ++++++++++-------- user/admin.py | 15 ++- user/forms.py | 35 ++++++ 26 files changed, 213 insertions(+), 113 deletions(-) diff --git a/api/tests/v1/create/test_api_create.py b/api/tests/v1/create/test_api_create.py index 72ece97..51a82e7 100644 --- a/api/tests/v1/create/test_api_create.py +++ b/api/tests/v1/create/test_api_create.py @@ -109,8 +109,8 @@ class APIV1CreateTestCase(BaseAPIV1TestCase): Returns: """ - self.intervention.share_with(self.superuser) - self.eco_account.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) + self.eco_account.share_with_user(self.superuser) url = reverse("api:v1:deduction") json_file_path = "api/tests/v1/create/deduction_create_post_body.json" diff --git a/api/tests/v1/delete/test_api_delete.py b/api/tests/v1/delete/test_api_delete.py index cd016cf..350fdf2 100644 --- a/api/tests/v1/delete/test_api_delete.py +++ b/api/tests/v1/delete/test_api_delete.py @@ -57,7 +57,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase): """ test_intervention = self.create_dummy_intervention() - test_intervention.share_with(self.superuser) + test_intervention.share_with_user(self.superuser) url = reverse("api:v1:intervention", args=(str(test_intervention.id),)) self._test_delete_object(test_intervention, url) @@ -68,7 +68,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase): """ test_comp = self.create_dummy_compensation() - test_comp.share_with(self.superuser) + test_comp.share_with_user(self.superuser) url = reverse("api:v1:compensation", args=(str(test_comp.id),)) self._test_delete_object(test_comp, url) @@ -79,7 +79,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase): """ test_acc = self.create_dummy_eco_account() - test_acc.share_with(self.superuser) + test_acc.share_with_user(self.superuser) url = reverse("api:v1:ecoaccount", args=(str(test_acc.id),)) self._test_delete_object(test_acc, url) @@ -90,7 +90,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase): """ test_ema = self.create_dummy_ema() - test_ema.share_with(self.superuser) + test_ema.share_with_user(self.superuser) url = reverse("api:v1:ema", args=(str(test_ema.id),)) self._test_delete_object(test_ema, url) @@ -101,7 +101,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase): """ test_deduction = self.create_dummy_deduction() - test_deduction.intervention.share_with(self.superuser) + test_deduction.intervention.share_with_user(self.superuser) url = reverse("api:v1:deduction", args=(str(test_deduction.id),)) response = self._run_delete_request(url) diff --git a/api/tests/v1/get/test_api_get.py b/api/tests/v1/get/test_api_get.py index 3c46454..5bfd67a 100644 --- a/api/tests/v1/get/test_api_get.py +++ b/api/tests/v1/get/test_api_get.py @@ -64,7 +64,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase): Returns: """ - self.intervention.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) url = reverse("api:v1:intervention", args=(str(self.intervention.id),)) geojson = self._test_get_object(self.intervention, url) self._assert_geojson_format(geojson) @@ -91,7 +91,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase): Returns: """ - self.intervention.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) self.compensation.intervention = self.intervention self.compensation.save() @@ -119,7 +119,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase): Returns: """ - self.eco_account.share_with(self.superuser) + self.eco_account.share_with_user(self.superuser) url = reverse("api:v1:ecoaccount", args=(str(self.eco_account.id),)) geojson = self._test_get_object(self.eco_account, url) @@ -148,7 +148,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase): Returns: """ - self.ema.share_with(self.superuser) + self.ema.share_with_user(self.superuser) url = reverse("api:v1:ema", args=(str(self.ema.id),)) geojson = self._test_get_object(self.ema, url) @@ -172,7 +172,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase): Returns: """ - self.deduction.intervention.share_with(self.superuser) + self.deduction.intervention.share_with_user(self.superuser) url = reverse("api:v1:deduction", args=(str(self.deduction.id),)) _json = self._test_get_object(self.deduction, url) diff --git a/api/tests/v1/update/test_api_update.py b/api/tests/v1/update/test_api_update.py index e371188..8689fbe 100644 --- a/api/tests/v1/update/test_api_update.py +++ b/api/tests/v1/update/test_api_update.py @@ -52,7 +52,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase): Returns: """ - self.intervention.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) modified_on = self.intervention.modified url = reverse("api:v1:intervention", args=(str(self.intervention.id),)) json_file_path = "api/tests/v1/update/intervention_update_put_body.json" @@ -79,7 +79,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase): """ self.compensation.intervention = self.intervention self.compensation.save() - self.intervention.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) modified_on = self.compensation.modified url = reverse("api:v1:compensation", args=(str(self.compensation.id),)) @@ -108,7 +108,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase): Returns: """ - self.eco_account.share_with(self.superuser) + self.eco_account.share_with_user(self.superuser) modified_on = self.eco_account.modified url = reverse("api:v1:ecoaccount", args=(str(self.eco_account.id),)) @@ -139,7 +139,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase): Returns: """ - self.ema.share_with(self.superuser) + self.ema.share_with_user(self.superuser) modified_on = self.ema.modified url = reverse("api:v1:ema", args=(str(self.ema.id),)) @@ -168,8 +168,8 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase): Returns: """ - self.deduction.intervention.share_with(self.superuser) - self.deduction.account.share_with(self.superuser) + self.deduction.intervention.share_with_user(self.superuser) + self.deduction.account.share_with_user(self.superuser) url = reverse("api:v1:deduction", args=(str(self.deduction.id),)) json_file_path = "api/tests/v1/update/deduction_update_put_body.json" diff --git a/api/views/views.py b/api/views/views.py index 35c54fe..db8b8f9 100644 --- a/api/views/views.py +++ b/api/views/views.py @@ -292,7 +292,7 @@ class AbstractModelShareAPIView(AbstractAPIView): id__in=obj.shared_users ) new_users_objs = obj.shared_users.union(new_users_to_be_added) - obj.share_with_list(new_users_objs) + obj.share_with_user_list(new_users_objs) return True diff --git a/compensation/forms/forms.py b/compensation/forms/forms.py index 72b1a71..46b235f 100644 --- a/compensation/forms/forms.py +++ b/compensation/forms/forms.py @@ -400,7 +400,7 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix comment=comment, legal=legal ) - acc.share_with(user) + acc.share_with_user(user) # Add the log entry to the main objects log list acc.log.add(action) diff --git a/compensation/tests/compensation/test_views.py b/compensation/tests/compensation/test_views.py index 27218f2..1174895 100644 --- a/compensation/tests/compensation/test_views.py +++ b/compensation/tests/compensation/test_views.py @@ -103,7 +103,7 @@ class CompensationViewTestCase(BaseViewTestCase): client = Client() client.login(username=self.superuser.username, password=self.superuser_pw) self.superuser.groups.set([]) - self.intervention.share_with_list([self.superuser]) + self.intervention.share_with_user_list([self.superuser]) # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference # to a user without access, since the important permissions are missing @@ -143,7 +143,7 @@ class CompensationViewTestCase(BaseViewTestCase): client.login(username=self.superuser.username, password=self.superuser_pw) self.superuser.groups.set([]) # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state - self.intervention.share_with_list([]) + self.intervention.share_with_user_list([]) # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference # to a user having shared access, since all important permissions are missing @@ -185,7 +185,7 @@ class CompensationViewTestCase(BaseViewTestCase): group = self.groups.get(name=DEFAULT_GROUP) self.superuser.groups.set([group]) # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state - self.intervention.share_with_list([self.superuser]) + self.intervention.share_with_user_list([self.superuser]) success_urls = [ self.index_url, @@ -221,7 +221,7 @@ class CompensationViewTestCase(BaseViewTestCase): group = self.groups.get(name=DEFAULT_GROUP) self.superuser.groups.set([group]) # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state - self.intervention.share_with_list([]) + self.intervention.share_with_user_list([]) success_urls = [ self.index_url, diff --git a/compensation/tests/compensation/test_workflow.py b/compensation/tests/compensation/test_workflow.py index 7b73be8..5b7decf 100644 --- a/compensation/tests/compensation/test_workflow.py +++ b/compensation/tests/compensation/test_workflow.py @@ -25,7 +25,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase): super().setUp() # Give the user shared access to the dummy intervention -> inherits the access to the compensation - self.intervention.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) # Make sure the intervention itself would be fine with valid data self.intervention = self.fill_out_intervention(self.intervention) diff --git a/compensation/tests/ecoaccount/test_views.py b/compensation/tests/ecoaccount/test_views.py index 617f743..aaa7a4c 100644 --- a/compensation/tests/ecoaccount/test_views.py +++ b/compensation/tests/ecoaccount/test_views.py @@ -78,7 +78,7 @@ class EcoAccountViewTestCase(CompensationViewTestCase): client = Client() client.login(username=self.superuser.username, password=self.superuser_pw) self.superuser.groups.set([]) - self.eco_account.share_with_list([self.superuser]) + self.eco_account.share_with_user_list([self.superuser]) # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference # to a user without access, since the important permissions are missing @@ -119,7 +119,7 @@ class EcoAccountViewTestCase(CompensationViewTestCase): client = Client() client.login(username=self.superuser.username, password=self.superuser_pw) self.superuser.groups.set([]) - self.eco_account.share_with_list([]) + self.eco_account.share_with_user_list([]) # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference # to a user having shared access, since all important permissions are missing @@ -163,7 +163,7 @@ class EcoAccountViewTestCase(CompensationViewTestCase): group = self.groups.get(name=DEFAULT_GROUP) self.superuser.groups.set([group]) # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state - self.eco_account.share_with_list([self.superuser]) + self.eco_account.share_with_user_list([self.superuser]) success_urls = [ self.index_url, @@ -200,7 +200,7 @@ class EcoAccountViewTestCase(CompensationViewTestCase): client.login(username=self.superuser.username, password=self.superuser_pw) group = self.groups.get(name=DEFAULT_GROUP) self.superuser.groups.set([group]) - self.eco_account.share_with_list([]) + self.eco_account.share_with_user_list([]) success_urls = [ self.index_url, diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py index afc4111..1b350e9 100644 --- a/compensation/tests/ecoaccount/test_workflow.py +++ b/compensation/tests/ecoaccount/test_workflow.py @@ -27,7 +27,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): # Add user to conservation office group and give shared access to the account self.superuser.groups.add(self.groups.get(name=DEFAULT_GROUP)) self.superuser.groups.add(self.groups.get(name=ETS_GROUP)) - self.eco_account.share_with_list([self.superuser]) + self.eco_account.share_with_user_list([self.superuser]) def test_new(self): """ Test the creation of an EcoAccount @@ -73,7 +73,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): Returns: """ - self.eco_account.share_with(self.superuser) + self.eco_account.share_with_user(self.superuser) url = reverse("compensation:acc:edit", args=(self.eco_account.id,)) pre_edit_log_count = self.eco_account.log.count() @@ -129,7 +129,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): """ # Add proper privilege for the user - self.eco_account.share_with(self.superuser) + self.eco_account.share_with_user(self.superuser) pre_record_log_count = self.eco_account.log.count() # Prepare url and form data @@ -178,7 +178,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): """ # Give user shared access to the dummy intervention, which will be needed here - self.intervention.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) pre_deduction_acc_log_count = self.eco_account.log.count() pre_deduction_int_log_count = self.intervention.log.count() @@ -231,7 +231,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): def test_edit_deduction(self): test_surface = self.eco_account.get_available_rest()[0] self.eco_account.set_recorded(self.superuser) - self.intervention.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) self.eco_account.refresh_from_db() self.assertIn(self.superuser, self.intervention.is_shared_with(self.superuser)) @@ -281,8 +281,8 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): "confirm": True, } - intervention.share_with(self.superuser) - account.share_with(self.superuser) + intervention.share_with_user(self.superuser) + account.share_with_user(self.superuser) pre_edit_intervention_log_count = intervention.log.count() pre_edit_account_log_count = account.log.count() diff --git a/compensation/tests/payment/test_views.py b/compensation/tests/payment/test_views.py index b1eca5a..30ffa00 100644 --- a/compensation/tests/payment/test_views.py +++ b/compensation/tests/payment/test_views.py @@ -64,7 +64,7 @@ class PaymentViewTestCase(BaseViewTestCase): client = Client() client.login(username=self.superuser.username, password=self.superuser_pw) self.superuser.groups.set([]) - self.intervention.share_with_list([self.superuser]) + self.intervention.share_with_user_list([self.superuser]) # Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference # to a user without access, since the important permissions are missing @@ -91,7 +91,7 @@ class PaymentViewTestCase(BaseViewTestCase): client.login(username=self.superuser.username, password=self.superuser_pw) self.superuser.groups.set([]) # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state - self.intervention.share_with_list([]) + self.intervention.share_with_user_list([]) # Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference # to a user having shared access, since all important permissions are missing @@ -120,7 +120,7 @@ class PaymentViewTestCase(BaseViewTestCase): group = self.groups.get(name=DEFAULT_GROUP) self.superuser.groups.set([group]) # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state - self.intervention.share_with_list([self.superuser]) + self.intervention.share_with_user_list([self.superuser]) success_urls = [ self.new_url, @@ -143,7 +143,7 @@ class PaymentViewTestCase(BaseViewTestCase): group = self.groups.get(name=DEFAULT_GROUP) self.superuser.groups.set([group]) # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state - self.intervention.share_with_list([]) + self.intervention.share_with_user_list([]) success_urls = [ ] diff --git a/compensation/tests/payment/test_workflow.py b/compensation/tests/payment/test_workflow.py index 790fb61..81259b8 100644 --- a/compensation/tests/payment/test_workflow.py +++ b/compensation/tests/payment/test_workflow.py @@ -21,7 +21,7 @@ class PaymentWorkflowTestCase(BaseWorkflowTestCase): def setUp(self) -> None: super().setUp() # Give the user shared access to the dummy intervention - self.intervention.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) self.payment = Payment.objects.get_or_create( intervention=self.intervention, diff --git a/compensation/views/eco_account.py b/compensation/views/eco_account.py index d291218..85b1371 100644 --- a/compensation/views/eco_account.py +++ b/compensation/views/eco_account.py @@ -796,7 +796,7 @@ def share_view(request: HttpRequest, id: str, token: str): request, _("{} has been shared with you").format(obj.identifier) ) - obj.share_with(user) + obj.share_with_user(user) return redirect("compensation:acc:detail", id=id) else: messages.error( diff --git a/ema/forms.py b/ema/forms.py index 2f19360..8e6faab 100644 --- a/ema/forms.py +++ b/ema/forms.py @@ -80,7 +80,7 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin): ) # Add the creating user to the list of shared users - acc.share_with(user) + acc.share_with_user(user) # Add the log entry to the main objects log list acc.log.add(action) diff --git a/ema/tests/test_views.py b/ema/tests/test_views.py index 9654e8f..b2c23e1 100644 --- a/ema/tests/test_views.py +++ b/ema/tests/test_views.py @@ -110,7 +110,7 @@ class EmaViewTestCase(CompensationViewTestCase): # Sharing does not have any effect in here, since the default group will prohibit further functionality access # to this user - self.ema.share_with_list([self.superuser]) + self.ema.share_with_user_list([self.superuser]) success_urls = [ self.index_url, @@ -160,7 +160,7 @@ class EmaViewTestCase(CompensationViewTestCase): # Sharing does not have any effect in here, since the default group will prohibit further functionality access # to this user - self.ema.share_with_list([]) + self.ema.share_with_user_list([]) success_urls = [ self.index_url, @@ -203,7 +203,7 @@ class EmaViewTestCase(CompensationViewTestCase): groups = self.groups.filter(Q(name=ETS_GROUP)|Q(name=DEFAULT_GROUP)) self.superuser.groups.set(groups) # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state - self.ema.share_with_list([self.superuser]) + self.ema.share_with_user_list([self.superuser]) success_urls = [ self.index_url, @@ -243,7 +243,7 @@ class EmaViewTestCase(CompensationViewTestCase): groups = self.groups.filter(Q(name=ETS_GROUP)|Q(name=DEFAULT_GROUP)) self.superuser.groups.set(groups) # Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state - self.ema.share_with_list([]) + self.ema.share_with_user_list([]) success_urls = [ self.index_url, diff --git a/ema/views.py b/ema/views.py index e9d0acb..c145511 100644 --- a/ema/views.py +++ b/ema/views.py @@ -621,7 +621,7 @@ def share_view(request: HttpRequest, id: str, token: str): request, _("{} has been shared with you").format(obj.identifier) ) - obj.share_with(user) + obj.share_with_user(user) return redirect("ema:detail", id=id) else: messages.error( diff --git a/intervention/forms/forms.py b/intervention/forms/forms.py index 1c5cf1c..94ff970 100644 --- a/intervention/forms/forms.py +++ b/intervention/forms/forms.py @@ -253,7 +253,7 @@ class NewInterventionForm(BaseForm): intervention.log.add(action) # Add the performing user as the first user having access to the data - intervention.share_with(user) + intervention.share_with_user(user) return intervention diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index 978360d..8e44ac2 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -115,7 +115,7 @@ class ShareModalForm(BaseModalForm): def _add_teams_to_field(self): form_data = { - "teams": [] + "team_select": self.instance.teams.all() } self.load_initial_data(form_data) diff --git a/intervention/tests/test_views.py b/intervention/tests/test_views.py index f12ee7a..a049f3e 100644 --- a/intervention/tests/test_views.py +++ b/intervention/tests/test_views.py @@ -144,7 +144,7 @@ class InterventionViewTestCase(BaseViewTestCase): # Add user to default group default_group = Group.objects.get(name=DEFAULT_GROUP) self.superuser.groups.set([default_group]) - self.intervention.share_with_list([self.superuser]) + self.intervention.share_with_user_list([self.superuser]) success_urls = [ self.index_url, @@ -190,7 +190,7 @@ class InterventionViewTestCase(BaseViewTestCase): # Add user to default group default_group = Group.objects.get(name=DEFAULT_GROUP) self.superuser.groups.set([default_group]) - self.intervention.share_with_list([]) + self.intervention.share_with_user_list([]) success_urls = [ self.index_url, @@ -236,7 +236,7 @@ class InterventionViewTestCase(BaseViewTestCase): # Add user to zb group zb_group = self.groups.get(name=ZB_GROUP) self.superuser.groups.set([zb_group]) - self.intervention.share_with_list([self.superuser]) + self.intervention.share_with_user_list([self.superuser]) success_urls = [ self.index_url, @@ -282,7 +282,7 @@ class InterventionViewTestCase(BaseViewTestCase): # Add user to zb group zb_group = self.groups.get(name=ZB_GROUP) self.superuser.groups.set([zb_group]) - self.intervention.share_with_list([]) + self.intervention.share_with_user_list([]) success_urls = [ self.index_url, @@ -328,7 +328,7 @@ class InterventionViewTestCase(BaseViewTestCase): # Add user to ets group ets_group = Group.objects.get(name=ETS_GROUP) self.superuser.groups.set([ets_group]) - self.intervention.share_with_list([self.superuser]) + self.intervention.share_with_user_list([self.superuser]) success_urls = [ self.index_url, @@ -374,7 +374,7 @@ class InterventionViewTestCase(BaseViewTestCase): # Add user to default group ets_group = Group.objects.get(name=ETS_GROUP) self.superuser.groups.set([ets_group]) - self.intervention.share_with_list([]) + self.intervention.share_with_user_list([]) success_urls = [ self.index_url, diff --git a/intervention/tests/test_workflow.py b/intervention/tests/test_workflow.py index 1a66245..c529050 100644 --- a/intervention/tests/test_workflow.py +++ b/intervention/tests/test_workflow.py @@ -30,7 +30,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): super().setUp() # Recreate a new (bare minimum) intervention before each test self.intervention = self.create_dummy_intervention() - self.intervention.share_with(self.superuser) + self.intervention.share_with_user(self.superuser) def test_new(self): """ @@ -365,7 +365,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): if self.eco_account.recorded is None: rec_action = UserActionLogEntry.get_recorded_action(self.superuser) self.eco_account.recorded = rec_action - self.eco_account.share_with_list([self.superuser]) + self.eco_account.share_with_user_list([self.superuser]) self.eco_account.save() num_all_deducs = EcoAccountDeduction.objects.count() diff --git a/intervention/views.py b/intervention/views.py index 00440bb..3004a79 100644 --- a/intervention/views.py +++ b/intervention/views.py @@ -432,7 +432,7 @@ def share_view(request: HttpRequest, id: str, token: str): request, _("{} has been shared with you").format(intervention.identifier) ) - intervention.share_with(user) + intervention.share_with_user(user) return redirect("intervention:detail", id=id) else: messages.error( diff --git a/konova/models/object.py b/konova/models/object.py index 69a0a2e..e6c27a6 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -378,6 +378,7 @@ class CheckableObjectMixin(models.Model): class ShareableObjectMixin(models.Model): # Users having access on this object users = models.ManyToManyField("user.User", help_text="Users having access (data shared with)") + teams = models.ManyToManyField("user.Team", help_text="Teams having access (data shared with)") access_token = models.CharField( max_length=255, null=True, @@ -435,9 +436,36 @@ class ShareableObjectMixin(models.Model): Returns: """ - return self.users.filter(id=user.id) + directly_shared = self.users.filter(id=user.id).exists() + team_shared = self.teams.filter( + users__in=[user] + ).exists() + is_shared = directly_shared or team_shared + return is_shared - def share_with(self, user): + def share_with_team(self, team): + """ Adds team to list of shared access teans + + Args: + team (Team): The team to be added to the object + + Returns: + + """ + self.teams.add(team) + + def share_with_team_list(self, team_list: list): + """ Sets the list of shared access teams + + Args: + team_list (list): The teams to be added to the object + + Returns: + + """ + self.teams.set(team_list) + + def share_with_user(self, user): """ Adds user to list of shared access users Args: @@ -449,7 +477,7 @@ class ShareableObjectMixin(models.Model): if not self.is_shared_with(user): self.users.add(user) - def share_with_list(self, user_list: list): + def share_with_user_list(self, user_list: list): """ Sets the list of shared access users Args: @@ -472,24 +500,32 @@ class ShareableObjectMixin(models.Model): from user.models import User form_data = form.cleaned_data - keep_accessing_users = form_data["users"] + # Fetch selected teams and find out which user IDs are in removed teams -> mails need to be sent + accessing_teams = form_data["team_select"] + removed_team_users = self.teams.all().exclude( + id__in=accessing_teams + ).values_list("users__id", flat=True) + new_accessing_users = list(form_data["user_select"].values_list("id", flat=True)) + keep_accessing_users = form_data["users"] accessing_users = keep_accessing_users + new_accessing_users users = User.objects.filter( id__in=accessing_users ) removed_users = self.users.all().exclude( id__in=accessing_users - ).values("id") + ).values_list("id", flat=True) + removed_users = removed_users.union(removed_team_users) # Send mails - for user in removed_users: - celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user["id"]) - for user in new_accessing_users: - celery_send_mail_shared_access_given.delay(self.identifier, self.title, user) + for user_id in removed_users: + celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user_id) + for user_id in new_accessing_users: + celery_send_mail_shared_access_given.delay(self.identifier, self.title, user_id) # Set new shared users - self.share_with_list(users) + self.share_with_user_list(users) + self.share_with_team_list(accessing_teams) @property def shared_users(self) -> QuerySet: diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index ca93bc886faec14fea82f27aa2689b03373d5b42..a20ac2daaaa222c759f4d6a6e75bd9e7c9424ee3 100644 GIT binary patch delta 11420 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 U@_a+OM&>omhzQ8boV-8!f8%^NWB>pF diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index d1da918..aba7981 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ #: compensation/forms/modalForms.py:47 compensation/forms/modalForms.py:63 #: compensation/forms/modalForms.py:356 compensation/forms/modalForms.py:463 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156 -#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:127 -#: intervention/forms/modalForms.py:140 intervention/forms/modalForms.py:153 +#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:148 +#: intervention/forms/modalForms.py:161 intervention/forms/modalForms.py:174 #: konova/filters/mixins.py:53 konova/filters/mixins.py:54 #: konova/filters/mixins.py:81 konova/filters/mixins.py:82 #: konova/filters/mixins.py:94 konova/filters/mixins.py:95 @@ -26,7 +26,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-17 13:42+0100\n" +"POT-Creation-Date: 2022-02-18 09:35+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -64,7 +64,7 @@ msgstr "Verantwortliche Stelle" #: compensation/forms/forms.py:165 intervention/forms/forms.py:64 #: intervention/forms/forms.py:81 intervention/forms/forms.py:97 #: intervention/forms/forms.py:113 intervention/forms/modalForms.py:49 -#: user/forms.py:196 +#: intervention/forms/modalForms.py:63 user/forms.py:196 msgid "Click for selection" msgstr "Auswählen..." @@ -221,7 +221,7 @@ msgstr "Abbuchungen" #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:36 #: ema/templates/ema/detail/includes/states-after.html:36 #: ema/templates/ema/detail/includes/states-before.html:36 -#: intervention/forms/modalForms.py:338 +#: intervention/forms/modalForms.py:359 msgid "Surface" msgstr "Fläche" @@ -284,8 +284,8 @@ msgid "Type" msgstr "Typ" #: analysis/templates/analysis/reports/includes/old_data/amount.html:24 -#: compensation/tables.py:89 intervention/forms/modalForms.py:349 -#: intervention/forms/modalForms.py:356 intervention/tables.py:88 +#: compensation/tables.py:89 intervention/forms/modalForms.py:370 +#: intervention/forms/modalForms.py:377 intervention/tables.py:88 #: intervention/templates/intervention/detail/view.html:19 #: konova/templates/konova/includes/quickstart/interventions.html:4 #: templates/navbars/navbar.html:22 @@ -295,7 +295,7 @@ msgstr "Eingriff" #: analysis/templates/analysis/reports/includes/old_data/amount.html:34 #: compensation/tables.py:266 #: compensation/templates/compensation/detail/eco_account/view.html:20 -#: intervention/forms/modalForms.py:322 intervention/forms/modalForms.py:329 +#: intervention/forms/modalForms.py:343 intervention/forms/modalForms.py:350 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4 #: templates/navbars/navbar.html:34 msgid "Eco-account" @@ -364,7 +364,7 @@ msgstr "Kompensation XY; Flur ABC" #: ema/templates/ema/detail/includes/actions.html:34 #: ema/templates/ema/detail/includes/deadlines.html:34 #: ema/templates/ema/detail/includes/documents.html:34 -#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:152 +#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:173 #: intervention/templates/intervention/detail/includes/documents.html:34 #: intervention/templates/intervention/detail/includes/payments.html:34 #: intervention/templates/intervention/detail/includes/revocation.html:38 @@ -484,7 +484,7 @@ msgid "Due on which date" msgstr "Zahlung wird an diesem Datum erwartet" #: compensation/forms/modalForms.py:64 compensation/forms/modalForms.py:357 -#: intervention/forms/modalForms.py:154 konova/forms.py:395 +#: intervention/forms/modalForms.py:175 konova/forms.py:395 msgid "Additional comment, maximum {} letters" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" @@ -512,7 +512,7 @@ msgstr "Zusatzbezeichnung" msgid "Select an additional biotope type" msgstr "Zusatzbezeichnung wählen" -#: compensation/forms/modalForms.py:197 intervention/forms/modalForms.py:340 +#: compensation/forms/modalForms.py:197 intervention/forms/modalForms.py:361 msgid "in m²" msgstr "" @@ -540,7 +540,7 @@ msgstr "Fristart wählen" #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31 #: ema/templates/ema/detail/includes/deadlines.html:31 -#: intervention/forms/modalForms.py:126 +#: intervention/forms/modalForms.py:147 msgid "Date" msgstr "Datum" @@ -752,7 +752,7 @@ msgstr "Menge" #: intervention/templates/intervention/detail/includes/documents.html:39 #: intervention/templates/intervention/detail/includes/payments.html:39 #: intervention/templates/intervention/detail/includes/revocation.html:43 -#: templates/log.html:10 user/templates/user/team/index.html:33 +#: templates/log.html:10 user/templates/user/team/index.html:32 msgid "Action" msgstr "Aktionen" @@ -1000,14 +1000,14 @@ msgstr "Zuletzt bearbeitet" #: compensation/templates/compensation/detail/compensation/view.html:100 #: compensation/templates/compensation/detail/eco_account/view.html:83 -#: ema/templates/ema/detail/view.html:76 intervention/forms/modalForms.py:56 +#: ema/templates/ema/detail/view.html:76 intervention/forms/modalForms.py:70 #: intervention/templates/intervention/detail/view.html:116 msgid "Shared with" msgstr "Freigegeben für" #: compensation/templates/compensation/detail/eco_account/includes/controls.html:15 #: ema/templates/ema/detail/includes/controls.html:15 -#: intervention/forms/modalForms.py:70 +#: intervention/forms/modalForms.py:84 #: intervention/templates/intervention/detail/includes/controls.html:15 msgid "Share" msgstr "Freigabe" @@ -1325,10 +1325,22 @@ msgid "Send this link to users who you want to have writing access on the data" msgstr "Andere Nutzer erhalten über diesen Link Zugriff auf die Daten" #: intervention/forms/modalForms.py:41 +msgid "Add team to share with" +msgstr "Team hinzufügen" + +#: intervention/forms/modalForms.py:43 +msgid "" +"Multiple selection possible - You can only select teams which do not already " +"have access." +msgstr "" +"Mehrfachauswahl möglich - Sie können nur Teams wählen, für die der Eintrag " +"noch nicht freigegeben wurde." + +#: intervention/forms/modalForms.py:55 msgid "Add user to share with" msgstr "Nutzer direkt hinzufügen" -#: intervention/forms/modalForms.py:43 +#: intervention/forms/modalForms.py:57 msgid "" "Multiple selection possible - You can only select users which do not already " "have access. Enter the full username." @@ -1336,46 +1348,46 @@ msgstr "" "Mehrfachauswahl möglich - Sie können nur Nutzer wählen, für die der Eintrag " "noch nicht freigegeben wurde. Geben Sie den ganzen Nutzernamen an." -#: intervention/forms/modalForms.py:59 +#: intervention/forms/modalForms.py:73 msgid "Remove check to remove access for this user" msgstr "Wählen Sie die Nutzer ab, die keinen Zugriff mehr haben sollen" -#: intervention/forms/modalForms.py:71 +#: intervention/forms/modalForms.py:85 msgid "Share settings for {}" msgstr "Freigabe Einstellungen für {}" -#: intervention/forms/modalForms.py:128 +#: intervention/forms/modalForms.py:149 msgid "Date of revocation" msgstr "Datum des Widerspruchs" -#: intervention/forms/modalForms.py:139 +#: intervention/forms/modalForms.py:160 #: intervention/templates/intervention/detail/includes/revocation.html:35 msgid "Document" msgstr "Dokument" -#: intervention/forms/modalForms.py:142 +#: intervention/forms/modalForms.py:163 msgid "Must be smaller than 15 Mb" msgstr "Muss kleiner als 15 Mb sein" -#: intervention/forms/modalForms.py:167 +#: intervention/forms/modalForms.py:188 #: intervention/templates/intervention/detail/includes/revocation.html:18 msgid "Add revocation" msgstr "Widerspruch hinzufügen" -#: intervention/forms/modalForms.py:224 +#: intervention/forms/modalForms.py:245 msgid "Checked intervention data" msgstr "Eingriffsdaten geprüft" -#: intervention/forms/modalForms.py:230 +#: intervention/forms/modalForms.py:251 msgid "Checked compensations data and payments" msgstr "Kompensationen und Zahlungen geprüft" -#: intervention/forms/modalForms.py:239 +#: intervention/forms/modalForms.py:260 #: intervention/templates/intervention/detail/includes/controls.html:19 msgid "Run check" msgstr "Prüfung vornehmen" -#: intervention/forms/modalForms.py:240 konova/forms.py:514 +#: intervention/forms/modalForms.py:261 konova/forms.py:514 msgid "" "I, {} {}, confirm that all necessary control steps have been performed by " "myself." @@ -1383,23 +1395,23 @@ msgstr "" "Ich, {} {}, bestätige, dass die notwendigen Kontrollschritte durchgeführt " "wurden:" -#: intervention/forms/modalForms.py:324 +#: intervention/forms/modalForms.py:345 msgid "Only recorded accounts can be selected for deductions" msgstr "Nur verzeichnete Ökokonten können für Abbuchungen verwendet werden." -#: intervention/forms/modalForms.py:351 +#: intervention/forms/modalForms.py:372 msgid "Only shared interventions can be selected" msgstr "Nur freigegebene Eingriffe können gewählt werden" -#: intervention/forms/modalForms.py:364 +#: intervention/forms/modalForms.py:385 msgid "New Deduction" msgstr "Neue Abbuchung" -#: intervention/forms/modalForms.py:365 +#: intervention/forms/modalForms.py:386 msgid "Enter the information for a new deduction from a chosen eco-account" msgstr "Geben Sie die Informationen für eine neue Abbuchung ein." -#: intervention/forms/modalForms.py:408 +#: intervention/forms/modalForms.py:429 msgid "" "Eco-account {} is not recorded yet. You can only deduct from recorded " "accounts." @@ -1407,7 +1419,7 @@ msgstr "" "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von " "verzeichneten Ökokonten erfolgen." -#: intervention/forms/modalForms.py:418 +#: intervention/forms/modalForms.py:439 msgid "" "The account {} has not enough surface for a deduction of {} m². There are " "only {} m² left" @@ -2273,7 +2285,7 @@ msgstr "* sind Pflichtfelder." msgid "New entry" msgstr "Neuer Eintrag" -#: templates/generic_index.html:41 user/templates/user/team/index.html:23 +#: templates/generic_index.html:41 user/templates/user/team/index.html:22 msgid "New" msgstr "Neu" @@ -2406,7 +2418,7 @@ msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!" msgid "Team name" msgstr "Team Name" -#: user/forms.py:179 user/templates/user/team/index.html:31 +#: user/forms.py:179 user/templates/user/team/index.html:30 msgid "Description" msgstr "Beschreibung" @@ -2434,19 +2446,23 @@ msgstr "" "Sie werden standardmäßig der Administrator dieses Teams. Sie müssen sich " "selbst nicht zur Liste der Mitglieder hinzufügen." -#: user/forms.py:230 +#: user/forms.py:218 user/forms.py:279 +msgid "Name already taken. Try another." +msgstr "Name bereits vergeben. Probieren Sie einen anderen." + +#: user/forms.py:249 msgid "Admin" msgstr "Administrator" -#: user/forms.py:231 +#: user/forms.py:250 msgid "Administrators manage team details and members" msgstr "Administratoren verwalten die Teamdaten und Mitglieder" -#: user/forms.py:244 +#: user/forms.py:263 msgid "Selected admin ({}) needs to be a member of this team." msgstr "Gewählter Administrator ({}) muss ein Mitglied des Teams sein." -#: user/forms.py:256 user/templates/user/team/index.html:52 +#: user/forms.py:291 user/templates/user/team/index.html:51 msgid "Edit team" msgstr "Team bearbeiten" @@ -2466,7 +2482,7 @@ msgstr "Gelöscht" msgid "Show contact data" msgstr "Zeige Kontaktdaten" -#: user/templates/user/index.html:13 user/templates/user/team/index.html:30 +#: user/templates/user/index.html:13 user/templates/user/team/index.html:29 msgid "Name" msgstr "" @@ -2515,20 +2531,20 @@ msgstr "" msgid "Manage teams" msgstr "" -#: user/templates/user/index.html:69 user/templates/user/team/index.html:19 +#: user/templates/user/index.html:69 user/templates/user/team/index.html:18 #: user/views.py:142 msgid "Teams" msgstr "" -#: user/templates/user/team/index.html:21 +#: user/templates/user/team/index.html:20 msgid "Add new team" msgstr "Neues Team hinzufügen" -#: user/templates/user/team/index.html:32 +#: user/templates/user/team/index.html:31 msgid "Members" msgstr "Mitglieder" -#: user/templates/user/team/index.html:55 +#: user/templates/user/team/index.html:54 msgid "Remove team" msgstr "Team entfernen" diff --git a/user/admin.py b/user/admin.py index 3e60861..1aeacee 100644 --- a/user/admin.py +++ b/user/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from user.models import UserNotification, UserActionLogEntry, User +from user.models import UserNotification, UserActionLogEntry, User, Team class UserNotificationAdmin(admin.ModelAdmin): @@ -64,7 +64,20 @@ class UserActionLogEntryAdmin(admin.ModelAdmin): ] +class TeamAdmin(admin.ModelAdmin): + list_display = [ + "name", + "description", + "admin", + ] + search_fields = [ + "name", + "description", + ] + + admin.site.register(User, UserAdmin) +admin.site.register(Team, TeamAdmin) # Outcommented for a cleaner admin backend on production #admin.site.register(UserNotification, UserNotificationAdmin) diff --git a/user/forms.py b/user/forms.py index 35c1e82..01ec54b 100644 --- a/user/forms.py +++ b/user/forms.py @@ -206,6 +206,25 @@ class NewTeamModalForm(BaseModalForm): self.action_url = reverse("user:team-new") self.cancel_redirect = reverse("user:team-index") + def _is_name_valid(self): + name = self.cleaned_data.get("name", None) + teams_with_same_name = Team.objects.filter( + name=name + ) + name_valid = not teams_with_same_name.exists() + if not name_valid: + self.add_error( + "name", + _("Name already taken. Try another.") + ) + + return name_valid + + def is_valid(self): + super_valid = super().is_valid() + name_valid = self._is_name_valid() + return super_valid and name_valid + def save(self): with transaction.atomic(): team = Team.objects.create( @@ -246,6 +265,22 @@ class EditTeamModalForm(NewTeamModalForm): return _is_valid + def _is_name_valid(self): + name = self.cleaned_data.get("name", None) + teams_with_same_name = Team.objects.filter( + name=name + ).exclude( + id=self.instance.id + ) + name_valid = not teams_with_same_name.exists() + if not name_valid: + self.add_error( + "name", + _("Name already taken. Try another.") + ) + + return name_valid + def is_valid(self): super_valid = super().is_valid() admin_valid = self.__is_admin_valid() -- 2.38.5 From edcf7b3c7832942f5f7fafc6f2acef4d96a4ea46 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Fri, 18 Feb 2022 13:52:27 +0100 Subject: [PATCH 23/26] #101 Team sharing tests * adds tests for team sharing * extends the API for team sharing support * adds shared_teams property shortcut for ShareableObjectMixin * adds full support for team-based sharing to all views and functions * simplifies ShareModalForm * adds/updates translations --- api/tests/v1/get/test_api_get.py | 20 ++++ .../intervention_share_update_put_body.json | 8 ++ api/tests/v1/update/test_api_update.py | 21 ++++ api/views/views.py | 48 ++++++++- compensation/filters.py | 23 +---- compensation/models/compensation.py | 40 +++++++- .../tests/ecoaccount/test_workflow.py | 2 +- intervention/forms/modalForms.py | 92 +++++++++--------- konova/autocompletes.py | 19 ++-- konova/filters/mixins.py | 5 +- konova/models/object.py | 34 +++++-- konova/tests/test_autocompletes.py | 1 + konova/tests/test_views.py | 21 +++- konova/utils/message_templates.py | 8 +- locale/de/LC_MESSAGES/django.mo | Bin 39754 -> 39816 bytes locale/de/LC_MESSAGES/django.po | 69 +++++++------ 16 files changed, 278 insertions(+), 133 deletions(-) create mode 100644 api/tests/v1/update/intervention_share_update_put_body.json diff --git a/api/tests/v1/get/test_api_get.py b/api/tests/v1/get/test_api_get.py index 5bfd67a..953b0f6 100644 --- a/api/tests/v1/get/test_api_get.py +++ b/api/tests/v1/get/test_api_get.py @@ -85,6 +85,26 @@ class APIV1GetTestCase(BaseAPIV1TestCase): except KeyError as e: self.fail(e) + def test_get_shared(self): + """ Tests api GET on shared info of the intervention + + Returns: + + """ + self.intervention.share_with_user(self.superuser) + self.intervention.share_with_team(self.team) + url = reverse("api:v1:intervention-share", args=(str(self.intervention.id),)) + response = self._run_get_request(url) + content = json.loads(response.content) + self.assertIn("users", content) + self.assertIn(self.superuser.username, content["users"]) + self.assertEqual(1, len(content["users"])) + self.assertIn("teams", content) + self.assertEqual(1, len(content["teams"])) + for team in content["teams"]: + self.assertEqual(team["id"], str(self.team.id)) + self.assertEqual(team["name"], self.team.name) + def test_get_compensation(self): """ Tests api GET diff --git a/api/tests/v1/update/intervention_share_update_put_body.json b/api/tests/v1/update/intervention_share_update_put_body.json new file mode 100644 index 0000000..c160aee --- /dev/null +++ b/api/tests/v1/update/intervention_share_update_put_body.json @@ -0,0 +1,8 @@ +{ + "users": [ + "CHANGE_ME" + ], + "teams": [ + "CHANGE_ME" + ] +} \ No newline at end of file diff --git a/api/tests/v1/update/test_api_update.py b/api/tests/v1/update/test_api_update.py index 8689fbe..500aec2 100644 --- a/api/tests/v1/update/test_api_update.py +++ b/api/tests/v1/update/test_api_update.py @@ -184,3 +184,24 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase): self.assertEqual(put_body["intervention"], str(self.deduction.intervention.id)) self.assertEqual(put_body["eco_account"], str(self.deduction.account.id)) self.assertEqual(put_body["surface"], self.deduction.surface) + + def test_update_share_intervention(self): + self.intervention.share_with_user(self.superuser) + url = reverse("api:v1:intervention-share", args=(str(self.intervention.id),)) + json_file_path = "api/tests/v1/update/intervention_share_update_put_body.json" + with open(json_file_path) as json_file: + put_body = json.load(fp=json_file) + put_body["users"] = [self.user.username] + put_body["teams"] = [self.team.name] + + self.assertFalse(self.intervention.is_shared_with(self.user)) + self.assertEqual(0, self.intervention.shared_teams.count()) + + response = self._run_update_request(url, put_body) + self.assertEqual(response.status_code, 200, msg=response.content) + self.intervention.refresh_from_db() + + self.assertEqual(1, self.intervention.shared_teams.count()) + self.assertEqual(2, self.intervention.shared_users.count()) + self.assertEqual(self.team.name, self.intervention.shared_teams.first().name) + self.assertTrue(self.intervention.is_shared_with(self.user)) diff --git a/api/views/views.py b/api/views/views.py index db8b8f9..75d764e 100644 --- a/api/views/views.py +++ b/api/views/views.py @@ -19,7 +19,7 @@ from ema.models import Ema from intervention.models import Intervention from konova.utils.message_templates import DATA_UNSHARED from konova.utils.user_checks import is_default_group_only -from user.models import User +from user.models import User, Team class AbstractAPIView(View): @@ -198,13 +198,21 @@ class AbstractModelShareAPIView(AbstractAPIView): """ try: users = self._get_shared_users_of_object(id) + teams = self._get_shared_teams_of_object(id) except Exception as e: return self._return_error_response(e) data = { "users": [ user.username for user in users - ] + ], + "teams": [ + { + "id": team.id, + "name": team.name, + } + for team in teams + ], } return JsonResponse(data) @@ -258,6 +266,22 @@ class AbstractModelShareAPIView(AbstractAPIView): users = obj.shared_users return users + def _get_shared_teams_of_object(self, id) -> QuerySet: + """ Check permissions and get the teams + + Args: + id (str): The object's id + + Returns: + users (QuerySet) + """ + obj = self.model.objects.get( + id=id + ) + self._check_user_has_shared_access(obj) + teams = obj.shared_teams + return teams + def _process_put_body(self, body: bytes, id: str): """ Reads the body data, performs validity checks and sets the new users @@ -271,19 +295,26 @@ class AbstractModelShareAPIView(AbstractAPIView): obj = self.model.objects.get(id=id) self._check_user_has_shared_access(obj) - new_users = json.loads(body.decode("utf-8")) - new_users = new_users.get("users", []) + content = json.loads(body.decode("utf-8")) + new_users = content.get("users", []) if len(new_users) == 0: raise ValueError("Shared user list must not be empty!") + new_teams = content.get("teams", []) # Eliminate duplicates new_users = list(dict.fromkeys(new_users)) + new_teams = list(dict.fromkeys(new_teams)) # Make sure each of these names exist as a user new_users_objs = [] for user in new_users: new_users_objs.append(User.objects.get(username=user)) + # Make sure each of these names exist as a user + new_teams_objs = [] + for team_name in new_teams: + new_teams_objs.append(Team.objects.get(name=team_name)) + if is_default_group_only(self.user): # Default only users are not allowed to remove other users from having access. They can only add new ones! new_users_to_be_added = User.objects.filter( @@ -292,7 +323,16 @@ class AbstractModelShareAPIView(AbstractAPIView): id__in=obj.shared_users ) new_users_objs = obj.shared_users.union(new_users_to_be_added) + + new_teams_to_be_added = Team.objects.filter( + name__in=new_teams + ).exclude( + id__in=obj.shared_teams + ) + new_teams_objs = obj.shared_teams.union(new_teams_to_be_added) + obj.share_with_user_list(new_users_objs) + obj.share_with_team_list(new_teams_objs) return True diff --git a/compensation/filters.py b/compensation/filters.py index d028e3c..b637709 100644 --- a/compensation/filters.py +++ b/compensation/filters.py @@ -59,8 +59,9 @@ class CheckboxCompensationTableFilter(CheckboxTableFilter): """ if not value: return queryset.filter( - intervention__users__in=[self.user], # requesting user has access - ) + Q(intervention__users__in=[self.user]) | # requesting user has access + Q(intervention__teams__users__in=[self.user]) + ).distinct() else: return queryset @@ -127,24 +128,6 @@ class CheckboxEcoAccountTableFilter(CheckboxTableFilter): ) ) - def filter_show_all(self, queryset, name, value) -> QuerySet: - """ Filters queryset depending on value of 'show_all' setting - - Args: - queryset (): - name (): - value (): - - Returns: - - """ - if not value: - return queryset.filter( - users__in=[self.user], # requesting user has access - ) - else: - return queryset - def filter_only_show_unrecorded(self, queryset, name, value) -> QuerySet: """ Filters queryset depending on value of 'show_recorded' setting diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py index 7a10aa0..ea69cef 100644 --- a/compensation/models/compensation.py +++ b/compensation/models/compensation.py @@ -8,7 +8,7 @@ Created on: 16.11.21 import shutil from django.contrib import messages -from user.models import User +from user.models import User, Team from django.db import models, transaction from django.db.models import QuerySet, Sum from django.http import HttpRequest @@ -299,7 +299,7 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin): # Compensations inherit their shared state from the interventions return self.intervention.is_shared_with(user) - def share_with(self, user: User): + def share_with_user(self, user: User): """ Adds user to list of shared access users Args: @@ -308,10 +308,9 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin): Returns: """ - if not self.intervention.is_shared_with(user): - self.intervention.users.add(user) + self.intervention.users.add(user) - def share_with_list(self, user_list: list): + def share_with_user_list(self, user_list: list): """ Sets the list of shared access users Args: @@ -322,6 +321,28 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin): """ self.intervention.users.set(user_list) + def share_with_team(self, team: Team): + """ Adds team to list of shared access teams + + Args: + team (Team): The team to be added to the object + + Returns: + + """ + self.intervention.teams.add(team) + + def share_with_team_list(self, team_list: list): + """ Sets the list of shared access teams + + Args: + team_list (list): The teams to be added to the object + + Returns: + + """ + self.intervention.teams.set(team_list) + @property def shared_users(self) -> QuerySet: """ Shortcut for fetching the users which have shared access on this object @@ -331,6 +352,15 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin): """ return self.intervention.users.all() + @property + def shared_teams(self) -> QuerySet: + """ Shortcut for fetching the teams which have shared access on this object + + Returns: + users (QuerySet) + """ + return self.intervention.teams.all() + def get_documents(self) -> QuerySet: """ Getter for all documents of a compensation diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py index 1b350e9..03b4996 100644 --- a/compensation/tests/ecoaccount/test_workflow.py +++ b/compensation/tests/ecoaccount/test_workflow.py @@ -233,7 +233,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase): self.eco_account.set_recorded(self.superuser) self.intervention.share_with_user(self.superuser) self.eco_account.refresh_from_db() - self.assertIn(self.superuser, self.intervention.is_shared_with(self.superuser)) + self.assertTrue(self.superuser, self.intervention.is_shared_with(self.superuser)) deduction = EcoAccountDeduction.objects.create( intervention=self.intervention, diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index 8e44ac2..3947938 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -9,7 +9,7 @@ from dal import autocomplete from django.core.exceptions import ObjectDoesNotExist from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \ - REVOCATION_EDITED + REVOCATION_EDITED, ENTRY_REMOVE_MISSING_PERMISSION from user.models import User, Team from user.models import UserActionLogEntry from django.db import transaction @@ -37,7 +37,7 @@ class ShareModalForm(BaseModalForm): } ) ) - team_select = forms.ModelMultipleChoiceField( + teams = forms.ModelMultipleChoiceField( label=_("Add team to share with"), label_suffix="", help_text=_("Multiple selection possible - You can only select teams which do not already have access."), @@ -51,7 +51,7 @@ class ShareModalForm(BaseModalForm): }, ), ) - user_select = forms.ModelMultipleChoiceField( + users = forms.ModelMultipleChoiceField( label=_("Add user to share with"), label_suffix="", help_text=_("Multiple selection possible - You can only select users which do not already have access. Enter the full username."), @@ -63,21 +63,8 @@ class ShareModalForm(BaseModalForm): "data-placeholder": _("Click for selection"), "data-minimum-input-length": 3, }, - forward=["users"] ), ) - users = forms.MultipleChoiceField( - label=_("Shared with"), - label_suffix="", - required=True, - help_text=_("Remove check to remove access for this user"), - widget=forms.CheckboxSelectMultiple( - attrs={ - "class": "list-unstyled", - } - ), - choices=[] - ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -91,6 +78,48 @@ class ShareModalForm(BaseModalForm): self._init_fields() + def _user_team_valid(self): + """ Checks whether users and teams have been removed by the user and if the user is allowed to do so or not + + Returns: + + """ + users = self.cleaned_data.get("users", User.objects.none()) + teams = self.cleaned_data.get("teams", Team.objects.none()) + + _is_valid = True + if is_default_group_only(self.user): + shared_users = self.instance.shared_users + shared_teams = self.instance.shared_teams + + shared_users_are_removed = not set(shared_users).issubset(users) + shared_teams_are_removed = not set(shared_teams).issubset(teams) + + if shared_users_are_removed: + self.add_error( + "users", + ENTRY_REMOVE_MISSING_PERMISSION + ) + _is_valid = False + if shared_teams_are_removed: + self.add_error( + "teams", + ENTRY_REMOVE_MISSING_PERMISSION + ) + _is_valid = False + return _is_valid + + def is_valid(self): + """ Extended validity check + + Returns: + + """ + super_valid = super().is_valid() + user_team_valid = self._user_team_valid() + _is_valid = super_valid and user_team_valid + return _is_valid + def _init_fields(self): """ Wraps initializing of fields @@ -105,39 +134,12 @@ class ShareModalForm(BaseModalForm): self.share_link ) - # Initialize users field - # Disable field if user is not in registration or conservation group - if is_default_group_only(self.request.user): - self.disable_form_field("users") - - self._add_user_choices_to_field() - self._add_teams_to_field() - - def _add_teams_to_field(self): form_data = { - "team_select": self.instance.teams.all() + "teams": self.instance.teams.all(), + "users": self.instance.users.all(), } self.load_initial_data(form_data) - def _add_user_choices_to_field(self): - """ Transforms the instance's sharing users into a list for the form field - - Returns: - - """ - users = self.instance.users.all() - choices = [] - for n in users: - choices.append( - (n.id, n.username) - ) - self.fields["users"].choices = choices - u_ids = list(users.values_list("id", flat=True)) - self.initialize_form_field( - "users", - u_ids - ) - def save(self): self.instance.update_sharing_user(self) diff --git a/konova/autocompletes.py b/konova/autocompletes.py index 9b1be2d..5ecc50d 100644 --- a/konova/autocompletes.py +++ b/konova/autocompletes.py @@ -76,19 +76,14 @@ class ShareUserAutocomplete(Select2QuerySetView): def get_queryset(self): if self.request.user.is_anonymous: return User.objects.none() - exclude_user_ids = self.forwarded.get("users", []) - _exclude = {"id__in": exclude_user_ids} - qs = User.objects.all().exclude( - **_exclude - ).order_by( - "username" - ) + qs = User.objects.all() if self.q: # Due to privacy concerns only a full username match will return the proper user entry qs = qs.filter( Q(username=self.q) | Q(email=self.q) ).distinct() + qs = qs.order_by("username") return qs @@ -99,13 +94,15 @@ class ShareTeamAutocomplete(Select2QuerySetView): def get_queryset(self): if self.request.user.is_anonymous: return Team.objects.none() + qs = Team.objects.all() if self.q: # Due to privacy concerns only a full username match will return the proper user entry - qs = Team.objects.filter( - Q(name__icontains=self.q) - ).order_by( - "name" + qs = qs.filter( + name__icontains=self.q ) + qs = qs.order_by( + "name" + ) return qs diff --git a/konova/filters/mixins.py b/konova/filters/mixins.py index 5625ff3..5a07e3c 100644 --- a/konova/filters/mixins.py +++ b/konova/filters/mixins.py @@ -297,8 +297,9 @@ class ShareableTableFilterMixin(django_filters.FilterSet): """ if not value: return queryset.filter( - users__in=[self.user], # requesting user has access - ) + Q(users__in=[self.user]) | # requesting user has access + Q(teams__users__in=[self.user]) + ).distinct() else: return queryset diff --git a/konova/models/object.py b/konova/models/object.py index e6c27a6..92b3cca 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -501,30 +501,37 @@ class ShareableObjectMixin(models.Model): form_data = form.cleaned_data # Fetch selected teams and find out which user IDs are in removed teams -> mails need to be sent - accessing_teams = form_data["team_select"] + accessing_teams = form_data["teams"] removed_team_users = self.teams.all().exclude( id__in=accessing_teams ).values_list("users__id", flat=True) - - new_accessing_users = list(form_data["user_select"].values_list("id", flat=True)) - keep_accessing_users = form_data["users"] - accessing_users = keep_accessing_users + new_accessing_users - users = User.objects.filter( - id__in=accessing_users + accessing_team_users = User.objects.filter( + id__in=accessing_teams.values_list("users", flat=True) ) + new_team_users = accessing_team_users.exclude( + id__in=self.shared_users + ).values_list("id", flat=True) + + # Fetch selected users + accessing_users = form_data["users"] removed_users = self.users.all().exclude( id__in=accessing_users ).values_list("id", flat=True) + new_users = accessing_users.exclude( + id__in=self.shared_users + ).values_list("id", flat=True) + + new_users = new_users.union(new_team_users) removed_users = removed_users.union(removed_team_users) # Send mails for user_id in removed_users: celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user_id) - for user_id in new_accessing_users: + for user_id in new_users: celery_send_mail_shared_access_given.delay(self.identifier, self.title, user_id) # Set new shared users - self.share_with_user_list(users) + self.share_with_user_list(accessing_users) self.share_with_team_list(accessing_teams) @property @@ -536,6 +543,15 @@ class ShareableObjectMixin(models.Model): """ return self.users.all() + @property + def shared_teams(self) -> QuerySet: + """ Shortcut for fetching the teams which have shared access on this object + + Returns: + teams (QuerySet) + """ + return self.teams.all() + @abstractmethod def get_share_url(self): """ Returns the share url for the object diff --git a/konova/tests/test_autocompletes.py b/konova/tests/test_autocompletes.py index 7d9e508..95a3508 100644 --- a/konova/tests/test_autocompletes.py +++ b/konova/tests/test_autocompletes.py @@ -71,6 +71,7 @@ class AutocompleteTestCase(BaseTestCase): "codes-registration-office-autocomplete", "codes-conservation-office-autocomplete", "share-user-autocomplete", + "share-team-autocomplete", ] for test in tests: self.client.login(username=self.superuser.username, password=self.superuser_pw) diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py index 6218a6c..ff99ea3 100644 --- a/konova/tests/test_views.py +++ b/konova/tests/test_views.py @@ -9,7 +9,7 @@ import datetime from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID from ema.models import Ema -from user.models import User +from user.models import User, Team from django.contrib.auth.models import Group from django.contrib.gis.geos import MultiPolygon, Polygon from django.core.exceptions import ObjectDoesNotExist @@ -65,6 +65,7 @@ class BaseTestCase(TestCase): self.create_dummy_states() self.create_dummy_action() self.codes = self.create_dummy_codes() + self.team = self.create_dummy_team() # Set the default group as only group for the user default_group = self.groups.get(name=DEFAULT_GROUP) @@ -251,6 +252,24 @@ class BaseTestCase(TestCase): ]) return codes + def create_dummy_team(self): + """ Creates a dummy team + + Returns: + + """ + if self.superuser is None: + self.create_users() + + team = Team.objects.get_or_create( + name="Testteam", + description="Testdescription", + admin=self.superuser, + )[0] + team.users.add(self.superuser) + + return team + @staticmethod def create_dummy_geometry() -> MultiPolygon: """ Creates some geometry diff --git a/konova/utils/message_templates.py b/konova/utils/message_templates.py index 5809b3b..6c0c614 100644 --- a/konova/utils/message_templates.py +++ b/konova/utils/message_templates.py @@ -12,12 +12,14 @@ FORM_INVALID = _("There was an error on this form.") PARAMS_INVALID = _("Invalid parameters") INTERVENTION_INVALID = _("There are errors in this intervention.") IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since another entry has been added in the meanwhile, which uses this identifier") -DATA_UNSHARED = _("This data is not shared with you") -DATA_UNSHARED_EXPLANATION = _("Remember: This data has not been shared with you, yet. This means you can only read but can not edit or perform any actions like running a check or recording.") +ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.") MISSING_GROUP_PERMISSION = _("You need to be part of another user group.") - CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted") +# SHARE +DATA_UNSHARED = _("This data is not shared with you") +DATA_UNSHARED_EXPLANATION = _("Remember: This data has not been shared with you, yet. This means you can only read but can not edit or perform any actions like running a check or recording.") + # FILES FILE_TYPE_UNSUPPORTED = _("Unsupported file type") FILE_SIZE_TOO_LARGE = _("File too large") diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index a20ac2daaaa222c759f4d6a6e75bd9e7c9424ee3..cbd6f0b09ac2c43691e3e31e27cb6e98f171d3df 100644 GIT binary patch delta 6607 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 delta 6562 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 diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index aba7981..bb96bab 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ #: compensation/forms/modalForms.py:47 compensation/forms/modalForms.py:63 #: compensation/forms/modalForms.py:356 compensation/forms/modalForms.py:463 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156 -#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:148 -#: intervention/forms/modalForms.py:161 intervention/forms/modalForms.py:174 +#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:154 +#: intervention/forms/modalForms.py:167 intervention/forms/modalForms.py:180 #: konova/filters/mixins.py:53 konova/filters/mixins.py:54 #: konova/filters/mixins.py:81 konova/filters/mixins.py:82 #: konova/filters/mixins.py:94 konova/filters/mixins.py:95 @@ -26,7 +26,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-18 09:35+0100\n" +"POT-Creation-Date: 2022-02-18 12:35+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -221,7 +221,7 @@ msgstr "Abbuchungen" #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:36 #: ema/templates/ema/detail/includes/states-after.html:36 #: ema/templates/ema/detail/includes/states-before.html:36 -#: intervention/forms/modalForms.py:359 +#: intervention/forms/modalForms.py:365 msgid "Surface" msgstr "Fläche" @@ -284,8 +284,8 @@ msgid "Type" msgstr "Typ" #: analysis/templates/analysis/reports/includes/old_data/amount.html:24 -#: compensation/tables.py:89 intervention/forms/modalForms.py:370 -#: intervention/forms/modalForms.py:377 intervention/tables.py:88 +#: compensation/tables.py:89 intervention/forms/modalForms.py:376 +#: intervention/forms/modalForms.py:383 intervention/tables.py:88 #: intervention/templates/intervention/detail/view.html:19 #: konova/templates/konova/includes/quickstart/interventions.html:4 #: templates/navbars/navbar.html:22 @@ -295,7 +295,7 @@ msgstr "Eingriff" #: analysis/templates/analysis/reports/includes/old_data/amount.html:34 #: compensation/tables.py:266 #: compensation/templates/compensation/detail/eco_account/view.html:20 -#: intervention/forms/modalForms.py:343 intervention/forms/modalForms.py:350 +#: intervention/forms/modalForms.py:349 intervention/forms/modalForms.py:356 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4 #: templates/navbars/navbar.html:34 msgid "Eco-account" @@ -364,7 +364,7 @@ msgstr "Kompensation XY; Flur ABC" #: ema/templates/ema/detail/includes/actions.html:34 #: ema/templates/ema/detail/includes/deadlines.html:34 #: ema/templates/ema/detail/includes/documents.html:34 -#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:173 +#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:179 #: intervention/templates/intervention/detail/includes/documents.html:34 #: intervention/templates/intervention/detail/includes/payments.html:34 #: intervention/templates/intervention/detail/includes/revocation.html:38 @@ -484,7 +484,7 @@ msgid "Due on which date" msgstr "Zahlung wird an diesem Datum erwartet" #: compensation/forms/modalForms.py:64 compensation/forms/modalForms.py:357 -#: intervention/forms/modalForms.py:175 konova/forms.py:395 +#: intervention/forms/modalForms.py:181 konova/forms.py:395 msgid "Additional comment, maximum {} letters" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" @@ -512,7 +512,7 @@ msgstr "Zusatzbezeichnung" msgid "Select an additional biotope type" msgstr "Zusatzbezeichnung wählen" -#: compensation/forms/modalForms.py:197 intervention/forms/modalForms.py:361 +#: compensation/forms/modalForms.py:197 intervention/forms/modalForms.py:367 msgid "in m²" msgstr "" @@ -540,7 +540,7 @@ msgstr "Fristart wählen" #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31 #: ema/templates/ema/detail/includes/deadlines.html:31 -#: intervention/forms/modalForms.py:147 +#: intervention/forms/modalForms.py:153 msgid "Date" msgstr "Datum" @@ -1000,14 +1000,14 @@ msgstr "Zuletzt bearbeitet" #: compensation/templates/compensation/detail/compensation/view.html:100 #: compensation/templates/compensation/detail/eco_account/view.html:83 -#: ema/templates/ema/detail/view.html:76 intervention/forms/modalForms.py:70 +#: ema/templates/ema/detail/view.html:76 #: intervention/templates/intervention/detail/view.html:116 msgid "Shared with" msgstr "Freigegeben für" #: compensation/templates/compensation/detail/eco_account/includes/controls.html:15 #: ema/templates/ema/detail/includes/controls.html:15 -#: intervention/forms/modalForms.py:84 +#: intervention/forms/modalForms.py:71 #: intervention/templates/intervention/detail/includes/controls.html:15 msgid "Share" msgstr "Freigabe" @@ -1348,46 +1348,48 @@ msgstr "" "Mehrfachauswahl möglich - Sie können nur Nutzer wählen, für die der Eintrag " "noch nicht freigegeben wurde. Geben Sie den ganzen Nutzernamen an." -#: intervention/forms/modalForms.py:73 -msgid "Remove check to remove access for this user" -msgstr "Wählen Sie die Nutzer ab, die keinen Zugriff mehr haben sollen" - -#: intervention/forms/modalForms.py:85 +#: intervention/forms/modalForms.py:72 msgid "Share settings for {}" msgstr "Freigabe Einstellungen für {}" -#: intervention/forms/modalForms.py:149 +#: intervention/forms/modalForms.py:105 intervention/forms/modalForms.py:111 +msgid "" +"Only conservation or registration office users are allowed to remove entries." +msgstr "" +"Nur Mitarbeiter der Naturschutz- oder Zulassungsbehördengruppe dürfen Einträge entfernen" + +#: intervention/forms/modalForms.py:155 msgid "Date of revocation" msgstr "Datum des Widerspruchs" -#: intervention/forms/modalForms.py:160 +#: intervention/forms/modalForms.py:166 #: intervention/templates/intervention/detail/includes/revocation.html:35 msgid "Document" msgstr "Dokument" -#: intervention/forms/modalForms.py:163 +#: intervention/forms/modalForms.py:169 msgid "Must be smaller than 15 Mb" msgstr "Muss kleiner als 15 Mb sein" -#: intervention/forms/modalForms.py:188 +#: intervention/forms/modalForms.py:194 #: intervention/templates/intervention/detail/includes/revocation.html:18 msgid "Add revocation" msgstr "Widerspruch hinzufügen" -#: intervention/forms/modalForms.py:245 +#: intervention/forms/modalForms.py:251 msgid "Checked intervention data" msgstr "Eingriffsdaten geprüft" -#: intervention/forms/modalForms.py:251 +#: intervention/forms/modalForms.py:257 msgid "Checked compensations data and payments" msgstr "Kompensationen und Zahlungen geprüft" -#: intervention/forms/modalForms.py:260 +#: intervention/forms/modalForms.py:266 #: intervention/templates/intervention/detail/includes/controls.html:19 msgid "Run check" msgstr "Prüfung vornehmen" -#: intervention/forms/modalForms.py:261 konova/forms.py:514 +#: intervention/forms/modalForms.py:267 konova/forms.py:514 msgid "" "I, {} {}, confirm that all necessary control steps have been performed by " "myself." @@ -1395,23 +1397,23 @@ msgstr "" "Ich, {} {}, bestätige, dass die notwendigen Kontrollschritte durchgeführt " "wurden:" -#: intervention/forms/modalForms.py:345 +#: intervention/forms/modalForms.py:351 msgid "Only recorded accounts can be selected for deductions" msgstr "Nur verzeichnete Ökokonten können für Abbuchungen verwendet werden." -#: intervention/forms/modalForms.py:372 +#: intervention/forms/modalForms.py:378 msgid "Only shared interventions can be selected" msgstr "Nur freigegebene Eingriffe können gewählt werden" -#: intervention/forms/modalForms.py:385 +#: intervention/forms/modalForms.py:391 msgid "New Deduction" msgstr "Neue Abbuchung" -#: intervention/forms/modalForms.py:386 +#: intervention/forms/modalForms.py:392 msgid "Enter the information for a new deduction from a chosen eco-account" msgstr "Geben Sie die Informationen für eine neue Abbuchung ein." -#: intervention/forms/modalForms.py:429 +#: intervention/forms/modalForms.py:435 msgid "" "Eco-account {} is not recorded yet. You can only deduct from recorded " "accounts." @@ -1419,7 +1421,7 @@ msgstr "" "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von " "verzeichneten Ökokonten erfolgen." -#: intervention/forms/modalForms.py:439 +#: intervention/forms/modalForms.py:445 msgid "" "The account {} has not enough surface for a deduction of {} m². There are " "only {} m² left" @@ -4107,6 +4109,9 @@ msgstr "" msgid "Unable to connect to qpid with SASL mechanism %s" msgstr "" +#~ msgid "Remove check to remove access for this user" +#~ msgstr "Wählen Sie die Nutzer ab, die keinen Zugriff mehr haben sollen" + #~ msgid "Select the action type" #~ msgstr "Maßnahmentyp wählen" -- 2.38.5 From 87a93b9a7fd7ca00437a2a0d44cf584c74dabdd2 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Fri, 18 Feb 2022 14:06:51 +0100 Subject: [PATCH 24/26] #101 Team data view * adds overview of shared teams on object detail view * adds team data view if button is clicked --- .../detail/compensation/view.html | 4 + .../compensation/detail/eco_account/view.html | 4 + ema/templates/ema/detail/view.html | 4 + .../templates/intervention/detail/view.html | 4 + locale/de/LC_MESSAGES/django.mo | Bin 39816 -> 39892 bytes locale/de/LC_MESSAGES/django.po | 191 +++++++++--------- user/forms.py | 43 ++++ .../user/includes/contact_modal_button.html | 2 +- .../user/includes/team_data_modal_button.html | 6 + user/urls.py | 1 + user/views.py | 27 ++- 11 files changed, 193 insertions(+), 93 deletions(-) create mode 100644 user/templates/user/includes/team_data_modal_button.html diff --git a/compensation/templates/compensation/detail/compensation/view.html b/compensation/templates/compensation/detail/compensation/view.html index c5ff41d..e387151 100644 --- a/compensation/templates/compensation/detail/compensation/view.html +++ b/compensation/templates/compensation/detail/compensation/view.html @@ -98,6 +98,10 @@ {% trans 'Shared with' %} + {% for team in obj.intervention.teams.all %} + {% include 'user/includes/team_data_modal_button.html' %} + {% endfor %} +
{% for user in obj.intervention.users.all %} {% include 'user/includes/contact_modal_button.html' %} {% endfor %} diff --git a/compensation/templates/compensation/detail/eco_account/view.html b/compensation/templates/compensation/detail/eco_account/view.html index 288b971..ee4a0f7 100644 --- a/compensation/templates/compensation/detail/eco_account/view.html +++ b/compensation/templates/compensation/detail/eco_account/view.html @@ -81,6 +81,10 @@ {% trans 'Shared with' %} + {% for team in obj.teams.all %} + {% include 'user/includes/team_data_modal_button.html' %} + {% endfor %} +
{% for user in obj.users.all %} {% include 'user/includes/contact_modal_button.html' %} {% endfor %} diff --git a/ema/templates/ema/detail/view.html b/ema/templates/ema/detail/view.html index 32ddd66..7b56703 100644 --- a/ema/templates/ema/detail/view.html +++ b/ema/templates/ema/detail/view.html @@ -74,6 +74,10 @@ {% trans 'Shared with' %} + {% for team in obj.teams.all %} + {% include 'user/includes/team_data_modal_button.html' %} + {% endfor %} +
{% for user in obj.users.all %} {% include 'user/includes/contact_modal_button.html' %} {% endfor %} diff --git a/intervention/templates/intervention/detail/view.html b/intervention/templates/intervention/detail/view.html index 8e7fc6c..f5680cc 100644 --- a/intervention/templates/intervention/detail/view.html +++ b/intervention/templates/intervention/detail/view.html @@ -114,6 +114,10 @@ {% trans 'Shared with' %} + {% for team in obj.teams.all %} + {% include 'user/includes/team_data_modal_button.html' %} + {% endfor %} +
{% for user in obj.users.all %} {% include 'user/includes/contact_modal_button.html' %} {% endfor %} diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index cbd6f0b09ac2c43691e3e31e27cb6e98f171d3df..ced4ab1ce70235e9c2f809480b12816984ba7a18 100644 GIT binary patch delta 9721 zcmZA5d3;Y-{>SnAjgUnWK_U`Skw_wfG$Pj6LM@Fgjit3riKJ*NW2>$BQhv0Sq8DSS zZBSdIG+I@nq}0%o=@ji)o0igQYMCyhlzF|K(|>;V@wk1SbMC$8e9q^bd-K&-_xhaP z>*Kvt&3Bo@5#r-G(YPSgaf&J5k5Q}R+)8$wDjvtVjV^UhBgdJ8%kUM9ZtOVya2*!l zuhbl;CF-SE8PD4GZ%_~Zf)mgu z)jT&DL#WTSE=N7L8RKxbZ9juy)URMD-o>hn?*uk64bfPI3yrWkwn06RjT-nl)Ij5~ z2EJlliILQIVNE=Ny6;=-9jr#(ljb-H7={(GH8y5^rvn8&Fd8eM7s;A43zdl?+=d@w zb$DuNuj0&t7`eG;4#JhRzg+ZtYpGS2x z85QYksI^>$3S<*zVIeZ6a~J1hYD@E6G3vRKr~oga0=R|h{~l_>fvrq{-gpX{K_lDH z6t#94r~$iLvr#D@fa+*8Do`&fz(uHjR-y)8Z?C_L>aPT~w5L#+IB%|do$o1V4e#0; zLR*^;QEk*7XoVWM7pjANREJ|x&rQcfT!vbjVr+vNqsJA0>jZ?@Bah} zN|_hc@nX~r-b6*Z(|W+RpFpkM1yn#cQJJ`pN_9{hlaUD29;l1TWD+VvO;CZh#EyFZ zdr;7TOHdz%ji?*9p&s0YwQwJ5^PE8idJZ*{?@;&sg8F_uKt1o%*4!U}8ZZR4RI#YQ z8=_Z*mJ~FD&KQCHP#qSa-uH>94p&(>pfa-+70~+L3==Stp{s<2;Q$u>iN=V_2huwqWSn}$xtcXh#II6wFLWZ z`$wqfkE8a+DSQ3jsEl6pQc$X|paLmJ&CJu$3>1RuFbow?P1OA{ww{FAys6eq)Sk#e z1(=HsaXdD{4Ojt7Q2}_rpr8oPp*GoNdlsI}|*gjtedSb_R%)XZN+ z1-Klwm)4?Yyb~4RKGeicpfdKQY4NKDQo zTQ5ef{TbARZlD6Wg&NrDVg?RI1r&k$`{UH7pa-8t&EzH29+-t%k`<`H-bO#%k4o*w zsF|EW1#}s;$$mox8rs#Eh`Qbudtg7*1UF%lQhbhr2KMb{BCdu>)ML>PyCdHgrw=}f zOKkfEtVsO|M&b`x34OUq0ar%#8)dDJ>aUrtw?(gR>`p;zKMMx-Bnbe*9t79(>8fZSMz7&ISJ!&Zm zQ7JD$Ey-!DgV%8_`jc06xCOPgJ5e+L2zCES)P%~cw^0-K^d$f4IJBo3FcuX+3aZ1_ z7=W4hXem&s?t>br!0N?7>WfhWt+wquQ2if3eRxix`Y*Hfay_U4^e0NMQy?l6A*fv( zfeIuV^+k+B%_tMK$@zbs88)Q z)UI8P>Zl0Y;b-=GP&U7isW-qXI0BpDB;*Bf-a$>|H`FF|o-!G#gvvx!&vJ;un2YECRCvNQ3D@CW$H_;iWg7=|Acz(Hw?z09JA(8sCp76>HTj` zK@SeaARLDpV1}(PKs~V1*0Nga%1;7^fiA@2ciZlw4TNo>i4YC{mhcJ!Fsg!#ilp~_5FAUlki7; z7Hjk;|B8451*L2XDzaIqHCu@K^tzac%Tb%?puK((_58P}nO;N9^d_pGpKbjCs{aa4 z+oeFgEm2RCf30CW4dIxM>L3f1+TN(8c^0eT7}QeCMs@fGDy6G!`xaCn@1X)KMg?*h zWDg(LrXy&M;n2lPRxmXhyq5^*lHS_JZ{;u_X z+x{VHbC=lmQdEE5FDN8aIBWG8WMbxgo=tcYPFi5{a+9hIUs@ikN+zo7#3f8I=>4r(b|pfcMLwQ2i5PyRLVKzm`N zbs7fHz67-dt8Kl|x)-&X4x<9Qh#JuM1+!GOFrIonRDe&Sc7HBL<6lwZE_i|b`%zd$ zg9iLNY6fqk1};WDSc-vo0hNJks9jx-y5H|b({WAI5~QP+t~cs69*GKg5~`o2sKC~F zZDB8Jz*0=YGJFgxk1`!KKn2JXZ`O`Q0gmpgNEy*lY$FE@sE=NsdGwN?f z5$d`3QJeE1Hph~e$iD{oorX~KonSf$N6n-jhGTQoeLb)l_D6O2I_hm$jv8SG2KftKT=C9-zm`MGhttU<~ z0q3CJhFsJ{UcfM%iZyWw*1+wki5#}|PrVehmfxT@%db}dm(6dxC~IrfOb4N6Hq1H# zHRDmJ0mh*+H_y5T_1rGhID1i<`xF(Z_X`SIlPjnhT(=i)qh{bU)eI0}t&O@r4)t~< zqBc_#d%Z1c4|GE9i5%+?)FvE-%J?*FtoMH_1#OZqQIVcQ&9DrWy6^Bse27}27pL*N z5AR|vTrl1IP}zzK>;fu)uTc}Yi5l3KrB-_wD${WosP{jeQ=6wf>IVIrQ1!tWf%+TK z9A}|sx*rwbG1LH`VI-DePrPUAPtGuVDG&MXI9pIlunm>^VhrJ4=M)94-9_w*Kcm*L z%}ir&)cZXQ72r(Nj25HTay@EOZAAt6E;hu2*a&Z6V~l*o`~c~S`XEh0{r+^eQ|OG9 zX0eXg0~O&6R3J;RJ#In`bQK@Ro7fIxW}Ce;#5xkAXdjEdxDb`{H&D-SLiN9MHu={K zKB7UXJcb(Z0xI=4@iFw{T~r4NsF}AwU+jw7Gd(Z~`ykuInT~JbJtRhF#T@f5sOY(7 zX$w%_m8o;dzXp7R2BmH@YM|Y={xK@$rPfPWhk7|CVDPKv`KGA$E_f1$qd%s|k)N?zKrS&@hprBMuks0SyaX0{O3@jA@JU8ouT2U9U{ zq1huDsLk0O+v5TZ#*?VboyQ=(|5qregWIUh=Ks3+$6_L?o`w3*JdcWej&0v)EkOlT zhDzxj)Bt{qOeRAyhW)m+m zehfqL6l!TMV>SE{gYh9M;Na!vdQH^*@u&ftp#p1*3akUB;*(wqttd>j?nS*WcTf+6 z{>`McIx2-tu{91xy)`RwIvzr;X}cB1PS$SLo~TUZ*m{3dAl^X~6k$GUrlaf)T@v$*I->dh;jJ!BRewaYO@*YV>}m{ zVhRpGZLWE!&9fb~Y4+LrQS?yX;FiTy5BP!N1!|YvKVl-n-=X#swNp5Qvyk$4Zc^Rm zp8vW->n3`>bziGnCwvQ65;zZXrg8qA^EbD+ZdSknN;)pPRby*~ze_EPS~=$i&b8D( zb34RVul)<9WLhTR8q_h0vW_BmOl-2}4|h#$t)wfokx%F5qbue=Q)&B}vli9OxSI32 zTNaxbyo1txT94qr-0-+Mo{MgJ-0=AKsHf9Dmh(R4T>SHBMO{Z__i$XY=dfEImt4cc z4!&mF%~gKrdfdkKo{#k5(h%C7R)mW;r#QsMR^V94>a7ym7IZ$u02*vS;uqu zt9v*;&a=_I8s9PhB=tVDM{o}1{DOK3XFBH=>Zdq2asHe7C43Wgd~dx)c^YR0#_5VT zICY%hdJ*p8{D-?RAvxe2q zI%?t+yoc-Y8_qP&F4WJtgA-FC^#N%|iytk&ppH_?d)>{6X)TXZilt>ezRwxPIi7PZ zZQbxH=Sa%>x&+gc4tBhA)~#AUE%-E7^Qle5AUC^ya@D((bQI!N{MlVtzn$j;_hkLJ z+GVtDrfm}EDtq5NET$gf1}CM&e?)Dhy>bZ4xT1rX^wDvT|88}MCZ*NBz}5ZK^!57l zcwh?;-Q7uvZT`a@lR3jVb=<_qIcIZj;|#E~F<1F_Fl{dSZ)|KFH2J1e-}4MXiy z$J|EAIl<>`O?T{e7bds!+$cJk+$1qzeE!f;F?mDB4|VT8b*<=p&dKV2`6COaPw;^Jk;C(2`tS%(xMg`^MH!2#c#1kN&aC`DXs;Yk delta 9658 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, YEAR. # -#: compensation/filters.py:122 compensation/forms/modalForms.py:36 +#: compensation/filters.py:123 compensation/forms/modalForms.py:36 #: compensation/forms/modalForms.py:47 compensation/forms/modalForms.py:63 #: compensation/forms/modalForms.py:356 compensation/forms/modalForms.py:463 #: intervention/forms/forms.py:54 intervention/forms/forms.py:156 -#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:154 -#: intervention/forms/modalForms.py:167 intervention/forms/modalForms.py:180 +#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:150 +#: intervention/forms/modalForms.py:163 intervention/forms/modalForms.py:176 #: konova/filters/mixins.py:53 konova/filters/mixins.py:54 #: konova/filters/mixins.py:81 konova/filters/mixins.py:82 #: konova/filters/mixins.py:94 konova/filters/mixins.py:95 #: konova/filters/mixins.py:107 konova/filters/mixins.py:108 #: konova/filters/mixins.py:120 konova/filters/mixins.py:121 #: konova/filters/mixins.py:134 konova/filters/mixins.py:135 -#: konova/filters/mixins.py:270 konova/filters/mixins.py:315 -#: konova/filters/mixins.py:353 konova/filters/mixins.py:354 -#: konova/filters/mixins.py:385 konova/filters/mixins.py:386 +#: konova/filters/mixins.py:270 konova/filters/mixins.py:316 +#: konova/filters/mixins.py:354 konova/filters/mixins.py:355 +#: konova/filters/mixins.py:386 konova/filters/mixins.py:387 #: konova/forms.py:143 konova/forms.py:244 konova/forms.py:315 #: konova/forms.py:359 konova/forms.py:369 konova/forms.py:382 #: konova/forms.py:394 konova/forms.py:412 user/forms.py:42 @@ -26,7 +26,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-18 12:35+0100\n" +"POT-Creation-Date: 2022-02-18 14:01+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -52,7 +52,7 @@ msgstr "Bis" #: intervention/forms/forms.py:102 #: intervention/templates/intervention/detail/view.html:56 #: intervention/templates/intervention/report/report.html:37 -#: intervention/utils/quality.py:49 konova/filters/mixins.py:395 +#: intervention/utils/quality.py:49 konova/filters/mixins.py:396 msgid "Conservation office" msgstr "Eintragungsstelle" @@ -221,7 +221,7 @@ msgstr "Abbuchungen" #: compensation/templates/compensation/detail/eco_account/includes/states-before.html:36 #: ema/templates/ema/detail/includes/states-after.html:36 #: ema/templates/ema/detail/includes/states-before.html:36 -#: intervention/forms/modalForms.py:365 +#: intervention/forms/modalForms.py:361 msgid "Surface" msgstr "Fläche" @@ -284,8 +284,8 @@ msgid "Type" msgstr "Typ" #: analysis/templates/analysis/reports/includes/old_data/amount.html:24 -#: compensation/tables.py:89 intervention/forms/modalForms.py:376 -#: intervention/forms/modalForms.py:383 intervention/tables.py:88 +#: compensation/tables.py:89 intervention/forms/modalForms.py:372 +#: intervention/forms/modalForms.py:379 intervention/tables.py:88 #: intervention/templates/intervention/detail/view.html:19 #: konova/templates/konova/includes/quickstart/interventions.html:4 #: templates/navbars/navbar.html:22 @@ -295,7 +295,7 @@ msgstr "Eingriff" #: analysis/templates/analysis/reports/includes/old_data/amount.html:34 #: compensation/tables.py:266 #: compensation/templates/compensation/detail/eco_account/view.html:20 -#: intervention/forms/modalForms.py:349 intervention/forms/modalForms.py:356 +#: intervention/forms/modalForms.py:345 intervention/forms/modalForms.py:352 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4 #: templates/navbars/navbar.html:34 msgid "Eco-account" @@ -309,7 +309,7 @@ msgstr "Altfälle" msgid "Before" msgstr "Vor" -#: compensation/filters.py:121 +#: compensation/filters.py:122 msgid "Show only unrecorded" msgstr "Nur unverzeichnete anzeigen" @@ -364,7 +364,7 @@ msgstr "Kompensation XY; Flur ABC" #: ema/templates/ema/detail/includes/actions.html:34 #: ema/templates/ema/detail/includes/deadlines.html:34 #: ema/templates/ema/detail/includes/documents.html:34 -#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:179 +#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:175 #: intervention/templates/intervention/detail/includes/documents.html:34 #: intervention/templates/intervention/detail/includes/payments.html:34 #: intervention/templates/intervention/detail/includes/revocation.html:38 @@ -484,7 +484,7 @@ msgid "Due on which date" msgstr "Zahlung wird an diesem Datum erwartet" #: compensation/forms/modalForms.py:64 compensation/forms/modalForms.py:357 -#: intervention/forms/modalForms.py:181 konova/forms.py:395 +#: intervention/forms/modalForms.py:177 konova/forms.py:395 msgid "Additional comment, maximum {} letters" msgstr "Zusätzlicher Kommentar, maximal {} Zeichen" @@ -512,7 +512,7 @@ msgstr "Zusatzbezeichnung" msgid "Select an additional biotope type" msgstr "Zusatzbezeichnung wählen" -#: compensation/forms/modalForms.py:197 intervention/forms/modalForms.py:367 +#: compensation/forms/modalForms.py:197 intervention/forms/modalForms.py:363 msgid "in m²" msgstr "" @@ -540,7 +540,7 @@ msgstr "Fristart wählen" #: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31 #: ema/templates/ema/detail/includes/deadlines.html:31 -#: intervention/forms/modalForms.py:153 +#: intervention/forms/modalForms.py:149 msgid "Date" msgstr "Datum" @@ -1142,7 +1142,7 @@ msgstr "Daten zu den verantwortlichen Stellen" msgid "Compensations - Overview" msgstr "Kompensationen - Übersicht" -#: compensation/views/compensation.py:151 konova/utils/message_templates.py:31 +#: compensation/views/compensation.py:151 konova/utils/message_templates.py:33 msgid "Compensation {} edited" msgstr "Kompensation {} bearbeitet" @@ -1269,7 +1269,7 @@ msgstr "Mehrfachauswahl möglich" #: intervention/forms/forms.py:86 #: intervention/templates/intervention/detail/view.html:48 #: intervention/templates/intervention/report/report.html:29 -#: intervention/utils/quality.py:46 konova/filters/mixins.py:363 +#: intervention/utils/quality.py:46 konova/filters/mixins.py:364 msgid "Registration office" msgstr "Zulassungsbehörde" @@ -1322,7 +1322,7 @@ msgstr "Freigabelink" #: intervention/forms/modalForms.py:31 msgid "Send this link to users who you want to have writing access on the data" -msgstr "Andere Nutzer erhalten über diesen Link Zugriff auf die Daten" +msgstr "Einzelne Nutzer erhalten über diesen Link Zugriff auf die Daten" #: intervention/forms/modalForms.py:41 msgid "Add team to share with" @@ -1338,7 +1338,7 @@ msgstr "" #: intervention/forms/modalForms.py:55 msgid "Add user to share with" -msgstr "Nutzer direkt hinzufügen" +msgstr "Nutzer einzeln hinzufügen" #: intervention/forms/modalForms.py:57 msgid "" @@ -1352,44 +1352,38 @@ msgstr "" msgid "Share settings for {}" msgstr "Freigabe Einstellungen für {}" -#: intervention/forms/modalForms.py:105 intervention/forms/modalForms.py:111 -msgid "" -"Only conservation or registration office users are allowed to remove entries." -msgstr "" -"Nur Mitarbeiter der Naturschutz- oder Zulassungsbehördengruppe dürfen Einträge entfernen" - -#: intervention/forms/modalForms.py:155 +#: intervention/forms/modalForms.py:151 msgid "Date of revocation" msgstr "Datum des Widerspruchs" -#: intervention/forms/modalForms.py:166 +#: intervention/forms/modalForms.py:162 #: intervention/templates/intervention/detail/includes/revocation.html:35 msgid "Document" msgstr "Dokument" -#: intervention/forms/modalForms.py:169 +#: intervention/forms/modalForms.py:165 msgid "Must be smaller than 15 Mb" msgstr "Muss kleiner als 15 Mb sein" -#: intervention/forms/modalForms.py:194 +#: intervention/forms/modalForms.py:190 #: intervention/templates/intervention/detail/includes/revocation.html:18 msgid "Add revocation" msgstr "Widerspruch hinzufügen" -#: intervention/forms/modalForms.py:251 +#: intervention/forms/modalForms.py:247 msgid "Checked intervention data" msgstr "Eingriffsdaten geprüft" -#: intervention/forms/modalForms.py:257 +#: intervention/forms/modalForms.py:253 msgid "Checked compensations data and payments" msgstr "Kompensationen und Zahlungen geprüft" -#: intervention/forms/modalForms.py:266 +#: intervention/forms/modalForms.py:262 #: intervention/templates/intervention/detail/includes/controls.html:19 msgid "Run check" msgstr "Prüfung vornehmen" -#: intervention/forms/modalForms.py:267 konova/forms.py:514 +#: intervention/forms/modalForms.py:263 konova/forms.py:514 msgid "" "I, {} {}, confirm that all necessary control steps have been performed by " "myself." @@ -1397,23 +1391,23 @@ msgstr "" "Ich, {} {}, bestätige, dass die notwendigen Kontrollschritte durchgeführt " "wurden:" -#: intervention/forms/modalForms.py:351 +#: intervention/forms/modalForms.py:347 msgid "Only recorded accounts can be selected for deductions" msgstr "Nur verzeichnete Ökokonten können für Abbuchungen verwendet werden." -#: intervention/forms/modalForms.py:378 +#: intervention/forms/modalForms.py:374 msgid "Only shared interventions can be selected" msgstr "Nur freigegebene Eingriffe können gewählt werden" -#: intervention/forms/modalForms.py:391 +#: intervention/forms/modalForms.py:387 msgid "New Deduction" msgstr "Neue Abbuchung" -#: intervention/forms/modalForms.py:392 +#: intervention/forms/modalForms.py:388 msgid "Enter the information for a new deduction from a chosen eco-account" msgstr "Geben Sie die Informationen für eine neue Abbuchung ein." -#: intervention/forms/modalForms.py:435 +#: intervention/forms/modalForms.py:431 msgid "" "Eco-account {} is not recorded yet. You can only deduct from recorded " "accounts." @@ -1421,7 +1415,7 @@ msgstr "" "Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von " "verzeichneten Ökokonten erfolgen." -#: intervention/forms/modalForms.py:445 +#: intervention/forms/modalForms.py:441 msgid "" "The account {} has not enough surface for a deduction of {} m². There are " "only {} m² left" @@ -1616,15 +1610,15 @@ msgstr "Nach Flurstücknenner suchen" msgid "Show unshared" msgstr "Nicht freigegebene anzeigen" -#: konova/filters/mixins.py:314 +#: konova/filters/mixins.py:315 msgid "Show recorded" msgstr "Verzeichnete anzeigen" -#: konova/filters/mixins.py:364 +#: konova/filters/mixins.py:365 msgid "Search for registration office" msgstr "Nach Zulassungsbehörde suchen" -#: konova/filters/mixins.py:396 +#: konova/filters/mixins.py:397 msgid "Search for conservation office" msgstr "Nch Eintragungsstelle suchen" @@ -1851,10 +1845,25 @@ msgstr "" "der Zwischenzeit angelegt wurde, welcher diese Kennung nun bereits verwendet" #: konova/utils/message_templates.py:15 +msgid "" +"Only conservation or registration office users are allowed to remove entries." +msgstr "" +"Nur Mitarbeiter der Naturschutz- oder Zulassungsbehördengruppe dürfen " +"Einträge entfernen" + +#: konova/utils/message_templates.py:16 +msgid "You need to be part of another user group." +msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!" + +#: konova/utils/message_templates.py:17 +msgid "Status of Checked and Recorded reseted" +msgstr "'Geprüft'/'Verzeichnet' wurde zurückgesetzt" + +#: konova/utils/message_templates.py:20 msgid "This data is not shared with you" msgstr "Diese Daten sind für Sie nicht freigegeben" -#: konova/utils/message_templates.py:16 +#: konova/utils/message_templates.py:21 msgid "" "Remember: This data has not been shared with you, yet. This means you can " "only read but can not edit or perform any actions like running a check or " @@ -1864,23 +1873,15 @@ msgstr "" "bedeutet, dass Sie nur lesenden Zugriff hierauf haben und weder bearbeiten, " "noch Prüfungen durchführen oder verzeichnen können." -#: konova/utils/message_templates.py:17 -msgid "You need to be part of another user group." -msgstr "Hierfür müssen Sie einer anderen Nutzergruppe angehören!" - -#: konova/utils/message_templates.py:19 -msgid "Status of Checked and Recorded reseted" -msgstr "'Geprüft'/'Verzeichnet' wurde zurückgesetzt" - -#: konova/utils/message_templates.py:22 +#: konova/utils/message_templates.py:24 msgid "Unsupported file type" msgstr "Dateiformat nicht unterstützt" -#: konova/utils/message_templates.py:23 +#: konova/utils/message_templates.py:25 msgid "File too large" msgstr "Datei zu groß" -#: konova/utils/message_templates.py:26 +#: konova/utils/message_templates.py:28 msgid "" "Action canceled. Eco account is recorded or deductions exist. Only " "conservation office member can perform this action." @@ -1888,119 +1889,119 @@ msgstr "" "Aktion abgebrochen. Ökokonto ist bereits verzeichnet oder Abbuchungen liegen " "vor. Nur Eintragungsstellennutzer können diese Aktion jetzt durchführen." -#: konova/utils/message_templates.py:29 +#: konova/utils/message_templates.py:31 msgid "Compensation {} added" msgstr "Kompensation {} hinzugefügt" -#: konova/utils/message_templates.py:30 +#: konova/utils/message_templates.py:32 msgid "Compensation {} removed" msgstr "Kompensation {} entfernt" -#: konova/utils/message_templates.py:32 +#: konova/utils/message_templates.py:34 msgid "Added compensation action" msgstr "Maßnahme hinzugefügt" -#: konova/utils/message_templates.py:33 +#: konova/utils/message_templates.py:35 msgid "Added compensation state" msgstr "Zustand hinzugefügt" -#: konova/utils/message_templates.py:36 +#: konova/utils/message_templates.py:38 msgid "State removed" msgstr "Zustand gelöscht" -#: konova/utils/message_templates.py:37 +#: konova/utils/message_templates.py:39 msgid "State edited" msgstr "Zustand bearbeitet" -#: konova/utils/message_templates.py:38 +#: konova/utils/message_templates.py:40 msgid "State added" msgstr "Zustand hinzugefügt" -#: konova/utils/message_templates.py:41 +#: konova/utils/message_templates.py:43 msgid "Action added" msgstr "Maßnahme hinzugefügt" -#: konova/utils/message_templates.py:42 +#: konova/utils/message_templates.py:44 msgid "Action edited" msgstr "Maßnahme bearbeitet" -#: konova/utils/message_templates.py:43 +#: konova/utils/message_templates.py:45 msgid "Action removed" msgstr "Maßnahme entfernt" -#: konova/utils/message_templates.py:46 +#: konova/utils/message_templates.py:48 msgid "Deduction added" msgstr "Abbuchung hinzugefügt" -#: konova/utils/message_templates.py:47 +#: konova/utils/message_templates.py:49 msgid "Deduction edited" msgstr "Abbuchung bearbeitet" -#: konova/utils/message_templates.py:48 +#: konova/utils/message_templates.py:50 msgid "Deduction removed" msgstr "Abbuchung entfernt" -#: konova/utils/message_templates.py:51 +#: konova/utils/message_templates.py:53 msgid "Deadline added" msgstr "Frist/Termin hinzugefügt" -#: konova/utils/message_templates.py:52 +#: konova/utils/message_templates.py:54 msgid "Deadline edited" msgstr "Frist/Termin bearbeitet" -#: konova/utils/message_templates.py:53 +#: konova/utils/message_templates.py:55 msgid "Deadline removed" msgstr "Frist/Termin gelöscht" -#: konova/utils/message_templates.py:56 +#: konova/utils/message_templates.py:58 msgid "Payment added" msgstr "Zahlung hinzugefügt" -#: konova/utils/message_templates.py:57 +#: konova/utils/message_templates.py:59 msgid "Payment edited" msgstr "Zahlung bearbeitet" -#: konova/utils/message_templates.py:58 +#: konova/utils/message_templates.py:60 msgid "Payment removed" msgstr "Zahlung gelöscht" -#: konova/utils/message_templates.py:61 +#: konova/utils/message_templates.py:63 msgid "Revocation added" msgstr "Widerspruch hinzugefügt" -#: konova/utils/message_templates.py:62 +#: konova/utils/message_templates.py:64 msgid "Revocation edited" msgstr "Widerspruch bearbeitet" -#: konova/utils/message_templates.py:63 +#: konova/utils/message_templates.py:65 msgid "Revocation removed" msgstr "Widerspruch entfernt" -#: konova/utils/message_templates.py:66 +#: konova/utils/message_templates.py:68 msgid "Document '{}' deleted" msgstr "Dokument '{}' gelöscht" -#: konova/utils/message_templates.py:67 +#: konova/utils/message_templates.py:69 msgid "Document added" msgstr "Dokument hinzugefügt" -#: konova/utils/message_templates.py:68 +#: konova/utils/message_templates.py:70 msgid "Document edited" msgstr "Dokument bearbeitet" -#: konova/utils/message_templates.py:71 +#: konova/utils/message_templates.py:73 msgid "Edited general data" msgstr "Allgemeine Daten bearbeitet" -#: konova/utils/message_templates.py:72 +#: konova/utils/message_templates.py:74 msgid "Added deadline" msgstr "Frist/Termin hinzugefügt" -#: konova/utils/message_templates.py:75 +#: konova/utils/message_templates.py:77 msgid "Geometry conflict detected with {}" msgstr "Geometriekonflikt mit folgenden Einträgen erkannt: {}" -#: konova/utils/message_templates.py:78 +#: konova/utils/message_templates.py:80 msgid "This intervention has {} revocations" msgstr "Dem Eingriff liegen {} Widersprüche vor" @@ -2416,11 +2417,11 @@ msgstr "Neuen Token generieren" msgid "A new token needs to be validated by an administrator!" msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!" -#: user/forms.py:168 user/forms.py:172 +#: user/forms.py:168 user/forms.py:172 user/forms.py:323 user/forms.py:328 msgid "Team name" msgstr "Team Name" -#: user/forms.py:179 user/templates/user/team/index.html:30 +#: user/forms.py:179 user/forms.py:336 user/templates/user/team/index.html:30 msgid "Description" msgstr "Beschreibung" @@ -2468,6 +2469,10 @@ msgstr "Gewählter Administrator ({}) muss ein Mitglied des Teams sein." msgid "Edit team" msgstr "Team bearbeiten" +#: user/forms.py:347 +msgid "Team" +msgstr "Team" + #: user/models/user_action.py:22 msgid "Unrecorded" msgstr "Entzeichnet" @@ -2484,6 +2489,10 @@ msgstr "Gelöscht" msgid "Show contact data" msgstr "Zeige Kontaktdaten" +#: user/templates/user/includes/team_data_modal_button.html:3 +msgid "Show team data" +msgstr "Zeige Teamdaten" + #: user/templates/user/index.html:13 user/templates/user/team/index.html:29 msgid "Name" msgstr "" @@ -2534,7 +2543,7 @@ msgid "Manage teams" msgstr "" #: user/templates/user/index.html:69 user/templates/user/team/index.html:18 -#: user/views.py:142 +#: user/views.py:167 msgid "Teams" msgstr "" @@ -2594,15 +2603,15 @@ msgstr "Neuer Token generiert. Administratoren sind informiert." msgid "User API token" msgstr "API Nutzer Token" -#: user/views.py:153 +#: user/views.py:178 msgid "New team added" msgstr "Neues Team hinzugefügt" -#: user/views.py:166 +#: user/views.py:191 msgid "Team edited" msgstr "Team bearbeitet" -#: user/views.py:179 +#: user/views.py:204 msgid "Team removed" msgstr "Team gelöscht" diff --git a/user/forms.py b/user/forms.py index 01ec54b..34c8fab 100644 --- a/user/forms.py +++ b/user/forms.py @@ -315,3 +315,46 @@ class EditTeamModalForm(NewTeamModalForm): class RemoveTeamModalForm(RemoveModalForm): pass + + +class TeamDataForm(BaseModalForm): + name = forms.CharField( + label_suffix="", + label=_("Team name"), + max_length=500, + required=False, + widget=forms.TextInput( + attrs={ + "placeholder": _("Team name"), + "class": "form-control", + } + ) + ) + description = forms.CharField( + label_suffix="", + required=False, + label=_("Description"), + widget=forms.Textarea( + attrs={ + "rows": 5, + "class": "form-control" + } + ) + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.form_title = _("Team") + self.form_caption = "" + self.render_submit = False + form_data = { + "name": self.instance.name, + "description": self.instance.description, + } + self.load_initial_data( + form_data, + [ + "name", + "description" + ] + ) \ No newline at end of file diff --git a/user/templates/user/includes/contact_modal_button.html b/user/templates/user/includes/contact_modal_button.html index 68ae53a..5cbd87f 100644 --- a/user/templates/user/includes/contact_modal_button.html +++ b/user/templates/user/includes/contact_modal_button.html @@ -1,6 +1,6 @@ {% load fontawesome_5 i18n %} \ No newline at end of file diff --git a/user/templates/user/includes/team_data_modal_button.html b/user/templates/user/includes/team_data_modal_button.html new file mode 100644 index 0000000..b41e63a --- /dev/null +++ b/user/templates/user/includes/team_data_modal_button.html @@ -0,0 +1,6 @@ +{% load fontawesome_5 i18n %} + + \ No newline at end of file diff --git a/user/urls.py b/user/urls.py index 76a8662..5c33427 100644 --- a/user/urls.py +++ b/user/urls.py @@ -16,6 +16,7 @@ urlpatterns = [ path("token/api", api_token_view, name="api-token"), path("contact/", contact_view, name="contact"), path("team/", index_team_view, name="team-index"), + path("team/", data_team_view, name="team-data"), path("team/new", new_team_view, name="team-new"), path("team//edit", edit_team_view, name="team-edit"), path("team//remove", remove_team_view, name="team-remove"), diff --git a/user/views.py b/user/views.py index afed572..9afede1 100644 --- a/user/views.py +++ b/user/views.py @@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _ from konova.contexts import BaseContext from konova.decorators import any_group_check, default_group_required from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm, NewTeamModalForm, EditTeamModalForm, \ - RemoveTeamModalForm + RemoveTeamModalForm, TeamDataForm @login_required @@ -133,6 +133,31 @@ def contact_view(request: HttpRequest, id: str): ) +@login_required +def data_team_view(request: HttpRequest, id: str): + """ Renders team data + + Args: + request (HttpRequest): The incoming request + id (str): The team's id + + Returns: + + """ + team = get_object_or_404(Team, id=id) + form = TeamDataForm(request.POST or None, instance=team, request=request) + template = "modal/modal_form.html" + context = { + "form": form, + } + context = BaseContext(request, context).context + return render( + request, + template, + context + ) + + @login_required def index_team_view(request: HttpRequest): template = "user/team/index.html" -- 2.38.5 From 50def040f2e0f8cc0213bbfb708594c36795d064 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Fri, 18 Feb 2022 14:09:45 +0100 Subject: [PATCH 25/26] #101 Datatype team migrations * adds migration files for ShareableObject data models --- .../migrations/0006_ecoaccount_teams.py | 19 +++++++++++++++++++ ema/migrations/0003_ema_teams.py | 19 +++++++++++++++++++ .../migrations/0003_intervention_teams.py | 19 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 compensation/migrations/0006_ecoaccount_teams.py create mode 100644 ema/migrations/0003_ema_teams.py create mode 100644 intervention/migrations/0003_intervention_teams.py diff --git a/compensation/migrations/0006_ecoaccount_teams.py b/compensation/migrations/0006_ecoaccount_teams.py new file mode 100644 index 0000000..14cf509 --- /dev/null +++ b/compensation/migrations/0006_ecoaccount_teams.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.3 on 2022-02-18 09:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0003_team'), + ('compensation', '0005_auto_20220218_0917'), + ] + + operations = [ + migrations.AddField( + model_name='ecoaccount', + name='teams', + field=models.ManyToManyField(help_text='Teams having access (data shared with)', to='user.Team'), + ), + ] diff --git a/ema/migrations/0003_ema_teams.py b/ema/migrations/0003_ema_teams.py new file mode 100644 index 0000000..606781d --- /dev/null +++ b/ema/migrations/0003_ema_teams.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.3 on 2022-02-18 09:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0003_team'), + ('ema', '0002_auto_20220114_0936'), + ] + + operations = [ + migrations.AddField( + model_name='ema', + name='teams', + field=models.ManyToManyField(help_text='Teams having access (data shared with)', to='user.Team'), + ), + ] diff --git a/intervention/migrations/0003_intervention_teams.py b/intervention/migrations/0003_intervention_teams.py new file mode 100644 index 0000000..8dd7624 --- /dev/null +++ b/intervention/migrations/0003_intervention_teams.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.3 on 2022-02-18 09:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0003_team'), + ('intervention', '0002_auto_20220114_0936'), + ] + + operations = [ + migrations.AddField( + model_name='intervention', + name='teams', + field=models.ManyToManyField(help_text='Teams having access (data shared with)', to='user.Team'), + ), + ] -- 2.38.5 From 963854652ef9d6df09be8eeedfde9b07be7b9259 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Fri, 18 Feb 2022 15:19:37 +0100 Subject: [PATCH 26/26] #101 Team mails * adds mail templates for shared data actions * fixes bug where deleted compensations would be used for checking --- intervention/forms/modalForms.py | 6 +- konova/models/object.py | 87 ++++++++--- konova/tasks.py | 42 ++++++ konova/utils/mailer.py | 138 ++++++++++++++++++ locale/de/LC_MESSAGES/django.mo | Bin 39892 -> 40282 bytes locale/de/LC_MESSAGES/django.po | 43 +++++- .../checking/shared_data_checked_team.html | 28 ++++ .../deleting/shared_data_deleted_team.html | 28 ++++ .../recording/shared_data_recorded_team.html | 33 +++++ .../shared_data_unrecorded_team.html | 33 +++++ .../sharing/shared_access_given_team.html | 34 +++++ .../sharing/shared_access_removed_team.html | 29 ++++ user/models/team.py | 79 ++++++++++ 13 files changed, 551 insertions(+), 29 deletions(-) create mode 100644 templates/email/checking/shared_data_checked_team.html create mode 100644 templates/email/deleting/shared_data_deleted_team.html create mode 100644 templates/email/recording/shared_data_recorded_team.html create mode 100644 templates/email/recording/shared_data_unrecorded_team.html create mode 100644 templates/email/sharing/shared_access_given_team.html create mode 100644 templates/email/sharing/shared_access_removed_team.html diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index 3947938..1e5febf 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -141,7 +141,7 @@ class ShareModalForm(BaseModalForm): self.load_initial_data(form_data) def save(self): - self.instance.update_sharing_user(self) + self.instance.update_shared_access(self) class NewRevocationModalForm(BaseModalForm): @@ -290,7 +290,9 @@ class CheckModalForm(BaseModalForm): Returns: """ - comps = self.instance.compensations.all() + comps = self.instance.compensations.filter( + deleted=None, + ) comps_valid = True for comp in comps: checker = comp.quality_check() diff --git a/konova/models/object.py b/konova/models/object.py index 92b3cca..f224b68 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -15,7 +15,10 @@ from django.db.models import QuerySet from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP, LANIS_ZOOM_LUT, LANIS_LINK_TEMPLATE from konova.tasks import celery_send_mail_shared_access_removed, celery_send_mail_shared_access_given, \ celery_send_mail_shared_data_recorded, celery_send_mail_shared_data_unrecorded, \ - celery_send_mail_shared_data_deleted, celery_send_mail_shared_data_checked + celery_send_mail_shared_data_deleted, celery_send_mail_shared_data_checked, \ + celery_send_mail_shared_access_given_team, celery_send_mail_shared_access_removed_team, \ + celery_send_mail_shared_data_checked_team, celery_send_mail_shared_data_deleted_team, \ + celery_send_mail_shared_data_unrecorded_team, celery_send_mail_shared_data_recorded_team from django.core.exceptions import ObjectDoesNotExist from django.http import HttpRequest from django.utils.timezone import now @@ -130,6 +133,11 @@ class BaseObject(BaseResource): for user_id in shared_users: celery_send_mail_shared_data_deleted.delay(self.identifier, self.title, user_id) + # Send mail + shared_teams = self.shared_teams.values_list("id", flat=True) + for team_id in shared_teams: + celery_send_mail_shared_data_deleted_team.delay(self.identifier, self.title, team_id) + self.save() def mark_as_edited(self, performing_user, request: HttpRequest = None, edit_comment: str = None): @@ -258,10 +266,15 @@ class RecordableObjectMixin(models.Model): self.save() self.log.add(action) - shared_users = self.users.all().values_list("id", flat=True) + shared_users = self.shared_users.values_list("id", flat=True) + shared_teams = self.shared_teams.values_list("id", flat=True) + for user_id in shared_users: celery_send_mail_shared_data_unrecorded.delay(self.identifier, self.title, user_id) + for team_id in shared_teams: + celery_send_mail_shared_data_unrecorded_team.delay(self.identifier, self.title, team_id) + return action def set_recorded(self, user): @@ -281,10 +294,15 @@ class RecordableObjectMixin(models.Model): self.save() self.log.add(action) - shared_users = self.users.all().values_list("id", flat=True) + shared_users = self.shared_users.values_list("id", flat=True) + shared_teams = self.shared_teams.values_list("id", flat=True) + for user_id in shared_users: celery_send_mail_shared_data_recorded.delay(self.identifier, self.title, user_id) + for team_id in shared_teams: + celery_send_mail_shared_data_recorded_team.delay(self.identifier, self.title, team_id) + return action def unrecord(self, performing_user, request: HttpRequest = None): @@ -367,10 +385,15 @@ class CheckableObjectMixin(models.Model): self.save() # Send mail - shared_users = self.users.all().values_list("id", flat=True) + shared_users = self.shared_users.values_list("id", flat=True) for user_id in shared_users: celery_send_mail_shared_data_checked.delay(self.identifier, self.title, user_id) + # Send mail + shared_teams = self.shared_teams.values_list("id", flat=True) + for team_id in shared_teams: + celery_send_mail_shared_data_checked_team.delay(self.identifier, self.title, team_id) + self.log.add(action) return action @@ -488,8 +511,8 @@ class ShareableObjectMixin(models.Model): """ self.users.set(user_list) - def update_sharing_user(self, form): - """ Adds a new user with shared access to the object + def _update_shared_teams(self, form): + """ Updates shared access on the object for teams Args: form (ShareModalForm): The form holding the data @@ -497,33 +520,46 @@ class ShareableObjectMixin(models.Model): Returns: """ - from user.models import User form_data = form.cleaned_data + shared_teams = self.shared_teams # Fetch selected teams and find out which user IDs are in removed teams -> mails need to be sent accessing_teams = form_data["teams"] - removed_team_users = self.teams.all().exclude( + removed_teams = shared_teams.exclude( id__in=accessing_teams - ).values_list("users__id", flat=True) - accessing_team_users = User.objects.filter( - id__in=accessing_teams.values_list("users", flat=True) - ) - new_team_users = accessing_team_users.exclude( - id__in=self.shared_users ).values_list("id", flat=True) + new_teams = accessing_teams.exclude( + id__in=shared_teams + ).values_list("id", flat=True) + + for team_id in new_teams: + celery_send_mail_shared_access_given_team.delay(self.identifier, self.title, team_id) + for team_id in removed_teams: + celery_send_mail_shared_access_removed_team.delay(self.identifier, self.title, team_id) + + self.share_with_team_list(accessing_teams) + + def _update_shared_users(self, form): + """ Updates shared access on the object for single users + + Args: + form (ShareModalForm): The form holding the data + + Returns: + + """ + form_data = form.cleaned_data + shared_users = self.shared_users # Fetch selected users accessing_users = form_data["users"] - removed_users = self.users.all().exclude( + removed_users = shared_users.exclude( id__in=accessing_users ).values_list("id", flat=True) new_users = accessing_users.exclude( - id__in=self.shared_users + id__in=shared_users ).values_list("id", flat=True) - new_users = new_users.union(new_team_users) - removed_users = removed_users.union(removed_team_users) - # Send mails for user_id in removed_users: celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user_id) @@ -532,7 +568,18 @@ class ShareableObjectMixin(models.Model): # Set new shared users self.share_with_user_list(accessing_users) - self.share_with_team_list(accessing_teams) + + def update_shared_access(self, form): + """ Updates shared access on the object + + Args: + form (ShareModalForm): The form holding the data + + Returns: + + """ + self._update_shared_teams(form) + self._update_shared_users(form) @property def shared_users(self) -> QuerySet: diff --git a/konova/tasks.py b/konova/tasks.py index c74a2bd..798effb 100644 --- a/konova/tasks.py +++ b/konova/tasks.py @@ -38,6 +38,20 @@ def celery_send_mail_shared_access_given(obj_identifier, obj_title=None, user_id user.send_mail_shared_access_given(obj_identifier, obj_title) +@shared_task +def celery_send_mail_shared_access_removed_team(obj_identifier, obj_title=None, team_id=None): + from user.models import Team + team = Team.objects.get(id=team_id) + team.send_mail_shared_access_removed(obj_identifier, obj_title) + + +@shared_task +def celery_send_mail_shared_access_given_team(obj_identifier, obj_title=None, team_id=None): + from user.models import Team + team = Team.objects.get(id=team_id) + team.send_mail_shared_access_given_team(obj_identifier, obj_title) + + @shared_task def celery_send_mail_shared_data_recorded(obj_identifier, obj_title=None, user_id=None): from user.models import User @@ -52,6 +66,20 @@ def celery_send_mail_shared_data_unrecorded(obj_identifier, obj_title=None, user user.send_mail_shared_data_unrecorded(obj_identifier, obj_title) +@shared_task +def celery_send_mail_shared_data_recorded_team(obj_identifier, obj_title=None, team_id=None): + from user.models import Team + team = Team.objects.get(id=team_id) + team.send_mail_shared_data_recorded(obj_identifier, obj_title) + + +@shared_task +def celery_send_mail_shared_data_unrecorded_team(obj_identifier, obj_title=None, team_id=None): + from user.models import Team + team = Team.objects.get(id=team_id) + team.send_mail_shared_data_unrecorded(obj_identifier, obj_title) + + @shared_task def celery_send_mail_shared_data_deleted(obj_identifier, obj_title=None, user_id=None): from user.models import User @@ -64,3 +92,17 @@ def celery_send_mail_shared_data_checked(obj_identifier, obj_title=None, user_id from user.models import User user = User.objects.get(id=user_id) user.send_mail_shared_data_checked(obj_identifier, obj_title) + + +@shared_task +def celery_send_mail_shared_data_deleted_team(obj_identifier, obj_title=None, team_id=None): + from user.models import Team + team = Team.objects.get(id=team_id) + team.send_mail_shared_data_deleted(obj_identifier, obj_title) + + +@shared_task +def celery_send_mail_shared_data_checked_team(obj_identifier, obj_title=None, team_id=None): + from user.models import Team + team = Team.objects.get(id=team_id) + team.send_mail_shared_data_checked(obj_identifier, obj_title) diff --git a/konova/utils/mailer.py b/konova/utils/mailer.py index 33907f3..dd8eef1 100644 --- a/konova/utils/mailer.py +++ b/konova/utils/mailer.py @@ -92,6 +92,144 @@ class Mailer: msg ) + def send_mail_shared_access_given_team(self, obj_identifier, obj_title, team): + """ Send a mail if a team just got access to the object + + Args: + obj_identifier (str): The object identifier + + Returns: + + """ + context = { + "team": team, + "obj_identifier": obj_identifier, + "obj_title": obj_title, + "EMAIL_REPLY_TO": EMAIL_REPLY_TO, + } + msg = render_to_string("email/sharing/shared_access_given_team.html", context) + user_mail_address = team.users.values_list("email", flat=True) + self.send( + user_mail_address, + _("{} - Shared access given").format(obj_identifier), + msg + ) + + def send_mail_shared_access_removed_team(self, obj_identifier, obj_title, team): + """ Send a mail if a team just lost access to the object + + Args: + obj_identifier (str): The object identifier + + Returns: + + """ + context = { + "team": team, + "obj_identifier": obj_identifier, + "obj_title": obj_title, + "EMAIL_REPLY_TO": EMAIL_REPLY_TO, + } + msg = render_to_string("email/sharing/shared_access_removed_team.html", context) + user_mail_address = team.users.values_list("email", flat=True) + self.send( + user_mail_address, + _("{} - Shared access removed").format(obj_identifier), + msg + ) + + def send_mail_shared_data_unrecorded_team(self, obj_identifier, obj_title, team): + """ Send a mail if data has just been unrecorded + + Args: + obj_identifier (str): The object identifier + + Returns: + + """ + context = { + "team": team, + "obj_identifier": obj_identifier, + "obj_title": obj_title, + "EMAIL_REPLY_TO": EMAIL_REPLY_TO, + } + msg = render_to_string("email/recording/shared_data_unrecorded_team.html", context) + user_mail_address = team.users.values_list("email", flat=True) + self.send( + user_mail_address, + _("{} - Shared data unrecorded").format(obj_identifier), + msg + ) + + def send_mail_shared_data_recorded_team(self, obj_identifier, obj_title, team): + """ Send a mail if data has just been recorded + + Args: + obj_identifier (str): The object identifier + + Returns: + + """ + context = { + "team": team, + "obj_identifier": obj_identifier, + "obj_title": obj_title, + "EMAIL_REPLY_TO": EMAIL_REPLY_TO, + } + msg = render_to_string("email/recording/shared_data_recorded_team.html", context) + user_mail_address = team.users.values_list("email", flat=True) + self.send( + user_mail_address, + _("{} - Shared data recorded").format(obj_identifier), + msg + ) + + def send_mail_shared_data_checked_team(self, obj_identifier, obj_title, team): + """ Send a mail if data has just been checked + + Args: + obj_identifier (str): The object identifier + + Returns: + + """ + context = { + "team": team, + "obj_identifier": obj_identifier, + "obj_title": obj_title, + "EMAIL_REPLY_TO": EMAIL_REPLY_TO, + } + msg = render_to_string("email/checking/shared_data_checked_team.html", context) + user_mail_address = team.users.values_list("email", flat=True) + self.send( + user_mail_address, + _("{} - Shared data checked").format(obj_identifier), + msg + ) + + def send_mail_shared_data_deleted_team(self, obj_identifier, obj_title, team): + """ Send a mail if data has just been deleted + + Args: + obj_identifier (str): The object identifier + + Returns: + + """ + context = { + "team": team, + "obj_identifier": obj_identifier, + "obj_title": obj_title, + "EMAIL_REPLY_TO": EMAIL_REPLY_TO, + } + msg = render_to_string("email/deleting/shared_data_deleted_team.html", context) + user_mail_address = team.users.values_list("email", flat=True) + self.send( + user_mail_address, + _("{} - Shared data deleted").format(obj_identifier), + msg + ) + def send_mail_shared_data_recorded(self, obj_identifier, obj_title, user): """ Send a mail if the user's shared data has just been unrecorded diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index ced4ab1ce70235e9c2f809480b12816984ba7a18..9437cb993a50c84e2c397c3f30f0e5d7cf7ba041 100644 GIT binary patch delta 11524 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 Zr47vWROJ^Gobsp8FUQ;wwRDTue*k7fY3cv~ delta 11374 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 Its3a{e>B)xF#rGn diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 94efbd7..051894c 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -26,7 +26,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-18 14:01+0100\n" +"POT-Creation-Date: 2022-02-18 14:50+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1796,27 +1796,27 @@ msgstr "In Zwischenablage kopiert" msgid "{} - Shared access removed" msgstr "{} - Zugriff entzogen" -#: konova/utils/mailer.py:91 +#: konova/utils/mailer.py:91 konova/utils/mailer.py:114 msgid "{} - Shared access given" msgstr "{} - Zugriff freigegeben" -#: konova/utils/mailer.py:114 +#: konova/utils/mailer.py:137 msgid "{} - Shared data recorded" msgstr "{} - Freigegebene Daten verzeichnet" -#: konova/utils/mailer.py:137 +#: konova/utils/mailer.py:160 msgid "{} - Shared data unrecorded" msgstr "{} - Freigegebene Daten entzeichnet" -#: konova/utils/mailer.py:160 +#: konova/utils/mailer.py:183 msgid "{} - Shared data deleted" msgstr "{} - Freigegebene Daten gelöscht" -#: konova/utils/mailer.py:183 +#: konova/utils/mailer.py:206 msgid "{} - Shared data checked" msgstr "{} - Freigegebene Daten geprüft" -#: konova/utils/mailer.py:204 templates/email/api/verify_token.html:4 +#: konova/utils/mailer.py:227 templates/email/api/verify_token.html:4 msgid "Request for new API token" msgstr "Anfrage für neuen API Token" @@ -2092,7 +2092,9 @@ msgstr "" #: templates/email/recording/shared_data_recorded.html:19 #: templates/email/recording/shared_data_unrecorded.html:19 #: templates/email/sharing/shared_access_given.html:20 +#: templates/email/sharing/shared_access_given_team.html:20 #: templates/email/sharing/shared_access_removed.html:20 +#: templates/email/sharing/shared_access_removed_team.html:20 msgid "Best regards" msgstr "Beste Grüße" @@ -2179,6 +2181,7 @@ msgstr "" "zugehörigen Kompensationen automatisch entzeichnet worden sind." #: templates/email/sharing/shared_access_given.html:4 +#: templates/email/sharing/shared_access_given_team.html:4 msgid "Access shared" msgstr "Zugriff freigegeben" @@ -2187,10 +2190,12 @@ msgid "the following dataset has just been shared with you" msgstr "der folgende Datensatz wurde soeben für Sie freigegeben " #: templates/email/sharing/shared_access_given.html:16 +#: templates/email/sharing/shared_access_given_team.html:16 msgid "This means you can now edit this dataset." msgstr "Das bedeutet, dass Sie diesen Datensatz nun auch bearbeiten können." #: templates/email/sharing/shared_access_given.html:17 +#: templates/email/sharing/shared_access_given_team.html:17 msgid "" "The shared dataset appears now by default on your overview for this dataset " "type." @@ -2199,6 +2204,7 @@ msgstr "" "Datensatztyp im KSP gelistet." #: templates/email/sharing/shared_access_given.html:27 +#: templates/email/sharing/shared_access_given_team.html:27 msgid "" "Please note: Shared access on an intervention means you automatically have " "editing access to related compensations." @@ -2207,7 +2213,17 @@ msgstr "" "Sie automatisch auch Zugriff auf die zugehörigen Kompensationen erhalten " "haben." +#: templates/email/sharing/shared_access_given_team.html:8 +#: templates/email/sharing/shared_access_removed_team.html:8 +msgid "Hello team" +msgstr "Hallo Team" + +#: templates/email/sharing/shared_access_given_team.html:10 +msgid "the following dataset has just been shared with your team" +msgstr "der folgende Datensatz wurde soeben für Ihr Team freigegeben " + #: templates/email/sharing/shared_access_removed.html:4 +#: templates/email/sharing/shared_access_removed_team.html:4 msgid "Shared access removed" msgstr "Freigegebener Zugriff entzogen" @@ -2219,10 +2235,12 @@ msgstr "" "entzogen: " #: templates/email/sharing/shared_access_removed.html:16 +#: templates/email/sharing/shared_access_removed_team.html:16 msgid "However, you are still able to view the dataset content." msgstr "Sie können den Datensatz aber immer noch im KSP einsehen." #: templates/email/sharing/shared_access_removed.html:17 +#: templates/email/sharing/shared_access_removed_team.html:17 msgid "" "Please use the provided search filter on the dataset`s overview pages to " "find them." @@ -2230,6 +2248,14 @@ msgstr "" "Nutzen Sie hierzu einfach die entsprechenden Suchfilter auf den " "Übersichtsseiten" +#: templates/email/sharing/shared_access_removed_team.html:10 +msgid "" +"your teams shared access, including editing, has been revoked for the " +"dataset " +msgstr "" +"Ihrem Team wurde soeben der bearbeitende Zugriff auf den folgenden Datensatz " +"entzogen: " + #: templates/email/signature.html:6 msgid "Please do not reply on this mail." msgstr "Bitte antworten Sie nicht auf diese Mail." @@ -4118,6 +4144,9 @@ msgstr "" msgid "Unable to connect to qpid with SASL mechanism %s" msgstr "" +#~ msgid "your teams" +#~ msgstr "Team entfernen" + #~ msgid "Remove check to remove access for this user" #~ msgstr "Wählen Sie die Nutzer ab, die keinen Zugriff mehr haben sollen" diff --git a/templates/email/checking/shared_data_checked_team.html b/templates/email/checking/shared_data_checked_team.html new file mode 100644 index 0000000..ee81381 --- /dev/null +++ b/templates/email/checking/shared_data_checked_team.html @@ -0,0 +1,28 @@ +{% load i18n %} + +
+

{% trans 'Shared data checked' %}

+

{{obj_identifier}}

+
+
+ {% trans 'Hello team' %} {{team.name}}, +
+ {% trans 'the following dataset has just been checked' %} +
+ {{obj_identifier}} +
+ {{obj_title}} +
+ {% trans 'This means, the responsible registration office just confirmed the correctness of this dataset.' %} +
+
+ {% trans 'Best regards' %} +
+ KSP +
+
+
+ {% include 'email/signature.html' %} +
+
+ diff --git a/templates/email/deleting/shared_data_deleted_team.html b/templates/email/deleting/shared_data_deleted_team.html new file mode 100644 index 0000000..cedb2a4 --- /dev/null +++ b/templates/email/deleting/shared_data_deleted_team.html @@ -0,0 +1,28 @@ +{% load i18n %} + +
+

{% trans 'Shared data deleted' %}

+

{{obj_identifier}}

+
+
+ {% trans 'Hello team' %} {{team.name}}, +
+ {% trans 'the following dataset has just been deleted' %} +
+ {{obj_identifier}} +
+ "{{obj_title}}" +
+ {% trans 'If this should not have been happened, please contact us. See the signature for details.' %} +
+
+ {% trans 'Best regards' %} +
+ KSP +
+
+
+ {% include 'email/signature.html' %} +
+
+ diff --git a/templates/email/recording/shared_data_recorded_team.html b/templates/email/recording/shared_data_recorded_team.html new file mode 100644 index 0000000..12efa8f --- /dev/null +++ b/templates/email/recording/shared_data_recorded_team.html @@ -0,0 +1,33 @@ +{% load i18n %} + +
+

{% trans 'Shared data recorded' %}

+

{{obj_identifier}}

+
+
+ {% trans 'Hello team' %} {{team.name}}, +
+ {% trans 'the following dataset has just been recorded' %} +
+ {{obj_identifier}} +
+ "{{obj_title}}" +
+ {% trans 'This means the data is now publicly available, e.g. in LANIS' %} +
+
+ {% trans 'Best regards' %} +
+ KSP +
+
+
+ + {% trans 'Please note: Recorded intervention means the compensations are recorded as well.' %} + +
+
+ {% include 'email/signature.html' %} +
+
+ diff --git a/templates/email/recording/shared_data_unrecorded_team.html b/templates/email/recording/shared_data_unrecorded_team.html new file mode 100644 index 0000000..6414155 --- /dev/null +++ b/templates/email/recording/shared_data_unrecorded_team.html @@ -0,0 +1,33 @@ +{% load i18n %} + +
+

{% trans 'Shared data unrecorded' %}

+

{{obj_identifier}}

+
+
+ {% trans 'Hello team' %} {{team.name}}, +
+ {% trans 'the following dataset has just been unrecorded' %} +
+ {{obj_identifier}} +
+ "{{obj_title}}" +
+ {% trans 'This means the data is no longer publicly available.' %} +
+
+ {% trans 'Best regards' %} +
+ KSP +
+
+
+ + {% trans 'Please note: Unrecorded intervention means the compensations are unrecorded as well.' %} + +
+
+ {% include 'email/signature.html' %} +
+
+ diff --git a/templates/email/sharing/shared_access_given_team.html b/templates/email/sharing/shared_access_given_team.html new file mode 100644 index 0000000..990ba2d --- /dev/null +++ b/templates/email/sharing/shared_access_given_team.html @@ -0,0 +1,34 @@ +{% load i18n %} + +
+

{% trans 'Access shared' %}

+

{{obj_identifier}}

+
+
+ {% trans 'Hello team' %} {{team.name}}, +
+ {% trans 'the following dataset has just been shared with your team' %} +
+ {{obj_identifier}} +
+ "{{obj_title}}" +
+ {% trans 'This means you can now edit this dataset.' %} + {% trans 'The shared dataset appears now by default on your overview for this dataset type.' %} +
+
+ {% trans 'Best regards' %} +
+ KSP +
+
+
+ + {% trans 'Please note: Shared access on an intervention means you automatically have editing access to related compensations.' %} + +
+
+ {% include 'email/signature.html' %} +
+
+ diff --git a/templates/email/sharing/shared_access_removed_team.html b/templates/email/sharing/shared_access_removed_team.html new file mode 100644 index 0000000..5472ef7 --- /dev/null +++ b/templates/email/sharing/shared_access_removed_team.html @@ -0,0 +1,29 @@ +{% load i18n %} + +
+

{% trans 'Shared access removed' %}

+

{{obj_identifier}}

+
+
+ {% trans 'Hello team' %} {{team.name}}, +
+ {% trans 'your teams shared access, including editing, has been revoked for the dataset ' %} +
+ {{obj_identifier}} +
+ "{{obj_title}}" +
+ {% trans 'However, you are still able to view the dataset content.' %} + {% trans 'Please use the provided search filter on the dataset`s overview pages to find them.' %} +
+
+ {% trans 'Best regards' %} +
+ KSP +
+
+
+ {% include 'email/signature.html' %} +
+
+ diff --git a/user/models/team.py b/user/models/team.py index c26af3c..e36c95b 100644 --- a/user/models/team.py +++ b/user/models/team.py @@ -1,6 +1,7 @@ from django.db import models from konova.models import UuidModel +from konova.utils.mailer import Mailer class Team(UuidModel): @@ -14,3 +15,81 @@ class Team(UuidModel): def __str__(self): return self.name + + def send_mail_shared_access_given_team(self, obj_identifier, obj_title): + """ Sends a mail to the team members in case of given shared access + + Args: + obj_identifier (): + obj_title (): + + Returns: + + """ + mailer = Mailer() + mailer.send_mail_shared_access_given_team(obj_identifier, obj_title, self) + + def send_mail_shared_access_removed(self, obj_identifier, obj_title): + """ Sends a mail to the team members in case of removed shared access + + Args: + obj_identifier (): + obj_title (): + + Returns: + + """ + mailer = Mailer() + mailer.send_mail_shared_access_removed_team(obj_identifier, obj_title, self) + + def send_mail_shared_data_unrecorded(self, obj_identifier, obj_title): + """ Sends a mail to the team members in case of unrecorded data + + Args: + obj_identifier (): + obj_title (): + + Returns: + + """ + mailer = Mailer() + mailer.send_mail_shared_data_unrecorded_team(obj_identifier, obj_title, self) + + def send_mail_shared_data_recorded(self, obj_identifier, obj_title): + """ Sends a mail to the team members in case of unrecorded data + + Args: + obj_identifier (): + obj_title (): + + Returns: + + """ + mailer = Mailer() + mailer.send_mail_shared_data_recorded_team(obj_identifier, obj_title, self) + + def send_mail_shared_data_checked(self, obj_identifier, obj_title): + """ Sends a mail to the team members in case of checked data + + Args: + obj_identifier (): + obj_title (): + + Returns: + + """ + mailer = Mailer() + mailer.send_mail_shared_data_checked_team(obj_identifier, obj_title, self) + + def send_mail_shared_data_deleted(self, obj_identifier, obj_title): + """ Sends a mail to the team members in case of deleted data + + Args: + obj_identifier (): + obj_title (): + + Returns: + + """ + mailer = Mailer() + mailer.send_mail_shared_data_deleted_team(obj_identifier, obj_title, self) -- 2.38.5