Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/phonebook/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ def save(self, *args, **kwargs) -> None:
self.check_unique_ipei()
super().save(*args, **kwargs)

def __str__(self) -> str:
return f"{self.number or self.letters} ({self.user.profile.get_name})"

def check_unique_ipei(self) -> None:
"""Check IPEI is unique."""
if (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 5.2.9 on 2026-01-23 11:48

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("phonebook", "0005_alter_dectregistration_ipei"),
("teams", "0064_alter_team_facilitator_group_and_more"),
]

operations = [
migrations.AddField(
model_name="team",
name="public_dect_number",
field=models.ForeignKey(
blank=True,
help_text="The public DECT for this team.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="public_team_numbers",
to="phonebook.dectregistration",
),
),
migrations.AddField(
model_name="team",
name="public_phone_number",
field=models.CharField(
blank=True,
help_text="The public phonenumber for this team.",
max_length=14,
null=True,
),
),
]
22 changes: 19 additions & 3 deletions src/teams/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
from typing import TYPE_CHECKING

from django.conf import settings
from django.core.validators import MaxValueValidator
from django.core.validators import MinValueValidator
from django.contrib.auth.models import Group
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import DateTimeRangeField
from django.db import models
from django.urls import reverse_lazy
from django_prometheus.models import ExportModelOperationsMixin

from camps.models import Permission as CampPermission
from phonebook.models import DectRegistration
from utils.models import CampRelatedModel
from utils.models import CreatedUpdatedModel
from utils.models import UUIDModel
Expand Down Expand Up @@ -211,6 +211,22 @@ class Team(ExportModelOperationsMixin("team"), CampRelatedModel):
public_signal_channel_link = models.URLField(null=True, blank=True, default="")
private_signal_channel_link = models.URLField(null=True, blank=True, default="")

public_phone_number = models.CharField(
max_length=14, # Allow for "+00 1234567890"
blank=True,
null=True,
help_text="The public phonenumber for this team.",
)

public_dect_number = models.ForeignKey(
DectRegistration,
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="public_team_numbers",
help_text="The public DECT for this team.",
)

