From 57a7431686a17a786cc75a60a4774be483074b24 Mon Sep 17 00:00:00 2001
From: Lucas Brandstaetter <lucas@brandstaetter.tech>
Date: Mon, 9 Dec 2024 00:10:20 +0100
Subject: [PATCH] Add create and edit views for teams

---
 .../locale/de/LC_MESSAGES/django.po           | 37 +++++++-
 .../locale/en/LC_MESSAGES/django.po           | 37 +++++++-
 .../backoffice/teams/create_edit.html         | 60 +++++++++++++
 src/backoffice/tests/teams/__init__.py        | 12 ++-
 src/backoffice/tests/teams/teams.py           | 89 +++++++++++++++++++
 src/backoffice/urls.py                        |  2 +
 src/backoffice/views/teams/__init__.py        |  4 +
 src/backoffice/views/teams/teams.py           | 33 ++++++-
 src/hub/settings/test.py                      |  2 +-
 9 files changed, 267 insertions(+), 9 deletions(-)
 create mode 100644 src/backoffice/templates/backoffice/teams/create_edit.html

diff --git a/src/backoffice/locale/de/LC_MESSAGES/django.po b/src/backoffice/locale/de/LC_MESSAGES/django.po
index 9850b08b0..63fd80716 100644
--- a/src/backoffice/locale/de/LC_MESSAGES/django.po
+++ b/src/backoffice/locale/de/LC_MESSAGES/django.po
@@ -1447,13 +1447,38 @@ msgstr "Self-organized Session löschen"
 msgid "SoS__delete__introduction"
 msgstr "Hiermit wird diese Self-organized Session gelöscht. Wenn du sie nur unsichtbar machen möchtest gibt es weiter oben die Möglichkeit die Self-organized Session zurückzunehmen!"
 
-msgid "Team__name"
-msgstr "Name"
+# use translation from core
+msgid "Team"
+msgstr ""
+
+msgid "Team__create__title"
+msgstr "Neues Team erstellen"
+
+#, python-format
+msgid "Team__edit__title %(form.instance.name)s"
+msgstr "Team %(form.instance.name)s bearbeiten"
+
+msgid "team__edit__metadata"
+msgstr "Team-Daten"
 
 # Use translation from core
 msgid "Team__require_staff"
 msgstr ""
 
+# Use translation from core
+msgid "Team__require_staff__help"
+msgstr ""
+
+msgid "Team__change_permission_admin"
+msgstr "Du kannst die mit dem Team verknüpften Berechtigungen hier bearbeiten: "
+
+msgid "Team__edit__submit"
+msgstr "Speichern"
+
+# Use translation from core
+msgid "Team__name"
+msgstr ""
+
 msgid "Team__member_count"
 msgstr "Mitgliederanzahl"
 
@@ -1995,6 +2020,14 @@ msgstr "%(project_type)s '%(project)s' erstellt"
 msgid "Project__updated %(project)s %(project_type)s"
 msgstr "%(project_type)s '%(project)s' aktualisiert"
 
+#, python-format
+msgid "Team__create__success %(name)s"
+msgstr "Das Team \"%(name)s\" wurde angelegt!"
+
+#, python-format
+msgid "Team__update__success %(name)s"
+msgstr "Das Aktualisieren des Teams \"%(name)s\" war erfolgreich!"
+
 msgid "Lock-gone"
 msgstr "Sperre bestand nicht mehr."
 
diff --git a/src/backoffice/locale/en/LC_MESSAGES/django.po b/src/backoffice/locale/en/LC_MESSAGES/django.po
index 7ea574c3f..c9ad2cd56 100644
--- a/src/backoffice/locale/en/LC_MESSAGES/django.po
+++ b/src/backoffice/locale/en/LC_MESSAGES/django.po
@@ -1452,13 +1452,38 @@ msgstr "Delete self-organized session"
 msgid "SoS__delete__introduction"
 msgstr "This removes this self-organized session. If you only want to make it invisible, there is an option above to recall the self-organized session above!"
 
-msgid "Team__name"
-msgstr "name"
+# use translation from core
+msgid "Team"
+msgstr ""
+
+msgid "Team__create__title"
+msgstr "Create team"
+
+#, python-format
+msgid "Team__edit__title %(form.instance.name)s"
+msgstr "Edit the %(form.instance.name)s team"
+
+msgid "team__edit__metadata"
+msgstr "team data"
 
 # Use translation from core
 msgid "Team__require_staff"
 msgstr ""
 
