I noticed that maps in the Geodjango admin have a menu on the right.
How can I add more layers than just one representing the model field the map is related in admin?
This is my model (model.py):
class Foresta(models.Model):
nome = models.CharField("Nome", blank = False, max_length = 255)
descrizione = tinymce_models.HTMLField("Descrizione", blank = True, help_text='Inserire una descrizione del bosco')
slug = models.SlugField("Slug", blank = True)
published = models.BooleanField("Pubblicato")
...
coord = models.PointField("Coordinata punto foresta", blank = False)
# GeoDjango-specific: a geometry field (MultiPolygonField), and
# overriding the default manager with a GeoManager instance.
mpoly = models.MultiPolygonField("Mappa foresta (poligono)", blank = False)
objects = models.GeoManager()
This is my admin model (admin.py):
class ForestaAdmin(admin.OSMGeoAdmin):
default_lon= 1308296
default_lat= 5714101
default_zoom= 9
overlays = ('coord', 'mpoly')
I tried with the 'overlays' option but without success!
You can find the default OSMGeoAdmin settings here. Unfortunately, it doesn't look like you can accomplish this with something as simple as specifying an overlays tuple. Instead, it looks like you should create a custom map template and then override the map_template option in your ForestaAdmin class with the path to your template. Start by copying the default openlayers template and customize the javascript to add your other layers as additional OpenLayers Vector layers.
I had to add extra readonly multipolygon on the map. I redefined osm.html (?just changed osm.js file path cause django used default "gis/admin/osm.js")
{% extends "gis/admin/openlayers.html" %}
{% block openlayers %}{% include "gis/osm.js" %}{% endblock %}
osm.js:
{% extends "gis/admin/osm.js" %}
{% block extra_layers %}
{% if extra_wkt %}
var extraLayer = new OpenLayers.Layer.Vector("extra_layer");
var extraGeometry = new OpenLayers.Feature.Vector(
new OpenLayers.Geometry.fromWKT('{{ extra_wkt }}'),
{}, // attrs
{
fillColor: "#8a8a8a",
fillOpacity: 0.4,
strokeColor: "#000000",
strokeOpacity: 0.6,
strokeWidth: 1,
}
)
extraLayer.addFeatures(extraGeometry);
{{ module }}.map.addLayer(extraLayer);
{% endif %}
{% endblock %}
There is I use extra_wkt value which passed as context.
(mpoly - name of geodjango multipolygon field)
from django.contrib.gis.admin import OSMGeoAdmin, OpenLayersWidget
class OSMLayersWidget(OpenLayersWidget):
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
if name == "mpoly":
extra_mpoly = MultiPolygon([..., ...])
if extra_mpoly:
srid = self.params['srid']
if extra_mpoly.srid != srid:
try:
extra_mpoly.transform(srid)
extra_wkt = extra_mpoly.wkt
except GDALException as err:
logger.error(
"Error creating geometry from value '%s' (%s)",
extra_mpoly,
err,
)
extra_wkt = ''
else:
extra_wkt = extra_mpoly.wkt
context["extra_wkt"] = extra_wkt
return context
admin.py:
class ServiceZoneAdmin(OSMGeoAdmin):
map_template = 'gis/osm.html'
widget = OSMLayersWidget
Result:
You can use olwidget http://docs.olwidget.org/en/latest/django-olwidget.html. It allows you to edit and show different layers (inside and outside Django admin). However, you might find it difficult to use depending on which Django version are you running.
Finally, I managed to do it as #garnetb said by modifying the openlayers.js file found in (my case) here:
/usr/local/lib/python2.7/dist-packages/django/contrib/gis/templates/gis/admin/openlayers.js
If you can't find it just look for it in the osm.html and/or openlayers.html. So, this is the way I managed to add a second layer. My objective was to add a "visual" only layer, but you can easily modify the controls if you want to edit it. So, let's assume you have a model like this one:
class Lines(gis_models.Model):
name = gis_models.CharField(max_length=10)
geom = gis_models.MultiLineStringField(srid=4326)
geom_points = gis_models.MultiPointField(srid=4326, null=True)
objects = gis_models.GeoManager()
def __unicode__(self):
return self.name
So you see, there are 2 elements being load, the first one are lines and the second one are points. So my objective is to add the lines as a visual reference for when I'm working with the points. To be able to do that, go to openlayers.js and look for the section where the Base Layer is being defined and add something like this.
// Base Layer
{% if field_name != "geom" %}
geom_layer = new OpenLayers.Layer.Vector("visual");
{{ module }}.map.addLayer(geom_layer);
var wkt_vis = document.getElementById('id_geom').value;
if (wkt_vis){
var features = {{ module }}.read_wkt(wkt_vis);
geom_layer.addFeatures(features);
}
else {
alert("no wkt id field");
}
{% endif %}
if ({{ module }}.is_point) {
var style = new OpenLayers.Style({
pointRadius : 4,
strokeColor : 'red',
strokeWidth : 2,
strokeOpacity : 1,
fillColor : 'white',
fillOpacity : 1
});
var layer_style = new OpenLayers.StyleMap({
'default' : style,
});
{{ module }}.layers.vector = new OpenLayers.Layer.Vector(" {{ field_name }}", {styleMap : layer_style});
}
else {
{{ module }}.layers.vector = new OpenLayers.Layer.Vector(" {{ field_name }}");
}
As you can see, the whole idea is that openlayers.js is loaded for every layer, but you can still access other objects. This configuration left me with a handy admin where I'm showing two maps. In the first one, I show the lines and I'm able to modify them. In the second one, I show the lines as a reference and the points are editable. The second part shows you how to set up the points style (just in case...) This is just a simplified example. If you need more details just let me know. I also managed to add a "delete feature" control. I only tried this with GeoDjango base on Django 1.6, but it should work with other versions as far as they use openlayers.js in the same way.
Related
I'm using Wagtail 2.0 with a custom Block that has the following code:
class LinkButtonBlock(blocks.StructBlock):
label = blocks.CharBlock()
URL = blocks.CharBlock()
styling = blocks.ChoiceBlock(
choices=[
('btn-primary', 'Primary button'),
('btn-secondary', 'Secondary button'),
('btn-success', 'Success button'),
('btn-info', 'Info button'),
('btn-warning', 'Warning button'),
('btn-error', 'Error button'),
],
default='btn-info',
)
outline = blocks.BooleanBlock(
default=False
)
#property
def css(self):
btn_class = self.styling
if self.outline is True:
btn_class = btn_class.replace('btn-', 'btn-outline-')
return btn_class
class Meta:
icon = 'link'
template = 'testapp/blocks/link_button_block.html'
If I then try to access this css "property" in my template, nothing seems to happen. Putting a print(self) as first line inside the css def also shows nothing on the console suggesting the function never even gets called.
Using the following template:
{% load wagtailcore_tags %}
<a class="btn {{ block.value.css }}" href="{{ block.value.URL }}">{{ block.value.label }}</a>
Simply yields:
<a class="btn " href="actual.url.from.instance">actual.label.from.instance</a>
Also, block.value.styling and block.value.outline on their own work just fine, so... what am I doing wrong here?
The thing that's tripping you up is that the value objects you get when iterating over a StreamField are not instances of StructBlock. Block objects such as StructBlock and CharBlock act as converters between different data representations; they don't hold on to the data themselves. In this respect, they work a lot like Django's form field objects; for example, Django's forms.CharField and Wagtail's CharBlock both define how to render a string as a form field, and how to retrieve a string from a form submission.
Note that CharBlock works with string objects - not instances of CharBlock. Likewise, the values returned from StructBlock are not instances of StructBlock - they are a dict-like object of type StructValue, and this is what you need to subclass to implement your css property. There's an example of doing this in the docs: http://docs.wagtail.io/en/v2.0/topics/streamfield.html#custom-value-class-for-structblock. Applied to your code, this would become:
class LinkButtonValue(blocks.StructValue):
#property
def css(self):
# Note that StructValue is a dict-like object, so `styling` and `outline`
# need to be accessed as dictionary keys
btn_class = self['styling']
if self['outline'] is True:
btn_class = btn_class.replace('btn-', 'btn-outline-')
return btn_class
class LinkButtonBlock(blocks.StructBlock):
label = blocks.CharBlock()
URL = blocks.CharBlock()
styling = blocks.ChoiceBlock(choices=[...])
outline = blocks.BooleanBlock(default=False)
class Meta:
icon = 'link'
template = 'testapp/blocks/link_button_block.html'
value_class = LinkButtonValue
Hi Have a choice field
class PropertyReportForm(forms.Form):
property = forms.ChoiceField(widget = forms.RadioSelect,required = False)
def __init__(self,*args,**kwargs):
properties = kwargs.pop('properties')
property_choice = []
for property1 in properties:
index = (property1.id,"Name :"+property1.name+" Area:"+str(property1.area)+" "+property1.image)
property_choice.append(index)
super( PropertyReportForm, self).__init__(*args, **kwargs)
self.fields['property'].choices = property_choice
Now while displaying I want it to display
How can I do this?
Template code similar to what I want. This does not work. But I want this
{% for field in propertyreportform %}
{{ field }} <img src="/media/{{ field.image }}" />
{% endfor %}
To solve this problem you basically have three options:
Create your own renderer, and use it as argument for ChoiceField, unfortunately you will need to create it from scratch, since django doesn't allow you to simply override RadioFieldRenderer class. https://github.com/django/django/blob/1.5.4/django/forms/widgets.py#L693
Just loop over your choices and use manually created radio input tags, there's not a lot of validation to do and retrieving selected item or model object is also simple enough.
The simplest and less recommended way could be to include the whole image tag inside label string (use settings to get media url part).
I've been scanning through Django documentation, and Google search results, all afternoon and I'm still somewhat stuck in my attempt to create a dynamic form. I'm hoping I just need someone to nudge me in the right direction :-) I'm just starting to learn Django, so I'm still very much a beginner; however, I'm already an intermediate python user.
What I'm trying to do is create a dynamic form, where the user makes a selection from a drop-down menu, and based on that selection another part of the form will automatically update to display results relevant to the currently selected item, but from another database table.
I'll try and use a simplified version of the models from the Django tutorial to better illustrate what I'm trying to do:
# models.py
from django.db import models
class Poll(models.Model):
question = models.CharField(max_length=200)
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(max_length=200)
So lets say I want to have something like a drop-down selection field, populated with the question from each Poll in the database. I also want to have a text-field, which displays the corresponding choices for the currently selected Poll, which will update on-the-fly whenever the user selects a different Pool. I've been able to figure this out by placing a button, and posting information back to the form; However, I'm trying to do this automatically as the user makes a selection. My view sort of looks something like this at the moment:
#view.py
from django import forms
from django.shortcuts import render_to_response
from myapp.models import Poll,Choice
class MyModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s" % obj.question
class PollSelectionForm(forms.Form):
polls = MyModelChoiceField( queryset=Poll.objects.all() )
class ChoiceResults(forms.Form):
def __init__(self, newid, *args, **kwargs):
super(ChoiceResults, self).__init__(*args, **kwargs)
self.fields['choice'] = forms.TextField( initial="" )
def main(request):
return render_to_response("myapp/index.html", {
"object": PollSelectionForm(),
"object2": ChoiceResults(),
})
My template is very simple, just something like
{{ object }}
{{ object2 }}
I'm sure the way I'm going about creating the forms is probably not the best either, so feel free to criticize that as well :-) As I mentioned, I've read solutions involving reposting the form, but I want this to happen on-the-fly... if I can repost transparently then that would be fine I guess. I've also seen libraries that will let you dynamically create forms, but that just seems like overkill.
Here is one approach - Django/jQuery Cascading Select Boxes?
You can create a new view that just renders json to a string,
and then trigger an event when you're done selecting from the first list which loads the data dynamically from that json.
I do a similar thing here, populating a form based on a selection in a drop down. Maybe this helps you.
Here is the model of the values used to pre-populate the form:
class OpmerkingenGebrek(models.Model):
opmerking = models.CharField(max_length=255)
advies = models.CharField(max_length=255)
urgentiecodering = models.CharField(max_length=2, choices=URGENTIE_CHOICES_2011)
bepaling = models.CharField(max_length=155,blank=True,null=True)
aard = models.CharField(max_length=3, choices=AARD_CHOICES)
The view that manages the form:
def manage_component(request,project_id,.....):
# get values for pre-populate
og = OpmerkingenGebrek.objects.all()
.........
formset = ComponentForm(request.POST,request.FILES)
.........
)))
return render_to_response(template, {
'formset':formset,
........
'og':og,
},context_instance=RequestContext(request))
The html the renders the form
{% extends "base.html" %}
{% block extra_js %}
<script type="text/javascript" src="/media/js/limitText.js"></script>
<script type="text/javascript" src="/media/js/getValueOpmerking.js"></script>
{% endblock %}
<form enctype="multipart/form-data" method="post" action="">
{{ formset.as_table }}
</form>
<p>Choose default values:</p>
<select id="default" onChange="getValue(this)">
{% for i in og %}
<option value="{{ i.opmerking }} | {{ i.advies }} | {{ i.urgentiecodering }} |
{{ i.aard }} | {{ i.bepaling }}">{{ i.opmerking }}</option>
{% endfor %}
</select>
The javascript that pre-populates the form:
function getValue(sel)
{
//get values
var opm = sel.options[sel.selectedIndex].value;
//split string to parts
var parts = opm.split("|");
// autofill form
var opmerking = document.getElementById("id_opmerking");
opmerking.value = parts[0];
var aanbeveling = document.getElementById("id_aanbeveling");
aanbeveling.value = parts[1];
var opt = document.getElementById("id_urgentie");
var urgentie = opt.selectedIndex;
for(var i=0;i<opt.length;i++){
if(opt.options[i].value == parts[2].split(' ').join('')){
opt.selectedIndex = i;
}};
var opt = document.getElementById("id_aard");
var aard = opt.selectedIndex;
for(var i=0;i<opt.length;i++){
if(opt.options[i].value == parts[3].split(' ').join('')){
opt.selectedIndex = i;
}};
var bepaling = document.getElementById("id_bepaling");
bepaling.value = parts[4];
};
Does someone know a simple way to set the default zoom and latitude/longitude with floppyforms.gis.BaseGMapWidget ? And additionally, when creating an entry set panning as default tool.
In the form i define:
class PointWidget(floppyforms.gis.PointWidget, floppyforms.gis.BaseGMapWidget):
map_width = 690
map_height = 300
class EventForm(ModelForm):
class Meta:
model = Event
widgets = {
'coordinates': PointWidget(),
}
This perfectly shows the map widget and i can set a point. It also nicely centers & zooms the point when loading the form for an existing entry.
But when displaying the form to create a new entry it centers at the west-cost of africa..
Is there a way to achieve this (preferably by defining in the form/widget-class and not using additional javascript).
First, override get_context_data() on your widget class and point it
to a custom template:
class PointWidget(floppyforms.gis.BaseGMapWidget, floppyforms.gis.PointWidget):
template_name = 'custom_lonlat.html'
default_lon = 'something'
default_lat = 'something'
def get_context_data(self):
ctx = super(PointWidget, self).get_context_data()
ctx.update({
'lon': self.default_lon,
'lat': self.default_lat,
})
return ctx
Then create the custom_lonlat.html template:
{% extends "floppyforms/gis/google.html" %}
{% block options %}{{ block.super }}
options['default_lon'] = {{ lon }};
options['default_lat'] = {{ lat }};
{% endblock %}
If you want to make the default lon/lat dynamic, you can set them directly on the form instance from your views code:
form = EventForm()
form.fields['coordinates'].widget.default_lon = 'something'
form.fields['coordinates'].widget.default_lat = 'something else'
I'm the author of floppyforms and I've seen this question a couple of times… I'll add this answer to the official docs soon.
I've been scanning through Django documentation, and Google search results, all afternoon and I'm still somewhat stuck in my attempt to create a dynamic form. I'm hoping I just need someone to nudge me in the right direction :-) I'm just starting to learn Django, so I'm still very much a beginner; however, I'm already an intermediate python user.
What I'm trying to do is create a dynamic form, where the user makes a selection from a drop-down menu, and based on that selection another part of the form will automatically update to display results relevant to the currently selected item, but from another database table.
I'll try and use a simplified version of the models from the Django tutorial to better illustrate what I'm trying to do:
# models.py
from django.db import models
class Poll(models.Model):
question = models.CharField(max_length=200)
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(max_length=200)
So lets say I want to have something like a drop-down selection field, populated with the question from each Poll in the database. I also want to have a text-field, which displays the corresponding choices for the currently selected Poll, which will update on-the-fly whenever the user selects a different Pool. I've been able to figure this out by placing a button, and posting information back to the form; However, I'm trying to do this automatically as the user makes a selection. My view sort of looks something like this at the moment:
#view.py
from django import forms
from django.shortcuts import render_to_response
from myapp.models import Poll,Choice
class MyModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s" % obj.question
class PollSelectionForm(forms.Form):
polls = MyModelChoiceField( queryset=Poll.objects.all() )
class ChoiceResults(forms.Form):
def __init__(self, newid, *args, **kwargs):
super(ChoiceResults, self).__init__(*args, **kwargs)
self.fields['choice'] = forms.TextField( initial="" )
def main(request):
return render_to_response("myapp/index.html", {
"object": PollSelectionForm(),
"object2": ChoiceResults(),
})
My template is very simple, just something like
{{ object }}
{{ object2 }}
I'm sure the way I'm going about creating the forms is probably not the best either, so feel free to criticize that as well :-) As I mentioned, I've read solutions involving reposting the form, but I want this to happen on-the-fly... if I can repost transparently then that would be fine I guess. I've also seen libraries that will let you dynamically create forms, but that just seems like overkill.
Here is one approach - Django/jQuery Cascading Select Boxes?
You can create a new view that just renders json to a string,
and then trigger an event when you're done selecting from the first list which loads the data dynamically from that json.
I do a similar thing here, populating a form based on a selection in a drop down. Maybe this helps you.
Here is the model of the values used to pre-populate the form:
class OpmerkingenGebrek(models.Model):
opmerking = models.CharField(max_length=255)
advies = models.CharField(max_length=255)
urgentiecodering = models.CharField(max_length=2, choices=URGENTIE_CHOICES_2011)
bepaling = models.CharField(max_length=155,blank=True,null=True)
aard = models.CharField(max_length=3, choices=AARD_CHOICES)
The view that manages the form:
def manage_component(request,project_id,.....):
# get values for pre-populate
og = OpmerkingenGebrek.objects.all()
.........
formset = ComponentForm(request.POST,request.FILES)
.........
)))
return render_to_response(template, {
'formset':formset,
........
'og':og,
},context_instance=RequestContext(request))
The html the renders the form
{% extends "base.html" %}
{% block extra_js %}
<script type="text/javascript" src="/media/js/limitText.js"></script>
<script type="text/javascript" src="/media/js/getValueOpmerking.js"></script>
{% endblock %}
<form enctype="multipart/form-data" method="post" action="">
{{ formset.as_table }}
</form>
<p>Choose default values:</p>
<select id="default" onChange="getValue(this)">
{% for i in og %}
<option value="{{ i.opmerking }} | {{ i.advies }} | {{ i.urgentiecodering }} |
{{ i.aard }} | {{ i.bepaling }}">{{ i.opmerking }}</option>
{% endfor %}
</select>
The javascript that pre-populates the form:
function getValue(sel)
{
//get values
var opm = sel.options[sel.selectedIndex].value;
//split string to parts
var parts = opm.split("|");
// autofill form
var opmerking = document.getElementById("id_opmerking");
opmerking.value = parts[0];
var aanbeveling = document.getElementById("id_aanbeveling");
aanbeveling.value = parts[1];
var opt = document.getElementById("id_urgentie");
var urgentie = opt.selectedIndex;
for(var i=0;i<opt.length;i++){
if(opt.options[i].value == parts[2].split(' ').join('')){
opt.selectedIndex = i;
}};
var opt = document.getElementById("id_aard");
var aard = opt.selectedIndex;
for(var i=0;i<opt.length;i++){
if(opt.options[i].value == parts[3].split(' ').join('')){
opt.selectedIndex = i;
}};
var bepaling = document.getElementById("id_bepaling");
bepaling.value = parts[4];
};