Merge pull request '146_Minor_improvements' (#150) from 146_Minor_improvements into master

Reviewed-on: SGD-Nord/konova#150
This commit is contained in:
mpeltriaux 2022-04-14 14:13:14 +02:00
commit d2ec3d9c08
29 changed files with 271 additions and 131 deletions

View File

@ -33,6 +33,7 @@ class KonovaCodeAdmin(admin.ModelAdmin):
"is_selectable", "is_selectable",
"is_leaf", "is_leaf",
"parent", "parent",
"found_in_codelists",
] ]
search_fields = [ search_fields = [
@ -42,6 +43,12 @@ class KonovaCodeAdmin(admin.ModelAdmin):
"short_name", "short_name",
] ]
def found_in_codelists(self, obj):
codelists = KonovaCodeList.objects.filter(
codes__in=[obj]
).values_list("id", flat=True)
codelists = "\n".join(str(x) for x in codelists)
return codelists
#admin.site.register(KonovaCodeList, KonovaCodeListAdmin) #admin.site.register(KonovaCodeList, KonovaCodeListAdmin)
admin.site.register(KonovaCode, KonovaCodeAdmin) admin.site.register(KonovaCode, KonovaCodeAdmin)

View File

@ -21,16 +21,30 @@ class AbstractCompensationAdmin(BaseObjectAdmin):
"identifier", "identifier",
"title", "title",
"comment", "comment",
"after_states", "list_after_states",
"before_states", "list_before_states",
"geometry",
] ]
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
return super().get_readonly_fields(request, obj) + [ return super().get_readonly_fields(request, obj) + [
"after_states", "list_after_states",
"before_states", "list_before_states",
"geometry",
] ]
def list_after_states(self, obj):
states = obj.after_states.all()
states = [str(state) for state in states]
states = "\n".join(states)
return states
def list_before_states(self, obj):
states = obj.before_states.all()
states = [str(state) for state in states]
states = "\n".join(states)
return states
class CompensationAdmin(AbstractCompensationAdmin): class CompensationAdmin(AbstractCompensationAdmin):
autocomplete_fields = [ autocomplete_fields = [

View File

@ -181,9 +181,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
""" """
if value is None: if value is None:
value = User.objects.none() value = User.objects.none()
has_access = value.filter( has_access = record.is_shared_with(self.user)
id=self.user.id
).exists()
html = self.render_icn( html = self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"), tooltip=_("Full access granted") if has_access else _("Access not granted"),
@ -343,7 +341,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
html = "" html = ""
# Do not use value in here, since value does use unprefetched 'users' manager, where record has already # Do not use value in here, since value does use unprefetched 'users' manager, where record has already
# prefetched users data # prefetched users data
has_access = self.user in record.users.all() has_access = record.is_shared_with(self.user)
html += self.render_icn( html += self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"), tooltip=_("Full access granted") if has_access else _("Access not granted"),
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit", icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",

View File

@ -41,14 +41,7 @@
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> {% include 'konova/includes/report/qrcodes.html' %}
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode|safe }}
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis|safe }}
</div>
</div> </div>
</div> </div>

View File

@ -54,14 +54,7 @@
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> {% include 'konova/includes/report/qrcodes.html' %}
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode|safe }}
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis|safe }}
</div>
</div> </div>
</div> </div>

View File

