Skip to content

Import resources and permissions from Map resource #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 207 additions & 1 deletion controllers/resources_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from sqlalchemy.ext.declarative import DeclarativeMeta

from .controller import Controller
from forms import ResourceForm
from forms import ImportResourceForm, ResourceForm


class ResourcesController(Controller):
Expand Down Expand Up @@ -52,6 +52,17 @@ def __init__(self, app, handler):
'import_children_%s' % suffix,
self.import_children, methods=['POST']
)
# import resources from parent map
app.add_url_rule(
'/%s/<int:id>/import' % base_route,
'import_%s' % suffix,
self.import_resources, methods=['GET', 'POST']
)
app.add_url_rule(
'/%s/<int:id>/import_from_parent_map' % base_route,
'import_%s_from_parent_map' % suffix,
self.import_resources_from_parent_map, methods=['GET', 'POST']
)

def resources_for_index_query(self, search_text, resource_type, session):
"""Return query for resources list filtered by resource type.
Expand Down Expand Up @@ -345,6 +356,38 @@ def create_form(self, resource=None, edit_form=False):

return form

def create_import_form(self):
"""Return form with fields loaded from DB.

:param object resource: Optional resource object
:param bool edit_form: Set if edit form
"""
form = ImportResourceForm()

session = self.session()

# query resource types
query = session.query(self.ResourceType) \
.order_by(self.ResourceType.list_order, self.ResourceType.name) \
.filter(self.ResourceType.name.in_(('layer', 'data', 'data_create', 'data_read', 'data_update', 'data_delete')))
resource_types_to_import_from_map = query.all()

# query permission roles
roles = session.query(self.Role).order_by(self.Role.name).all()

session.close()

# set choices for import_type select field
form.import_type.choices = [
(t.name, t.description) for t in resource_types_to_import_from_map
]
# set choices for permission_role_id select field
form.role_id.choices = [(0, "")] + [
(r.id, r.name) for r in roles
]

return form

def create_or_update_resources(self, resource, form, session):
"""Create or update resource records in DB.

