mirror of
https://github.com/umap-project/umap.git
synced 2025-04-29 03:42:37 +02:00
wip: use auth.models.Group and manage permissions
This commit is contained in:
parent
6126a6666e
commit
dce0ee5f73
15 changed files with 237 additions and 34 deletions
|
@ -35,7 +35,7 @@ def can_edit_map(view_func):
|
|||
map_inst = get_object_or_404(Map, pk=kwargs["map_id"])
|
||||
user = request.user
|
||||
kwargs["map_inst"] = map_inst # Avoid rerequesting the map in the view
|
||||
if map_inst.edit_status >= map_inst.EDITORS:
|
||||
if map_inst.edit_status >= map_inst.COLLABORATORS:
|
||||
can_edit = map_inst.can_edit(user=user, request=request)
|
||||
if not can_edit:
|
||||
if map_inst.owner and not user.is_authenticated:
|
||||
|
|
|
@ -36,7 +36,7 @@ class SendLinkForm(forms.Form):
|
|||
class UpdateMapPermissionsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Map
|
||||
fields = ("edit_status", "editors", "share_status", "owner")
|
||||
fields = ("edit_status", "editors", "share_status", "owner", "group")
|
||||
|
||||
|
||||
class AnonymousMapPermissionsForm(forms.ModelForm):
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
# Generated by Django 5.1 on 2024-08-15 11:33
|
||||
|
||||
import django.db.models.deletion
|
||||
import umap.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
("umap", "0021_remove_map_description"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="map",
|
||||
name="group",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="auth.group",
|
||||
verbose_name="group",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="datalayer",
|
||||
name="edit_status",
|
||||
field=models.SmallIntegerField(
|
||||
choices=[
|
||||
(0, "Inherit"),
|
||||
(1, "Everyone"),
|
||||
(2, "Editors and team only"),
|
||||
(3, "Owner only"),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="edit status",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="map",
|
||||
name="edit_status",
|
||||
field=models.SmallIntegerField(
|
||||
choices=[
|
||||
(1, "Everyone"),
|
||||
(2, "Editors and team only"),
|
||||
(3, "Owner only"),
|
||||
],
|
||||
default=umap.models.get_default_edit_status,
|
||||
verbose_name="edit status",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="map",
|
||||
name="share_status",
|
||||
field=models.SmallIntegerField(
|
||||
choices=[
|
||||
(1, "Everyone (public)"),
|
||||
(2, "Anyone with link"),
|
||||
(3, "Editors and team only"),
|
||||
(9, "Blocked"),
|
||||
],
|
||||
default=umap.models.get_default_share_status,
|
||||
verbose_name="share status",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -5,7 +5,7 @@ import time
|
|||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.gis.db import models
|
||||
from django.core.files.base import File
|
||||
from django.core.signing import Signer
|
||||
|
@ -36,9 +36,19 @@ def get_user_stars_url(self):
|
|||
return reverse("user_stars", kwargs={"identifier": identifier})
|
||||
|
||||
|
||||
def get_group_url(self):
|
||||
return "TODO"
|
||||
|
||||
|
||||
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():
|
||||
|
@ -137,7 +147,7 @@ class Map(NamedModel):
|
|||
"""
|
||||
|
||||
ANONYMOUS = 1
|
||||
EDITORS = 2
|
||||
COLLABORATORS = 2
|
||||
OWNER = 3
|
||||
PUBLIC = 1
|
||||
OPEN = 2
|
||||
|
@ -145,13 +155,13 @@ class Map(NamedModel):
|
|||
BLOCKED = 9
|
||||
EDIT_STATUS = (
|
||||
(ANONYMOUS, _("Everyone")),
|
||||
(EDITORS, _("Editors only")),
|
||||
(COLLABORATORS, _("Editors and team only")),
|
||||
(OWNER, _("Owner only")),
|
||||
)
|
||||
SHARE_STATUS = (
|
||||
(PUBLIC, _("Everyone (public)")),
|
||||
(OPEN, _("Anyone with link")),
|
||||
(PRIVATE, _("Editors only")),
|
||||
(PRIVATE, _("Editors and team only")),
|
||||
(BLOCKED, _("Blocked")),
|
||||
)
|
||||
slug = models.SlugField(db_index=True)
|
||||
|
@ -180,6 +190,13 @@ class Map(NamedModel):
|
|||
editors = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL, blank=True, verbose_name=_("editors")
|
||||
)
|
||||
group = models.ForeignKey(
|
||||
"auth.Group",
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("team"),
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
edit_status = models.SmallIntegerField(
|
||||
choices=EDIT_STATUS,
|
||||
default=get_default_edit_status,
|
||||
|
@ -281,7 +298,7 @@ class Map(NamedModel):
|
|||
|
||||
In owner mode:
|
||||
- only owner by default (OWNER)
|
||||
- any editor if mode is EDITORS
|
||||
- any editor or group member if mode is COLLABORATORS
|
||||
- anyone otherwise (ANONYMOUS)
|
||||
In anonymous owner mode:
|
||||
- only owner (has ownership cookie) by default (OWNER)
|
||||
|
@ -297,8 +314,9 @@ class Map(NamedModel):
|
|||
can = False
|
||||
elif user == self.owner:
|
||||
can = True
|
||||
elif self.edit_status == self.EDITORS and user in self.editors.all():
|
||||
can = True
|
||||
elif self.edit_status == self.COLLABORATORS:
|
||||
if user in self.editors.all() or self.group in user.groups.all():
|
||||
can = True
|
||||
return can
|
||||
|
||||
def can_view(self, request):
|
||||
|
@ -308,12 +326,15 @@ class Map(NamedModel):
|
|||
can = True
|
||||
elif self.share_status in [self.PUBLIC, self.OPEN]:
|
||||
can = True
|
||||
elif request.user is None:
|
||||
can = False
|
||||
elif request.user == self.owner:
|
||||
can = True
|
||||
else:
|
||||
can = not (
|
||||
self.share_status == self.PRIVATE
|
||||
and request.user not in self.editors.all()
|
||||
and self.group not in request.user.groups.all()
|
||||
)
|
||||
return can
|
||||
|
||||
|
@ -383,12 +404,12 @@ class DataLayer(NamedModel):
|
|||
|
||||
INHERIT = 0
|
||||
ANONYMOUS = 1
|
||||
EDITORS = 2
|
||||
COLLABORATORS = 2
|
||||
OWNER = 3
|
||||
EDIT_STATUS = (
|
||||
(INHERIT, _("Inherit")),
|
||||
(ANONYMOUS, _("Everyone")),
|
||||
(EDITORS, _("Editors only")),
|
||||
(COLLABORATORS, _("Editors and team only")),
|
||||
(OWNER, _("Owner only")),
|
||||
)
|
||||
uuid = models.UUIDField(
|
||||
|
@ -538,8 +559,9 @@ class DataLayer(NamedModel):
|
|||
can = True
|
||||
elif user is not None and user == self.map.owner:
|
||||
can = True
|
||||
elif self.edit_status == self.EDITORS and user in self.map.editors.all():
|
||||
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():
|
||||
can = True
|
||||
return can
|
||||
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ export class MapPermissions {
|
|||
this.options = Object.assign(
|
||||
{
|
||||
owner: null,
|
||||
group: null,
|
||||
editors: [],
|
||||
share_status: null,
|
||||
edit_status: null,
|
||||
|
@ -96,6 +97,16 @@ export class MapPermissions {
|
|||
'options.owner',
|
||||
{ handler: 'ManageOwner', label: translate("Map's owner") },
|
||||
])
|
||||
if (this.map.options.user?.groups?.length) {
|
||||
fields.push([
|
||||
'options.group',
|
||||
{
|
||||
handler: 'ManageGroup',
|
||||
label: translate('Attach map to a team'),
|
||||
groups: this.map.options.user.groups,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
fields.push([
|
||||
'options.editors',
|
||||
|
@ -150,6 +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('share_status', this.options.share_status)
|
||||
}
|
||||
const [data, response, error] = await this.map.server.post(
|
||||
|
|
|
@ -1086,6 +1086,23 @@ L.FormBuilder.ManageEditors = L.FormBuilder.Element.extend({
|
|||
},
|
||||
})
|
||||
|
||||
L.FormBuilder.ManageGroup = L.FormBuilder.IntSelect.extend({
|
||||
getOptions: function () {
|
||||
return [[null, L._('None')]].concat(
|
||||
this.options.groups.map((group) => [group.id, group.name])
|
||||
)
|
||||
},
|
||||
toHTML: function () {
|
||||
return this.get()?.id
|
||||
},
|
||||
toJS: function () {
|
||||
const value = this.value()
|
||||
for (const group of this.options.groups) {
|
||||
if (group.id === value) return group
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
U.FormBuilder = L.FormBuilder.extend({
|
||||
options: {
|
||||
className: 'umap-form',
|
||||
|
|
|
@ -3,6 +3,7 @@ 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
|
||||
|
||||
|
@ -58,6 +59,13 @@ class UserFactory(factory.django.DjangoModelFactory):
|
|||
model = User
|
||||
|
||||
|
||||
class GroupFactory(factory.django.DjangoModelFactory):
|
||||
name = "Awesome Group"
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
|
||||
|
||||
class MapFactory(factory.django.DjangoModelFactory):
|
||||
name = "test map"
|
||||
slug = "test-map"
|
||||
|
|
|
@ -9,6 +9,7 @@ from umap.models import Map
|
|||
|
||||
from .base import (
|
||||
DataLayerFactory,
|
||||
GroupFactory,
|
||||
LicenceFactory,
|
||||
MapFactory,
|
||||
TileLayerFactory,
|
||||
|
@ -29,6 +30,11 @@ def pytest_runtest_teardown():
|
|||
cache.clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def group():
|
||||
return GroupFactory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user():
|
||||
return UserFactory(password="123123")
|
||||
|
|
|
@ -81,7 +81,7 @@ def test_owner_permissions_form(map, datalayer, live_server, login):
|
|||
|
||||
|
||||
def test_map_update_with_editor(map, live_server, login, user):
|
||||
map.edit_status = Map.EDITORS
|
||||
map.edit_status = Map.COLLABORATORS
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
page = login(user)
|
||||
|
@ -104,7 +104,7 @@ def test_map_update_with_editor(map, live_server, login, user):
|
|||
|
||||
|
||||
def test_permissions_form_with_editor(map, datalayer, live_server, login, user):
|
||||
map.edit_status = Map.EDITORS
|
||||
map.edit_status = Map.COLLABORATORS
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
page = login(user)
|
||||
|
@ -141,7 +141,7 @@ def test_owner_has_delete_map_button(map, live_server, login):
|
|||
|
||||
|
||||
def test_editor_do_not_have_delete_map_button(map, live_server, login, user):
|
||||
map.edit_status = Map.EDITORS
|
||||
map.edit_status = Map.COLLABORATORS
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
page = login(user)
|
||||
|
@ -241,3 +241,19 @@ def test_can_delete_datalayer(live_server, map, login, datalayer):
|
|||
expect(markers).to_have_count(0)
|
||||
# FIXME does not work, resolve to 1 element, even if this command is empty:
|
||||
expect(layers).to_have_count(0)
|
||||
|
||||
|
||||
def test_can_set_group(map, live_server, login, group):
|
||||
map.owner.groups.add(group)
|
||||
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))
|
||||
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
|
||||
|
|
|
@ -101,22 +101,33 @@ def test_should_remove_old_versions_on_save(map, settings):
|
|||
|
||||
|
||||
def test_anonymous_cannot_edit_in_editors_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.EDITORS
|
||||
datalayer.edit_status = DataLayer.COLLABORATORS
|
||||
datalayer.save()
|
||||
assert not datalayer.can_edit()
|
||||
|
||||
|
||||
def test_owner_can_edit_in_editors_mode(datalayer, user):
|
||||
datalayer.edit_status = DataLayer.EDITORS
|
||||
datalayer.edit_status = DataLayer.COLLABORATORS
|
||||
datalayer.save()
|
||||
assert datalayer.can_edit(datalayer.map.owner)
|
||||
|
||||
|
||||
def test_editor_can_edit_in_editors_mode(datalayer, user):
|
||||
def test_editor_can_edit_in_collaborators_mode(datalayer, user):
|
||||
map = datalayer.map
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
datalayer.edit_status = DataLayer.EDITORS
|
||||
datalayer.edit_status = DataLayer.COLLABORATORS
|
||||
datalayer.save()
|
||||
assert datalayer.can_edit(user)
|
||||
|
||||
|
||||
def test_group_members_can_edit_in_collaborators_mode(datalayer, user, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
map = datalayer.map
|
||||
map.group = group
|
||||
map.save()
|
||||
datalayer.edit_status = DataLayer.COLLABORATORS
|
||||
datalayer.save()
|
||||
assert datalayer.can_edit(user)
|
||||
|
||||
|
@ -170,6 +181,20 @@ 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
|
||||
):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
user.groups.add(group)
|
||||
group.save()
|
||||
map = datalayer.map
|
||||
map.group = group
|
||||
map.edit_status = Map.OWNER
|
||||
map.save()
|
||||
assert not datalayer.can_edit(user)
|
||||
|
||||
|
||||
def test_anonymous_cannot_edit_in_inherit_mode_and_map_in_owner_mode(datalayer):
|
||||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
|
@ -183,7 +208,7 @@ def test_owner_can_edit_in_inherit_mode_and_map_in_editors_mode(datalayer):
|
|||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.edit_status = Map.EDITORS
|
||||
map.edit_status = Map.COLLABORATORS
|
||||
map.save()
|
||||
assert datalayer.can_edit(map.owner)
|
||||
|
||||
|
@ -193,7 +218,7 @@ def test_editors_can_edit_in_inherit_mode_and_map_in_editors_mode(datalayer, use
|
|||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.editors.add(user)
|
||||
map.edit_status = Map.EDITORS
|
||||
map.edit_status = Map.COLLABORATORS
|
||||
map.save()
|
||||
assert datalayer.can_edit(user)
|
||||
|
||||
|
@ -202,7 +227,7 @@ def test_anonymous_cannot_edit_in_inherit_mode_and_map_in_editors_mode(datalayer
|
|||
datalayer.edit_status = DataLayer.INHERIT
|
||||
datalayer.save()
|
||||
map = datalayer.map
|
||||
map.edit_status = Map.EDITORS
|
||||
map.edit_status = Map.COLLABORATORS
|
||||
map.save()
|
||||
assert not datalayer.can_edit()
|
||||
|
||||
|
|
|
@ -379,7 +379,7 @@ def test_owner_can_edit_in_owner_mode(datalayer, client, map, post_data):
|
|||
|
||||
def test_editor_can_edit_in_editors_mode(datalayer, client, map, post_data):
|
||||
client.login(username=map.owner.username, password="123123")
|
||||
datalayer.edit_status = DataLayer.EDITORS
|
||||
datalayer.edit_status = DataLayer.COLLABORATORS
|
||||
datalayer.save()
|
||||
url = reverse("datalayer_update", args=(map.pk, datalayer.pk))
|
||||
name = "new name"
|
||||
|
|
|
@ -43,13 +43,31 @@ def test_editors_cannot_edit_if_status_owner(map, user):
|
|||
assert not map.can_edit(user)
|
||||
|
||||
|
||||
def test_editors_can_edit_if_status_editors(map, user):
|
||||
map.edit_status = map.EDITORS
|
||||
def test_editors_can_edit_if_status_collaborators(map, user):
|
||||
map.edit_status = map.COLLABORATORS
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
assert map.can_edit(user)
|
||||
|
||||
|
||||
def test_group_members_cannot_edit_if_status_owner(map, user, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
map.edit_status = map.OWNER
|
||||
map.group = group
|
||||
map.save()
|
||||
assert not map.can_edit(user)
|
||||
|
||||
|
||||
def test_group_members_can_edit_if_status_collaborators(map, user, group):
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
map.edit_status = map.COLLABORATORS
|
||||
map.group = group
|
||||
map.save()
|
||||
assert map.can_edit(user)
|
||||
|
||||
|
||||
def test_logged_in_user_should_be_allowed_for_anonymous_map_with_anonymous_edit_status(
|
||||
map, user, rf
|
||||
): # noqa
|
||||
|
@ -87,6 +105,14 @@ 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
|
||||
map.save()
|
||||
clone = map.clone()
|
||||
assert map.pk != clone.pk
|
||||
assert clone.group == group
|
||||
|
||||
|
||||
def test_clone_should_update_owner_if_passed(map, user):
|
||||
clone = map.clone(owner=user)
|
||||
assert map.pk != clone.pk
|
||||
|
@ -119,9 +145,9 @@ def test_publicmanager_should_get_only_public_maps(map, user, licence):
|
|||
def test_can_change_default_edit_status(user, settings):
|
||||
map = MapFactory(owner=user)
|
||||
assert map.edit_status == Map.OWNER
|
||||
settings.UMAP_DEFAULT_EDIT_STATUS = Map.EDITORS
|
||||
settings.UMAP_DEFAULT_EDIT_STATUS = Map.COLLABORATORS
|
||||
map = MapFactory(owner=user)
|
||||
assert map.edit_status == Map.EDITORS
|
||||
assert map.edit_status == Map.COLLABORATORS
|
||||
|
||||
|
||||
def test_can_change_default_share_status(user, settings):
|
||||
|
|
|
@ -210,7 +210,7 @@ def test_user_not_allowed_should_not_clone_map(client, map, user, settings):
|
|||
|
||||
def test_clone_should_set_cloner_as_owner(client, map, user):
|
||||
url = reverse("map_clone", kwargs={"map_id": map.pk})
|
||||
map.edit_status = map.EDITORS
|
||||
map.edit_status = map.COLLABORATORS
|
||||
map.editors.add(user)
|
||||
map.save()
|
||||
client.login(username=user.username, password="123123")
|
||||
|
@ -330,7 +330,7 @@ def test_only_owner_can_delete(client, map, user):
|
|||
|
||||
def test_map_editors_do_not_see_owner_change_input(client, map, user):
|
||||
map.editors.add(user)
|
||||
map.edit_status = map.EDITORS
|
||||
map.edit_status = map.COLLABORATORS
|
||||
map.save()
|
||||
url = reverse("map_update_permissions", kwargs={"map_id": map.pk})
|
||||
client.login(username=user.username, password="123123")
|
||||
|
|
|
@ -474,7 +474,7 @@ def test_websocket_token_returns_a_valid_token_when_authorized(client, user, map
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_websocket_token_is_generated_for_editors(client, user, user2, map):
|
||||
map.edit_status = Map.EDITORS
|
||||
map.edit_status = Map.COLLABORATORS
|
||||
map.editors.add(user2)
|
||||
map.save()
|
||||
|
||||
|
|
|
@ -459,14 +459,16 @@ def simple_json_response(**kwargs):
|
|||
class SessionMixin:
|
||||
def get_user_data(self):
|
||||
data = {}
|
||||
user = self.request.user
|
||||
if hasattr(self, "object"):
|
||||
data["is_owner"] = self.object.is_owner(self.request.user, self.request)
|
||||
if self.request.user.is_anonymous:
|
||||
data["is_owner"] = self.object.is_owner(user, self.request)
|
||||
if user.is_anonymous:
|
||||
return data
|
||||
return {
|
||||
"id": self.request.user.pk,
|
||||
"id": user.pk,
|
||||
"name": str(self.request.user),
|
||||
"url": reverse("user_dashboard"),
|
||||
"groups": [group.get_metadata() for group in user.groups.all()],
|
||||
**data,
|
||||
}
|
||||
|
||||
|
@ -605,6 +607,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 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