@ -596,14 +596,12 @@ def report_view(request: HttpRequest, id: str):
instance=comp instance=comp
) )
parcels = comp.get_underlying_parcels() parcels = comp.get_underlying_parcels()
qrcode_img = generate_qr_code(
request.build_absolute_uri(reverse("compensation:report", args=(id,))), qrcode_url = request.build_absolute_uri(reverse("compensation:report", args=(id,)))
10 qrcode_img = generate_qr_code(qrcode_url, 10)
) qrcode_lanis_url = comp.get_LANIS_link()
qrcode_img_lanis = generate_qr_code( qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
comp.get_LANIS_link(),
7
)
# Order states by surface # Order states by surface
before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type") before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type") after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
@ -611,8 +609,14 @@ def report_view(request: HttpRequest, id: str):
context = { context = {
"obj": comp, "obj": comp,
"qrcode": qrcode_img, "qrcode": {
"qrcode_lanis": qrcode_img_lanis, "img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"has_access": False, # disables action buttons during rendering "has_access": False, # disables action buttons during rendering
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,

View File

@ -731,18 +731,16 @@ def report_view(request:HttpRequest, id: str):
instance=acc instance=acc
) )
parcels = acc.get_underlying_parcels() parcels = acc.get_underlying_parcels()
qrcode_img = generate_qr_code(
request.build_absolute_uri(reverse("ema:report", args=(id,))), qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
10 qrcode_img = generate_qr_code(qrcode_url, 10)
) qrcode_lanis_url = acc.get_LANIS_link()
qrcode_img_lanis = generate_qr_code( qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
acc.get_LANIS_link(),
7
)
# Order states by surface # Order states by surface
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent") before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent") after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
actions = acc.actions.all().select_related("action_type__parent") actions = acc.actions.all().prefetch_related("action_type__parent")
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier) # Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
deductions = acc.deductions.all()\ deductions = acc.deductions.all()\
@ -752,8 +750,14 @@ def report_view(request:HttpRequest, id: str):
context = { context = {
"obj": acc, "obj": acc,
"qrcode": qrcode_img, "qrcode": {
"qrcode_lanis": qrcode_img_lanis, "img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"has_access": False, # disables action buttons during rendering "has_access": False, # disables action buttons during rendering
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,

View File

@ -151,9 +151,7 @@ class EmaTable(BaseTable, TableRenderMixin):
""" """
html = "" html = ""
has_access = value.filter( has_access = record.is_shared_with(self.user)
id=self.user.id
).exists()
html += self.render_icn( html += self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"), tooltip=_("Full access granted") if has_access else _("Access not granted"),

View File

@ -41,14 +41,7 @@
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> {% include 'konova/includes/report/qrcodes.html' %}
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode|safe }}
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis|safe }}
</div>
</div> </div>
</div> </div>

View File

@ -563,14 +563,12 @@ def report_view(request:HttpRequest, id: str):
instance=ema, instance=ema,
) )
parcels = ema.get_underlying_parcels() parcels = ema.get_underlying_parcels()
qrcode_img = generate_qr_code(
request.build_absolute_uri(reverse("ema:report", args=(id,))), qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
10 qrcode_img = generate_qr_code(qrcode_url, 10)
) qrcode_lanis_url = ema.get_LANIS_link()
qrcode_img_lanis = generate_qr_code( qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
ema.get_LANIS_link(),
7
)
# Order states by surface # Order states by surface
before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type") before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type")
after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type") after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type")
@ -578,8 +576,14 @@ def report_view(request:HttpRequest, id: str):
context = { context = {
"obj": ema, "obj": ema,
"qrcode": qrcode_img, "qrcode": {
"qrcode_lanis": qrcode_img_lanis, "img": qrcode_img,
"url": qrcode_url
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url
},
"has_access": False, # disables action buttons during rendering "has_access": False, # disables action buttons during rendering
"before_states": before_states, "before_states": before_states,
"after_states": after_states, "after_states": after_states,

View File

@ -25,12 +25,14 @@ class InterventionAdmin(BaseObjectAdmin):
"checked", "checked",
"recorded", "recorded",
"users", "users",
"geometry",
] ]
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
return super().get_readonly_fields(request, obj) + [ return super().get_readonly_fields(request, obj) + [
"checked", "checked",
"recorded", "recorded",
"geometry",
] ]

View File

