From 67328c69d4e560242f427bedcf95f8a3cca7bc26 Mon Sep 17 00:00:00 2001 From: Walter Lorenzetti Date: Tue, 28 Nov 2023 07:38:15 +0100 Subject: [PATCH 1/2] :sparkles: Multilayer editing capability activation (#669) * Continue activation editing multilayer. * Add 'scale' field. * Activation/deactivation of multi-layers editing capability * Add 'load_project_layers_actions' signal workflow. * Remove old print statement. * Logging fields management for editing multilayer activation. * Add logging user fields to multi layer activation workflow. * Added tests. * Typo * Add form warning message. * Update core css. * Update template project layers list --------- Co-authored-by: wlorenzetti --- g3w-admin/core/signals.py | 2 + g3w-admin/core/static/css/g3wadmin.css | 14 + .../core/static/dist/css/g3wadmin.min.css | 2 +- g3w-admin/core/templatetags/g3wadmin_tags.py | 19 +- g3w-admin/editing/forms.py | 143 ++++++-- g3w-admin/editing/receivers.py | 28 +- g3w-admin/editing/static/editing/js/widget.js | 142 +++++++- .../editing/editing_layer_active_form.html | 8 + .../editing/project_layers_action.html | 13 + g3w-admin/editing/tests/test_api.py | 2 - g3w-admin/editing/tests/test_views.py | 326 ++++++++++++++++++ g3w-admin/editing/urls.py | 6 + g3w-admin/editing/views.py | 198 ++++++++++- g3w-admin/qdjango/api/layers/serializers.py | 11 +- g3w-admin/qdjango/api/layers/views.py | 12 + g3w-admin/qdjango/apiurls.py | 8 +- .../templates/qdjango/layers_list.html | 16 +- 17 files changed, 889 insertions(+), 61 deletions(-) create mode 100644 g3w-admin/editing/templates/editing/project_layers_action.html diff --git a/g3w-admin/core/signals.py b/g3w-admin/core/signals.py index a046ddf50..3ba29cf4d 100644 --- a/g3w-admin/core/signals.py +++ b/g3w-admin/core/signals.py @@ -82,5 +82,7 @@ # signal send for execute model searches. execute_search_on_models = django.dispatch.Signal(providing_args=['request', 'search_text']) +# signal to load actions for project layers pages +load_project_layers_actions = django.dispatch.Signal(providing_args=["app_name", "project"]) diff --git a/g3w-admin/core/static/css/g3wadmin.css b/g3w-admin/core/static/css/g3wadmin.css index 2d214c3fc..e6f422430 100644 --- a/g3w-admin/core/static/css/g3wadmin.css +++ b/g3w-admin/core/static/css/g3wadmin.css @@ -251,4 +251,18 @@ a.layer-action { .recaptcha-registration { padding-left: 11.5px; +} + +.project_layers_action-content > div.row { + font-size: 16px; + margin: -10px; + margin-bottom: 20px; + margin-top: 20px; +} + +.project_layers_action-content > div.row > div:first-child { + text-align: right; +} +.project_layers_action-content > div.row > div:last-child { + font-size: 22px; } \ No newline at end of file diff --git a/g3w-admin/core/static/dist/css/g3wadmin.min.css b/g3w-admin/core/static/dist/css/g3wadmin.min.css index 3225d5b3f..aaed8aa1a 100644 --- a/g3w-admin/core/static/dist/css/g3wadmin.min.css +++ b/g3w-admin/core/static/dist/css/g3wadmin.min.css @@ -1 +1 @@ -.box-footer .description-block .icon{font-size:30px}.box-footer .projects-group{text-align:left}table span.icon{font-size:22px}table span.iconmap{font-size:30px}.icon .ion-trash-b,.qq-upload-cancel .ion-close,.qq-upload-delete .ion-trash-b{color:#bf2d2d}.icon .ion-trash-b:hover{color:#f28a7e}.icon .ion-android-upload{color:#605ca8}.widget-group-add{height:192px;text-align:center;font-size:136px;background-color:rgba(255,255,255,.7)}.widget-group-add a{color:rgba(0,0,0,.15)}.widget-group-title{background-color:rgba(0,0,0,.5)}.qq-upload-drop-area,.qq-upload-extra-drop-area{background-color:#ecf0f5;border:2px dotted gray}.qq-upload-list li{background-color:transparent}.qq-upload-list li.qq-upload-success{background-color:transparent;color:inherit}.qq-upload-list li.qq-upload-fail{background-color:transparent;color:red}.qq-upload-cancel,.qq-upload-continue,.qq-upload-delete,.qq-upload-pause,.qq-upload-retry,.qq-upload-size{font-size:18px;font-weight:400}.qq-upload-cancel,.qq-upload-delete{color:#bf2d2d}.qq-upload-file{font-size:12px}.qq-upload-button-selector{width:100%;height:120px;border:2px dashed #3c8dbc;padding-top:50px;color:#3c8dbc;text-align:center}.qq-upload-button-selector:hover{background-color:#ecf0f5}.select2{color:#000}.modalMap{height:400px}h5 small{color:#fff}.sort-highlight-group{background:#f4f4f4;border:1px dashed #ddd;margin-bottom:10px;height:192px}.sort-highlight-project{background:#ced4da;height:112px}.widget-user .widget-user-username{font-weight:400}.acl-box .help-block{color:#fff}.color-orange{color:#ff4500}.color-orange:hover{color:#ff4500;opacity:80%}.color-red{color:#bf2d2d}.color-red:hover{color:#bf2d2d;opacity:80%}.label-action-layer{position:absolute;top:-9px;right:1px;text-align:center;font-size:11px;padding:2px 3px;line-height:.9}#qdjango_projects_list table a.project-thumb:hover img{border:2px solid #ff4500}.search-admin-input{width:245px;padding-right:24px;border:none;border-bottom:2px solid #fff;color:#fff;background:0 0;font-size:20px}.search-admin-input::placeholder{color:#fff}.search-admin-input:focus{border-color:#fff}.result-search-item{margin-bottom:10px;margin-left:10px;margin-right:10px;padding:5px}.result-search-item-odd{background:#fff}.result-search-item-odd{background:#d8dee9}.result-search-item-links{font-size:150%}div.vector-download li{list-style:none;box-sizing:border-box;display:list-item;padding:10px;padding-bottom:6px;padding-top:6px}div.vector-download>ul.menu{padding:0}.group-abstract-action,.project-abstract-action{margin-left:-10px}input[type=file]::file-selector-button{margin-right:20px;border:none;background:#00c0ef;padding:10px 20px;border-radius:2px;color:#fff;cursor:pointer;transition:background .2s ease-in-out}input[type=file]::file-selector-button:hover{background:#00acd6}.translate{border:1px dashed #069}.translate+div.note-editor{border:1px dashed #069}.translatable_fields{display:inline-block;width:40px;height:20px}a.layer-action{cursor:pointer}.register-box{width:720px;margin:7% auto}.recaptcha{padding-left:9px}.recaptcha-registration{padding-left:11.5px}.dashboard-box-footer-icon{font-size:24px}.dashboard-box-footer-text{font-size:14px}.pholder-item{background:#fff;border:1px solid;border-color:#e5e6e9 #dfe0e4 #d0d1d5;border-radius:3px;padding:12px;margin:0 auto}@keyframes placeHolderShimmer{0%{background-position:-468px 0}100%{background-position:468px 0}}.animated-background{animation-duration:1s;animation-fill-mode:forwards;animation-iteration-count:infinite;animation-name:placeHolderShimmer;animation-timing-function:linear;background:#f6f7f8;background:linear-gradient(to right,#eee 8%,#ddd 18%,#eee 33%);background-size:800px 104px;height:96px;position:relative}.background-masker{background:#fff;position:absolute}.background-masker.header-bottom,.background-masker.header-top,.background-masker.subheader-bottom{top:0;left:40px;right:0;height:10px}.background-masker.header-left,.background-masker.header-right,.background-masker.subheader-left,.background-masker.subheader-right{top:10px;left:40px;height:8px;width:10px}.background-masker.header-bottom{top:18px;height:6px}.background-masker.subheader-left,.background-masker.subheader-right{top:24px;height:6px}.background-masker.header-right,.background-masker.subheader-right{width:auto;left:300px;right:0}.background-masker.subheader-right{left:230px}.background-masker.subheader-bottom{top:30px;height:10px}.background-masker.content-first-end,.background-masker.content-second-end,.background-masker.content-second-line,.background-masker.content-third-end,.background-masker.content-third-line,.background-masker.content-top{top:40px;left:0;right:0;height:6px}.background-masker.content-top{height:20px}.background-masker.content-first-end,.background-masker.content-second-end,.background-masker.content-third-end{width:auto;left:380px;right:0;top:60px;height:8px}.background-masker.content-second-line{top:68px}.background-masker.content-second-end{left:420px;top:74px}.background-masker.content-third-line{top:82px}.background-masker.content-third-end{left:300px;top:88px} \ No newline at end of file +.box-footer .description-block .icon{font-size:30px}.box-footer .projects-group{text-align:left}table span.icon{font-size:22px}table span.iconmap{font-size:30px}.icon .ion-trash-b,.qq-upload-cancel .ion-close,.qq-upload-delete .ion-trash-b{color:#bf2d2d}.icon .ion-trash-b:hover{color:#f28a7e}.icon .ion-android-upload{color:#605ca8}.widget-group-add{height:192px;text-align:center;font-size:136px;background-color:rgba(255,255,255,.7)}.widget-group-add a{color:rgba(0,0,0,.15)}.widget-group-title{background-color:rgba(0,0,0,.5)}.qq-upload-drop-area,.qq-upload-extra-drop-area{background-color:#ecf0f5;border:2px dotted gray}.qq-upload-list li{background-color:transparent}.qq-upload-list li.qq-upload-success{background-color:transparent;color:inherit}.qq-upload-list li.qq-upload-fail{background-color:transparent;color:red}.qq-upload-cancel,.qq-upload-continue,.qq-upload-delete,.qq-upload-pause,.qq-upload-retry,.qq-upload-size{font-size:18px;font-weight:400}.qq-upload-cancel,.qq-upload-delete{color:#bf2d2d}.qq-upload-file{font-size:12px}.qq-upload-button-selector{width:100%;height:120px;border:2px dashed #3c8dbc;padding-top:50px;color:#3c8dbc;text-align:center}.qq-upload-button-selector:hover{background-color:#ecf0f5}.select2{color:#000}.modalMap{height:400px}h5 small{color:#fff}.sort-highlight-group{background:#f4f4f4;border:1px dashed #ddd;margin-bottom:10px;height:192px}.sort-highlight-project{background:#ced4da;height:112px}.widget-user .widget-user-username{font-weight:400}.acl-box .help-block{color:#fff}.color-orange{color:#ff4500}.color-orange:hover{color:#ff4500;opacity:80%}.color-red{color:#bf2d2d}.color-red:hover{color:#bf2d2d;opacity:80%}.label-action-layer{position:absolute;top:-9px;right:1px;text-align:center;font-size:11px;padding:2px 3px;line-height:.9}#qdjango_projects_list table a.project-thumb:hover img{border:2px solid #ff4500}.search-admin-input{width:245px;padding-right:24px;border:none;border-bottom:2px solid #fff;color:#fff;background:0 0;font-size:20px}.search-admin-input::placeholder{color:#fff}.search-admin-input:focus{border-color:#fff}.result-search-item{margin-bottom:10px;margin-left:10px;margin-right:10px;padding:5px}.result-search-item-odd{background:#fff}.result-search-item-odd{background:#d8dee9}.result-search-item-links{font-size:150%}div.vector-download li{list-style:none;box-sizing:border-box;display:list-item;padding:10px;padding-bottom:6px;padding-top:6px}div.vector-download>ul.menu{padding:0}.group-abstract-action,.project-abstract-action{margin-left:-10px}input[type=file]::file-selector-button{margin-right:20px;border:none;background:#00c0ef;padding:10px 20px;border-radius:2px;color:#fff;cursor:pointer;transition:background .2s ease-in-out}input[type=file]::file-selector-button:hover{background:#00acd6}.translate{border:1px dashed #069}.translate+div.note-editor{border:1px dashed #069}.translatable_fields{display:inline-block;width:40px;height:20px}a.layer-action{cursor:pointer}.register-box{width:720px;margin:7% auto}.recaptcha{padding-left:9px}.recaptcha-registration{padding-left:11.5px}.project_layers_action-content>div.row{font-size:16px;margin:-10px;margin-bottom:20px;margin-top:20px}.project_layers_action-content>div.row>div:first-child{text-align:right}.project_layers_action-content>div.row>div:last-child{font-size:22px}.dashboard-box-footer-icon{font-size:24px}.dashboard-box-footer-text{font-size:14px}.pholder-item{background:#fff;border:1px solid;border-color:#e5e6e9 #dfe0e4 #d0d1d5;border-radius:3px;padding:12px;margin:0 auto}@keyframes placeHolderShimmer{0%{background-position:-468px 0}100%{background-position:468px 0}}.animated-background{animation-duration:1s;animation-fill-mode:forwards;animation-iteration-count:infinite;animation-name:placeHolderShimmer;animation-timing-function:linear;background:#f6f7f8;background:linear-gradient(to right,#eee 8%,#ddd 18%,#eee 33%);background-size:800px 104px;height:96px;position:relative}.background-masker{background:#fff;position:absolute}.background-masker.header-bottom,.background-masker.header-top,.background-masker.subheader-bottom{top:0;left:40px;right:0;height:10px}.background-masker.header-left,.background-masker.header-right,.background-masker.subheader-left,.background-masker.subheader-right{top:10px;left:40px;height:8px;width:10px}.background-masker.header-bottom{top:18px;height:6px}.background-masker.subheader-left,.background-masker.subheader-right{top:24px;height:6px}.background-masker.header-right,.background-masker.subheader-right{width:auto;left:300px;right:0}.background-masker.subheader-right{left:230px}.background-masker.subheader-bottom{top:30px;height:10px}.background-masker.content-first-end,.background-masker.content-second-end,.background-masker.content-second-line,.background-masker.content-third-end,.background-masker.content-third-line,.background-masker.content-top{top:40px;left:0;right:0;height:6px}.background-masker.content-top{height:20px}.background-masker.content-first-end,.background-masker.content-second-end,.background-masker.content-third-end{width:auto;left:380px;right:0;top:60px;height:8px}.background-masker.content-second-line{top:68px}.background-masker.content-second-end{left:420px;top:74px}.background-masker.content-third-line{top:82px}.background-masker.content-third-end{left:300px;top:88px} \ No newline at end of file diff --git a/g3w-admin/core/templatetags/g3wadmin_tags.py b/g3w-admin/core/templatetags/g3wadmin_tags.py index c953e436c..755170160 100644 --- a/g3w-admin/core/templatetags/g3wadmin_tags.py +++ b/g3w-admin/core/templatetags/g3wadmin_tags.py @@ -3,7 +3,11 @@ from django import template from django.apps import apps from django.conf.urls.static import static -from core.signals import load_project_widgets, load_layer_actions +from core.signals import ( + load_project_widgets, + load_layer_actions, + load_project_layers_actions +) from core.models import GroupProjectPanoramic @@ -106,6 +110,19 @@ def g3wadmin_layer_actions(layer, app_name, user): return template_actions +@register.simple_tag() +def g3wadmin_project_layers_actions(project, app_name, user): + """ + Template tag to get actions from load_project_layers_actions signal + """ + actions = load_project_layers_actions.send(user, project=project, app_name=app_name) + + template_actions = [] + for action in actions: + if actions and action[1]: + template_actions.append(action[1]) + + return template_actions @register.filter def lookup(d, key): diff --git a/g3w-admin/editing/forms.py b/g3w-admin/editing/forms.py index 63c7fb47b..fd062dcb2 100644 --- a/g3w-admin/editing/forms.py +++ b/g3w-admin/editing/forms.py @@ -12,20 +12,61 @@ from usersmanage.utils import get_users_for_object, get_groups_for_object, userHasGroups, get_viewers_for_object from usersmanage.forms import label_users from usersmanage.configs import * +from qdjango.models import Layer from qgis.PyQt.QtCore import QVariant +class ActiveEditingMixin(object): -class ActiveEditingLayerForm(G3WRequestFormMixin, G3WProjectFormMixin, forms.Form): + def _set_viewer_users_choices(self): + """ + Set choices for viewer_users select by permission on project and by user main role + """ + + with_anonymous = getattr(settings, 'EDITING_ANONYMOUS', False) + viewers = get_viewers_for_object(self.project, self.request.user, 'view_project', with_anonymous=with_anonymous) + + # get Editor Level 1 and Editor level 2 to clear from list + editor_pk = self.project.editor.pk if self.project.editor else None + editor2_pk = self.project.editor2.pk if self.project.editor2 else None + + self.fields['viewer_users'].choices = [(v.pk, label_users(v)) for v in viewers + if v.pk not in (editor_pk, editor2_pk)] + + def _set_viewer_user_groups_choices(self): + """ + Set choices for viewer_user_groups select by permission on project and by user main role + """ + + # add user_groups_viewer choices + user_groups_viewers = get_groups_for_object(self.project, 'view_project', grouprole='viewer') + + # for Editor level filter by his groups + if userHasGroups(self.request.user, [G3W_EDITOR1]): + editor1_user_gorups_viewers = get_objects_for_user(self.request.user, 'auth.change_group', + AuthGroup).order_by('name').filter( + grouprole__role='viewer') + + user_groups_viewers = list(set(user_groups_viewers).intersection(set(editor1_user_gorups_viewers))) + + self.fields['user_groups_viewer'].choices = [(v.pk, v) for v in user_groups_viewers] + + + +class ActiveEditingLayerForm(ActiveEditingMixin, G3WRequestFormMixin, G3WProjectFormMixin, forms.Form): + """ + Form for sinlge layer editing mode activation + """ active = forms.BooleanField(label=_('Active'), required=False) scale = forms.IntegerField(label=_('Scale'), required=False, help_text=_('Scale after that editing mode is active')) viewer_users = forms.MultipleChoiceField(choices=[], label=_('Viewers'), required=False, help_text=_('Select user with viewer role can do editing on layer')) user_groups_viewer = forms.MultipleChoiceField( - choices=[], required=False, help_text=_('Select VIEWER groups can do editing on layer'), + choices=[], required=False, help_text=_('Select VIEWER groups can do editing on layer'), label=_('User viewer groups') ) + add_user_field = forms.ChoiceField(choices=[], label=_('User adding data field'), required=False, help_text=_('Optional: select layer field to store ' 'username that entered the data. ' @@ -59,7 +100,7 @@ def __init__(self, *args, **kwargs): self.helper.form_tag = False layout_args = [ - HTML(_('Check on uncheck to attive/deactive editing layer capabilities:')), + HTML(_('Check on uncheck to active/deactive editing layer capabilities:')), 'active' ] @@ -90,37 +131,6 @@ def clean_edit_user_field(self): return self.cleaned_data['edit_user_field'] - def _set_viewer_users_choices(self): - """ - Set choices for viewer_users select by permission on project and by user main role - """ - - with_anonymous = getattr(settings, 'EDITING_ANONYMOUS', False) - viewers = get_viewers_for_object(self.project, self.request.user, 'view_project', with_anonymous=with_anonymous) - - # get Editor Level 1 and Editor level 2 to clear from list - editor_pk = self.project.editor.pk if self.project.editor else None - editor2_pk = self.project.editor2.pk if self.project.editor2 else None - - self.fields['viewer_users'].choices = [(v.pk, label_users(v)) for v in viewers - if v.pk not in (editor_pk, editor2_pk)] - - def _set_viewer_user_groups_choices(self): - """ - Set choices for viewer_user_groups select by permission on project and by user main role - """ - - # add user_groups_viewer choices - user_groups_viewers = get_groups_for_object(self.project, 'view_project', grouprole='viewer') - - # for Editor level filter by his groups - if userHasGroups(self.request.user, [G3W_EDITOR1]): - editor1_user_gorups_viewers = get_objects_for_user(self.request.user, 'auth.change_group', - AuthGroup).order_by('name').filter(grouprole__role='viewer') - - user_groups_viewers = list(set(user_groups_viewers).intersection(set(editor1_user_gorups_viewers))) - - self.fields['user_groups_viewer'].choices = [(v.pk, v) for v in user_groups_viewers] def _set_add_edit_user_field_choices(self): """ @@ -137,3 +147,68 @@ def _set_add_edit_user_field_choices(self): self.fields['edit_user_field'].choices = \ self.fields['add_user_field'].choices = [(None, '--------')] + [(f, f) for f in touse] + + +class ActiveEditingMultiLayerForm(ActiveEditingMixin, G3WRequestFormMixin, G3WProjectFormMixin, forms.Form): + """ + Form for multi layer editing mode activation + """ + + layers = forms.MultipleChoiceField(choices=[], label=_('Layers'), required=True, + help_text=_('Select layers on which to activate editing')) + + scale = forms.IntegerField(label=_('Scale'), required=False, help_text=_('Scale after that editing mode is active')) + + active = forms.BooleanField(label=_('Active'), required=False) + viewer_users = forms.MultipleChoiceField(choices=[], label=_('Viewers'), required=False, + help_text=_('Select user with viewer role can do editing on layer')) + user_groups_viewer = forms.MultipleChoiceField( + choices=[], required=False, help_text=_('Select VIEWER groups can do editing on layer'), + label=_('User viewer groups') + ) + + def __init__(self, *args, **kwargs): + + super().__init__(*args, **kwargs) + + # set choices + self._set_layers_choices() + self._set_viewer_users_choices() + self._set_viewer_user_groups_choices() + + self.helper = FormHelper(self) + self.helper.form_tag = False + + layout_args = [ + HTML(_('Check on uncheck to attive/deactive editing layers capabilities:')), + 'active' + ] + + layout_args += [ + Field('layers', css_class='select2', style="width:100%;"), + Div(css_class='add_edit_user_fields'), + 'scale', + HTML(_('Select viewers with \'view permission\' on project that can edit layer:')), + Field('viewer_users', css_class='select2', style="width:100%;"), + Div(css_class='users_atomic_capabilities'), + Field('user_groups_viewer', css_class='select2', style="width:100%;"), + Div(css_class='user_groups_atomic_capabilities') + ] + + self.helper.layout = Layout(*layout_args) + + + def _set_layers_choices(self): + """ + Set choices for layers + """ + + project_layers = self.project.layer_set.filter(layer_type__in=( + Layer.TYPES.postgres, + Layer.TYPES.spatialite, + Layer.TYPES.ogr, + Layer.TYPES.mssql, + Layer.TYPES.oracle + )) + self.fields['layers'].choices = [(l.pk, l.name) for l in project_layers] + diff --git a/g3w-admin/editing/receivers.py b/g3w-admin/editing/receivers.py index 8d644b909..b2b8a111e 100644 --- a/g3w-admin/editing/receivers.py +++ b/g3w-admin/editing/receivers.py @@ -12,8 +12,17 @@ from django.contrib.auth.signals import user_logged_out from django.template import loader from django.db.models.signals import pre_delete -from core.signals import load_layer_actions, initconfig_plugin_start, after_serialized_project_layer, \ - pre_save_maplayer, post_save_maplayer, pre_delete_maplayer, load_js_modules, before_return_vector_data_layer +from core.signals import ( + load_layer_actions, + initconfig_plugin_start, + after_serialized_project_layer, + pre_save_maplayer, + post_save_maplayer, + pre_delete_maplayer, + load_js_modules, + before_return_vector_data_layer, + load_project_layers_actions +) from core.utils.qgisapi import get_qgis_layer from qdjango.api.projects.serializers import QGIS_LAYER_TYPE_NO_GEOM from qdjango.vector import LayerVectorView, MODE_CONFIG @@ -78,6 +87,21 @@ def editing_layer_actions(sender, **kwargs): template = loader.get_template('editing/layer_action_blank.html') return template.render(kwargs) +@receiver(load_project_layers_actions) +def editing_project_layers_actions(sender, **kwargs): + """ + Return html actions editing for project layers list. + """ + + editing_button = getattr(settings, 'EDITING_SHOW_ACTIVE_BUTTON', True) + + if (sender.has_perm('change_project', kwargs['project']) and + kwargs['app_name'] == 'qdjango' + and editing_button): + template = loader.get_template('editing/project_layers_action.html') + return template.render(kwargs) + + @receiver(initconfig_plugin_start) def set_initconfig_value(sender, **kwargs): diff --git a/g3w-admin/editing/static/editing/js/widget.js b/g3w-admin/editing/static/editing/js/widget.js index 919c2abd4..65d562c5d 100644 --- a/g3w-admin/editing/static/editing/js/widget.js +++ b/g3w-admin/editing/static/editing/js/widget.js @@ -17,7 +17,8 @@ ga.Editing.widget = { this.selects = { 'user': $("#editing_layer_form").find('#id_viewer_users'), - 'group': $("#editing_layer_form").find('#id_user_groups_viewer') + 'group': $("#editing_layer_form").find('#id_user_groups_viewer'), + 'layer': $("#editing_layer_form").find('#id_layers') }; this.capas = { @@ -25,8 +26,12 @@ ga.Editing.widget = { 'group': $("#editing_layer_form").find('.user_groups_atomic_capabilities') }; + // For multilayer activation editing form + this.fields_add_edit_user = $("#editing_layer_form").find('.add_edit_user_fields'); + this.init_active_status(); this.init_atomic_capabilities(); + this.init_add_edit_user_fields(); }, /** @@ -34,7 +39,7 @@ ga.Editing.widget = { */ init_active_status: function () { $check = $("#editing_layer_form").find('#id_active'); - $select = $("#editing_layer_form").find('.select2, #id_scale'); + $select = $("#editing_layer_form").find('#id_scale, select:not(#id_layers)'); var check_active_status = function (event) { if ($check.prop('checked')) { @@ -66,6 +71,21 @@ ga.Editing.widget = { }); }, + /** + * Initialize for mutli layer editing activation form + * the add/edit user fields + * @param context + * @returns {(function(*): void)|*} + */ + init_add_edit_user_fields: function () { + + var that = this; + + that.selects['layer'].on('select2:select', that.add_edit_user_row()); + that.selects['layer'].on('select2:unselect', that.remove_add_edit_user_row()); + + }, + /** * Add row to capabilities table for users and user_groups. * @param context @@ -150,7 +170,100 @@ ga.Editing.widget = { that.capas[context].find('#row_' + data.id).remove(); } - } + }, + + /** + * Adds a new row for each layer selected in the layers field, + * in each row it allows the choice of the field to be used to save + * the user who created the new feature + * + * Not update only create form state + * @returns {(function(*): void)|*} + */ + add_edit_user_row: function () { + + var that = this; + return function (e) { + + try { + var data = e.params.data; + } catch { + var data = e; + } + + // check if table is rendered + var $table = that.fields_add_edit_user.find('table'); + + // build table row + var $row = ga.tpl.editing.userfields.choosingfields.tablerow({ + layer_name: data.text, + layer_id: data.id + }); + if ($table.length == 0) { + + var $table = ga.tpl.editing.userfields.choosingfields.table({ + layer_name_title: gettext('Layer'), + add_user_field_title: gettext('Add logging field'), + edit_user_field_title: gettext('Update logging field'), + body_table: $row + }); + + that.fields_add_edit_user.append($table); + + } else { + $table.find('tbody').append($row); + } + + that.fields_add_edit_user.find('select').select2({ + width: '100%', + }); + + + + $.ajax({ + method: "get", + url: ga.Editing.widget.layerinfobaseurl + data.id + "/", + success: function (res) { + var fields = res.database_columns; + + // For selects + var $add_field = $('#add_user_field_' + data.id); + var $edit_field = $('#edit_user_field_' + data.id); + _.each(fields, function (field) { + $add_field.append($('')); + $edit_field.append($('')); + }) + + }, + error: function (xhr, textStatus, errorMessage) { + }, + }); + + } + }, + + /** + * Remove row from table for choosing log user field + * @returns {(function(*): void)|*} + */ + remove_add_edit_user_row: function () { + + + var that = this; + return function (e) { + + try { + var data = e.params.data; + } catch { + var data = e; + } + + that.fields_add_edit_user.find('#row_' + data.id).remove(); + } + + }, + + }; _.extend(ga.tpl, { @@ -182,6 +295,29 @@ _.extend(ga.tpl, { \ ') } + }, + userfields: { + choosingfields: { + table: _.template('\ + \ + \ + \ + \ + \ + \ + \ + \ + <%= body_table %>\ +
<%= layer_name_title %><%= add_user_field_title %><%= edit_user_field_title %>
\ + '), + tablerow: _.template('\ + \ + <%= layer_name %>\ + \ + \ + \ + ') + } } } }); \ No newline at end of file diff --git a/g3w-admin/editing/templates/editing/editing_layer_active_form.html b/g3w-admin/editing/templates/editing/editing_layer_active_form.html index e078f6977..c6c19be29 100644 --- a/g3w-admin/editing/templates/editing/editing_layer_active_form.html +++ b/g3w-admin/editing/templates/editing/editing_layer_active_form.html @@ -8,6 +8,12 @@

{% trans 'Alert' %}!

{% trans 'Some errors on form. Check it and send again.' %} +
+
+

{% trans 'Important' %}!

+ {% trans 'The following settings will be applied to all the layers indicated and will overwrite the settings set at the single layer level' %} +
+
@@ -18,6 +24,8 @@

{% trans 'Alert' %}!

/* Set for widget initial atomic capabilities */ ga.Editing.widget.initial_atomic_capabilities = {{ initial_atomic_capabilities|safe }}; + ga.Editing.widget.multilayer = {{ multilayer|lower }}; + ga.Editing.widget.layerinfobaseurl= '/qdjango/api/info/layer/'; ga.Editing.widget.init(); \ No newline at end of file diff --git a/g3w-admin/editing/templates/editing/project_layers_action.html b/g3w-admin/editing/templates/editing/project_layers_action.html new file mode 100644 index 000000000..8acf0373b --- /dev/null +++ b/g3w-admin/editing/templates/editing/project_layers_action.html @@ -0,0 +1,13 @@ +{% load i18n %} +{% load static %} + + + + + + + + diff --git a/g3w-admin/editing/tests/test_api.py b/g3w-admin/editing/tests/test_api.py index fced001ef..a72149027 100644 --- a/g3w-admin/editing/tests/test_api.py +++ b/g3w-admin/editing/tests/test_api.py @@ -1659,8 +1659,6 @@ def test_layer_info_api(self): response = client.get(url_layer, {}, format='json') self.assertEqual(response.status_code, 200) - print(json.loads(response.content)['results']) - jcontent = json.loads(response.content)['results'] self.assertEqual(len(jcontent), 2) diff --git a/g3w-admin/editing/tests/test_views.py b/g3w-admin/editing/tests/test_views.py index 1ac88b2df..6c21cb1e8 100644 --- a/g3w-admin/editing/tests/test_views.py +++ b/g3w-admin/editing/tests/test_views.py @@ -230,6 +230,332 @@ def test_editing_layer_active(self): self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', cities_layer)) self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', cities_layer)) + def test_editing_multi_layer_active(self): + """ Testing view for activation of editing capability on multi layer """ + + # Get qdjango layer instances + cities_layer_id = 'cities_54d40b01_2af8_4b17_8495_c5833485536e' + cities_layer = self.editing_project.instance.layer_set.get( + qgs_layer_id=cities_layer_id) + + # Get layer + countries_layer_id = 'countries_63e59c18_a1d0_4e25_968a_ccbaecd11725' + countries_layer = self.editing_project.instance.layer_set.get( + qgs_layer_id=countries_layer_id) + + # Not activated + with self.assertRaises(ObjectDoesNotExist) as ex: + G3WEditingLayer.objects.get(layer_id=cities_layer.pk) + + with self.assertRaises(ObjectDoesNotExist) as ex: + G3WEditingLayer.objects.get(layer_id=countries_layer.pk) + + # TEST activate/deactivate editing + url = reverse('editing-multi-layer-active', args=[ + self.editing_project.instance.group.slug, + 'qdjango', + self.editing_project.instance.slug + ]) + + data = { + 'active': 'on', + 'layers': [cities_layer.pk, countries_layer.pk], + 'scale': 10000 + } + + self.assertTrue( + self.client.login( + username=self.test_user_admin1.username, + password=self.test_user_admin1.username + ) + ) + + res = self.client.post(url, data) + + # redirect on ok results + self.assertEqual(res.status_code, 302) + + editing_layers = G3WEditingLayer.objects.filter(layer_id__in=[cities_layer.pk, countries_layer.pk]) + self.assertTrue(len(editing_layers) == 2) + self.assertEqual(editing_layers[0].scale, 10000) + self.assertEqual(editing_layers[1].scale, 10000) + + # Check ATOMIC permissions + # ======================== + + # Give permissions to viewers and user_groups viewers + assign_perm('view_project', self.test_user3, self.editing_project.instance) + assign_perm('view_project', self.test_user_group1, self.editing_project.instance) + + data.update({ + 'viewer_users': [self.test_user3.pk], + 'user_groups_viewer': [self.test_user_group1.pk] + }) + + res = self.client.post(url, data) + + # redirect on ok results + self.assertEqual(res.status_code, 302) + + # Users + self.assertTrue(self.test_user3.has_perm('qdjango.change_layer', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertTrue(self.test_user3.has_perm('qdjango.change_layer', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.add_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', countries_layer)) + + # User_groups + self.assertTrue(self.test_user4.has_perm('qdjango.change_layer', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertTrue(self.test_user4.has_perm('qdjango.change_layer', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.add_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', countries_layer)) + + data.update({ + 'layers': [cities_layer.pk], + 'viewer_users': [self.test_user3.pk], + f'user_add_capability_{self.test_user3.pk}': 'on', + f'user_delete_capability_{self.test_user3.pk}': 'on', + 'user_groups_viewer': [self.test_user_group1.pk], + f'group_add_capability_{self.test_user_group1.pk}': 'on', + f'group_change_capability_{self.test_user_group1.pk}': 'on', + }) + + res = self.client.post(url, data) + + # redirect on ok results + self.assertEqual(res.status_code, 302) + + # Users + self.assertTrue(self.test_user3.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_feature', cities_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertTrue(self.test_user3.has_perm('qdjango.change_layer', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.add_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', countries_layer)) + + # User_groups + self.assertTrue(self.test_user4.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.add_feature', cities_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertTrue(self.test_user4.has_perm('qdjango.change_layer', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.add_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', countries_layer)) + + data = { + 'layers': [cities_layer.pk, countries_layer.pk], + 'active': 'on', + 'scale': 10000, + 'viewer_users': [self.test_user3.pk], + 'user_groups_viewer': [self.test_user_group1.pk], + f'user_add_capability_{self.test_user3.pk}': 'on', + f'user_change_capability_{self.test_user3.pk}': 'on', + f'group_add_capability_{self.test_user_group1.pk}': 'on', + f'group_changeattributes_capability_{self.test_user_group1.pk}': 'on', + } + + res = self.client.post(url, data) + + # redirect on ok results + self.assertEqual(res.status_code, 302) + + # Users + self.assertTrue(self.test_user3.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.add_feature', cities_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertTrue(self.test_user3.has_perm('qdjango.change_layer', countries_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.add_feature', countries_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', countries_layer)) + + # User_groups + self.assertTrue(self.test_user4.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', cities_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertTrue(self.test_user4.has_perm('qdjango.change_layer', countries_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.add_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', countries_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.change_attr_feature', countries_layer)) + + # Deactive editing for countries_layer + data = { + 'layers': [countries_layer.pk], + } + + res = self.client.post(url, data) + + # redirect on ok results + self.assertEqual(res.status_code, 302) + + editing_layers = G3WEditingLayer.objects.filter(layer_id__in=[cities_layer.pk, countries_layer.pk]) + self.assertTrue(len(editing_layers) == 1) + + # Users + self.assertTrue(self.test_user3.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.add_feature', cities_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertFalse(self.test_user3.has_perm('qdjango.change_layer', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.add_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', countries_layer)) + + # User_groups + self.assertTrue(self.test_user4.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', cities_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertFalse(self.test_user4.has_perm('qdjango.change_layer', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.add_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', countries_layer)) + + # Deactive editing for all 2 layers + data = { + 'layers': [countries_layer.pk, cities_layer.pk], + } + + res = self.client.post(url, data) + + # redirect on ok results + self.assertEqual(res.status_code, 302) + + editing_layers = G3WEditingLayer.objects.filter(layer_id__in=[cities_layer.pk, countries_layer.pk]) + self.assertTrue(len(editing_layers) == 0) + + # Users + self.assertFalse(self.test_user3.has_perm('qdjango.change_layer', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertFalse(self.test_user3.has_perm('qdjango.change_layer', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.add_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', countries_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', countries_layer)) + + # User_groups + self.assertFalse(self.test_user4.has_perm('qdjango.change_layer', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', cities_layer)) + + self.assertFalse(self.test_user4.has_perm('qdjango.change_layer', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.add_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', countries_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', countries_layer)) + + + # Test override of single layer editing property + # ---------------------------------------------- + + url_single_layer = reverse('editing-layer-active', args=[ + self.editing_project.instance.group.slug, + 'qdjango', + self.editing_project.instance.slug, + cities_layer.pk + ]) + + data = { + 'active': 'on', + 'scale': 10000, + 'viewer_users': [self.test_user3.pk], + f'user_add_capability_{self.test_user3.pk}': 'on', + f'user_delete_capability_{self.test_user3.pk}': 'on', + 'user_groups_viewer': [self.test_user_group1.pk], + f'group_add_capability_{self.test_user_group1.pk}': 'on', + f'group_change_capability_{self.test_user_group1.pk}': 'on', + } + + res = self.client.post(url_single_layer, data) + + # redirect on ok results + self.assertEqual(res.status_code, 302) + + # Users + self.assertTrue(self.test_user3.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_feature', cities_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', cities_layer)) + + # User_groups + self.assertTrue(self.test_user4.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.add_feature', cities_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', cities_layer)) + + # Override + data = { + 'active': 'on', + 'layers': [cities_layer.pk], + 'scale': 10000, + 'viewer_users': [self.test_user3.pk], + f'user_add_capability_{self.test_user3.pk}': 'on', + 'user_groups_viewer': [self.test_user_group1.pk], + f'group_add_capability_{self.test_user_group1.pk}': 'on', + } + + res = self.client.post(url, data) + + # redirect on ok results + self.assertEqual(res.status_code, 302) + + # Users + self.assertTrue(self.test_user3.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user3.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user3.has_perm('qdjango.change_attr_feature', cities_layer)) + + # User_groups + self.assertTrue(self.test_user4.has_perm('qdjango.change_layer', cities_layer)) + self.assertTrue(self.test_user4.has_perm('qdjango.add_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.delete_feature', cities_layer)) + self.assertFalse(self.test_user4.has_perm('qdjango.change_attr_feature', cities_layer)) + def test_editing_layer_active_logging_fields(self): """ Test active logging fields for insert and update """ diff --git a/g3w-admin/editing/urls.py b/g3w-admin/editing/urls.py index ab1a5eb23..1be7aa6b3 100644 --- a/g3w-admin/editing/urls.py +++ b/g3w-admin/editing/urls.py @@ -13,6 +13,7 @@ from .views import ( UploadFileView, ActiveEditingLayerView, + ActiveEditingMultiLayerView ) @@ -36,5 +37,10 @@ login_required(ActiveEditingLayerView.as_view()), name='editing-layer-active' ), + re_path( + '^(?P[-_\w\d]+)/(?P[-_\w\d]+)/(?P[-_\w\d]+)/layers/active/$', + login_required(ActiveEditingMultiLayerView.as_view()), + name='editing-multi-layer-active' + ), ] \ No newline at end of file diff --git a/g3w-admin/editing/views.py b/g3w-admin/editing/views.py index 5f6f8314f..e2b77c8f3 100644 --- a/g3w-admin/editing/views.py +++ b/g3w-admin/editing/views.py @@ -19,7 +19,7 @@ from core.utils.vector import BaseUserMediaHandler from usersmanage.utils import setPermissionUserObject, get_viewers_for_object, \ get_user_groups_for_object -from .forms import ActiveEditingLayerForm +from .forms import ActiveEditingLayerForm, ActiveEditingMultiLayerForm from .models import G3WEditingLayer, EDITING_ATOMIC_PERMISSIONS import os import json @@ -126,7 +126,7 @@ class ActiveEditingLayerView(AjaxableFormResponseMixin, G3WProjectViewMixin, G3W @method_decorator(project_type_permission_required('change_project', ('project_type', 'project_slug'), return_403=True)) def dispatch(self, request, *args, **kwargs): - self.layer_id = kwargs['layer_id'] + self.layer_id = kwargs.get('layer_id') # Instance user/groups atomic capabilitites _capabilities = {ap:[] for ap in EDITING_ATOMIC_PERMISSIONS} @@ -144,6 +144,23 @@ def dispatch(self, request, *args, **kwargs): def get_success_url(self): return None + def get_initial_viewer_users(self, layer): + """ Get from db viewer users with 'change_layer' permission on layer """ + + with_anonymous = getattr(settings, 'EDITING_ANONYMOUS', False) + viewers = get_viewers_for_object(layer, self.request.user, 'change_layer', + with_anonymous=with_anonymous) + + editor_pk = layer.project.editor.pk if layer.project.editor else None + editor2_pk = layer.project.editor2.pk if layer.project.editor2 else None + return with_anonymous, editor_pk, editor2_pk, [int(o.id) for o in viewers if o.id not in (editor_pk, editor2_pk)] + + def get_initial_viewer_user_groups(self, layer): + """ Get from db viewer group users with 'change_layer' permission on layer """ + + return [o.id for o in get_user_groups_for_object(layer, self.request.user, 'change_layer', 'viewer')] + + def get_form_kwargs(self): kwargs = super(ActiveEditingLayerView, self).get_form_kwargs() @@ -167,18 +184,12 @@ def get_form_kwargs(self): kwargs['initial']['active'] = False # get viewer users - with_anonymous = getattr(settings, 'EDITING_ANONYMOUS', False) - viewers = get_viewers_for_object(self.layer, self.request.user, 'change_layer', with_anonymous=with_anonymous) - - editor_pk = self.layer.project.editor.pk if self.layer.project.editor else None - editor2_pk = self.layer.project.editor2.pk if self.layer.project.editor2 else None - self.initial_viewer_users = kwargs['initial']['viewer_users'] = [int(o.id) for o in viewers - if o.id not in (editor_pk, editor2_pk)] + with_anonymous, editor_pk, editor2_pk, kwargs['initial']['viewer_users'] = self.get_initial_viewer_users(self.layer) + self.initial_viewer_users = kwargs['initial']['viewer_users'] self.initial_atomic_capabilitites['user']['change_layer'] = self.initial_viewer_users - group_viewers = get_user_groups_for_object(self.layer, self.request.user, 'change_layer', 'viewer') - self.initial_viewer_user_groups = kwargs['initial']['user_groups_viewer'] = [o.id for o in group_viewers] + self.initial_viewer_user_groups = kwargs['initial']['user_groups_viewer'] = self.get_initial_viewer_user_groups(self.layer) self.initial_atomic_capabilitites['group']['change_layer'] = self.initial_viewer_user_groups @@ -195,6 +206,9 @@ def get_context_data(self, **kwargs): # Add initial atomic capabilities for widget.js context['initial_atomic_capabilities'] = json.dumps(self.initial_atomic_capabilitites) + # Set multi variable to False + context['multilayer'] = False + return context def get_initial_atomic_capabilitites(self, with_anonymous, editor_pk, editor2_pk): @@ -247,7 +261,7 @@ def add_remove_atomic_permissions(self): setPermissionUserObject(model.objects.get(pk=uid), self.layer, [ap], mode='remove') def remove_from_atomic_permissions(self, context, u_g_id): - """remove from self.atomic_capbilitites ugid""" + """remove from self.atomic_capbilities ugid""" for ap in EDITING_ATOMIC_PERMISSIONS: if u_g_id in self.atomic_capabilitites[context][ap]: @@ -291,7 +305,7 @@ def form_valid(self, form): for uid in toRemove: setPermissionUserObject(User.objects.get(pk=uid), self.layer, ['change_layer'], mode='remove') - # remove from atomic_capabilitites user id + # remove from atomic_capabilities user id self.remove_from_atomic_permissions('user', uid) # give permission to user groups viewers: @@ -312,7 +326,7 @@ def form_valid(self, form): for aid in to_remove: setPermissionUserObject(AuhtGroup.objects.get(pk=aid), self.layer, ['change_layer'], mode='remove') - # remove from atomic_capabilitites user id + # remove from atomic_capabilities user id self.remove_from_atomic_permissions('group', aid) # ADD/REMOVE atomic permissions @@ -320,4 +334,158 @@ def form_valid(self, form): - return super(ActiveEditingLayerView, self).form_valid(form) \ No newline at end of file + return super(ActiveEditingLayerView, self).form_valid(form) + + +class ActiveEditingMultiLayerView(ActiveEditingLayerView): + """ + Activate/deactivate editing multi layer + """ + + form_class = ActiveEditingMultiLayerForm + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # Set multi variable to true + context['multilayer'] = True + + return context + + def get_form_kwargs(self): + + # Bypass ActiveEditingLayerView get_from_kwargs method + kwargs = super(ActiveEditingLayerView, self).get_form_kwargs() + + self.layer_model = apps.get_app_config(self.app_name).get_model('layer') + + if 'data' in kwargs: + self.get_atomic_capabilities(kwargs['data']) + + return kwargs + + def add_remove_atomic_permissions(self, layer, with_anonymous=False, editor_pk=None, editor2_pk=None): + """ Add and remove atomic permissions for user and groups""" + + initial_atomic_capabilitites = {} + + + + for context in self.contexts: + + # Initializze initial_atomic_capabilitites + initial_atomic_capabilitites[context] = {} + + for ap in EDITING_ATOMIC_PERMISSIONS: + + # Get initial atomic capabilities + # ------------------------------- + if context == 'user': + viewers = get_viewers_for_object(layer, self.request.user, ap, with_anonymous=with_anonymous) + initial_atomic_capabilitites['user'][ap] = [int(o.id) for o in viewers + if o.id not in (editor_pk, editor2_pk)] + if context == 'group': + group_viewers = get_user_groups_for_object(layer, self.request.user, ap, 'viewer') + initial_atomic_capabilitites['group'][ap] = [o.id for o in group_viewers] + + model = User if context == 'user' else AuhtGroup + + to_remove = list(set(initial_atomic_capabilitites[context][ap]) - + set(self.atomic_capabilitites[context][ap])) + to_add = list(set(self.atomic_capabilitites[context][ap]) - + set(initial_atomic_capabilitites[context][ap])) + + if to_add: + for uid in to_add: + setPermissionUserObject(model.objects.get(pk=uid), layer, [ap]) + + if to_remove: + for uid in to_remove: + setPermissionUserObject(model.objects.get(pk=uid), layer, [ap], mode='remove') + + @transaction.atomic + def form_valid(self, form): + + # Get current G3WEditingLayer instance + layers = form.cleaned_data['layers'] + activated = { + str(a.layer_id): a for a in G3WEditingLayer.objects.filter( + app_name=self.app_name, + layer_id__in=layers) + } + + scale = form.cleaned_data['scale'] + + for layer_pk in layers: + + add_user_field = self.request.POST.get(f'add_user_field_{layer_pk}', None) + edit_user_field = self.request.POST.get(f'edit_user_field_{layer_pk}', None) + + if form.cleaned_data['active']: + + if layer_pk not in activated.keys(): + activated[layer_pk] = G3WEditingLayer.objects.create(app_name=self.app_name, + layer_id=layer_pk, + edit_user_field=edit_user_field, + add_user_field=add_user_field, + scale=scale) + else: + activated[layer_pk].scale = scale + activated[layer_pk].add_user_field = add_user_field + activated[layer_pk].edit_user_field = edit_user_field + activated[layer_pk].save() + else: + if layer_pk in activated.keys(): + activated[layer_pk].delete() + + # Get layer and initial permissions + layer = self.layer_model.objects.get(pk=layer_pk) + with_anonymous, editor_pk, edito2_pk, initial_viewer_users = self.get_initial_viewer_users(layer) + initial_viewer_user_groups = self.get_initial_viewer_user_groups(layer) + + # Give permission to viewers: + toAdd = toRemove = None + if activated.get(layer_pk) and activated.get(layer_pk).pk: + currentViewerUsers = [int(i) for i in form.cleaned_data['viewer_users']] + toRemove = list(set(initial_viewer_users) - set(currentViewerUsers)) + toAdd = list(set(currentViewerUsers) - set(initial_viewer_users)) + else: + if initial_viewer_users: + toRemove = initial_viewer_users + + if toAdd: + for uid in toAdd: + setPermissionUserObject(User.objects.get(pk=uid), layer, ['change_layer']) + + if toRemove: + for uid in toRemove: + setPermissionUserObject(User.objects.get(pk=uid), layer, ['change_layer'], mode='remove') + + # Remove from atomic_capabilities user id + # self.remove_from_atomic_permissions('user', uid) + + # Give permission to user groups viewers: + to_add = to_remove = None + if activated.get(layer_pk) and activated.get(layer_pk).pk: + current_user_groups_viewers = [int(i) for i in form.cleaned_data['user_groups_viewer']] + to_remove = list(set(initial_viewer_user_groups) - set(current_user_groups_viewers)) + to_add = list(set(current_user_groups_viewers) - set(initial_viewer_user_groups)) + else: + if initial_viewer_user_groups: + to_remove = initial_viewer_user_groups + + if to_add: + for aid in to_add: + setPermissionUserObject(AuhtGroup.objects.get(pk=aid), layer, ['change_layer']) + + if to_remove: + for aid in to_remove: + setPermissionUserObject(AuhtGroup.objects.get(pk=aid), layer, ['change_layer'], mode='remove') + + # # remove from atomic_capabilities user id + # self.remove_from_atomic_permissions('group', aid) + # + # ADD/REMOVE atomic permissions + self.add_remove_atomic_permissions(layer, with_anonymous, editor_pk, edito2_pk) + + return super(ActiveEditingLayerView, self).form_valid(form) diff --git a/g3w-admin/qdjango/api/layers/serializers.py b/g3w-admin/qdjango/api/layers/serializers.py index f1da1de9f..84fc50dc9 100644 --- a/g3w-admin/qdjango/api/layers/serializers.py +++ b/g3w-admin/qdjango/api/layers/serializers.py @@ -17,9 +17,18 @@ class LayerInfoSerializer(serializers.ModelSerializer): + def to_representation(self, instance): + + ret = super().to_representation(instance) + + # From str to python + ret['database_columns'] = eval(ret['database_columns']) + + return ret + class Meta: model = Layer - fields = ['pk', 'name', 'title', 'origname', 'qgs_layer_id', 'layer_type'] + fields = ['pk', 'name', 'title', 'origname', 'qgs_layer_id', 'layer_type', 'database_columns'] class LayerInfoUserSerializer(serializers.ModelSerializer): diff --git a/g3w-admin/qdjango/api/layers/views.py b/g3w-admin/qdjango/api/layers/views.py index cb0867e2f..96d3f87de 100644 --- a/g3w-admin/qdjango/api/layers/views.py +++ b/g3w-admin/qdjango/api/layers/views.py @@ -376,3 +376,15 @@ def get_queryset(self): geometrytype__in=['Polygon', 'MultiPolygon'] ) return qs + +class LayerInfoView(generics.RetrieveAPIView): + """Return info about qdjango layer by pk""" + + queryset = Layer.objects.all() + serializer_class = LayerInfoSerializer + + def get_queryset(self): + qs = super().get_queryset() + if 'layer_id' in self.kwargs: + qs = Layer.objects.get(pk=self.kwargs['layer_id']) + return qs diff --git a/g3w-admin/qdjango/apiurls.py b/g3w-admin/qdjango/apiurls.py index 447abb635..d7adb6489 100644 --- a/g3w-admin/qdjango/apiurls.py +++ b/g3w-admin/qdjango/apiurls.py @@ -15,7 +15,8 @@ LayerAuthGroupInfoAPIView, LayerStyleListView, LayerStyleDetailView, - LayerPolygonView + LayerPolygonView, + LayerInfoView ) from .api.constraints.views import ( ConstraintExpressionRuleDetail, @@ -355,4 +356,9 @@ name='qdjango-api-info-layer-authgroup' ), + path('api/info/layer//', + login_required(LayerInfoView.as_view()), + name='qdjango-api-info-layer' + ), + ] \ No newline at end of file diff --git a/g3w-admin/qdjango/templates/qdjango/layers_list.html b/g3w-admin/qdjango/templates/qdjango/layers_list.html index 369a643a6..9e440c973 100644 --- a/g3w-admin/qdjango/templates/qdjango/layers_list.html +++ b/g3w-admin/qdjango/templates/qdjango/layers_list.html @@ -98,7 +98,21 @@

