2013-04-08 10:50:59 +02:00
|
|
|
# w.c.s. - web application for online forms
|
|
|
|
# Copyright (C) 2005-2013 Entr'ouvert
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2023-09-08 11:13:18 +02:00
|
|
|
import base64
|
2022-09-24 18:43:55 +02:00
|
|
|
import copy
|
2020-12-07 09:50:04 +01:00
|
|
|
import datetime
|
2015-05-14 20:57:13 +02:00
|
|
|
import json
|
2023-09-09 11:31:23 +02:00
|
|
|
import re
|
2021-02-28 16:19:33 +01:00
|
|
|
import urllib.parse
|
2013-04-08 10:50:59 +02:00
|
|
|
|
2019-04-04 15:22:50 +02:00
|
|
|
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
|
2024-01-13 16:08:33 +01:00
|
|
|
from django.utils.encoding import force_bytes
|
2024-02-10 12:24:29 +01:00
|
|
|
from django.utils.timezone import localtime, make_naive
|
2023-11-08 15:42:00 +01:00
|
|
|
from quixote import get_publisher, get_request, get_response, get_session, redirect
|
2020-10-30 11:05:21 +01:00
|
|
|
from quixote.directory import Directory
|
2021-05-11 20:33:05 +02:00
|
|
|
from quixote.errors import MethodNotAllowedError, RequestError
|
2024-02-28 14:01:48 +01:00
|
|
|
from quixote.html import TemplateIO, htmltext
|
2013-04-08 10:50:59 +02:00
|
|
|
|
2015-09-18 15:21:24 +02:00
|
|
|
import wcs.qommon.storage as st
|
2021-03-22 11:53:54 +01:00
|
|
|
from wcs.admin.settings import UserFieldsFormDef
|
2023-11-08 15:42:00 +01:00
|
|
|
from wcs.api_utils import get_query_flag, get_user_from_api_query_string, is_url_signed, sign_url_auto_orig
|
2019-07-23 21:34:14 +02:00
|
|
|
from wcs.carddef import CardDef
|
2015-03-26 23:16:59 +01:00
|
|
|
from wcs.categories import Category
|
2019-10-24 11:37:40 +02:00
|
|
|
from wcs.conditions import Condition, ValidationError
|
2023-10-04 08:50:41 +02:00
|
|
|
from wcs.ctl.management.commands.hobo_notify import Command as CmdHoboNotify
|
2021-03-14 12:24:50 +01:00
|
|
|
from wcs.data_sources import NamedDataSource
|
2020-11-09 09:41:01 +01:00
|
|
|
from wcs.data_sources import get_object as get_data_source_object
|
2022-10-29 14:54:07 +02:00
|
|
|
from wcs.data_sources import request_json_items
|
2021-03-09 15:35:21 +01:00
|
|
|
from wcs.formdef import FormDef
|
2023-05-16 15:41:39 +02:00
|
|
|
from wcs.forms.common import FileDirectory, FormStatusPage
|
2021-03-30 10:16:20 +02:00
|
|
|
from wcs.qommon import get_cfg
|
2021-03-17 16:22:05 +01:00
|
|
|
from wcs.qommon.afterjobs import AfterJob
|
2019-10-24 11:37:40 +02:00
|
|
|
from wcs.roles import logged_users_role
|
2023-05-02 12:22:40 +02:00
|
|
|
from wcs.sql_criterias import (
|
|
|
|
Contains,
|
2023-06-20 14:59:57 +02:00
|
|
|
ElementIntersects,
|
2023-05-02 12:22:40 +02:00
|
|
|
Equal,
|
|
|
|
FtsMatch,
|
|
|
|
ILike,
|
|
|
|
Intersects,
|
|
|
|
NotContains,
|
2023-07-28 16:07:54 +02:00
|
|
|
Nothing,
|
2023-05-02 12:22:40 +02:00
|
|
|
Null,
|
|
|
|
Or,
|
|
|
|
StrictNotEqual,
|
|
|
|
)
|
2022-09-24 18:43:55 +02:00
|
|
|
from wcs.workflows import ContentSnapshotPart
|
2024-02-28 14:01:48 +01:00
|
|
|
from wcs.wscalls import UnflattenKeysException, unflatten_keys
|
2015-03-26 23:16:59 +01:00
|
|
|
|
2020-11-03 11:08:04 +01:00
|
|
|
from .backoffice.data_management import CardPage as BackofficeCardPage
|
2019-09-29 20:53:23 +02:00
|
|
|
from .backoffice.management import FormPage as BackofficeFormPage
|
|
|
|
from .backoffice.management import ManagementDirectory
|
2020-10-20 15:19:07 +02:00
|
|
|
from .backoffice.submission import SubmissionDirectory
|
2019-09-29 20:53:23 +02:00
|
|
|
from .qommon import _, misc
|
2022-10-29 14:54:07 +02:00
|
|
|
from .qommon.errors import AccessForbiddenError, TraversalError, UnknownNameIdAccessForbiddenError
|
2021-03-30 10:16:20 +02:00
|
|
|
from .qommon.template import Template, TemplateError
|
2015-03-26 23:16:59 +01:00
|
|
|
|
2020-01-18 20:33:44 +01:00
|
|
|
|
2016-05-02 12:41:33 +02:00
|
|
|
def posted_json_data_to_formdata_data(formdef, data):
|
2024-04-18 18:25:37 +02:00
|
|
|
data = copy.deepcopy(data)
|
2016-05-02 12:41:33 +02:00
|
|
|
# remap fields from varname to field id
|
2017-01-27 14:38:58 +01:00
|
|
|
for field in formdef.get_all_fields():
|
2016-05-02 12:41:33 +02:00
|
|
|
if not field.varname:
|
|
|
|
continue
|
2021-03-22 11:14:42 +01:00
|
|
|
if field.varname not in data:
|
2016-05-02 12:41:33 +02:00
|
|
|
continue
|
|
|
|
raw = '%s_raw' % field.varname
|
|
|
|
structured = '%s_structured' % field.varname
|
|
|
|
if field.store_display_value and raw in data:
|
|
|
|
data[field.id] = data.pop(raw)
|
|
|
|
data['%s_display' % field.id] = data.pop(field.varname)
|
|
|
|
else:
|
|
|
|
data[field.id] = data.pop(field.varname)
|
|
|
|
if field.store_structured_value and structured in data:
|
|
|
|
data['%s_structured' % field.id] = data.pop(structured)
|
|
|
|
|
2022-07-18 16:18:25 +02:00
|
|
|
# merge unnamed fields if they exist
|
|
|
|
if '_unnamed' in data:
|
2023-01-23 15:21:03 +01:00
|
|
|
unnamed_data = data.pop('_unnamed')
|
|
|
|
for k in unnamed_data.keys():
|
|
|
|
data[k] = unnamed_data.get('%s_raw' % k, unnamed_data.get(k)) # prefer raw value
|
2022-07-18 16:18:25 +02:00
|
|
|
|
2021-11-23 13:30:32 +01:00
|
|
|
# create a temporary formdata so datasources using previous fields in
|
|
|
|
# parameters can find their values.
|
|
|
|
transient_formdata = formdef.data_class()()
|
|
|
|
transient_formdata.data = data
|
|
|
|
|
|
|
|
with get_publisher().substitutions.temporary_feed(transient_formdata, force_mode='lazy'):
|
|
|
|
# complete/adapt field values
|
|
|
|
for field in formdef.get_all_fields():
|
|
|
|
structured = '%s_structured' % field.id
|
|
|
|
display = '%s_display' % field.id
|
|
|
|
if data.get(field.id) is None:
|
|
|
|
continue
|
|
|
|
if hasattr(field, 'from_json_value'):
|
|
|
|
data[field.id] = field.from_json_value(data[field.id])
|
|
|
|
# only fill display/structured if both are absent
|
|
|
|
if display not in data and structured not in data:
|
|
|
|
if field.store_display_value:
|
|
|
|
display_value = field.store_display_value(data, field.id)
|
|
|
|
if display_value is not None:
|
|
|
|
data[display] = display_value
|
|
|
|
if field.store_structured_value:
|
|
|
|
structured_value = field.store_structured_value(data, field.id)
|
|
|
|
if structured_value is not None:
|
|
|
|
data[structured] = structured_value
|
2016-05-02 12:41:33 +02:00
|
|
|
return data
|
|
|
|
|
2015-03-26 23:16:59 +01:00
|
|
|
|
2017-05-28 00:17:55 +02:00
|
|
|
def get_formdata_dict(formdata, user, consider_status_visibility=True):
|
2017-11-28 10:28:19 +01:00
|
|
|
if consider_status_visibility and not formdata.is_draft():
|
2017-05-28 00:17:55 +02:00
|
|
|
status = formdata.get_visible_status(user=user)
|
|
|
|
else:
|
|
|
|
status = formdata.get_status()
|
|
|
|
|
2020-06-23 18:02:34 +02:00
|
|
|
status_name = None
|
|
|
|
if formdata.is_draft():
|
|
|
|
status_name = _('Draft')
|
|
|
|
elif status:
|
|
|
|
status_name = status.name
|
|
|
|
|
2017-11-28 10:28:19 +01:00
|
|
|
d = {
|
2017-05-28 00:17:55 +02:00
|
|
|
'name': formdata.formdef.name,
|
|
|
|
'url': formdata.get_url(),
|
2017-11-01 20:45:37 +01:00
|
|
|
'datetime': misc.strftime('%Y-%m-%d %H:%M:%S', formdata.receipt_time),
|
2020-06-23 18:02:34 +02:00
|
|
|
'status': status_name,
|
2017-11-28 10:28:19 +01:00
|
|
|
'status_css_class': status.extra_css_class if status else None,
|
2017-05-28 00:17:55 +02:00
|
|
|
'keywords': formdata.formdef.keywords_list,
|
2017-11-28 10:28:19 +01:00
|
|
|
'draft': formdata.is_draft(),
|
2017-05-28 00:17:55 +02:00
|
|
|
}
|
2017-10-11 09:58:53 +02:00
|
|
|
if formdata.last_update_time:
|
2017-11-01 20:45:37 +01:00
|
|
|
d['last_update_time'] = misc.strftime('%Y-%m-%d %H:%M:%S', formdata.last_update_time)
|
2017-11-28 10:28:19 +01:00
|
|
|
|
|
|
|
if formdata.is_draft():
|
|
|
|
d['form_number_raw'] = d['form_number'] = None
|
|
|
|
d['title'] = _('%(name)s (draft)') % {'name': formdata.formdef.name}
|
|
|
|
else:
|
|
|
|
d['title'] = _('%(name)s #%(id)s (%(status)s)') % {
|
|
|
|
'name': formdata.formdef.name,
|
|
|
|
'id': formdata.get_display_id(),
|
2020-06-27 14:29:32 +02:00
|
|
|
'status': status_name or _('unknown'),
|
2017-11-28 10:28:19 +01:00
|
|
|
}
|
|
|
|
|
2018-05-21 10:35:37 +02:00
|
|
|
d.update(formdata.get_static_substitution_variables(minimal=True))
|
2017-05-28 00:17:55 +02:00
|
|
|
if get_request().form.get('full') == 'on':
|
2019-01-09 13:12:04 +01:00
|
|
|
d.update(formdata.get_json_export_dict(include_files=False, user=user))
|
2024-02-10 12:24:29 +01:00
|
|
|
if d.get('form_receipt_datetime'):
|
|
|
|
d['form_receipt_datetime'] = make_naive(d['form_receipt_datetime'].replace(microsecond=0))
|
|
|
|
if d.get('form_last_update_datetime'):
|
|
|
|
d['form_last_update_datetime'] = make_naive(d['form_last_update_datetime'].replace(microsecond=0))
|
|
|
|
|
2017-05-28 00:17:55 +02:00
|
|
|
return d
|
|
|
|
|
|
|
|
|
2015-10-17 21:53:58 +02:00
|
|
|
class ApiFormdataPage(FormStatusPage):
|
2016-12-14 11:14:11 +01:00
|
|
|
_q_exports_orig = ['', 'download']
|
2015-10-17 21:53:58 +02:00
|
|
|
|
|
|
|
def _q_index(self):
|
2016-05-02 12:41:33 +02:00
|
|
|
if get_request().get_method() == 'POST':
|
|
|
|
return self.post()
|
2015-10-17 21:53:58 +02:00
|
|
|
return self.json()
|
|
|
|
|
2016-05-02 12:41:33 +02:00
|
|
|
def post(self):
|
|
|
|
get_response().set_content_type('application/json')
|
|
|
|
api_user = get_user_from_api_query_string()
|
|
|
|
|
2024-01-16 14:02:35 +01:00
|
|
|
if self.formdata.is_draft():
|
|
|
|
raise AccessForbiddenError('formdata is not editable (still a draft)')
|
|
|
|
|
2016-05-02 12:41:33 +02:00
|
|
|
# check the formdata is currently editable
|
|
|
|
wf_status = self.formdata.get_status()
|
|
|
|
for item in wf_status.items:
|
|
|
|
if not item.key == 'editable':
|
|
|
|
continue
|
|
|
|
if not item.check_auth(self.formdata, api_user):
|
|
|
|
continue
|
|
|
|
|
|
|
|
json_input = get_request().json
|
2022-01-08 00:23:39 +01:00
|
|
|
if not isinstance(json_input, dict):
|
|
|
|
raise RequestError('payload is not a dict')
|
|
|
|
if 'data' not in json_input:
|
|
|
|
raise RequestError('missing data entry in payload')
|
2016-05-02 12:41:33 +02:00
|
|
|
data = posted_json_data_to_formdata_data(self.formdef, json_input['data'])
|
2022-09-24 18:43:55 +02:00
|
|
|
old_data = copy.deepcopy(self.formdata.data)
|
2016-05-02 12:41:33 +02:00
|
|
|
self.formdata.data.update(data)
|
|
|
|
self.formdata.store()
|
|
|
|
|
2023-04-08 09:28:44 +02:00
|
|
|
if self.formdata.jump_status(item.status):
|
2022-12-30 10:04:45 +01:00
|
|
|
self.formdata.record_workflow_event('api-post-edit-action', action_item_id=item.id)
|
|
|
|
self.formdata.perform_workflow()
|
2023-12-15 11:32:45 +01:00
|
|
|
ContentSnapshotPart.take(formdata=self.formdata, old_data=old_data, user=api_user)
|
2022-09-24 18:43:55 +02:00
|
|
|
self.formdata.store()
|
2016-05-02 12:41:33 +02:00
|
|
|
|
2021-05-31 21:14:57 +02:00
|
|
|
return json.dumps({'err': 0, 'data': {'id': str(self.formdata.id)}})
|
2016-05-02 12:41:33 +02:00
|
|
|
|
|
|
|
raise AccessForbiddenError('formdata is not editable by given user')
|
|
|
|
|
2015-10-17 21:53:58 +02:00
|
|
|
def check_receiver(self):
|
|
|
|
api_user = get_user_from_api_query_string()
|
|
|
|
if not api_user:
|
|
|
|
if get_request().user and get_request().user.is_admin:
|
|
|
|
return # grant access to admins, to ease debug
|
2015-11-17 11:52:20 +01:00
|
|
|
raise AccessForbiddenError('user not authenticated')
|
2015-10-17 21:53:58 +02:00
|
|
|
if not self.formdef.is_user_allowed_read_status_and_history(api_user, self.filled):
|
2015-11-17 11:52:20 +01:00
|
|
|
raise AccessForbiddenError('unsufficient roles')
|
2015-10-17 21:53:58 +02:00
|
|
|
|
|
|
|
|
2020-11-03 11:08:04 +01:00
|
|
|
class ApiFormPageMixin:
|
2015-03-26 23:16:59 +01:00
|
|
|
def __init__(self, component):
|
|
|
|
try:
|
2019-07-23 21:34:14 +02:00
|
|
|
self.formdef = self.formdef_class.get_by_urlname(component)
|
2015-03-26 23:16:59 +01:00
|
|
|
except KeyError:
|
2015-04-01 17:03:18 +02:00
|
|
|
raise TraversalError()
|
2022-02-10 19:56:38 +01:00
|
|
|
self._view = None
|
2015-03-26 23:16:59 +01:00
|
|
|
|
2017-07-26 13:54:22 +02:00
|
|
|
def check_access(self, api_name=None):
|
2021-05-10 13:47:51 +02:00
|
|
|
if get_request().user and get_request().user.is_admin:
|
|
|
|
return # grant access to admins, to ease debug
|
|
|
|
|
|
|
|
if get_request().has_anonymised_data_api_restriction() and is_url_signed():
|
|
|
|
# when requesting anonymous data, a signature is enough
|
|
|
|
return
|
|
|
|
|
|
|
|
api_user = get_user_from_api_query_string(api_name=api_name)
|
|
|
|
|
|
|
|
if not api_user:
|
|
|
|
raise AccessForbiddenError('user not authenticated')
|
|
|
|
if not self.formdef.is_of_concern_for_user(api_user):
|
|
|
|
raise AccessForbiddenError('unsufficient roles')
|
2015-03-26 23:16:59 +01:00
|
|
|
|
2015-10-17 21:53:58 +02:00
|
|
|
def _q_lookup(self, component):
|
2017-07-26 11:53:09 +02:00
|
|
|
if component == 'ics':
|
|
|
|
return self.ics()
|
|
|
|
|
2023-05-12 11:11:03 +02:00
|
|
|
if not misc.is_ascii_digit(component) and not self._view:
|
2022-03-24 09:35:21 +01:00
|
|
|
for view in self.get_custom_views(
|
|
|
|
[StrictNotEqual('visibility', 'owner'), Equal('slug', component)]
|
|
|
|
):
|
2022-02-10 19:56:38 +01:00
|
|
|
# /api/cards/<carddef-slug>/<custom view>/<optional card id>/
|
|
|
|
self._view = view
|
|
|
|
return self
|
|
|
|
|
2019-04-11 10:48:03 +02:00
|
|
|
# check access for all paths (except webooks), to block access to
|
|
|
|
# formdata that would otherwise be accessible if the user is the
|
|
|
|
# submitter.
|
|
|
|
if not self.is_webhook:
|
|
|
|
self.check_access()
|
2015-10-17 21:53:58 +02:00
|
|
|
try:
|
2024-02-22 14:04:40 +01:00
|
|
|
formdata = self.formdef.data_class().get_by_id(component)
|
2015-10-17 21:53:58 +02:00
|
|
|
except KeyError:
|
|
|
|
raise TraversalError()
|
2022-02-10 19:56:38 +01:00
|
|
|
return ApiFormdataPage(self.formdef, formdata, custom_view=self._view)
|
2015-10-17 21:53:58 +02:00
|
|
|
|
2019-04-11 10:48:03 +02:00
|
|
|
def _q_traverse(self, path):
|
2022-02-10 19:56:38 +01:00
|
|
|
if path[0] in ('list', 'ods', 'geojson') and len([x for x in path if x]) < 3:
|
|
|
|
# /api/cards/<carddef-slug>/<mode: one of list, ods, geojson>/
|
|
|
|
# /api/cards/<carddef-slug>/<mode: one of list, ods, geojson>/<custom view>/
|
|
|
|
if path[-1]:
|
|
|
|
# always consider a trailing slash
|
|
|
|
path.append('')
|
|
|
|
if path[1]:
|
2022-03-24 09:35:21 +01:00
|
|
|
for view in self.get_custom_views(
|
|
|
|
[StrictNotEqual('visibility', 'owner'), Equal('slug', path[1])]
|
|
|
|
):
|
2022-02-10 19:56:38 +01:00
|
|
|
self._view = view
|
|
|
|
break
|
2020-04-16 08:48:18 +02:00
|
|
|
else:
|
2022-05-17 16:24:18 +02:00
|
|
|
path = ['not-found']
|
2022-02-10 19:56:38 +01:00
|
|
|
path = [path[0]]
|
2020-03-09 09:35:09 +01:00
|
|
|
|
2020-10-05 17:16:37 +02:00
|
|
|
if len(path) >= 2 and path[1] == 'ics':
|
2022-03-24 09:35:21 +01:00
|
|
|
for view in self.get_custom_views(
|
|
|
|
[StrictNotEqual('visibility', 'owner'), Equal('slug', path[0])]
|
|
|
|
):
|
2020-10-13 15:10:49 +02:00
|
|
|
self._view = view
|
2020-10-05 17:16:37 +02:00
|
|
|
path = path[1:]
|
|
|
|
|
2023-05-12 11:11:03 +02:00
|
|
|
if misc.is_ascii_digit(path[-1]):
|
2022-02-10 19:56:38 +01:00
|
|
|
# allow trailing / after <id>
|
|
|
|
path.append('')
|
|
|
|
|
2019-04-11 10:48:03 +02:00
|
|
|
self.is_webhook = False
|
|
|
|
if len(path) > 1:
|
|
|
|
# webhooks have their own access checks, request cannot be blocked
|
|
|
|
# at this point.
|
|
|
|
self.is_webhook = bool(path[1] == 'hooks')
|
2022-02-10 19:56:38 +01:00
|
|
|
|
2020-11-03 11:08:04 +01:00
|
|
|
return super()._q_traverse(path)
|
|
|
|
|
|
|
|
|
|
|
|
class ApiFormPage(ApiFormPageMixin, BackofficeFormPage):
|
|
|
|
_q_exports = [('list', 'json'), 'geojson', 'ods'] # restrict to API endpoints
|
2019-04-11 10:48:03 +02:00
|
|
|
|
2015-03-26 23:16:59 +01:00
|
|
|
|
2020-11-03 11:08:04 +01:00
|
|
|
class ApiCardPage(ApiFormPageMixin, BackofficeCardPage):
|
|
|
|
_q_exports = [ # restricted to API endpoints
|
|
|
|
('list', 'json'),
|
|
|
|
('import-csv', 'import_csv'),
|
2022-07-18 16:18:25 +02:00
|
|
|
('import-json', 'import_json'),
|
2020-11-03 11:08:04 +01:00
|
|
|
'geojson',
|
|
|
|
'ods',
|
|
|
|
('@schema', 'schema'),
|
2021-02-22 16:15:12 +01:00
|
|
|
'submit',
|
2020-11-03 11:08:04 +01:00
|
|
|
]
|
2020-09-25 10:09:22 +02:00
|
|
|
|
2020-11-20 09:48:50 +01:00
|
|
|
def check_access(self, api_name=None):
|
|
|
|
if is_url_signed() and get_user_from_api_query_string(api_name=api_name) is None:
|
|
|
|
# signed but no user specified, grant access.
|
|
|
|
class ApiAdminUser:
|
2023-05-01 10:49:50 +02:00
|
|
|
id = Ellipsis # make sure it fails all over the place if used
|
2020-11-20 09:48:50 +01:00
|
|
|
is_admin = True
|
2023-05-01 10:49:50 +02:00
|
|
|
is_api_user = True
|
2022-08-30 10:22:55 +02:00
|
|
|
get_roles = lambda x: []
|
2021-02-04 10:37:40 +01:00
|
|
|
|
2020-11-20 09:48:50 +01:00
|
|
|
get_request()._user = ApiAdminUser()
|
|
|
|
return True
|
|
|
|
return super().check_access(api_name=api_name)
|
|
|
|
|
2020-09-25 10:09:22 +02:00
|
|
|
def schema(self):
|
2023-05-09 13:34:01 +02:00
|
|
|
if is_url_signed() or self.formdef.has_admin_access(get_user_from_api_query_string()):
|
2021-10-23 11:02:30 +02:00
|
|
|
get_response().set_content_type('application/json')
|
2022-09-12 08:50:39 +02:00
|
|
|
return self.formdef.export_to_json(with_user_fields=True)
|
2021-10-23 11:02:30 +02:00
|
|
|
raise AccessForbiddenError()
|
2019-07-23 21:34:14 +02:00
|
|
|
|
2021-02-22 16:15:12 +01:00
|
|
|
def submit(self):
|
|
|
|
get_response().set_content_type('application/json')
|
2021-05-30 22:10:19 +02:00
|
|
|
|
2021-02-22 16:15:12 +01:00
|
|
|
user = get_user_from_api_query_string()
|
2021-05-30 22:10:19 +02:00
|
|
|
if user and user.is_api_user:
|
|
|
|
pass # API users are ok
|
|
|
|
else:
|
|
|
|
get_request()._user = user
|
2021-02-22 16:15:12 +01:00
|
|
|
json_input = get_request().json
|
|
|
|
formdata = self.formdef.data_class()()
|
|
|
|
|
2021-03-26 09:08:21 +01:00
|
|
|
if not (user and self.formdef.can_user_add_cards(user)):
|
2021-02-22 16:15:12 +01:00
|
|
|
raise AccessForbiddenError('cannot create card')
|
|
|
|
|
2022-06-03 17:06:01 +02:00
|
|
|
if not isinstance(json_input, dict):
|
|
|
|
raise RequestError('invalid payload')
|
|
|
|
|
2021-02-22 16:15:12 +01:00
|
|
|
if 'data' in json_input:
|
|
|
|
# the published API expects data in 'data'.
|
|
|
|
data = json_input['data']
|
|
|
|
elif 'fields' in json_input:
|
|
|
|
# but the API also supports data in 'fields', to match the json
|
|
|
|
# output produded by wf/wscall.py.
|
|
|
|
data = json_input['fields']
|
|
|
|
if 'workflow' in json_input and json_input['workflow'].get('fields'):
|
|
|
|
# handle workflow fields, put them all in the same data dictionary.
|
|
|
|
data.update(json_input['workflow']['fields'])
|
|
|
|
if 'extra' in json_input:
|
|
|
|
data.update(json_input['extra'])
|
|
|
|
else:
|
|
|
|
data = {}
|
|
|
|
|
2022-05-06 10:52:54 +02:00
|
|
|
if not isinstance(data, dict):
|
|
|
|
raise RequestError('invalid data parameter')
|
|
|
|
|
2021-02-22 16:15:12 +01:00
|
|
|
formdata.data = posted_json_data_to_formdata_data(self.formdef, data)
|
|
|
|
|
|
|
|
if 'user' in json_input:
|
2022-11-01 11:35:57 +01:00
|
|
|
if not isinstance(json_input['user'], dict):
|
|
|
|
raise RequestError('invalid user parameter')
|
2022-07-18 16:18:25 +02:00
|
|
|
formdata.set_user_from_json(json_input['user'])
|
2021-04-17 14:40:28 +02:00
|
|
|
elif user and not user.is_api_user:
|
2021-02-22 16:15:12 +01:00
|
|
|
formdata.user_id = user.id
|
|
|
|
|
|
|
|
formdata.store()
|
|
|
|
formdata.just_created()
|
|
|
|
formdata.store()
|
2024-01-22 13:06:24 +01:00
|
|
|
formdata.refresh_from_storage()
|
2022-12-30 10:04:45 +01:00
|
|
|
formdata.record_workflow_event('api-created')
|
|
|
|
formdata.perform_workflow()
|
2021-02-22 16:15:12 +01:00
|
|
|
formdata.store()
|
|
|
|
return json.dumps(
|
|
|
|
{
|
|
|
|
'err': 0,
|
|
|
|
'data': {
|
2021-05-31 21:14:57 +02:00
|
|
|
'id': str(formdata.id),
|
2021-02-22 16:15:12 +01:00
|
|
|
'url': formdata.get_url(),
|
|
|
|
'backoffice_url': formdata.get_url(backoffice=True),
|
|
|
|
'api_url': formdata.get_api_url(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-11-03 11:08:04 +01:00
|
|
|
def import_csv(self):
|
2022-07-18 16:18:25 +02:00
|
|
|
return self.import_file('csv')
|
|
|
|
|
|
|
|
def import_json(self):
|
|
|
|
return self.import_file('json')
|
|
|
|
|
|
|
|
def import_file(self, file_format):
|
2023-09-08 11:13:18 +02:00
|
|
|
if get_request().get_method() not in ('POST', 'PUT'):
|
|
|
|
raise MethodNotAllowedError(allowed_methods=['POST', 'PUT'])
|
2020-11-03 11:08:04 +01:00
|
|
|
get_request()._user = get_user_from_api_query_string()
|
2021-03-26 09:08:21 +01:00
|
|
|
if not (get_request()._user and self.formdef.can_user_add_cards(get_request()._user)):
|
2020-11-03 11:08:04 +01:00
|
|
|
raise AccessForbiddenError('cannot import cards')
|
|
|
|
|
2021-03-17 15:28:09 +01:00
|
|
|
afterjob = bool(get_request().form.get('async') == 'on')
|
2023-01-23 14:53:15 +01:00
|
|
|
do_update = bool(get_request().form.get('update') == 'on')
|
2020-11-03 11:08:04 +01:00
|
|
|
get_response().set_content_type('application/json')
|
|
|
|
try:
|
2022-07-18 16:18:25 +02:00
|
|
|
if file_format == 'csv':
|
2023-09-08 11:13:18 +02:00
|
|
|
if get_request().get_method() == 'POST':
|
|
|
|
try:
|
|
|
|
content = base64.decodebytes(force_bytes(get_request().json['file']['content']))
|
|
|
|
except (ValueError, KeyError):
|
|
|
|
return json.dumps(
|
|
|
|
{'err': 1, 'err_desc': 'Invalid format (must be {"file": {"content": base64}})'}
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
content = get_request().stdin.read()
|
2022-07-18 16:18:25 +02:00
|
|
|
job = self.import_csv_submit(content, afterjob=afterjob, api=True)
|
|
|
|
elif file_format == 'json':
|
2023-01-23 14:53:15 +01:00
|
|
|
job = self.import_json_submit(
|
|
|
|
get_request().json, update_existing_cards=do_update, afterjob=afterjob, api=True
|
|
|
|
)
|
2020-11-03 11:08:04 +01:00
|
|
|
except ValueError as e:
|
|
|
|
return json.dumps({'err': 1, 'err_desc': str(e)})
|
2021-03-17 15:28:09 +01:00
|
|
|
if job is None:
|
|
|
|
return json.dumps({'err': 0})
|
|
|
|
return json.dumps(
|
|
|
|
{
|
|
|
|
'err': 0,
|
|
|
|
'data': {
|
|
|
|
'job': {
|
2021-05-31 21:14:57 +02:00
|
|
|
'id': str(job.id),
|
2021-03-17 16:22:05 +01:00
|
|
|
'url': get_publisher().get_frontoffice_url() + '/api/jobs/%s/' % job.id,
|
2021-03-17 15:28:09 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
2020-11-03 11:08:04 +01:00
|
|
|
|
2019-07-23 21:34:14 +02:00
|
|
|
|
2023-05-16 15:41:39 +02:00
|
|
|
class CardFileByTokenDirectory(Directory):
|
|
|
|
def _q_lookup(self, component):
|
|
|
|
get_request().ignore_session = True
|
|
|
|
try:
|
2023-07-15 07:18:05 +02:00
|
|
|
token = get_session().get_token('card-file-by-token', component)
|
|
|
|
except KeyError:
|
2023-05-16 15:41:39 +02:00
|
|
|
raise TraversalError()
|
|
|
|
|
2023-07-15 07:18:05 +02:00
|
|
|
context = token.data
|
|
|
|
carddef = CardDef.get_by_urlname(context['carddef_slug'])
|
|
|
|
data = carddef.data_class().get(context['data_id'])
|
2024-04-23 14:15:07 +02:00
|
|
|
for field_data in data.get_all_file_data(with_history=True):
|
2023-07-15 07:18:05 +02:00
|
|
|
if not hasattr(field_data, 'file_digest'):
|
|
|
|
continue
|
|
|
|
if field_data.file_digest() == context['file_digest']:
|
|
|
|
return FileDirectory.serve_file(field_data)
|
|
|
|
raise TraversalError()
|
|
|
|
|
2023-05-16 15:41:39 +02:00
|
|
|
|
2023-11-08 15:42:00 +01:00
|
|
|
class SignUrlTokenDirectory(Directory):
|
|
|
|
def _q_lookup(self, component):
|
|
|
|
get_request().ignore_session = True
|
|
|
|
try:
|
|
|
|
token = get_session().get_token('sign-url-token', component)
|
|
|
|
except KeyError:
|
|
|
|
raise TraversalError()
|
|
|
|
return redirect(sign_url_auto_orig(token.data['url']))
|
|
|
|
|
|
|
|
|
2015-03-26 23:16:59 +01:00
|
|
|
class ApiFormsDirectory(Directory):
|
2017-05-28 00:17:55 +02:00
|
|
|
_q_exports = ['', 'geojson']
|
|
|
|
|
|
|
|
def check_access(self):
|
|
|
|
if not is_url_signed():
|
2021-05-10 19:21:29 +02:00
|
|
|
api_user = get_user_from_api_query_string()
|
|
|
|
if api_user and api_user.is_api_user:
|
|
|
|
# API users are ok
|
|
|
|
return
|
|
|
|
|
2017-05-28 00:17:55 +02:00
|
|
|
# grant access to admins, to ease debug
|
|
|
|
if not (get_request().user and get_request().user.is_admin):
|
|
|
|
raise AccessForbiddenError('user not authenticated')
|
2019-10-24 11:37:40 +02:00
|
|
|
ignore_roles = get_query_flag('ignore-roles')
|
|
|
|
if ignore_roles and not get_request().user.can_go_in_backoffice():
|
2018-11-20 18:01:56 +01:00
|
|
|
raise AccessForbiddenError('user not allowed to ignore roles')
|
2017-05-28 00:17:55 +02:00
|
|
|
|
|
|
|
def _q_index(self):
|
|
|
|
self.check_access()
|
2019-11-16 11:33:32 +01:00
|
|
|
get_request()._user = get_user_from_api_query_string() or get_request().user
|
2017-05-28 00:17:55 +02:00
|
|
|
|
2021-05-11 20:33:05 +02:00
|
|
|
if get_request().form.get('full') == 'on':
|
|
|
|
raise RequestError('no such parameter "full"')
|
|
|
|
|
2022-05-17 18:10:20 +02:00
|
|
|
if not (FormDef.exists()):
|
2019-04-17 11:57:13 +02:00
|
|
|
# early return, this avoids running a query against a missing SQL view.
|
|
|
|
get_response().set_content_type('application/json')
|
2019-11-13 11:13:04 +01:00
|
|
|
return json.dumps({'data': []}, cls=misc.JSONEncoder)
|
2019-04-17 11:57:13 +02:00
|
|
|
|
2017-05-28 00:17:55 +02:00
|
|
|
from wcs import sql
|
|
|
|
|
|
|
|
management_directory = ManagementDirectory()
|
2021-09-03 19:52:46 +02:00
|
|
|
criterias = management_directory.get_global_listing_criterias()
|
|
|
|
if get_query_flag('ignore-roles'):
|
2018-11-20 18:01:56 +01:00
|
|
|
roles_criterias = criterias
|
2021-09-03 19:52:46 +02:00
|
|
|
criterias = management_directory.get_global_listing_criterias(ignore_user_roles=True)
|
2017-05-28 00:17:55 +02:00
|
|
|
|
2024-02-06 13:49:10 +01:00
|
|
|
if not get_query_flag('include-anonymised', default=False):
|
2023-05-02 12:22:40 +02:00
|
|
|
criterias.append(Null('anonymised'))
|
2019-10-24 14:37:01 +02:00
|
|
|
|
2023-07-28 16:07:54 +02:00
|
|
|
related_filter = get_request().form.get('related')
|
|
|
|
if related_filter:
|
|
|
|
try:
|
|
|
|
formdef_type, formdef_slug, formdata_id = related_filter.split(':')
|
|
|
|
key = f'{formdef_type}:{formdef_slug}'
|
|
|
|
criterias.append(ElementIntersects('relations_data', key, [formdata_id]))
|
|
|
|
except ValueError:
|
|
|
|
criterias.append(Nothing())
|
|
|
|
|
2020-05-13 12:42:13 +02:00
|
|
|
limit = misc.get_int_or_400(
|
2018-12-08 08:25:24 +01:00
|
|
|
get_request().form.get('limit', get_publisher().get_site_option('default-page-size') or 20)
|
2021-02-04 10:37:40 +01:00
|
|
|
)
|
2020-05-13 12:42:13 +02:00
|
|
|
offset = misc.get_int_or_400(get_request().form.get('offset', 0))
|
2020-05-13 12:51:07 +02:00
|
|
|
order_by = misc.get_order_by_or_400(
|
|
|
|
get_request().form.get(
|
|
|
|
'order_by', get_publisher().get_site_option('default-sort-order') or '-receipt_time'
|
|
|
|
)
|
2021-02-04 10:37:40 +01:00
|
|
|
)
|
2017-05-28 00:17:55 +02:00
|
|
|
|
2018-11-20 18:01:56 +01:00
|
|
|
formdatas = sql.AnyFormData.select(criterias, order_by=order_by, limit=limit, offset=offset)
|
2021-09-03 19:52:46 +02:00
|
|
|
if get_query_flag('ignore-roles'):
|
2018-11-20 18:01:56 +01:00
|
|
|
# When ignoring roles formdatas will be returned even if they are
|
|
|
|
# not readable by the user, an additional attribute (readable) is
|
|
|
|
# added to differentiate readable and non-readable formdatas.
|
|
|
|
#
|
|
|
|
# A full SQL query is run as it will benefit from cached
|
|
|
|
# concerned_roles/action_roles.
|
|
|
|
limited_formdatas = [
|
|
|
|
(x.formdef.id, x.id)
|
|
|
|
for x in sql.AnyFormData.select(
|
|
|
|
roles_criterias, order_by=order_by, limit=limit, offset=offset
|
2021-02-04 10:37:40 +01:00
|
|
|
)
|
2018-11-20 18:01:56 +01:00
|
|
|
]
|
2021-09-03 19:52:46 +02:00
|
|
|
output = []
|
|
|
|
for formdata in formdatas:
|
2018-11-20 18:01:56 +01:00
|
|
|
readable = bool((formdata.formdef.id, formdata.id) in limited_formdatas)
|
|
|
|
if not readable and formdata.formdef.skip_from_360_view:
|
|
|
|
continue
|
2021-09-03 19:52:46 +02:00
|
|
|
formdata_dict = get_formdata_dict(
|
|
|
|
formdata, user=get_request().user, consider_status_visibility=False
|
|
|
|
)
|
|
|
|
formdata_dict['readable'] = readable
|
|
|
|
output.append(formdata_dict)
|
|
|
|
else:
|
|
|
|
output = [
|
|
|
|
get_formdata_dict(x, user=get_request().user, consider_status_visibility=False)
|
|
|
|
for x in formdatas
|
|
|
|
]
|
2017-05-28 00:17:55 +02:00
|
|
|
|
|
|
|
get_response().set_content_type('application/json')
|
2019-11-13 11:13:04 +01:00
|
|
|
return json.dumps({'data': output}, cls=misc.JSONEncoder)
|
2017-05-28 00:17:55 +02:00
|
|
|
|
|
|
|
def geojson(self):
|
|
|
|
self.check_access()
|
2019-11-16 11:33:32 +01:00
|
|
|
get_request()._user = get_user_from_api_query_string() or get_request().user
|
2017-05-28 00:17:55 +02:00
|
|
|
return ManagementDirectory().geojson()
|
|
|
|
|
2015-03-26 23:16:59 +01:00
|
|
|
def _q_lookup(self, component):
|
|
|
|
return ApiFormPage(component)
|
|
|
|
|
|
|
|
|
2019-07-23 21:34:14 +02:00
|
|
|
class ApiCardsDirectory(Directory):
|
2020-04-27 14:00:06 +02:00
|
|
|
_q_exports = [('@list', 'list')]
|
|
|
|
|
|
|
|
def list(self):
|
2020-10-29 15:59:36 +01:00
|
|
|
def get_custom_views(carddef):
|
|
|
|
custom_views = []
|
2022-03-24 09:35:21 +01:00
|
|
|
for view in get_publisher().custom_view_class.select(
|
|
|
|
clause=[StrictNotEqual('visibility', 'owner')]
|
|
|
|
):
|
2020-10-29 15:59:36 +01:00
|
|
|
if view.match(user=None, formdef=carddef):
|
|
|
|
custom_views.append({'id': view.slug, 'text': view.title})
|
|
|
|
custom_views.sort(key=lambda x: misc.simplify(x['text']))
|
|
|
|
return custom_views
|
|
|
|
|
2020-04-27 14:00:06 +02:00
|
|
|
get_response().set_content_type('application/json')
|
2024-01-06 09:25:05 +01:00
|
|
|
if not is_url_signed():
|
|
|
|
user = get_user_from_api_query_string() or get_request().user
|
|
|
|
if not get_publisher().get_backoffice_root().is_global_accessible('cards', user=user):
|
|
|
|
raise AccessForbiddenError('unsigned request or API user has no access to cards')
|
2020-04-27 14:00:06 +02:00
|
|
|
carddefs = CardDef.select(order_by='name', ignore_errors=True, lightweight=True)
|
|
|
|
data = [
|
|
|
|
{
|
|
|
|
'id': x.url_name,
|
|
|
|
'text': x.name,
|
|
|
|
'title': x.name,
|
|
|
|
'slug': x.url_name,
|
|
|
|
'url': x.get_url(),
|
2020-11-02 17:40:49 +01:00
|
|
|
'category_slug': x.category.url_name if x.category else None,
|
|
|
|
'category_name': x.category.name if x.category else None,
|
2020-04-27 14:00:06 +02:00
|
|
|
'description': x.description or '',
|
|
|
|
'keywords': x.keywords_list,
|
2020-10-29 15:59:36 +01:00
|
|
|
'custom_views': get_custom_views(x),
|
2020-04-27 14:00:06 +02:00
|
|
|
}
|
|
|
|
for x in carddefs
|
|
|
|
]
|
|
|
|
return json.dumps({'data': data, 'err': 0})
|
|
|
|
|
2019-07-23 21:34:14 +02:00
|
|
|
def _q_lookup(self, component):
|
|
|
|
return ApiCardPage(component)
|
|
|
|
|
|
|
|
|
2015-09-18 15:21:24 +02:00
|
|
|
class ApiFormdefDirectory(Directory):
|
2015-09-19 19:12:49 +02:00
|
|
|
_q_exports = ['schema', 'submit']
|
2015-09-18 15:21:24 +02:00
|
|
|
|
|
|
|
def __init__(self, formdef):
|
|
|
|
self.formdef = formdef
|
|
|
|
|
|
|
|
def schema(self):
|
2023-05-09 13:34:01 +02:00
|
|
|
if is_url_signed() or self.formdef.has_admin_access(get_user_from_api_query_string()):
|
2021-10-23 11:02:30 +02:00
|
|
|
get_response().set_content_type('application/json')
|
|
|
|
return self.formdef.export_to_json()
|
|
|
|
raise AccessForbiddenError()
|
2015-09-18 15:21:24 +02:00
|
|
|
|
2015-09-19 19:12:49 +02:00
|
|
|
def submit(self):
|
|
|
|
# expects json as input
|
|
|
|
# {
|
|
|
|
# "meta": {
|
|
|
|
# "attr": "value"
|
|
|
|
# },
|
|
|
|
# "data": {
|
|
|
|
# "0": "value",
|
|
|
|
# "1": "value",
|
|
|
|
# ...
|
|
|
|
# }
|
|
|
|
# }
|
|
|
|
get_response().set_content_type('application/json')
|
|
|
|
if self.formdef.is_disabled():
|
|
|
|
raise AccessForbiddenError('disabled form')
|
2016-04-29 11:04:47 +02:00
|
|
|
user = get_user_from_api_query_string()
|
2021-09-14 15:49:48 +02:00
|
|
|
if user and user.is_api_user:
|
|
|
|
pass # API users are ok
|
2015-09-19 19:12:49 +02:00
|
|
|
json_input = get_request().json
|
|
|
|
formdata = self.formdef.data_class()()
|
2016-04-29 11:04:47 +02:00
|
|
|
|
2022-06-03 17:06:01 +02:00
|
|
|
if not isinstance(json_input, dict):
|
|
|
|
raise RequestError('invalid payload')
|
|
|
|
|
2016-04-29 11:04:47 +02:00
|
|
|
if 'data' in json_input:
|
|
|
|
# the published API expects data in 'data'.
|
|
|
|
data = json_input['data']
|
|
|
|
elif 'fields' in json_input:
|
|
|
|
# but the API also supports data in 'fields', to match the json
|
|
|
|
# output produded by wf/wscall.py.
|
|
|
|
data = json_input['fields']
|
2017-01-27 14:38:58 +01:00
|
|
|
if 'workflow' in json_input and json_input['workflow'].get('fields'):
|
|
|
|
# handle workflow fields, put them all in the same data dictionary.
|
|
|
|
data.update(json_input['workflow']['fields'])
|
2017-01-27 14:45:00 +01:00
|
|
|
if 'extra' in json_input:
|
|
|
|
data.update(json_input['extra'])
|
2016-04-29 11:04:47 +02:00
|
|
|
else:
|
|
|
|
data = {}
|
|
|
|
|
2022-05-06 10:52:54 +02:00
|
|
|
if not isinstance(data, dict):
|
|
|
|
raise RequestError('invalid data parameter')
|
|
|
|
|
2016-05-02 12:41:33 +02:00
|
|
|
formdata.data = posted_json_data_to_formdata_data(self.formdef, data)
|
2016-04-29 11:04:47 +02:00
|
|
|
|
2015-09-19 19:12:49 +02:00
|
|
|
meta = json_input.get('meta') or {}
|
2021-09-14 15:49:48 +02:00
|
|
|
if meta.get('backoffice-submission') or user and user.is_api_user:
|
2016-04-29 11:04:47 +02:00
|
|
|
if not user:
|
|
|
|
raise AccessForbiddenError('no user set for backoffice submission')
|
2015-09-19 19:12:49 +02:00
|
|
|
if not self.formdef.backoffice_submission_roles:
|
|
|
|
raise AccessForbiddenError('no backoffice submission roles')
|
2019-09-03 10:55:53 +02:00
|
|
|
if not set(user.get_roles()).intersection(self.formdef.backoffice_submission_roles):
|
2015-09-19 19:12:49 +02:00
|
|
|
raise AccessForbiddenError('not cleared for backoffice submit')
|
|
|
|
formdata.backoffice_submission = True
|
2021-09-14 15:49:48 +02:00
|
|
|
if not meta.get('backoffice-submission'):
|
|
|
|
if 'user' in json_input:
|
2022-11-01 11:35:57 +01:00
|
|
|
if not isinstance(json_input['user'], dict):
|
|
|
|
raise RequestError('invalid user parameter')
|
2022-07-18 16:18:25 +02:00
|
|
|
formdata.set_user_from_json(json_input['user'])
|
2021-09-14 15:49:48 +02:00
|
|
|
elif user and not user.is_api_user:
|
|
|
|
formdata.user_id = user.id
|
2021-04-17 14:40:28 +02:00
|
|
|
|
2015-09-19 18:08:43 +02:00
|
|
|
if json_input.get('context'):
|
|
|
|
formdata.submission_context = json_input['context']
|
2015-11-08 19:44:08 +01:00
|
|
|
formdata.submission_channel = formdata.submission_context.pop('channel', None)
|
2015-11-10 15:17:05 +01:00
|
|
|
formdata.user_id = formdata.submission_context.pop('user_id', None)
|
2016-04-29 14:56:42 +02:00
|
|
|
|
2016-05-25 09:32:03 +02:00
|
|
|
if self.formdef.only_allow_one and formdata.user_id:
|
|
|
|
user_id = formdata.user_id
|
2023-10-01 10:02:16 +02:00
|
|
|
user_forms = self.formdef.data_class().select([Equal('user_id', str(user_id))])
|
2018-04-13 16:05:59 +02:00
|
|
|
if [x for x in user_forms if not x.is_draft()]:
|
2016-05-25 09:32:03 +02:00
|
|
|
raise AccessForbiddenError('only one formdata by user is allowed')
|
|
|
|
|
2021-09-14 15:49:48 +02:00
|
|
|
if meta.get('backoffice-submission') and not user.is_api_user:
|
2016-04-29 14:56:42 +02:00
|
|
|
# keep track of the agent that did the submit
|
2020-07-13 15:33:39 +02:00
|
|
|
formdata.submission_agent_id = str(user.id)
|
2016-04-29 14:56:42 +02:00
|
|
|
|
2015-09-19 19:12:49 +02:00
|
|
|
formdata.store()
|
|
|
|
if self.formdef.enable_tracking_codes:
|
|
|
|
code = get_publisher().tracking_code_class()
|
|
|
|
code.formdata = formdata # this will .store() the code
|
|
|
|
if meta.get('draft'):
|
|
|
|
formdata.status = 'draft'
|
2024-02-10 12:24:29 +01:00
|
|
|
formdata.receipt_time = localtime()
|
2015-09-19 19:12:49 +02:00
|
|
|
formdata.store()
|
|
|
|
else:
|
|
|
|
formdata.just_created()
|
|
|
|
formdata.store()
|
2024-01-22 13:06:24 +01:00
|
|
|
formdata.refresh_from_storage()
|
2022-12-30 10:04:45 +01:00
|
|
|
formdata.record_workflow_event('api-created')
|
|
|
|
formdata.perform_workflow()
|
2015-09-19 19:12:49 +02:00
|
|
|
formdata.store()
|
2018-05-19 16:05:39 +02:00
|
|
|
return json.dumps(
|
|
|
|
{
|
|
|
|
'err': 0,
|
|
|
|
'data': {
|
2021-05-31 21:14:57 +02:00
|
|
|
'id': str(formdata.id),
|
2018-05-19 16:05:39 +02:00
|
|
|
'url': formdata.get_url(),
|
|
|
|
'backoffice_url': formdata.get_url(backoffice=True),
|
|
|
|
'api_url': formdata.get_api_url(),
|
2021-02-04 10:37:40 +01:00
|
|
|
},
|
2018-05-19 16:05:39 +02:00
|
|
|
}
|
|
|
|
)
|
2015-09-19 19:12:49 +02:00
|
|
|
|
2015-09-18 15:21:24 +02:00
|
|
|
|
|
|
|
class ApiFormdefsDirectory(Directory):
|
|
|
|
_q_exports = ['']
|
|
|
|
|
|
|
|
def __init__(self, category=None):
|
2015-09-23 21:55:35 +02:00
|
|
|
self.category = category
|
2015-09-18 15:21:24 +02:00
|
|
|
|
2017-07-28 17:52:13 +02:00
|
|
|
def get_list_forms(self, user, list_all_forms=False, formdefs=None, backoffice_submission=False):
|
2015-09-18 15:21:24 +02:00
|
|
|
list_forms = []
|
2017-07-28 17:52:13 +02:00
|
|
|
|
|
|
|
if not user and backoffice_submission:
|
|
|
|
return list_forms
|
|
|
|
|
2023-09-23 09:21:14 +02:00
|
|
|
if get_request().form.get('q'):
|
|
|
|
from wcs import sql
|
|
|
|
|
|
|
|
object_ids = sql.SearchableFormDef.search(FormDef.xml_root_node, get_request().form.get('q'))
|
|
|
|
if formdefs is None:
|
|
|
|
formdefs = FormDef.get_ids(object_ids, ignore_errors=True, lightweight=True)
|
|
|
|
else:
|
|
|
|
formdefs = [x for x in formdefs if str(x.id) in object_ids]
|
|
|
|
elif formdefs is None:
|
2018-04-09 12:21:44 +02:00
|
|
|
formdefs = FormDef.select(order_by='name', ignore_errors=True, lightweight=True)
|
2015-11-19 15:32:52 +01:00
|
|
|
|
2020-08-14 13:56:08 +02:00
|
|
|
include_disabled = get_query_flag('include-disabled')
|
2021-04-26 14:54:43 +02:00
|
|
|
category_slugs = (get_request().form.get('category_slugs') or '').split(',')
|
|
|
|
category_slugs = [c.strip() for c in category_slugs if c.strip()]
|
2020-08-14 13:56:08 +02:00
|
|
|
|
|
|
|
if not include_disabled:
|
|
|
|
if backoffice_submission:
|
|
|
|
formdefs = [x for x in formdefs if not x.is_disabled()]
|
|
|
|
else:
|
|
|
|
formdefs = [x for x in formdefs if not x.is_disabled() or x.disabled_redirection]
|
2015-11-19 15:32:52 +01:00
|
|
|
|
2015-09-18 15:21:24 +02:00
|
|
|
if self.category:
|
2015-11-19 15:32:52 +01:00
|
|
|
formdefs = [x for x in formdefs if str(x.category_id) == str(self.category.id)]
|
2021-04-26 14:54:43 +02:00
|
|
|
elif category_slugs:
|
|
|
|
formdefs = [x for x in formdefs if x.category and (x.category.url_name in category_slugs)]
|
2015-09-18 15:21:24 +02:00
|
|
|
|
2019-10-24 11:37:40 +02:00
|
|
|
include_count = get_query_flag('include-count')
|
2018-01-13 22:24:23 +01:00
|
|
|
|
2015-09-18 15:21:24 +02:00
|
|
|
for formdef in formdefs:
|
|
|
|
authentication_required = False
|
2017-07-28 17:52:13 +02:00
|
|
|
if formdef.roles and not list_all_forms and not backoffice_submission:
|
2015-09-18 15:21:24 +02:00
|
|
|
if not user:
|
|
|
|
if not formdef.always_advertise:
|
|
|
|
continue
|
|
|
|
authentication_required = True
|
|
|
|
elif logged_users_role().id not in formdef.roles:
|
2019-09-03 10:55:53 +02:00
|
|
|
for q in user.get_roles():
|
2015-09-18 15:21:24 +02:00
|
|
|
if q in formdef.roles:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
if not formdef.always_advertise:
|
|
|
|
continue
|
|
|
|
authentication_required = True
|
2018-08-09 22:22:16 +02:00
|
|
|
elif backoffice_submission:
|
2017-07-28 17:52:13 +02:00
|
|
|
if not formdef.backoffice_submission_roles:
|
|
|
|
continue
|
2019-10-16 16:17:35 +02:00
|
|
|
for role in user.get_roles():
|
|
|
|
if role in formdef.backoffice_submission_roles:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
continue
|
2018-06-14 10:29:00 +02:00
|
|
|
elif formdef.roles and user is None and list_all_forms:
|
|
|
|
# anonymous API call, mark authentication as required
|
|
|
|
authentication_required = True
|
2015-09-18 15:21:24 +02:00
|
|
|
|
2019-11-11 20:10:57 +01:00
|
|
|
formdict = {
|
2024-01-13 16:08:33 +01:00
|
|
|
'title': formdef.name,
|
2015-09-18 15:21:24 +02:00
|
|
|
'slug': formdef.url_name,
|
|
|
|
'url': formdef.get_url(),
|
2015-09-18 15:43:22 +02:00
|
|
|
'description': formdef.description or '',
|
2015-11-07 14:05:02 +01:00
|
|
|
'keywords': formdef.keywords_list,
|
2020-08-25 10:31:58 +02:00
|
|
|
'authentication_required': authentication_required,
|
|
|
|
'always_advertise': formdef.always_advertise,
|
|
|
|
}
|
2017-01-10 10:52:40 +01:00
|
|
|
if formdef.required_authentication_contexts:
|
|
|
|
formdict['required_authentication_contexts'] = formdef.required_authentication_contexts
|
2018-08-09 22:41:24 +02:00
|
|
|
if backoffice_submission:
|
|
|
|
formdict['backoffice_submission_url'] = formdef.get_backoffice_submission_url()
|
2015-09-18 15:21:24 +02:00
|
|
|
|
|
|
|
formdict['redirection'] = bool(formdef.is_disabled() and formdef.disabled_redirection)
|
|
|
|
|
2018-01-13 22:24:23 +01:00
|
|
|
if include_count:
|
|
|
|
# we include the count of submitted forms so it's possible to sort
|
|
|
|
# them by "popularity"
|
2022-07-10 21:32:33 +02:00
|
|
|
from wcs import sql
|
|
|
|
|
|
|
|
# 4 * number of submitted forms of last 2 days
|
|
|
|
# + 2 * number of submitted forms of last 8 days
|
|
|
|
# + 1 * number of submitted forms of last 30 days
|
|
|
|
# exclude drafts
|
|
|
|
criterias = [Equal('formdef_id', formdef.id), StrictNotEqual('status', 'draft')]
|
|
|
|
d_now = datetime.datetime.now()
|
|
|
|
count = 4 * sql.get_period_total(
|
|
|
|
period_start=d_now - datetime.timedelta(days=2),
|
|
|
|
include_start=True,
|
|
|
|
criterias=criterias,
|
|
|
|
)
|
|
|
|
count += 2 * sql.get_period_total(
|
|
|
|
period_start=d_now - datetime.timedelta(days=8),
|
|
|
|
include_start=True,
|
|
|
|
period_end=d_now - datetime.timedelta(days=2),
|
|
|
|
include_end=False,
|
|
|
|
criterias=criterias,
|
|
|
|
)
|
|
|
|
count += sql.get_period_total(
|
|
|
|
period_start=d_now - datetime.timedelta(days=30),
|
|
|
|
include_start=True,
|
|
|
|
period_end=d_now - datetime.timedelta(days=8),
|
|
|
|
include_end=False,
|
|
|
|
criterias=criterias,
|
|
|
|
)
|
2020-12-07 09:50:04 +01:00
|
|
|
formdict['count'] = count
|
2015-09-18 15:21:24 +02:00
|
|
|
|
2015-10-09 16:56:28 +02:00
|
|
|
formdict['functions'] = {}
|
|
|
|
formdef_workflow_roles = formdef.workflow_roles or {}
|
2023-03-15 16:12:35 +01:00
|
|
|
for wf_role_id, wf_role_label in formdef.workflow.roles.items():
|
2015-10-09 16:56:28 +02:00
|
|
|
workflow_function = {'label': wf_role_label}
|
2015-10-11 16:29:37 +02:00
|
|
|
role_id = formdef_workflow_roles.get(wf_role_id)
|
|
|
|
if role_id:
|
|
|
|
try:
|
2021-03-09 15:35:21 +01:00
|
|
|
workflow_function['role'] = (
|
|
|
|
get_publisher().role_class.get(role_id).get_json_export_dict()
|
|
|
|
)
|
2015-10-11 16:29:37 +02:00
|
|
|
except KeyError:
|
|
|
|
pass
|
2015-10-09 16:56:28 +02:00
|
|
|
formdict['functions'][wf_role_id] = workflow_function
|
|
|
|
|
2015-09-18 15:21:24 +02:00
|
|
|
if formdef.category:
|
2024-01-13 16:08:33 +01:00
|
|
|
formdict['category'] = formdef.category.name
|
|
|
|
formdict['category_slug'] = formdef.category.url_name
|
2015-09-18 15:21:24 +02:00
|
|
|
|
|
|
|
list_forms.append(formdict)
|
|
|
|
|
2015-11-15 18:06:19 +01:00
|
|
|
return list_forms
|
|
|
|
|
|
|
|
def _q_index(self):
|
|
|
|
try:
|
2015-11-24 13:39:28 +01:00
|
|
|
user = get_user_from_api_query_string()
|
2015-11-15 18:06:19 +01:00
|
|
|
except UnknownNameIdAccessForbiddenError:
|
|
|
|
# if authenticating the user via the query string failed, return
|
|
|
|
# results for the anonymous case; user is set to 'False' as a
|
|
|
|
# signed URL with a None user is considered like an appropriate
|
|
|
|
# webservice call.
|
|
|
|
user = False
|
2021-09-13 16:26:38 +02:00
|
|
|
url_signed = is_url_signed()
|
|
|
|
if user and user.is_api_user:
|
|
|
|
pass # API users are ok
|
|
|
|
elif not url_signed:
|
2020-10-06 11:22:52 +02:00
|
|
|
if not (get_request().user and get_request().user.is_admin):
|
|
|
|
raise AccessForbiddenError('user not authenticated')
|
|
|
|
user = get_request().user
|
|
|
|
|
2021-09-13 16:26:38 +02:00
|
|
|
list_all_forms = (user and user.is_admin) or (url_signed and user is None)
|
2023-09-23 09:21:14 +02:00
|
|
|
backoffice_submission = get_query_flag('backoffice-submission')
|
2015-11-15 18:06:19 +01:00
|
|
|
|
2017-07-28 17:52:13 +02:00
|
|
|
list_forms = self.get_list_forms(user, list_all_forms, backoffice_submission=backoffice_submission)
|
2015-11-15 18:06:19 +01:00
|
|
|
|
2015-09-18 15:21:24 +02:00
|
|
|
get_response().set_content_type('application/json')
|
2017-11-04 03:01:13 +01:00
|
|
|
return json.dumps({'err': 0, 'data': list_forms})
|
2015-09-18 15:21:24 +02:00
|
|
|
|
|
|
|
def _q_lookup(self, component):
|
2016-09-30 13:54:55 +02:00
|
|
|
try:
|
|
|
|
formdef = FormDef.get_by_urlname(component)
|
|
|
|
except KeyError:
|
|
|
|
raise TraversalError()
|
|
|
|
return ApiFormdefDirectory(formdef)
|
2015-09-18 15:21:24 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ApiCategoryDirectory(Directory):
|
|
|
|
_q_exports = ['formdefs']
|
|
|
|
|
|
|
|
def __init__(self, category):
|
|
|
|
self.category = category
|
|
|
|
self.formdefs = ApiFormdefsDirectory(category)
|
|
|
|
|
|
|
|
|
2015-11-15 18:06:19 +01:00
|
|
|
class ApiCategoriesDirectory(Directory):
|
2015-09-18 15:21:24 +02:00
|
|
|
_q_exports = ['']
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def _q_index(self):
|
|
|
|
try:
|
|
|
|
user = get_user_from_api_query_string() or get_request().user
|
|
|
|
except UnknownNameIdAccessForbiddenError:
|
|
|
|
# the name id was unknown, return the categories for anonymous
|
|
|
|
# users.
|
|
|
|
user = None
|
2016-04-13 12:26:15 +02:00
|
|
|
list_all_forms = (user and user.is_admin) or (is_url_signed() and user is None)
|
2017-07-28 17:52:13 +02:00
|
|
|
backoffice_submission = get_request().form.get('backoffice-submission') == 'on'
|
2015-09-18 15:21:24 +02:00
|
|
|
list_categories = []
|
2015-11-15 18:06:19 +01:00
|
|
|
categories = Category.select()
|
2015-09-18 15:21:24 +02:00
|
|
|
Category.sort_by_position(categories)
|
2018-04-09 12:21:44 +02:00
|
|
|
all_formdefs = FormDef.select(order_by='name', ignore_errors=True, lightweight=True)
|
2015-09-18 15:21:24 +02:00
|
|
|
for category in categories:
|
|
|
|
d = {}
|
2024-01-13 16:08:33 +01:00
|
|
|
d['title'] = category.name
|
2015-09-18 15:21:24 +02:00
|
|
|
d['slug'] = category.url_name
|
|
|
|
d['url'] = category.get_url()
|
|
|
|
if category.description:
|
2024-01-13 16:08:33 +01:00
|
|
|
d['description'] = str(category.get_description_html_text())
|
2015-11-19 15:32:52 +01:00
|
|
|
formdefs = ApiFormdefsDirectory(category).get_list_forms(
|
|
|
|
user,
|
2017-07-28 17:52:13 +02:00
|
|
|
formdefs=all_formdefs,
|
|
|
|
list_all_forms=list_all_forms,
|
|
|
|
backoffice_submission=backoffice_submission,
|
|
|
|
)
|
2015-11-15 18:06:19 +01:00
|
|
|
if not formdefs:
|
|
|
|
# don't advertise empty categories
|
|
|
|
continue
|
2015-11-12 20:44:54 +01:00
|
|
|
keywords = {}
|
|
|
|
for formdef in formdefs:
|
2015-11-15 18:06:19 +01:00
|
|
|
for keyword in formdef['keywords']:
|
|
|
|
keywords[keyword] = True
|
2019-11-13 09:49:04 +01:00
|
|
|
d['keywords'] = list(keywords.keys())
|
2015-11-15 18:06:19 +01:00
|
|
|
if get_request().form.get('full') == 'on':
|
|
|
|
d['forms'] = formdefs
|
2015-09-18 15:21:24 +02:00
|
|
|
list_categories.append(d)
|
|
|
|
get_response().set_content_type('application/json')
|
|
|
|
return json.dumps({'data': list_categories})
|
|
|
|
|
|
|
|
def _q_lookup(self, component):
|
2016-10-09 11:21:35 +02:00
|
|
|
try:
|
|
|
|
return ApiCategoryDirectory(Category.get_by_urlname(component))
|
|
|
|
except KeyError:
|
|
|
|
raise TraversalError()
|
2015-09-18 15:21:24 +02:00
|
|
|
|
|
|
|
|
2015-09-19 10:11:38 +02:00
|
|
|
class ApiUserDirectory(Directory):
|
2023-09-17 12:45:45 +02:00
|
|
|
_q_exports = ['', 'forms', 'drafts', 'preferences']
|
2015-09-19 10:11:38 +02:00
|
|
|
|
2015-09-24 12:23:22 +02:00
|
|
|
def __init__(self, user=None):
|
|
|
|
self.user = user
|
|
|
|
|
2015-09-19 10:11:38 +02:00
|
|
|
def _q_index(self):
|
|
|
|
get_response().set_content_type('application/json')
|
2015-09-24 12:23:22 +02:00
|
|
|
user = self.user or get_user_from_api_query_string() or get_request().user
|
2015-09-19 10:11:38 +02:00
|
|
|
if not user:
|
|
|
|
raise AccessForbiddenError('no user specified')
|
2021-04-17 14:40:28 +02:00
|
|
|
if user.is_api_user:
|
|
|
|
raise AccessForbiddenError('restricted API access')
|
2015-09-19 10:11:38 +02:00
|
|
|
user_info = user.get_substitution_variables(prefix='')
|
|
|
|
del user_info['user']
|
2015-11-16 14:08:19 +01:00
|
|
|
user_info['id'] = user.id
|
2021-03-09 15:35:21 +01:00
|
|
|
user_roles = [get_publisher().role_class.get(x, ignore_errors=True) for x in user.roles or []]
|
2015-12-28 16:14:59 +01:00
|
|
|
user_info['user_roles'] = [x.get_json_export_dict() for x in user_roles if x]
|
2018-05-10 10:00:47 +02:00
|
|
|
return json.dumps(user_info, cls=misc.JSONEncoder)
|
2015-09-19 10:11:38 +02:00
|
|
|
|
2022-06-08 12:43:45 +02:00
|
|
|
def get_user_forms(self, user, include_drafts=False, include_non_drafts=True):
|
2022-05-17 18:10:20 +02:00
|
|
|
if not (FormDef.exists()):
|
2018-11-19 12:01:33 +01:00
|
|
|
# early return, this avoids running a query against a missing SQL view.
|
|
|
|
return []
|
2019-08-14 14:20:01 +02:00
|
|
|
|
2021-04-26 14:54:43 +02:00
|
|
|
category_slugs = (get_request().form.get('category_slugs') or '').split(',')
|
|
|
|
category_slugs = [c.strip() for c in category_slugs if c.strip()]
|
|
|
|
if category_slugs:
|
2023-05-02 12:22:40 +02:00
|
|
|
categories = Category.select([st.Contains('url_name', category_slugs)])
|
2021-04-26 14:54:43 +02:00
|
|
|
|
2022-07-10 21:32:33 +02:00
|
|
|
from wcs import sql
|
2021-03-19 14:41:24 +01:00
|
|
|
|
2022-07-10 21:32:33 +02:00
|
|
|
order_by = 'receipt_time'
|
|
|
|
if get_request().form.get('sort') == 'desc':
|
|
|
|
order_by = '-receipt_time'
|
|
|
|
if get_query_flag('include-accessible'):
|
|
|
|
user_roles = user.get_roles()
|
|
|
|
criterias = [
|
|
|
|
Or(
|
|
|
|
[
|
|
|
|
Intersects('concerned_roles_array', user_roles),
|
|
|
|
Equal('user_id', str(user.id)),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
criterias = [Equal('user_id', str(user.id))]
|
|
|
|
if category_slugs:
|
|
|
|
criterias.append(Contains('category_id', [c.id for c in categories]))
|
|
|
|
|
|
|
|
status_criteria = get_request().form.get('status') or 'all'
|
|
|
|
if status_criteria == 'open':
|
|
|
|
criterias.append(Equal('is_at_endpoint', False))
|
|
|
|
elif status_criteria == 'done':
|
|
|
|
criterias.append(Equal('is_at_endpoint', True))
|
|
|
|
elif status_criteria == 'all':
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
return HttpResponseBadRequest('invalid status parameter value')
|
|
|
|
|
2023-06-20 14:59:57 +02:00
|
|
|
related_filter = get_request().form.get('related')
|
|
|
|
if related_filter:
|
|
|
|
try:
|
|
|
|
formdef_type, formdef_slug, formdata_id = related_filter.split(':')
|
|
|
|
key = f'{formdef_type}:{formdef_slug}'
|
|
|
|
criterias.append(ElementIntersects('relations_data', key, [formdata_id]))
|
|
|
|
except ValueError:
|
2023-07-28 16:07:54 +02:00
|
|
|
criterias.append(Nothing())
|
2023-06-20 14:59:57 +02:00
|
|
|
|
2022-07-10 21:32:33 +02:00
|
|
|
if include_drafts:
|
|
|
|
disabled_formdef_ids = [formdef.id for formdef in FormDef.select() if formdef.is_disabled()]
|
|
|
|
if disabled_formdef_ids:
|
|
|
|
criterias.append(
|
2021-08-31 15:07:31 +02:00
|
|
|
Or(
|
|
|
|
[
|
2022-07-10 21:32:33 +02:00
|
|
|
StrictNotEqual('status', 'draft'),
|
|
|
|
NotContains('formdef_id', disabled_formdef_ids),
|
2021-08-31 15:07:31 +02:00
|
|
|
]
|
|
|
|
)
|
2022-07-10 21:32:33 +02:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
criterias.append(StrictNotEqual('status', 'draft'))
|
2022-06-08 12:43:45 +02:00
|
|
|
|
2022-07-10 21:32:33 +02:00
|
|
|
if not include_non_drafts:
|
|
|
|
criterias.append(Equal('status', 'draft'))
|
2022-06-08 12:43:45 +02:00
|
|
|
|
2022-07-10 21:32:33 +02:00
|
|
|
user_forms = sql.AnyFormData.select(
|
|
|
|
criterias,
|
|
|
|
limit=misc.get_int_or_400(get_request().form.get('limit')),
|
|
|
|
offset=misc.get_int_or_400(get_request().form.get('offset')),
|
|
|
|
order_by=order_by,
|
|
|
|
)
|
|
|
|
if get_request().form.get('full') == 'on':
|
|
|
|
# load full objects
|
|
|
|
formdefs = {x.formdef_id: x.formdef for x in user_forms}
|
|
|
|
formdef_user_forms = {}
|
|
|
|
for formdef_id, formdef in formdefs.items():
|
|
|
|
formdef_user_forms.update(
|
|
|
|
{
|
|
|
|
(formdef_id, x.id): x
|
|
|
|
for x in formdef.data_class().select(
|
|
|
|
[Contains('id', [x.id for x in user_forms if x.formdef_id == formdef_id])]
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
# and put them back in order
|
|
|
|
sorted_user_forms_tuples = [(x.formdef_id, x.id) for x in user_forms]
|
|
|
|
user_forms = [formdef_user_forms.get(x) for x in sorted_user_forms_tuples]
|
2017-09-26 10:35:22 +02:00
|
|
|
else:
|
2022-07-10 21:32:33 +02:00
|
|
|
# prefetch evolutions to avoid individual loads when computing
|
|
|
|
# formdata.get_visible_status().
|
|
|
|
sql.AnyFormData.load_all_evolutions(user_forms)
|
2015-09-19 10:11:38 +02:00
|
|
|
return user_forms
|
|
|
|
|
|
|
|
def drafts(self):
|
2017-11-28 10:28:19 +01:00
|
|
|
return self.forms(include_drafts=True, include_non_drafts=False)
|
2015-09-19 10:11:38 +02:00
|
|
|
|
2017-11-28 10:28:19 +01:00
|
|
|
def forms(self, include_drafts=False, include_non_drafts=True):
|
2022-06-08 12:43:45 +02:00
|
|
|
include_drafts = include_drafts or get_query_flag('include-drafts')
|
|
|
|
|
2015-09-19 10:11:38 +02:00
|
|
|
get_response().set_content_type('application/json')
|
2017-11-04 03:01:13 +01:00
|
|
|
try:
|
|
|
|
user = self.user or get_user_from_api_query_string() or get_request().user
|
|
|
|
except UnknownNameIdAccessForbiddenError:
|
|
|
|
return json.dumps({'err': 1, 'err_desc': 'unknown NameID', 'data': []})
|
2015-09-19 10:11:38 +02:00
|
|
|
if not user:
|
2017-11-04 03:01:13 +01:00
|
|
|
return json.dumps({'err': 1, 'err_desc': 'no user specified', 'data': []})
|
2021-04-17 14:40:28 +02:00
|
|
|
if user.is_api_user:
|
|
|
|
raise AccessForbiddenError('restricted API access')
|
2018-12-07 18:34:10 +01:00
|
|
|
|
2021-09-14 10:55:19 +02:00
|
|
|
query_user = get_user_from_api_query_string() or get_request().user
|
|
|
|
if query_user and query_user.is_api_user and query_user.api_access.restrict_to_anonymised_data:
|
|
|
|
raise AccessForbiddenError('restricted API access')
|
|
|
|
|
2022-06-08 12:43:45 +02:00
|
|
|
forms = self.get_user_forms(
|
|
|
|
user, include_drafts=include_drafts, include_non_drafts=include_non_drafts
|
|
|
|
)
|
2018-12-07 18:34:10 +01:00
|
|
|
|
|
|
|
if self.user:
|
|
|
|
# call to /api/users/<id>/forms, this returns the forms of the
|
|
|
|
# given user filtered according to the permissions of the caller
|
|
|
|
# (from query string or session).
|
|
|
|
if query_user and query_user.id != self.user.id:
|
2021-09-14 10:55:19 +02:00
|
|
|
if not query_user.is_api_user and not query_user.can_go_in_backoffice():
|
2018-12-07 18:34:10 +01:00
|
|
|
raise AccessForbiddenError('user not allowed to query data from others')
|
|
|
|
# mark forms that are readable by querying user
|
2019-09-03 10:55:53 +02:00
|
|
|
user_roles = set(query_user.get_roles())
|
2022-07-10 21:32:33 +02:00
|
|
|
# use concerned_roles_array attribute that was saved in the
|
|
|
|
# table.
|
|
|
|
for form in forms:
|
|
|
|
form.readable = bool(set(form.concerned_roles_array).intersection(user_roles))
|
2018-12-07 18:34:10 +01:00
|
|
|
# ignore confidential forms
|
|
|
|
forms = [x for x in forms if x.readable or not x.formdef.skip_from_360_view]
|
|
|
|
|
|
|
|
result = []
|
|
|
|
for form in forms:
|
2015-09-19 10:11:38 +02:00
|
|
|
if form.is_draft():
|
2017-11-28 10:28:19 +01:00
|
|
|
if not include_drafts:
|
|
|
|
continue
|
2018-10-19 16:03:16 +02:00
|
|
|
if form.formdef.is_disabled():
|
2017-11-28 10:28:19 +01:00
|
|
|
# the form or its draft support has been disabled
|
|
|
|
continue
|
|
|
|
elif not include_non_drafts:
|
2015-09-19 10:11:38 +02:00
|
|
|
continue
|
2017-05-28 00:17:55 +02:00
|
|
|
formdata_dict = get_formdata_dict(form, user)
|
|
|
|
if not formdata_dict:
|
|
|
|
# skip hidden forms
|
2015-09-19 10:11:38 +02:00
|
|
|
continue
|
2018-12-07 18:34:10 +01:00
|
|
|
formdata_dict['readable'] = getattr(form, 'readable', True)
|
|
|
|
result.append(formdata_dict)
|
2015-09-19 10:11:38 +02:00
|
|
|
|
2019-11-13 11:13:04 +01:00
|
|
|
return json.dumps({'err': 0, 'data': result}, cls=misc.JSONEncoder)
|
2015-09-19 10:11:38 +02:00
|
|
|
|
2023-09-17 12:45:45 +02:00
|
|
|
def preferences(self):
|
|
|
|
if get_request().get_method() != 'POST':
|
|
|
|
raise MethodNotAllowedError(allowed_methods=['POST'])
|
|
|
|
get_response().set_content_type('application/json')
|
|
|
|
user = self.user or get_request().user
|
|
|
|
if not user:
|
|
|
|
raise AccessForbiddenError('user not authenticated')
|
|
|
|
if int(get_request().environ.get('CONTENT_LENGTH')) > 1000:
|
|
|
|
# protect against storing "huge" blobs
|
|
|
|
raise RequestError('too much data')
|
|
|
|
user.update_preferences(get_request().json)
|
|
|
|
return json.dumps({'err': 0})
|
|
|
|
|
2015-09-19 10:11:38 +02:00
|
|
|
|
2015-09-24 10:47:51 +02:00
|
|
|
class ApiUsersDirectory(Directory):
|
|
|
|
_q_exports = ['']
|
|
|
|
|
2022-02-22 19:07:26 +01:00
|
|
|
def can_create_cards(self, user):
|
|
|
|
for role_id in user.roles or []:
|
|
|
|
ids = CardDef.get_ids_with_indexed_value('backoffice_submission_roles', role_id)
|
|
|
|
if ids:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2015-09-24 10:47:51 +02:00
|
|
|
def _q_index(self):
|
|
|
|
get_response().set_content_type('application/json')
|
|
|
|
if not (
|
|
|
|
is_url_signed()
|
|
|
|
or (
|
2020-10-20 15:19:07 +02:00
|
|
|
get_request().user
|
|
|
|
and (
|
|
|
|
get_request().user.can_go_in_admin()
|
|
|
|
or SubmissionDirectory().is_accessible(get_request().user)
|
2022-02-22 19:07:26 +01:00
|
|
|
or self.can_create_cards(get_request().user)
|
2021-02-04 10:37:40 +01:00
|
|
|
)
|
|
|
|
)
|
2020-10-20 15:19:07 +02:00
|
|
|
):
|
|
|
|
# request must be signed, or user must be an administrator or
|
|
|
|
# allowed to submit forms (as they have a form to select an user).
|
2015-11-17 11:52:20 +01:00
|
|
|
raise AccessForbiddenError('unsigned request or user has no access to backoffice')
|
2015-09-24 10:47:51 +02:00
|
|
|
|
2021-05-07 19:37:27 +02:00
|
|
|
api_user = get_user_from_api_query_string()
|
|
|
|
if api_user and api_user.is_api_user:
|
|
|
|
raise AccessForbiddenError('restricted API access')
|
|
|
|
|
2023-05-02 12:22:40 +02:00
|
|
|
criterias = [Null('deleted_timestamp')]
|
2015-09-24 10:47:51 +02:00
|
|
|
query = get_request().form.get('q')
|
|
|
|
if query:
|
2015-11-27 12:17:00 +01:00
|
|
|
formdef = UserFieldsFormDef()
|
2017-03-31 14:42:38 +02:00
|
|
|
criteria_fields = [
|
2023-05-02 12:22:40 +02:00
|
|
|
ILike('name', query),
|
|
|
|
ILike('ascii_name', misc.simplify(query, ' ')),
|
|
|
|
ILike('email', query),
|
2017-03-31 14:42:38 +02:00
|
|
|
]
|
2015-11-27 12:17:00 +01:00
|
|
|
for field in formdef.fields:
|
2023-05-21 18:05:31 +02:00
|
|
|
if field.key in ('string', 'text', 'email'):
|
2023-05-02 12:22:40 +02:00
|
|
|
criteria_fields.append(ILike('f%s' % field.id, query))
|
|
|
|
criteria_fields.append(FtsMatch(query))
|
|
|
|
criterias.append(Or(criteria_fields))
|
2015-09-24 10:47:51 +02:00
|
|
|
|
2024-04-21 13:26:54 +02:00
|
|
|
roles = get_request().form.get('roles')
|
|
|
|
if roles:
|
|
|
|
criterias.append(Intersects('roles', roles.split(',')))
|
|
|
|
|
2015-09-24 10:47:51 +02:00
|
|
|
def as_dict(user):
|
2021-03-30 10:16:20 +02:00
|
|
|
users_cfg = get_cfg('users', {})
|
|
|
|
template = (
|
|
|
|
users_cfg.get('search_result_template')
|
|
|
|
or get_publisher().user_class.default_search_result_template
|
|
|
|
)
|
|
|
|
|
2015-09-24 10:47:51 +02:00
|
|
|
user_info = user.get_substitution_variables(prefix='')
|
|
|
|
del user_info['user']
|
|
|
|
user_info['user_id'] = user.id
|
2021-03-09 15:35:21 +01:00
|
|
|
user_roles = [get_publisher().role_class.get(x, ignore_errors=True) for x in user.roles or []]
|
2016-07-10 20:17:27 +02:00
|
|
|
user_info['user_roles'] = [x.get_json_export_dict() for x in user_roles if x]
|
2018-01-24 10:35:04 +01:00
|
|
|
# add attributes to be usable as datasource
|
|
|
|
user_info['id'] = user.id
|
|
|
|
user_info['text'] = user_info['user_display_name']
|
2021-03-30 10:16:20 +02:00
|
|
|
if Template.is_template_string(template):
|
|
|
|
try:
|
|
|
|
user_info['description'] = Template(template).render(user_info)
|
|
|
|
except TemplateError:
|
|
|
|
pass
|
2015-09-24 10:47:51 +02:00
|
|
|
return user_info
|
|
|
|
|
2020-05-13 12:42:13 +02:00
|
|
|
limit = misc.get_int_or_400(get_request().form.get('limit'))
|
2015-09-24 10:47:51 +02:00
|
|
|
users = get_publisher().user_class.select(order_by='name', clause=criterias, limit=limit)
|
|
|
|
data = [as_dict(x) for x in users]
|
2018-05-10 10:00:47 +02:00
|
|
|
return json.dumps({'data': data, 'err': 0}, cls=misc.JSONEncoder)
|
2015-09-24 10:47:51 +02:00
|
|
|
|
2015-09-24 12:23:22 +02:00
|
|
|
def _q_lookup(self, component):
|
2021-05-07 19:37:27 +02:00
|
|
|
api_user = get_user_from_api_query_string()
|
|
|
|
if api_user and api_user.is_api_user:
|
2021-09-14 10:55:19 +02:00
|
|
|
# API users are ok except if they are restricted to anonymised data
|
|
|
|
if api_user.api_access.restrict_to_anonymised_data:
|
|
|
|
raise AccessForbiddenError('restricted API access')
|
|
|
|
elif not (is_url_signed() or (get_request().user and get_request().user.can_go_in_admin())):
|
|
|
|
raise AccessForbiddenError('unsigned request or user has no access to backoffice')
|
2021-05-07 19:37:27 +02:00
|
|
|
|
2015-11-16 14:08:19 +01:00
|
|
|
user_class = get_publisher().user_class
|
|
|
|
try:
|
|
|
|
int(component) # makes sure this is an id
|
|
|
|
user = user_class.get(component)
|
|
|
|
except (KeyError, ValueError):
|
|
|
|
try:
|
|
|
|
user = user_class.get_users_with_name_identifier(component)[0]
|
|
|
|
except IndexError:
|
|
|
|
raise TraversalError()
|
|
|
|
return ApiUserDirectory(user)
|
2015-09-24 12:23:22 +02:00
|
|
|
|
2015-09-24 10:47:51 +02:00
|
|
|
|
2016-07-08 20:14:11 +02:00
|
|
|
class ApiTrackingCodeDirectory(Directory):
|
|
|
|
def _q_lookup(self, component):
|
2023-09-30 19:01:03 +02:00
|
|
|
# /api/code/$code
|
|
|
|
# * allows signed requests
|
|
|
|
# * allows HTTP basic auth requests with API user with no role restriction
|
2016-07-08 20:14:11 +02:00
|
|
|
get_response().set_content_type('application/json')
|
2023-09-30 19:01:03 +02:00
|
|
|
|
|
|
|
user = get_user_from_api_query_string()
|
|
|
|
if not (user and user.is_api_user and not user.roles): # HTTP auth
|
|
|
|
if not is_url_signed(): # signed request
|
|
|
|
raise AccessForbiddenError('missing signature')
|
2016-07-08 20:14:11 +02:00
|
|
|
try:
|
|
|
|
tracking_code = get_publisher().tracking_code_class.get(component)
|
|
|
|
except KeyError:
|
|
|
|
raise TraversalError()
|
|
|
|
try:
|
|
|
|
formdata = tracking_code.formdata
|
|
|
|
except KeyError:
|
|
|
|
raise TraversalError()
|
|
|
|
if formdata.formdef.enable_tracking_codes is False:
|
|
|
|
raise TraversalError()
|
2023-10-07 09:46:53 +02:00
|
|
|
# return load_url with a temporary access URL so the caller can directly
|
|
|
|
# redirect the user to the formdata.
|
2018-02-14 23:48:26 +01:00
|
|
|
data = {
|
|
|
|
'err': 0,
|
2023-11-06 16:45:30 +01:00
|
|
|
'url': formdata.get_url(backoffice=get_query_flag('backoffice')),
|
|
|
|
'load_url': formdata.get_temporary_access_url(
|
|
|
|
duration=300, backoffice=get_query_flag('backoffice')
|
|
|
|
),
|
2018-02-14 23:48:26 +01:00
|
|
|
}
|
2016-07-08 20:14:11 +02:00
|
|
|
return json.dumps(data)
|
|
|
|
|
|
|
|
|
2019-04-04 15:22:50 +02:00
|
|
|
class AutocompleteDirectory(Directory):
|
|
|
|
def _q_lookup(self, component):
|
2022-06-13 17:33:34 +02:00
|
|
|
get_request().ignore_session = True
|
2022-01-17 17:50:11 +01:00
|
|
|
try:
|
2023-07-15 07:18:05 +02:00
|
|
|
autocomplete_context = get_session().get_token('autocomplete', component)
|
2023-05-01 09:41:52 +02:00
|
|
|
if autocomplete_context.data.get('url') == '':
|
2022-01-17 17:50:11 +01:00
|
|
|
# this is a datasource without a json url
|
|
|
|
# (typically a Python source)
|
|
|
|
raise KeyError()
|
|
|
|
except KeyError:
|
2019-04-04 15:22:50 +02:00
|
|
|
raise AccessForbiddenError()
|
2020-09-29 21:07:22 +02:00
|
|
|
get_response().set_content_type('application/json')
|
2020-11-12 19:30:49 +01:00
|
|
|
|
2023-05-01 09:41:52 +02:00
|
|
|
info = autocomplete_context.data
|
2020-11-12 19:30:49 +01:00
|
|
|
|
|
|
|
if 'url' in info:
|
2022-10-29 14:54:07 +02:00
|
|
|
named_data_source = None
|
2022-12-09 16:24:00 +01:00
|
|
|
cache_duration = 0
|
2022-10-29 14:54:07 +02:00
|
|
|
if info.get('data_source'):
|
|
|
|
named_data_source = NamedDataSource.get(info['data_source'])
|
2023-01-05 18:40:46 +01:00
|
|
|
if named_data_source.cache_duration:
|
|
|
|
cache_duration = int(named_data_source.cache_duration)
|
2020-11-12 19:30:49 +01:00
|
|
|
url = info['url']
|
2021-07-09 13:34:25 +02:00
|
|
|
url += urllib.parse.quote(get_request().form.get('q', ''))
|
2020-11-12 19:30:49 +01:00
|
|
|
get_response().set_content_type('application/json')
|
2022-12-09 16:24:00 +01:00
|
|
|
entries = request_json_items(
|
|
|
|
url,
|
|
|
|
named_data_source and named_data_source.extended_data_source,
|
|
|
|
cache_duration=cache_duration,
|
|
|
|
)
|
2022-10-29 14:54:07 +02:00
|
|
|
if entries is not None:
|
|
|
|
return json.dumps({'err': 0, 'data': entries})
|
|
|
|
return json.dumps({'err': 1, 'data': []})
|
2020-11-12 19:30:49 +01:00
|
|
|
|
|
|
|
# carddef_ref in info
|
|
|
|
carddef_ref = info['carddef_ref']
|
2022-01-17 17:50:11 +01:00
|
|
|
custom_view = None
|
|
|
|
if 'dynamic_custom_view' in info:
|
|
|
|
custom_view = get_publisher().custom_view_class.get(info['dynamic_custom_view'])
|
|
|
|
custom_view.filters = info['dynamic_custom_view_filters']
|
2023-01-31 16:03:56 +01:00
|
|
|
query = get_request().form.get('q', '')
|
2023-05-09 14:16:04 +02:00
|
|
|
limit = misc.get_int_or_400(get_request().form.get('page_limit'))
|
2023-02-03 17:30:00 +01:00
|
|
|
with_related = info.get('with_related')
|
|
|
|
with_related_urls = query and limit and with_related
|
2020-11-12 19:30:49 +01:00
|
|
|
values = CardDef.get_data_source_items(
|
|
|
|
carddef_ref,
|
2022-01-17 17:50:11 +01:00
|
|
|
custom_view=custom_view,
|
2023-01-31 16:03:56 +01:00
|
|
|
query=query,
|
|
|
|
limit=limit,
|
2023-02-03 17:30:00 +01:00
|
|
|
with_related_urls=with_related_urls,
|
2020-11-12 19:30:49 +01:00
|
|
|
)
|
2023-01-31 16:03:56 +01:00
|
|
|
keys = ['id', 'text']
|
2023-02-03 17:30:00 +01:00
|
|
|
if with_related_urls:
|
|
|
|
keys += ['edit_related_url', 'view_related_url']
|
2023-01-31 16:03:56 +01:00
|
|
|
return json.dumps({'data': [{key: x.get(key, '') for key in keys} for x in values]})
|
2019-04-04 15:22:50 +02:00
|
|
|
|
|
|
|
|
2020-11-09 09:41:01 +01:00
|
|
|
class GeoJsonDirectory(Directory):
|
|
|
|
def _q_lookup(self, component):
|
2021-03-02 10:21:04 +01:00
|
|
|
url = None
|
2020-11-09 09:41:01 +01:00
|
|
|
try:
|
|
|
|
data_source = get_data_source_object({'type': component}, ignore_errors=False)
|
|
|
|
except KeyError:
|
2022-01-17 17:50:11 +01:00
|
|
|
try:
|
2023-07-15 07:18:05 +02:00
|
|
|
context = get_session().get_token('geojson', component)
|
2022-01-17 17:50:11 +01:00
|
|
|
except KeyError:
|
2021-03-02 10:21:04 +01:00
|
|
|
raise TraversalError()
|
2023-07-15 07:18:05 +02:00
|
|
|
info = context.data
|
2021-03-02 10:21:04 +01:00
|
|
|
try:
|
|
|
|
data_source = get_data_source_object({'type': info['slug']}, ignore_errors=False)
|
|
|
|
except KeyError:
|
|
|
|
raise TraversalError()
|
|
|
|
url = info['url']
|
2020-11-09 09:41:01 +01:00
|
|
|
get_response().set_content_type('application/json')
|
2021-03-02 10:21:04 +01:00
|
|
|
return json.dumps(data_source.get_geojson_data(force_url=url))
|
2020-11-09 09:41:01 +01:00
|
|
|
|
|
|
|
|
2021-03-17 16:22:05 +01:00
|
|
|
class AfterJobDirectory(Directory):
|
|
|
|
_q_exports = ['']
|
|
|
|
|
|
|
|
def __init__(self, afterjob):
|
|
|
|
self.afterjob = afterjob
|
|
|
|
|
|
|
|
def _q_index(self):
|
|
|
|
get_response().set_content_type('application/json')
|
|
|
|
return json.dumps(
|
|
|
|
{
|
|
|
|
'err': 0,
|
|
|
|
'data': {
|
|
|
|
'status': self.afterjob.status,
|
|
|
|
'label': self.afterjob.label,
|
|
|
|
'creation_time': self.afterjob.creation_time,
|
|
|
|
'completion_time': self.afterjob.completion_time,
|
2021-08-12 09:26:46 +02:00
|
|
|
'completion_status': self.afterjob.get_completion_status(),
|
2021-03-17 16:22:05 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
cls=misc.JSONEncoder,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class AfterJobsDirectory(Directory):
|
2021-10-19 20:21:33 +02:00
|
|
|
_q_exports = []
|
2021-03-17 16:22:05 +01:00
|
|
|
|
|
|
|
def _q_lookup(self, component):
|
2021-05-30 22:10:19 +02:00
|
|
|
api_user = get_user_from_api_query_string()
|
|
|
|
if api_user and api_user.is_api_user:
|
|
|
|
pass # API users are ok
|
|
|
|
elif not (is_url_signed() or (get_request().user and get_request().user.is_admin)):
|
2021-03-17 16:22:05 +01:00
|
|
|
raise AccessForbiddenError('unsigned request or user is not admin')
|
|
|
|
try:
|
|
|
|
afterjob = AfterJob.get(component, ignore_errors=False)
|
|
|
|
except KeyError:
|
|
|
|
raise TraversalError()
|
|
|
|
return AfterJobDirectory(afterjob)
|
|
|
|
|
|
|
|
|
2014-11-04 10:39:35 +01:00
|
|
|
class ApiDirectory(Directory):
|
2015-09-18 15:21:24 +02:00
|
|
|
_q_exports = [
|
|
|
|
'forms',
|
|
|
|
'roles',
|
|
|
|
('reverse-geocoding', 'reverse_geocoding'),
|
2019-07-23 21:34:14 +02:00
|
|
|
'formdefs',
|
|
|
|
'categories',
|
|
|
|
'user',
|
|
|
|
'users',
|
|
|
|
'code',
|
|
|
|
'autocomplete',
|
2020-11-09 09:41:01 +01:00
|
|
|
'cards',
|
|
|
|
'geojson',
|
2021-03-17 16:22:05 +01:00
|
|
|
'jobs',
|
2023-05-16 15:41:39 +02:00
|
|
|
('card-file-by-token', 'card_file_by_token'),
|
2024-02-28 14:01:48 +01:00
|
|
|
('preview-payload-structure', 'preview_payload_structure'),
|
2023-11-08 15:42:00 +01:00
|
|
|
('sign-url-token', 'sign_url_token'),
|
2020-11-09 09:41:01 +01:00
|
|
|
]
|
2015-03-26 23:16:59 +01:00
|
|
|
|
2019-07-23 21:34:14 +02:00
|
|
|
cards = ApiCardsDirectory()
|
2015-03-26 23:16:59 +01:00
|
|
|
forms = ApiFormsDirectory()
|
2015-09-18 15:21:24 +02:00
|
|
|
formdefs = ApiFormdefsDirectory()
|
|
|
|
categories = ApiCategoriesDirectory()
|
2015-09-19 10:11:38 +02:00
|
|
|
user = ApiUserDirectory()
|
2015-09-24 10:47:51 +02:00
|
|
|
users = ApiUsersDirectory()
|
2016-07-08 20:14:11 +02:00
|
|
|
code = ApiTrackingCodeDirectory()
|
2019-04-04 15:22:50 +02:00
|
|
|
autocomplete = AutocompleteDirectory()
|
2020-11-09 09:41:01 +01:00
|
|
|
geojson = GeoJsonDirectory()
|
2021-03-17 16:22:05 +01:00
|
|
|
jobs = AfterJobsDirectory()
|
2023-05-16 15:41:39 +02:00
|
|
|
card_file_by_token = CardFileByTokenDirectory()
|
2023-11-08 15:42:00 +01:00
|
|
|
sign_url_token = SignUrlTokenDirectory()
|
2014-11-04 10:39:35 +01:00
|
|
|
|
2015-05-14 20:57:13 +02:00
|
|
|
def roles(self):
|
|
|
|
get_response().set_content_type('application/json')
|
2015-06-11 14:31:27 +02:00
|
|
|
if not (is_url_signed() or (get_request().user and get_request().user.can_go_in_admin())):
|
2015-11-17 11:52:20 +01:00
|
|
|
raise AccessForbiddenError('unsigned request or user has no access to backoffice')
|
2015-05-14 20:57:13 +02:00
|
|
|
list_roles = []
|
2021-03-09 15:35:21 +01:00
|
|
|
for role in get_publisher().role_class.select():
|
2019-09-27 13:14:49 +02:00
|
|
|
if not role.is_internal():
|
|
|
|
list_roles.append(role.get_json_export_dict())
|
2015-05-14 20:57:13 +02:00
|
|
|
get_response().set_content_type('application/json')
|
2017-11-04 03:01:13 +01:00
|
|
|
return json.dumps({'err': 0, 'data': list_roles})
|
2015-11-25 14:06:28 +01:00
|
|
|
|
2024-02-28 14:01:48 +01:00
|
|
|
def preview_payload_structure(self):
|
|
|
|
if not (get_request().user and get_request().user.can_go_in_admin()):
|
|
|
|
raise AccessForbiddenError('user has no access to backoffice')
|
|
|
|
|
|
|
|
def parse_payload():
|
|
|
|
payload = {}
|
|
|
|
for param, value in get_request().form.items():
|
|
|
|
# skip elements which are not part of payload
|
|
|
|
if 'post_data$element' not in param or param.endswith('value_python'):
|
|
|
|
continue
|
2024-04-15 19:36:50 +02:00
|
|
|
prefix, order, field = re.split(r'(\d+)(?!\d)', param) # noqa pylint: disable=unused-variable
|
2024-02-28 14:01:48 +01:00
|
|
|
# skip elements that aren't ordered
|
|
|
|
if not order:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if order not in payload:
|
|
|
|
payload[order] = []
|
|
|
|
|
|
|
|
if field == 'key':
|
|
|
|
# skip empty keys
|
|
|
|
if not value:
|
|
|
|
continue
|
|
|
|
# insert key on first position
|
|
|
|
payload[order].insert(0, value)
|
|
|
|
else:
|
|
|
|
payload[order].append(value)
|
|
|
|
return dict([v for v in payload.values() if len(v) > 1])
|
|
|
|
|
|
|
|
def format_payload(o, html=htmltext(''), last_element=True):
|
|
|
|
if isinstance(o, (list, tuple)):
|
|
|
|
html += htmltext('[<span class="payload-preview--obj">')
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
head, tail = o[0], o[1:]
|
|
|
|
except IndexError:
|
|
|
|
break
|
|
|
|
html = format_payload(head, html=html, last_element=len(tail) < 1)
|
|
|
|
o = tail
|
|
|
|
html += htmltext('</span>]')
|
|
|
|
elif isinstance(o, dict):
|
|
|
|
html += htmltext('{<span class="payload-preview--obj">')
|
|
|
|
for i, (k, v) in enumerate(o.items()):
|
|
|
|
html += htmltext('<span class="payload-preview--key">"%s"</span>: ' % k)
|
|
|
|
html = format_payload(v, html=html, last_element=i == len(o) - 1)
|
|
|
|
html += htmltext('</span>}')
|
|
|
|
else:
|
|
|
|
# check if it's empty string, a template with text around or just text
|
|
|
|
if not o or re.sub('^({[{|%]).+([%|}]})$', '', o):
|
|
|
|
# and add double quotes
|
|
|
|
html += htmltext('<span class="payload-preview--value">"%s"</span>' % o)
|
|
|
|
else:
|
|
|
|
html += htmltext('<span class="payload-preview--template-value">%s</span>' % o)
|
|
|
|
# last element doesn't need separator
|
|
|
|
if not last_element:
|
|
|
|
html += htmltext('<span class="payload-preview--item-separator">,</span>')
|
|
|
|
return html
|
|
|
|
|
|
|
|
payload = parse_payload()
|
|
|
|
r = TemplateIO(html=True)
|
|
|
|
r += htmltext('<h2>%s</h2>') % _('Payload structure preview')
|
|
|
|
r += htmltext('<div class="payload-preview">')
|
|
|
|
try:
|
|
|
|
unflattened_payload = unflatten_keys(payload)
|
|
|
|
r += htmltext('<div class="payload-preview--structure">')
|
|
|
|
r += format_payload(unflattened_payload)
|
|
|
|
r += htmltext('</div>')
|
|
|
|
except UnflattenKeysException as e:
|
|
|
|
r += htmltext('<div class="errornotice"><p>%s</p><p>%s %s</p></div>') % (
|
|
|
|
_('Unable to preview payload.'),
|
|
|
|
_('Following error occured: '),
|
|
|
|
e,
|
|
|
|
)
|
|
|
|
r += htmltext('</div>')
|
|
|
|
return r.getvalue()
|
|
|
|
|
2015-11-25 14:06:28 +01:00
|
|
|
def _q_traverse(self, path):
|
|
|
|
get_request().is_json_marker = True
|
2021-03-19 18:39:01 +01:00
|
|
|
return super()._q_traverse(path)
|
2018-04-07 09:24:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
def reverse_geocoding(request, *args, **kwargs):
|
|
|
|
if not ('lat' in request.GET and 'lon' in request.GET):
|
|
|
|
return HttpResponseBadRequest()
|
|
|
|
lat = request.GET['lat']
|
|
|
|
lon = request.GET['lon']
|
2024-05-05 13:41:30 +02:00
|
|
|
return HttpResponse(misc.get_reverse_geocoding_data(lat, lon), content_type='application/json')
|
2018-04-07 09:24:51 +02:00
|
|
|
|
2020-01-18 20:33:44 +01:00
|
|
|
|
2019-04-13 18:06:02 +02:00
|
|
|
def geocoding(request, *args, **kwargs):
|
2021-03-22 11:14:42 +01:00
|
|
|
if 'q' not in request.GET:
|
2019-04-13 18:06:02 +02:00
|
|
|
return HttpResponseBadRequest()
|
|
|
|
q = request.GET['q']
|
|
|
|
url = get_publisher().get_geocoding_service_url()
|
|
|
|
if '?' in url:
|
|
|
|
url += '&'
|
|
|
|
else:
|
|
|
|
url += '?'
|
2021-02-28 16:19:33 +01:00
|
|
|
url += 'format=json&q=%s' % urllib.parse.quote(q.encode('utf-8'))
|
2019-04-13 18:06:02 +02:00
|
|
|
url += '&accept-language=%s' % (get_publisher().get_site_language() or 'en')
|
|
|
|
return HttpResponse(misc.urlopen(url).read(), content_type='application/json')
|
|
|
|
|
|
|
|
|
2018-04-07 09:24:51 +02:00
|
|
|
def validate_condition(request, *args, **kwargs):
|
|
|
|
condition = {}
|
|
|
|
condition['type'] = request.GET.get('type') or ''
|
|
|
|
condition['value'] = request.GET.get('value_' + condition['type']) or ''
|
2024-02-11 12:18:58 +01:00
|
|
|
hint = {'msg': ''}
|
2018-04-07 09:24:51 +02:00
|
|
|
try:
|
|
|
|
Condition(condition).validate()
|
|
|
|
except ValidationError as e:
|
|
|
|
hint['msg'] = str(e)
|
2023-09-09 11:31:23 +02:00
|
|
|
else:
|
|
|
|
if request.GET.get('warn-on-datetime') == 'true' and condition['type'] == 'django':
|
|
|
|
variables = re.compile(r'\b(today|now)\b')
|
|
|
|
filters = re.compile(r'\|age_in_(years|months|days|hours)')
|
|
|
|
if variables.search(condition['value']) or filters.search(condition['value']):
|
|
|
|
hint['msg'] = _(
|
|
|
|
'Warning: conditions are only evaluated when entering the action, '
|
|
|
|
'you may need to set a timeout if you want it to be evaluated regularly.'
|
|
|
|
)
|
2021-05-15 15:34:16 +02:00
|
|
|
return JsonResponse(hint)
|
2020-05-25 21:57:31 +02:00
|
|
|
|
|
|
|
|
2021-07-13 18:45:47 +02:00
|
|
|
class ProvisionAfterJob(AfterJob):
|
|
|
|
def __init__(self, json_data, **kwargs):
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
self.json_data = json_data
|
|
|
|
|
|
|
|
def execute(self):
|
|
|
|
CmdHoboNotify().process_notification(self.json_data)
|
|
|
|
|
|
|
|
|
2020-05-25 21:57:31 +02:00
|
|
|
def provisionning(request):
|
|
|
|
if not is_url_signed():
|
|
|
|
raise AccessForbiddenError()
|
2021-02-04 10:37:40 +01:00
|
|
|
|
2021-09-28 20:15:29 +02:00
|
|
|
sync = request.GET.get('sync') == '1'
|
2021-09-14 07:05:37 +02:00
|
|
|
|
|
|
|
if sync:
|
|
|
|
CmdHoboNotify().process_notification(get_request().json)
|
|
|
|
else:
|
|
|
|
job = ProvisionAfterJob(json_data=get_request().json)
|
|
|
|
job.run(spool=True)
|
2020-05-25 21:57:31 +02:00
|
|
|
return JsonResponse({'err': 0})
|