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('\
+
\
+ \
+ \
+ <%= layer_name_title %> | \
+ <%= add_user_field_title %> | \
+ <%= edit_user_field_title %> | \
+
\
+ \
+ <%= body_table %>\
+
\
+ '),
+ 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/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/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 %}
+
+
+
+
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