+# Use translation from core
+msgid "Team__require_staff__help"
+msgstr ""
+
+msgid "Team__change_permission_admin"
+msgstr "You are allowed to change the teams permissions at: "
+
+msgid "Team__edit__submit"
+msgstr "Save"
+
+# Use translation from core
+msgid "Team__name"
+msgstr ""
+
 msgid "Team__member_count"
 msgstr "members count"
 
@@ -2000,6 +2025,14 @@ msgstr "%(project_type)s '%(project)s' created"
 msgid "Project__updated %(project)s %(project_type)s"
 msgstr "%(project_type)s '%(project)s' updated"
 
+#, python-format
+msgid "Team__create__success %(name)s"
+msgstr "The team \"%(name)s\" was successfully created."
+
+#, python-format
+msgid "Team__update__success %(name)s"
+msgstr "The team \"%(name)s\" was successfully updated."
+
 msgid "Lock-gone"
 msgstr "Lock was gone already."
 
diff --git a/src/backoffice/templates/backoffice/teams/create_edit.html b/src/backoffice/templates/backoffice/teams/create_edit.html
new file mode 100644
index 000000000..7d3696ca2
--- /dev/null
+++ b/src/backoffice/templates/backoffice/teams/create_edit.html
@@ -0,0 +1,60 @@
+{% extends "backoffice/base.html" %}
+{% load django_bootstrap5 %}
+{% load i18n %}
+{% load static %}
+{% load rules %}
+
+{% block title %}
+  {% if form.create %}
+    {% trans "create" %}
+  {% else %}
+    {{ form.instance.name }}
+  {% endif %}
+  | {% trans "Team" %} | {{ conference.name }}
+{% endblock title %}
+{% block content %}
+  {% has_perm 'core.change_permissions_team' request.user team as can_change_permissions %}
+  <form method="post" enctype="multipart/form-data" id="teamForm">
+    {% csrf_token %}
+    <div class="card border-default">
+      <div class="card-header bg-default">
+        {% if form.create %}
+          {% trans "Team__create__title" %}
+        {% else %}
+          {% blocktranslate %}Team__edit__title {{ form.instance.name }}{% endblocktranslate %}
+        {% endif %}
+      </div>
+      <div class="card-body">
+        <p class="fw-bold border-bottom mb-3">{% trans "team__edit__metadata" %}</p>
+        <div class="row mb-3">
+          <div class="col-md-9">{% bootstrap_field form.name %}</div>
+          {% if form.require_staff %}
+            <div class="col-md-3">{% bootstrap_field form.require_staff %}</div>
+          {% else %}
+            <div class="col-md-3">
+              <label for="require_staff">{% trans "Team__require_staff" %}:</label>
+              <span id="require_staff">{{ form.require_staff|yesno }}</span>
+              <div class="form-text">{% trans "Team__require_staff__help" %}</div>
+            </div>
+          {% endif %}
+        </div>
+
+        <div class="row mb-3">
+          <div class="col-md-6">{% bootstrap_field form.description_de %}</div>
+          <div class="col-md-6">{% bootstrap_field form.description_en %}</div>
+        </div>
+        {% if not form.create and can_change_permissions %}
+          <div class="row">
+            <p class="col-md-12">
+              {% trans "Team__change_permission_admin" %} <a href="{% url "admin:core_team_change" team.id %}">test</a>
+            </p>
+          </div>
+        {% endif %}
+      </div>
+      {% trans "Team__edit__submit" as button_text %}
+      <div class="card-footer">
+        {% bootstrap_button button_text button_type="submit" button_class="btn-primary float-end" %}
+      </div>
+    </div>
+  </form>
+{% endblock content %}
diff --git a/src/backoffice/tests/teams/__init__.py b/src/backoffice/tests/teams/__init__.py
index db9ed6730..c1553ae61 100644
--- a/src/backoffice/tests/teams/__init__.py
+++ b/src/backoffice/tests/teams/__init__.py
@@ -1,3 +1,11 @@
-from backoffice.tests.teams.teams import TeamListViewTestCase
+from backoffice.tests.teams.teams import (
+    TeamCreateViewTestCase,
+    TeamListViewTestCase,
+    TeamUpdateViewTestCase,
+)
 
