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 functools import wraps
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group
from django.http import HttpResponseForbidden from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -60,3 +61,14 @@ def can_view_map(view_func):
return view_func(request, *args, **kwargs) return view_func(request, *args, **kwargs)
return wrapper 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 import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.gis.geos import Point from django.contrib.gis.geos import Point
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
from django.template.defaultfilters import slugify from django.template.defaultfilters import slugify
@ -110,3 +111,13 @@ class UserProfileForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ("username", "first_name", "last_name") 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 %} {% load i18n %}
{% block maincontent %} {% block maincontent %}
<div class="row"> {% include "umap/dashboard_menu.html" with selected="profile" %}
<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>
<div class="wrapper"> <div class="wrapper">
<div class="row"> <div class="row">
{% if form.non_field_errors %} {% 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 %} {% endblock head_title %}
{% block maincontent %} {% block maincontent %}
{% trans "Search my maps" as placeholder %} {% trans "Search my maps" as placeholder %}
<div class="row"> {% include "umap/dashboard_menu.html" with selected="maps" %}
<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>
<div class="wrapper"> <div class="wrapper">
<div class="row"> <div class="row">
<div class="table-header"> <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 ( from .decorators import (
can_edit_map, can_edit_map,
can_view_map, can_view_map,
group_members_only,
login_required_if_not_anonymous_allowed, login_required_if_not_anonymous_allowed,
) )
from .utils import decorated_patterns from .utils import decorated_patterns
@ -96,8 +97,8 @@ i18n_urls += decorated_patterns(
) )
i18n_urls += decorated_patterns( i18n_urls += decorated_patterns(
[ensure_csrf_cookie], [ensure_csrf_cookie],
re_path(r"^map/$", views.MapPreview.as_view(), name="map_preview"), path("map/", views.MapPreview.as_view(), name="map_preview"),
re_path(r"^map/new/$", views.MapNew.as_view(), name="map_new"), path("map/new/", views.MapNew.as_view(), name="map_new"),
) )
i18n_urls += decorated_patterns( i18n_urls += decorated_patterns(
[login_required_if_not_anonymous_allowed, never_cache], [login_required_if_not_anonymous_allowed, never_cache],
@ -110,9 +111,16 @@ i18n_urls += decorated_patterns(
views.ToggleMapStarStatus.as_view(), views.ToggleMapStarStatus.as_view(),
name="map_star", name="map_star",
), ),
re_path(r"^me$", views.user_dashboard, name="user_dashboard"), path("me", views.user_dashboard, name="user_dashboard"),
re_path(r"^me/profile$", views.user_profile, name="user_profile"), path("me/profile", views.user_profile, name="user_profile"),
re_path(r"^me/download$", views.user_download, name="user_download"), 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 = [ map_urls = [
re_path( re_path(

View file

@ -61,6 +61,7 @@ from .forms import (
DataLayerForm, DataLayerForm,
DataLayerPermissionsForm, DataLayerPermissionsForm,
FlatErrorList, FlatErrorList,
GroupForm,
MapSettingsForm, MapSettingsForm,
SendLinkForm, SendLinkForm,
UpdateMapPermissionsForm, UpdateMapPermissionsForm,
@ -189,6 +190,63 @@ class About(Home):
about = About.as_view() 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): class UserProfile(UpdateView):
model = User model = User
form_class = UserProfileForm form_class = UserProfileForm