diff --git a/compensation/templates/compensation/detail/compensation/view.html b/compensation/templates/compensation/detail/compensation/view.html
index be8dd3b9..4a1177a5 100644
--- a/compensation/templates/compensation/detail/compensation/view.html
+++ b/compensation/templates/compensation/detail/compensation/view.html
@@ -109,7 +109,7 @@
{% trans 'Shared with' %} |
- {% for team in obj.teams.all %}
+ {% for team in obj.shared_teams %}
{% include 'user/includes/team_data_modal_button.html' %}
{% endfor %}
diff --git a/konova/admin.py b/konova/admin.py
index 213120ea..b30f4b14 100644
--- a/konova/admin.py
+++ b/konova/admin.py
@@ -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):
fields = [
"created",
@@ -109,7 +121,7 @@ class BaseResourceAdmin(admin.ModelAdmin):
]
-class BaseObjectAdmin(BaseResourceAdmin):
+class BaseObjectAdmin(BaseResourceAdmin, DeletableObjectMixinAdmin):
search_fields = [
"identifier",
"title",
@@ -126,13 +138,6 @@ class BaseObjectAdmin(BaseResourceAdmin):
"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
diff --git a/konova/autocompletes.py b/konova/autocompletes.py
index 288ee02d..fbd92f75 100644
--- a/konova/autocompletes.py
+++ b/konova/autocompletes.py
@@ -96,7 +96,9 @@ class ShareTeamAutocomplete(Select2QuerySetView):
def get_queryset(self):
if self.request.user.is_anonymous:
return Team.objects.none()
- qs = Team.objects.all()
+ qs = Team.objects.filter(
+ deleted__isnull=True
+ )
if self.q:
# Due to privacy concerns only a full username match will return the proper user entry
qs = qs.filter(
diff --git a/konova/models/object.py b/konova/models/object.py
index 325762f4..b468932a 100644
--- a/konova/models/object.py
+++ b/konova/models/object.py
@@ -87,25 +87,15 @@ class BaseResource(UuidModel):
super().delete()
-class BaseObject(BaseResource):
- """
- A basic object model, which specifies BaseResource.
+class DeletableObjectMixin(models.Model):
+ """ Wraps deleted field and related functionality
- 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='+')
- 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_deleted(self, user, send_mail: bool = True):
""" Mark an entry as deleted
@@ -140,6 +130,25 @@ class BaseObject(BaseResource):
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):
""" 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:
"""
- directly_shared = self.users.filter(id=user.id).exists()
- team_shared = self.teams.filter(
+ directly_shared = self.shared_users.filter(id=user.id).exists()
+ team_shared = self.shared_teams.filter(
users__in=[user]
).exists()
is_shared = directly_shared or team_shared
@@ -622,7 +631,9 @@ class ShareableObjectMixin(models.Model):
Returns:
teams (QuerySet)
"""
- return self.teams.all()
+ return self.teams.filter(
+ deleted__isnull=True
+ )
@abstractmethod
def get_share_url(self):
diff --git a/user/admin.py b/user/admin.py
index d5640665..bf5f5f86 100644
--- a/user/admin.py
+++ b/user/admin.py
@@ -1,6 +1,7 @@
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):
@@ -64,10 +65,11 @@ class UserActionLogEntryAdmin(admin.ModelAdmin):
]
-class TeamAdmin(admin.ModelAdmin):
+class TeamAdmin(DeletableObjectMixinAdmin, admin.ModelAdmin):
list_display = [
"name",
"description",
+ "deleted",
]
search_fields = [
"name",
@@ -78,6 +80,13 @@ class TeamAdmin(admin.ModelAdmin):
"admins",
]
+ readonly_fields = [
+ "deleted"
+ ]
+
+ actions = [
+ "restore_deleted_data"
+ ]
admin.site.register(User, UserAdmin)
admin.site.register(Team, TeamAdmin)
diff --git a/user/forms.py b/user/forms.py
index f6831ff6..cfb6d723 100644
--- a/user/forms.py
+++ b/user/forms.py
@@ -230,8 +230,8 @@ class NewTeamModalForm(BaseModalForm):
team = Team.objects.create(
name=self.cleaned_data.get("name", None),
description=self.cleaned_data.get("description", None),
- admins__in=[self.user],
)
+ team.admins.add(self.user)
members = self.cleaned_data.get("members", User.objects.none())
if self.user.id not in members:
members = members.union(
@@ -335,6 +335,10 @@ class RemoveTeamModalForm(RemoveModalForm):
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):
def __init__(self, *args, **kwargs):
diff --git a/user/migrations/0005_team_deleted.py b/user/migrations/0005_team_deleted.py
new file mode 100644
index 00000000..b3a5c68d
--- /dev/null
+++ b/user/migrations/0005_team_deleted.py
@@ -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'),
+ ),
+ ]
diff --git a/user/models/team.py b/user/models/team.py
index 7908115f..5e728e71 100644
--- a/user/models/team.py
+++ b/user/models/team.py
@@ -1,10 +1,11 @@
from django.db import models
-from konova.models import UuidModel
+from konova.models import UuidModel, DeletableObjectMixin
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
"""
@@ -16,6 +17,19 @@ class Team(UuidModel):
def __str__(self):
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):
""" Sends a mail to the team members in case of given shared access
diff --git a/user/models/user.py b/user/models/user.py
index df63dd76..b40a3b1b 100644
--- a/user/models/user.py
+++ b/user/models/user.py
@@ -160,3 +160,15 @@ class User(AbstractUser):
else:
token = self.api_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
\ No newline at end of file
diff --git a/user/views.py b/user/views.py
index b6e7f09a..2c5b86a1 100644
--- a/user/views.py
+++ b/user/views.py
@@ -163,7 +163,7 @@ def index_team_view(request: HttpRequest):
template = "user/team/index.html"
user = request.user
context = {
- "teams": user.teams.all(),
+ "teams": user.shared_teams,
"tab_title": _("Teams"),
}
context = BaseContext(request, context).context
|