shifts_enabled = models.BooleanField(
default=False,
help_text="Does this team have shifts? This enables defining shifts for this team.",
Expand Down
48 changes: 26 additions & 22 deletions src/teams/templates/team_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ <h1>{{ team.name }} Team</h1>
</a>
</li>

{% if is_team_infopager %}
<li class="nav-item">
<a class="nav-link{% if view.active_menu == "info_categories" %} active{% endif %}" href="{% url "teams:info_categories" camp_slug=team.camp.slug team_slug=team.slug %}">
Info categories
</a>
</li>
{% endif %}

<li class="nav-item">
<a class="nav-link {% if view.active_menu == "members" %} active{% endif %}"href="{% url "teams:members" camp_slug=team.camp.slug team_slug=team.slug %}">
Members
Expand All @@ -31,40 +39,40 @@ <h1>{{ team.name }} Team</h1>
</a>
</li>

<li class="nav-item">
<a class="nav-link {% if view.active_menu == "tasks" %} active{% endif %}" href="{% url "teams:tasks" camp_slug=team.camp.slug team_slug=team.slug %}">
Tasks
</a>
</li>

{% if request.user in team.members.all %}
{% if request.user in team.leads.all %}
<li class="nav-item">
<a class="nav-link{% if view.active_menu == "shifts" %} active{% endif %}" href="{% url "teams:shifts" camp_slug=team.camp.slug team_slug=team.slug %}">
Shifts
<a class="nav-link" href="{% url "backoffice:team_permission_manage" camp_slug=team.camp.slug team_slug=team.slug %}">
Permissions
</a>
</li>
{% endif %}

{% if is_team_infopager %}
{% if request.user in team.leads.all %}
<li class="nav-item">
<a class="nav-link{% if view.active_menu == "info_categories" %} active{% endif %}" href="{% url "teams:info_categories" camp_slug=team.camp.slug team_slug=team.slug %}">
Info categories
<a class="nav-link{% if view.active_menu == "settings" %} active{% endif %}" href="{% url "teams:settings" camp_slug=team.camp.slug team_slug=team.slug %}">
Settings <span class="badge text-bg-info">New</span>
</a>
</li>
{% endif %}

{% if request.user in team.approved_members %}
{% if request.user in team.members.all %}
<li class="nav-item">
<a class="nav-link{% if view.active_menu == "guide" %} active{% endif %}" href="{% url "teams:guide" camp_slug=team.camp.slug team_slug=team.slug %}">
Team guide
<a class="nav-link{% if view.active_menu == "shifts" %} active{% endif %}" href="{% url "teams:shifts" camp_slug=team.camp.slug team_slug=team.slug %}">
Shifts
</a>
</li>
{% endif %}

{% if request.user in team.leads.all %}
<li class="nav-item">
<a class="nav-link {% if view.active_menu == "tasks" %} active{% endif %}" href="{% url "teams:tasks" camp_slug=team.camp.slug team_slug=team.slug %}">
Tasks
</a>
</li>

{% if request.user in team.approved_members %}
<li class="nav-item">
<a class="nav-link" href="{% url "backoffice:team_permission_manage" camp_slug=team.camp.slug team_slug=team.slug %}">
Permissions
<a class="nav-link{% if view.active_menu == "guide" %} active{% endif %}" href="{% url "teams:guide" camp_slug=team.camp.slug team_slug=team.slug %}">
Team guide
</a>
</li>
{% endif %}
Expand All @@ -78,10 +86,6 @@ <h1>{{ team.name }} Team</h1>
{% if request.user in team.members.all %}
<p>Your membership status: <b>{% membershipstatus user team %}</b></p>

{% if request.user in team.leads.all %}
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-cog"></i> Manage Team</a>
{% endif %}

{% else %}
{% if team.needs_members %}
<b>This team is looking for members!</b> <a href="{% url 'teams:join' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-xs btn-success"><i class="fas fa-plus"></i> Join Team</a>
Expand Down
14 changes: 14 additions & 0 deletions src/teams/templates/team_general.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ <h5>Signal</h5>
<p>The {{ team.name }} Team does not have a public Signal Group.</p>
{% endif %}

<h5>Phone</h5>
{% if team.public_phone_number %}
<p>The {{ team.name }} Team public phone number <a href="tel:{{ team.public_phone_number }}">{{ team.public_phone_number|urlize }}</a></p>
{% else %}
<p>The {{ team.name }} Team does not have a public phone number.</p>
{% endif %}

<h5>DECT</h5>
{% if team.public_dect_number %}
<p>The {{ team.name }} Team public DECT number is <a href="{% url 'phonebook:list' camp_slug=camp.slug %}">{{ team.public_dect_number.number }}</a></p>
{% else %}
<p>The {{ team.name }} Team does not have a public DECT number.</p>
{% endif %}

{% if request.user in team.approved_members.all and team.private_signal_channel_link %}
<p>The {{ team.name }} Team private Signal group is <a href="{{ team.private_signal_channel_link }}">{{ team.private_signal_channel_link|urlize }}</a></p>
{% endif %}
Expand Down
26 changes: 14 additions & 12 deletions src/teams/templates/team_guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,21 @@

<div class="card">
<div class="card-header">
<div class="btn-group pull-right">
<a href="{% url 'teams:guide_print' camp_slug=camp.slug team_slug=team.slug %}"
class="btn btn-secondary"
target="_blank">
Print
</a>
{% if request.user in team.leads.all %}
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary">Edit</a>
{% endif %}
<div class="d-flex justify-content-between">
<h4>
Guide / Howto for {{ team.name }}
</h4>
<div class="btn-group pull-right">
<a href="{% url 'teams:guide_print' camp_slug=camp.slug team_slug=team.slug %}"
class="btn btn-secondary"
target="_blank">
Print
</a>
{% if request.user in team.leads.all %}
<a href="{% url 'teams:settings' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary">Edit</a>
{% endif %}
</div>
</div>
<h4>
Guide / Howto for {{ team.name }}
</h4>
</div>
<div class="card-body">
<p>
Expand Down
2 changes: 1 addition & 1 deletion src/teams/templates/team_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ <h4>Your teams</h4>
<div class="btn-group-vertical">
<a class="btn btn-sm btn-primary text-nowrap" href="{% url 'teams:general' camp_slug=camp.slug team_slug=team.slug %}"><i class="fas fa-search"></i>&nbsp;Details</a>
{% if request.user in team.leads.all %}
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-sm btn-primary text-nowrap"><i class="fas fa-cog"></i>&nbsp;Manage</a>
<a href="{% url 'teams:settings' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-sm btn-info text-nowrap"><i class="fas fa-cog"></i>&nbsp;Settings</a>
{% endif %}
{% if request.user in team.members.all %}
<a href="{% url 'teams:leave' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-sm btn-danger text-nowrap"><i class="fas fa-times"></i>&nbsp;Leave</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@
{% endblock extra_head %}

{% block title %}
Manage Team: {{ team.name }} | {{ block.super }}
Settings | {{ block.super }}
{% endblock %}

{% block team_content %}
<div class="card">
<div class="card-header"><h4>Manage {{ team.name }} Team</h4></div>
<div class="card-header"><h4>Settings</h4></div>
<div class="card-body" style="margin-left: 1em; margin-right: 1em;">
<div class="form-group">
<form method="post" class="form-horizontal">
{% csrf_token %}

{% bootstrap_form form %}

<button class="btn btn-success ms-auto" type="submit"><i class="fas fa-check"></i> Save Team</button>
<button class="btn btn-success ms-auto" type="submit"><i class="fas fa-check"></i> Save</button>
<a class="btn btn-primary ms-auto" href="{% url 'teams:general' team_slug=team.slug camp_slug=camp.slug %}"><i class="fas fa-times"></i> Cancel</a>&nbsp;
</form>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/teams/tests/test_base_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ def test_team_list_view(self) -> None:
response = self.client.get(path=url)
assert response.status_code == 200

def test_team_manage_view(self) -> None:
"""Test the team manage view."""
def test_team_settings_view(self) -> None:
"""Test the team settings view."""
self.client.force_login(self.users[4])
url = reverse(
"teams:manage",
"teams:settings",
kwargs={
"team_slug": self.teams["noc"].slug,
"camp_slug": self.camp.slug,
Expand Down
4 changes: 2 additions & 2 deletions src/teams/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from teams.views.base import FixIrcAclView
from teams.views.base import TeamGeneralView
from teams.views.base import TeamListView
from teams.views.base import TeamManageView
from teams.views.base import TeamSettingsView
from teams.views.guide import TeamGuidePrintView
from teams.views.guide import TeamGuideView
from teams.views.info import InfoCategoriesListView
Expand Down Expand Up @@ -46,7 +46,7 @@
path("", TeamGeneralView.as_view(), name="general"),
path("join/", TeamJoinView.as_view(), name="join"),
path("leave/", TeamLeaveView.as_view(), name="leave"),
path("manage/", TeamManageView.as_view(), name="manage"),
path("settings/", TeamSettingsView.as_view(), name="settings"),
path("guide/", TeamGuideView.as_view(), name="guide"),
path("guide/print/", TeamGuidePrintView.as_view(), name="guide_print"),
path("fix_irc_acl/", FixIrcAclView.as_view(), name="fix_irc_acl"),
Expand Down
26 changes: 20 additions & 6 deletions src/teams/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from django.views.generic.edit import UpdateView

from camps.mixins import CampViewMixin
from phonebook.models import DectRegistration
from teams.models import Team
from teams.models import TeamMember
from utils.mixins import IsTeamPermContextMixin
Expand Down Expand Up @@ -76,11 +77,11 @@ def get_context_data(self, **kwargs) -> dict:
return context


class TeamManageView(CampViewMixin, EnsureTeamLeadMixin, IsTeamPermContextMixin, UpdateView):
class TeamSettingsView(CampViewMixin, EnsureTeamLeadMixin, IsTeamPermContextMixin, UpdateView):
"""View for mananaging team members."""

model = Team
template_name = "team_manage.html"
template_name = "team_settings.html"
fields = (
"description",
"needs_members",
Expand All @@ -92,22 +93,35 @@ class TeamManageView(CampViewMixin, EnsureTeamLeadMixin, IsTeamPermContextMixin,
"private_irc_channel_managed",
"public_signal_channel_link",
"private_signal_channel_link",
"public_phone_number",
"public_dect_number",
"guide",
)
slug_url_kwarg = "team_slug"
active_menu = "settings"

def get_form(self, *args, **kwargs) -> Form:
"""Method for updating form widgets."""
form = super().get_form(*args, **kwargs)
form.fields["guide"].widget = MarkdownWidget()

dect_filter = DectRegistration.objects.filter(
camp=self.camp,
user__in=self.object.members.all()
)
form.fields["public_dect_number"].queryset = dect_filter.filter(
publish_in_phonebook=True
)

return form

def get_success_url(self) -> str:
"""Method for returning the success url."""
return reverse_lazy(
"teams:general",
kwargs={"camp_slug": self.camp.slug, "team_slug": self.get_object().slug},
)
kwargs = {
"camp_slug": self.camp.slug,
"team_slug": self.get_object().slug
}
return reverse_lazy("teams:general", kwargs=kwargs)

def form_valid(self, form: Form) -> HttpResponseRedirect:
"""Method for sending success message if form is valid."""
Expand Down
Loading