110
user/forms.py
110
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
|
||||
|
||||
@@ -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
|
||||
|
||||
16
user/models/team.py
Normal file
16
user/models/team.py
Normal file
@@ -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
|
||||
@@ -62,6 +62,14 @@
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<a href="{% url 'user:team-index' %}" title="{% trans 'Manage teams' %}">
|
||||
<button class="btn btn-default">
|
||||
{% fa5_icon 'users' %}
|
||||
<span>{% trans 'Teams' %}</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
67
user/templates/user/team/index.html
Normal file
67
user/templates/user/team/index.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n fontawesome_5 %}
|
||||
|
||||
{% block head %}
|
||||
|
||||
{% comment %}
|
||||
dal documentation (django-autocomplete-light) states using form.media for adding needed scripts.
|
||||
This does not work properly with modal forms, as the scripts are not loaded properly inside the modal.
|
||||
Therefore the script linkages from form.media have been extracted and put inside dal/scripts.html to ensure
|
||||
these scripts are loaded when needed.
|
||||
{% endcomment %}
|
||||
{% include 'dal/scripts.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h4>{% trans 'Teams' %}</h4>
|
||||
<div class="col-md">
|
||||
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-new' %}" title="{% trans 'Add new team' %}">
|
||||
{% fa5_icon 'plus' %}
|
||||
{% trans 'New' %}
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<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">{% trans 'Members' %}</th>
|
||||
<th scope="col" class="align-middle">{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for team in teams %}
|
||||
<tr>
|
||||
<td>{{team.name}}</td>
|
||||
<td>
|
||||
<div class="scroll-150">
|
||||
{{team.description}}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% for member in team.users.all %}
|
||||
<span class="badge badge-pill rlp-r">{{member.username}}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
{% if team.admin == user %}
|
||||
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-edit' team.id %}" title="{% trans 'Edit team' %}">
|
||||
{% fa5_icon 'edit' %}
|
||||
</button>
|
||||
<button class="btn rlp-r btn-modal" data-form-url="{% url 'user:team-remove' team.id %}" title="{% trans 'Remove team' %}">
|
||||
{% fa5_icon 'trash' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% with 'btn-modal' as btn_class %}
|
||||
{% include 'modal/modal_form_script.html' %}
|
||||
{% endwith %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -15,5 +15,9 @@ urlpatterns = [
|
||||
path("notifications/", notifications_view, name="notifications"),
|
||||
path("token/api", api_token_view, name="api-token"),
|
||||
path("contact/<id>", contact_view, name="contact"),
|
||||
path("team/", index_team_view, name="team-index"),
|
||||
path("team/new", new_team_view, name="team-new"),
|
||||
path("team/<id>/edit", edit_team_view, name="team-edit"),
|
||||
path("team/<id>/remove", remove_team_view, name="team-remove"),
|
||||
|
||||
]
|
||||
@@ -1,17 +1,19 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import reverse
|
||||
|
||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||
from konova.utils.mailer import Mailer
|
||||
from konova.utils.message_templates import FORM_INVALID
|
||||
from user.models import User
|
||||
from django.http import HttpRequest
|
||||
from user.models import User, Team
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from konova.contexts import BaseContext
|
||||
from konova.decorators import any_group_check, default_group_required
|
||||
from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm
|
||||
from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm, NewTeamModalForm, EditTeamModalForm, \
|
||||
RemoveTeamModalForm
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -128,4 +130,52 @@ def contact_view(request: HttpRequest, id: str):
|
||||
request,
|
||||
template,
|
||||
context
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def index_team_view(request: HttpRequest):
|
||||
template = "user/team/index.html"
|
||||
user = request.user
|
||||
context = {
|
||||
"teams": user.teams.all(),
|
||||
"tab_title": _("Teams"),
|
||||
}
|
||||
context = BaseContext(request, context).context
|
||||
return render(request, template, context)
|
||||
|
||||
|
||||
@login_required
|
||||
def new_team_view(request: HttpRequest):
|
||||
form = NewTeamModalForm(request.POST or None, request=request)
|
||||
return form.process_request(
|
||||
request,
|
||||
_("New team added"),
|
||||
redirect_url=reverse("user:team-index")
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_team_view(request: HttpRequest, id: str):
|
||||
team = get_object_or_404(Team, id=id)
|
||||
if request.user != team.admin:
|
||||
raise Http404()
|
||||
form = EditTeamModalForm(request.POST or None, instance=team, request=request)
|
||||
return form.process_request(
|
||||
request,
|
||||
_("Team edited"),
|
||||
redirect_url=reverse("user:team-index")
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def remove_team_view(request: HttpRequest, id: str):
|
||||
team = get_object_or_404(Team, id=id)
|
||||
if request.user != team.admin:
|
||||
raise Http404()
|
||||
form = RemoveTeamModalForm(request.POST or None, instance=team, request=request)
|
||||
return form.process_request(
|
||||
request,
|
||||
_("Team removed"),
|
||||
redirect_url=reverse("user:team-index")
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user