StreamField : Add Map block - wagtail

I'm working on a leaflet wagtail block integration.
https://github.com/frague59/wagtail-leaflet
I've difficulties with the widget rendering when I add the new block into the stream : nothing displays. The widget, from django-leaflet / django-geojson, involves the setting of geojson parameters 'window wide', which is not reflected in template instanciation.
In my leaflet/widget.html:
<script type="text/javascript">
{% block vars %}var {{ module }} = {};
{{ module }}.fieldid = '{{ id_css }}';
{{ module }}.modifiable = {{ modifiable|yesno:"true,false" }};
{{ module }}.geom_type = '{{ geom_type }}';
{{ module }}.srid = {{ map_srid }};
{% endblock vars %}
function {{ id_map_callback }}(map, options) {
window.LEAFLET_DATA['{{ module }}'].store_class = {{ field_store_class }};
(new {{ geometry_field_class}}(window.LEAFLET_DATA['{{ module }}'])).addTo(map);
{% block callback %}{% endblock callback %}
};
{% if target_map %}
window.addEventListener('map:init', function (e) {
var target_map = e.detail.map;
target_map.on('map:loadfield', function (me) {
if (me.fieldid == 'id_{{ target_map }}') {
setTimeout(function () {
{{ id_map_callback }}(target_map, e.detail.options);
}, 0);
}
});
}, false);
{% endif %}
</script>
From text/template element:
<script type="text/javascript">
var geodjango___prefix___value = {};
geodjango___prefix___value.fieldid = '__prefix__-value';
geodjango___prefix___value.modifiable = true;
geodjango___prefix___value.geom_type = 'Geometry';
geodjango___prefix___value.srid = 4326;
function __prefix___value_map_callback(map, options) {
geodjango___prefix___value.store_class = L.FieldStore;
(new L.GeometryField(geodjango___prefix___value)).addTo(map);
};
</script>
The Streamfield block API does not mention clearly this use case.
Do you have a working block example with this kind of block ?
Thanks !

Requirements:
django-leaflet
django-geosjon
jsonfield
The block definition: uses the leaflet widget from django-leaflet / django-geosjon
class GeoJSONBlock(FieldBlock):
geom_type = 'POINT'
def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs):
if self.geom_type is None:
raise NotImplemented('You are attempting to use ``GeoJSONBlock`` directly, which *WILL* not work !')
self.field = GeoJSONFormField(required=required,
help_text=help_text,
max_length=max_length,
min_length=min_length,
geom_type=self.geom_type)
super(GeoJSONBlock, self).__init__(**kwargs)
def render_form(self, value, prefix='', errors=None):
"""
Renders ``edit`` form
:param value: current value
:param prefix: prefix of the form item
:param errors: Validations errors
:returns: HTML Fragment
"""
logger.debug('MapBlock::render_form() value = %s', value)
rendered = super(GeoJSONBlock, self).render_form(value=value, prefix=prefix, errors=errors)
return rendered
def render(self, value, context=None):
"""
Renders the widget in the web site
:param value: current value
:param context: Additional render context
:returns: HTML Fragment
"""
logger.debug('MapBlock::render() value = %s', value)
rendered = super(GeoJSONBlock, self).render(value=value, context=context)
return rendered
#property
def media(self):
return forms.Media(
js=['wagtailleaflet/leaflet_init.js', ]
)
def js_initializer(self):
"""
JS function to launch on start'up
:returns: JS function name, from ``wagtailleaflet/leaflet_init.js``
"""
output = 'drawMap'
logger.debug('MapBlock::js_initializer() output = %s', output)
return output
def html_declarations(self):
output = render_to_string('wagtailleaflet/leaflet_forms.html')
return output
Usage:
from wagtail.wagtailadmin.edit_handlers import StreamFieldPanel
from wagtail.wagtailcore.fields import StreamField
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore import blocks
from wagtailleaflet.blocks import GeoJSONPointBlock
class DemoPage(Page):
"""
Simple demo page
"""
body = StreamField([('text', blocks.RichTextBlock()),
('map', GeoJSONPointBlock()), ])
content_panels = Page.content_panels + [StreamFieldPanel('body')]
I've found that the problem comes from the leaflet implementation, which uses slugify (set the id to lower) to create the id, which breaks the __PREFIX__ replacement in sequence.js on block initialization.
It's a bit complicated to re-produce, that's why I did put the link to my repo with a demo wagtail project.

