diff --git a/.gitignore b/.gitignore index 2b6313d3..1e56d1d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ # Project exclude paths /venv/ -/.idea/ -*/migrations/ \ No newline at end of file +/.idea/ \ No newline at end of file diff --git a/api/admin.py b/api/admin.py index c5e1fbac..2d214d07 100644 --- a/api/admin.py +++ b/api/admin.py @@ -12,5 +12,9 @@ class APITokenAdmin(admin.ModelAdmin): readonly_fields = [ "token" ] + search_fields = [ + "token" + ] + admin.site.register(APIUserToken, APITokenAdmin) diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py new file mode 100644 index 00000000..ead9fb27 --- /dev/null +++ b/api/migrations/0001_initial.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.3 on 2022-01-28 15:48 + +from django.db import migrations, models +import konova.utils.generators + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='APIUserToken', + fields=[ + ('token', models.CharField(default=konova.utils.generators.generate_token, max_length=1000, primary_key=True, serialize=False)), + ('valid_until', models.DateField(blank=True, help_text='Token is only valid until this date', null=True)), + ('is_active', models.BooleanField(default=False, help_text='Must be activated by an admin')), + ], + ), + ] diff --git a/logs/error.log b/api/migrations/__init__.py similarity index 100% rename from logs/error.log rename to api/migrations/__init__.py diff --git a/api/utils/serializer/v1/serializer.py b/api/utils/serializer/v1/serializer.py index 66421708..f56db81b 100644 --- a/api/utils/serializer/v1/serializer.py +++ b/api/utils/serializer/v1/serializer.py @@ -14,7 +14,8 @@ from django.db.models import QuerySet from api.utils.serializer.serializer import AbstractModelAPISerializer from codelist.models import KonovaCode from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \ - CODELIST_LAW_ID, CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID + CODELIST_LAW_ID, CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, \ + CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID from compensation.models import CompensationAction, UnitChoices, CompensationState from intervention.models import Responsibility, Legal from konova.models import Deadline, DeadlineType @@ -323,6 +324,9 @@ class AbstractCompensationAPISerializerV1Mixin: states = [] for entry in states_data: biotope_type = entry["biotope"] + biotope_details = [ + self._konova_code_from_json(e, CODELIST_BIOTOPES_EXTRA_CODES_ID) for e in entry["biotope_details"] + ] surface = float(entry["surface"]) # Check on validity @@ -331,22 +335,22 @@ 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 - pre_existing_state = states_manager.filter( + state = states_manager.filter( biotope_type__atom_id=biotope_type, surface=surface, ).exclude( id__in=states ).first() - if pre_existing_state is not None: - states.append(pre_existing_state.id) + if state is not None: + states.append(state.id) else: # Create and add id to list - new_state = CompensationState.objects.create( + state = CompensationState.objects.create( biotope_type=self._konova_code_from_json(biotope_type, CODELIST_BIOTOPES_ID), surface=surface ) - states.append(new_state.id) - + states.append(state.id) + state.biotope_type_details.set(biotope_details) states_manager.set(states) return obj @@ -364,6 +368,9 @@ class AbstractCompensationAPISerializerV1Mixin: actions = [] for entry in actions_data: action = entry["action"] + action_details = [ + self._konova_code_from_json(e, CODELIST_COMPENSATION_ACTION_DETAIL_ID) for e in entry["action_details"] + ] amount = float(entry["amount"]) unit = entry["unit"] comment = entry["comment"] @@ -376,7 +383,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 - pre_existing_action = obj.actions.filter( + action_entry = obj.actions.filter( action_type__atom_id=action, amount=amount, unit=unit, @@ -384,17 +391,19 @@ class AbstractCompensationAPISerializerV1Mixin: ).exclude( id__in=actions ).first() - if pre_existing_action is not None: - actions.append(pre_existing_action.id) + if action_entry is not None: + actions.append(action_entry.id) else: # Create and add id to list - new_action = CompensationAction.objects.create( + 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(new_action.id) + actions.append(action_entry.id) + + action_entry.action_type_details.set(action_details) obj.actions.set(actions) return obj @@ -410,6 +419,9 @@ class AbstractCompensationAPISerializerV1Mixin: return [ { "biotope": self._konova_code_to_json(entry.biotope_type), + "biotope_details": [ + self._konova_code_to_json(detail) for detail in entry.biotope_type_details.all() + ], "surface": entry.surface, } for entry in qs @@ -427,6 +439,9 @@ class AbstractCompensationAPISerializerV1Mixin: return [ { "action": self._konova_code_to_json(entry.action_type), + "action_details": [ + self._konova_code_to_json(detail) for detail in entry.action_type_details.all() + ], "amount": entry.amount, "unit": entry.unit, "comment": entry.comment, diff --git a/codelist/admin.py b/codelist/admin.py index f306b041..55ce827d 100644 --- a/codelist/admin.py +++ b/codelist/admin.py @@ -35,6 +35,13 @@ class KonovaCodeAdmin(admin.ModelAdmin): "parent", ] + search_fields = [ + "id", + "atom_id", + "long_name", + "short_name", + ] + #admin.site.register(KonovaCodeList, KonovaCodeListAdmin) admin.site.register(KonovaCode, KonovaCodeAdmin) diff --git a/codelist/management/commands/update_codelist.py b/codelist/management/commands/update_codelist.py index 5bb55b6a..85c90324 100644 --- a/codelist/management/commands/update_codelist.py +++ b/codelist/management/commands/update_codelist.py @@ -13,7 +13,8 @@ from codelist.models import KonovaCode, KonovaCodeList from codelist.settings import CODELIST_INTERVENTION_HANDLER_ID, CODELIST_CONSERVATION_OFFICE_ID, \ 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_BASE_URL, CODELIST_PROCESS_TYPE_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \ + CODELIST_COMPENSATION_ACTION_DETAIL_ID from konova.management.commands.setup import BaseKonovaCommand from konova.settings import PROXIES @@ -33,10 +34,12 @@ class Command(BaseKonovaCommand): CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, + CODELIST_BIOTOPES_EXTRA_CODES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, + CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, CODELIST_PROCESS_TYPE_ID, ] diff --git a/codelist/migrations/0001_initial.py b/codelist/migrations/0001_initial.py new file mode 100644 index 00000000..7962e3c7 --- /dev/null +++ b/codelist/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 3.1.3 on 2022-01-14 08:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='KonovaCode', + fields=[ + ('id', models.IntegerField(help_text='Regular Id', primary_key=True, serialize=False)), + ('atom_id', models.IntegerField(blank=True, help_text='AtomId; Identifies this code uniquely over all NatIT projects; Duplicates possible', null=True)), + ('short_name', models.CharField(blank=True, help_text='Short version of long name', max_length=500, null=True)), + ('long_name', models.CharField(blank=True, max_length=1000, null=True)), + ('is_selectable', models.BooleanField(default=False, help_text='Whether this code shall be used for any select actions or not')), + ('is_leaf', models.BooleanField(default=False, help_text='Whether this code has children or not')), + ('is_archived', models.BooleanField(default=False, help_text='Whether this code is archived or not')), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='codelist.konovacode')), + ], + ), + migrations.CreateModel( + name='KonovaCodeList', + fields=[ + ('id', models.IntegerField(help_text='List identifier', primary_key=True, serialize=False)), + ('codes', models.ManyToManyField(blank=True, help_text='Codes for this list', related_name='code_lists', to='codelist.KonovaCode')), + ], + ), + ] diff --git a/codelist/migrations/__init__.py b/codelist/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/codelist/settings.py b/codelist/settings.py index 15c523ac..598a3a66 100644 --- a/codelist/settings.py +++ b/codelist/settings.py @@ -14,11 +14,13 @@ 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_EXTRA_CODES_ID = 975 # CLZusatzbezeichnung CODELIST_LAW_ID = 1048 # CLVerfahrensrecht CODELIST_PROCESS_TYPE_ID = 44382 # CLVerfahrenstyp CODELIST_COMPENSATION_HANDLER_ID = 1052 # CLEingreifer CODELIST_COMPENSATION_ACTION_ID = 1026 # CLMassnahmedetail +CODELIST_COMPENSATION_ACTION_DETAIL_ID = 1035 # CLZusatzmerkmal CODELIST_COMPENSATION_ACTION_CLASS_ID = 1034 # CLMassnahmeklasse CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID = 1028 # CLMassnahmetyp, CEF and stuff CODELIST_COMPENSATION_FUNDING_ID = 1049 # CLKombimassnahme diff --git a/compensation/admin.py b/compensation/admin.py index 735ffb45..6519a1e8 100644 --- a/compensation/admin.py +++ b/compensation/admin.py @@ -2,7 +2,100 @@ from django.contrib import admin from compensation.models import Compensation, CompensationAction, CompensationState, Payment, \ EcoAccountDeduction, EcoAccount -from konova.admin import BaseObjectAdmin +from konova.admin import BaseObjectAdmin, BaseResourceAdmin + + +class AbstractCompensationAdmin(BaseObjectAdmin): + list_display = [ + "id", + "identifier", + "title", + "created", + "deleted", + ] + + def get_fields(self, request, obj=None): + return super().get_fields(request, obj) + [ + "identifier", + "title", + "comment", + "after_states", + "before_states", + ] + + def get_readonly_fields(self, request, obj=None): + return super().get_readonly_fields(request, obj) + [ + "after_states", + "before_states", + ] + + +class CompensationAdmin(AbstractCompensationAdmin): + autocomplete_fields = [ + "intervention", + ] + + def get_fields(self, request, obj=None): + return super().get_fields(request, obj) + [ + "is_cef", + "is_coherence_keeping", + "intervention", + ] + + +class EcoAccountAdmin(AbstractCompensationAdmin): + list_display = [ + "id", + "identifier", + "title", + "created", + "deleted", + ] + + filter_horizontal = [ + "users" + ] + + def get_fields(self, request, obj=None): + return super().get_fields(request, obj) + [ + "deductable_surface", + "users" + ] + + +class PaymentAdmin(admin.ModelAdmin): + list_display = [ + "id", + "amount", + "due_on", + ] + + +class EcoAccountDeductionAdmin(BaseResourceAdmin): + list_display = [ + "id", + "account", + "intervention", + "surface", + ] + search_fields = [ + "account__identifier", + "account__title", + "intervention__identifier", + "intervention__title", + "surface", + ] + autocomplete_fields = [ + "account", + "intervention", + ] + + def get_fields(self, request, obj=None): + return super().get_fields(request, obj) + [ + "account", + "intervention", + "surface", + ] class CompensationStateAdmin(admin.ModelAdmin): @@ -23,44 +116,11 @@ class CompensationActionAdmin(admin.ModelAdmin): ] -class CompensationAdmin(BaseObjectAdmin): - list_display = [ - "id", - "identifier", - "title", - "created", - "deleted", - ] - - -class EcoAccountAdmin(admin.ModelAdmin): - list_display = [ - "id", - "identifier", - "title", - ] - - -class PaymentAdmin(admin.ModelAdmin): - list_display = [ - "id", - "amount", - "due_on", - ] - - -class EcoAccountDeductionAdmin(admin.ModelAdmin): - list_display = [ - "id", - "account", - "intervention", - "surface", - ] - - admin.site.register(Compensation, CompensationAdmin) -admin.site.register(Payment, PaymentAdmin) -admin.site.register(CompensationAction, CompensationActionAdmin) -admin.site.register(CompensationState, CompensationStateAdmin) admin.site.register(EcoAccount, EcoAccountAdmin) admin.site.register(EcoAccountDeduction, EcoAccountDeductionAdmin) + +# For a more cleaner admin interface these rarely used admin views are not important for deployment +#admin.site.register(Payment, PaymentAdmin) +#admin.site.register(CompensationAction, CompensationActionAdmin) +#admin.site.register(CompensationState, CompensationStateAdmin) \ No newline at end of file diff --git a/compensation/forms/modalForms.py b/compensation/forms/modalForms.py index 885db951..dd9f4c70 100644 --- a/compensation/forms/modalForms.py +++ b/compensation/forms/modalForms.py @@ -14,7 +14,8 @@ from django.shortcuts import render 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 +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 konova.contexts import BaseContext from konova.forms import BaseModalForm, NewDocumentForm @@ -127,6 +128,23 @@ class NewStateModalForm(BaseModalForm): } ), ) + biotope_extra = forms.ModelMultipleChoiceField( + label=_("Biotope additional type"), + label_suffix="", + required=False, + help_text=_("Select an additional biotope type"), + queryset=KonovaCode.objects.filter( + is_archived=False, + is_leaf=True, + code_lists__in=[CODELIST_BIOTOPES_EXTRA_CODES_ID], + ), + widget=autocomplete.ModelSelect2Multiple( + url="codes-biotope-extra-type-autocomplete", + attrs={ + "data-placeholder": _("Biotope additional type"), + } + ), + ) surface = forms.DecimalField( min_value=0.00, decimal_places=2, @@ -283,6 +301,23 @@ class NewActionModalForm(BaseModalForm): } ), ) + action_type_details = forms.ModelMultipleChoiceField( + label=_("Action Type detail"), + label_suffix="", + required=False, + help_text=_("Select the action type detail"), + queryset=KonovaCode.objects.filter( + is_archived=False, + is_leaf=True, + code_lists__in=[CODELIST_COMPENSATION_ACTION_DETAIL_ID], + ), + widget=autocomplete.ModelSelect2Multiple( + url="codes-compensation-action-detail-autocomplete", + attrs={ + "data-placeholder": _("Action Type detail"), + } + ), + ) unit = forms.ChoiceField( label=_("Unit"), label_suffix="", diff --git a/compensation/migrations/0001_initial.py b/compensation/migrations/0001_initial.py new file mode 100644 index 00000000..8866b840 --- /dev/null +++ b/compensation/migrations/0001_initial.py @@ -0,0 +1,115 @@ +# Generated by Django 3.1.3 on 2022-01-14 08:36 + +import django.core.validators +from django.db import migrations, models +import konova.models.document +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Compensation', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('identifier', models.CharField(blank=True, max_length=1000, null=True)), + ('title', models.CharField(blank=True, max_length=1000, null=True)), + ('comment', models.TextField(blank=True, null=True)), + ('is_cef', models.BooleanField(blank=True, default=False, help_text="Flag if compensation is a 'CEF-Maßnahme'", null=True)), + ('is_coherence_keeping', models.BooleanField(blank=True, default=False, help_text="Flag if compensation is a 'Kohärenzsicherung'", null=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='CompensationAction', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('amount', models.FloatField()), + ('unit', models.CharField(blank=True, choices=[('cm', 'cm'), ('m', 'm'), ('km', 'km'), ('qm', 'm²'), ('ha', 'ha'), ('pcs', 'Pieces')], max_length=100, null=True)), + ('comment', models.TextField(blank=True, help_text='Additional comment', null=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='CompensationDocument', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(blank=True, max_length=500, null=True)), + ('date_of_creation', models.DateField()), + ('comment', models.TextField()), + ('file', models.FileField(max_length=1000, upload_to=konova.models.document.generate_document_file_upload_path)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='CompensationState', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('surface', models.FloatField()), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='EcoAccount', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('identifier', models.CharField(blank=True, max_length=1000, null=True)), + ('title', models.CharField(blank=True, max_length=1000, null=True)), + ('comment', models.TextField(blank=True, null=True)), + ('access_token', models.CharField(blank=True, help_text='Used for sharing access', max_length=255, null=True)), + ('deductable_surface', models.FloatField(blank=True, default=0, help_text='Amount of deductable surface - can be lower than the total surface due to deduction limitations', null=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='EcoAccountDeduction', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('surface', models.FloatField(blank=True, help_text='Amount deducted (m²)', null=True, validators=[django.core.validators.MinValueValidator(limit_value=0.0)])), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='EcoAccountDocument', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(blank=True, max_length=500, null=True)), + ('date_of_creation', models.DateField()), + ('comment', models.TextField()), + ('file', models.FileField(max_length=1000, upload_to=konova.models.document.generate_document_file_upload_path)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Payment', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('amount', models.FloatField(validators=[django.core.validators.MinValueValidator(limit_value=0.0)])), + ('due_on', models.DateField(blank=True, null=True)), + ('comment', models.TextField(blank=True, help_text="Refers to german money transfer 'Verwendungszweck'", null=True)), + ], + options={ + 'ordering': ['-amount'], + }, + ), + ] diff --git a/compensation/migrations/0002_auto_20220114_0936.py b/compensation/migrations/0002_auto_20220114_0936.py new file mode 100644 index 00000000..624007fb --- /dev/null +++ b/compensation/migrations/0002_auto_20220114_0936.py @@ -0,0 +1,227 @@ +# Generated by Django 3.1.3 on 2022-01-14 08:36 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('intervention', '0001_initial'), + ('konova', '0001_initial'), + ('compensation', '0001_initial'), + ('user', '0001_initial'), + ('codelist', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='payment', + name='created', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='payment', + name='intervention', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='intervention.intervention'), + ), + migrations.AddField( + model_name='payment', + name='modified', + field=models.ForeignKey(blank=True, help_text='Last modified', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='ecoaccountdocument', + name='created', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='ecoaccountdocument', + name='instance', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='compensation.ecoaccount'), + ), + migrations.AddField( + model_name='ecoaccountdocument', + name='modified', + field=models.ForeignKey(blank=True, help_text='Last modified', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='ecoaccountdeduction', + name='account', + field=models.ForeignKey(blank=True, help_text='Deducted from', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deductions', to='compensation.ecoaccount'), + ), + migrations.AddField( + model_name='ecoaccountdeduction', + name='created', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='ecoaccountdeduction', + name='intervention', + field=models.ForeignKey(blank=True, help_text='Deducted for', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='deductions', to='intervention.intervention'), + ), + migrations.AddField( + model_name='ecoaccountdeduction', + name='modified', + field=models.ForeignKey(blank=True, help_text='Last modified', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='ecoaccount', + name='actions', + field=models.ManyToManyField(blank=True, help_text="Refers to 'Maßnahmen'", to='compensation.CompensationAction'), + ), + migrations.AddField( + model_name='ecoaccount', + name='after_states', + field=models.ManyToManyField(blank=True, help_text="Refers to 'Zielzustand Biotop'", related_name='_ecoaccount_after_states_+', to='compensation.CompensationState'), + ), + migrations.AddField( + model_name='ecoaccount', + name='before_states', + field=models.ManyToManyField(blank=True, help_text="Refers to 'Ausgangszustand Biotop'", related_name='_ecoaccount_before_states_+', to='compensation.CompensationState'), + ), + migrations.AddField( + model_name='ecoaccount', + name='created', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='ecoaccount', + name='deadlines', + field=models.ManyToManyField(blank=True, related_name='_ecoaccount_deadlines_+', to='konova.Deadline'), + ), + migrations.AddField( + model_name='ecoaccount', + name='deleted', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='ecoaccount', + name='geometry', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.geometry'), + ), + migrations.AddField( + model_name='ecoaccount', + name='legal', + field=models.OneToOneField(blank=True, help_text='Holds data on legal dates or law', null=True, on_delete=django.db.models.deletion.SET_NULL, to='intervention.legal'), + ), + migrations.AddField( + model_name='ecoaccount', + name='log', + field=models.ManyToManyField(blank=True, editable=False, help_text='Keeps all user actions of an object', to='user.UserActionLogEntry'), + ), + migrations.AddField( + model_name='ecoaccount', + name='modified', + field=models.ForeignKey(blank=True, help_text='Last modified', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='ecoaccount', + name='recorded', + field=models.OneToOneField(blank=True, help_text='Holds data on user and timestamp of this action', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='ecoaccount', + name='responsible', + field=models.OneToOneField(blank=True, help_text="Holds data on responsible organizations ('Zulassungsbehörde', 'Eintragungsstelle') and handler", null=True, on_delete=django.db.models.deletion.SET_NULL, to='intervention.responsibility'), + ), + migrations.AddField( + model_name='ecoaccount', + name='users', + field=models.ManyToManyField(help_text='Users having access (data shared with)', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='compensationstate', + name='biotope_type', + field=models.ForeignKey(blank=True, limit_choices_to={'code_lists__in': [974], 'is_archived': False, 'is_selectable': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, to='codelist.konovacode'), + ), + migrations.AddField( + model_name='compensationdocument', + name='created', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='compensationdocument', + name='instance', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='compensation.compensation'), + ), + migrations.AddField( + model_name='compensationdocument', + name='modified', + field=models.ForeignKey(blank=True, help_text='Last modified', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='compensationaction', + name='action_type', + field=models.ForeignKey(blank=True, limit_choices_to={'code_lists__in': [1026], 'is_archived': False, 'is_selectable': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, to='codelist.konovacode'), + ), + migrations.AddField( + model_name='compensationaction', + name='created', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='compensationaction', + name='modified', + field=models.ForeignKey(blank=True, help_text='Last modified', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='compensation', + name='actions', + field=models.ManyToManyField(blank=True, help_text="Refers to 'Maßnahmen'", to='compensation.CompensationAction'), + ), + migrations.AddField( + model_name='compensation', + name='after_states', + field=models.ManyToManyField(blank=True, help_text="Refers to 'Zielzustand Biotop'", related_name='_compensation_after_states_+', to='compensation.CompensationState'), + ), + migrations.AddField( + model_name='compensation', + name='before_states', + field=models.ManyToManyField(blank=True, help_text="Refers to 'Ausgangszustand Biotop'", related_name='_compensation_before_states_+', to='compensation.CompensationState'), + ), + migrations.AddField( + model_name='compensation', + name='created', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='compensation', + name='deadlines', + field=models.ManyToManyField(blank=True, related_name='_compensation_deadlines_+', to='konova.Deadline'), + ), + migrations.AddField( + model_name='compensation', + name='deleted', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='compensation', + name='geometry', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.geometry'), + ), + migrations.AddField( + model_name='compensation', + name='intervention', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='compensations', to='intervention.intervention'), + ), + migrations.AddField( + model_name='compensation', + name='log', + field=models.ManyToManyField(blank=True, editable=False, help_text='Keeps all user actions of an object', to='user.UserActionLogEntry'), + ), + migrations.AddField( + model_name='compensation', + name='modified', + field=models.ForeignKey(blank=True, help_text='Last modified', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'), + ), + migrations.AddField( + model_name='compensation', + name='responsible', + field=models.OneToOneField(blank=True, help_text="Holds data on responsible organizations ('Zulassungsbehörde', 'Eintragungsstelle') and handler", null=True, on_delete=django.db.models.deletion.SET_NULL, to='intervention.responsibility'), + ), + ] diff --git a/compensation/migrations/0003_auto_20220202_0846.py b/compensation/migrations/0003_auto_20220202_0846.py new file mode 100644 index 00000000..8542c0eb --- /dev/null +++ b/compensation/migrations/0003_auto_20220202_0846.py @@ -0,0 +1,35 @@ +# Generated by Django 3.1.3 on 2022-02-02 07:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('codelist', '0001_initial'), + ('compensation', '0002_auto_20220114_0936'), + ] + + operations = [ + migrations.AddField( + model_name='compensationaction', + name='action_type_details', + field=models.ManyToManyField(blank=True, limit_choices_to={'code_lists__in': [1035], 'is_archived': False, 'is_selectable': True}, related_name='_compensationaction_action_type_details_+', to='codelist.KonovaCode'), + ), + migrations.AddField( + model_name='compensationstate', + name='biotope_type_details', + field=models.ManyToManyField(blank=True, limit_choices_to={'code_lists__in': [975], 'is_archived': False, 'is_selectable': True}, related_name='_compensationstate_biotope_type_details_+', to='codelist.KonovaCode'), + ), + migrations.AlterField( + model_name='compensationaction', + name='action_type', + field=models.ForeignKey(blank=True, limit_choices_to={'code_lists__in': [1026], 'is_archived': False, 'is_selectable': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='codelist.konovacode'), + ), + migrations.AlterField( + model_name='compensationstate', + name='biotope_type', + field=models.ForeignKey(blank=True, limit_choices_to={'code_lists__in': [974], 'is_archived': False, 'is_selectable': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='codelist.konovacode'), + ), + ] diff --git a/compensation/migrations/__init__.py b/compensation/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/compensation/models/action.py b/compensation/models/action.py index 35d4c3f7..087f48be 100644 --- a/compensation/models/action.py +++ b/compensation/models/action.py @@ -9,7 +9,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from codelist.models import KonovaCode -from codelist.settings import CODELIST_COMPENSATION_ACTION_ID +from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID from compensation.managers import CompensationActionManager from konova.models import BaseResource @@ -39,7 +39,18 @@ class CompensationAction(BaseResource): "code_lists__in": [CODELIST_COMPENSATION_ACTION_ID], "is_selectable": True, "is_archived": False, - } + }, + related_name='+', + ) + action_type_details = models.ManyToManyField( + KonovaCode, + blank=True, + limit_choices_to={ + "code_lists__in": [CODELIST_COMPENSATION_ACTION_DETAIL_ID], + "is_selectable": True, + "is_archived": False, + }, + related_name='+', ) amount = models.FloatField() unit = models.CharField(max_length=100, null=True, blank=True, choices=UnitChoices.choices) @@ -48,7 +59,7 @@ class CompensationAction(BaseResource): objects = CompensationActionManager() def __str__(self): - return "{} | {} {}".format(self.action_type, self.amount, self.unit) + return f"{self.action_type} | {self.amount} {self.unit}" @property def unit_humanize(self): diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py index 89d6dc2f..de5a3461 100644 --- a/compensation/models/compensation.py +++ b/compensation/models/compensation.py @@ -95,6 +95,8 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin): comment=form_data["comment"], created=user_action, ) + comp_action_details = form_data["action_type_details"] + comp_action.action_type_details.set(comp_action_details) self.actions.add(comp_action) return comp_action @@ -114,6 +116,8 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin): biotope_type=form_data["biotope_type"], surface=form_data["surface"], ) + state_additional_types = form_data["biotope_extra"] + state.biotope_type_details.set(state_additional_types) if is_before_state: self.before_states.add(state) else: @@ -310,17 +314,19 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin): ) return docs - def mark_as_edited(self, user: User, request: HttpRequest = None, edit_comment: str = None): + def mark_as_edited(self, user: User, request: HttpRequest = None, edit_comment: str = None, reset_recorded: bool = True): """ Performs internal logic for setting the recordedd/checked state of the related intervention Args: user (User): The performing user request (HttpRequest): The performing request + edit_comment (str): Additional comment for the log entry + reset_recorded (bool): Whether the record-state of the object should be reset Returns: """ - self.intervention.mark_as_edited(user, request, edit_comment) + self.intervention.mark_as_edited(user, request, edit_comment, reset_recorded) def is_ready_for_publish(self) -> bool: """ Not inherited by RecordableObjectMixin diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py index 84a61409..afe556f9 100644 --- a/compensation/models/eco_account.py +++ b/compensation/models/eco_account.py @@ -46,7 +46,7 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix objects = EcoAccountManager() def __str__(self): - return "{}".format(self.identifier) + return f"{self.identifier} ({self.title})" def clean(self): # Deductable surface can not be larger than added states after surface diff --git a/compensation/models/state.py b/compensation/models/state.py index 01aad147..ce0fc699 100644 --- a/compensation/models/state.py +++ b/compensation/models/state.py @@ -8,7 +8,7 @@ Created on: 16.11.21 from django.db import models from codelist.models import KonovaCode -from codelist.settings import CODELIST_BIOTOPES_ID +from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID from compensation.managers import CompensationStateManager from konova.models import UuidModel @@ -26,11 +26,22 @@ class CompensationState(UuidModel): "code_lists__in": [CODELIST_BIOTOPES_ID], "is_selectable": True, "is_archived": False, - } + }, + related_name='+', + ) + biotope_type_details = models.ManyToManyField( + KonovaCode, + blank=True, + limit_choices_to={ + "code_lists__in": [CODELIST_BIOTOPES_EXTRA_CODES_ID], + "is_selectable": True, + "is_archived": False, + }, + related_name='+', ) surface = models.FloatField() objects = CompensationStateManager() def __str__(self): - return "{} | {} m²".format(self.biotope_type, self.surface) + return f"{self.biotope_type} | {self.surface} m²" diff --git a/compensation/templates/compensation/detail/compensation/includes/actions.html b/compensation/templates/compensation/detail/compensation/includes/actions.html index 92cf45ff..648e777a 100644 --- a/compensation/templates/compensation/detail/compensation/includes/actions.html +++ b/compensation/templates/compensation/detail/compensation/includes/actions.html @@ -24,9 +24,12 @@ - + @@ -46,9 +49,14 @@ + -
+ {% trans 'Action type' %} + {% trans 'Action type details' %} + {% trans 'Amount' context 'Compensation' %} {{ action.action_type }} + {% for detail in action.action_type_details.all %} +
{{detail.long_name}}
+ {% endfor %} +
{{ action.amount|floatformat:2|intcomma }} {{ action.unit_humanize }} {{ action.comment|default_if_none:"" }} + {% if is_default_member and has_access %}