{% trans 'Project' %}

  • {% trans 'Structure' %}
  • -
    +
    +
    +
    + {% trans 'Project layers actions' %}: +
    +
    + {% g3wadmin_project_layers_actions project 'qdjango' user as project_layers_actions %} + + {% for action in project_layers_actions %} + {{ action }} + {% endfor %} +
    +
    + + From eb21e1caf58464687d071605980471ec30c23796 Mon Sep 17 00:00:00 2001 From: Raruto Date: Tue, 28 Nov 2023 09:28:19 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Mispelled=20word:=20`datasoruce`=20?= =?UTF-8?q?=E2=86=92=20`datasource`=20(#675)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g3w-admin/qdjango/locale/it/LC_MESSAGES/django.po | 2 +- g3w-admin/qdjango/templates/qdjango/ajax/layer_detail.html | 2 +- g3w-admin/qdjango/utils/models.py | 4 ++-- g3w-admin/qdjango/utils/structure.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/g3w-admin/qdjango/locale/it/LC_MESSAGES/django.po b/g3w-admin/qdjango/locale/it/LC_MESSAGES/django.po index f0fae27b5..b44b03b6f 100644 --- a/g3w-admin/qdjango/locale/it/LC_MESSAGES/django.po +++ b/g3w-admin/qdjango/locale/it/LC_MESSAGES/django.po @@ -844,7 +844,7 @@ msgstr "" #: templates/qdjango/ajax/layer_detail.html:20 #, fuzzy #| msgid "Datasource" -msgid "Datasoruce" +msgid "Datasource" msgstr "Sorgente dati" #: templates/qdjango/ajax/layer_widgets.html:4 diff --git a/g3w-admin/qdjango/templates/qdjango/ajax/layer_detail.html b/g3w-admin/qdjango/templates/qdjango/ajax/layer_detail.html index 2d5fb67d2..dec19b3f6 100644 --- a/g3w-admin/qdjango/templates/qdjango/ajax/layer_detail.html +++ b/g3w-admin/qdjango/templates/qdjango/ajax/layer_detail.html @@ -17,7 +17,7 @@

    {% trans 'QGIS layer ID' %}:

    {{ object.qgs_layer_id }}
  • -

    {% trans 'Datasoruce' %}:

    +

    {% trans 'Datasource' %}:

    {{ object.datasource }}
  • diff --git a/g3w-admin/qdjango/utils/models.py b/g3w-admin/qdjango/utils/models.py index a2b8b7e0b..fe4f41188 100644 --- a/g3w-admin/qdjango/utils/models.py +++ b/g3w-admin/qdjango/utils/models.py @@ -24,7 +24,7 @@ def comparedbdatasource(ds1, ds2, layer_type='postgres'): """ Compare postgis/sqlite datasource bosed on dbname, host and table name - :param ds1: qgis db datasoruce string from compare + :param ds1: qgis db datasource string from compare :param ds2: qgis db datasource string to compare :return: Boolean """ @@ -54,7 +54,7 @@ def get_widgets4layer(layer): :param layer: Qdjango Layer model instance :return: List or Querydict fo Widget models """ - from .structure import datasource2dict, qgsdatasoruceuri2dict + from .structure import datasource2dict, qgsdatasourceuri2dict from qdjango.models import Widget # different by layer type diff --git a/g3w-admin/qdjango/utils/structure.py b/g3w-admin/qdjango/utils/structure.py index f28ed111b..acac9fcf9 100644 --- a/g3w-admin/qdjango/utils/structure.py +++ b/g3w-admin/qdjango/utils/structure.py @@ -40,7 +40,7 @@ def get_schema_table(datasource_table): return schema, table -def qgsdatasoruceuri2dict(datasource: str) -> dict: +def qgsdatasourceuri2dict(datasource: str) -> dict: """ From QgsDatasourceUri to dict At now only for postgres type layer