Using django-select2 for a dropdown list with pictures - javascript

I'm trying to take another approach for the problem I desribed below making use of django-select2 module.
Django choicefield using pictures instead of text not displaying
I have a django model which looks like:
My model looks like:
class MyPicture(models.Model):
name = models.CharField(max_length=60,
blank=False,
unique=True)
logo = models.ImageField(upload_to='logos')
def __str__(self):
return self.name
I would like to create a drop down looking like
source: http://select2.github.io/select2/
where essentially I have a big picture on the left (corresponding to what I called logo) and some text on the right (corresponding to name).
My form looks like:
from django_select2.forms import Select2MultipleWidget
class MyPictureForm(forms.Form):
pictures = forms.ModelChoiceField(widget=Select2MultipleWidget, queryset=MyPicture.objects.all())
My view looks like:
def mypicture(request):
form = MyPictureForm(request.POST or None)
if form.is_valid():
#TODO
return render(request, 'myproj/picture.html', {'form': form})
and finally, my hmtl (where something is wrong):
{% load staticfiles %}
<head>
{{ form.media.css }}
<title>In construction</title>
</head>
<form action="{% url 'mypicture' %}" method="post">
{% csrf_token %}
<select id="#e4" name="picture">
{% for x in form.pictures.field.queryset %}
<option value="{{ x.id }}"><img src="{{ MEDIA_URL }}{{ x.logo.url }}" height=150px/></option>
{% endfor %}
</select>
<br>
<br>
<input type="submit" value="Valider" class="btn btn-primary"/>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script type="text/javascript">
function format(x) {
if (!x.id) return x.name; // optgroup
return "<img src=" + x.logo.url + "/>" + x.name;
}
$(document).ready(function () {
$("#e4").select2({
formatResult: format,
formatSelection: format,
dropdownCssClass: "bigdrop", // apply css that makes the dropdown taller
escapeMarkup: function (m) { return m; } // we do not want to escape markup since we are displaying html in results
});
});
</script>
{{ form.media.js }}
</form>
Unfortunately I'm not good enough to tell whether my error is in the javascript or in the loop. When I right click to get the source code, the options are properly set. So I must not be to far from the answer.
It might be trivial for those of you with more javascript experience than me.
Thanks for your help!!
Best:
Eric