@ -177,9 +177,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
""" """
html = "" html = ""
has_access = value.filter( has_access = record.is_shared_with(self.user)
id=self.user.id
).exists()
html += self.render_icn( html += self.render_icn(
tooltip=_("Full access granted") if has_access else _("Access not granted"), tooltip=_("Full access granted") if has_access else _("Access not granted"),

View File

@ -100,14 +100,7 @@
{% include 'konova/includes/parcels.html' %} {% include 'konova/includes/parcels.html' %}
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-md-6 col-lg-6"> {% include 'konova/includes/report/qrcodes.html' %}
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode|safe }}
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis|safe }}
</div>
</div> </div>
</div> </div>

View File

@ -693,19 +693,22 @@ def report_view(request:HttpRequest, id: str):
distinct_deductions = intervention.deductions.all().distinct( distinct_deductions = intervention.deductions.all().distinct(
"account" "account"
) )
qrcode_img = generate_qr_code( qrcode_url = request.build_absolute_uri(reverse("intervention:report", args=(id,)))
request.build_absolute_uri(reverse("intervention:report", args=(id,))), qrcode_img = generate_qr_code(qrcode_url, 10)
10 qrcode_lanis_url = intervention.get_LANIS_link()
) qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
qrcode_img_lanis = generate_qr_code(
intervention.get_LANIS_link(),
7
)
context = { context = {
"obj": intervention, "obj": intervention,
"deductions": distinct_deductions, "deductions": distinct_deductions,
"qrcode": qrcode_img, "qrcode": {
"qrcode_lanis": qrcode_img_lanis, "img": qrcode_img,
"url": qrcode_url,
},
"qrcode_lanis": {
"img": qrcode_img_lanis,
"url": qrcode_lanis_url,
},
"geom_form": geom_form, "geom_form": geom_form,
"parcels": parcels, "parcels": parcels,
TAB_TITLE_IDENTIFIER: tab_title, TAB_TITLE_IDENTIFIER: tab_title,

View File

@ -8,6 +8,7 @@ Created on: 22.07.21
from django.contrib import admin from django.contrib import admin
from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
from user.models import UserAction from user.models import UserAction
@ -16,7 +17,22 @@ class GeometryAdmin(admin.ModelAdmin):
list_display = [ list_display = [
"id", "id",
"created", "created",
"st_area",
] ]
readonly_fields = [
"st_area",
"created",
"modified",
]
def st_area(self, obj):
val = None
geom = obj.geom
if geom is not None:
geom.transform(ct=DEFAULT_SRID_RLP)
val = geom.area
return val
st_area.short_description = f"Area (srid={DEFAULT_SRID_RLP})"
class ParcelAdmin(admin.ModelAdmin): class ParcelAdmin(admin.ModelAdmin):

View File

@ -5,6 +5,10 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 04.01.22 Created on: 04.01.22
""" """
import datetime
from django.contrib.gis.db.models.functions import Area
from konova.management.commands.setup import BaseKonovaCommand from konova.management.commands.setup import BaseKonovaCommand
from konova.models import Geometry, Parcel, District from konova.models import Geometry, Parcel, District
@ -23,12 +27,21 @@ class Command(BaseKonovaCommand):
num_parcels_before = Parcel.objects.count() num_parcels_before = Parcel.objects.count()
num_districts_before = District.objects.count() num_districts_before = District.objects.count()
self._write_warning("=== Update parcels and districts ===") self._write_warning("=== Update parcels and districts ===")
# Order geometries by size to process smaller once at first
geometries = Geometry.objects.all().exclude( geometries = Geometry.objects.all().exclude(
geom=None geom=None
).annotate(area=Area("geom")).order_by(
'area'
) )
self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...") self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...")
i = 0
num_geoms = geometries.count()
for geometry in geometries: for geometry in geometries:
self._write_warning(f"--- {datetime.datetime.now()} Process {geometry.id} now ...")
geometry.update_parcels() geometry.update_parcels()
self._write_warning(f"--- Processed {geometry.get_underlying_parcels().count()} underlying parcels")
i += 1
self._write_warning(f"--- {i}/{num_geoms} processed")
num_parcels_after = Parcel.objects.count() num_parcels_after = Parcel.objects.count()
num_districts_after = District.objects.count() num_districts_after = District.objects.count()

View File

@ -20,6 +20,9 @@ class Geometry(BaseResource):
from konova.settings import DEFAULT_SRID from konova.settings import DEFAULT_SRID
geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID) geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID)
def __str__(self):
return str(self.id)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super().save(*args, **kwargs) super().save(*args, **kwargs)
self.check_for_conflicts() self.check_for_conflicts()