Related

A good design pattern for shiny application with navigation buttons (wizard-like approach)

I'm trying to figure out a good design pattern for the following case (I'm developing a shiny app, but the language can be generalized, I guess): In my application, I'm trying to implement HOC pattern known from React - I've got a navigation component (a module in shiny) with two buttons - Back and Next. This component enhances another shiny module with navigation logic. The code is like this:
#' HOC - enhances modules with navigation buttons
#'
#' #param id unique identifier
#' #rdname hoc_with_nav_buttons
with_nav_buttons_ui <- function(id) {
ns <- NS(id)
tagList(
uiOutput(ns("component_ui")),
fluidRow(
column(
12,
actionButton(ns("btn_back"), "Back"),
actionButton(ns("btn_next"), "Next")
)
)
)
}
#' #rdname hoc_with_nav_buttons
#'
#' #param input shiny's default
#' #param output shiny's default
#' #param session shiny's default
#' #param component an object of class component created with \link{create_component} function
with_nav_buttons <- function(input, output, session, component) {
move <- reactiveVal()
# handling component's ui and logic: ----
output$component_ui <- renderUI({
component$ui(session$ns)
})
component$server(
data = list(navi = move)
)
# native logic: ----
observeEvent(input$btn_back, {
futile.logger::flog.debug(" <<< 'btn_back' clicked")
move(-input$btn_back)
})
observeEvent(input$btn_next, {
futile.logger::flog.debug("'btn_next >>>' clicked")
move(input$btn_next)
})
return(move)
}
and the module being enhanced:
outer_ui <- function(id) {
ns <- NS(id)
tagList(
tags$style(
".mockup-content {
min-height: 400px;
border: 1px gray solid;
padding: 15px;
}"
),
uiOutput(ns("ui_tabset"))
)
}
outer <- function(input, output, session, data) {
# tabs configuration: ----
tabs <- paste("tab", 1:4)
panels <- lapply(tabs, function(i) create_tab_panel(i))
active_tab <- reactiveVal(1) # control which tab is selected (a kind of state)
output$ui_tabset <- renderUI({
tabsetPanel(
id = session$ns("tabs"),
panels[[1]],
panels[[2]],
panels[[3]],
panels[[4]]
)
})
observeEvent(data$navi(), {
req(data$navi())
move <- ifelse(data$navi() > 0, 1, -1)
move_to <- active_tab() + move
if (move_to == 0) {
active_tab(1)
} else if (move_to > length(tabs)) {
active_tab(length(tabs))
} else {
active_tab(move_to)
}
})
observeEvent(active_tab(), {
updateTabsetPanel(
session,
inputId = "tabs",
selected = tabs[[active_tab()]]
)
})
}
I put these all int the app with some helper functions:
#' Creates a component object that can be used inside HOCs
#'
#' #param module_ui function; the ui part of a standard shiny module
#' #param module_server function; the server part of a standard shiny module
#' #param id character; the unique identifier used to create a namespace; the same
#' as the \code{id} parameter in the ui part of shiny modules
#' #param ... extra arguments to the \code{module_server} function
#'
#' #return a list with 'component' class
create_component <- function(module_ui, module_server, id, ...) {
component <- list(
ui = function(ns) {
module_ui(ns(id))
},
server = function(...) { # dots in case a module should accept an extra input
callModule(
module_server,
id,
...
)
}
)
class(component) <- append(class(component), "component")
return(component)
}
# helper for content creation:
create_tab_panel <- function(title) {
tabPanel(
title = title,
div(class = "mockup-content",
sprintf("%s content", title))
)
}
# create a custom component: ----
custom_component <- create_component(
outer_ui, outer, "tabs"
)
# the App: ----
ui <- dashboardPage(
dashboardHeader(title = "Nav buttons example"),
dashboardSidebar(collapsed = TRUE),
dashboardBody(
with_nav_buttons_ui("hoc")
)
)
server <- function(input, output, session) {
callModule(
with_nav_buttons,
"hoc",
component = custom_component
)
}
shinyApp(ui, server)
Trust me, this is a minimal example.
Now I wonder: what design pattern will be the best for communicating both modules two-directionally, e.g. I'd like to disable Back button if a user is on the first pane. In this case I need to send a message to the navigation component from the outer component. What design pattern is the best for such concept?