Expand Down Expand Up @@ -718,6 +761,169 @@ def import_layers(self, map_resource, config_generator_service_url,
self.logger.error(msg)
flash(msg, 'error')

def import_resources(self, id):
"""Import child resources for a map:

* Import resources for a map

:param int id: Resource ID
"""
self.setup_models()
template = '%s/import_form.html' % self.templates_dir
form = self.create_import_form()
title = "Import %s" % self.resource_name
action = url_for('import_%s_from_parent_map' % self.endpoint_suffix, id=id)

return render_template(
template, title=title, form=form, action=action, method='POST'
)

def import_resources_from_parent_map(self, id):
"""Import layers for a map.

:param int id: Resource ID
"""
self.setup_models()
form = self.create_import_form()
if form.validate_on_submit():
try:
# find resource
session = self.session()
parent_resource = self.find_resource(id, session)
if parent_resource is not None:
# get config generator URL
config_generator_service_url = self.handler().config().get(
"config_generator_service_url",
"http://qwc-config-service:9090"
)
type = form.import_type.data
if parent_resource.type == 'map':

# get map details from config generator service
url = urljoin(
config_generator_service_url, 'maps/%s' % parent_resource.name
)
tenant = self.handler().tenant
response = requests.get(url, params={'tenant': tenant})
if response.status_code != requests.codes.ok:
self.logger.error(
"Could not get map details from %s:\n%s" %
(response.url, response.content)
)
flash(
'Could not import layers: Status %s' %
response.status_code, 'error'
)
return redirect(url_for(self.base_route))

layers_from_config = response.json().get('layers', [])

if layers_from_config:
new_resources = []
new_permissions = []
for layer in layers_from_config:
# first query to know if resource already exists
query = session.query(self.Resource) \
.filter(self.Resource.name == layer) \
.filter(self.Resource.type == type) \
.filter(self.Resource.parent_id == parent_resource.id)
resources = query.all()

if not resources:
# resource does not exist in database so create new resource
resource = self.Resource()
resource.type = type
resource.name = layer
resource.parent_id = parent_resource.id
new_resources.append(resource)
session.add(resource)

# new query to get id of new resource
query = session.query(self.Resource) \
.filter(self.Resource.name == layer) \
.filter(self.Resource.type == type) \
.filter(self.Resource.parent_id == parent_resource.id)
resources = query.all()

# handle permission for existing or new resource if role has been chosen
role = form.role_id.data
if role > 0:
for resource in resources:
resource_id = resource.__dict__.get('id')
# query resource permissions
query = session.query(self.Permission) \
.filter(self.Permission.resource_id == resource_id) \
.filter(self.Permission.role_id == role)

permissions = []
for permission in query.all():
permissions.append({
"role": permission.role.name,
"write": permission.write
})
if not permissions:
# create new permission for resource
permission = self.Permission()
session.add(permission)

# update permission
permission.priority = form.priority.data
permission.write = form.write.data

permission.role_id = role

permission.resource_id = resource_id
new_permissions.append(permission)

# commit resources
session.commit()
self.update_config_timestamp(session)

if new_resources:
flash(
'%d new resources have been added.' %
len(new_resources), 'success'
)
else:
flash('No additional resources found.', 'info')

if new_permissions:
flash(
'%d new permissions have been added.' %
len(new_permissions), 'success'
)
else:
flash('No additional permissions found.', 'info')
else:
# map not found or no layers
flash('No layers found for this map.', 'warning')

else:
flash('Child import not supported for this resource type.',
'warning')

else:
# resource not found
session.close()
abort(404)

session.close()

return redirect(
url_for('hierarchy_%s' % self.endpoint_suffix, id=id)
)
except Exception as e:
if session:
session.close()
msg = "Could not import resources: %s" % e
self.logger.error(msg)
flash(msg, 'error')
return redirect(
url_for(self.base_route)
)
else:
flash('Could not import resources from %s.' % parent_resource,
'warning')

class AlchemyEncoder(json.JSONEncoder):

Expand Down
2 changes: 1 addition & 1 deletion forms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .user_form import UserForm
from .group_form import GroupForm
from .role_form import RoleForm
from .resource_form import ResourceForm
from .resource_form import ImportResourceForm, ResourceForm
from .permission_form import PermissionForm
from .registrable_group_form import RegistrableGroupForm
from .registration_request_form import RegistrationRequestForm
17 changes: 15 additions & 2 deletions forms/resource_form.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask_wtf import FlaskForm
from wtforms import SelectField, StringField, SubmitField
from wtforms.validators import DataRequired, Optional
from wtforms import BooleanField, IntegerField, SelectField, StringField, SubmitField
from wtforms.validators import DataRequired, NumberRange, Optional


class ResourceForm(FlaskForm):
Expand All @@ -25,3 +25,16 @@ class ResourceForm(FlaskForm):
parent_choices = []

submit = SubmitField('Save')

class ImportResourceForm(FlaskForm):
"""Form for Import Resource from Map"""
import_type = SelectField('Type of resources to import from map', coerce=str)
role_id = SelectField('Role permission of resources to import from map', coerce=int)
priority = IntegerField(
'Priority',
validators=[
Optional(),
NumberRange(min=0, message="Priority must be greater or equal 0")
]
)
write = BooleanField('Write')
5 changes: 5 additions & 0 deletions templates/base_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ <h1>{{ self.title() }}</h1>
<a href="{{ url_for('new_permission', resource_id=resource['id']) }}" class="btn btn-success" role="button">
{{ utils.icon('plus') }} New Permission
</a>
{% if resource['type'] == 'map' %}
<a href="{{ url_for('import_resource', id=resource['id']) }}" method="post" class="btn btn-success" role="button">
{{ utils.icon('plus') }} Import Resources
</a>
{% endif %}
{% endif %}
<form action="{{ url_for('destroy_%s' % endpoint_suffix, id=resource[pkey]) }}" method="post" style="display: inline;">
<input type="hidden" name="_method" value="DELETE" />
Expand Down
31 changes: 31 additions & 0 deletions templates/resources/import_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% import "bootstrap/wtf.html" as wtf %}
{% extends "templates/base.html" %}

{%- block styles %}
{{ super() }}
<link href="{{ url_for('static', filename='css/bootstrap-chosen.css') }}" rel="stylesheet">
<style type="text/css">
.chosen-container .chosen-results li.group-result.chosen-hidden {
display: none;
}
</style>
{% endblock %}

{% block title %}{{ title }}{% endblock %}
{% block container %}
<h1>{{ title }}</h1>

<form class="form form-horizontal" action="{{ action }}" method="post">
{% if method != 'POST' %}
<input type="hidden" name="_method" value="{{method}}" />
{% endif %}
{{ form.csrf_token }}
{{ wtf.form_field(form.import_type, form_type="horizontal", horizontal_columns=('sm', 2, 5)) }}
{{ wtf.form_field(form.role_id, form_type="horizontal", horizontal_columns=('sm', 2, 5)) }}
{{ wtf.form_field(form.priority, form_type="horizontal", horizontal_columns=('sm', 2, 5)) }}
{{ wtf.form_field(form.write, form_type="horizontal", horizontal_columns=('sm', 2, 5)) }}
<button type="submit" class="col-sm-offset-2 btn btn-success btn-spin-on-click" data-spinning-msg="Importing resources...">
{{ utils.icon('download-alt') }} Import resources
</button>
</form>
{% endblock %}