View File

@ -289,6 +289,8 @@ class RecordableObjectMixin(models.Model):
from user.models import UserActionLogEntry from user.models import UserActionLogEntry
if self.recorded: if self.recorded:
return None return None
self.unshare_with_default_users()
action = UserActionLogEntry.get_recorded_action(user) action = UserActionLogEntry.get_recorded_action(user)
self.recorded = action self.recorded = action
self.save() self.save()
@ -608,6 +610,26 @@ class ShareableObjectMixin(models.Model):
""" """
raise NotImplementedError("Must be implemented in subclasses!") raise NotImplementedError("Must be implemented in subclasses!")
def unshare_with_default_users(self):
""" Removes all shared users from direct shared access which are only default group users
Returns:
"""
from konova.utils.user_checks import is_default_group_only
users = self.shared_users
cleaned_users = []
default_users = []
for user in users:
if not is_default_group_only(user):
cleaned_users.append(user)
else:
default_users.append(user)
self.share_with_user_list(cleaned_users)
for user in default_users:
celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user.id)
class GeoReferencedMixin(models.Model): class GeoReferencedMixin(models.Model):
geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL) geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL)

View File

@ -19,6 +19,6 @@ PAGE_SIZE_OPTIONS_TUPLES = [
(50, 50), (50, 50),
(100, 100), (100, 100),
] ]
PAGE_SIZE_DEFAULT = 5 PAGE_SIZE_DEFAULT = 10
PAGE_SIZE_MAX = 100 PAGE_SIZE_MAX = 100
PAGE_DEFAULT = 1 PAGE_DEFAULT = 1

View File

@ -8,7 +8,7 @@
</h5> </h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div hx-trigger="every 2s" hx-get="{% url 'geometry-parcels' geom_form.instance.geometry.id %}"> <div hx-trigger="load, every 5s" hx-get="{% url 'geometry-parcels' geom_form.instance.geometry.id %}">
<div class="row justify-content-center"> <div class="row justify-content-center">
<span class="spinner-border rlp-r-inv" role="status"></span> <span class="spinner-border rlp-r-inv" role="status"></span>
</div> </div>

View File

@ -0,0 +1,19 @@
{% load i18n %}
<div class="col-sm-6 col-md-6 col-lg-6">
<button class="btn btn-outline-default col-sm-12">
<a href="{{qrcode.url}}" target="_blank">
<h4>{% trans 'Open in browser' %}</h4>
{{ qrcode.img|safe }}
</a>
</button>
</div>
<div class="col-sm-6 col-md-6 col-lg-6">
<button class="btn btn-outline-default col-sm-12">
<a href="{{qrcode_lanis.url}}" target="_blank">
<h4>{% trans 'View in LANIS' %}</h4>
{{ qrcode_lanis.img|safe }}
</a>
</button>
</div>

Binary file not shown.

View File