Custom field panel performance in stream block

I'm using wagtailgmaps for add google maps feature to my project. Use it
is easy, and i want add a stream block for use Google Maps block to the
content.
But, we have a big problem, this package only add a new edit_handler, it doesn't add a new model or field, only add a new handler for the admin.
So, my question is, how can i create a Google Maps stream block adding
the handler feature?
Thanks!
For create a new StreamBlock do you need create a widget for your field:
def render_form(self, value, prefix='', errors=None):
widget = self.field.widget
widget_attrs = {'id': prefix, 'placeholder': self.label}
field_value = self.value_for_form(value)
if hasattr(widget, 'render_with_errors'):
widget_html = widget.render_with_errors(prefix, field_value, attrs=widget_attrs, errors=errors)
widget_has_rendered_errors = True
else:
widget_html = widget.render(prefix, field_value, attrs=widget_attrs)
widget_has_rendered_errors = False
return render_to_string('wagtailadmin/block_forms/field.html', {
'name': self.name,
'classes': self.meta.classname,
'widget': widget_html,
'field': self.field,
'errors': errors if (not widget_has_rendered_errors) else None
})
Normally, all blocks in the base core have this function, and you can see that use widget.
So, first we have to create a widget for the map:
class MapsField(HiddenInput):
***
def __init__(self, *args, **kwargs):
self.address_field = kwargs.pop('address_field', self.address_field)
***
super(MapField, self).__init__(*args, **kwargs)
class Media:
css = {
'all': ('*/**/geo-field.css',)
}
js = (
'*/**/geo-field.js',
'https://maps.google.com/maps/api/js?key={}&libraries=places'
.format(
GOOGLE_MAPS_V3_APIKEY
),
)
def render(self, name, value, attrs=None):
out = super(MapField, self).render(name, value, attrs)
location = format_html(
'<div class="input">'
'<input id="_id_{}_latlng" class="geo-field-location" maxlength="250" type="text">' # NOQA
'</div>',
name
)
***
return mark_safe(
'<script>window["{}"] = {};</script>'.format(data_id, json_data) +
out +
location +
'<div class="geo-field" data-data-id="{}"></div>'.format(data_id) +
"""
<script>
(function(){
if (document.readyState === 'complete') {
return initializeGeoFields();
}
$(window).load(function() {
initializeGeoFields();
});
})();
</script>
"""
)
Now, we will create a new block extending the default wagtail block:from
from wagtail.wagtailcore.blocks import FieldBlock
from wagtailgeowidget.widgets import GeoField
def __init__(self, address_field=None, required=True, help_text=None,
**kwargs):
self.field_options = {}
self.address_field = address_field
super(GeoBlock, self).__init__(**kwargs)
#cached_property
def field(self):
field_kwargs = {'widget': GeoField(
srid=4326,
id_prefix='',
address_field=self.address_field,
)}
field_kwargs.update(self.field_options)
return forms.CharField(**field_kwargs)
def clean(self, value):
if not value:
value = "SRID={};POINT({} {})".format(
4326,
GEO_WIDGET_DEFAULT_LOCATION['lng'],
GEO_WIDGET_DEFAULT_LOCATION['lat']
)
return super(GeoBlock, self).clean(value)
def render_form(self, value, prefix='', errors=None):
if value and isinstance(value, dict):
value = "SRID={};POINT({} {})".format(value['srid'],
value['lng'],
value['lat'])
return super(GeoBlock, self).render_form(value, prefix, errors)
def to_python(self, value):
if isinstance(value, dict):
return value
value = geosgeometry_str_to_struct(value)
value = {
'lat': value['y'],
'lng': value['x'],
'srid': value['srid'],
}
return super(MapBlock, self).to_python(value)
This is basically the structure of wagtail block.

Django show only one element