I think (most) browsers don't allow anything inside an <option>-tag besides plain text.
This is what Mozilla says about the allowed content of <option>-tags:
Permitted content: Text, possibly with escaped characters (like
é).`
source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option
Not sure if you can set something as a background though.

Related

How to "load" dependent drop down upon page load?

I have a form with a dependent drop-down. This secondary drop-down is hidden whenever the primary option selected does not have any secondary options, and when the page first loads. Whenever the form is submitted, only the first field gets cleared out, since most of the time the drop-downs remain the same, however, since the script works whenever there is a change in the primary drop-down, since the load upon does not constitute a change, it just keeps the selected/submitted option on the primary drop-down, and will just display an empty secondary drop-down, even when the primary option selected does have secondary options. I got most of the JS from the drop-down from a tutorial, as I am not very familiar with it. For a more visual understanding:
This is the form when the page first loads
When you select an option that has secondary options, the other dropdown appears
After you select a Station and submit, the Employee # clears, but the other two are supposed to remain, however, when the page reloads upon submission, it looks like this, and the station has been cleared according to the debugger since there are none technically. I don't care so much about the station clearing, but more about not having an empty drop-down that should not be empty.
And when I look at the data that remained in the form, only the work area stayed, because the dependent dropdown does not load until you select another option from the drop down, and if you wanted to be able to see the Box Assembly options again, you'd have to click another option and then go back to Box Assembly (for example)
How could I fix this issue? Is there a way to force the javascript to attempt to load first so that it checks if the option that remained does have the secondary options, whether it has been triggered or not?
forms.py
class WarehouseForm(AppsModelForm):
class Meta:
model = EmployeeWorkAreaLog
widgets = {
'employee_number': ForeignKeyRawIdWidget(EmployeeWorkAreaLog._meta.get_field('employee_number').remote_field, site, attrs={'id':'employee_number_field'}),
}
fields = ('employee_number', 'work_area', 'station_number')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['station_number'].queryset = Station.objects.none()
if 'work_area' in self.data:
try:
work_area_id = int(self.data.get('work_area'))
self.fields['station_number'].queryset = Station.objects.filter(work_area_id=work_area_id).order_by('name')
except (ValueError, TypeError):
pass
elif self.instance.pk:
self.fields['station_number'].queryset = self.instance.work_area.stations.order_by('name')
views.py
def enter_exit_area(request):
enter_without_exit = None
exit_without_enter = None
if request.method == 'POST':
form = WarehouseForm(request.POST)
if form.is_valid():
emp_num = form.cleaned_data['employee_number']
area = form.cleaned_data['work_area']
station = form.cleaned_data['station_number']
# Submission logic
form = WarehouseForm(initial={'employee_number': '', 'work_area': area, 'station_number': station})
else:
form = WarehouseForm()
return render(request, "operations/enter_exit_area.html", {
'form': form,
'enter_without_exit': enter_without_exit,
'exit_without_enter': exit_without_enter,
})
urls.py
urlpatterns = [
url(r'enter-exit-area/$', views.enter_exit_area, name='enter_exit_area'),
path('ajax/load-stations/', views.load_stations, name='ajax_load_stations'),
]
At the end of this html is the script that handles the dependent drop-down
enter_exit_area.html
{% extends "operations/base.html" %}
{% block main %}
<form id="warehouseForm" action="" method="POST" data-stations-url="{% url 'operations:ajax_load_stations' %}" novalidate >
{% csrf_token %}
<div>
<div>
<label>Employee #</label>
{{ form.employee_number }}
</div>
<div>
<label>Work Area</label>
{{ form.work_area }}
</div>
<div class="col-xs-8" id="my-hidden-div">
<label>Station</label>
{{ form.station_number }}
</div>
</div>
</form>
<script>
function loadStations() {
var url = $("#warehouseForm").attr("data-stations-url");
var workAreaId = $(this).val();
var $stationNumberField = $("#{{ form.station_number.id_for_label }}");
$.ajax({
url: url,
data: {
'work_area': workAreaId
},
success: function (data) {
$("#my-hidden-div").show(); // show it
$stationNumberField.html(data);
// Check the length of the options child elements of the select
if ($stationNumberField.find("option").length === 1) {
$stationNumberField.parent().hide(); // Hide parent of the select node
} else {
// If any option, ensure the select is shown
$stationNumberField.parent().show();
}
}
});
}
$("#id_work_area").change(loadStations);
$(document).ready(loadStations);
</script>
{% endblock main %}
station_number_dropdown_options.html
<option value="">---------</option>
{% for station in stations %}
<option value="{{ station.pk }}">{{ station.name }}</option>
{% endfor %}
I see that you have $(document).ready(loadStations);.
But the problem is that in loadStations, you do var workAreaId = $(this).val();.
this will be document, and $(document).val() is an empty string.
Either hardcode the selector in loadStations:
// var workAreaId = $(this).val();
var workAreaId = $("#id_work_area").val();
Or trigger the change from the element instead:
$("#id_work_area").change(loadStations);
// $(document).ready(loadStations);
$("#id_work_area").change();

Django : combine variable and HTML id in template

I'm looking for a way to "concatene" a django object list (get via the get_queryset in the views.py) with a Javascript variable. then concatene them to a field of a model object.
My model is a blog post, and contains the post_title field (a charfield).
In my index.html, I have a pager button with id="1" or id="2" corresponding to the index of the page and a onClick="displayPostContentOnPageButtonCliked(this.id)" attribute.
My index.html retrieve all post objects in a context_object_name called all_post_by_date.
In my index.html, I want to display on alert dialog the post title corresponding to the button clicked, something like:
<script>
function displayPostContentOnPageButtonCliked(page_button_clicked_id){
var all_posts_by_date = "{{all_posts_by_date.page_button_clicked_id.post_title}}";
alert(all_posts_by_date);
}
</script>
But this doesn't work. I tried to add filter or create my own filter (called get_index, but only manage to get like: all_posts_by_date|get_index:page_button_clicked_id and not be able to combine the .post_title part.
My question is: how to get {{all_posts_by_date.page_button_clicked_id.post_title}} to work, and alert "Title of post 3" when the button 3 is clicked ?
Thank u !
Two solutions for you: (1) send in the data you want to your JS function in the call to the JS function.
<script>
function showPostTitle(post_title){
alert(post_title);
}
</script>
. . .
{% for x in all_posts_by_date %}
<div id='blog-post-{{ x.id }}' on-click="showPostTitle('{{ x.post_title }}')">
. . .
{% endfor %}
Or you can just serialize the data yourself for later use:
<script>
var titles = [];
{% for x in all_posts_by_date %}
titles.push('{{ x.post_title }}');
{% endfor %}
function displayPostContentOnPageButtonCliked(nId) {
var title = titles[nId - 1];
alert(title);
}
</script>

Not able to implement a dynamic dropdown list in Django

I need to implement two dropdown lists that the values of the seconds depends on the selection of the first.
I was able to implement that in the backend but I am struggling to do it in the front end and more specifically with javascript!
countries = Country.objects.filter(Enabled=True)
citiesByCountry = {}
for country in countries:
citiesInCountry = City.objects.filter(Enabled=True, Country=country)
cities = []
for city in citiesInCountry:
cities.append(city.Name)
citiesByCountry[country.Name] = cities
context = {'citiesByCountry': citiesByCountry}
return render(request, 'index.html', context)
So I have the following structure:
'Country':['City1', 'City2']
Here is the HTML:
<div class="form-group col-md-4">
<select class="form-control" onchange="test(this.value)" id="sel1">
{% for country in citiesByCountry %}
<option value="{{ country }}">{{ country }}</option>
{% endfor %}
</select>
</div>
<div class="form-group col-md-4">
<select class="form-control" id="cities">
</select>
</div>
So I have added the following javascript:
<script>
var country_objs = {};
{% for country, cities in citiesByCountry.items %}
country_objs['{{country|escapejs}}'] = '{{cities|escapejs}}';
{% endfor %}
</script>
<script type="application/javascript">
function test(country) {
var $cities_select = $("#cities");
$(country_objs[country]).each(function(){
$cities_select.append('<option>' + this + '<\option>');
});
}
</script>
The second dropdown never get populated but when I print the contents of the country_objs like this: console.log(country_objs[country]);
I get the following:
['City1', 'City2', 'City3']
Which is correct, but the .each function does not loop through the items. I think the problem is that the above is not a proper array but a string but still can't understand why.
Note that I get the following error:
jquery.min.js:2 Uncaught Error: Syntax error, unrecognized expression: ['City1', 'City2', 'City3']
Unfortunately whatever I try won't work, I couldn't imagine that implementing this in Django will be so hard.
I would like to avoid using a third-party app or module to do this simple thing and I would like to use a proper way to do it (i.e the best way) so any ideas will be really valuable.
There are two solutions:
Solution 1:
use a for loop:
country_objs['{{country|escapejs}}'] = [{% for city in cities %}"city",{% endfor %}];
Solution 2:
Switch the line:
citiesByCountry[country.Name] = cities
for:
citiesByCountry[country.Name] = json.dumps(cities)
to encode to json, and then in the template:
country_objs['{{country|escapejs}}'] = {{cities|safe}};
Obs regarding solution 2:
You can't have the single quotes around the variable
'{{cities|safe}}';
in the second solution, or else when you add the list ['City1', 'City2', 'City3'] you're gonna have:
'['City1', 'City2', 'City3']'
I think you want to remove the |escapejs filter for the part you want to be parsed in JavaScript. You might even find you need |safe, but you should be certain that you have control over what gets output there before considering that.
var country_objs = {};
{% for country, cities in citiesByCountry.items %}
country_objs['{{country|escapejs}}'] = {{cities|safe}};
{% endfor %}
For the updating part, this should work:
function updateCities(country) {
var $cities_select = $("#cities");
$(country_objs[country]).each(function(key, value) {
$('#cities').append($("<option></option>")
.attr("value",key)
.text(value));
});
}
$('#sel1').change(function() {
updateCities(this.value);
});
Credit due in part to this answer https://stackoverflow.com/a/171007/823020.
The above is missing an initial setting, which you could either do in templating or JavaScript. For JavaScript, you could insert another updateCities($('#cities).val());.
The above also appends every time, instead of resetting the options to an empty list. This is left as an exercise for the reader.
Suggestion 1: You didn't discuss this, but your initial query would be better done something like this:
# the following may differ based on your foreign key's related_name attribute
countries = Country.objects.filter(Enabled=True).select_related('city_set')
for country in countries:
citiesInCountry = country.city_set.values_list('name', flat=True)
This would all be a single query. However you'd need to rethink about the 'active' flag, or how to achieve that if you still need it.
Suggestion 2: To be honest, it might be better in general to wrap it up in json. In your view:
import json
countries_json = json.dumps(citiesByCountry)
and then in the template:
var country_objs = {{ citiesByCountry|safe }};

Django-autocomplete-light how to get data from html?

I can't get how to fetch data from HTML-element that contains data generated by django-autocomplete-light.
Here is a code of the form:
class ThreadForm(forms.Form):
topic = forms.CharField(label="Topic", max_length=255)
body = forms.CharField(label="Body", widget=forms.Textarea(attrs={'rows': '12', 'cols':'100'}))
tags = autocomplete_light.fields.MultipleChoiceField(choices=(tuple((tag.name, tag.name) for tag in Tag.objects.all())),
label='Tags',
widget=autocomplete_light.widgets.MultipleChoiceWidget('TagAutocomplete',
attrs={'class':'form-control',
'placeholder':'Tag'}
)
)
def save(self, author, created):
topic = self.cleaned_data['topic']
body = self.cleaned_data['body']
tags = self.cleaned_data['tags']
th = Thread(author = author,
topic = topic,
body = body,
created = created,
)
rtags = []
for tag in tags:
sr = Tag.objects.get(tag)
rtags.append(sr.name)
th.save()
Tag.objects.update_tags(th, tags)
And autocomplete_light_registry.py:
from threads.models import Thread
import autocomplete_light
from tagging.models import Tag
class TagAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['^name']
autocomplete_light.register(Tag, TagAutocomplete, attrs={
'data-autocomplete-minimum-characters': 1,
},)
As you see I've changed the django-autocomplete app. In the base.py I found a variable choice_html_format = '<span data-value="%s" name="choice">%s</span>'
Attribute name was added by me to get data like that:
tags = request.POST.get('name')
But this doesn't work. I'm getting an error like "NoneType in not callable"
Next thing I've tried is change choice_html from base.py:
def choice_html(self, choice):
"""
Format a choice using :py:attr:`choice_html_format`.
"""
return self.choice_html_format % (
escape(self.choice_value(choice)),
escape(self.choice_label(choice)))
It is original function, I've changed choice_value(choice) to choice_label(choice). And got an error "invalid literal for int() with base 10: <tag_name_here>". Looks like data-value attribute is only for int() type (but I can't get where I can change it, maybe in js-function, I don't know).
And the last, I'm trying to get the pk of each tag, and then get the name via manager. But I'm getting error Cannot resolve keyword '4' into field. Choices are: id, items, name.
I absolutely sure that there is an easy way to perform the task I need.
autocomplete-light has a template called widget.html that is rendered in the template:
...
{% block select %}
{# a hidden select, that contains the actual selected values #}
<select style="display:none" class="value-select" name="{{ name }}" id="{{ widget.html_id }}" multiple="multiple">
{% for value in values %}
<option value="{{ value|unlocalize }}" selected="selected">{{ value }}</option>
{% endfor %}
</select>
{% endblock %}
...
as you can see, this <select> element contains all selected choices for the autocomplete widget.
Its name (we are going to identify it by its name attribute later in the view) is simply the autocomplete's name ('tags').
So now you need to make sure your autocomplete field in the template is wrapped in <form> tags so the values get submitted (if you haven't already).
The next step is to retrieve the data in the view:
request.POST.getlist('tags')
That's it. You now have a list of primary keys of the selected values:
>>> print(str(request.POST.getlist('tags'))
['1', '3', '4', '7', ...]

How to POST and capture a jQuery ui-slider outputted value in a Django view?

I am trying to POST the value from a jQuery UI slider that appears on an internal page of a survey form to a Django SessionWizardView. I am pretty new to JavaScript and jQuery but have some experience with Python 2.7.3 / Django 1.6.2.
Question: How do I 'send' and 'capture' the data sent from a jQuery ui-slider via POST in a Django view?
Aditional questions/info
I have been having an issue with CSRF validation which may be the root cause of the issue.
Is there a particular method I should be using?
What is missing from the steps I have taken below?
wizard_form.html
I have a jQuery-UI Slider adopted from a tutorial by Thoriq Firdaus. This appears on an internal page within my SessionWizardView.
<form action="" method="post">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
</table>
{% load staticfiles %}
{% if 'surveyone' in request.path %}
{% if wizard.steps.current in steps %}
<img src="{% static "survey/images/pathone/" %}{{display_image}}" height="600" width="500" style="border:1px solid black;" align="middle"/>
<section>
<span class="tooltip"></span>
<div id="slider"></div>
<span class="volume"></span>
</section>
/my_project/src/survey/static/survey/js/slider.js
This is the relevant part of my slider.js code. I am trying to use jQuery.post to post the value to my Django view.
slide: function (event, ui) {
var value = slider.slider('value'),
volume = $('.volume');
console.log(value)
$.post("/surveyone/", {value : value, csrfmiddlewaretoken : '{{csrf_token}}' } );
},
/my_project/src/survey/views.py
This is my SessionWizardView which contains get_context_data and the done methods, both of which work fine. My problem is that I'm not really sure how to 'receive' the posted data.
I have read through the Form Wizard documentation but nothing stands out, the closest thing is the render(self, form=None, **kwargs): method, but that says it should be used "after the GET or POST request has been handled"
class SurveyWizardOne(SessionWizardView):
def get_context_data(self, form, **kwargs):
context = super(SurveyWizardOne, self).get_context_data(form, **kwargs)
if self.steps.current in ['5','6','7','8','9','10','11','12','13','14','15','16']:
step = int(self.steps.current)
if step in (5, 6, 7):
image = random.choice(PATH_ONE_IMAGES)
images.insert(step - 5, image)
PATH_ONE_IMAGES.remove(image)
context['display_image'] = image
elif step == 8:
context['first_image'] = images[0]
context['second_image'] = images[1]
context['third_image'] = images[2]
elif step in (9, 10, 11):
image = random.choice(PATH_ONE_IMAGES)
images.insert(step - 6, image)
PATH_ONE_IMAGES.remove(image)
context['display_image'] = image
elif step == 12:
context['fourth_image'] = images[3]
context['fifth_image'] = images[4]
context['sixth_image'] = images[5]
steps = ['5','6','7','9','10','11']
dv_steps = ['8','12']
context.update({'steps': steps,
'dv_steps': dv_steps
})
def done(self, form_list, **kwargs):
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
So in essence, I'm not really sure what to do/try next. Could anyone provide any hints or tips?
Since the data is to be picked in SessionWizardView i would suggest to have a hidden field slider_value in a form. Now in the frontend this field is not visible.
The slider function will update the value in this hidden field which can be posted by posting the form. The value can then be accessed from the form object in session wizrd view.

Categories

Resources