Traceback (most recent call last):
File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/jinja.py", line 80, in render_template
return get_jenv().from_string(template).render(context)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/asyncsupport.py", line 76, in render
return original_render(self, *args, **kwargs)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/environment.py", line 1008, in render
return self.environment.handle_exception(exc_info, True)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/environment.py", line 780, in handle_exception
reraise(exc_type, exc_value, tb)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/_compat.py", line 37, in reraise
raise value.with_traceback(tb)
File "<template>", line 1, in top-level template code
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/web.html", line 1, in top-level template code
{% extends base_template_path %}
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/base.html", line 70, in top-level template code
{% block content %}
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/web.html", line 60, in block "content"
{{ main_content() }}
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/sandbox.py", line 440, in call
return __context.call(__obj, *args, **kwargs)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/runtime.py", line 574, in _invoke
rv = self._func(*arguments)
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/web.html", line 15, in template
{% block page_container %}
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/web.html", line 30, in block "page_container"
{%- block page_content -%}{%- endblock -%}
File "<template>", line 141, in block "page_content"
jinja2.exceptions.UndefinedError: 'page_length' is undefined
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/frappe/frappe-bench/apps/frappe/frappe/website/render.py", line 50, in render
data = render_page_by_language(path)
File "/home/frappe/frappe-bench/apps/frappe/frappe/website/render.py", line 177, in render_page_by_language
return render_page(path)
File "/home/frappe/frappe-bench/apps/frappe/frappe/website/render.py", line 193, in render_page
return build(path)
File "/home/frappe/frappe-bench/apps/frappe/frappe/website/render.py", line 200, in build
return build_page(path)
File "/home/frappe/frappe-bench/apps/frappe/frappe/website/render.py", line 218, in build_page
html = frappe.render_template(context.source, context)
File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/jinja.py", line 82, in render_template
throw(title="Jinja Template Error", msg="<pre>{template}</pre><pre>{tb}</pre>".format(template=template, tb=get_traceback()))
File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 377, in throw
msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable)
File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 356, in msgprint
_raise_exception()
File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 316, in _raise_exception
raise raise_exception(msg)
frappe.exceptions.ValidationError: <pre>{% extends "templates/web.html" %}
{% block title %}¡Bienvenidos/as!{% endblock %}
{% block header %}
<!-- <h1>¡Bienvenidos/as!</h1> -->
{% endblock header %}
{% block page_content %}
<div class="row">
<div class="col-lg-10 col-sm-12">
<div class="input-group input-group-sm mb-3">
<input type="search" class="form-control" placeholder="{{_('Product Search')}}"
aria-label="{{_('Product Search')}}" aria-describedby="product-search"
value="{{ frappe.form_dict.search or '' }}" style="height: 45px;"
>
</div>
</div>
<div class="col-4 pl-0">
<button class="btn btn-light btn-sm btn-block d-md-none"
type="button"
data-toggle="collapse"
data-target="#product-filters"
aria-expanded="false"
aria-controls="product-filters"
style="white-space: nowrap;"
>
{{ _('Toggle Filters') }}
</button>
</div>
</div>
<div class="row">
<!-- <div class="col-12 order-2 col-lg-4 order-md-1 products-list"> -->
{% if items %}
{% for item in items %}
{% include "erpnext/www/all-products/item_row.html" %}
{% endfor %}
{% else %}
{% include "erpnext/www/all-products/not_found.html" %}
{% endif %}
<!-- </div> -->
{#<div class="col-12 order-1 col-md-4 order-md-2">
{% if frappe.form_dict.start or frappe.form_dict.field_filters or frappe.form_dict.attribute_filters or frappe.form_dict.search %}
<a class="mb-3 d-inline-block" href="/all-products">{{ _('Clear filters') }}</a>
{% endif %}
<div class="collapse d-md-block" id="product-filters">
{% for field_filter in field_filters %}
{%- set item_field = field_filter[0] %}
{%- set values = field_filter[1] %}
<div class="mb-4">
<!-- <h6>{{ item_field.label }}</h6> -->
{% if values | len > 20 %}
<!-- show inline filter if values more than 20 -->
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
{% endif %}
{% if values %}
<div class="filter-options">
{% for value in values %}
<div class="custom-control custom-checkbox" data-value="{{ value }}">
<input type="checkbox"
class="product-filter field-filter custom-control-input"
id="{{value}}"
data-filter-name="{{ item_field.fieldname }}"
data-filter-value="{{ value }}"
>
<label class="custom-control-label" for="{{value}}">
{{ value }}
</label>
</div>
{% endfor %}
</div>
{% else %}
<i class="text-muted">{{ _('No values') }}</i>
{% endif %}
</div>
{% endfor %}
{% for attribute in attribute_filters %}
<div class="mb-4">
<h6>{{ attribute.name }}</h6>
{% if values | len > 20 %}
<!-- show inline filter if values more than 20 -->
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
{% endif %}
{% if attribute.item_attribute_values %}
<div class="filter-options">
{% for attr_value in attribute.item_attribute_values %}
<div class="custom-control custom-checkbox" data-value="{{ value }}">
<input type="checkbox"
class="product-filter attribute-filter custom-control-input"
id="{{attr_value.name}}"
data-attribute-name="{{ attribute.name }}"
data-attribute-value="{{ attr_value.attribute_value }}"
{% if attr_value.checked %} checked {% endif %}
>
<label class="custom-control-label" for="{{attr_value.name}}">
{{ attr_value.attribute_value }}
</label>
</div>
{% endfor %}
</div>
{% else %}
<i class="text-muted">{{ _('No values') }}</i>
{% endif %}
</div>
{% endfor %}
</div>
<script>
frappe.ready(() => {
$('.product-filter-filter').on('keydown', frappe.utils.debounce((e) => {
const $input = $(e.target);
const keyword = ($input.val() || '').toLowerCase();
const $filter_options = $input.next('.filter-options');
$filter_options.find('.custom-control').show();
$filter_options.find('.custom-control').each((i, el) => {
const $el = $(el);
const value = $el.data('value').toLowerCase();
if (!value.includes(keyword)) {
$el.hide();
}
});
}, 300));
})
</script>
</div>#}
</div>
<div class="row">
<div class="col-12">
{% if frappe.form_dict.start|int > 0 %}
<button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</button>
{% endif %}
{% if items|length >= page_length %}
<button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
{% endif %}
</div>
</div>
<script>
frappe.ready(() => {
$('.btn-prev, .btn-next').click((e) => {
const $btn = $(e.target);
$btn.prop('disabled', true);
const start = $btn.data('start');
let query_params = frappe.utils.get_query_params();
query_params.start = start;
let path = window.location.pathname + '?' + frappe.utils.get_url_from_dict(query_params);
window.location.href = path;
});
});
</script>
{% endblock %}
{% block script %}<script>$(() => {
class ProductListing {
constructor() {
this.bind_filters();
this.bind_search();
this.restore_filters_state();
}
bind_filters() {
this.field_filters = {};
this.attribute_filters = {};
$('.product-filter').on('change', frappe.utils.debounce((e) => {
const $checkbox = $(e.target);
const is_checked = $checkbox.is(':checked');
if ($checkbox.is('.attribute-filter')) {
const {
attributeName: attribute_name,
attributeValue: attribute_value
} = $checkbox.data();
if (is_checked) {
this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
this.attribute_filters[attribute_name].push(attribute_value);
} else {
this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name].filter(v => v !== attribute_value);
}
if (this.attribute_filters[attribute_name].length === 0) {
delete this.attribute_filters[attribute_name];
}
} else if ($checkbox.is('.field-filter')) {
const {
filterName: filter_name,
filterValue: filter_value
} = $checkbox.data();
if (is_checked) {
this.field_filters[filter_name] = this.field_filters[filter_name] || [];
this.field_filters[filter_name].push(filter_value);
} else {
this.field_filters[filter_name] = this.field_filters[filter_name] || [];
this.field_filters[filter_name] = this.field_filters[filter_name].filter(v => v !== filter_value);
}
if (this.field_filters[filter_name].length === 0) {
delete this.field_filters[filter_name];
}
}
const query_string = get_query_string({
field_filters: JSON.stringify(if_key_exists(this.field_filters)),
attribute_filters: JSON.stringify(if_key_exists(this.attribute_filters)),
});
window.history.pushState('filters', '', '/all-products?' + query_string);
$('.page_content input').prop('disabled', true);
this.get_items_with_filters()
.then(html => {
$('.products-list').html(html);
})
.then(data => {
$('.page_content input').prop('disabled', false);
return data;
})
.catch(() => {
$('.page_content input').prop('disabled', false);
});
}, 1000));
}
make_filters() {
}
bind_search() {
$('input[type=search]').on('keydown', (e) => {
if (e.keyCode === 13) {
// Enter
const value = e.target.value;
if (value) {
window.location.search = 'search=' + e.target.value;
} else {
window.location.search = '';
}
}
});
}
restore_filters_state() {
const filters = frappe.utils.get_query_params();
let {field_filters, attribute_filters} = filters;
if (field_filters) {
field_filters = JSON.parse(field_filters);
for (let fieldname in field_filters) {
const values = field_filters[fieldname];
const selector = values.map(value => {
return `input[data-filter-name="${fieldname}"][data-filter-value="${value}"]`;
}).join(',');
$(selector).prop('checked', true);
}
this.field_filters = field_filters;
}
if (attribute_filters) {
attribute_filters = JSON.parse(attribute_filters);
for (let attribute in attribute_filters) {
const values = attribute_filters[attribute];
const selector = values.map(value => {
return `input[data-attribute-name="${attribute}"][data-attribute-value="${value}"]`;
}).join(',');
$(selector).prop('checked', true);
}
this.attribute_filters = attribute_filters;
}
}
get_items_with_filters() {
const { attribute_filters, field_filters } = this;
const args = {
field_filters: if_key_exists(field_filters),
attribute_filters: if_key_exists(attribute_filters)
};
return new Promise((resolve, reject) => {
frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args)
.then(r => {
if (r.exc) reject(r.exc);
else resolve(r.message);
})
.fail(reject);
});
}
}
new ProductListing();
function get_query_string(object) {
const url = new URLSearchParams();
for (let key in object) {
const value = object[key];
if (value) {
url.append(key, value);
}
}
return url.toString();
}
function if_key_exists(obj) {
let exists = false;
for (let key in obj) {
if (obj.hasOwnProperty(key) && obj[key]) {
exists = true;
break;
}
}
return exists ? obj : undefined;
}
});
</script>
{% endblock %}</pre><pre>Traceback (most recent call last):
File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/jinja.py", line 80, in render_template
return get_jenv().from_string(template).render(context)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/asyncsupport.py", line 76, in render
return original_render(self, *args, **kwargs)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/environment.py", line 1008, in render
return self.environment.handle_exception(exc_info, True)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/environment.py", line 780, in handle_exception
reraise(exc_type, exc_value, tb)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/_compat.py", line 37, in reraise
raise value.with_traceback(tb)
File "<template>", line 1, in top-level template code
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/web.html", line 1, in top-level template code
{% extends base_template_path %}
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/base.html", line 70, in top-level template code
{% block content %}
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/web.html", line 60, in block "content"
{{ main_content() }}
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/sandbox.py", line 440, in call
return __context.call(__obj, *args, **kwargs)
File "/home/frappe/frappe-bench/env/lib/python3.6/site-packages/jinja2/runtime.py", line 574, in _invoke
rv = self._func(*arguments)
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/web.html", line 15, in template
{% block page_container %}
File "/home/frappe/frappe-bench/apps/frappe/frappe/./templates/web.html", line 30, in block "page_container"
{%- block page_content -%}{%- endblock -%}
File "<template>", line 141, in block "page_content"
jinja2.exceptions.UndefinedError: 'page_length' is undefined
</pre>