I have product with image but when i try show image only this product django show me all image product how i can fix it ? I try wite slice don't work beacuse if i do |slice:"0:1" show me image but not image this product.
class Product(models.Model):
category = models.ForeignKey(Category, related_name='product')
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=200, db_index=True)
image = models.ImageField(upload_to='product/%Y/%m/%d',
blank=True)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.PositiveIntegerField()
available = models.BooleanField(default=True)
promo = models.BooleanField(default=False)
news = models.BooleanField(default=True)
section = models.CharField(max_length=50, choices=SECTION_CHOICES, default=None)
detailimage = models.ImageField(upload_to='product/detail/%Y/%m/%d',
blank=True)
detailimagetwo = models.ImageField(upload_to='product/detail/%Y/%m/%d',
blank=True)
class Meta:
ordering = ('name',)
index_together = (('id', 'slug'),)
def __str__(self):
return self.name
#property
def image_url(self):
if self.image and hasattr(self.image, 'url'):
return self.image.url
#property
def detail_url(self):
if self.detailimage and hasattr(self.detailimage, 'url'):
return self.detailimage.url
#property
def detailtwo_url(self):
if self.detailimagetwo and hasattr(self.detailimagetwo, 'url'):
return self.detailimagetwo.url
def get_absolute_url(self):
return reverse('shop:product_detail',
args=[self.id, self.slug])
My views.py
def product_detail(request, id, slug):
product = get_object_or_404(Product,
id=id,
slug=slug,
available=True)
products = Product.objects.all()
return render(request,
'shop/product/detail.html',
{'product': product,
'products': products})
and my detail.html
{% extends "shop/base.html" %}
{% load static %}
{% block title %}{{ product.name }}{% endblock %}
{% block content %}
{% for p in products %}
<img src="{{p.detail_url}}" class="img-responsive">
{% endfor %}
</div>
</div>
{% endblock %}
if u want somethink more comment and i will how fast i can add it .
change your view
def product_detail(request, id, slug):
product = get_object_or_404(Product,
id=id,
slug=slug,
available=True)
return render(request,
'shop/product/detail.html',
{'product': product})
and in template change
{% for p in products %}
to
{% for p in product %}
you can solve the problem by removing one 's' in template but i suggest change the view function as mentioned
in case your using Pillow not just the address of image as string change this
{{p.detail_url}}
to
{{p.fieldname.url}}

Using formsets in django-angular

