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

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', ...]

Related

dynamic form in django using choiceField

I'm building my first web app with django and am having a hard time figuring out how to use dynamic form in order to have different output for different selected choice.
for example, for my measure choice qualitative, I want the form be the same(no extra fields) but if the quantitative value is selected, I want my template to show two more fields(value_min and value_max)
the first option when qualitative value is selected
the second option when quantitative value is selected
thank you for your help...
You can't use the django tags for conditions, because it only renders from the backend, so this is a frontend issue. In my implementations I normally use javascript with the following idea:
Start with the values min and max not displayed (style.display = "None")
AddEventListener (onChange type) to the selector (in your case, Mesure)
Check if the condition is met with javascript and change the style.display to block, for example
Forms are rendered on the template before the page load. So, django variables cannot e manipulated by the user.
While rendering your form, django allows you to set classes to the form fields. use them to hide the extra fields.
example value_min = forms.CharField(widget=forms.TextInput(attrs={'class':'hide'}))
you may add a form check while clean
class MYform(forms.Form):
#....
def clean_min_value(self):
#check here for choice field value
#return value or blank accordingly
similarly you can add validators to the form to ensure this value is only set if choice is set to quantitative
value_min = forms.CharField(widget=forms.TextInput(attrs={'class':'hide'}), validators=[check_choice])
def check_choice(Value):
# validate Value
thanks #videap and #Rhea for your help... so I figured out how to resolve my problem using the guidance of videap and the post Show and hide dynamically fields in Django form
So the solution was:
for the form :
class NewCriterioForm(forms.ModelForm):
parent = TreeNodeChoiceField(queryset=Criteria.objects.all())
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.criteria_CHOICES = [('FunCriteria','FunCriteria'),('nonFunCriteria','nonFunCriteria')]
self.mesure_CHOICES = (('Quantitative','Quantitative'),('Qualitative','Qualitative'))
self.fields['parent'].label=''
self.fields['parent'].required=False
self.fields['type']= forms.CharField(widget=forms.Select(choices=self.criteria_CHOICES))
self.fields['mesure']= forms.ChoiceField(choices=self.mesure_CHOICES)
class Meta:
model = Criteria
fields = ('name', 'parent', 'type','slug','description','mesure','value_min','value_max')
}
for the view :
......
criterion = NewCriterioForm()
return render(request, 'addCriteria.html', {'criterion': criterion})
and finaly, in the template , I put this code:
<script>
function Hide() {
if(document.getElementById('id_mesure').options[document.getElementById('id_mesure').selectedIndex].value == "Qualitative") {
document.getElementById('id_value_min').style.display = 'none';
document.getElementById('id_value_max').style.display = 'none';
} else {
document.getElementById('id_value_min').style.display = '';
document.getElementById('id_value_max').style.display = '';
}
}
window.onload = function() {
document.getElementById('id_mesure').onchange = Hide;
};
</script>
<div>
{{ criterion.name.label_tag }}{{ criterion.name }}
</div>
<tr></tr>
<div>
{{ criterion.parent.label_tag }}{{ criterion.parent }}
</div>
<div>
{{ criterion.type.label_tag }}{{ criterion.type }}
</div>
<div>
{{ criterion.slug.label_tag }}{{ criterion.slug }}
</div>
<div>
{{ criterion.description.label_tag }}{{ criterion.description }}
</div>
<div>
{{ criterion.mesure.label_tag }}{{ criterion.mesure }}
</div>
<div id="id_value_min">
{{ criterion.value_min.label_tag }}{{ criterion.value_min }}
</div>
<div id="id_value_max">
{{ criterion.value_max.label_tag }}{{ criterion.value_max }}
</div>

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();

How to select document Ids that are being populated via Flask on page load

I am using a submit button to pass form values into flask, which then populate the ids and values of an html table on another page.
I then want to select those values by their ids and use them to filter a dataset. However, I'm finding that when I try to use d3.select(#thing-id).value, I am getting back undefined, I think because the file which is selecting the ids is running before they are populated in the html.
I've tried to use a variety of selection methods including d3 and get Document by Id.
The Flask:
#app.route("/prediction", methods=["GET", "POST"])
def predict():
if request.method == 'POST':
new_film = [request.form["adults"], request.form["years"],
request.form["film_length"], \
request.form["genre_1"], request.form["genre_2"], request.form["genre_3"]]
def commentary(rating):
if rating <= 3:
comment = "This sucks worse than what Rachel said"
elif rating <= 6:
comment = "Mediocre at best"
else:
comment = "The best thing since sliced bread"
return comment
prediction = {
"adults_entered1": new_film[0],
"years_entered1": new_film[1],
"film_length_entered1": new_film[2],
"genre1_entered1" : new_film[3],
"genre2_entered1" : new_film[4],
"genre3_entered1" : new_film[5],
"prediction": commentary(rating[0]),
"rating": rating[0]
}
for key, value in prediction.items():
print(f"{key}: {value}")
return render_template('prediction.html', result=prediction)
The html:
<tbody id="filter-table"></tbody>
<tr>
{% if result %}
{% for key, value in result.items() %}
<td id={{key}}>
{{value}}
</td>
{% endfor %}
{% endif %}
</tr>
The Javascript:
filter_val = d3.select(#adults_entered1).value
console.log(filter_val)
I would expect to be returned the {{value}} for the adults_entered1. Instead it is undefined.

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 }};

Categories

Resources