-__all__ = ('TeamListViewTestCase',)
+__all__ = (
+    'TeamCreateViewTestCase',
+    'TeamListViewTestCase',
+    'TeamUpdateViewTestCase',
+)
diff --git a/src/backoffice/tests/teams/teams.py b/src/backoffice/tests/teams/teams.py
index ecdfd789e..844c12a40 100644
--- a/src/backoffice/tests/teams/teams.py
+++ b/src/backoffice/tests/teams/teams.py
@@ -64,3 +64,92 @@ class TeamListViewTestCase(BackOfficeTestCase):
         for team in self.teams:
             self.assertContains(response, team)
         self.assertNotContains(response, _('Team__create__button'))
+
+
+class TeamCreateViewTestCase(BackOfficeTestCase):
+    def test_team_create_unauthenicated(self):
+        activate('en')
+        response = self.client.get(reverse('backoffice:team-create'))
+        self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:team-create'))
+
+    def test_team_create_admin(self):
+        self.client.force_login(self.admin)
+        response = self.client.get(reverse('backoffice:team-create'))
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'backoffice/teams/create_edit.html')
+        self.assertContains(response, _('create'))
+        self.assertContains(response, _('Team__create__title'))
+
+        response = self.client.post(reverse('backoffice:team-create'), data={'name': 'team'})
+        self.assertTrue(Team.objects.filter(name='team').exists())
+        team = Team.objects.get(name='team')
+        self.assertRedirects(response, reverse('backoffice:team', kwargs={'uuid': team.uuid}))
+
+    def test_team_create_staff(self):
+        self.client.force_login(self.staff)
+        response = self.client.get(reverse('backoffice:team-create'))
+        self.assertEqual(response.status_code, 403)
+
+    def test_team_create_team_member(self):
+        self.client.force_login(self.user)
+        response = self.client.get(reverse('backoffice:team-create'))
+        self.assertEqual(response.status_code, 403)
+
+
+class TeamUpdateViewTestCase(BackOfficeTestCase):
+    def setUp(self):
+        super().setUp()
+        self.user_2 = PlatformUser.objects.create_user(
+            username='user2',
+        )
+        self.user_2_cm = ConferenceMember.objects.create(conference=self.conf, user=self.user_2)
+        self.team = Team.objects.create(
+            name='team',
+            conference=self.conf,
+        )
+        self.team_member = TeamMember.objects.create(team=self.team, user=self.admin)
+        self.team_member2 = TeamMember.objects.create(team=self.team, user=self.staff, can_manage=True)
+        self.team_member3 = TeamMember.objects.create(team=self.team, user=self.user)
+
+    def test_team_update_unauthenicated(self):
+        activate('en')
+        response = self.client.get(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
+        self.assertRedirects(response, reverse('backoffice:login') + '?next=' + reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
+
+    def test_team_edit_admin(self):
+        self.client.force_login(self.admin)
+        response = self.client.get(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'backoffice/teams/create_edit.html')
+        self.assertNotContains(response, _('create'))
+        # TODO: Add test for title
+        self.assertContains(response, _('Team__edit__submit'))
+
+        response = self.client.post(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}), data={'name': 'team'})
+        self.assertTrue(Team.objects.filter(name='team').exists())
+        team = Team.objects.get(name='team')
+        self.assertRedirects(response, reverse('backoffice:team', kwargs={'uuid': team.uuid}))
+
+    def test_team_edit_staff(self):
+        self.client.force_login(self.staff)
+        response = self.client.get(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'backoffice/teams/create_edit.html')
+        self.assertNotContains(response, _('create'))
+        # TODO: Add test for title
+        self.assertContains(response, _('Team__edit__submit'))
+
+        response = self.client.post(
+            reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}),
+            data={'name': 'team', 'description_de': 'description_de', 'description_en': 'description_en'},
+        )
+        self.assertTrue(Team.objects.filter(name='team').exists())
+        self.assertRedirects(response, reverse('backoffice:team', kwargs={'uuid': self.team.uuid}))
+        self.team.refresh_from_db()
+        self.assertEqual(self.team.description_de, 'description_de')
+        self.assertEqual(self.team.description_en, 'description_en')
+
+    def test_team_edit_team_member(self):
+        self.client.force_login(self.user)
+        response = self.client.get(reverse('backoffice:team-edit', kwargs={'uuid': self.team.uuid}))
+        self.assertEqual(response.status_code, 403)
diff --git a/src/backoffice/urls.py b/src/backoffice/urls.py
index cf888bac6..dd93c1201 100644
--- a/src/backoffice/urls.py
+++ b/src/backoffice/urls.py
@@ -154,6 +154,8 @@ urlpatterns = [
     path('sos/new', RedirectView.as_view(pattern_name='backoffice:sos-create')),
     path('sos/<uuid:pk>/', RedirectView.as_view(pattern_name='backoffice:sos-edit')),
     path('teams', teams.TeamListView.as_view(), name='teams'),
+    path('team/create', teams.TeamCreateView.as_view(), name='team-create'),
+    path('team/<uuid:uuid>/edit', teams.TeamUpdateView.as_view(), name='team-edit'),
     path('vouchers', vouchers.VouchersView.as_view(), name='vouchers'),
     path('_boom', misc.BoomView.as_view()),
 ]
