169_Unknown_admin_on_teams #170
@ -109,7 +109,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans 'Shared with' %}</th>
|
<th scope="row">{% trans 'Shared with' %}</th>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% for team in obj.intervention.teams.all %}
|
{% for team in obj.intervention.shared_teams %}
|
||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -87,7 +87,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans 'Shared with' %}</th>
|
<th scope="row">{% trans 'Shared with' %}</th>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% for team in obj.teams.all %}
|
{% for team in obj.shared_teams %}
|
||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -73,11 +73,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans 'Shared with' %}</th>
|
<th scope="row">{% trans 'Shared with' %}</th>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% for team in obj.teams.all %}
|
{% for team in obj.shared_teams %}
|
||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
{% for user in obj.users.all %}
|
{% for user in obj.user.all %}
|
||||||
{% include 'user/includes/contact_modal_button.html' %}
|
{% include 'user/includes/contact_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
|
@ -125,7 +125,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans 'Shared with' %}</th>
|
<th scope="row">{% trans 'Shared with' %}</th>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% for team in obj.teams.all %}
|
{% for team in obj.shared_teams %}
|
||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -98,6 +98,18 @@ class DeadlineAdmin(admin.ModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class DeletableObjectMixinAdmin(admin.ModelAdmin):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def restore_deleted_data(self, request, queryset):
|
||||||
|
queryset = queryset.filter(
|
||||||
|
deleted__isnull=False
|
||||||
|
)
|
||||||
|
for entry in queryset:
|
||||||
|
entry.deleted.delete()
|
||||||
|
|
||||||
|
|
||||||
class BaseResourceAdmin(admin.ModelAdmin):
|
class BaseResourceAdmin(admin.ModelAdmin):
|
||||||
fields = [
|
fields = [
|
||||||
"created",
|
"created",
|
||||||
@ -109,7 +121,7 @@ class BaseResourceAdmin(admin.ModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class BaseObjectAdmin(BaseResourceAdmin):
|
class BaseObjectAdmin(BaseResourceAdmin, DeletableObjectMixinAdmin):
|
||||||
search_fields = [
|
search_fields = [
|
||||||
"identifier",
|
"identifier",
|
||||||
"title",
|
"title",
|
||||||
@ -126,13 +138,6 @@ class BaseObjectAdmin(BaseResourceAdmin):
|
|||||||
"deleted",
|
"deleted",
|
||||||
]
|
]
|
||||||
|
|
||||||
def restore_deleted_data(self, request, queryset):
|
|
||||||
queryset = queryset.filter(
|
|
||||||
deleted__isnull=False
|
|
||||||
)
|
|
||||||
for entry in queryset:
|
|
||||||
entry.deleted.delete()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Outcommented for a cleaner admin backend on production
|
# Outcommented for a cleaner admin backend on production
|
||||||
|
@ -96,7 +96,9 @@ class ShareTeamAutocomplete(Select2QuerySetView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.request.user.is_anonymous:
|
if self.request.user.is_anonymous:
|
||||||
return Team.objects.none()
|
return Team.objects.none()
|
||||||
qs = Team.objects.all()
|
qs = Team.objects.filter(
|
||||||
|
deleted__isnull=True
|
||||||
|
)
|
||||||
if self.q:
|
if self.q:
|
||||||
# Due to privacy concerns only a full username match will return the proper user entry
|
# Due to privacy concerns only a full username match will return the proper user entry
|
||||||
qs = qs.filter(
|
qs = qs.filter(
|
||||||
@ -108,6 +110,29 @@ class ShareTeamAutocomplete(Select2QuerySetView):
|
|||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class TeamAdminAutocomplete(Select2QuerySetView):
|
||||||
|
""" Autocomplete for share with teams
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_queryset(self):
|
||||||
|
if self.request.user.is_anonymous:
|
||||||
|
return User.objects.none()
|
||||||
|
qs = User.objects.filter(
|
||||||
|
id__in=self.forwarded.get("members", [])
|
||||||
|
).exclude(
|
||||||
|
id__in=self.forwarded.get("admins", [])
|
||||||
|
)
|
||||||
|
if self.q:
|
||||||
|
# Due to privacy concerns only a full username match will return the proper user entry
|
||||||
|
qs = qs.filter(
|
||||||
|
name__icontains=self.q
|
||||||
|
)
|
||||||
|
qs = qs.order_by(
|
||||||
|
"username"
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class KonovaCodeAutocomplete(Select2GroupQuerySetView):
|
class KonovaCodeAutocomplete(Select2GroupQuerySetView):
|
||||||
"""
|
"""
|
||||||
Provides simple autocomplete functionality for codes
|
Provides simple autocomplete functionality for codes
|
||||||
|
@ -326,7 +326,7 @@ class SimpleGeomForm(BaseForm):
|
|||||||
features = []
|
features = []
|
||||||
features_json = geom.get("features", [])
|
features_json = geom.get("features", [])
|
||||||
for feature in features_json:
|
for feature in features_json:
|
||||||
g = gdal.OGRGeometry(json.dumps(feature["geometry"]), srs=DEFAULT_SRID_RLP)
|
g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP)
|
||||||
if g.geom_type not in ["Polygon", "MultiPolygon"]:
|
if g.geom_type not in ["Polygon", "MultiPolygon"]:
|
||||||
self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
|
self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
|
||||||
is_valid = False
|
is_valid = False
|
||||||
|
@ -87,25 +87,15 @@ class BaseResource(UuidModel):
|
|||||||
super().delete()
|
super().delete()
|
||||||
|
|
||||||
|
|
||||||
class BaseObject(BaseResource):
|
class DeletableObjectMixin(models.Model):
|
||||||
"""
|
""" Wraps deleted field and related functionality
|
||||||
A basic object model, which specifies BaseResource.
|
|
||||||
|
|
||||||
Mainly used for intervention, compensation, ecoaccount
|
|
||||||
"""
|
"""
|
||||||
identifier = models.CharField(max_length=1000, null=True, blank=True)
|
|
||||||
title = models.CharField(max_length=1000, null=True, blank=True)
|
|
||||||
deleted = models.ForeignKey("user.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("user.UserActionLogEntry", blank=True, help_text="Keeps all user actions of an object", editable=False)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def set_status_messages(self, request: HttpRequest):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def mark_as_deleted(self, user, send_mail: bool = True):
|
def mark_as_deleted(self, user, send_mail: bool = True):
|
||||||
""" Mark an entry as deleted
|
""" Mark an entry as deleted
|
||||||
|
|
||||||
@ -140,6 +130,25 @@ class BaseObject(BaseResource):
|
|||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseObject(BaseResource, DeletableObjectMixin):
|
||||||
|
"""
|
||||||
|
A basic object model, which specifies BaseResource.
|
||||||
|
|
||||||
|
Mainly used for intervention, compensation, ecoaccount
|
||||||
|
"""
|
||||||
|
identifier = models.CharField(max_length=1000, null=True, blank=True)
|
||||||
|
title = models.CharField(max_length=1000, null=True, blank=True)
|
||||||
|
comment = models.TextField(null=True, blank=True)
|
||||||
|
log = models.ManyToManyField("user.UserActionLogEntry", blank=True, help_text="Keeps all user actions of an object", editable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def set_status_messages(self, request: HttpRequest):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def mark_as_edited(self, performing_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
|
""" In case the object or a related object changed the log history needs to be updated
|
||||||
|
|
||||||
@ -484,8 +493,8 @@ class ShareableObjectMixin(models.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
directly_shared = self.users.filter(id=user.id).exists()
|
directly_shared = self.shared_users.filter(id=user.id).exists()
|
||||||
team_shared = self.teams.filter(
|
team_shared = self.shared_teams.filter(
|
||||||
users__in=[user]
|
users__in=[user]
|
||||||
).exists()
|
).exists()
|
||||||
is_shared = directly_shared or team_shared
|
is_shared = directly_shared or team_shared
|
||||||
@ -622,7 +631,9 @@ class ShareableObjectMixin(models.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
teams (QuerySet)
|
teams (QuerySet)
|
||||||
"""
|
"""
|
||||||
return self.teams.all()
|
return self.teams.filter(
|
||||||
|
deleted__isnull=True
|
||||||
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_share_url(self):
|
def get_share_url(self):
|
||||||
|
@ -72,6 +72,7 @@ class AutocompleteTestCase(BaseTestCase):
|
|||||||
"codes-conservation-office-autocomplete",
|
"codes-conservation-office-autocomplete",
|
||||||
"share-user-autocomplete",
|
"share-user-autocomplete",
|
||||||
"share-team-autocomplete",
|
"share-team-autocomplete",
|
||||||
|
"team-admin-autocomplete",
|
||||||
]
|
]
|
||||||
for test in tests:
|
for test in tests:
|
||||||
self.client.login(username=self.superuser.username, password=self.superuser_pw)
|
self.client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||||
|
@ -274,7 +274,6 @@ class BaseTestCase(TestCase):
|
|||||||
team = Team.objects.get_or_create(
|
team = Team.objects.get_or_create(
|
||||||
name="Testteam",
|
name="Testteam",
|
||||||
description="Testdescription",
|
description="Testdescription",
|
||||||
admin=self.superuser,
|
|
||||||
)[0]
|
)[0]
|
||||||
team.users.add(self.superuser)
|
team.users.add(self.superuser)
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ from konova.autocompletes import EcoAccountAutocomplete, \
|
|||||||
InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \
|
InterventionAutocomplete, CompensationActionCodeAutocomplete, BiotopeCodeAutocomplete, LawCodeAutocomplete, \
|
||||||
RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete, ProcessTypeCodeAutocomplete, \
|
RegistrationOfficeCodeAutocomplete, ConservationOfficeCodeAutocomplete, ProcessTypeCodeAutocomplete, \
|
||||||
ShareUserAutocomplete, BiotopeExtraCodeAutocomplete, CompensationActionDetailCodeAutocomplete, \
|
ShareUserAutocomplete, BiotopeExtraCodeAutocomplete, CompensationActionDetailCodeAutocomplete, \
|
||||||
ShareTeamAutocomplete, HandlerCodeAutocomplete
|
ShareTeamAutocomplete, HandlerCodeAutocomplete, TeamAdminAutocomplete
|
||||||
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||||
from konova.sso.sso import KonovaSSOClient
|
from konova.sso.sso import KonovaSSOClient
|
||||||
from konova.views import logout_view, home_view, get_geom_parcels, get_geom_parcels_content, map_client_proxy_view
|
from konova.views import logout_view, home_view, get_geom_parcels, get_geom_parcels_content, map_client_proxy_view
|
||||||
@ -58,6 +58,7 @@ urlpatterns = [
|
|||||||
path("atcmplt/codes/handler", HandlerCodeAutocomplete.as_view(), name="codes-handler-autocomplete"),
|
path("atcmplt/codes/handler", HandlerCodeAutocomplete.as_view(), name="codes-handler-autocomplete"),
|
||||||
path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"),
|
path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"),
|
||||||
path("atcmplt/share/t", ShareTeamAutocomplete.as_view(), name="share-team-autocomplete"),
|
path("atcmplt/share/t", ShareTeamAutocomplete.as_view(), name="share-team-autocomplete"),
|
||||||
|
path("atcmplt/team/admin", TeamAdminAutocomplete.as_view(), name="team-admin-autocomplete"),
|
||||||
]
|
]
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
Binary file not shown.
@ -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-05-30 09:16+0200\n"
|
"POT-Creation-Date: 2022-05-30 11:51+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"
|
||||||
@ -65,7 +65,7 @@ msgstr "Verantwortliche Stelle"
|
|||||||
#: intervention/forms/forms.py:64 intervention/forms/forms.py:81
|
#: intervention/forms/forms.py:64 intervention/forms/forms.py:81
|
||||||
#: intervention/forms/forms.py:97 intervention/forms/forms.py:113
|
#: intervention/forms/forms.py:97 intervention/forms/forms.py:113
|
||||||
#: intervention/forms/forms.py:154 intervention/forms/modalForms.py:49
|
#: intervention/forms/forms.py:154 intervention/forms/modalForms.py:49
|
||||||
#: intervention/forms/modalForms.py:63 user/forms.py:196
|
#: intervention/forms/modalForms.py:63 user/forms.py:196 user/forms.py:260
|
||||||
msgid "Click for selection"
|
msgid "Click for selection"
|
||||||
msgstr "Auswählen..."
|
msgstr "Auswählen..."
|
||||||
|
|
||||||
@ -138,11 +138,11 @@ msgstr "Zuständigkeitsbereich"
|
|||||||
#: analysis/templates/analysis/reports/includes/intervention/amount.html:17
|
#: analysis/templates/analysis/reports/includes/intervention/amount.html:17
|
||||||
#: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:8
|
#: analysis/templates/analysis/reports/includes/intervention/compensated_by.html:8
|
||||||
#: analysis/templates/analysis/reports/includes/intervention/laws.html:17
|
#: analysis/templates/analysis/reports/includes/intervention/laws.html:17
|
||||||
#: compensation/tables.py:41
|
#: compensation/tables.py:38
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:64
|
#: compensation/templates/compensation/detail/compensation/view.html:64
|
||||||
#: intervention/tables.py:40
|
#: intervention/tables.py:38
|
||||||
#: intervention/templates/intervention/detail/view.html:68
|
#: intervention/templates/intervention/detail/view.html:68
|
||||||
#: user/models/user_action.py:20
|
#: user/models/user_action.py:21
|
||||||
msgid "Checked"
|
msgid "Checked"
|
||||||
msgstr "Geprüft"
|
msgstr "Geprüft"
|
||||||
|
|
||||||
@ -154,14 +154,14 @@ 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:47 compensation/tables.py:230
|
#: compensation/tables.py:44 compensation/tables.py:219
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:78
|
#: compensation/templates/compensation/detail/compensation/view.html:83
|
||||||
#: 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
|
||||||
#: ema/tables.py:44 ema/templates/ema/detail/view.html:35
|
#: ema/tables.py:44 ema/templates/ema/detail/view.html:35
|
||||||
#: intervention/tables.py:46
|
#: intervention/tables.py:44
|
||||||
#: intervention/templates/intervention/detail/view.html:82
|
#: intervention/templates/intervention/detail/view.html:87
|
||||||
#: user/models/user_action.py:21
|
#: user/models/user_action.py:22
|
||||||
msgid "Recorded"
|
msgid "Recorded"
|
||||||
msgstr "Verzeichnet"
|
msgstr "Verzeichnet"
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ msgid "Other registration office"
|
|||||||
msgstr "Andere Zulassungsbehörden"
|
msgstr "Andere Zulassungsbehörden"
|
||||||
|
|
||||||
#: analysis/templates/analysis/reports/includes/compensation/card_compensation.html:11
|
#: analysis/templates/analysis/reports/includes/compensation/card_compensation.html:11
|
||||||
#: compensation/tables.py:68
|
#: compensation/tables.py:65
|
||||||
#: intervention/templates/intervention/detail/includes/compensations.html:8
|
#: intervention/templates/intervention/detail/includes/compensations.html:8
|
||||||
#: intervention/templates/intervention/report/report.html:45
|
#: intervention/templates/intervention/report/report.html:45
|
||||||
msgid "Compensations"
|
msgid "Compensations"
|
||||||
@ -227,7 +227,7 @@ msgid "Surface"
|
|||||||
msgstr "Fläche"
|
msgstr "Fläche"
|
||||||
|
|
||||||
#: analysis/templates/analysis/reports/includes/intervention/card_intervention.html:10
|
#: analysis/templates/analysis/reports/includes/intervention/card_intervention.html:10
|
||||||
#: intervention/tables.py:67
|
#: intervention/tables.py:65
|
||||||
msgid "Interventions"
|
msgid "Interventions"
|
||||||
msgstr "Eingriffe"
|
msgstr "Eingriffe"
|
||||||
|
|
||||||
@ -285,8 +285,8 @@ msgid "Type"
|
|||||||
msgstr "Typ"
|
msgstr "Typ"
|
||||||
|
|
||||||
#: analysis/templates/analysis/reports/includes/old_data/amount.html:24
|
#: analysis/templates/analysis/reports/includes/old_data/amount.html:24
|
||||||
#: compensation/tables.py:90 intervention/forms/modalForms.py:375
|
#: compensation/tables.py:87 intervention/forms/modalForms.py:375
|
||||||
#: intervention/forms/modalForms.py:382 intervention/tables.py:89
|
#: intervention/forms/modalForms.py:382 intervention/tables.py:87
|
||||||
#: intervention/templates/intervention/detail/view.html:19
|
#: intervention/templates/intervention/detail/view.html:19
|
||||||
#: konova/templates/konova/includes/quickstart/interventions.html:4
|
#: konova/templates/konova/includes/quickstart/interventions.html:4
|
||||||
#: templates/navbars/navbar.html:22
|
#: templates/navbars/navbar.html:22
|
||||||
@ -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:274
|
#: compensation/tables.py:263
|
||||||
#: 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
|
||||||
@ -314,9 +314,9 @@ msgstr "Vor"
|
|||||||
msgid "Show only unrecorded"
|
msgid "Show only unrecorded"
|
||||||
msgstr "Nur unverzeichnete anzeigen"
|
msgstr "Nur unverzeichnete anzeigen"
|
||||||
|
|
||||||
#: compensation/forms/forms.py:32 compensation/tables.py:26
|
#: compensation/forms/forms.py:32 compensation/tables.py:23
|
||||||
#: compensation/tables.py:205 ema/tables.py:29 intervention/forms/forms.py:28
|
#: compensation/tables.py:194 ema/tables.py:29 intervention/forms/forms.py:28
|
||||||
#: intervention/tables.py:25
|
#: intervention/tables.py:23
|
||||||
#: intervention/templates/intervention/detail/includes/compensations.html:30
|
#: intervention/templates/intervention/detail/includes/compensations.html:30
|
||||||
msgid "Identifier"
|
msgid "Identifier"
|
||||||
msgstr "Kennung"
|
msgstr "Kennung"
|
||||||
@ -326,8 +326,8 @@ msgstr "Kennung"
|
|||||||
msgid "Generated automatically"
|
msgid "Generated automatically"
|
||||||
msgstr "Automatisch generiert"
|
msgstr "Automatisch generiert"
|
||||||
|
|
||||||
#: compensation/forms/forms.py:44 compensation/tables.py:31
|
#: compensation/forms/forms.py:44 compensation/tables.py:28
|
||||||
#: compensation/tables.py:210
|
#: compensation/tables.py:199
|
||||||
#: 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
|
||||||
@ -337,7 +337,7 @@ msgstr "Automatisch generiert"
|
|||||||
#: ema/tables.py:34 ema/templates/ema/detail/includes/documents.html:28
|
#: ema/tables.py:34 ema/templates/ema/detail/includes/documents.html:28
|
||||||
#: ema/templates/ema/detail/view.html:31
|
#: ema/templates/ema/detail/view.html:31
|
||||||
#: ema/templates/ema/report/report.html:12 intervention/forms/forms.py:40
|
#: ema/templates/ema/report/report.html:12 intervention/forms/forms.py:40
|
||||||
#: intervention/tables.py:30
|
#: intervention/tables.py:28
|
||||||
#: intervention/templates/intervention/detail/includes/compensations.html:33
|
#: intervention/templates/intervention/detail/includes/compensations.html:33
|
||||||
#: intervention/templates/intervention/detail/includes/documents.html:28
|
#: intervention/templates/intervention/detail/includes/documents.html:28
|
||||||
#: intervention/templates/intervention/detail/view.html:31
|
#: intervention/templates/intervention/detail/view.html:31
|
||||||
@ -675,62 +675,62 @@ 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:36 compensation/tables.py:215 ema/tables.py:39
|
#: compensation/tables.py:33 compensation/tables.py:204 ema/tables.py:39
|
||||||
#: intervention/tables.py:35 konova/filters/mixins.py:98
|
#: intervention/tables.py:33 konova/filters/mixins.py:98
|
||||||
msgid "Parcel gmrkng"
|
msgid "Parcel gmrkng"
|
||||||
msgstr "Gemarkung"
|
msgstr "Gemarkung"
|
||||||
|
|
||||||
#: compensation/tables.py:53 compensation/tables.py:236 ema/tables.py:50
|
#: compensation/tables.py:50 compensation/tables.py:225 ema/tables.py:50
|
||||||
#: intervention/tables.py:52
|
#: intervention/tables.py:50
|
||||||
msgid "Editable"
|
msgid "Editable"
|
||||||
msgstr "Freigegeben"
|
msgstr "Freigegeben"
|
||||||
|
|
||||||
#: compensation/tables.py:59 compensation/tables.py:242 ema/tables.py:56
|
#: compensation/tables.py:56 compensation/tables.py:231 ema/tables.py:56
|
||||||
#: intervention/tables.py:58
|
#: intervention/tables.py:56
|
||||||
msgid "Last edit"
|
msgid "Last edit"
|
||||||
msgstr "Zuletzt bearbeitet"
|
msgstr "Zuletzt bearbeitet"
|
||||||
|
|
||||||
#: compensation/tables.py:90 compensation/tables.py:274 ema/tables.py:89
|
#: compensation/tables.py:87 compensation/tables.py:263 ema/tables.py:89
|
||||||
#: intervention/tables.py:89
|
#: intervention/tables.py:87
|
||||||
msgid "Open {}"
|
msgid "Open {}"
|
||||||
msgstr "Öffne {}"
|
msgstr "Öffne {}"
|
||||||
|
|
||||||
#: compensation/tables.py:170
|
#: compensation/tables.py:163
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:81
|
#: compensation/templates/compensation/detail/compensation/view.html:86
|
||||||
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58
|
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:58
|
||||||
#: compensation/templates/compensation/detail/eco_account/view.html:48
|
#: compensation/templates/compensation/detail/eco_account/view.html:48
|
||||||
#: ema/tables.py:131 ema/templates/ema/detail/view.html:38
|
#: ema/tables.py:130 ema/templates/ema/detail/view.html:38
|
||||||
#: intervention/tables.py:167
|
#: intervention/tables.py:161
|
||||||
#: intervention/templates/intervention/detail/view.html:85
|
#: intervention/templates/intervention/detail/view.html:90
|
||||||
msgid "Not recorded yet"
|
msgid "Not recorded yet"
|
||||||
msgstr "Noch nicht verzeichnet"
|
msgstr "Noch nicht verzeichnet"
|
||||||
|
|
||||||
#: compensation/tables.py:175 compensation/tables.py:334 ema/tables.py:136
|
#: compensation/tables.py:166 compensation/tables.py:321 ema/tables.py:133
|
||||||
#: intervention/tables.py:172
|
#: intervention/tables.py:164
|
||||||
msgid "Recorded on {} by {}"
|
msgid "Recorded on {} by {}"
|
||||||
msgstr "Am {} von {} verzeichnet worden"
|
msgstr "Am {} von {} verzeichnet worden"
|
||||||
|
|
||||||
#: compensation/tables.py:197 compensation/tables.py:356 ema/tables.py:157
|
#: compensation/tables.py:186 compensation/tables.py:343 ema/tables.py:154
|
||||||
#: intervention/tables.py:193
|
#: intervention/tables.py:185
|
||||||
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:197 compensation/tables.py:356 ema/tables.py:157
|
#: compensation/tables.py:186 compensation/tables.py:343 ema/tables.py:154
|
||||||
#: intervention/tables.py:193
|
#: intervention/tables.py:185
|
||||||
msgid "Access not granted"
|
msgid "Access not granted"
|
||||||
msgstr "Nicht freigegeben - Datensatz nur lesbar"
|
msgstr "Nicht freigegeben - Datensatz nur lesbar"
|
||||||
|
|
||||||
#: compensation/tables.py:220
|
#: compensation/tables.py:209
|
||||||
#: 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:251
|
#: compensation/tables.py:240
|
||||||
msgid "Eco Accounts"
|
msgid "Eco Accounts"
|
||||||
msgstr "Ökokonten"
|
msgstr "Ökokonten"
|
||||||
|
|
||||||
#: compensation/tables.py:329
|
#: compensation/tables.py:318
|
||||||
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."
|
||||||
@ -782,7 +782,7 @@ msgstr "Menge"
|
|||||||
#: intervention/templates/intervention/detail/includes/documents.html:39
|
#: intervention/templates/intervention/detail/includes/documents.html:39
|
||||||
#: intervention/templates/intervention/detail/includes/payments.html:39
|
#: intervention/templates/intervention/detail/includes/payments.html:39
|
||||||
#: intervention/templates/intervention/detail/includes/revocation.html:43
|
#: intervention/templates/intervention/detail/includes/revocation.html:43
|
||||||
#: templates/log.html:10 user/templates/user/team/index.html:32
|
#: templates/log.html:10 user/templates/user/team/index.html:33
|
||||||
msgid "Action"
|
msgid "Action"
|
||||||
msgstr "Aktionen"
|
msgstr "Aktionen"
|
||||||
|
|
||||||
@ -975,43 +975,43 @@ msgstr "Nein"
|
|||||||
msgid "Is Coherence keeping compensation"
|
msgid "Is Coherence keeping compensation"
|
||||||
msgstr "Ist Kohärenzsicherungsmaßnahme"
|
msgstr "Ist Kohärenzsicherungsmaßnahme"
|
||||||
|
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:71
|
#: compensation/templates/compensation/detail/compensation/view.html:76
|
||||||
#: intervention/templates/intervention/detail/view.html:75
|
#: intervention/templates/intervention/detail/view.html:80
|
||||||
msgid "Checked on "
|
msgid "Checked on "
|
||||||
msgstr "Geprüft am "
|
msgstr "Geprüft am "
|
||||||
|
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:71
|
#: compensation/templates/compensation/detail/compensation/view.html:76
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:85
|
#: compensation/templates/compensation/detail/compensation/view.html:90
|
||||||
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:56
|
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:56
|
||||||
#: compensation/templates/compensation/detail/eco_account/view.html:52
|
#: compensation/templates/compensation/detail/eco_account/view.html:52
|
||||||
#: ema/templates/ema/detail/view.html:42
|
#: ema/templates/ema/detail/view.html:42
|
||||||
#: intervention/templates/intervention/detail/view.html:75
|
#: intervention/templates/intervention/detail/view.html:80
|
||||||
#: intervention/templates/intervention/detail/view.html:89
|
#: intervention/templates/intervention/detail/view.html:94
|
||||||
msgid "by"
|
msgid "by"
|
||||||
msgstr "von"
|
msgstr "von"
|
||||||
|
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:85
|
#: compensation/templates/compensation/detail/compensation/view.html:90
|
||||||
#: compensation/templates/compensation/detail/eco_account/view.html:52
|
#: compensation/templates/compensation/detail/eco_account/view.html:52
|
||||||
#: ema/templates/ema/detail/view.html:42
|
#: ema/templates/ema/detail/view.html:42
|
||||||
#: intervention/templates/intervention/detail/view.html:89
|
#: intervention/templates/intervention/detail/view.html:94
|
||||||
msgid "Recorded on "
|
msgid "Recorded on "
|
||||||
msgstr "Verzeichnet am"
|
msgstr "Verzeichnet am"
|
||||||
|
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:92
|
#: compensation/templates/compensation/detail/compensation/view.html:97
|
||||||
#: compensation/templates/compensation/detail/eco_account/view.html:75
|
#: compensation/templates/compensation/detail/eco_account/view.html:75
|
||||||
#: compensation/templates/compensation/report/compensation/report.html:24
|
#: compensation/templates/compensation/report/compensation/report.html:24
|
||||||
#: compensation/templates/compensation/report/eco_account/report.html:37
|
#: compensation/templates/compensation/report/eco_account/report.html:37
|
||||||
#: ema/templates/ema/detail/view.html:61
|
#: ema/templates/ema/detail/view.html:61
|
||||||
#: ema/templates/ema/report/report.html:24
|
#: ema/templates/ema/report/report.html:24
|
||||||
#: intervention/templates/intervention/detail/view.html:108
|
#: intervention/templates/intervention/detail/view.html:113
|
||||||
#: intervention/templates/intervention/report/report.html:87
|
#: intervention/templates/intervention/report/report.html:87
|
||||||
msgid "Last modified"
|
msgid "Last modified"
|
||||||
msgstr "Zuletzt bearbeitet"
|
msgstr "Zuletzt bearbeitet"
|
||||||
|
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:106
|
#: compensation/templates/compensation/detail/compensation/view.html:111
|
||||||
#: compensation/templates/compensation/detail/eco_account/view.html:89
|
#: compensation/templates/compensation/detail/eco_account/view.html:89
|
||||||
#: ema/templates/ema/detail/view.html:75
|
#: ema/templates/ema/detail/view.html:75
|
||||||
#: intervention/templates/intervention/detail/view.html:122
|
#: intervention/templates/intervention/detail/view.html:127
|
||||||
msgid "Shared with"
|
msgid "Shared with"
|
||||||
msgstr "Freigegeben für"
|
msgstr "Freigegeben für"
|
||||||
|
|
||||||
@ -1050,7 +1050,7 @@ msgstr "Eingriffskennung"
|
|||||||
|
|
||||||
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:37
|
#: compensation/templates/compensation/detail/eco_account/includes/deductions.html:37
|
||||||
#: intervention/templates/intervention/detail/includes/deductions.html:34
|
#: intervention/templates/intervention/detail/includes/deductions.html:34
|
||||||
#: user/models/user_action.py:23
|
#: user/models/user_action.py:24
|
||||||
msgid "Created"
|
msgid "Created"
|
||||||
msgstr "Erstellt"
|
msgstr "Erstellt"
|
||||||
|
|
||||||
@ -1087,8 +1087,8 @@ msgstr "Keine Flächenmenge für Abbuchungen eingegeben. Bitte bearbeiten."
|
|||||||
#: intervention/templates/intervention/detail/view.html:55
|
#: intervention/templates/intervention/detail/view.html:55
|
||||||
#: intervention/templates/intervention/detail/view.html:59
|
#: intervention/templates/intervention/detail/view.html:59
|
||||||
#: intervention/templates/intervention/detail/view.html:63
|
#: intervention/templates/intervention/detail/view.html:63
|
||||||
#: intervention/templates/intervention/detail/view.html:95
|
#: intervention/templates/intervention/detail/view.html:100
|
||||||
#: intervention/templates/intervention/detail/view.html:99
|
#: intervention/templates/intervention/detail/view.html:104
|
||||||
msgid "Missing"
|
msgid "Missing"
|
||||||
msgstr "fehlt"
|
msgstr "fehlt"
|
||||||
|
|
||||||
@ -1142,17 +1142,17 @@ msgid "Compensation {} edited"
|
|||||||
msgstr "Kompensation {} bearbeitet"
|
msgstr "Kompensation {} bearbeitet"
|
||||||
|
|
||||||
#: compensation/views/compensation.py:182 compensation/views/eco_account.py:173
|
#: compensation/views/compensation.py:182 compensation/views/eco_account.py:173
|
||||||
#: ema/views.py:240 intervention/views.py:332
|
#: ema/views.py:240 intervention/views.py:338
|
||||||
msgid "Edit {}"
|
msgid "Edit {}"
|
||||||
msgstr "Bearbeite {}"
|
msgstr "Bearbeite {}"
|
||||||
|
|
||||||
#: compensation/views/compensation.py:261 compensation/views/eco_account.py:359
|
#: compensation/views/compensation.py:268 compensation/views/eco_account.py:359
|
||||||
#: ema/views.py:194 intervention/views.py:536
|
#: ema/views.py:194 intervention/views.py:542
|
||||||
msgid "Log"
|
msgid "Log"
|
||||||
msgstr "Log"
|
msgstr "Log"
|
||||||
|
|
||||||
#: compensation/views/compensation.py:605 compensation/views/eco_account.py:727
|
#: compensation/views/compensation.py:612 compensation/views/eco_account.py:727
|
||||||
#: ema/views.py:558 intervention/views.py:682
|
#: ema/views.py:558 intervention/views.py:688
|
||||||
msgid "Report {}"
|
msgid "Report {}"
|
||||||
msgstr "Bericht {}"
|
msgstr "Bericht {}"
|
||||||
|
|
||||||
@ -1173,32 +1173,32 @@ msgid "Eco-account removed"
|
|||||||
msgstr "Ökokonto entfernt"
|
msgstr "Ökokonto entfernt"
|
||||||
|
|
||||||
#: compensation/views/eco_account.py:380 ema/views.py:282
|
#: compensation/views/eco_account.py:380 ema/views.py:282
|
||||||
#: intervention/views.py:635
|
#: intervention/views.py:641
|
||||||
msgid "{} unrecorded"
|
msgid "{} unrecorded"
|
||||||
msgstr "{} entzeichnet"
|
msgstr "{} entzeichnet"
|
||||||
|
|
||||||
#: compensation/views/eco_account.py:380 ema/views.py:282
|
#: compensation/views/eco_account.py:380 ema/views.py:282
|
||||||
#: intervention/views.py:635
|
#: intervention/views.py:641
|
||||||
msgid "{} recorded"
|
msgid "{} recorded"
|
||||||
msgstr "{} verzeichnet"
|
msgstr "{} verzeichnet"
|
||||||
|
|
||||||
#: compensation/views/eco_account.py:804 ema/views.py:628
|
#: compensation/views/eco_account.py:804 ema/views.py:628
|
||||||
#: intervention/views.py:433
|
#: intervention/views.py:439
|
||||||
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:809 ema/views.py:633
|
#: compensation/views/eco_account.py:809 ema/views.py:633
|
||||||
#: intervention/views.py:438
|
#: intervention/views.py:444
|
||||||
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:816 ema/views.py:640
|
#: compensation/views/eco_account.py:816 ema/views.py:640
|
||||||
#: intervention/views.py:445
|
#: intervention/views.py:451
|
||||||
msgid "Share link invalid"
|
msgid "Share link invalid"
|
||||||
msgstr "Freigabelink ungültig"
|
msgstr "Freigabelink ungültig"
|
||||||
|
|
||||||
#: compensation/views/eco_account.py:839 ema/views.py:663
|
#: compensation/views/eco_account.py:839 ema/views.py:663
|
||||||
#: intervention/views.py:468
|
#: intervention/views.py:474
|
||||||
msgid "Share settings updated"
|
msgid "Share settings updated"
|
||||||
msgstr "Freigabe Einstellungen aktualisiert"
|
msgstr "Freigabe Einstellungen aktualisiert"
|
||||||
|
|
||||||
@ -1292,14 +1292,14 @@ msgid "Intervention handler detail"
|
|||||||
msgstr "Detailangabe zum Eingriffsverursacher"
|
msgstr "Detailangabe zum Eingriffsverursacher"
|
||||||
|
|
||||||
#: intervention/forms/forms.py:173
|
#: intervention/forms/forms.py:173
|
||||||
#: intervention/templates/intervention/detail/view.html:96
|
#: intervention/templates/intervention/detail/view.html:101
|
||||||
#: intervention/templates/intervention/report/report.html:79
|
#: intervention/templates/intervention/report/report.html:79
|
||||||
#: intervention/utils/quality.py:73
|
#: intervention/utils/quality.py:73
|
||||||
msgid "Registration date"
|
msgid "Registration date"
|
||||||
msgstr "Datum Zulassung bzw. Satzungsbeschluss"
|
msgstr "Datum Zulassung bzw. Satzungsbeschluss"
|
||||||
|
|
||||||
#: intervention/forms/forms.py:185
|
#: intervention/forms/forms.py:185
|
||||||
#: intervention/templates/intervention/detail/view.html:100
|
#: intervention/templates/intervention/detail/view.html:105
|
||||||
#: intervention/templates/intervention/report/report.html:83
|
#: intervention/templates/intervention/report/report.html:83
|
||||||
msgid "Binding on"
|
msgid "Binding on"
|
||||||
msgstr "Datum Bestandskraft bzw. Rechtskraft"
|
msgstr "Datum Bestandskraft bzw. Rechtskraft"
|
||||||
@ -1471,7 +1471,7 @@ msgid "Remove payment"
|
|||||||
msgstr "Zahlung entfernen"
|
msgstr "Zahlung entfernen"
|
||||||
|
|
||||||
#: intervention/templates/intervention/detail/includes/revocation.html:8
|
#: intervention/templates/intervention/detail/includes/revocation.html:8
|
||||||
#: intervention/templates/intervention/detail/view.html:104
|
#: intervention/templates/intervention/detail/view.html:109
|
||||||
msgid "Revocations"
|
msgid "Revocations"
|
||||||
msgstr "Widersprüche"
|
msgstr "Widersprüche"
|
||||||
|
|
||||||
@ -1493,7 +1493,7 @@ msgstr "Widerspruch entfernen"
|
|||||||
msgid "Intervention handler"
|
msgid "Intervention handler"
|
||||||
msgstr "Eingriffsverursacher"
|
msgstr "Eingriffsverursacher"
|
||||||
|
|
||||||
#: intervention/templates/intervention/detail/view.html:103
|
#: intervention/templates/intervention/detail/view.html:108
|
||||||
msgid "Exists"
|
msgid "Exists"
|
||||||
msgstr "vorhanden"
|
msgstr "vorhanden"
|
||||||
|
|
||||||
@ -1532,19 +1532,19 @@ msgstr "Eingriffe - Übersicht"
|
|||||||
msgid "Intervention {} added"
|
msgid "Intervention {} added"
|
||||||
msgstr "Eingriff {} hinzugefügt"
|
msgstr "Eingriff {} hinzugefügt"
|
||||||
|
|
||||||
#: intervention/views.py:320
|
#: intervention/views.py:326
|
||||||
msgid "Intervention {} edited"
|
msgid "Intervention {} edited"
|
||||||
msgstr "Eingriff {} bearbeitet"
|
msgstr "Eingriff {} bearbeitet"
|
||||||
|
|
||||||
#: intervention/views.py:356
|
#: intervention/views.py:362
|
||||||
msgid "{} removed"
|
msgid "{} removed"
|
||||||
msgstr "{} entfernt"
|
msgstr "{} entfernt"
|
||||||
|
|
||||||
#: intervention/views.py:489
|
#: intervention/views.py:495
|
||||||
msgid "Check performed"
|
msgid "Check performed"
|
||||||
msgstr "Prüfung durchgeführt"
|
msgstr "Prüfung durchgeführt"
|
||||||
|
|
||||||
#: intervention/views.py:640
|
#: intervention/views.py:646
|
||||||
msgid "There are errors on this intervention:"
|
msgid "There are errors on this intervention:"
|
||||||
msgstr "Es liegen Fehler in diesem Eingriff vor:"
|
msgstr "Es liegen Fehler in diesem Eingriff vor:"
|
||||||
|
|
||||||
@ -2058,7 +2058,8 @@ msgstr "Am {} von {} geprüft worden"
|
|||||||
|
|
||||||
#: konova/utils/message_templates.py:87
|
#: konova/utils/message_templates.py:87
|
||||||
msgid "Data has changed since last check on {} by {}"
|
msgid "Data has changed since last check on {} by {}"
|
||||||
msgstr "Daten wurden nach der letzten Prüfung geändert. Letzte Prüfung am {} durch {}"
|
msgstr ""
|
||||||
|
"Daten wurden nach der letzten Prüfung geändert. Letzte Prüfung am {} durch {}"
|
||||||
|
|
||||||
#: konova/utils/message_templates.py:88
|
#: konova/utils/message_templates.py:88
|
||||||
msgid "Current data not checked yet"
|
msgid "Current data not checked yet"
|
||||||
@ -2547,11 +2548,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:332 user/forms.py:337
|
#: user/forms.py:168 user/forms.py:172 user/forms.py:351 user/forms.py:356
|
||||||
msgid "Team name"
|
msgid "Team name"
|
||||||
msgstr "Team Name"
|
msgstr "Team Name"
|
||||||
|
|
||||||
#: user/forms.py:179 user/forms.py:345 user/templates/user/team/index.html:30
|
#: user/forms.py:179 user/forms.py:364 user/templates/user/team/index.html:30
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Beschreibung"
|
msgstr "Beschreibung"
|
||||||
|
|
||||||
@ -2579,43 +2580,61 @@ msgstr ""
|
|||||||
"Sie werden standardmäßig der Administrator dieses Teams. Sie müssen sich "
|
"Sie werden standardmäßig der Administrator dieses Teams. Sie müssen sich "
|
||||||
"selbst nicht zur Liste der Mitglieder hinzufügen."
|
"selbst nicht zur Liste der Mitglieder hinzufügen."
|
||||||
|
|
||||||
#: user/forms.py:218 user/forms.py:279
|
#: user/forms.py:218 user/forms.py:296
|
||||||
msgid "Name already taken. Try another."
|
msgid "Name already taken. Try another."
|
||||||
msgstr "Name bereits vergeben. Probieren Sie einen anderen."
|
msgstr "Name bereits vergeben. Probieren Sie einen anderen."
|
||||||
|
|
||||||
#: user/forms.py:249
|
#: user/forms.py:248
|
||||||
msgid "Admin"
|
msgid "Admins"
|
||||||
msgstr "Administrator"
|
msgstr "Administratoren"
|
||||||
|
|
||||||
#: user/forms.py:250
|
#: user/forms.py:250
|
||||||
msgid "Administrators manage team details and members"
|
msgid "Administrators manage team details and members"
|
||||||
msgstr "Administratoren verwalten die Teamdaten und Mitglieder"
|
msgstr "Administratoren verwalten die Teamdaten und Mitglieder"
|
||||||
|
|
||||||
#: user/forms.py:263
|
#: user/forms.py:273
|
||||||
msgid "Selected admin ({}) needs to be a member of this team."
|
msgid "Selected admins need to be members of this team."
|
||||||
msgstr "Gewählter Administrator ({}) muss ein Mitglied des Teams sein."
|
msgstr "Gewählte Administratoren müssen Teammitglieder sein."
|
||||||
|
|
||||||
#: user/forms.py:291 user/templates/user/team/index.html:54
|
#: user/forms.py:280
|
||||||
|
msgid "There must be at least one admin on this team."
|
||||||
|
msgstr "Es muss mindestens einen Administrator für das Team geben."
|
||||||
|
|
||||||
|
#: user/forms.py:308 user/templates/user/team/index.html:60
|
||||||
msgid "Edit team"
|
msgid "Edit team"
|
||||||
msgstr "Team bearbeiten"
|
msgstr "Team bearbeiten"
|
||||||
|
|
||||||
#: user/forms.py:323 user/templates/user/team/index.html:50
|
#: user/forms.py:336
|
||||||
|
msgid ""
|
||||||
|
"ATTENTION!\n"
|
||||||
|
"\n"
|
||||||
|
"Removing the team means all members will lose their access to data, based on "
|
||||||
|
"this team! \n"
|
||||||
|
"\n"
|
||||||
|
"Are you sure to remove this team?"
|
||||||
|
msgstr ""
|
||||||
|
"ACHTUNG!\n\n"
|
||||||
|
"Wenn dieses Team gelöscht wird, verlieren alle Teammitglieder den Zugriff auf die Daten, die nur über dieses Team freigegeben sind!\n"
|
||||||
|
"\n"
|
||||||
|
"Sind Sie sicher, dass Sie dieses Team löschen möchten?"
|
||||||
|
|
||||||
|
#: user/forms.py:342 user/templates/user/team/index.html:56
|
||||||
msgid "Leave team"
|
msgid "Leave team"
|
||||||
msgstr "Team verlassen"
|
msgstr "Team verlassen"
|
||||||
|
|
||||||
#: user/forms.py:356
|
#: user/forms.py:375
|
||||||
msgid "Team"
|
msgid "Team"
|
||||||
msgstr "Team"
|
msgstr "Team"
|
||||||
|
|
||||||
#: user/models/user_action.py:22
|
#: user/models/user_action.py:23
|
||||||
msgid "Unrecorded"
|
msgid "Unrecorded"
|
||||||
msgstr "Entzeichnet"
|
msgstr "Entzeichnet"
|
||||||
|
|
||||||
#: user/models/user_action.py:24
|
#: user/models/user_action.py:25
|
||||||
msgid "Edited"
|
msgid "Edited"
|
||||||
msgstr "Bearbeitet"
|
msgstr "Bearbeitet"
|
||||||
|
|
||||||
#: user/models/user_action.py:25
|
#: user/models/user_action.py:26
|
||||||
msgid "Deleted"
|
msgid "Deleted"
|
||||||
msgstr "Gelöscht"
|
msgstr "Gelöscht"
|
||||||
|
|
||||||
@ -2632,8 +2651,8 @@ msgid "Name"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: user/templates/user/index.html:21
|
#: user/templates/user/index.html:21
|
||||||
msgid "Groups"
|
msgid "Permissions"
|
||||||
msgstr "Gruppen"
|
msgstr "Berechtigungen"
|
||||||
|
|
||||||
#: user/templates/user/index.html:34
|
#: user/templates/user/index.html:34
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -2689,7 +2708,11 @@ msgstr "Neues Team hinzufügen"
|
|||||||
msgid "Members"
|
msgid "Members"
|
||||||
msgstr "Mitglieder"
|
msgstr "Mitglieder"
|
||||||
|
|
||||||
#: user/templates/user/team/index.html:57
|
#: user/templates/user/team/index.html:32
|
||||||
|
msgid "Administrator"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: user/templates/user/team/index.html:63
|
||||||
msgid "Remove team"
|
msgid "Remove team"
|
||||||
msgstr "Team entfernen"
|
msgstr "Team entfernen"
|
||||||
|
|
||||||
@ -2741,19 +2764,19 @@ msgstr "API Nutzer Token"
|
|||||||
msgid "New team added"
|
msgid "New team added"
|
||||||
msgstr "Neues Team hinzugefügt"
|
msgstr "Neues Team hinzugefügt"
|
||||||
|
|
||||||
#: user/views.py:191
|
#: user/views.py:192
|
||||||
msgid "Team edited"
|
msgid "Team edited"
|
||||||
msgstr "Team bearbeitet"
|
msgstr "Team bearbeitet"
|
||||||
|
|
||||||
#: user/views.py:204
|
#: user/views.py:206
|
||||||
msgid "Team removed"
|
msgid "Team removed"
|
||||||
msgstr "Team gelöscht"
|
msgstr "Team gelöscht"
|
||||||
|
|
||||||
#: user/views.py:218
|
#: user/views.py:220
|
||||||
msgid "You are not a member of this team"
|
msgid "You are not a member of this team"
|
||||||
msgstr "Sie sind kein Mitglied dieses Teams"
|
msgstr "Sie sind kein Mitglied dieses Teams"
|
||||||
|
|
||||||
#: user/views.py:225
|
#: user/views.py:227
|
||||||
msgid "Left Team"
|
msgid "Left Team"
|
||||||
msgstr "Team verlassen"
|
msgstr "Team verlassen"
|
||||||
|
|
||||||
@ -4256,6 +4279,9 @@ msgstr ""
|
|||||||
msgid "Unable to connect to qpid with SASL mechanism %s"
|
msgid "Unable to connect to qpid with SASL mechanism %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Groups"
|
||||||
|
#~ msgstr "Gruppen"
|
||||||
|
|
||||||
#~ msgid "Show more..."
|
#~ msgid "Show more..."
|
||||||
#~ msgstr "Mehr anzeigen..."
|
#~ msgstr "Mehr anzeigen..."
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</h4>
|
</h4>
|
||||||
{% if form.form_caption is not None %}
|
{% if form.form_caption is not None %}
|
||||||
<small>
|
<small>
|
||||||
{{ form.form_caption }}
|
{{ form.form_caption|linebreaks }}
|
||||||
</small>
|
</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="post" action="{{ form.action_url }}" {% for attr_key, attr_val in form.form_attrs.items %} {{attr_key}}="{{attr_val}}"{% endfor %}>
|
<form method="post" action="{{ form.action_url }}" {% for attr_key, attr_val in form.form_attrs.items %} {{attr_key}}="{{attr_val}}"{% endfor %}>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<article>
|
<article>
|
||||||
{{ form.form_caption }}
|
{{ form.form_caption|linebreaks }}
|
||||||
</article>
|
</article>
|
||||||
{% include 'form/table/generic_table_form_body.html' %}
|
{% include 'form/table/generic_table_form_body.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from user.models import UserNotification, UserActionLogEntry, User, Team
|
from konova.admin import DeletableObjectMixinAdmin
|
||||||
|
from user.models import User, Team
|
||||||
|
|
||||||
|
|
||||||
class UserNotificationAdmin(admin.ModelAdmin):
|
class UserNotificationAdmin(admin.ModelAdmin):
|
||||||
@ -64,26 +65,28 @@ class UserActionLogEntryAdmin(admin.ModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TeamAdmin(admin.ModelAdmin):
|
class TeamAdmin(DeletableObjectMixinAdmin, admin.ModelAdmin):
|
||||||
list_display = [
|
list_display = [
|
||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
"admin",
|
"deleted",
|
||||||
]
|
]
|
||||||
search_fields = [
|
search_fields = [
|
||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
]
|
]
|
||||||
filter_horizontal = [
|
filter_horizontal = [
|
||||||
"users"
|
"users",
|
||||||
|
"admins",
|
||||||
]
|
]
|
||||||
|
|
||||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
readonly_fields = [
|
||||||
if db_field.name == "admin":
|
"deleted"
|
||||||
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)
|
|
||||||
|
|
||||||
|
actions = [
|
||||||
|
"restore_deleted_data"
|
||||||
|
]
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
admin.site.register(Team, TeamAdmin)
|
admin.site.register(Team, TeamAdmin)
|
||||||
|
@ -230,8 +230,8 @@ class NewTeamModalForm(BaseModalForm):
|
|||||||
team = Team.objects.create(
|
team = Team.objects.create(
|
||||||
name=self.cleaned_data.get("name", None),
|
name=self.cleaned_data.get("name", None),
|
||||||
description=self.cleaned_data.get("description", None),
|
description=self.cleaned_data.get("description", None),
|
||||||
admin=self.user,
|
|
||||||
)
|
)
|
||||||
|
team.admins.add(self.user)
|
||||||
members = self.cleaned_data.get("members", User.objects.none())
|
members = self.cleaned_data.get("members", User.objects.none())
|
||||||
if self.user.id not in members:
|
if self.user.id not in members:
|
||||||
members = members.union(
|
members = members.union(
|
||||||
@ -244,23 +244,40 @@ class NewTeamModalForm(BaseModalForm):
|
|||||||
|
|
||||||
|
|
||||||
class EditTeamModalForm(NewTeamModalForm):
|
class EditTeamModalForm(NewTeamModalForm):
|
||||||
admin = forms.ModelChoiceField(
|
admins = forms.ModelMultipleChoiceField(
|
||||||
|
label=_("Admins"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
label=_("Admin"),
|
|
||||||
help_text=_("Administrators manage team details and members"),
|
help_text=_("Administrators manage team details and members"),
|
||||||
queryset=User.objects.none(),
|
required=True,
|
||||||
empty_label=None,
|
queryset=User.objects.all(),
|
||||||
|
widget=autocomplete.ModelSelect2Multiple(
|
||||||
|
url="team-admin-autocomplete",
|
||||||
|
forward=[
|
||||||
|
"members",
|
||||||
|
"admins",
|
||||||
|
],
|
||||||
|
attrs={
|
||||||
|
"data-placeholder": _("Click for selection"),
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __is_admin_valid(self):
|
def __is_admins_valid(self):
|
||||||
admin = self.cleaned_data.get("admin", None)
|
admins = set(self.cleaned_data.get("admins", {}))
|
||||||
members = self.cleaned_data.get("members", None)
|
members = set(self.cleaned_data.get("members", {}))
|
||||||
_is_valid = admin in members
|
_is_valid = admins.issubset(members)
|
||||||
|
|
||||||
if not _is_valid:
|
if not _is_valid:
|
||||||
self.add_error(
|
self.add_error(
|
||||||
"members",
|
"admins",
|
||||||
_("Selected admin ({}) needs to be a member of this team.").format(admin.username)
|
_("Selected admins need to be members of this team.")
|
||||||
|
)
|
||||||
|
|
||||||
|
_is_admin_length_valid = len(admins) > 0
|
||||||
|
if not _is_admin_length_valid:
|
||||||
|
self.add_error(
|
||||||
|
"admins",
|
||||||
|
_("There must be at least one admin on this team.")
|
||||||
)
|
)
|
||||||
|
|
||||||
return _is_valid
|
return _is_valid
|
||||||
@ -283,7 +300,7 @@ class EditTeamModalForm(NewTeamModalForm):
|
|||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
super_valid = super().is_valid()
|
super_valid = super().is_valid()
|
||||||
admin_valid = self.__is_admin_valid()
|
admin_valid = self.__is_admins_valid()
|
||||||
return super_valid and admin_valid
|
return super_valid and admin_valid
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -293,13 +310,12 @@ class EditTeamModalForm(NewTeamModalForm):
|
|||||||
self.cancel_redirect = reverse("user:team-index")
|
self.cancel_redirect = reverse("user:team-index")
|
||||||
|
|
||||||
members = self.instance.users.all()
|
members = self.instance.users.all()
|
||||||
self.fields["admin"].queryset = members
|
|
||||||
|
|
||||||
form_data = {
|
form_data = {
|
||||||
"members": members,
|
"members": members,
|
||||||
"name": self.instance.name,
|
"name": self.instance.name,
|
||||||
"description": self.instance.description,
|
"description": self.instance.description,
|
||||||
"admin": self.instance.admin,
|
"admins": self.instance.admins.all(),
|
||||||
}
|
}
|
||||||
self.load_initial_data(form_data)
|
self.load_initial_data(form_data)
|
||||||
|
|
||||||
@ -307,14 +323,20 @@ class EditTeamModalForm(NewTeamModalForm):
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.instance.name = self.cleaned_data.get("name", None)
|
self.instance.name = self.cleaned_data.get("name", None)
|
||||||
self.instance.description = self.cleaned_data.get("description", None)
|
self.instance.description = self.cleaned_data.get("description", None)
|
||||||
self.instance.admin = self.cleaned_data.get("admin", None)
|
|
||||||
self.instance.save()
|
self.instance.save()
|
||||||
self.instance.users.set(self.cleaned_data.get("members", []))
|
self.instance.users.set(self.cleaned_data.get("members", []))
|
||||||
|
self.instance.admins.set(self.cleaned_data.get("admins", []))
|
||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
class RemoveTeamModalForm(RemoveModalForm):
|
class RemoveTeamModalForm(RemoveModalForm):
|
||||||
pass
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.form_caption = _("ATTENTION!\n\nRemoving the team means all members will lose their access to data, based on this team! \n\nAre you sure to remove this team?")
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
self.instance.mark_as_deleted(self.user)
|
||||||
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
class LeaveTeamModalForm(RemoveModalForm):
|
class LeaveTeamModalForm(RemoveModalForm):
|
||||||
|
35
user/migrations/0004_auto_20220530_1105.py
Normal file
35
user/migrations/0004_auto_20220530_1105.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2022-05-30 09:05
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_fkey_admin_to_m2m(apps, schema_editor):
|
||||||
|
Team = apps.get_model('user', 'Team')
|
||||||
|
all_teams = Team.objects.all()
|
||||||
|
for team in all_teams:
|
||||||
|
admin = team.admin
|
||||||
|
if admin is None:
|
||||||
|
continue
|
||||||
|
team.admins.add(admin)
|
||||||
|
team.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('user', '0003_team'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='admins',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='_team_admins_+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_fkey_admin_to_m2m),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='team',
|
||||||
|
name='admin',
|
||||||
|
),
|
||||||
|
]
|
19
user/migrations/0005_team_deleted.py
Normal file
19
user/migrations/0005_team_deleted.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2022-05-30 12:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('user', '0004_auto_20220530_1105'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='deleted',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry'),
|
||||||
|
),
|
||||||
|
]
|
@ -1,21 +1,35 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from konova.models import UuidModel
|
from konova.models import UuidModel, DeletableObjectMixin
|
||||||
from konova.utils.mailer import Mailer
|
from konova.utils.mailer import Mailer
|
||||||
|
from user.models import UserActionLogEntry
|
||||||
|
|
||||||
|
|
||||||
class Team(UuidModel):
|
class Team(UuidModel, DeletableObjectMixin):
|
||||||
""" Groups users in self managed teams. Can be used for multi-sharing of data
|
""" Groups users in self managed teams. Can be used for multi-sharing of data
|
||||||
|
|
||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=500, null=True, blank=True)
|
name = models.CharField(max_length=500, null=True, blank=True)
|
||||||
description = models.TextField(null=True, blank=True)
|
description = models.TextField(null=True, blank=True)
|
||||||
users = models.ManyToManyField("user.User", blank=True, related_name="teams")
|
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)
|
admins = models.ManyToManyField("user.User", blank=True, related_name="+")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def mark_as_deleted(self, user):
|
||||||
|
""" Creates an UserAction entry and stores it in the correct field
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (User): The performing user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
delete_action = UserActionLogEntry.get_deleted_action(user, "Team deleted")
|
||||||
|
self.deleted = delete_action
|
||||||
|
self.save()
|
||||||
|
|
||||||
def send_mail_shared_access_given_team(self, obj_identifier, obj_title):
|
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
|
""" Sends a mail to the team members in case of given shared access
|
||||||
|
|
||||||
@ -104,6 +118,19 @@ class Team(UuidModel):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
self.users.remove(user)
|
self.users.remove(user)
|
||||||
if self.admin == user:
|
self.admins.remove(user)
|
||||||
self.admin = self.users.first()
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def is_user_admin(self, user) -> bool:
|
||||||
|
""" Returns whether a given user is an admin of the team
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (User): The user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
user_is_admin (bool): Whether the user is an admin or not
|
||||||
|
"""
|
||||||
|
user_is_admin = self.admins.filter(
|
||||||
|
id=user.id
|
||||||
|
).exists()
|
||||||
|
return user_is_admin
|
||||||
|
@ -160,3 +160,15 @@ class User(AbstractUser):
|
|||||||
else:
|
else:
|
||||||
token = self.api_token
|
token = self.api_token
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shared_teams(self):
|
||||||
|
""" Wrapper for fetching active teams of this user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
shared_teams = self.teams.filter(
|
||||||
|
deleted__isnull=True
|
||||||
|
)
|
||||||
|
return shared_teams
|
@ -18,7 +18,7 @@
|
|||||||
<td>{{user.email}}</td>
|
<td>{{user.email}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans 'Groups' %}</th>
|
<th scope="row">{% trans 'Permissions' %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% for group in user.groups.all %}
|
{% for group in user.groups.all %}
|
||||||
<span class="badge badge-pill rlp-r">{% trans group.name %}</span>
|
<span class="badge badge-pill rlp-r">{% trans group.name %}</span>
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
<th scope="col" class="align-middle">{% trans 'Name' %}</th>
|
<th scope="col" class="align-middle">{% trans 'Name' %}</th>
|
||||||
<th scope="col" class="align-middle w-20">{% trans 'Description' %}</th>
|
<th scope="col" class="align-middle w-20">{% trans 'Description' %}</th>
|
||||||
<th scope="col" class="align-middle">{% trans 'Members' %}</th>
|
<th scope="col" class="align-middle">{% trans 'Members' %}</th>
|
||||||
|
<th scope="col" class="align-middle">{% trans 'Administrator' %}</th>
|
||||||
<th scope="col" class="align-middle">{% trans 'Action' %}</th>
|
<th scope="col" class="align-middle">{% trans 'Action' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -45,11 +46,16 @@
|
|||||||
<span class="badge badge-pill rlp-r">{{member.username}}</span>
|
<span class="badge badge-pill rlp-r">{{member.username}}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for admin in team.admins.all %}
|
||||||
|
<span class="badge badge-pill rlp-r">{{admin.username}}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-leave' team.id %}" title="{% trans 'Leave team' %}">
|
<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' %}
|
{% fa5_icon 'sign-out-alt' %}
|
||||||
</button>
|
</button>
|
||||||
{% if team.admin == user %}
|
{% if user in team.admins.all %}
|
||||||
<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' %}
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
7
user/tests/__init__.py
Normal file
7
user/tests/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 30.05.22
|
||||||
|
|
||||||
|
"""
|
112
user/tests/test_views.py
Normal file
112
user/tests/test_views.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 30.05.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.test import Client
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from intervention.models import Revocation
|
||||||
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
|
from konova.tests.test_views import BaseViewTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class UserViewTestCase(BaseViewTestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls) -> None:
|
||||||
|
super().setUpTestData()
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super().setUp()
|
||||||
|
self.team.users.add(self.superuser)
|
||||||
|
self.team.admins.add(self.superuser)
|
||||||
|
# Prepare urls
|
||||||
|
self.index_url = reverse("user:index", args=())
|
||||||
|
self.notification_url = reverse("user:notifications", args=())
|
||||||
|
self.api_token_url = reverse("user:api-token", args=())
|
||||||
|
self.contact_url = reverse("user:contact", args=(self.superuser.id,))
|
||||||
|
self.team_url = reverse("user:team-index", args=())
|
||||||
|
self.new_team_url = reverse("user:team-new", args=())
|
||||||
|
self.data_team_url = reverse("user:team-data", args=(self.team.id,))
|
||||||
|
self.edit_team_url = reverse("user:team-edit", args=(self.team.id,))
|
||||||
|
self.remove_team_url = reverse("user:team-remove", args=(self.team.id,))
|
||||||
|
self.leave_team_url = reverse("user:team-leave", args=(self.team.id,))
|
||||||
|
|
||||||
|
def test_views_anonymous_user(self):
|
||||||
|
""" Check correct status code for all requests
|
||||||
|
|
||||||
|
Assumption: User not logged in
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Unknown client
|
||||||
|
client = Client()
|
||||||
|
|
||||||
|
login_redirect_base = f"{self.login_url}?next="
|
||||||
|
fail_urls = {
|
||||||
|
self.index_url: f"{login_redirect_base}{self.index_url}",
|
||||||
|
self.notification_url: f"{login_redirect_base}{self.notification_url}",
|
||||||
|
self.api_token_url: f"{login_redirect_base}{self.api_token_url}",
|
||||||
|
self.contact_url: f"{login_redirect_base}{self.contact_url}",
|
||||||
|
self.team_url: f"{login_redirect_base}{self.team_url}",
|
||||||
|
self.new_team_url: f"{login_redirect_base}{self.new_team_url}",
|
||||||
|
self.data_team_url: f"{login_redirect_base}{self.data_team_url}",
|
||||||
|
self.edit_team_url: f"{login_redirect_base}{self.edit_team_url}",
|
||||||
|
self.remove_team_url: f"{login_redirect_base}{self.remove_team_url}",
|
||||||
|
self.leave_team_url: f"{login_redirect_base}{self.leave_team_url}",
|
||||||
|
}
|
||||||
|
|
||||||
|
for url in fail_urls:
|
||||||
|
response = client.get(url, follow=True)
|
||||||
|
self.assertEqual(response.redirect_chain[0], (f"{self.login_url}?next={url}", 302), msg=f"Failed for {url}. Redirect chain is {response.redirect_chain}")
|
||||||
|
|
||||||
|
def test_views_logged_in(self):
|
||||||
|
""" Check correct status code for all requests
|
||||||
|
|
||||||
|
Assumption: User logged in but has no groups
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Login client
|
||||||
|
client = Client()
|
||||||
|
client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||||
|
self.superuser.groups.set([])
|
||||||
|
success_urls = [
|
||||||
|
self.index_url,
|
||||||
|
self.notification_url,
|
||||||
|
self.contact_url,
|
||||||
|
self.team_url,
|
||||||
|
self.new_team_url,
|
||||||
|
self.data_team_url,
|
||||||
|
self.edit_team_url,
|
||||||
|
self.remove_team_url,
|
||||||
|
self.leave_team_url,
|
||||||
|
]
|
||||||
|
|
||||||
|
fail_urls = [
|
||||||
|
self.api_token_url, # expects default permission
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assert_url_success(client, success_urls)
|
||||||
|
self.assert_url_fail(client, fail_urls)
|
||||||
|
|
||||||
|
# Check for modified default user permission
|
||||||
|
self.superuser.groups.add(
|
||||||
|
Group.objects.get(
|
||||||
|
name=DEFAULT_GROUP
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
success_url = [
|
||||||
|
self.api_token_url, # must work now
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assert_url_success(client, success_url)
|
||||||
|
|
158
user/tests/test_workflow.py
Normal file
158
user/tests/test_workflow.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 30.05.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.urls import reverse
|
||||||
|
from konova.tests.test_views import BaseWorkflowTestCase
|
||||||
|
from user.models import Team
|
||||||
|
|
||||||
|
|
||||||
|
class UserWorkflowTestCase(BaseWorkflowTestCase):
|
||||||
|
""" This test case adds workflow tests
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
super().setUpTestData()
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
# Add user to team
|
||||||
|
self.team.users.add(self.superuser)
|
||||||
|
|
||||||
|
def test_new_team(self):
|
||||||
|
"""
|
||||||
|
Check a normal creation of a new team.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
team_name = self.create_dummy_string()
|
||||||
|
team_description = self.create_dummy_string()
|
||||||
|
|
||||||
|
new_url = reverse("user:team-new", args=())
|
||||||
|
|
||||||
|
post_data = {
|
||||||
|
"name": team_name,
|
||||||
|
"description": team_description,
|
||||||
|
"members": [self.superuser.id],
|
||||||
|
}
|
||||||
|
response = self.client_user.post(
|
||||||
|
new_url,
|
||||||
|
post_data
|
||||||
|
)
|
||||||
|
response_code = response.status_code
|
||||||
|
self.assertEqual(response_code, 302, msg=f"Unexpected status code received from response ({response_code})")
|
||||||
|
new_team = Team.objects.get(
|
||||||
|
name=team_name
|
||||||
|
)
|
||||||
|
self.assertEqual(new_team.description, team_description)
|
||||||
|
self.assertEqual([self.superuser], list(new_team.users.all()))
|
||||||
|
self.assertEqual([self.superuser], list(new_team.admins.all()), msg="Creator is not admin by default but should!")
|
||||||
|
|
||||||
|
def test_edit_team(self):
|
||||||
|
"""
|
||||||
|
Check editing of an existing team.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
existing_team = self.team
|
||||||
|
existing_team_name = existing_team.name
|
||||||
|
existing_team_description = existing_team.description
|
||||||
|
|
||||||
|
edited_team_name = self.create_dummy_string()
|
||||||
|
edited_team_description = self.create_dummy_string()
|
||||||
|
|
||||||
|
new_url = reverse("user:team-edit", args=(existing_team.id,))
|
||||||
|
|
||||||
|
post_data = {
|
||||||
|
"name": edited_team_name,
|
||||||
|
"description": edited_team_description,
|
||||||
|
}
|
||||||
|
# Expect the first try to fail since user is member but not admin of the team
|
||||||
|
response = self.client_user.post(
|
||||||
|
new_url,
|
||||||
|
post_data
|
||||||
|
)
|
||||||
|
response_code = response.status_code
|
||||||
|
self.assertEqual(response_code, 404, msg=f"Unexpected status code received from response ({response_code})")
|
||||||
|
|
||||||
|
# Now add the user to the list of team admins and try again!
|
||||||
|
existing_team.admins.add(self.superuser)
|
||||||
|
response = self.client_user.post(
|
||||||
|
new_url,
|
||||||
|
post_data
|
||||||
|
)
|
||||||
|
response_code = response.status_code
|
||||||
|
self.assertEqual(response_code, 200, msg=f"Unexpected status code received from response ({response_code})")
|
||||||
|
|
||||||
|
existing_team.refresh_from_db()
|
||||||
|
self.assertEqual(existing_team.description, existing_team_description)
|
||||||
|
self.assertEqual(existing_team.name, existing_team_name)
|
||||||
|
self.assertEqual([self.superuser], list(existing_team.users.all()))
|
||||||
|
self.assertEqual([self.superuser], list(existing_team.admins.all()), msg="Creator is not admin by default but should!")
|
||||||
|
|
||||||
|
def test_leave_team(self):
|
||||||
|
"""
|
||||||
|
Checks leaving of a user from an existing team.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
existing_team = self.team
|
||||||
|
|
||||||
|
new_url = reverse("user:team-leave", args=(existing_team.id,))
|
||||||
|
|
||||||
|
post_data = {
|
||||||
|
"confirm": True,
|
||||||
|
}
|
||||||
|
response = self.client_user.post(
|
||||||
|
new_url,
|
||||||
|
post_data
|
||||||
|
)
|
||||||
|
response_code = response.status_code
|
||||||
|
self.assertEqual(response_code, 302, msg=f"Unexpected status code received from response ({response_code})")
|
||||||
|
existing_team.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual([], list(existing_team.users.all()))
|
||||||
|
self.assertEqual([], list(existing_team.admins.all()))
|
||||||
|
|
||||||
|
def test_remove_team(self):
|
||||||
|
"""
|
||||||
|
Checks removing of an existing team.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
existing_team = self.team
|
||||||
|
|
||||||
|
new_url = reverse("user:team-remove", args=(existing_team.id,))
|
||||||
|
|
||||||
|
post_data = {
|
||||||
|
"confirm": True,
|
||||||
|
}
|
||||||
|
# User is member but not admin. This response must fail!
|
||||||
|
response = self.client_user.post(
|
||||||
|
new_url,
|
||||||
|
post_data
|
||||||
|
)
|
||||||
|
response_code = response.status_code
|
||||||
|
self.assertEqual(response_code, 404, msg=f"Unexpected status code received from response ({response_code})")
|
||||||
|
|
||||||
|
# Add user to admins and try again
|
||||||
|
existing_team.admins.add(self.superuser)
|
||||||
|
response = self.client_user.post(
|
||||||
|
new_url,
|
||||||
|
post_data
|
||||||
|
)
|
||||||
|
response_code = response.status_code
|
||||||
|
self.assertEqual(response_code, 302, msg=f"Unexpected status code received from response ({response_code})")
|
||||||
|
existing_team.refresh_from_db()
|
||||||
|
self.assertIsNotNone(existing_team.deleted, msg="Deleted action not created")
|
||||||
|
self.assertNotIn(existing_team, self.superuser.shared_teams)
|
@ -163,7 +163,7 @@ def index_team_view(request: HttpRequest):
|
|||||||
template = "user/team/index.html"
|
template = "user/team/index.html"
|
||||||
user = request.user
|
user = request.user
|
||||||
context = {
|
context = {
|
||||||
"teams": user.teams.all(),
|
"teams": user.shared_teams,
|
||||||
"tab_title": _("Teams"),
|
"tab_title": _("Teams"),
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
@ -183,7 +183,8 @@ def new_team_view(request: HttpRequest):
|
|||||||
@login_required
|
@login_required
|
||||||
def edit_team_view(request: HttpRequest, id: str):
|
def edit_team_view(request: HttpRequest, id: str):
|
||||||
team = get_object_or_404(Team, id=id)
|
team = get_object_or_404(Team, id=id)
|
||||||
if request.user != team.admin:
|
user_is_admin = team.is_user_admin(request.user)
|
||||||
|
if not user_is_admin:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
form = EditTeamModalForm(request.POST or None, instance=team, request=request)
|
form = EditTeamModalForm(request.POST or None, instance=team, request=request)
|
||||||
return form.process_request(
|
return form.process_request(
|
||||||
@ -196,7 +197,8 @@ def edit_team_view(request: HttpRequest, id: str):
|
|||||||
@login_required
|
@login_required
|
||||||
def remove_team_view(request: HttpRequest, id: str):
|
def remove_team_view(request: HttpRequest, id: str):
|
||||||
team = get_object_or_404(Team, id=id)
|
team = get_object_or_404(Team, id=id)
|
||||||
if request.user != team.admin:
|
user_is_admin = team.is_user_admin(request.user)
|
||||||
|
if not user_is_admin:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
form = RemoveTeamModalForm(request.POST or None, instance=team, request=request)
|
form = RemoveTeamModalForm(request.POST or None, instance=team, request=request)
|
||||||
return form.process_request(
|
return form.process_request(
|
||||||
|
Loading…
Reference in New Issue
Block a user