@ -26,7 +26,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-12 10:28+0200\n" "POT-Creation-Date: 2022-04-13 15:13+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -154,7 +154,7 @@ msgstr "Geprüft"
#: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:9 #: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:9
#: analysis/templates/analysis/reports/includes/intervention/laws.html:20 #: analysis/templates/analysis/reports/includes/intervention/laws.html:20
#: analysis/templates/analysis/reports/includes/old_data/amount.html:18 #: analysis/templates/analysis/reports/includes/old_data/amount.html:18
#: compensation/tables.py:46 compensation/tables.py:222 #: compensation/tables.py:46 compensation/tables.py:220
#: compensation/templates/compensation/detail/compensation/view.html:78 #: compensation/templates/compensation/detail/compensation/view.html:78
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31 #: compensation/templates/compensation/detail/eco_account/includes/deductions.html:31
#: compensation/templates/compensation/detail/eco_account/view.html:45 #: compensation/templates/compensation/detail/eco_account/view.html:45
@ -294,7 +294,7 @@ msgid "Intervention"
msgstr "Eingriff" msgstr "Eingriff"
#: analysis/templates/analysis/reports/includes/old_data/amount.html:34 #: analysis/templates/analysis/reports/includes/old_data/amount.html:34
#: compensation/tables.py:266 #: compensation/tables.py:264
#: compensation/templates/compensation/detail/eco_account/view.html:20 #: compensation/templates/compensation/detail/eco_account/view.html:20
#: intervention/forms/modalForms.py:348 intervention/forms/modalForms.py:355 #: intervention/forms/modalForms.py:348 intervention/forms/modalForms.py:355
#: konova/templates/konova/includes/quickstart/ecoaccounts.html:4 #: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
@ -315,7 +315,7 @@ msgid "Show only unrecorded"
msgstr "Nur unverzeichnete anzeigen" msgstr "Nur unverzeichnete anzeigen"
#: compensation/forms/forms.py:32 compensation/tables.py:25 #: compensation/forms/forms.py:32 compensation/tables.py:25
#: compensation/tables.py:197 ema/tables.py:29 intervention/forms/forms.py:28 #: compensation/tables.py:195 ema/tables.py:29 intervention/forms/forms.py:28
#: intervention/tables.py:24 #: intervention/tables.py:24
#: intervention/templates/intervention/detail/includes/compensations.html:30 #: intervention/templates/intervention/detail/includes/compensations.html:30
msgid "Identifier" msgid "Identifier"
@ -327,7 +327,7 @@ msgid "Generated automatically"
msgstr "Automatisch generiert" msgstr "Automatisch generiert"
#: compensation/forms/forms.py:44 compensation/tables.py:30 #: compensation/forms/forms.py:44 compensation/tables.py:30
#: compensation/tables.py:202 #: compensation/tables.py:200
#: compensation/templates/compensation/detail/compensation/includes/documents.html:28 #: compensation/templates/compensation/detail/compensation/includes/documents.html:28
#: compensation/templates/compensation/detail/compensation/view.html:32 #: compensation/templates/compensation/detail/compensation/view.html:32
#: compensation/templates/compensation/detail/eco_account/includes/documents.html:28 #: compensation/templates/compensation/detail/eco_account/includes/documents.html:28
@ -675,22 +675,22 @@ msgstr ""
"Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen " "Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar einstellen "
"wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!" "wollen. Kontaktieren Sie die für die Abbuchungen verantwortlichen Nutzer!"
#: compensation/tables.py:35 compensation/tables.py:207 ema/tables.py:39 #: compensation/tables.py:35 compensation/tables.py:205 ema/tables.py:39
#: intervention/tables.py:34 konova/filters/mixins.py:98 #: intervention/tables.py:34 konova/filters/mixins.py:98
msgid "Parcel gmrkng" msgid "Parcel gmrkng"
msgstr "Gemarkung" msgstr "Gemarkung"
#: compensation/tables.py:52 compensation/tables.py:228 ema/tables.py:50 #: compensation/tables.py:52 compensation/tables.py:226 ema/tables.py:50
#: intervention/tables.py:51 #: intervention/tables.py:51
msgid "Editable" msgid "Editable"
msgstr "Freigegeben" msgstr "Freigegeben"
#: compensation/tables.py:58 compensation/tables.py:234 ema/tables.py:56 #: compensation/tables.py:58 compensation/tables.py:232 ema/tables.py:56
#: intervention/tables.py:57 #: intervention/tables.py:57
msgid "Last edit" msgid "Last edit"
msgstr "Zuletzt bearbeitet" msgstr "Zuletzt bearbeitet"
#: compensation/tables.py:89 compensation/tables.py:266 ema/tables.py:89 #: compensation/tables.py:89 compensation/tables.py:264 ema/tables.py:89
#: intervention/tables.py:88 #: intervention/tables.py:88
msgid "Open {}" msgid "Open {}"
msgstr "Öffne {}" msgstr "Öffne {}"
@ -713,32 +713,32 @@ msgstr "Am {} von {} geprüft worden"
msgid "Not recorded yet" msgid "Not recorded yet"
msgstr "Noch nicht verzeichnet" msgstr "Noch nicht verzeichnet"
#: compensation/tables.py:165 compensation/tables.py:326 ema/tables.py:136 #: compensation/tables.py:165 compensation/tables.py:324 ema/tables.py:136
#: intervention/tables.py:162 #: intervention/tables.py:162
msgid "Recorded on {} by {}" msgid "Recorded on {} by {}"
msgstr "Am {} von {} verzeichnet worden" msgstr "Am {} von {} verzeichnet worden"
#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159 #: compensation/tables.py:187 compensation/tables.py:346 ema/tables.py:157
#: intervention/tables.py:185 #: intervention/tables.py:183
msgid "Full access granted" msgid "Full access granted"
msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden" msgstr "Für Sie freigegeben - Datensatz kann bearbeitet werden"
#: compensation/tables.py:189 compensation/tables.py:348 ema/tables.py:159 #: compensation/tables.py:187 compensation/tables.py:346 ema/tables.py:157
#: intervention/tables.py:185 #: intervention/tables.py:183
msgid "Access not granted" msgid "Access not granted"
msgstr "Nicht freigegeben - Datensatz nur lesbar" msgstr "Nicht freigegeben - Datensatz nur lesbar"
#: compensation/tables.py:212 #: compensation/tables.py:210
#: compensation/templates/compensation/detail/eco_account/view.html:36 #: compensation/templates/compensation/detail/eco_account/view.html:36
#: konova/templates/konova/widgets/progressbar.html:3 #: konova/templates/konova/widgets/progressbar.html:3
msgid "Available" msgid "Available"
msgstr "Verfügbar" msgstr "Verfügbar"
#: compensation/tables.py:243 #: compensation/tables.py:241
msgid "Eco Accounts" msgid "Eco Accounts"
msgstr "Ökokonten" msgstr "Ökokonten"
#: compensation/tables.py:321 #: compensation/tables.py:319
msgid "Not recorded yet. Can not be used for deductions, yet." msgid "Not recorded yet. Can not be used for deductions, yet."
msgstr "" msgstr ""
"Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden." "Noch nicht verzeichnet. Kann noch nicht für Abbuchungen genutzt werden."
@ -1112,20 +1112,6 @@ msgstr "Maßnahmenträger"
msgid "Report" msgid "Report"
msgstr "Bericht" msgstr "Bericht"
#: compensation/templates/compensation/report/compensation/report.html:45
#: compensation/templates/compensation/report/eco_account/report.html:58
#: ema/templates/ema/report/report.html:45
#: intervention/templates/intervention/report/report.html:104
msgid "Open in browser"
msgstr "Im Browser öffnen"
#: compensation/templates/compensation/report/compensation/report.html:49
#: compensation/templates/compensation/report/eco_account/report.html:62
#: ema/templates/ema/report/report.html:49
#: intervention/templates/intervention/report/report.html:108
msgid "View in LANIS"
msgstr "In LANIS öffnen"
#: compensation/templates/compensation/report/eco_account/report.html:24 #: compensation/templates/compensation/report/eco_account/report.html:24
msgid "Deductions for" msgid "Deductions for"
msgstr "Abbuchungen für" msgstr "Abbuchungen für"
@ -1204,22 +1190,22 @@ msgstr "{} entzeichnet"
msgid "{} recorded" msgid "{} recorded"
msgstr "{} verzeichnet" msgstr "{} verzeichnet"
#: compensation/views/eco_account.py:792 ema/views.py:617 #: compensation/views/eco_account.py:796 ema/views.py:621
#: intervention/views.py:428 #: intervention/views.py:428
msgid "{} has already been shared with you" msgid "{} has already been shared with you"
msgstr "{} wurde bereits für Sie freigegeben" msgstr "{} wurde bereits für Sie freigegeben"
#: compensation/views/eco_account.py:797 ema/views.py:622 #: compensation/views/eco_account.py:801 ema/views.py:626
#: intervention/views.py:433 #: intervention/views.py:433
msgid "{} has been shared with you" msgid "{} has been shared with you"
msgstr "{} ist nun für Sie freigegeben" msgstr "{} ist nun für Sie freigegeben"
#: compensation/views/eco_account.py:804 ema/views.py:629 #: compensation/views/eco_account.py:808 ema/views.py:633
#: intervention/views.py:440 #: intervention/views.py:440
msgid "Share link invalid" msgid "Share link invalid"
msgstr "Freigabelink ungültig" msgstr "Freigabelink ungültig"
#: compensation/views/eco_account.py:827 ema/views.py:652 #: compensation/views/eco_account.py:831 ema/views.py:656
#: intervention/views.py:463 #: intervention/views.py:463
msgid "Share settings updated" msgid "Share settings updated"
msgstr "Freigabe Einstellungen aktualisiert" msgstr "Freigabe Einstellungen aktualisiert"
@ -1802,6 +1788,14 @@ msgstr "Neu"
msgid "Show" msgid "Show"
msgstr "Anzeigen" msgstr "Anzeigen"
#: konova/templates/konova/includes/report/qrcodes.html:7
msgid "Open in browser"
msgstr "Im Browser öffnen"
#: konova/templates/konova/includes/report/qrcodes.html:15
msgid "View in LANIS"
msgstr "In LANIS öffnen"
#: konova/templates/konova/widgets/checkbox-tree-select.html:4 #: konova/templates/konova/widgets/checkbox-tree-select.html:4
#: templates/generic_index.html:56 #: templates/generic_index.html:56
msgid "Search" msgid "Search"
@ -2451,9 +2445,9 @@ msgid ""
" " " "
msgstr "" msgstr ""
"\n" "\n"
" Diese Daten sind noch nicht veröffentlicht und/oder haben das Bestandskraftdatum noch nicht erreicht. " " Diese Daten sind noch nicht veröffentlicht und/oder haben das "
"Sie können daher aktuell nicht eingesehen werden. Schauen Sie zu einem späteren Zeitpunkt " "Bestandskraftdatum noch nicht erreicht. Sie können daher aktuell nicht "
"wieder vorbei. \n" "eingesehen werden. Schauen Sie zu einem späteren Zeitpunkt wieder vorbei. \n"
" " " "
#: templates/table/gmrkng_col.html:6 #: templates/table/gmrkng_col.html:6
@ -2504,11 +2498,11 @@ msgstr "Neuen Token generieren"
msgid "A new token needs to be validated by an administrator!" msgid "A new token needs to be validated by an administrator!"
msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!" msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!"
#: user/forms.py:168 user/forms.py:172 user/forms.py:323 user/forms.py:328 #: user/forms.py:168 user/forms.py:172 user/forms.py:332 user/forms.py:337
msgid "Team name" msgid "Team name"
msgstr "Team Name" msgstr "Team Name"
#: user/forms.py:179 user/forms.py:336 user/templates/user/team/index.html:30 #: user/forms.py:179 user/forms.py:345 user/templates/user/team/index.html:30
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
@ -2556,7 +2550,11 @@ msgstr "Gewählter Administrator ({}) muss ein Mitglied des Teams sein."
msgid "Edit team" msgid "Edit team"
msgstr "Team bearbeiten" msgstr "Team bearbeiten"
#: user/forms.py:347 #: user/forms.py:323 user/templates/user/team/index.html:58
msgid "Leave team"
msgstr "Team verlassen"
#: user/forms.py:356
msgid "Team" msgid "Team"
msgstr "Team" msgstr "Team"
@ -2702,6 +2700,14 @@ msgstr "Team bearbeitet"
msgid "Team removed" msgid "Team removed"
msgstr "Team gelöscht" msgstr "Team gelöscht"
#: user/views.py:218
msgid "You are not a member of this team"
msgstr "Sie sind kein Mitglied dieses Teams"
#: user/views.py:225
msgid "Left Team"
msgstr "Team verlassen"
#: venv/lib/python3.7/site-packages/bootstrap4/components.py:17 #: 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/form_errors.html:3
#: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/messages.html:4 #: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/messages.html:4

