Penambahan Fungsi render xhr request

1 parent 30d40a72
......@@ -43,7 +43,6 @@ static_route = []
titles = {}
def get_params(params, alternate=None, settings=None):
"""
Digunakan untuk mengambil nilai dari konfigurasi sesuai params yang disebut
......@@ -63,6 +62,7 @@ def get_params(params, alternate=None, settings=None):
f"get_params: {params}, Alternate: {alternate} Settings: {not settings == None} Result: {result}")
return result and result.strip() or alternate
def add_cors_headers_response_callback(event):
def cors_headers(request, response):
origin = request.headers.get("Origin", None)
......@@ -93,9 +93,11 @@ def add_cors_headers_response_callback(event):
event.request.add_response_callback(cors_headers)
def get_app_name(request):
return get_params('app_name', 'openSIPKD Application')
def get_menus(request):
"""
digunakan untuk mengambil daftar menu untuk setiap modul yg sudah diregistrasikan di config key "menus".
......@@ -160,20 +162,25 @@ def get_menus(request):
return result
def get_home(request):
return request.route_url('base-home')[:-1]
def get_host(request):
host = get_params('_host', "")
return host and host or get_home(request)
def get_title(request):
route_name = request.matched_route.name
return titles[route_name]
def get_company(request):
return get_params('company', 'openSIPKD').upper()
def format_datetime(v):
if v.time() != datetime.time(0, 0):
return dmyhms(v)
......@@ -197,9 +204,11 @@ def json_rpc():
json_r.add_adapter(decimal.Decimal, lambda v, request: str(v))
return json_r
def allow_register(request):
return BASE_CLASS.allow_register
def google_signin_client_ids(request):
ids = get_params('google-signin-client-id', '')
if ids:
......@@ -292,6 +301,7 @@ def get_config(settings):
return config
def init_db(settings):
engine = engine_from_config(
settings, 'sqlalchemy.', client_encoding='utf8',
......@@ -315,7 +325,7 @@ def main(global_config, **settings):
settings['timezone'] = DefaultTimeZone
# settings["captcha_files"] = "c:\\tmp\\captcha\\"
tmp = get_params("temp_files", "/tmp", settings=settings)
settings["captcha_files"] = os.path.join(tmp , "captcha") + os.sep
settings["captcha_files"] = os.path.join(tmp, "captcha") + os.sep
init_db(settings=settings)
config = get_config(settings=settings)
......@@ -377,13 +387,11 @@ def _add_view_config(config, paket, route):
config.add_view(views, **params)
except Exception as e:
_logging.error("Add View Config :{code} Kode {error}"\
_logging.error("Add View Config :{code} Kode {error}"
.format(code=route["kode"], error=str(e)))
_logging.debug(f"Route: {route.get('kode')} {route.get('path')}")
class BaseApp():
def __init__(self):
self.menus = []
......@@ -430,8 +438,8 @@ class BaseApp():
'captcha', self.captcha_files, cache_max_age=0)
self.login_tpl = get_params("login_tpl", "", settings=settings)
self.login_captcha = int(get_params("login_captcha", 0, settings=settings))
self.login_captcha = int(get_params(
"login_captcha", 0, settings=settings))
def add_menu(self, config, route_menus, parent=None, paket="opensipkd.base.views"):
route_names = []
......@@ -442,7 +450,7 @@ class BaseApp():
route["route_name"] = [route["kode"]]
route["permission"] = route.get("permission", "")
route["icon"] = route.get("icon", None)
route_typ = route.get("typ", 0)
route_typ = route.get("typ", 0) or route.get("type", 0) or 0
if route_typ == "" or route_typ == None:
route_typ = 0
else:
......@@ -497,7 +505,6 @@ class BaseApp():
if p["children"]:
self.route_children(p["children"], row)
def route_from_csv_(self, config, paket="tangsel.base.views", rows=[]):
new_routes = []
for row in rows:
......@@ -526,9 +533,9 @@ class BaseApp():
else:
import xlsx_dict_reader
from openpyxl import load_workbook
wb= load_workbook(fullpath, data_only=True)
wb = load_workbook(fullpath, data_only=True)
ws = wb.active
rows = xlsx_dict_reader.DictReader(ws) #skip_blank=True
rows = xlsx_dict_reader.DictReader(ws) # skip_blank=True
self.route_from_csv_(config, paket, rows=rows)
# with self.get_route_file(filename) as f:
......@@ -607,6 +614,7 @@ def add_global(event):
# # event['get_module_menus'] = get_module_menus
# # event['get_module_submenus'] = get_module_submenus
def get_params_(params, alternate=None, settings=None):
return get_params(params, alternate, settings)
......
import os
import sys
from opensipkd.tools import get_settings
def is_live(pid):
try:
os.kill(pid, 0)
except OSError:
return
return True
def get_pid(pidfile):
try:
f = open(pidfile, 'r')
pid_int = int(f.read().split()[0])
f.close()
return pid_int
except IOError:
return
except ValueError:
return
except IndexError:
return
def get_pid_file(pid_name=None, settings=None):
if not pid_name:
pid_name = os.path.split(sys.argv[0])[-1]
pid_name = os.path.splitext(pid_name)[0]
if not settings:
settings = get_settings()
dir_path = 'session.lock_dir'
if not os.path.exists(dir_path):
mkdir(dir_path)
return os.path.join(dir_path, pid_name)
def make_pid(pid_file, settings=None):
pid = get_pid(pid_file)
if pid and is_live(pid):
msg = 'PID saya {pid} masih ada.'.format(pid=pid)
print(msg)
sys.exit()
pid = os.getpid()
f = open(pid_file, 'w')
f.write(str(pid))
f.close()
return pid
def one_pid():
pid_file = get_pid_file()
make_pid(pid_file)
return pid_file
def mkdir(dir_name):
if not os.path.exists(dir_name):
os.makedirs(dir_name)
def get_fullpath(filename):
dir_name = os.path.split(__file__)[0]
return os.path.join(dir_name, filename)
.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb}
import logging
import os
# import re
import re
from datetime import datetime
# from email.utils import parseaddr
from email.utils import parseaddr
import colander
from opensipkd.base import BASE_CLASS
from pyramid.csrf import new_csrf_token, get_csrf_token
from datatables import ColumnDT
# from dateutil.relativedelta import relativedelta
from dateutil.relativedelta import relativedelta
from deform import (widget, Form, ValidationFailure, FileData, )
from deform.widget import SelectWidget
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
......@@ -22,11 +20,14 @@ from opensipkd.tools.buttons import (
btn_pdf, btn_unpost, btn_post, btn_upload)
# from opensipkd.tools.captcha import get_captcha
from opensipkd.tools.report import csv_response, file_response
from pyramid.request import Response
from .common import DataTables
from ..models import DBSession, Partner
# , get_params, get_urls
from ..scripts.initializedb import append_csv
from ...detable import DeTable
from opensipkd.base import BASE_CLASS
from pyramid.csrf import new_csrf_token, get_csrf_token
log = logging.getLogger(__name__)
......@@ -37,6 +38,7 @@ class UploadSchema(colander.Schema):
widget=widget.FileUploadWidget(mem_tmp_store),
title='Unggah')
class CSRFSchema(colander.Schema):
def after_bind(self, schema, kwargs):
request = kwargs["request"]
......@@ -96,8 +98,8 @@ class BaseView(object):
self.html_buttons = {}
self.new_buttons = {}
self.list_form = None #List dam Form
self.form_list = None #Form kemudian detail list
self.list_form = None # List dam Form
self.form_list = None # Form kemudian detail list
self.form_scripts = """
$('#parent_nm').bind('typeahead:selected', function(obj, datum) {
......@@ -127,9 +129,9 @@ class BaseView(object):
self.init_session(request)
def init_session(self, request):
# # if not request.user:
# if "g_state" in request.cookies:
# request.response.delete_cookie("g_state", '/')
# # if not request.user:
# if "g_state" in request.cookies:
# request.response.delete_cookie("g_state", '/')
now = datetime.now()
# # self.dt_awal = self.ses["dt_awal"] if "dt_awal" in self.ses else now
......@@ -216,7 +218,26 @@ class BaseView(object):
# 'jenis'] or self.jenis
# self.ses['jenis'] = self.jenis
def form2dict(self, field):
children = []
for c in field.children:
children.append(self.form2dict(c))
value = hasattr(field, "cstruct") and field.cstruct or ""
if type(value) in (colander.null, colander._null):
value = ""
if type(value) == dict:
for k, v in value.items():
if type(v) in (colander.null, colander._null):
value[k] = ""
d = {
"id": field.oid,
"name": field.name,
"error": {"msg": field.error and field.error.msg or ""},
"children": children,
"value": value
}
return d
# def query_register(self, **kwargs):
# pass
......@@ -244,13 +265,10 @@ class BaseView(object):
if self.headers:
return HTTPFound(
location=self.req.route_url(self.list_route),
# location=get_urls(list_url),
headers=self.headers)
else:
return HTTPFound(
location=self.req.route_url(self.list_route),
# location=get_urls(list_url)
)
location=self.req.route_url(self.list_route))
def form_validator(self, form, value):
pass
......@@ -568,6 +586,11 @@ class BaseView(object):
return self.route_list(**kwargs)
def returned_form(self, form, table=None, **kwargs):
if self.req.is_xhr:
d = self.form2dict(form)
import json
return Response(json=d)
resources = form.get_widget_resources()
readonly = "readonly" in kwargs and kwargs["readonly"] or False
kwargs["readonly"] = readonly
......@@ -675,7 +698,6 @@ class BaseView(object):
def view_upload(self, **kw):
return self.view_import(**kw)
def view_import(self, **kw):
exts = kw.get("exts")
table = None
......@@ -1068,7 +1090,6 @@ class BaseView(object):
if not os.path.exists(path):
os.makedirs(path)
upload = Upload(path)
resp = upload.save_fp(value)
if filename:
......@@ -1128,4 +1149,3 @@ class BaseView(object):
# def get_url_captcha(request):
# captcha = get_captcha(request)
# return os.path.join(get_urls(request.route_url('home')), 'captcha', captcha)
<html metal:use-macro="load: ./base3.1.pt" tal:define="
scripts scripts|scripts;
readonly readonly|readonly;">
<div metal:fill-slot="content">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-fw fa-plus"></i>&nbsp;${request.title}</h3>
</div>
<div class="panel-body">
<div tal:content="structure form.render(readonly=readonly)"></div>
</div>
</div>
</div>
<div metal:fill-slot="scripts">
<script>
$(document).ready(function () {
// $(".read-only").attr("readonly", true);
$(".readonly").attr("readonly", true);
$(".date").attr("readonly", true);
// $(".date").datepicker({
// format: 'dd-mm-yyyy'
// });
${ structure: scripts }
});
</script>
<div metal:define-slot="scripts"></div>
</div>
</html>
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!