diff --git a/src/backoffice/views/teams/__init__.py b/src/backoffice/views/teams/__init__.py
index a5dc575b1..2f56a60e9 100644
--- a/src/backoffice/views/teams/__init__.py
+++ b/src/backoffice/views/teams/__init__.py
@@ -1,7 +1,11 @@
 from backoffice.views.teams.teams import (
+    TeamCreateView,
     TeamListView,
+    TeamUpdateView,
 )
 
 __all__ = [
+    'TeamCreateView',
     'TeamListView',
+    'TeamUpdateView',
 ]
diff --git a/src/backoffice/views/teams/teams.py b/src/backoffice/views/teams/teams.py
index ffcfe4ad2..4c2a0eee2 100644
--- a/src/backoffice/views/teams/teams.py
+++ b/src/backoffice/views/teams/teams.py
@@ -5,11 +5,15 @@ from django.db.models import BooleanField, Case, Count, When
 from django.db.models.query import Q, QuerySet
 from django.urls import reverse
 from django.utils.translation import gettext as _
-from django.views.generic import ListView
+from django.views.generic import FormView, ListView
+from django.views.generic.edit import CreateView, UpdateView
+from rules.contrib.views import AutoPermissionRequiredMixin
 
+from core.forms import TeamForm
 from core.models import ConferenceMember, Team
+from core.views.mixins import FormMesssageMixin
 
-from backoffice.views.mixins import ConferenceRuleLoginRequiredMixin, guess_active_sidebar_item
+from backoffice.views.mixins import ConferenceRuleLoginRequiredMixin, SingleUUIDObjectMixin, guess_active_sidebar_item
 
 
 class TeamNavContextMixin(ConferenceRuleLoginRequiredMixin):
@@ -85,3 +89,28 @@ class TeamListView(TeamNavContextMixin, ListView):
         ).annotate(
             can_manage=Case(When(self_manage_count__gt=0, then=True), default=False, output_field=BooleanField()),
         )
+
+
+class TeamFormMixin(AutoPermissionRequiredMixin, TeamNavContextMixin, FormMesssageMixin, FormView):
+    model = Team
+    object_name = 'team'
+    form_class = TeamForm
+    template_name = 'backoffice/teams/create_edit.html'
+
+    def get_success_url(self):
+        return reverse('backoffice:team', kwargs={'uuid': self.object.uuid})
+
+    def get_form_kwargs(self) -> dict[str, Any]:
+        return {
+            **super().get_form_kwargs(),
+            'super_user': self.request.user.is_superuser,
+            'conference': self.conference,
+        }
+
+
+class TeamCreateView(TeamFormMixin, CreateView):
+    success_message = _('Team__create__success %(name)s')
+
+
+class TeamUpdateView(SingleUUIDObjectMixin, TeamFormMixin, UpdateView):
+    success_message = _('Team__update__success %(name)s')
diff --git a/src/hub/settings/test.py b/src/hub/settings/test.py
index f2333e628..8fde833fe 100644
--- a/src/hub/settings/test.py
+++ b/src/hub/settings/test.py
@@ -1,7 +1,7 @@
 import os
 
 os.environ.setdefault('DJANGO_SECRET_KEY', 'Testing101')
-
+os.environ.setdefault('SERVE_ADMIN', 'yes')
 from .default import *  # noqa: F401, E402, F403 # pylint: disable=wildcard-import,unused-wildcard-import
 
 INTEGRATIONS_BBB = True
-- 
GitLab