View File

@ -74,6 +74,15 @@ class TeamAdmin(admin.ModelAdmin):
"name", "name",
"description", "description",
] ]
filter_horizontal = [
"users"
]
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "admin":
team_id = request.resolver_match.kwargs.get("object_id", None)
kwargs["queryset"] = User.objects.filter(teams__id__in=[team_id])
return super().formfield_for_foreignkey(db_field, request, **kwargs)
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)

View File

@ -317,6 +317,15 @@ class RemoveTeamModalForm(RemoveModalForm):
pass pass
class LeaveTeamModalForm(RemoveModalForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Leave team")
def save(self):
self.instance.remove_user(self.user)
class TeamDataForm(BaseModalForm): class TeamDataForm(BaseModalForm):
name = forms.CharField( name = forms.CharField(
label_suffix="", label_suffix="",

View File

@ -93,3 +93,17 @@ class Team(UuidModel):
""" """
mailer = Mailer() mailer = Mailer()
mailer.send_mail_shared_data_deleted_team(obj_identifier, obj_title, self) mailer.send_mail_shared_data_deleted_team(obj_identifier, obj_title, self)
def remove_user(self, user):
""" Removes a user from the team
Args:
user (User): The user to be removed
Returns:
"""
self.users.remove(user)
if self.admin == user:
self.admin = self.users.first()
self.save()

View File

@ -46,6 +46,9 @@
{% endfor %} {% endfor %}
</td> </td>
<td> <td>
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-leave' team.id %}" title="{% trans 'Leave team' %}">
{% fa5_icon 'sign-out-alt' %}
</button>
{% if team.admin == user %} {% if team.admin == user %}
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-edit' team.id %}" title="{% trans 'Edit team' %}"> <button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-edit' team.id %}" title="{% trans 'Edit team' %}">
{% fa5_icon 'edit' %} {% fa5_icon 'edit' %}

View File

@ -20,5 +20,6 @@ urlpatterns = [
path("team/<id>", data_team_view, name="team-data"), path("team/<id>", data_team_view, name="team-data"),
path("team/<id>/edit", edit_team_view, name="team-edit"), path("team/<id>/edit", edit_team_view, name="team-edit"),
path("team/<id>/remove", remove_team_view, name="team-remove"), path("team/<id>/remove", remove_team_view, name="team-remove"),
path("team/<id>/leave", leave_team_view, name="team-leave"),
] ]

View File

@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _
from konova.contexts import BaseContext from konova.contexts import BaseContext
from konova.decorators import any_group_check, default_group_required from konova.decorators import any_group_check, default_group_required
from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm, NewTeamModalForm, EditTeamModalForm, \ from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm, NewTeamModalForm, EditTeamModalForm, \
RemoveTeamModalForm, TeamDataForm RemoveTeamModalForm, TeamDataForm, LeaveTeamModalForm
@login_required @login_required
@ -204,3 +204,24 @@ def remove_team_view(request: HttpRequest, id: str):
_("Team removed"), _("Team removed"),
redirect_url=reverse("user:team-index") redirect_url=reverse("user:team-index")
) )
@login_required
def leave_team_view(request: HttpRequest, id: str):
team = get_object_or_404(Team, id=id)
user = request.user
is_user_team_member = team.users.filter(id=user.id).exists()
if not is_user_team_member:
messages.info(
request,
_("You are not a member of this team")
)
return redirect("user:team-index")
form = LeaveTeamModalForm(request.POST or None, instance=team, request=request)
return form.process_request(
request,
_("Left Team"),
redirect_url=reverse("user:team-index")
)