overlay edit permissions via access restrictions
This commit is contained in:
parent
e1b820ae89
commit
0e6faa0673
4 changed files with 75 additions and 30 deletions
|
@ -379,14 +379,14 @@ def create_editor_form(editor_model):
|
|||
'slug', 'name', 'title', 'title_plural', 'help_text', 'position_secret', 'icon', 'join_edges', 'todo',
|
||||
'up_separate', 'bssid', 'main_point', 'external_url', 'external_url_label', 'hub_import_type', 'walk',
|
||||
'ordering', 'category', 'width', 'groups', 'height', 'color', 'in_legend', 'priority', 'hierarchy', 'icon_name',
|
||||
'base_altitude', 'intermediate', 'waytype', 'access_restriction', 'default_height', 'door_height', 'outside',
|
||||
"identifyable", 'can_search', 'can_describe', 'geometry', 'single', 'altitude', 'level_index', 'short_label',
|
||||
'origin_space', 'target_space', 'data',
|
||||
'comment', 'slow_down_factor', 'groundaltitude', 'node_number', 'wifi_bssids', 'bluetooth_address', "group",
|
||||
'ibeacon_uuid', 'ibeacon_major', 'ibeacon_minor', 'uwb_address', 'extra_seconds', 'speed', 'can_report_missing',
|
||||
"can_report_mistake", 'description', 'speed_up', 'description_up', 'avoid_by_default', 'report_help_text',
|
||||
'enter_description', 'level_change_description', 'base_mapdata_accessible', 'label_settings', 'label_override',
|
||||
'min_zoom', 'max_zoom', 'font_size', 'members', 'allow_levels', 'allow_spaces', 'allow_areas', 'allow_pois',
|
||||
'base_altitude', 'intermediate', 'waytype', 'access_restriction', 'edit_access_restriction', 'default_height',
|
||||
'door_height', 'outside', 'identifyable', 'can_search', 'can_describe', 'geometry', 'single', 'altitude',
|
||||
'level_index', 'short_label', 'origin_space', 'target_space', 'data', 'comment', 'slow_down_factor',
|
||||
'groundaltitude', 'node_number', 'wifi_bssids', 'bluetooth_address', 'group', 'ibeacon_uuid', 'ibeacon_major',
|
||||
'ibeacon_minor', 'uwb_address', 'extra_seconds', 'speed', 'can_report_missing', 'can_report_mistake',
|
||||
'description', 'speed_up', 'description_up', 'avoid_by_default', 'report_help_text', 'enter_description',
|
||||
'level_change_description', 'base_mapdata_accessible', 'label_settings', 'label_override', 'min_zoom',
|
||||
'max_zoom', 'font_size', 'members', 'allow_levels', 'allow_spaces', 'allow_areas', 'allow_pois',
|
||||
'allow_dynamic_locations', 'left', 'top', 'right', 'bottom', 'import_tag', 'import_block_data',
|
||||
'import_block_geom', 'public', 'default', 'dark', 'high_contrast', 'funky', 'randomize_primary_color',
|
||||
'color_logo', 'color_css_initial', 'color_css_primary', 'color_css_secondary', 'color_css_tertiary',
|
||||
|
@ -396,9 +396,9 @@ def create_editor_form(editor_model):
|
|||
'color_background', 'color_wall_fill', 'color_wall_border', 'color_door_fill', 'color_ground_fill',
|
||||
'color_obstacles_default_fill', 'color_obstacles_default_border', 'stroke_color', 'stroke_width',
|
||||
'stroke_opacity', 'fill_color', 'fill_opacity', 'interactive', 'point_icon', 'extra_data', 'show_label',
|
||||
'show_geometry', 'show_label', 'show_geometry', 'default_geomtype', 'cluster_points',
|
||||
"load_group_display", "load_group_contribute",
|
||||
"altitude_quest",
|
||||
'show_geometry', 'show_label', 'show_geometry', 'default_geomtype', 'cluster_points', 'update_interval',
|
||||
'load_group_display', 'load_group_contribute',
|
||||
'altitude_quest',
|
||||
]
|
||||
field_names = [field.name for field in editor_model._meta.get_fields()
|
||||
if not field.one_to_many and not isinstance(field, ManyToManyRel)]
|
||||
|
|
|
@ -16,7 +16,8 @@ from django_pydantic_field import SchemaField
|
|||
from c3nav.editor.changes import ChangedObjectCollection, ChangeProblems
|
||||
from c3nav.editor.operations import DatabaseOperationCollection
|
||||
from c3nav.editor.tasks import send_changeset_proposed_notification
|
||||
from c3nav.mapdata.models import LocationSlug, MapUpdate
|
||||
from c3nav.mapdata.models import LocationSlug, MapUpdate, DataOverlayFeature, DataOverlay
|
||||
from c3nav.mapdata.models.access import AccessPermission
|
||||
from c3nav.mapdata.models.locations import LocationRedirect
|
||||
|
||||
|
||||
|
@ -59,12 +60,6 @@ class ChangeSet(models.Model):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.created_objects = {}
|
||||
self.updated_existing = {}
|
||||
self.deleted_existing = {}
|
||||
self.m2m_added = {}
|
||||
self.m2m_removed = {}
|
||||
|
||||
self._object_changed = False
|
||||
self._request = None
|
||||
self._original_state = self.state
|
||||
|
@ -179,40 +174,67 @@ class ChangeSet(models.Model):
|
|||
return (self.can_edit(request) and self.can_review(request)
|
||||
and not self.proposed and self.changes and not self.problems.any)
|
||||
|
||||
def has_space_access_on_all_objects(self, request, force=False):
|
||||
def has_edit_access_on_all_objects(self, request, force=False):
|
||||
# todo: reimplement this
|
||||
if not request.user.is_authenticated:
|
||||
return False
|
||||
|
||||
try:
|
||||
request._has_space_access_on_all_objects_cache
|
||||
request._has_edit_access_on_all_objects_cache
|
||||
except AttributeError:
|
||||
request._has_space_access_on_all_objects_cache = {}
|
||||
request._has_edit_access_on_all_objects_cache = {}
|
||||
|
||||
can_edit_spaces = {space_id for space_id, can_edit in request.user_space_accesses.items() if can_edit}
|
||||
|
||||
if not can_edit_spaces:
|
||||
return False
|
||||
|
||||
if not force:
|
||||
try:
|
||||
return request._has_space_access_on_all_objects_cache[self.pk]
|
||||
return request._has_edit_access_on_all_objects_cache[self.pk]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for model in self.changed_objects.keys():
|
||||
for modelname in self.changes.objects.keys():
|
||||
model = apps.get_model('mapdata', modelname)
|
||||
if issubclass(model, LocationRedirect):
|
||||
continue
|
||||
if issubclass(model, (DataOverlay, DataOverlayFeature)):
|
||||
continue
|
||||
try:
|
||||
model._meta.get_field('space')
|
||||
except FieldDoesNotExist:
|
||||
return False
|
||||
|
||||
permissions = AccessPermission.get_for_request(request)
|
||||
|
||||
overlay_ids = []
|
||||
|
||||
|
||||
result = True
|
||||
for model, objects in self.get_objects(many=False).items():
|
||||
for model_name, objects in self.changes.objects.items():
|
||||
model = apps.get_model('mapdata', model_name)
|
||||
if issubclass(model, (LocationRedirect, LocationSlug)):
|
||||
continue
|
||||
|
||||
if issubclass(model, DataOverlay):
|
||||
ids = [obj.obj.id for obj in objects.values()]
|
||||
restriction_ids = set(model.objects.filter(pk__in=ids).values_list('edit_access_restriction_id', flat=True))
|
||||
if None in restriction_ids or (restriction_ids - permissions - {None}):
|
||||
result = False
|
||||
break
|
||||
|
||||
continue
|
||||
|
||||
if issubclass(model, DataOverlayFeature):
|
||||
ids = [obj.obj.id for obj in objects.values()]
|
||||
|
||||
restriction_ids = set(model.objects
|
||||
.filter(pk__in=ids)
|
||||
.values_list('overlay__edit_access_restriction_id', flat=True))
|
||||
if None in restriction_ids or (restriction_ids - permissions - {None}):
|
||||
result = False
|
||||
break
|
||||
|
||||
continue
|
||||
|
||||
try:
|
||||
model._meta.get_field('space')
|
||||
except FieldDoesNotExist:
|
||||
|
@ -250,7 +272,7 @@ class ChangeSet(models.Model):
|
|||
if not result:
|
||||
break
|
||||
|
||||
request._has_space_access_on_all_objects_cache[self.pk] = result
|
||||
request._has_edit_access_on_all_objects_cache[self.pk] = result
|
||||
return result
|
||||
|
||||
def can_review(self, request):
|
||||
|
@ -258,7 +280,7 @@ class ChangeSet(models.Model):
|
|||
return False
|
||||
if request.user_permissions.review_changesets:
|
||||
return True
|
||||
return self.has_space_access_on_all_objects(request)
|
||||
return self.has_edit_access_on_all_objects(request)
|
||||
|
||||
@classmethod
|
||||
def can_direct_edit(cls, request):
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 5.1.3 on 2024-12-26 18:56
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mapdata', '0129_dataoverlay_cluster_points'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dataoverlay',
|
||||
name='edit_access_restriction',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='edit_access_restrictions', to='mapdata.accessrestriction', verbose_name='Editor Access Restriction'),
|
||||
),
|
||||
]
|
|
@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from django_pydantic_field import SchemaField
|
||||
|
||||
from c3nav.mapdata.fields import GeometryField
|
||||
from c3nav.mapdata.models.access import AccessRestrictionMixin
|
||||
from c3nav.mapdata.models.access import AccessRestrictionMixin, AccessRestriction
|
||||
from c3nav.mapdata.models.base import TitledMixin
|
||||
from c3nav.mapdata.models.geometry.level import LevelGeometryMixin
|
||||
from c3nav.mapdata.utils.geometry import smart_mapping
|
||||
|
@ -34,6 +34,10 @@ class DataOverlay(TitledMixin, AccessRestrictionMixin, models.Model):
|
|||
pull_headers: dict[str, str] = SchemaField(schema=dict[str, str], null=True,
|
||||
verbose_name=_('headers for pull http request (JSON object)'))
|
||||
pull_interval = models.DurationField(blank=True, null=True, verbose_name=_('pull interval'))
|
||||
edit_access_restriction = models.ForeignKey(AccessRestriction, null=True, blank=True,
|
||||
related_name='edit_access_restrictions',
|
||||
verbose_name=_('Editor Access Restriction'),
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Data Overlay')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue