wip: add very basic CRUD for groups

This commit is contained in:
Yohan Boniface 2024-08-19 15:21:06 +02:00
parent a3e972bf5d
commit 9b2a99019b
11 changed files with 199 additions and 18 deletions

View file

@ -1,6 +1,7 @@
from functools import wraps
from django.conf import settings
from django.contrib.auth.models import Group
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
@ -60,3 +61,14 @@ def can_view_map(view_func):
return view_func(request, *args, **kwargs)
return wrapper
def group_members_only(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
group = get_object_or_404(Group, pk=kwargs["pk"])
if group not in request.user.groups.all():
return HttpResponseForbidden()
return view_func(request, *args, **kwargs)
return wrapper

View file

@ -1,6 +1,7 @@
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.gis.geos import Point
from django.forms.utils import ErrorList
from django.template.defaultfilters import slugify
@ -110,3 +111,13 @@ class UserProfileForm(forms.ModelForm):
class Meta:
model = User
fields = ("username", "first_name", "last_name")
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = ["name", "members"]
members = forms.ModelMultipleChoiceField(
queryset=User.objects.all(), widget=forms.CheckboxSelectMultiple
)

View file

@ -0,0 +1,19 @@
{% extends "umap/content.html" %}
{% load i18n %}
{% block maincontent %}
{% include "umap/dashboard_menu.html" with selected="groups" %}
<div class="wrapper">
<div class="row">
<form method="post">
{% csrf_token %}
<p>
Are you sure you want to delete "{{ object }}"?
</p>
{{ form }}
<input type="submit" value="Confirm">
</form>
</div>
</div>
{% endblock maincontent %}

View file

@ -0,0 +1,22 @@
{% extends "umap/content.html" %}
{% load i18n %}
{% block maincontent %}
<div class="col wide">
<h2 class="section">
{% blocktrans %}Browse {{ current_group }}'s maps{% endblocktrans %}
</h2>
</div>
<div class="wrapper">
<div class="map_list row">
{% if maps %}
{% include "umap/map_list.html" %}
{% else %}
<div>
{% blocktrans %}{{ current_group }} has no public maps.{% endblocktrans %}
</div>
{% endif %}
</div>
</div>
{% endblock maincontent %}

View file

@ -0,0 +1,28 @@
{% extends "umap/content.html" %}
{% load i18n %}
{% block maincontent %}
{% include "umap/dashboard_menu.html" with selected="groups" %}
<div class="wrapper">
<div class="row">
{% if form.non_field_errors %}
<ul class="form-errors">
{% for error in form.non_field_errors %}
<li>
{{ error }}
</li>
{% endfor %}
</ul>
{% endif %}
<form id="group_form" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="{% trans 'Save' %}" />
</form>
{% if group.user_set.count == 1 %}
<a href="{% url 'group_delete' group.pk %}">{% trans "Delete this group" %}</a>
{% endif %}
</div>
</div>
{% endblock maincontent %}

View file

@ -3,12 +3,7 @@
{% load i18n %}
{% block maincontent %}
<div class="row">
<h2 class="section tabs">
<a href="{% url "user_dashboard" %}">{% trans "My Maps" %}</a>
<a class="selected" href="{% url 'user_profile' %}">{% trans "My Profile" %}</a>
</h2>
</div>
{% include "umap/dashboard_menu.html" with selected="profile" %}
<div class="wrapper">
<div class="row">
{% if form.non_field_errors %}

View file

@ -0,0 +1,15 @@
{% load i18n %}
<div class="row">
<h2 class="section tabs">
{% if selected == "maps" %}
<a class="selected" href="{% url 'user_dashboard' %}">{% blocktranslate with count=maps.paginator.count %}My Maps ({{ count }}){% endblocktranslate %}</a>
{% else %}
<a href="{% url 'user_dashboard' %}">{% trans "My Maps" %}</a>
{% endif %}
<a {% if selected == "profile" %}class="selected"{% endif %}
href="{% url 'user_profile' %}">{% trans "My profile" %}</a>
<a {% if selected == "groups" %}class="selected"{% endif %}
href="{% url 'user_groups' %}">{% trans "My teams" %}</a>
</h2>
</div>

View file

@ -7,13 +7,7 @@
{% endblock head_title %}
{% block maincontent %}
{% trans "Search my maps" as placeholder %}
<div class="row">
<h2 class="section tabs">
<a class="selected" href="{% url 'user_dashboard' %}">{% blocktranslate with count=maps.paginator.count %}My Maps ({{ count }}){% endblocktranslate %}
</a>
<a href="{% url 'user_profile' %}">{% trans "My profile" %}</a>
</h2>
</div>
{% include "umap/dashboard_menu.html" with selected="maps" %}
<div class="wrapper">
<div class="row">
<div class="table-header">

View file

@ -0,0 +1,19 @@
{% extends "umap/content.html" %}
{% load i18n %}
{% block maincontent %}
{% include "umap/dashboard_menu.html" with selected="groups" %}
<div class="wrapper">
<div class="row">
<ul>
{% for group in groups %}
<li>
{{ group }} <a href="{% url 'group_update' group.pk %}">({% trans "Edit" %})</a>
</li>
{% endfor %}
</ul>
<a href="{% url 'group_new' %}">{% trans "New team" %}</a>
</div>
</div>
{% endblock %}

View file

@ -15,6 +15,7 @@ from . import views
from .decorators import (
can_edit_map,
can_view_map,
group_members_only,
login_required_if_not_anonymous_allowed,
)
from .utils import decorated_patterns
@ -96,8 +97,8 @@ i18n_urls += decorated_patterns(
)
i18n_urls += decorated_patterns(
[ensure_csrf_cookie],
re_path(r"^map/$", views.MapPreview.as_view(), name="map_preview"),
re_path(r"^map/new/$", views.MapNew.as_view(), name="map_new"),
path("map/", views.MapPreview.as_view(), name="map_preview"),
path("map/new/", views.MapNew.as_view(), name="map_new"),
)
i18n_urls += decorated_patterns(
[login_required_if_not_anonymous_allowed, never_cache],
@ -110,9 +111,16 @@ i18n_urls += decorated_patterns(
views.ToggleMapStarStatus.as_view(),
name="map_star",
),
re_path(r"^me$", views.user_dashboard, name="user_dashboard"),
re_path(r"^me/profile$", views.user_profile, name="user_profile"),
re_path(r"^me/download$", views.user_download, name="user_download"),
path("me", views.user_dashboard, name="user_dashboard"),
path("me/profile", views.user_profile, name="user_profile"),
path("me/download", views.user_download, name="user_download"),
path("me/groups", views.UserGroups.as_view(), name="user_groups"),
path("group/create/", views.GroupNew.as_view(), name="group_new"),
)
i18n_urls += decorated_patterns(
[login_required, group_members_only],
path("group/<int:pk>/edit/", views.GroupUpdate.as_view(), name="group_update"),
path("group/<int:pk>/delete/", views.GroupDelete.as_view(), name="group_delete"),
)
map_urls = [
re_path(

View file

@ -61,6 +61,7 @@ from .forms import (
DataLayerForm,
DataLayerPermissionsForm,
FlatErrorList,
GroupForm,
MapSettingsForm,
SendLinkForm,
UpdateMapPermissionsForm,
@ -189,6 +190,63 @@ class About(Home):
about = About.as_view()
class GroupNew(CreateView):
model = Group
fields = ["name"]
success_url = reverse_lazy("user_groups")
def form_valid(self, form):
response = super().form_valid(form)
self.request.user.groups.add(self.object)
self.request.user.save()
return response
class GroupUpdate(UpdateView):
model = Group
form_class = GroupForm
success_url = reverse_lazy("user_groups")
def get_initial(self):
initial = super().get_initial()
initial["members"] = self.object.user_set.all()
return initial
def form_valid(self, form):
for user in form.cleaned_data["members"]:
user.groups.add(self.object)
user.save()
return super().form_valid(form)
class GroupDelete(DeleteView):
model = Group
success_url = reverse_lazy("user_groups")
def form_valid(self, form):
if self.object.user_set.count() > 1:
return HttpResponseBadRequest(
_("Cannot delete a group with more than one member")
)
messages.info(
self.request,
_("Group “%(name)s” has been deleted") % {"name": self.object.name},
)
return super().form_valid(form)
class UserGroups(DetailView):
model = User
template_name = "umap/user_groups.html"
def get_object(self):
return self.get_queryset().get(pk=self.request.user.pk)
def get_context_data(self, **kwargs):
kwargs.update({"groups": self.object.groups.all()})
return super().get_context_data(**kwargs)
class UserProfile(UpdateView):
model = User
form_class = UserProfileForm