#101 Team sharing tests
* adds tests for team sharing * extends the API for team sharing support * adds shared_teams property shortcut for ShareableObjectMixin * adds full support for team-based sharing to all views and functions * simplifies ShareModalForm * adds/updates translations
This commit is contained in:
parent
6dac847d22
commit
aa675aa046
@ -85,6 +85,26 @@ class APIV1GetTestCase(BaseAPIV1TestCase):
|
|||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
self.fail(e)
|
self.fail(e)
|
||||||
|
|
||||||
|
def test_get_shared(self):
|
||||||
|
""" Tests api GET on shared info of the intervention
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.intervention.share_with_user(self.superuser)
|
||||||
|
self.intervention.share_with_team(self.team)
|
||||||
|
url = reverse("api:v1:intervention-share", args=(str(self.intervention.id),))
|
||||||
|
response = self._run_get_request(url)
|
||||||
|
content = json.loads(response.content)
|
||||||
|
self.assertIn("users", content)
|
||||||
|
self.assertIn(self.superuser.username, content["users"])
|
||||||
|
self.assertEqual(1, len(content["users"]))
|
||||||
|
self.assertIn("teams", content)
|
||||||
|
self.assertEqual(1, len(content["teams"]))
|
||||||
|
for team in content["teams"]:
|
||||||
|
self.assertEqual(team["id"], str(self.team.id))
|
||||||
|
self.assertEqual(team["name"], self.team.name)
|
||||||
|
|
||||||
def test_get_compensation(self):
|
def test_get_compensation(self):
|
||||||
""" Tests api GET
|
""" Tests api GET
|
||||||
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"users": [
|
||||||
|
"CHANGE_ME"
|
||||||
|
],
|
||||||
|
"teams": [
|
||||||
|
"CHANGE_ME"
|
||||||
|
]
|
||||||
|
}
|
@ -184,3 +184,24 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
self.assertEqual(put_body["intervention"], str(self.deduction.intervention.id))
|
self.assertEqual(put_body["intervention"], str(self.deduction.intervention.id))
|
||||||
self.assertEqual(put_body["eco_account"], str(self.deduction.account.id))
|
self.assertEqual(put_body["eco_account"], str(self.deduction.account.id))
|
||||||
self.assertEqual(put_body["surface"], self.deduction.surface)
|
self.assertEqual(put_body["surface"], self.deduction.surface)
|
||||||
|
|
||||||
|
def test_update_share_intervention(self):
|
||||||
|
self.intervention.share_with_user(self.superuser)
|
||||||
|
url = reverse("api:v1:intervention-share", args=(str(self.intervention.id),))
|
||||||
|
json_file_path = "api/tests/v1/update/intervention_share_update_put_body.json"
|
||||||
|
with open(json_file_path) as json_file:
|
||||||
|
put_body = json.load(fp=json_file)
|
||||||
|
put_body["users"] = [self.user.username]
|
||||||
|
put_body["teams"] = [self.team.name]
|
||||||
|
|
||||||
|
self.assertFalse(self.intervention.is_shared_with(self.user))
|
||||||
|
self.assertEqual(0, self.intervention.shared_teams.count())
|
||||||
|
|
||||||
|
response = self._run_update_request(url, put_body)
|
||||||
|
self.assertEqual(response.status_code, 200, msg=response.content)
|
||||||
|
self.intervention.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(1, self.intervention.shared_teams.count())
|
||||||
|
self.assertEqual(2, self.intervention.shared_users.count())
|
||||||
|
self.assertEqual(self.team.name, self.intervention.shared_teams.first().name)
|
||||||
|
self.assertTrue(self.intervention.is_shared_with(self.user))
|
||||||
|
@ -19,7 +19,7 @@ from ema.models import Ema
|
|||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.utils.message_templates import DATA_UNSHARED
|
from konova.utils.message_templates import DATA_UNSHARED
|
||||||
from konova.utils.user_checks import is_default_group_only
|
from konova.utils.user_checks import is_default_group_only
|
||||||
from user.models import User
|
from user.models import User, Team
|
||||||
|
|
||||||
|
|
||||||
class AbstractAPIView(View):
|
class AbstractAPIView(View):
|
||||||
@ -198,13 +198,21 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
users = self._get_shared_users_of_object(id)
|
users = self._get_shared_users_of_object(id)
|
||||||
|
teams = self._get_shared_teams_of_object(id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self._return_error_response(e)
|
return self._return_error_response(e)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"users": [
|
"users": [
|
||||||
user.username for user in users
|
user.username for user in users
|
||||||
]
|
],
|
||||||
|
"teams": [
|
||||||
|
{
|
||||||
|
"id": team.id,
|
||||||
|
"name": team.name,
|
||||||
|
}
|
||||||
|
for team in teams
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
@ -258,6 +266,22 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
users = obj.shared_users
|
users = obj.shared_users
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
def _get_shared_teams_of_object(self, id) -> QuerySet:
|
||||||
|
""" Check permissions and get the teams
|
||||||
|
|
||||||
|
Args:
|
||||||
|
id (str): The object's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
users (QuerySet)
|
||||||
|
"""
|
||||||
|
obj = self.model.objects.get(
|
||||||
|
id=id
|
||||||
|
)
|
||||||
|
self._check_user_has_shared_access(obj)
|
||||||
|
teams = obj.shared_teams
|
||||||
|
return teams
|
||||||
|
|
||||||
def _process_put_body(self, body: bytes, id: str):
|
def _process_put_body(self, body: bytes, id: str):
|
||||||
""" Reads the body data, performs validity checks and sets the new users
|
""" Reads the body data, performs validity checks and sets the new users
|
||||||
|
|
||||||
@ -271,19 +295,26 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
obj = self.model.objects.get(id=id)
|
obj = self.model.objects.get(id=id)
|
||||||
self._check_user_has_shared_access(obj)
|
self._check_user_has_shared_access(obj)
|
||||||
|
|
||||||
new_users = json.loads(body.decode("utf-8"))
|
content = json.loads(body.decode("utf-8"))
|
||||||
new_users = new_users.get("users", [])
|
new_users = content.get("users", [])
|
||||||
if len(new_users) == 0:
|
if len(new_users) == 0:
|
||||||
raise ValueError("Shared user list must not be empty!")
|
raise ValueError("Shared user list must not be empty!")
|
||||||
|
new_teams = content.get("teams", [])
|
||||||
|
|
||||||
# Eliminate duplicates
|
# Eliminate duplicates
|
||||||
new_users = list(dict.fromkeys(new_users))
|
new_users = list(dict.fromkeys(new_users))
|
||||||
|
new_teams = list(dict.fromkeys(new_teams))
|
||||||
|
|
||||||
# Make sure each of these names exist as a user
|
# Make sure each of these names exist as a user
|
||||||
new_users_objs = []
|
new_users_objs = []
|
||||||
for user in new_users:
|
for user in new_users:
|
||||||
new_users_objs.append(User.objects.get(username=user))
|
new_users_objs.append(User.objects.get(username=user))
|
||||||
|
|
||||||
|
# Make sure each of these names exist as a user
|
||||||
|
new_teams_objs = []
|
||||||
|
for team_name in new_teams:
|
||||||
|
new_teams_objs.append(Team.objects.get(name=team_name))
|
||||||
|
|
||||||
if is_default_group_only(self.user):
|
if is_default_group_only(self.user):
|
||||||
# Default only users are not allowed to remove other users from having access. They can only add new ones!
|
# Default only users are not allowed to remove other users from having access. They can only add new ones!
|
||||||
new_users_to_be_added = User.objects.filter(
|
new_users_to_be_added = User.objects.filter(
|
||||||
@ -292,7 +323,16 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
id__in=obj.shared_users
|
id__in=obj.shared_users
|
||||||
)
|
)
|
||||||
new_users_objs = obj.shared_users.union(new_users_to_be_added)
|
new_users_objs = obj.shared_users.union(new_users_to_be_added)
|
||||||
|
|
||||||
|
new_teams_to_be_added = Team.objects.filter(
|
||||||
|
name__in=new_teams
|
||||||
|
).exclude(
|
||||||
|
id__in=obj.shared_teams
|
||||||
|
)
|
||||||
|
new_teams_objs = obj.shared_teams.union(new_teams_to_be_added)
|
||||||
|
|
||||||
obj.share_with_user_list(new_users_objs)
|
obj.share_with_user_list(new_users_objs)
|
||||||
|
obj.share_with_team_list(new_teams_objs)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,8 +59,9 @@ class CheckboxCompensationTableFilter(CheckboxTableFilter):
|
|||||||
"""
|
"""
|
||||||
if not value:
|
if not value:
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
intervention__users__in=[self.user], # requesting user has access
|
Q(intervention__users__in=[self.user]) | # requesting user has access
|
||||||
)
|
Q(intervention__teams__users__in=[self.user])
|
||||||
|
).distinct()
|
||||||
else:
|
else:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
@ -127,24 +128,6 @@ class CheckboxEcoAccountTableFilter(CheckboxTableFilter):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_show_all(self, queryset, name, value) -> QuerySet:
|
|
||||||
""" Filters queryset depending on value of 'show_all' setting
|
|
||||||
|
|
||||||
Args:
|
|
||||||
queryset ():
|
|
||||||
name ():
|
|
||||||
value ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not value:
|
|
||||||
return queryset.filter(
|
|
||||||
users__in=[self.user], # requesting user has access
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def filter_only_show_unrecorded(self, queryset, name, value) -> QuerySet:
|
def filter_only_show_unrecorded(self, queryset, name, value) -> QuerySet:
|
||||||
""" Filters queryset depending on value of 'show_recorded' setting
|
""" Filters queryset depending on value of 'show_recorded' setting
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ Created on: 16.11.21
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from user.models import User
|
from user.models import User, Team
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import QuerySet, Sum
|
from django.db.models import QuerySet, Sum
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
@ -299,7 +299,7 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
|||||||
# Compensations inherit their shared state from the interventions
|
# Compensations inherit their shared state from the interventions
|
||||||
return self.intervention.is_shared_with(user)
|
return self.intervention.is_shared_with(user)
|
||||||
|
|
||||||
def share_with(self, user: User):
|
def share_with_user(self, user: User):
|
||||||
""" Adds user to list of shared access users
|
""" Adds user to list of shared access users
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -308,10 +308,9 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.intervention.is_shared_with(user):
|
self.intervention.users.add(user)
|
||||||
self.intervention.users.add(user)
|
|
||||||
|
|
||||||
def share_with_list(self, user_list: list):
|
def share_with_user_list(self, user_list: list):
|
||||||
""" Sets the list of shared access users
|
""" Sets the list of shared access users
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -322,6 +321,28 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
|||||||
"""
|
"""
|
||||||
self.intervention.users.set(user_list)
|
self.intervention.users.set(user_list)
|
||||||
|
|
||||||
|
def share_with_team(self, team: Team):
|
||||||
|
""" Adds team to list of shared access teams
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team (Team): The team to be added to the object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.intervention.teams.add(team)
|
||||||
|
|
||||||
|
def share_with_team_list(self, team_list: list):
|
||||||
|
""" Sets the list of shared access teams
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_list (list): The teams to be added to the object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.intervention.teams.set(team_list)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def shared_users(self) -> QuerySet:
|
def shared_users(self) -> QuerySet:
|
||||||
""" Shortcut for fetching the users which have shared access on this object
|
""" Shortcut for fetching the users which have shared access on this object
|
||||||
@ -331,6 +352,15 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
|||||||
"""
|
"""
|
||||||
return self.intervention.users.all()
|
return self.intervention.users.all()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shared_teams(self) -> QuerySet:
|
||||||
|
""" Shortcut for fetching the teams which have shared access on this object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
users (QuerySet)
|
||||||
|
"""
|
||||||
|
return self.intervention.teams.all()
|
||||||
|
|
||||||
def get_documents(self) -> QuerySet:
|
def get_documents(self) -> QuerySet:
|
||||||
""" Getter for all documents of a compensation
|
""" Getter for all documents of a compensation
|
||||||
|
|
||||||
|
@ -233,7 +233,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
self.eco_account.set_recorded(self.superuser)
|
self.eco_account.set_recorded(self.superuser)
|
||||||
self.intervention.share_with_user(self.superuser)
|
self.intervention.share_with_user(self.superuser)
|
||||||
self.eco_account.refresh_from_db()
|
self.eco_account.refresh_from_db()
|
||||||
self.assertIn(self.superuser, self.intervention.is_shared_with(self.superuser))
|
self.assertTrue(self.superuser, self.intervention.is_shared_with(self.superuser))
|
||||||
|
|
||||||
deduction = EcoAccountDeduction.objects.create(
|
deduction = EcoAccountDeduction.objects.create(
|
||||||
intervention=self.intervention,
|
intervention=self.intervention,
|
||||||
|
@ -9,7 +9,7 @@ from dal import autocomplete
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \
|
from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \
|
||||||
REVOCATION_EDITED
|
REVOCATION_EDITED, ENTRY_REMOVE_MISSING_PERMISSION
|
||||||
from user.models import User, Team
|
from user.models import User, Team
|
||||||
from user.models import UserActionLogEntry
|
from user.models import UserActionLogEntry
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
@ -37,7 +37,7 @@ class ShareModalForm(BaseModalForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
team_select = forms.ModelMultipleChoiceField(
|
teams = forms.ModelMultipleChoiceField(
|
||||||
label=_("Add team to share with"),
|
label=_("Add team to share with"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
help_text=_("Multiple selection possible - You can only select teams which do not already have access."),
|
help_text=_("Multiple selection possible - You can only select teams which do not already have access."),
|
||||||
@ -51,7 +51,7 @@ class ShareModalForm(BaseModalForm):
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
user_select = forms.ModelMultipleChoiceField(
|
users = forms.ModelMultipleChoiceField(
|
||||||
label=_("Add user to share with"),
|
label=_("Add user to share with"),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
help_text=_("Multiple selection possible - You can only select users which do not already have access. Enter the full username."),
|
help_text=_("Multiple selection possible - You can only select users which do not already have access. Enter the full username."),
|
||||||
@ -63,21 +63,8 @@ class ShareModalForm(BaseModalForm):
|
|||||||
"data-placeholder": _("Click for selection"),
|
"data-placeholder": _("Click for selection"),
|
||||||
"data-minimum-input-length": 3,
|
"data-minimum-input-length": 3,
|
||||||
},
|
},
|
||||||
forward=["users"]
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
users = forms.MultipleChoiceField(
|
|
||||||
label=_("Shared with"),
|
|
||||||
label_suffix="",
|
|
||||||
required=True,
|
|
||||||
help_text=_("Remove check to remove access for this user"),
|
|
||||||
widget=forms.CheckboxSelectMultiple(
|
|
||||||
attrs={
|
|
||||||
"class": "list-unstyled",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
choices=[]
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -91,6 +78,48 @@ class ShareModalForm(BaseModalForm):
|
|||||||
|
|
||||||
self._init_fields()
|
self._init_fields()
|
||||||
|
|
||||||
|
def _user_team_valid(self):
|
||||||
|
""" Checks whether users and teams have been removed by the user and if the user is allowed to do so or not
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
users = self.cleaned_data.get("users", User.objects.none())
|
||||||
|
teams = self.cleaned_data.get("teams", Team.objects.none())
|
||||||
|
|
||||||
|
_is_valid = True
|
||||||
|
if is_default_group_only(self.user):
|
||||||
|
shared_users = self.instance.shared_users
|
||||||
|
shared_teams = self.instance.shared_teams
|
||||||
|
|
||||||
|
shared_users_are_removed = not set(shared_users).issubset(users)
|
||||||
|
shared_teams_are_removed = not set(shared_teams).issubset(teams)
|
||||||
|
|
||||||
|
if shared_users_are_removed:
|
||||||
|
self.add_error(
|
||||||
|
"users",
|
||||||
|
ENTRY_REMOVE_MISSING_PERMISSION
|
||||||
|
)
|
||||||
|
_is_valid = False
|
||||||
|
if shared_teams_are_removed:
|
||||||
|
self.add_error(
|
||||||
|
"teams",
|
||||||
|
ENTRY_REMOVE_MISSING_PERMISSION
|
||||||
|
)
|
||||||
|
_is_valid = False
|
||||||
|
return _is_valid
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
""" Extended validity check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
super_valid = super().is_valid()
|
||||||
|
user_team_valid = self._user_team_valid()
|
||||||
|
_is_valid = super_valid and user_team_valid
|
||||||
|
return _is_valid
|
||||||
|
|
||||||
def _init_fields(self):
|
def _init_fields(self):
|
||||||
""" Wraps initializing of fields
|
""" Wraps initializing of fields
|
||||||
|
|
||||||
@ -105,39 +134,12 @@ class ShareModalForm(BaseModalForm):
|
|||||||
self.share_link
|
self.share_link
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize users field
|
|
||||||
# Disable field if user is not in registration or conservation group
|
|
||||||
if is_default_group_only(self.request.user):
|
|
||||||
self.disable_form_field("users")
|
|
||||||
|
|
||||||
self._add_user_choices_to_field()
|
|
||||||
self._add_teams_to_field()
|
|
||||||
|
|
||||||
def _add_teams_to_field(self):
|
|
||||||
form_data = {
|
form_data = {
|
||||||
"team_select": self.instance.teams.all()
|
"teams": self.instance.teams.all(),
|
||||||
|
"users": self.instance.users.all(),
|
||||||
}
|
}
|
||||||
self.load_initial_data(form_data)
|
self.load_initial_data(form_data)
|
||||||
|
|
||||||
def _add_user_choices_to_field(self):
|
|
||||||
""" Transforms the instance's sharing users into a list for the form field
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
users = self.instance.users.all()
|
|
||||||
choices = []
|
|
||||||
for n in users:
|
|
||||||
choices.append(
|
|
||||||
(n.id, n.username)
|
|
||||||
)
|
|
||||||
self.fields["users"].choices = choices
|
|
||||||
u_ids = list(users.values_list("id", flat=True))
|
|
||||||
self.initialize_form_field(
|
|
||||||
"users",
|
|
||||||
u_ids
|
|
||||||
)
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
self.instance.update_sharing_user(self)
|
self.instance.update_sharing_user(self)
|
||||||
|
|
||||||
|
@ -76,19 +76,14 @@ class ShareUserAutocomplete(Select2QuerySetView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.request.user.is_anonymous:
|
if self.request.user.is_anonymous:
|
||||||
return User.objects.none()
|
return User.objects.none()
|
||||||
exclude_user_ids = self.forwarded.get("users", [])
|
qs = User.objects.all()
|
||||||
_exclude = {"id__in": exclude_user_ids}
|
|
||||||
qs = User.objects.all().exclude(
|
|
||||||
**_exclude
|
|
||||||
).order_by(
|
|
||||||
"username"
|
|
||||||
)
|
|
||||||
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(
|
||||||
Q(username=self.q) |
|
Q(username=self.q) |
|
||||||
Q(email=self.q)
|
Q(email=self.q)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
qs = qs.order_by("username")
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
@ -99,13 +94,15 @@ 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()
|
||||||
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 = Team.objects.filter(
|
qs = qs.filter(
|
||||||
Q(name__icontains=self.q)
|
name__icontains=self.q
|
||||||
).order_by(
|
|
||||||
"name"
|
|
||||||
)
|
)
|
||||||
|
qs = qs.order_by(
|
||||||
|
"name"
|
||||||
|
)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
@ -297,8 +297,9 @@ class ShareableTableFilterMixin(django_filters.FilterSet):
|
|||||||
"""
|
"""
|
||||||
if not value:
|
if not value:
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
users__in=[self.user], # requesting user has access
|
Q(users__in=[self.user]) | # requesting user has access
|
||||||
)
|
Q(teams__users__in=[self.user])
|
||||||
|
).distinct()
|
||||||
else:
|
else:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -501,30 +501,37 @@ class ShareableObjectMixin(models.Model):
|
|||||||
form_data = form.cleaned_data
|
form_data = form.cleaned_data
|
||||||
|
|
||||||
# Fetch selected teams and find out which user IDs are in removed teams -> mails need to be sent
|
# Fetch selected teams and find out which user IDs are in removed teams -> mails need to be sent
|
||||||
accessing_teams = form_data["team_select"]
|
accessing_teams = form_data["teams"]
|
||||||
removed_team_users = self.teams.all().exclude(
|
removed_team_users = self.teams.all().exclude(
|
||||||
id__in=accessing_teams
|
id__in=accessing_teams
|
||||||
).values_list("users__id", flat=True)
|
).values_list("users__id", flat=True)
|
||||||
|
accessing_team_users = User.objects.filter(
|
||||||
new_accessing_users = list(form_data["user_select"].values_list("id", flat=True))
|
id__in=accessing_teams.values_list("users", flat=True)
|
||||||
keep_accessing_users = form_data["users"]
|
|
||||||
accessing_users = keep_accessing_users + new_accessing_users
|
|
||||||
users = User.objects.filter(
|
|
||||||
id__in=accessing_users
|
|
||||||
)
|
)
|
||||||
|
new_team_users = accessing_team_users.exclude(
|
||||||
|
id__in=self.shared_users
|
||||||
|
).values_list("id", flat=True)
|
||||||
|
|
||||||
|
# Fetch selected users
|
||||||
|
accessing_users = form_data["users"]
|
||||||
removed_users = self.users.all().exclude(
|
removed_users = self.users.all().exclude(
|
||||||
id__in=accessing_users
|
id__in=accessing_users
|
||||||
).values_list("id", flat=True)
|
).values_list("id", flat=True)
|
||||||
|
new_users = accessing_users.exclude(
|
||||||
|
id__in=self.shared_users
|
||||||
|
).values_list("id", flat=True)
|
||||||
|
|
||||||
|
new_users = new_users.union(new_team_users)
|
||||||
removed_users = removed_users.union(removed_team_users)
|
removed_users = removed_users.union(removed_team_users)
|
||||||
|
|
||||||
# Send mails
|
# Send mails
|
||||||
for user_id in removed_users:
|
for user_id in removed_users:
|
||||||
celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user_id)
|
celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user_id)
|
||||||
for user_id in new_accessing_users:
|
for user_id in new_users:
|
||||||
celery_send_mail_shared_access_given.delay(self.identifier, self.title, user_id)
|
celery_send_mail_shared_access_given.delay(self.identifier, self.title, user_id)
|
||||||
|
|
||||||
# Set new shared users
|
# Set new shared users
|
||||||
self.share_with_user_list(users)
|
self.share_with_user_list(accessing_users)
|
||||||
self.share_with_team_list(accessing_teams)
|
self.share_with_team_list(accessing_teams)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -536,6 +543,15 @@ class ShareableObjectMixin(models.Model):
|
|||||||
"""
|
"""
|
||||||
return self.users.all()
|
return self.users.all()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shared_teams(self) -> QuerySet:
|
||||||
|
""" Shortcut for fetching the teams which have shared access on this object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
teams (QuerySet)
|
||||||
|
"""
|
||||||
|
return self.teams.all()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_share_url(self):
|
def get_share_url(self):
|
||||||
""" Returns the share url for the object
|
""" Returns the share url for the object
|
||||||
|
@ -71,6 +71,7 @@ class AutocompleteTestCase(BaseTestCase):
|
|||||||
"codes-registration-office-autocomplete",
|
"codes-registration-office-autocomplete",
|
||||||
"codes-conservation-office-autocomplete",
|
"codes-conservation-office-autocomplete",
|
||||||
"share-user-autocomplete",
|
"share-user-autocomplete",
|
||||||
|
"share-team-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)
|
||||||
|
@ -9,7 +9,7 @@ import datetime
|
|||||||
|
|
||||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
|
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
|
||||||
from ema.models import Ema
|
from ema.models import Ema
|
||||||
from user.models import User
|
from user.models import User, Team
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.contrib.gis.geos import MultiPolygon, Polygon
|
from django.contrib.gis.geos import MultiPolygon, Polygon
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
@ -65,6 +65,7 @@ class BaseTestCase(TestCase):
|
|||||||
self.create_dummy_states()
|
self.create_dummy_states()
|
||||||
self.create_dummy_action()
|
self.create_dummy_action()
|
||||||
self.codes = self.create_dummy_codes()
|
self.codes = self.create_dummy_codes()
|
||||||
|
self.team = self.create_dummy_team()
|
||||||
|
|
||||||
# Set the default group as only group for the user
|
# Set the default group as only group for the user
|
||||||
default_group = self.groups.get(name=DEFAULT_GROUP)
|
default_group = self.groups.get(name=DEFAULT_GROUP)
|
||||||
@ -251,6 +252,24 @@ class BaseTestCase(TestCase):
|
|||||||
])
|
])
|
||||||
return codes
|
return codes
|
||||||
|
|
||||||
|
def create_dummy_team(self):
|
||||||
|
""" Creates a dummy team
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.superuser is None:
|
||||||
|
self.create_users()
|
||||||
|
|
||||||
|
team = Team.objects.get_or_create(
|
||||||
|
name="Testteam",
|
||||||
|
description="Testdescription",
|
||||||
|
admin=self.superuser,
|
||||||
|
)[0]
|
||||||
|
team.users.add(self.superuser)
|
||||||
|
|
||||||
|
return team
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_dummy_geometry() -> MultiPolygon:
|
def create_dummy_geometry() -> MultiPolygon:
|
||||||
""" Creates some geometry
|
""" Creates some geometry
|
||||||
|
@ -12,11 +12,13 @@ FORM_INVALID = _("There was an error on this form.")
|
|||||||
PARAMS_INVALID = _("Invalid parameters")
|
PARAMS_INVALID = _("Invalid parameters")
|
||||||
INTERVENTION_INVALID = _("There are errors in this intervention.")
|
INTERVENTION_INVALID = _("There are errors in this intervention.")
|
||||||
IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since another entry has been added in the meanwhile, which uses this identifier")
|
IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since another entry has been added in the meanwhile, which uses this identifier")
|
||||||
|
ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.")
|
||||||
|
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
|
||||||
|
CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
|
||||||
|
|
||||||
|
# SHARE
|
||||||
DATA_UNSHARED = _("This data is not shared with you")
|
DATA_UNSHARED = _("This data is not shared with you")
|
||||||
DATA_UNSHARED_EXPLANATION = _("Remember: This data has not been shared with you, yet. This means you can only read but can not edit or perform any actions like running a check or recording.")
|
DATA_UNSHARED_EXPLANATION = _("Remember: This data has not been shared with you, yet. This means you can only read but can not edit or perform any actions like running a check or recording.")
|
||||||
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
|
|
||||||
|
|
||||||
CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
|
|
||||||
|
|
||||||
# FILES
|
# FILES
|
||||||
FILE_TYPE_UNSUPPORTED = _("Unsupported file type")
|
FILE_TYPE_UNSUPPORTED = _("Unsupported file type")
|
||||||
|
Binary file not shown.
@ -7,8 +7,8 @@
|
|||||||
#: compensation/forms/modalForms.py:47 compensation/forms/modalForms.py:63
|
#: compensation/forms/modalForms.py:47 compensation/forms/modalForms.py:63
|
||||||
#: compensation/forms/modalForms.py:356 compensation/forms/modalForms.py:463
|
#: compensation/forms/modalForms.py:356 compensation/forms/modalForms.py:463
|
||||||
#: intervention/forms/forms.py:54 intervention/forms/forms.py:156
|
#: intervention/forms/forms.py:54 intervention/forms/forms.py:156
|
||||||
#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:148
|
#: intervention/forms/forms.py:168 intervention/forms/modalForms.py:154
|
||||||
#: intervention/forms/modalForms.py:161 intervention/forms/modalForms.py:174
|
#: intervention/forms/modalForms.py:167 intervention/forms/modalForms.py:180
|
||||||
#: konova/filters/mixins.py:53 konova/filters/mixins.py:54
|
#: konova/filters/mixins.py:53 konova/filters/mixins.py:54
|
||||||
#: konova/filters/mixins.py:81 konova/filters/mixins.py:82
|
#: konova/filters/mixins.py:81 konova/filters/mixins.py:82
|
||||||
#: konova/filters/mixins.py:94 konova/filters/mixins.py:95
|
#: konova/filters/mixins.py:94 konova/filters/mixins.py:95
|
||||||
@ -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-02-18 09:35+0100\n"
|
"POT-Creation-Date: 2022-02-18 12:35+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"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"
|
||||||
@ -221,7 +221,7 @@ msgstr "Abbuchungen"
|
|||||||
#: compensation/templates/compensation/detail/eco_account/includes/states-before.html:36
|
#: compensation/templates/compensation/detail/eco_account/includes/states-before.html:36
|
||||||
#: ema/templates/ema/detail/includes/states-after.html:36
|
#: ema/templates/ema/detail/includes/states-after.html:36
|
||||||
#: ema/templates/ema/detail/includes/states-before.html:36
|
#: ema/templates/ema/detail/includes/states-before.html:36
|
||||||
#: intervention/forms/modalForms.py:359
|
#: intervention/forms/modalForms.py:365
|
||||||
msgid "Surface"
|
msgid "Surface"
|
||||||
msgstr "Fläche"
|
msgstr "Fläche"
|
||||||
|
|
||||||
@ -284,8 +284,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:89 intervention/forms/modalForms.py:370
|
#: compensation/tables.py:89 intervention/forms/modalForms.py:376
|
||||||
#: intervention/forms/modalForms.py:377 intervention/tables.py:88
|
#: intervention/forms/modalForms.py:383 intervention/tables.py:88
|
||||||
#: 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
|
||||||
@ -295,7 +295,7 @@ 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:266
|
||||||
#: compensation/templates/compensation/detail/eco_account/view.html:20
|
#: compensation/templates/compensation/detail/eco_account/view.html:20
|
||||||
#: intervention/forms/modalForms.py:343 intervention/forms/modalForms.py:350
|
#: intervention/forms/modalForms.py:349 intervention/forms/modalForms.py:356
|
||||||
#: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
|
#: konova/templates/konova/includes/quickstart/ecoaccounts.html:4
|
||||||
#: templates/navbars/navbar.html:34
|
#: templates/navbars/navbar.html:34
|
||||||
msgid "Eco-account"
|
msgid "Eco-account"
|
||||||
@ -364,7 +364,7 @@ msgstr "Kompensation XY; Flur ABC"
|
|||||||
#: ema/templates/ema/detail/includes/actions.html:34
|
#: ema/templates/ema/detail/includes/actions.html:34
|
||||||
#: ema/templates/ema/detail/includes/deadlines.html:34
|
#: ema/templates/ema/detail/includes/deadlines.html:34
|
||||||
#: ema/templates/ema/detail/includes/documents.html:34
|
#: ema/templates/ema/detail/includes/documents.html:34
|
||||||
#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:173
|
#: intervention/forms/forms.py:180 intervention/forms/modalForms.py:179
|
||||||
#: intervention/templates/intervention/detail/includes/documents.html:34
|
#: intervention/templates/intervention/detail/includes/documents.html:34
|
||||||
#: intervention/templates/intervention/detail/includes/payments.html:34
|
#: intervention/templates/intervention/detail/includes/payments.html:34
|
||||||
#: intervention/templates/intervention/detail/includes/revocation.html:38
|
#: intervention/templates/intervention/detail/includes/revocation.html:38
|
||||||
@ -484,7 +484,7 @@ msgid "Due on which date"
|
|||||||
msgstr "Zahlung wird an diesem Datum erwartet"
|
msgstr "Zahlung wird an diesem Datum erwartet"
|
||||||
|
|
||||||
#: compensation/forms/modalForms.py:64 compensation/forms/modalForms.py:357
|
#: compensation/forms/modalForms.py:64 compensation/forms/modalForms.py:357
|
||||||
#: intervention/forms/modalForms.py:175 konova/forms.py:395
|
#: intervention/forms/modalForms.py:181 konova/forms.py:395
|
||||||
msgid "Additional comment, maximum {} letters"
|
msgid "Additional comment, maximum {} letters"
|
||||||
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
|
msgstr "Zusätzlicher Kommentar, maximal {} Zeichen"
|
||||||
|
|
||||||
@ -512,7 +512,7 @@ msgstr "Zusatzbezeichnung"
|
|||||||
msgid "Select an additional biotope type"
|
msgid "Select an additional biotope type"
|
||||||
msgstr "Zusatzbezeichnung wählen"
|
msgstr "Zusatzbezeichnung wählen"
|
||||||
|
|
||||||
#: compensation/forms/modalForms.py:197 intervention/forms/modalForms.py:361
|
#: compensation/forms/modalForms.py:197 intervention/forms/modalForms.py:367
|
||||||
msgid "in m²"
|
msgid "in m²"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -540,7 +540,7 @@ msgstr "Fristart wählen"
|
|||||||
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31
|
#: compensation/templates/compensation/detail/compensation/includes/deadlines.html:31
|
||||||
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31
|
#: compensation/templates/compensation/detail/eco_account/includes/deadlines.html:31
|
||||||
#: ema/templates/ema/detail/includes/deadlines.html:31
|
#: ema/templates/ema/detail/includes/deadlines.html:31
|
||||||
#: intervention/forms/modalForms.py:147
|
#: intervention/forms/modalForms.py:153
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Datum"
|
msgstr "Datum"
|
||||||
|
|
||||||
@ -1000,14 +1000,14 @@ msgstr "Zuletzt bearbeitet"
|
|||||||
|
|
||||||
#: compensation/templates/compensation/detail/compensation/view.html:100
|
#: compensation/templates/compensation/detail/compensation/view.html:100
|
||||||
#: compensation/templates/compensation/detail/eco_account/view.html:83
|
#: compensation/templates/compensation/detail/eco_account/view.html:83
|
||||||
#: ema/templates/ema/detail/view.html:76 intervention/forms/modalForms.py:70
|
#: ema/templates/ema/detail/view.html:76
|
||||||
#: intervention/templates/intervention/detail/view.html:116
|
#: intervention/templates/intervention/detail/view.html:116
|
||||||
msgid "Shared with"
|
msgid "Shared with"
|
||||||
msgstr "Freigegeben für"
|
msgstr "Freigegeben für"
|
||||||
|
|
||||||
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:15
|
#: compensation/templates/compensation/detail/eco_account/includes/controls.html:15
|
||||||
#: ema/templates/ema/detail/includes/controls.html:15
|
#: ema/templates/ema/detail/includes/controls.html:15
|
||||||
#: intervention/forms/modalForms.py:84
|
#: intervention/forms/modalForms.py:71
|
||||||
#: intervention/templates/intervention/detail/includes/controls.html:15
|
#: intervention/templates/intervention/detail/includes/controls.html:15
|
||||||
msgid "Share"
|
msgid "Share"
|
||||||
msgstr "Freigabe"
|
msgstr "Freigabe"
|
||||||
@ -1348,46 +1348,48 @@ msgstr ""
|
|||||||
"Mehrfachauswahl möglich - Sie können nur Nutzer wählen, für die der Eintrag "
|
"Mehrfachauswahl möglich - Sie können nur Nutzer wählen, für die der Eintrag "
|
||||||
"noch nicht freigegeben wurde. Geben Sie den ganzen Nutzernamen an."
|
"noch nicht freigegeben wurde. Geben Sie den ganzen Nutzernamen an."
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:73
|
#: intervention/forms/modalForms.py:72
|
||||||
msgid "Remove check to remove access for this user"
|
|
||||||
msgstr "Wählen Sie die Nutzer ab, die keinen Zugriff mehr haben sollen"
|
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:85
|
|
||||||
msgid "Share settings for {}"
|
msgid "Share settings for {}"
|
||||||
msgstr "Freigabe Einstellungen für {}"
|
msgstr "Freigabe Einstellungen für {}"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:149
|
#: intervention/forms/modalForms.py:105 intervention/forms/modalForms.py:111
|
||||||
|
msgid ""
|
||||||
|
"Only conservation or registration office users are allowed to remove entries."
|
||||||
|
msgstr ""
|
||||||
|
"Nur Mitarbeiter der Naturschutz- oder Zulassungsbehördengruppe dürfen Einträge entfernen"
|
||||||
|
|
||||||
|
#: intervention/forms/modalForms.py:155
|
||||||
msgid "Date of revocation"
|
msgid "Date of revocation"
|
||||||
msgstr "Datum des Widerspruchs"
|
msgstr "Datum des Widerspruchs"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:160
|
#: intervention/forms/modalForms.py:166
|
||||||
#: intervention/templates/intervention/detail/includes/revocation.html:35
|
#: intervention/templates/intervention/detail/includes/revocation.html:35
|
||||||
msgid "Document"
|
msgid "Document"
|
||||||
msgstr "Dokument"
|
msgstr "Dokument"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:163
|
#: intervention/forms/modalForms.py:169
|
||||||
msgid "Must be smaller than 15 Mb"
|
msgid "Must be smaller than 15 Mb"
|
||||||
msgstr "Muss kleiner als 15 Mb sein"
|
msgstr "Muss kleiner als 15 Mb sein"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:188
|
#: intervention/forms/modalForms.py:194
|
||||||
#: intervention/templates/intervention/detail/includes/revocation.html:18
|
#: intervention/templates/intervention/detail/includes/revocation.html:18
|
||||||
msgid "Add revocation"
|
msgid "Add revocation"
|
||||||
msgstr "Widerspruch hinzufügen"
|
msgstr "Widerspruch hinzufügen"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:245
|
#: intervention/forms/modalForms.py:251
|
||||||
msgid "Checked intervention data"
|
msgid "Checked intervention data"
|
||||||
msgstr "Eingriffsdaten geprüft"
|
msgstr "Eingriffsdaten geprüft"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:251
|
#: intervention/forms/modalForms.py:257
|
||||||
msgid "Checked compensations data and payments"
|
msgid "Checked compensations data and payments"
|
||||||
msgstr "Kompensationen und Zahlungen geprüft"
|
msgstr "Kompensationen und Zahlungen geprüft"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:260
|
#: intervention/forms/modalForms.py:266
|
||||||
#: intervention/templates/intervention/detail/includes/controls.html:19
|
#: intervention/templates/intervention/detail/includes/controls.html:19
|
||||||
msgid "Run check"
|
msgid "Run check"
|
||||||
msgstr "Prüfung vornehmen"
|
msgstr "Prüfung vornehmen"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:261 konova/forms.py:514
|
#: intervention/forms/modalForms.py:267 konova/forms.py:514
|
||||||
msgid ""
|
msgid ""
|
||||||
"I, {} {}, confirm that all necessary control steps have been performed by "
|
"I, {} {}, confirm that all necessary control steps have been performed by "
|
||||||
"myself."
|
"myself."
|
||||||
@ -1395,23 +1397,23 @@ msgstr ""
|
|||||||
"Ich, {} {}, bestätige, dass die notwendigen Kontrollschritte durchgeführt "
|
"Ich, {} {}, bestätige, dass die notwendigen Kontrollschritte durchgeführt "
|
||||||
"wurden:"
|
"wurden:"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:345
|
#: intervention/forms/modalForms.py:351
|
||||||
msgid "Only recorded accounts can be selected for deductions"
|
msgid "Only recorded accounts can be selected for deductions"
|
||||||
msgstr "Nur verzeichnete Ökokonten können für Abbuchungen verwendet werden."
|
msgstr "Nur verzeichnete Ökokonten können für Abbuchungen verwendet werden."
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:372
|
#: intervention/forms/modalForms.py:378
|
||||||
msgid "Only shared interventions can be selected"
|
msgid "Only shared interventions can be selected"
|
||||||
msgstr "Nur freigegebene Eingriffe können gewählt werden"
|
msgstr "Nur freigegebene Eingriffe können gewählt werden"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:385
|
#: intervention/forms/modalForms.py:391
|
||||||
msgid "New Deduction"
|
msgid "New Deduction"
|
||||||
msgstr "Neue Abbuchung"
|
msgstr "Neue Abbuchung"
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:386
|
#: intervention/forms/modalForms.py:392
|
||||||
msgid "Enter the information for a new deduction from a chosen eco-account"
|
msgid "Enter the information for a new deduction from a chosen eco-account"
|
||||||
msgstr "Geben Sie die Informationen für eine neue Abbuchung ein."
|
msgstr "Geben Sie die Informationen für eine neue Abbuchung ein."
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:429
|
#: intervention/forms/modalForms.py:435
|
||||||
msgid ""
|
msgid ""
|
||||||
"Eco-account {} is not recorded yet. You can only deduct from recorded "
|
"Eco-account {} is not recorded yet. You can only deduct from recorded "
|
||||||
"accounts."
|
"accounts."
|
||||||
@ -1419,7 +1421,7 @@ msgstr ""
|
|||||||
"Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von "
|
"Ökokonto {} ist noch nicht verzeichnet. Abbuchungen können nur von "
|
||||||
"verzeichneten Ökokonten erfolgen."
|
"verzeichneten Ökokonten erfolgen."
|
||||||
|
|
||||||
#: intervention/forms/modalForms.py:439
|
#: intervention/forms/modalForms.py:445
|
||||||
msgid ""
|
msgid ""
|
||||||
"The account {} has not enough surface for a deduction of {} m². There are "
|
"The account {} has not enough surface for a deduction of {} m². There are "
|
||||||
"only {} m² left"
|
"only {} m² left"
|
||||||
@ -4107,6 +4109,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 "Remove check to remove access for this user"
|
||||||
|
#~ msgstr "Wählen Sie die Nutzer ab, die keinen Zugriff mehr haben sollen"
|
||||||
|
|
||||||
#~ msgid "Select the action type"
|
#~ msgid "Select the action type"
|
||||||
#~ msgstr "Maßnahmentyp wählen"
|
#~ msgstr "Maßnahmentyp wählen"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user