mirror of
https://github.com/umap-project/umap.git
synced 2025-04-29 03:42:37 +02:00
chore: use our own Team model
We suppose we'll quickly want more than a name, like a description or a logo_url, and maybe a access_status or permissions…
This commit is contained in:
parent
6b6be017bb
commit
13735a5739
28 changed files with 435 additions and 378 deletions
|
@ -1,12 +1,11 @@
|
|||
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
|
||||
|
||||
from .models import Map
|
||||
from .models import Map, Team
|
||||
from .views import simple_json_response
|
||||
|
||||
LOGIN_URL = getattr(settings, "LOGIN_URL", "login")
|
||||
|
@ -63,11 +62,11 @@ def can_view_map(view_func):
|
|||
return wrapper
|
||||
|
||||
|
||||
def group_members_only(view_func):
|
||||
def team_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():
|
||||
team = get_object_or_404(Team, pk=kwargs["pk"])
|
||||
if not request.user.is_authenticated or team not in request.user.teams.all():
|
||||
return HttpResponseForbidden()
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
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
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import DataLayer, Map
|
||||
from .models import DataLayer, Map, Team
|
||||
|
||||
DEFAULT_LATITUDE = (
|
||||
settings.LEAFLET_LATITUDE if hasattr(settings, "LEAFLET_LATITUDE") else 51
|
||||
|
@ -37,7 +36,7 @@ class SendLinkForm(forms.Form):
|
|||
class UpdateMapPermissionsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Map
|
||||
fields = ("edit_status", "editors", "share_status", "owner", "group")
|
||||
fields = ("edit_status", "editors", "share_status", "owner", "team")
|
||||
|
||||
|
||||
class AnonymousMapPermissionsForm(forms.ModelForm):
|
||||
|
@ -113,25 +112,25 @@ class UserProfileForm(forms.ModelForm):
|
|||
fields = ("username", "first_name", "last_name")
|
||||
|
||||
|
||||
class GroupMembersField(forms.ModelMultipleChoiceField):
|
||||
class TeamMembersField(forms.ModelMultipleChoiceField):
|
||||
def set_choices(self, choices):
|
||||
iterator = self.iterator(self)
|
||||
# Override queryset so to expose only selected choices:
|
||||
# - we don't want a select with 100000 options
|
||||
# - the select values will be used by the autocomplete widget to display
|
||||
# already existing members of the group
|
||||
# already existing members of the team
|
||||
iterator.queryset = choices
|
||||
self.choices = iterator
|
||||
|
||||
|
||||
class GroupForm(forms.ModelForm):
|
||||
class TeamForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ["name", "members"]
|
||||
model = Team
|
||||
fields = ["name", "description", "logo_url", "members"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["members"].set_choices(self.initial["members"])
|
||||
self.fields["members"].widget.attrs["hidden"] = "hidden"
|
||||
|
||||
members = GroupMembersField(queryset=User.objects.all())
|
||||
members = TeamMembersField(queryset=User.objects.all())
|
||||
|
|
|
@ -1,27 +1,62 @@
|
|||
# Generated by Django 5.1 on 2024-08-15 11:33
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import umap.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
("umap", "0021_remove_map_description"),
|
||||
]
|
||||
dependencies = [("umap", "0021_remove_map_description")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Team",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(max_length=200, unique=True, verbose_name="name"),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
models.TextField(blank=True, null=True, verbose_name="description"),
|
||||
),
|
||||
(
|
||||
"logo_url",
|
||||
models.URLField(
|
||||
help_text="URL to an image.",
|
||||
verbose_name="Logo URL",
|
||||
blank=True,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"users",
|
||||
models.ManyToManyField(
|
||||
related_name="teams", to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="map",
|
||||
name="group",
|
||||
name="team",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="auth.group",
|
||||
verbose_name="group",
|
||||
to="umap.team",
|
||||
verbose_name="team",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
|
@ -5,7 +5,7 @@ import time
|
|||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.gis.db import models
|
||||
from django.core.files.base import File
|
||||
from django.core.signing import Signer
|
||||
|
@ -36,19 +36,9 @@ def get_user_stars_url(self):
|
|||
return reverse("user_stars", kwargs={"identifier": identifier})
|
||||
|
||||
|
||||
def get_group_url(self):
|
||||
return reverse("group_maps", kwargs={"pk": self.pk})
|
||||
|
||||
|
||||
def get_group_metadata(self):
|
||||
return {"id": self.pk, "name": self.name, "url": self.get_url()}
|
||||
|
||||
|
||||
User.add_to_class("__str__", display_name)
|
||||
User.add_to_class("get_url", get_user_url)
|
||||
User.add_to_class("get_stars_url", get_user_stars_url)
|
||||
Group.add_to_class("get_url", get_group_url)
|
||||
Group.add_to_class("get_metadata", get_group_metadata)
|
||||
|
||||
|
||||
def get_default_share_status():
|
||||
|
@ -59,6 +49,32 @@ def get_default_edit_status():
|
|||
return settings.UMAP_DEFAULT_EDIT_STATUS or Map.OWNER
|
||||
|
||||
|
||||
class Team(models.Model):
|
||||
name = models.CharField(
|
||||
max_length=200, verbose_name=_("name"), unique=True, blank=False, null=False
|
||||
)
|
||||
description = models.TextField(blank=True, null=True, verbose_name=_("description"))
|
||||
logo_url = models.URLField(
|
||||
verbose_name=_("Logo URL"),
|
||||
help_text=_("URL to an image."),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
users = models.ManyToManyField(User, related_name="teams")
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_url(self):
|
||||
return reverse("team_maps", kwargs={"pk": self.pk})
|
||||
|
||||
def get_metadata(self):
|
||||
return {"id": self.pk, "name": self.name, "url": self.get_url()}
|
||||
|
||||
|
||||
class NamedModel(models.Model):
|
||||
name = models.CharField(max_length=200, verbose_name=_("name"))
|
||||
|
||||
|
@ -190,8 +206,8 @@ class Map(NamedModel):
|
|||
editors = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL, blank=True, verbose_name=_("editors")
|
||||
)
|
||||
group = models.ForeignKey(
|
||||
"auth.Group",
|
||||
team = models.ForeignKey(
|
||||
Team,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("team"),
|
||||
|
@ -269,7 +285,7 @@ class Map(NamedModel):
|
|||
return settings.SITE_URL + path
|
||||
|
||||
def get_author(self):
|
||||
return self.group or self.owner
|
||||
return self.team or self.owner
|
||||
|
||||
def is_owner(self, user=None, request=None):
|
||||
if user and self.owner == user:
|
||||
|
@ -301,7 +317,7 @@ class Map(NamedModel):
|
|||
|
||||
In owner mode:
|
||||
- only owner by default (OWNER)
|
||||
- any editor or group member if mode is COLLABORATORS
|
||||
- any editor or team member if mode is COLLABORATORS
|
||||
- anyone otherwise (ANONYMOUS)
|
||||
In anonymous owner mode:
|
||||
- only owner (has ownership cookie) by default (OWNER)
|
||||
|
@ -318,7 +334,7 @@ class Map(NamedModel):
|
|||
elif user == self.owner:
|
||||
can = True
|
||||
elif self.edit_status == self.COLLABORATORS:
|
||||
if user in self.editors.all() or self.group in user.groups.all():
|
||||
if user in self.editors.all() or self.team in user.teams.all():
|
||||
can = True
|
||||
return can
|
||||
|
||||
|
@ -337,7 +353,7 @@ class Map(NamedModel):
|
|||
can = not (
|
||||
self.share_status == self.PRIVATE
|
||||
and request.user not in self.editors.all()
|
||||
and self.group not in request.user.groups.all()
|
||||
and self.team not in request.user.teams.all()
|
||||
)
|
||||
return can
|
||||
|
||||
|
@ -563,7 +579,7 @@ class DataLayer(NamedModel):
|
|||
elif user is not None and user == self.map.owner:
|
||||
can = True
|
||||
elif user is not None and self.edit_status == self.COLLABORATORS:
|
||||
if user in self.map.editors.all() or self.map.group in user.groups.all():
|
||||
if user in self.map.editors.all() or self.map.team in user.teams.all():
|
||||
can = True
|
||||
return can
|
||||
|
||||
|
|
|
@ -345,7 +345,7 @@ input + .error {
|
|||
input[type="file"] + .error {
|
||||
margin-top: 0;
|
||||
}
|
||||
input:invalid {
|
||||
input[value]:invalid {
|
||||
border-color: red;
|
||||
background-color: darkred;
|
||||
}
|
||||
|
|
|
@ -133,6 +133,8 @@ h2.section {
|
|||
text-align: center;
|
||||
}
|
||||
h2.tabs {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-darkBlue);
|
||||
text-align: start;
|
||||
|
@ -144,7 +146,6 @@ h2.tabs a {
|
|||
text-decoration-thickness: 3px;
|
||||
text-decoration-skip-ink: none;
|
||||
text-underline-offset: 7px;
|
||||
margin-inline-end: 2rem;
|
||||
display: inline-block;
|
||||
}
|
||||
h2.tabs a:not(.selected) {
|
||||
|
|
|
@ -25,7 +25,7 @@ export class MapPermissions {
|
|||
this.options = Object.assign(
|
||||
{
|
||||
owner: null,
|
||||
group: null,
|
||||
team: null,
|
||||
editors: [],
|
||||
share_status: null,
|
||||
edit_status: null,
|
||||
|
@ -97,13 +97,13 @@ export class MapPermissions {
|
|||
'options.owner',
|
||||
{ handler: 'ManageOwner', label: translate("Map's owner") },
|
||||
])
|
||||
if (this.map.options.user?.groups?.length) {
|
||||
if (this.map.options.user?.teams?.length) {
|
||||
fields.push([
|
||||
'options.group',
|
||||
'options.team',
|
||||
{
|
||||
handler: 'ManageGroup',
|
||||
handler: 'ManageTeam',
|
||||
label: translate('Attach map to a team'),
|
||||
groups: this.map.options.user.groups,
|
||||
teams: this.map.options.user.teams,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ export class MapPermissions {
|
|||
formData.append('edit_status', this.options.edit_status)
|
||||
if (this.isOwner()) {
|
||||
formData.append('owner', this.options.owner?.id)
|
||||
formData.append('group', this.options.group?.id || '')
|
||||
formData.append('team', this.options.team?.id || '')
|
||||
formData.append('share_status', this.options.share_status)
|
||||
}
|
||||
const [data, response, error] = await this.map.server.post(
|
||||
|
|
|
@ -1086,10 +1086,10 @@ L.FormBuilder.ManageEditors = L.FormBuilder.Element.extend({
|
|||
},
|
||||
})
|
||||
|
||||
L.FormBuilder.ManageGroup = L.FormBuilder.IntSelect.extend({
|
||||
L.FormBuilder.ManageTeam = L.FormBuilder.IntSelect.extend({
|
||||
getOptions: function () {
|
||||
return [[null, L._('None')]].concat(
|
||||
this.options.groups.map((group) => [group.id, group.name])
|
||||
this.options.teams.map((team) => [team.id, team.name])
|
||||
)
|
||||
},
|
||||
toHTML: function () {
|
||||
|
@ -1097,8 +1097,8 @@ L.FormBuilder.ManageGroup = L.FormBuilder.IntSelect.extend({
|
|||
},
|
||||
toJS: function () {
|
||||
const value = this.value()
|
||||
for (const group of this.options.groups) {
|
||||
if (group.id === value) return group
|
||||
for (const team of this.options.teams) {
|
||||
if (team.id === value) return team
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
{% 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 %}
|
|
@ -1,58 +0,0 @@
|
|||
{% 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 team" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" defer>
|
||||
const form = document.querySelector("#group_form")
|
||||
const select = form.querySelector('#id_members')
|
||||
function onSelect({item: {value, label}}) {
|
||||
const option = document.createElement('option')
|
||||
option.value = value
|
||||
option.textContent = label
|
||||
option.selected = "selected"
|
||||
select.appendChild(option)
|
||||
}
|
||||
function onUnselect({item: {value, label}}) {
|
||||
const option = select.querySelector(`[value="${value}"]`)
|
||||
select.removeChild(option)
|
||||
}
|
||||
const options = {
|
||||
className: 'edit-group-members',
|
||||
on_select: onSelect,
|
||||
on_unselect: onUnselect,
|
||||
placeholder: "{% trans "Add user" %}"
|
||||
}
|
||||
const autocomplete = new U.AjaxAutocompleteMultiple(form, options)
|
||||
for (const option of select.options) {
|
||||
autocomplete.displaySelected({
|
||||
item: { value: option.value, label: option.textContent },
|
||||
})
|
||||
}
|
||||
const submit = form.querySelector('input[type="submit"]')
|
||||
// Move it after the autocomplete widget.
|
||||
form.appendChild(submit)
|
||||
</script>
|
||||
{% endblock maincontent %}
|
|
@ -9,7 +9,7 @@
|
|||
{% 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>
|
||||
<a {% if selected == "teams" %}class="selected"{% endif %}
|
||||
href="{% url 'user_teams' %}">{% trans "My teams" %}</a>
|
||||
</h2>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% block maincontent %}
|
||||
{% include "umap/dashboard_menu.html" with selected="groups" %}
|
||||
{% include "umap/dashboard_menu.html" with selected="teams" %}
|
||||
<div class="wrapper">
|
||||
<div class="row">
|
||||
<form method="post">
|
30
umap/templates/umap/team_detail.html
Normal file
30
umap/templates/umap/team_detail.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{% extends "umap/content.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block maincontent %}
|
||||
<div class="wrapper">
|
||||
<div class="row">
|
||||
<div class="col wide">
|
||||
<h2 class="section">
|
||||
{% blocktrans %}Browse {{ current_team }}'s maps{% endblocktrans %}
|
||||
</h2>
|
||||
{% if current_team.description %}
|
||||
<p>{{ current_team.description }}</p>
|
||||
{% endif %}
|
||||
{% if current_team.logo_url %}
|
||||
<p><img class="logo" src="{{ current_team.logo_url }}" /></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="map_list row">
|
||||
{% if maps %}
|
||||
{% include "umap/map_list.html" %}
|
||||
{% else %}
|
||||
<div>
|
||||
{% blocktrans %}{{ current_team }} has no public maps.{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock maincontent %}
|
60
umap/templates/umap/team_form.html
Normal file
60
umap/templates/umap/team_form.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
{% extends "umap/content.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block maincontent %}
|
||||
{% include "umap/dashboard_menu.html" with selected="teams" %}
|
||||
<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="team_form" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="{% trans "Save" %}" />
|
||||
</form>
|
||||
{% if team.users.count == 1 %}
|
||||
<a href="{% url 'team_delete' team.pk %}">{% trans "Delete this team" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" defer>
|
||||
const form = document.querySelector("#team_form")
|
||||
const select = form.querySelector('#id_members')
|
||||
if (select) {
|
||||
function onSelect({item: {value, label}}) {
|
||||
const option = document.createElement('option')
|
||||
option.value = value
|
||||
option.textContent = label
|
||||
option.selected = "selected"
|
||||
select.appendChild(option)
|
||||
}
|
||||
function onUnselect({item: {value, label}}) {
|
||||
const option = select.querySelector(`[value="${value}"]`)
|
||||
select.removeChild(option)
|
||||
}
|
||||
const options = {
|
||||
className: 'edit-team-members',
|
||||
on_select: onSelect,
|
||||
on_unselect: onUnselect,
|
||||
placeholder: "{% trans "Add user" %}"
|
||||
}
|
||||
const autocomplete = new U.AjaxAutocompleteMultiple(form, options)
|
||||
for (const option of select.options) {
|
||||
autocomplete.displaySelected({
|
||||
item: { value: option.value, label: option.textContent },
|
||||
})
|
||||
}
|
||||
const submit = form.querySelector('input[type="submit"]')
|
||||
// Move it after the autocomplete widget.
|
||||
form.appendChild(submit)
|
||||
}
|
||||
</script>
|
||||
{% endblock maincontent %}
|
|
@ -1,19 +0,0 @@
|
|||
{% 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>
|
||||
<a href="{% url 'group_maps' group.pk %}">{{ group }}</a> <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 maincontent %}
|
19
umap/templates/umap/user_teams.html
Normal file
19
umap/templates/umap/user_teams.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "umap/content.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block maincontent %}
|
||||
{% include "umap/dashboard_menu.html" with selected="teams" %}
|
||||
<div class="wrapper">
|
||||
<div class="row">
|
||||
<ul>
|
||||
{% for team in teams %}
|
||||
<li>
|
||||
<a href="{% url 'team_maps' team.pk %}">{{ team }}</a> <a href="{% url 'team_update' team.pk %}">({% trans "Edit" %})</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{% url 'team_new' %}">{% trans "New team" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock maincontent %}
|
|
@ -3,12 +3,11 @@ import json
|
|||
|
||||
import factory
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.files.base import ContentFile
|
||||
from django.urls import reverse
|
||||
|
||||
from umap.forms import DEFAULT_CENTER
|
||||
from umap.models import DataLayer, Licence, Map, TileLayer
|
||||
from umap.models import DataLayer, Licence, Map, TileLayer, Team
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
@ -59,11 +58,11 @@ class UserFactory(factory.django.DjangoModelFactory):
|
|||
model = User
|
||||
|
||||
|
||||
class GroupFactory(factory.django.DjangoModelFactory):
|
||||
name = "Awesome Group"
|
||||
class TeamFactory(factory.django.DjangoModelFactory):
|
||||
name = "Awesome Team"
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
model = Team
|
||||
|
||||
|
||||
class MapFactory(factory.django.DjangoModelFactory):
|
||||
|
|
|
@ -9,7 +9,7 @@ from umap.models import Map
|
|||
|
||||
from .base import (
|
||||
DataLayerFactory,
|
||||
GroupFactory,
|
||||
TeamFactory,
|
||||
LicenceFactory,
|
||||
MapFactory,
|
||||
TileLayerFactory,
|
||||
|
@ -31,8 +31,8 @@ def pytest_runtest_teardown():
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def group():
|
||||
return GroupFactory()
|
||||
def team():
|
||||
return TeamFactory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
@ -36,12 +36,12 @@ def test_caption_should_display_owner_as_author(live_server, page, map):
|
|||
expect(panel.get_by_text("By Gabriel")).to_be_visible()
|
||||
|
||||
|
||||
def test_caption_should_display_group_as_author(live_server, page, map, group):
|
||||
def test_caption_should_display_team_as_author(live_server, page, map, team):
|
||||
map.settings["properties"]["onLoadPanel"] = "caption"
|
||||
map.group = group
|
||||
map.team = team
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
panel = page.locator(".panel.left.on")
|
||||
expect(panel).to_be_visible()
|
||||
expect(panel.get_by_text("By Gabriel")).to_be_hidden()
|
||||
expect(panel.get_by_text("By Awesome Group")).to_be_visible()
|
||||
expect(panel.get_by_text("By Awesome Team")).to_be_visible()
|
||||
|
|
|
@ -243,17 +243,17 @@ def test_can_delete_datalayer(live_server, map, login, datalayer):
|
|||
expect(layers).to_have_count(0)
|
||||
|
||||
|
||||
def test_can_set_group(map, live_server, login, group):
|
||||
map.owner.groups.add(group)
|
||||
def test_can_set_team(map, live_server, login, team):
|
||||
map.owner.teams.add(team)
|
||||
map.owner.save()
|
||||
page = login(map.owner)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||
edit_permissions = page.get_by_title("Update permissions and editors")
|
||||
edit_permissions.click()
|
||||
page.locator("select[name=group]").select_option(str(group.pk))
|
||||
page.locator("select[name=team]").select_option(str(team.pk))
|
||||
save = page.get_by_role("button", name="Save")
|
||||
expect(save).to_be_visible()
|
||||
with page.expect_response(re.compile(r".*/update/permissions/.*")):
|
||||
save.click()
|
||||
modified = Map.objects.get(pk=map.pk)
|
||||
assert modified.group == group
|
||||
assert modified.team == team
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import re
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from umap.models import Team
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_can_add_user_to_group(live_server, map, user, group, login):
|
||||
map.owner.groups.add(group)
|
||||
def test_can_add_user_to_team(live_server, map, user, team, login):
|
||||
map.owner.teams.add(team)
|
||||
map.owner.save()
|
||||
assert Group.objects.count() == 1
|
||||
assert Team.objects.count() == 1
|
||||
page = login(map.owner)
|
||||
with page.expect_navigation():
|
||||
page.get_by_role("link", name="My teams").click()
|
||||
|
@ -20,19 +21,19 @@ def test_can_add_user_to_group(live_server, map, user, group, login):
|
|||
page.get_by_placeholder("Add user").press_sequentially("joe")
|
||||
page.get_by_text("Joe").click()
|
||||
page.get_by_role("button", name="Save").click()
|
||||
assert Group.objects.count() == 1
|
||||
modified = Group.objects.first()
|
||||
assert user in modified.user_set.all()
|
||||
assert Team.objects.count() == 1
|
||||
modified = Team.objects.first()
|
||||
assert user in modified.users.all()
|
||||
|
||||
|
||||
def test_can_remove_user_from_group(live_server, map, user, user2, group, login):
|
||||
map.owner.groups.add(group)
|
||||
def test_can_remove_user_from_team(live_server, map, user, user2, team, login):
|
||||
map.owner.teams.add(team)
|
||||
map.owner.save()
|
||||
user.groups.add(group)
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
user2.groups.add(group)
|
||||
user2.teams.add(team)
|
||||
user2.save()
|
||||
assert Group.objects.count() == 1
|
||||
assert Team.objects.count() == 1
|
||||
page = login(map.owner)
|
||||
with page.expect_navigation():
|
||||
page.get_by_role("link", name="My teams").click()
|
||||
|
@ -40,7 +41,7 @@ def test_can_remove_user_from_group(live_server, map, user, user2, group, login)
|
|||
page.get_by_role("link", name="Edit").click()
|
||||
page.locator("li").filter(has_text="Averell").locator(".close").click()
|
||||
page.get_by_role("button", name="Save").click()
|
||||
assert Group.objects.count() == 1
|
||||
modified = Group.objects.first()
|
||||
assert user in modified.user_set.all()
|
||||
assert user2 not in modified.user_set.all()
|
||||
assert Team.objects.count() == 1
|
||||
modified = Team.objects.first()
|
||||
assert user in modified.users.all()
|
||||
assert user2 not in modified.users.all()
|
|
@ -121,11 +121,11 @@ def test_editor_can_edit_in_collaborators_mode(datalayer, user):
|
|||
assert datalayer.can_edit(user)
|
||||
|
||||
|
||||
def test_group_members_can_edit_in_collaborators_mode(datalayer, user, group):
|
||||
user.groups.add(group)
|
||||
def test_team_members_can_edit_in_collaborators_mode(datalayer, user, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
map = datalayer.map
|
||||
map.group = group
|
||||
map.team = team
|
||||
map.save()
|
||||
datalayer.edit_status = DataLayer.COLLABORATORS
|
||||
datalayer.save()
|
||||
|
@ -181,15 +181,15 @@ def test_editors_cannot_edit_in_inherit_mode_and_map_in_owner_mode(datalayer, us
|
|||
assert not datalayer.can_edit(user)
|
||||
|
||||
|
||||
def test_group_members_cannot_edit_in_inherit_mode_and_map_in_owner_mode(
|
||||
datalayer, user, group
|
||||
def test_team_members_cannot_edit_in_inherit_mode_and_map_in_owner_mode(
|
||||
datalayer, user, team
|
||||
):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
user.groups.add(group)
|
||||
group.save()
|
||||
user.teams.add(team)
|
||||
team.save()
|
||||
map = datalayer.map
|
||||
map.group = group
|
||||
map.team = team
|
||||
map.edit_status = Map.OWNER
|
||||
map.save()
|
||||
assert not datalayer.can_edit(user)
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
import pytest
|
||||
from django.contrib.auth.models import Group
|
||||
from django.urls import reverse
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_can_see_group_maps(client, map, group):
|
||||
map.group = group
|
||||
map.save()
|
||||
url = reverse("group_maps", args=(group.pk,))
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert map.name in response.content.decode()
|
||||
|
||||
|
||||
def test_user_can_see_their_groups(client, group, user):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
url = reverse("user_groups")
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert group.name in response.content.decode()
|
||||
|
||||
|
||||
def test_can_create_a_group(client, user):
|
||||
assert not Group.objects.count()
|
||||
url = reverse("group_new")
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url, {"name": "my new group", "members": [user.pk]})
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/groups"
|
||||
assert Group.objects.count() == 1
|
||||
group = Group.objects.first()
|
||||
assert group.name == "my new group"
|
||||
assert group in user.groups.all()
|
||||
|
||||
|
||||
def test_can_edit_a_group_name(client, user, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
assert Group.objects.count() == 1
|
||||
url = reverse("group_update", args=(group.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url, {"name": "my new group", "members": [user.pk]})
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/groups"
|
||||
assert Group.objects.count() == 1
|
||||
modified = Group.objects.first()
|
||||
assert modified.name == "my new group"
|
||||
assert modified in user.groups.all()
|
||||
|
||||
|
||||
def test_can_add_user_to_group(client, user, user2, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
assert Group.objects.count() == 1
|
||||
url = reverse("group_update", args=(group.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url, {"name": group.name, "members": [user.pk, user2.pk]})
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/groups"
|
||||
assert Group.objects.count() == 1
|
||||
modified = Group.objects.first()
|
||||
assert user in modified.user_set.all()
|
||||
assert user2 in modified.user_set.all()
|
||||
|
||||
|
||||
def test_can_remove_user_from_group(client, user, user2, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
user2.groups.add(group)
|
||||
user2.save()
|
||||
assert Group.objects.count() == 1
|
||||
url = reverse("group_update", args=(group.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url, {"name": group.name, "members": [user.pk]})
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/groups"
|
||||
assert Group.objects.count() == 1
|
||||
modified = Group.objects.first()
|
||||
assert user in modified.user_set.all()
|
||||
assert user2 not in modified.user_set.all()
|
||||
|
||||
|
||||
def test_cannot_edit_a_group_if_not_member(client, user, user2, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
assert Group.objects.count() == 1
|
||||
url = reverse("group_update", args=(group.pk,))
|
||||
client.login(username=user2.username, password="123123")
|
||||
response = client.post(url, {"name": "my new group", "members": [user.pk]})
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_can_delete_a_group(client, user, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
assert Group.objects.count() == 1
|
||||
url = reverse("group_delete", args=(group.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url)
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/groups"
|
||||
assert Group.objects.count() == 0
|
||||
|
||||
|
||||
def test_cannot_delete_a_group_if_not_member(client, user, user2, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
assert Group.objects.count() == 1
|
||||
url = reverse("group_delete", args=(group.pk,))
|
||||
client.login(username=user2.username, password="123123")
|
||||
response = client.post(url)
|
||||
assert response.status_code == 403
|
||||
assert Group.objects.count() == 1
|
||||
|
||||
|
||||
def test_cannot_delete_a_group_if_more_than_one_member(client, user, user2, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
user2.groups.add(group)
|
||||
user2.save()
|
||||
assert Group.objects.count() == 1
|
||||
url = reverse("group_delete", args=(group.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url)
|
||||
assert response.status_code == 400
|
||||
assert Group.objects.count() == 1
|
|
@ -50,20 +50,20 @@ def test_editors_can_edit_if_status_collaborators(map, user):
|
|||
assert map.can_edit(user)
|
||||
|
||||
|
||||
def test_group_members_cannot_edit_if_status_owner(map, user, group):
|
||||
user.groups.add(group)
|
||||
def test_team_members_cannot_edit_if_status_owner(map, user, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
map.edit_status = map.OWNER
|
||||
map.group = group
|
||||
map.team = team
|
||||
map.save()
|
||||
assert not map.can_edit(user)
|
||||
|
||||
|
||||
def test_group_members_can_edit_if_status_collaborators(map, user, group):
|
||||
user.groups.add(group)
|
||||
def test_team_members_can_edit_if_status_collaborators(map, user, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
map.edit_status = map.COLLABORATORS
|
||||
map.group = group
|
||||
map.team = team
|
||||
map.save()
|
||||
assert map.can_edit(user)
|
||||
|
||||
|
@ -105,12 +105,12 @@ def test_clone_should_keep_editors(map, user):
|
|||
assert user in clone.editors.all()
|
||||
|
||||
|
||||
def test_clone_should_keep_group(map, user, group):
|
||||
map.group = group
|
||||
def test_clone_should_keep_team(map, user, team):
|
||||
map.team = team
|
||||
map.save()
|
||||
clone = map.clone()
|
||||
assert map.pk != clone.pk
|
||||
assert clone.group == group
|
||||
assert clone.team == team
|
||||
|
||||
|
||||
def test_clone_should_update_owner_if_passed(map, user):
|
||||
|
|
131
umap/tests/test_team_views.py
Normal file
131
umap/tests/test_team_views.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
import pytest
|
||||
from django.urls import reverse
|
||||
|
||||
from umap.models import Team
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_can_see_team_maps(client, map, team):
|
||||
map.team = team
|
||||
map.save()
|
||||
url = reverse("team_maps", args=(team.pk,))
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert map.name in response.content.decode()
|
||||
|
||||
|
||||
def test_user_can_see_their_teams(client, team, user):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
url = reverse("user_teams")
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert team.name in response.content.decode()
|
||||
|
||||
|
||||
def test_can_create_a_team(client, user):
|
||||
assert not Team.objects.count()
|
||||
url = reverse("team_new")
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url, {"name": "my new team", "members": [user.pk]})
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/teams"
|
||||
assert Team.objects.count() == 1
|
||||
team = Team.objects.first()
|
||||
assert team.name == "my new team"
|
||||
assert team in user.teams.all()
|
||||
|
||||
|
||||
def test_can_edit_a_team_name(client, user, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
assert Team.objects.count() == 1
|
||||
url = reverse("team_update", args=(team.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url, {"name": "my new team", "members": [user.pk]})
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/teams"
|
||||
assert Team.objects.count() == 1
|
||||
modified = Team.objects.first()
|
||||
assert modified.name == "my new team"
|
||||
assert modified in user.teams.all()
|
||||
|
||||
|
||||
def test_can_add_user_to_team(client, user, user2, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
assert Team.objects.count() == 1
|
||||
url = reverse("team_update", args=(team.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url, {"name": team.name, "members": [user.pk, user2.pk]})
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/teams"
|
||||
assert Team.objects.count() == 1
|
||||
modified = Team.objects.first()
|
||||
assert user in modified.users.all()
|
||||
assert user2 in modified.users.all()
|
||||
|
||||
|
||||
def test_can_remove_user_from_team(client, user, user2, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
user2.teams.add(team)
|
||||
user2.save()
|
||||
assert Team.objects.count() == 1
|
||||
url = reverse("team_update", args=(team.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url, {"name": team.name, "members": [user.pk]})
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/teams"
|
||||
assert Team.objects.count() == 1
|
||||
modified = Team.objects.first()
|
||||
assert user in modified.users.all()
|
||||
assert user2 not in modified.users.all()
|
||||
|
||||
|
||||
def test_cannot_edit_a_team_if_not_member(client, user, user2, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
assert Team.objects.count() == 1
|
||||
url = reverse("team_update", args=(team.pk,))
|
||||
client.login(username=user2.username, password="456456")
|
||||
response = client.post(url, {"name": "my new team", "members": [user.pk]})
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_can_delete_a_team(client, user, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
assert Team.objects.count() == 1
|
||||
url = reverse("team_delete", args=(team.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url)
|
||||
assert response.status_code == 302
|
||||
assert response["Location"] == "/en/me/teams"
|
||||
assert Team.objects.count() == 0
|
||||
|
||||
|
||||
def test_cannot_delete_a_team_if_not_member(client, user, user2, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
assert Team.objects.count() == 1
|
||||
url = reverse("team_delete", args=(team.pk,))
|
||||
client.login(username=user2.username, password="456456")
|
||||
response = client.post(url)
|
||||
assert response.status_code == 403
|
||||
assert Team.objects.count() == 1
|
||||
|
||||
|
||||
def test_cannot_delete_a_team_if_more_than_one_member(client, user, user2, team):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
user2.teams.add(team)
|
||||
user2.save()
|
||||
assert Team.objects.count() == 1
|
||||
url = reverse("team_delete", args=(team.pk,))
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.post(url)
|
||||
assert response.status_code == 400
|
||||
assert Team.objects.count() == 1
|
|
@ -289,10 +289,10 @@ def test_user_dashboard_display_user_maps(client, map):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_dashboard_display_user_group_maps(client, map, group, user):
|
||||
user.groups.add(group)
|
||||
def test_user_dashboard_display_user_team_maps(client, map, team, user):
|
||||
user.teams.add(team)
|
||||
user.save()
|
||||
map.group = group
|
||||
map.team = team
|
||||
map.save()
|
||||
client.login(username=user.username, password="123123")
|
||||
response = client.get(reverse("user_dashboard"))
|
||||
|
|
14
umap/urls.py
14
umap/urls.py
|
@ -15,7 +15,7 @@ from . import views
|
|||
from .decorators import (
|
||||
can_edit_map,
|
||||
can_view_map,
|
||||
group_members_only,
|
||||
team_members_only,
|
||||
login_required_if_not_anonymous_allowed,
|
||||
)
|
||||
from .utils import decorated_patterns
|
||||
|
@ -117,13 +117,13 @@ i18n_urls += decorated_patterns(
|
|||
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"),
|
||||
path("me/teams", views.UserTeams.as_view(), name="user_teams"),
|
||||
path("team/create/", views.TeamNew.as_view(), name="team_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"),
|
||||
[login_required, team_members_only],
|
||||
path("team/<int:pk>/edit/", views.TeamUpdate.as_view(), name="team_update"),
|
||||
path("team/<int:pk>/delete/", views.TeamDelete.as_view(), name="team_delete"),
|
||||
)
|
||||
map_urls = [
|
||||
re_path(
|
||||
|
@ -196,7 +196,7 @@ urlpatterns += i18n_patterns(
|
|||
path("about/", views.about, name="about"),
|
||||
re_path(r"^user/(?P<identifier>.+)/stars/$", views.user_stars, name="user_stars"),
|
||||
re_path(r"^user/(?P<identifier>.+)/$", views.user_maps, name="user_maps"),
|
||||
path("group/<int:pk>/", views.group_maps, name="group_maps"),
|
||||
path("team/<int:pk>/", views.TeamMaps.as_view(), name="team_maps"),
|
||||
re_path(r"", include(i18n_urls)),
|
||||
)
|
||||
urlpatterns += (
|
||||
|
|
|
@ -18,7 +18,6 @@ from django.conf import settings
|
|||
from django.contrib import messages
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth import logout as do_logout
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.gis.measure import D
|
||||
from django.contrib.postgres.search import SearchQuery, SearchVector
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
|
@ -61,13 +60,13 @@ from .forms import (
|
|||
DataLayerForm,
|
||||
DataLayerPermissionsForm,
|
||||
FlatErrorList,
|
||||
GroupForm,
|
||||
TeamForm,
|
||||
MapSettingsForm,
|
||||
SendLinkForm,
|
||||
UpdateMapPermissionsForm,
|
||||
UserProfileForm,
|
||||
)
|
||||
from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer
|
||||
from .models import DataLayer, Licence, Map, Pictogram, Star, Team, TileLayer
|
||||
from .utils import (
|
||||
ConflictError,
|
||||
_urls_for_js,
|
||||
|
@ -190,67 +189,67 @@ class About(Home):
|
|||
about = About.as_view()
|
||||
|
||||
|
||||
class GroupNew(CreateView):
|
||||
model = Group
|
||||
fields = ["name"]
|
||||
success_url = reverse_lazy("user_groups")
|
||||
class TeamNew(CreateView):
|
||||
model = Team
|
||||
fields = ["name", "description", "logo_url"]
|
||||
success_url = reverse_lazy("user_teams")
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
self.request.user.groups.add(self.object)
|
||||
self.request.user.teams.add(self.object)
|
||||
self.request.user.save()
|
||||
return response
|
||||
|
||||
|
||||
class GroupUpdate(UpdateView):
|
||||
model = Group
|
||||
form_class = GroupForm
|
||||
success_url = reverse_lazy("user_groups")
|
||||
class TeamUpdate(UpdateView):
|
||||
model = Team
|
||||
form_class = TeamForm
|
||||
success_url = reverse_lazy("user_teams")
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
initial["members"] = self.object.user_set.all()
|
||||
initial["members"] = self.object.users.all()
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
actual = self.object.user_set.all()
|
||||
actual = self.object.users.all()
|
||||
wanted = form.cleaned_data["members"]
|
||||
for user in wanted:
|
||||
if user not in actual:
|
||||
user.groups.add(self.object)
|
||||
user.teams.add(self.object)
|
||||
user.save()
|
||||
for user in actual:
|
||||
if user not in wanted:
|
||||
user.groups.remove(self.object)
|
||||
user.teams.remove(self.object)
|
||||
user.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class GroupDelete(DeleteView):
|
||||
model = Group
|
||||
success_url = reverse_lazy("user_groups")
|
||||
class TeamDelete(DeleteView):
|
||||
model = Team
|
||||
success_url = reverse_lazy("user_teams")
|
||||
|
||||
def form_valid(self, form):
|
||||
if self.object.user_set.count() > 1:
|
||||
if self.object.users.count() > 1:
|
||||
return HttpResponseBadRequest(
|
||||
_("Cannot delete a group with more than one member")
|
||||
_("Cannot delete a team with more than one member")
|
||||
)
|
||||
messages.info(
|
||||
self.request,
|
||||
_("Group “%(name)s” has been deleted") % {"name": self.object.name},
|
||||
_("Team “%(name)s” has been deleted") % {"name": self.object.name},
|
||||
)
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class UserGroups(DetailView):
|
||||
class UserTeams(DetailView):
|
||||
model = User
|
||||
template_name = "umap/user_groups.html"
|
||||
template_name = "umap/user_teams.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()})
|
||||
kwargs.update({"teams": self.object.teams.all()})
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
@ -313,13 +312,13 @@ class UserStars(UserMaps):
|
|||
user_stars = UserStars.as_view()
|
||||
|
||||
|
||||
class GroupMaps(PaginatorMixin, DetailView):
|
||||
model = Group
|
||||
class TeamMaps(PaginatorMixin, DetailView):
|
||||
model = Team
|
||||
list_template_name = "umap/map_list.html"
|
||||
context_object_name = "current_group"
|
||||
context_object_name = "current_team"
|
||||
|
||||
def get_maps(self):
|
||||
return Map.public.filter(group=self.object).order_by("-modified_at")
|
||||
return Map.public.filter(team=self.object).order_by("-modified_at")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs.update(
|
||||
|
@ -328,9 +327,6 @@ class GroupMaps(PaginatorMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
group_maps = GroupMaps.as_view()
|
||||
|
||||
|
||||
class SearchMixin:
|
||||
def get_search_queryset(self, **kwargs):
|
||||
q = self.request.GET.get("q")
|
||||
|
@ -377,11 +373,11 @@ class UserDashboard(PaginatorMixin, DetailView, SearchMixin):
|
|||
|
||||
def get_maps(self):
|
||||
qs = self.get_search_queryset() or Map.objects.all()
|
||||
groups = self.object.groups.all()
|
||||
teams = self.object.teams.all()
|
||||
qs = (
|
||||
qs.filter(owner=self.object)
|
||||
.union(qs.filter(editors=self.object))
|
||||
.union(qs.filter(group__in=groups))
|
||||
.union(qs.filter(team__in=teams))
|
||||
)
|
||||
return qs.order_by("-modified_at")
|
||||
|
||||
|
@ -557,7 +553,7 @@ class SessionMixin:
|
|||
"id": user.pk,
|
||||
"name": str(self.request.user),
|
||||
"url": reverse("user_dashboard"),
|
||||
"groups": [group.get_metadata() for group in user.groups.all()],
|
||||
"teams": [team.get_metadata() for team in user.teams.all()],
|
||||
**data,
|
||||
}
|
||||
|
||||
|
@ -696,8 +692,8 @@ class PermissionsMixin:
|
|||
{"id": editor.pk, "name": str(editor)}
|
||||
for editor in self.object.editors.all()
|
||||
]
|
||||
if self.object.group:
|
||||
permissions["group"] = self.object.group.get_metadata()
|
||||
if self.object.team:
|
||||
permissions["team"] = self.object.team.get_metadata()
|
||||
if not self.object.owner and self.object.is_anonymous_owner(self.request):
|
||||
permissions["anonymous_edit_url"] = self.object.get_anonymous_edit_url()
|
||||
return permissions
|
||||
|
|
Loading…
Reference in a new issue