django-angular is very cool. In particular, I like how it automates rendering errors. I have a bunch of forms where this works very nicely. I am trying to do the same thing w/ formsets and running into problems.
First, there was an issue w/ the field names not mapping to the underlying angular models. I am using a nested model like this:
var parent = {
field1 : 'a',
field2: 'b',
child_model: [
{ childfield1: 'c',
childfield2: 'd'
},
{ childfield1: 'e',
childfield2: 'f'
}
]
}
In order to get this structure to map to the form fields, I use the following code:
class MyFormSet(BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
"""
Instantiates and returns the i-th form instance in a formset.
"""
defaults = {
'auto_id': self.auto_id,
'prefix': self.add_prefix(i),
'error_class': self.error_class,
# THESE NEXT 3 LINES ARE THE DIFFERENT BIT...
'formset_class': self.__class__,
'scope_prefix': "%s[%d]" % (self.scope_prefix, i),
'form_name': "%s-%s" % (self.formset_name, i),
}
if self.is_bound:
defaults['data'] = self.data
defaults['files'] = self.files
if self.initial and 'initial' not in kwargs:
try:
defaults['initial'] = self.initial[i]
except IndexError:
pass
# Allow extra forms to be empty, unless they're part of
# the minimum forms.
if i >= self.initial_form_count() and i >= self.min_num:
defaults['empty_permitted'] = True
defaults.update(kwargs)
form = self.form(**defaults)
self.add_fields(form, i)
return form
class MyChildForm(Bootstrap3ModelForm, NgModelFormMixin, NgFormValidationMixin):
class Meta:
model = MyChildModel
def __init__(self, *args, **kwargs):
formset_class = kwargs.pop("formset_class", None)
super(MyChildForm, self).__init__(*args, **kwargs)
self.formset_class = formset_class
def get_widget_attrs(self, bound_field):
"""
just like the base class fn
except it sets "ng-model" using "get_qualified_model_field_name"
"""
attrs = super(NgModelFormMixin, self).get_widget_attrs(bound_field)
identifier = self.add_prefix(bound_field.name)
ng = {
'name': bound_field.name,
'identifier': identifier,
'model': self.get_qualified_model_field_name(bound_field.name)
}
if hasattr(self, 'Meta') and bound_field.name in getattr(self.Meta, 'ng_models', []):
attrs['ng-model'] = ng['model']
for key, fmtstr in self.ng_directives.items():
attrs[key] = fmtstr % ng
return attrs
def is_formset(self):
"""
tells whether or not the form is a member of a formset
"""
return self.formset_class is not None
def get_qualified_form_field_name(self, field_name):
"""
gets a field name suitable for ng use when binding to form
"""
if self.is_formset():
identifier = field_name
else:
identifier = self.add_prefix(field_name)
return format_html("{0}['{1}']", self.form_name, identifier)
def get_qualified_model_field_name(self, field_name):
"""
gets a field name suitable for ng use when binding to model
"""
if self.is_formset():
identifier = field_name
else:
identifier = self.add_prefix(field_name)
return format_html("{0}['{1}']", self.scope_prefix, identifier)
def MyChildModelFormSetFactory(*args, **kwargs):
instance = kwargs.pop("instance", None)
initial = kwargs.pop("initial", [])
queryset = kwargs.pop("queryset", MyChildModel.objects.none())
prefix = kwargs.pop("prefix", None)
scope_prefix = kwargs.pop("scope_prefix", None)
formset_name = kwargs.pop("formset_name", None)
kwargs.update({
"can_delete": False,
"extra": kwargs.pop("extra", 0),
"formset": MyFormSet,
"form": MyChildForm,
})
formset = inlineformset_factory(MyParentModel, MyChildModel, *args, **kwargs)
formset.scope_prefix = scope_prefix
formset.formset_name = formset_name
return formset(instance=instance, initial=initial, queryset=queryset)
The change in get_widget_attrs correctly renders fields like this:
<input class="form-control ng-pristine ng-untouched ng-valid ng-valid-parse ng-valid-required ng-valid-maxlength" id="id_my_formset_name-0.field_name" name="my_formset_name-0.field_name" ng-model="parent.child_model[0]['field_name']">
But when I try to render {{field.errors}} I just get a bunch of garbage. Instead of sensible elements w/ all the expected django-angular classes ("djng-field-errors", etc.), I get raw text like this:
(u'my_formset_name-0.my_scope_prefix-0.my_field_name', u'djng-form-control-feedback djng-field-errors', u'$dirty', u'$error.required', u'invalid', u'This field is required.')
Any ideas?
update:
For what it's worth, if I print out what .get_field_errors returns from a field from a "stand-alone" form I get:
<ul class="djng-form-control-feedback djng-field-errors" ng-show="my_form_name['field1'].$dirty" ng-cloak>
<li ng-show="my_form_name['field1'].$error.required" class="invalid">This field is required.</li>
</ul>
And if I print out what .get_field_errors returns from a field from a form that is part of a formset I get:
<ul class="errorlist">
<li>("my_formset_name-0['childfield1']", u'djng-form-control-feedback djng-field-errors', '$dirty', u'$error.required', 'invalid', u'This field is required.')</li>
</ul>
In addition to the HTML not being escaped, note that the parent element has the wrong class.
The problem was simple. django-angular forms use a custom error_class. But the _construct_form fn overrides that. I just needed to specify that same error class in the FormSet:
class MyFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
kwargs.update({
# makes sure that child forms render errors correctly...
"error_class": TupleErrorList,
})
super(MyFormSet, self).__init__(*args, **kwargs)

GAE ReferenceProperty Error from option value

I'm trying to add a new Goal via POST using my select option populated by the Referenced Category. The dropdown populates correctly, but the key I'm getting from the value returned is causing a ReferenceProperty Error.
models.py:
class Categories(db.Model):
name = db.StringProperty(required=True)
amount = db.FloatProperty(required=True)
class Goals(db.Model):
name = db.StringProperty(required=True)
amount = db.FloatProperty(required=True)
category = db.ReferenceProperty(Categories)
add_goal.html:
select type="select" name="category" id="id_cat"
{% for c in cats %}
option value='{{c.name}}' {{ c.name }} /option
{% endfor %}
/select>
CORRECTED VERSION:
{% for c in cats %}
{{ c.name }}
{% endfor %}
views.py:
def post(self):
cat_key = db.Key.from_path('Categories', self.request.get('category'))
logging.info('cat_key= '+ str(cat_key))
g = Goals(name=self.request.get('name'),
category=cat_key,
amount=float(self.request.get('amount')))
g.put()
return webapp2.redirect('/view_goals')
CORRECTED VERSION:
def post(self):
cat_key = db.Key.from_path('Categories', int(self.request.get('category')))
g = Goals(name=self.request.get('name'),
category=cat_key,
amount=float(self.request.get('amount')))
g.put()

Resources