0001-forms-always-use-a-template-to-render-map-widget-239.patch
tests/test_widgets.py | ||
---|---|---|
43 | 43 |
def set_form_value(self, name, value): |
44 | 44 |
self.form.set_value(value, name) |
45 | 45 | |
46 |
def set_form_hidden_value(self, name, value): |
|
47 |
self.form.find_control(name).readonly = False |
|
48 |
self.form.set_value(value, name) |
|
49 | ||
46 | 50 |
def get_parsed_query(self): |
47 | 51 |
return parse_query(self.form._request_data()[1], 'utf-8') |
48 | 52 | |
49 |
def mock_form_submission(req, widget, html_vars={}, click=None): |
|
53 |
def mock_form_submission(req, widget, html_vars={}, click=None, hidden_html_vars={}):
|
|
50 | 54 |
form = MockHtmlForm(widget) |
51 | 55 |
for k, v in html_vars.items(): |
52 | 56 |
form.set_form_value(k, v) |
57 |
for k, v in hidden_html_vars.items(): |
|
58 |
form.set_form_hidden_value(k, v) |
|
53 | 59 |
if click is not None: |
54 | 60 |
request = form.form.click(click) |
55 | 61 |
req.form = parse_query(request.data, 'utf-8') |
... | ... | |
571 | 577 |
assert (html_frags.index('name="test$element0key"') < # a |
572 | 578 |
html_frags.index('name="test$element2key"') < # b |
573 | 579 |
html_frags.index('name="test$element1key"')) # c |
580 | ||
581 |
def test_map_widget(): |
|
582 |
widget = MapWidget('test', title='Map') |
|
583 |
form = MockHtmlForm(widget) |
|
584 |
assert 'name="test$latlng"' in form.as_html |
|
585 |
req.form = {} |
|
586 |
assert widget.parse() is None |
|
587 | ||
588 |
widget = MapWidget('test', title='Map') |
|
589 |
mock_form_submission(req, widget, hidden_html_vars={'test$latlng': '1.23;2.34'}) |
|
590 |
assert not widget.has_error() |
|
591 |
assert widget.parse() == '1.23;2.34' |
|
592 | ||
593 |
assert '<label' in str(widget.render()) |
|
594 |
assert not '<label ' in str(widget.render_widget_content()) |
wcs/fields.py | ||
---|---|---|
1978 | 1978 | |
1979 | 1979 |
def get_view_value(self, value): |
1980 | 1980 |
widget = self.widget_class('x%s' % random.random(), value, readonly=True) |
1981 |
return widget.render_content() |
|
1981 |
return widget.render_widget_content()
|
|
1982 | 1982 | |
1983 | 1983 |
def get_rst_view_value(self, value, indent=''): |
1984 | 1984 |
return indent + value |
wcs/qommon/form.py | ||
---|---|---|
72 | 72 |
import misc |
73 | 73 |
from .misc import strftime, C_ |
74 | 74 |
from publisher import get_cfg |
75 |
from .template_utils import render_block_to_string |
|
75 | 76 | |
76 | 77 |
QuixoteForm = Form |
77 | 78 | |
... | ... | |
114 | 115 |
else: |
115 | 116 |
return '' |
116 | 117 | |
117 | ||
118 |
def render(self): |
|
119 |
# quixote/form/widget.py, Widget::render |
|
120 |
def safe(text): |
|
121 |
return mark_safe(str(htmlescape(text))) |
|
122 |
if hasattr(self, 'add_media'): |
|
123 |
self.add_media() |
|
124 |
self.class_name = self.__class__.__name__ |
|
125 |
self.rendered_title = lambda: safe(self.render_title(self.get_title())) |
|
126 |
self.rendered_error = lambda: safe(self.render_error(self.get_error())) |
|
127 |
self.rendered_hint = lambda: safe(self.render_hint(self.get_hint())) |
|
128 |
self.rendered_message = lambda: safe(self.render_message(self.get_message())) |
|
129 |
context = {'widget': self} |
|
118 |
def get_template_names(widget): |
|
130 | 119 |
template_names = [] |
131 |
widget_template_name = getattr(self, 'template_name', None)
|
|
132 |
for extra_css_class in (getattr(self, 'extra_css_class', '') or '').split():
|
|
120 |
widget_template_name = getattr(widget, 'template_name', None)
|
|
121 |
for extra_css_class in (getattr(widget, 'extra_css_class', '') or '').split():
|
|
133 | 122 |
if not extra_css_class.startswith('template-'): |
134 | 123 |
continue |
135 | 124 |
template_name = extra_css_class.split('-', 1)[1] |
... | ... | |
142 | 131 |
if widget_template_name: |
143 | 132 |
template_names.append(widget_template_name) |
144 | 133 |
template_names.append('qommon/forms/widget.html') |
134 |
return template_names |
|
135 | ||
136 |
def render(self): |
|
137 |
# quixote/form/widget.py, Widget::render |
|
138 |
def safe(text): |
|
139 |
return mark_safe(str(htmlescape(text))) |
|
140 |
if hasattr(self, 'add_media'): |
|
141 |
self.add_media() |
|
142 |
self.class_name = self.__class__.__name__ |
|
143 |
self.rendered_title = lambda: safe(self.render_title(self.get_title())) |
|
144 |
self.rendered_error = lambda: safe(self.render_error(self.get_error())) |
|
145 |
self.rendered_hint = lambda: safe(self.render_hint(self.get_hint())) |
|
146 |
self.rendered_message = lambda: safe(self.render_message(self.get_message())) |
|
147 |
context = {'widget': self} |
|
148 |
template_names = get_template_names(self) |
|
145 | 149 |
return htmltext(render_template(template_names, context)) |
146 | 150 | |
147 | 151 |
Widget.get_error = get_i18n_error |
... | ... | |
2178 | 2182 |
if value and get_request().form and not get_request().form.get(widget.name): |
2179 | 2183 |
get_request().form[widget.name] = value |
2180 | 2184 |
self.readonly = kwargs.pop('readonly', False) |
2181 |
self.kwargs = kwargs |
|
2185 |
self.map_attributes = {} |
|
2186 |
self.map_attributes.update(get_publisher().get_map_attributes()) |
|
2187 |
for attribute in ('initial_zoom', 'min_zoom', 'max_zoom', 'init_with_geoloc'): |
|
2188 |
if attribute in kwargs: |
|
2189 |
self.map_attributes['data-' + attribute] = kwargs.pop(attribute) |
|
2190 |
if kwargs.get('default_position'): |
|
2191 |
self.map_attributes['data-def-lat'] = kwargs['default_position'].split(';')[0] |
|
2192 |
self.map_attributes['data-def-lng'] = kwargs['default_position'].split(';')[1] |
|
2193 | ||
2194 |
def initial_position(self): |
|
2195 |
if self.value: |
|
2196 |
return {'lat': self.value.split(';')[0], |
|
2197 |
'lng': self.value.split(';')[1]} |
|
2198 |
return None |
|
2182 | 2199 | |
2183 |
def render_content(self):
|
|
2200 |
def add_media(self):
|
|
2184 | 2201 |
get_response().add_javascript(['qommon.map.js']) |
2185 |
r = TemplateIO(html=True) |
|
2186 |
for widget in self.get_widgets(): |
|
2187 |
r += widget.render() |
|
2188 |
attrs = { |
|
2189 |
'class': 'qommon-map', |
|
2190 |
'id': 'map-%s' % self.name, |
|
2191 |
} |
|
2192 |
if self.value: |
|
2193 |
attrs['data-init-lat'], attrs['data-init-lng'] = self.value.split(';') |
|
2194 |
if self.readonly: |
|
2195 |
attrs['data-readonly'] = 'true' |
|
2196 |
for attribute in ('initial_zoom', 'min_zoom', 'max_zoom'): |
|
2197 |
if attribute in self.kwargs and self.kwargs.get(attribute) is not None: |
|
2198 |
attrs['data-%s' % attribute] = self.kwargs.get(attribute) |
|
2199 |
attrs.update(get_publisher().get_map_attributes()) |
|
2200 |
default_position = self.kwargs.get('default_position') |
|
2201 |
if default_position: |
|
2202 |
attrs['data-def-lat'], attrs['data-def-lng'] = default_position.split(';') |
|
2203 |
if self.kwargs.get('init_with_geoloc'): |
|
2204 |
attrs['data-init-with-geoloc'] = 1 |
|
2205 |
r += htmltext('<div %s></div>' % ' '.join(['%s="%s"' % x for x in attrs.items()])) |
|
2206 |
return r.getvalue() |
|
2202 | ||
2203 |
def render_widget_content(self): |
|
2204 |
# widget content (without label, hint, etc.) is reused on status page; |
|
2205 |
# render the appropriate block. |
|
2206 |
self.add_media() |
|
2207 |
template_names = get_template_names(self) |
|
2208 |
context = {'widget': self} |
|
2209 |
return htmltext(render_block_to_string(template_names, 'widget-content', context).encode('utf-8')) |
|
2207 | 2210 | |
2208 | 2211 |
def _parse(self, request): |
2209 | 2212 |
CompositeWidget._parse(self, request) |
wcs/qommon/template_utils.py | ||
---|---|---|
1 |
# w.c.s. - web application for online forms |
|
2 |
# Copyright (C) 2005-2018 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or modify |
|
5 |
# it under the terms of the GNU General Public License as published by |
|
6 |
# the Free Software Foundation; either version 2 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU General Public License |
|
15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
# This is based on https://github.com/clokep/django-render-block, |
|
18 |
# originally Django snippet 769, then Django snippet 942. |
|
19 |
# |
|
20 |
# Reduced to only support Django templates. |
|
21 | ||
22 |
from __future__ import absolute_import |
|
23 | ||
24 |
from django.template import loader, Context |
|
25 |
from django.template.base import TextNode |
|
26 |
from django.template.loader_tags import ( |
|
27 |
BLOCK_CONTEXT_KEY, BlockContext, BlockNode, ExtendsNode) |
|
28 | ||
29 | ||
30 |
class BlockNotFound(Exception): |
|
31 |
pass |
|
32 | ||
33 | ||
34 |
def render_block_to_string(template_name, block_name, context=None): |
|
35 |
""" |
|
36 |
Loads the given template_name and renders the given block with the given |
|
37 |
dictionary as context. Returns a string. |
|
38 | ||
39 |
template_name |
|
40 |
The name of the template to load and render. If it's a list of |
|
41 |
template names, Django uses select_template() instead of |
|
42 |
get_template() to find the template. |
|
43 |
""" |
|
44 | ||
45 |
# Like render_to_string, template_name can be a string or a list/tuple. |
|
46 |
if isinstance(template_name, (tuple, list)): |
|
47 |
t = loader.select_template(template_name) |
|
48 |
else: |
|
49 |
t = loader.get_template(template_name) |
|
50 | ||
51 |
# Create a Django Context. |
|
52 |
context_instance = Context(context or {}) |
|
53 | ||
54 |
# Get the underlying django.template.base.Template object. |
|
55 |
template = t.template |
|
56 | ||
57 |
# Bind the template to the context. |
|
58 |
with context_instance.bind_template(template): |
|
59 |
# Before trying to render the template, we need to traverse the tree of |
|
60 |
# parent templates and find all blocks in them. |
|
61 |
parent_template = _build_block_context(template, context_instance) |
|
62 | ||
63 |
try: |
|
64 |
return _render_template_block(template, block_name, context_instance) |
|
65 |
except BlockNotFound: |
|
66 |
# The block wasn't found in the current template. |
|
67 | ||
68 |
# If there's no parent template (i.e. no ExtendsNode), re-raise. |
|
69 |
if not parent_template: |
|
70 |
raise |
|
71 | ||
72 |
# Check the parent template for this block. |
|
73 |
return _render_template_block( |
|
74 |
parent_template, block_name, context_instance) |
|
75 | ||
76 | ||
77 |
def _build_block_context(template, context): |
|
78 |
"""Populate the block context with BlockNodes from parent templates.""" |
|
79 | ||
80 |
# Ensure there's a BlockContext before rendering. This allows blocks in |
|
81 |
# ExtendsNodes to be found by sub-templates (allowing {{ block.super }} and |
|
82 |
# overriding sub-blocks to work). |
|
83 |
if BLOCK_CONTEXT_KEY not in context.render_context: |
|
84 |
context.render_context[BLOCK_CONTEXT_KEY] = BlockContext() |
|
85 |
block_context = context.render_context[BLOCK_CONTEXT_KEY] |
|
86 | ||
87 |
for node in template.nodelist: |
|
88 |
if isinstance(node, ExtendsNode): |
|
89 |
compiled_parent = node.get_parent(context) |
|
90 | ||
91 |
# Add the parent node's blocks to the context. (This ends up being |
|
92 |
# similar logic to ExtendsNode.render(), where we're adding the |
|
93 |
# parent's blocks to the context so a child can find them.) |
|
94 |
block_context.add_blocks( |
|
95 |
{n.name: n for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)}) |
|
96 | ||
97 |
_build_block_context(compiled_parent, context) |
|
98 |
return compiled_parent |
|
99 | ||
100 |
# The ExtendsNode has to be the first non-text node. |
|
101 |
if not isinstance(node, TextNode): |
|
102 |
break |
|
103 | ||
104 | ||
105 |
def _render_template_block(template, block_name, context): |
|
106 |
"""Renders a single block from a template.""" |
|
107 |
return _render_template_block_nodelist(template.nodelist, block_name, context) |
|
108 | ||
109 | ||
110 |
def _render_template_block_nodelist(nodelist, block_name, context): |
|
111 |
"""Recursively iterate over a node to find the wanted block.""" |
|
112 | ||
113 |
# Attempt to find the wanted block in the current template. |
|
114 |
for node in nodelist: |
|
115 |
# If the wanted block was found, return it. |
|
116 |
if isinstance(node, BlockNode): |
|
117 |
# No matter what, add this block to the rendering context. |
|
118 |
context.render_context[BLOCK_CONTEXT_KEY].push(node.name, node) |
|
119 | ||
120 |
# If the name matches, you're all set and we found the block! |
|
121 |
if node.name == block_name: |
|
122 |
return node.render(context) |
|
123 | ||
124 |
# If a node has children, recurse into them. Based on |
|
125 |
# django.template.base.Node.get_nodes_by_type. |
|
126 |
for attr in node.child_nodelists: |
|
127 |
try: |
|
128 |
new_nodelist = getattr(node, attr) |
|
129 |
except AttributeError: |
|
130 |
continue |
|
131 | ||
132 |
# Try to find the block recursively. |
|
133 |
try: |
|
134 |
return _render_template_block_nodelist(new_nodelist, block_name, context) |
|
135 |
except BlockNotFound: |
|
136 |
continue |
|
137 | ||
138 |
# The wanted block_name was not found. |
|
139 |
raise BlockNotFound("block with name '%s' does not exist" % block_name) |
wcs/qommon/templates/qommon/forms/widgets/map.html | ||
---|---|---|
1 |
{% extends "qommon/forms/widget.html" %} |
|
2 | ||
3 |
{% block widget-control %} |
|
4 |
<input type="hidden" name="{{widget.name}}$latlng" {% if widget.value %}value="{{widget.value}}"{% endif %}> |
|
5 |
<div id="map-{{widget.name}}" class="qommon-map" |
|
6 |
{% if widget.readonly %}data-readonly="true"{% endif %} |
|
7 |
{% for key, value in widget.map_attributes.items %}{{key}}="{{value}}" {% endfor %} |
|
8 |
{% if widget.initial_position %} |
|
9 |
data-init-lat="{{ widget.initial_position.lat }}" |
|
10 |
data-init-lng="{{ widget.initial_position.lng }}" |
|
11 |
{% endif %} |
|
12 |
></div> |
|
13 |
{% endblock %} |
|
0 |
- |