diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index a8fa98de..0b72ae84 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -7,11 +7,11 @@ Created on: 27.09.21 """ from dal import autocomplete from django.core.exceptions import ObjectDoesNotExist -from django.db.models.fields.files import FieldFile from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \ - REVOCATION_EDITED, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE -from user.models import User, UserActionLogEntry + REVOCATION_EDITED +from user.models import User +from user.models import UserActionLogEntry from django.db import transaction from django import forms from django.utils.translation import gettext_lazy as _ diff --git a/konova/models/object.py b/konova/models/object.py index a6164f5a..69a0a2e7 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -16,7 +16,6 @@ from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP, LANIS_ZOOM_LUT, from konova.tasks import celery_send_mail_shared_access_removed, celery_send_mail_shared_access_given, \ celery_send_mail_shared_data_recorded, celery_send_mail_shared_data_unrecorded, \ celery_send_mail_shared_data_deleted, celery_send_mail_shared_data_checked -from user.models import User from django.core.exceptions import ObjectDoesNotExist from django.http import HttpRequest from django.utils.timezone import now @@ -28,7 +27,6 @@ from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_I from konova.utils import generators from konova.utils.generators import generate_random_string from konova.utils.message_templates import CHECKED_RECORDED_RESET, GEOMETRY_CONFLICT_WITH_TEMPLATE -from user.models import UserActionLogEntry, UserAction class UuidModel(models.Model): @@ -50,14 +48,14 @@ class BaseResource(UuidModel): A basic resource model, which defines attributes for every derived model """ created = models.ForeignKey( - UserActionLogEntry, + "user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, related_name='+' ) modified = models.ForeignKey( - UserActionLogEntry, + "user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, @@ -94,9 +92,9 @@ class BaseObject(BaseResource): """ identifier = models.CharField(max_length=1000, null=True, blank=True) title = models.CharField(max_length=1000, null=True, blank=True) - deleted = models.ForeignKey(UserActionLogEntry, on_delete=models.SET_NULL, null=True, blank=True, related_name='+') + deleted = models.ForeignKey("user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, related_name='+') comment = models.TextField(null=True, blank=True) - log = models.ManyToManyField(UserActionLogEntry, blank=True, help_text="Keeps all user actions of an object", editable=False) + log = models.ManyToManyField("user.UserActionLogEntry", blank=True, help_text="Keeps all user actions of an object", editable=False) class Meta: abstract = True @@ -105,7 +103,7 @@ class BaseObject(BaseResource): def set_status_messages(self, request: HttpRequest): raise NotImplementedError - def mark_as_deleted(self, user: User, send_mail: bool = True): + def mark_as_deleted(self, user, send_mail: bool = True): """ Mark an entry as deleted Does not delete from database but sets a timestamp for being deleted on and which user deleted the object @@ -116,6 +114,7 @@ class BaseObject(BaseResource): Returns: """ + from user.models import UserActionLogEntry if self.deleted: # Nothing to do here return @@ -133,7 +132,7 @@ class BaseObject(BaseResource): self.save() - def mark_as_edited(self, performing_user: User, request: HttpRequest = None, edit_comment: str = None): + def mark_as_edited(self, performing_user, request: HttpRequest = None, edit_comment: str = None): """ In case the object or a related object changed the log history needs to be updated Args: @@ -144,13 +143,14 @@ class BaseObject(BaseResource): Returns: """ + from user.models import UserActionLogEntry edit_action = UserActionLogEntry.get_edited_action(performing_user, edit_comment) self.modified = edit_action self.log.add(edit_action) self.save() return edit_action - def add_log_entry(self, action: UserAction, user: User, comment: str): + def add_log_entry(self, action, user, comment: str): """ Wraps adding of UserActionLogEntry to log Args: @@ -161,6 +161,7 @@ class BaseObject(BaseResource): Returns: """ + from user.models import UserActionLogEntry user_action = UserActionLogEntry.objects.create( user=user, action=action, @@ -229,7 +230,7 @@ class RecordableObjectMixin(models.Model): """ # Refers to "verzeichnen" recorded = models.OneToOneField( - UserActionLogEntry, + "user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, @@ -240,7 +241,7 @@ class RecordableObjectMixin(models.Model): class Meta: abstract = True - def set_unrecorded(self, user: User): + def set_unrecorded(self, user): """ Perform unrecording Args: @@ -249,6 +250,7 @@ class RecordableObjectMixin(models.Model): Returns: """ + from user.models import UserActionLogEntry if not self.recorded: return None action = UserActionLogEntry.get_unrecorded_action(user) @@ -262,7 +264,7 @@ class RecordableObjectMixin(models.Model): return action - def set_recorded(self, user: User): + def set_recorded(self, user): """ Perform recording Args: @@ -271,6 +273,7 @@ class RecordableObjectMixin(models.Model): Returns: """ + from user.models import UserActionLogEntry if self.recorded: return None action = UserActionLogEntry.get_recorded_action(user) @@ -284,7 +287,7 @@ class RecordableObjectMixin(models.Model): return action - def unrecord(self, performing_user: User, request: HttpRequest = None): + def unrecord(self, performing_user, request: HttpRequest = None): """ Unrecords a dataset Args: @@ -318,7 +321,7 @@ class RecordableObjectMixin(models.Model): class CheckableObjectMixin(models.Model): # Checks - Refers to "Genehmigen" but optional checked = models.OneToOneField( - UserActionLogEntry, + "user.UserActionLogEntry", on_delete=models.SET_NULL, null=True, blank=True, @@ -346,7 +349,7 @@ class CheckableObjectMixin(models.Model): self.save() return None - def set_checked(self, user: User) -> UserActionLogEntry: + def set_checked(self, user): """ Perform checking Args: @@ -355,6 +358,7 @@ class CheckableObjectMixin(models.Model): Returns: """ + from user.models import UserActionLogEntry if self.checked: # Nothing to do return @@ -373,7 +377,7 @@ class CheckableObjectMixin(models.Model): class ShareableObjectMixin(models.Model): # Users having access on this object - users = models.ManyToManyField(User, help_text="Users having access (data shared with)") + users = models.ManyToManyField("user.User", help_text="Users having access (data shared with)") access_token = models.CharField( max_length=255, null=True, @@ -420,7 +424,7 @@ class ShareableObjectMixin(models.Model): self.access_token = token self.save() - def is_shared_with(self, user: User): + def is_shared_with(self, user): """ Access check Checks whether a given user has access to this object @@ -433,7 +437,7 @@ class ShareableObjectMixin(models.Model): """ return self.users.filter(id=user.id) - def share_with(self, user: User): + def share_with(self, user): """ Adds user to list of shared access users Args: @@ -465,6 +469,7 @@ class ShareableObjectMixin(models.Model): Returns: """ + from user.models import User form_data = form.cleaned_data keep_accessing_users = form_data["users"] diff --git a/user/forms.py b/user/forms.py index fd66a916..1b509bad 100644 --- a/user/forms.py +++ b/user/forms.py @@ -5,17 +5,17 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 08.07.21 """ +from dal import autocomplete from django import forms -from django.db import IntegrityError +from django.db import IntegrityError, transaction from django.urls import reverse, reverse_lazy from django.utils.translation import gettext_lazy as _ from api.models import APIUserToken from intervention.inputs import GenerateInput -from user.models import User +from user.models import User, UserNotification, Team -from konova.forms import BaseForm, BaseModalForm -from user.models import UserNotification +from konova.forms import BaseForm, BaseModalForm, RemoveModalForm class UserNotificationForm(BaseForm): @@ -160,3 +160,105 @@ class UserAPITokenForm(BaseForm): user.api_token = new_token user.save() return new_token + + +class NewTeamModalForm(BaseModalForm): + name = forms.CharField( + label_suffix="", + label=_("Team name"), + max_length=500, + widget=forms.TextInput( + attrs={ + "placeholder": _("Team name"), + "class": "form-control", + } + ) + ) + description = forms.CharField( + label_suffix="", + label=_("Description"), + widget=forms.Textarea( + attrs={ + "rows": 5, + "class": "form-control" + } + ) + ) + members = forms.ModelMultipleChoiceField( + label=_("Manage team members"), + label_suffix="", + help_text=_("Multiple selection possible - You can only select users which are not already a team member. Enter the full username or e-mail."), + required=True, + queryset=User.objects.all(), + widget=autocomplete.ModelSelect2Multiple( + url="share-user-autocomplete", + attrs={ + "data-placeholder": _("Click for selection"), + "data-minimum-input-length": 3, + }, + ), + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.form_title = _("Create new team") + self.form_caption = _("You will become the administrator for this group by default. You do not need to add yourself to the list of members.") + self.action_url = reverse("user:team-new") + self.cancel_redirect = reverse("user:team-index") + + def save(self): + with transaction.atomic(): + team = Team.objects.create( + name=self.cleaned_data.get("name", None), + description=self.cleaned_data.get("description", None), + admin=self.user, + ) + members = self.cleaned_data.get("members", User.objects.none()) + if self.user.id not in members: + members = members.union( + User.objects.filter( + id=self.user.id + ) + ) + team.users.set(members) + return team + + +class EditTeamModalForm(NewTeamModalForm): + admin = forms.ModelChoiceField( + label_suffix="", + label=_("Admin"), + help_text=_("Administrators manage team details and members"), + queryset=User.objects.none(), + empty_label=None, + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.form_title = _("Edit team") + self.form_caption = None + self.action_url = reverse("user:team-edit", args=(self.instance.id,)) + self.cancel_redirect = reverse("user:team-index") + + members = self.instance.users.all() + self.fields["admin"].queryset = members + + form_data = { + "members": members, + "name": self.instance.name, + "description": self.instance.description, + "admin": self.instance.admin, + } + self.load_initial_data(form_data) + + def save(self): + with transaction.atomic(): + self.instance.name = self.cleaned_data.get("name", None) + self.instance.description = self.cleaned_data.get("description", None) + self.instance.save() + self.instance.users.set(self.cleaned_data.get("members", [])) + return self.instance + + +class RemoveTeamModalForm(RemoveModalForm): + pass diff --git a/user/models/__init__.py b/user/models/__init__.py index 7788d8e4..06dbe738 100644 --- a/user/models/__init__.py +++ b/user/models/__init__.py @@ -5,6 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 15.11.21 """ -from .user_action import * -from .user import * -from .notification import * +from .user_action import UserActionLogEntry, UserAction +from .user import User +from .notification import UserNotification, UserNotificationEnum +from .team import Team diff --git a/user/models/team.py b/user/models/team.py new file mode 100644 index 00000000..c26af3c6 --- /dev/null +++ b/user/models/team.py @@ -0,0 +1,16 @@ +from django.db import models + +from konova.models import UuidModel + + +class Team(UuidModel): + """ Groups users in self managed teams. Can be used for multi-sharing of data + + """ + name = models.CharField(max_length=500, null=True, blank=True) + description = models.TextField(null=True, blank=True) + users = models.ManyToManyField("user.User", blank=True, related_name="teams") + admin = models.ForeignKey("user.User", blank=True, null=True, related_name="+", on_delete=models.SET_NULL) + + def __str__(self): + return self.name diff --git a/user/templates/user/index.html b/user/templates/user/index.html index 1193340c..c31de94f 100644 --- a/user/templates/user/index.html +++ b/user/templates/user/index.html @@ -62,6 +62,14 @@ +
{% trans 'Name' %} | +{% trans 'Description' %} | +{% trans 'Members' %} | +{% trans 'Actions' %} | +
---|---|---|---|
{{team.name}} | +
+
+ {{team.description}}
+
+ |
+ + {% for member in team.users.all %} + {{member.username}} + {% endfor %} + | ++ {% if team.admin == user %} + + + {% endif %} + | +