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 }};
Related
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.
I'm learning django/python/css/etc... and while doing this, I've decided to build an app for my website that can pull simple movie data from TMDb. What I'm having trouble with is figuring out a way to add a way for the user to select two different movies, and once selected, see the differences between them (run time, budget, etc).
I've got grabbing the data from the API covered in that doing a search for a movie on my site returns expected results. But now I'm having a really tough time trying to figure out how to select 1 item from the results to "save" it, search again, select the second movie, and have the comparison show up.
I know it's pretty vague, but any help getting me pointed in the right direction would be greatly appreciated!
here's what I'm doing so far with the code:
views.py:
from django.shortcuts import render
from django.conf import settings
from .forms import MovieSearch
import tmdbsimple as tmdb
tmdb.API_KEY = settings.TMDB_API_KEY
def search_movie(request):
"""
Search movie title and return 5 pages of results
"""
parsed_data = {'results': []}
if request.method == 'POST':
form = MovieSearch(request.POST)
if form.is_valid():
search = tmdb.Search()
query = form.cleaned_data['moviename']
response = search.movie(query=query)
for movie in response['results']:
parsed_data['results'].append(
{
'title': movie['title'],
'id': movie['id'],
'poster_path': movie['poster_path'],
'release_date': movie['release_date'][:-6],
'popularity': movie['popularity'],
'overview': movie['overview']
}
)
for i in range(2, 5 + 1):
response = search.movie(query=query, page=i)
for movie in response['results']:
parsed_data['results'].append(
{
'title': movie['title'],
'id': movie['id'],
'poster_path': movie['poster_path'],
'release_date': movie['release_date'][:-6],
'popularity': movie['popularity'],
'overview': movie['overview']
}
)
context = {
"form": form,
"parsed_data": parsed_data
}
return render(request, './moviecompare/movies.html', context)
else:
form = MovieSearch()
else:
form = MovieSearch()
return render(request, './moviecompare/compare.html', {"form": form})
def get_movie(request, movid):
"""
from search/movie results, get details by movie id (movid)
"""
movie = tmdb.Movies(movid)
response = movie.info()
context = {
'response': response
}
return render(request, './moviecompare/detail.html', context)
movies.html:
{% extends 'moviecompare/compare.html' %}
{% block movies_returned %}
<div class="wrap">
<div class="compare-gallery">
{% for key in parsed_data.results|dictsortreversed:'release_date' %}
{% if key.poster_path and key.release_date and key.title and key.overview %}
<div class="gallery-item">
<img src="http://image.tmdb.org/t/p/w185/{{ key.poster_path }}">
<div class="gallery-text">
<div class="gallery-date"><h5><span><i class="material-icons">date_range</i></span> {{ key.release_date }}</h5></div>
<div class="gallery-title"><h3>{{ key.title }}</h3></div>
<div class="gallery-overview">{{ key.overview|truncatechars:80 }}</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endblock %}
and I've got a simple detail.html that doesn't do anything just yet, but it's just for showing detail results for a single movie, so not important to get into yet as it'll just be styling.
I'd like for each result in the gallery to have a link to a details page for the movie(done), and also a select box (or similar) to select it as one of the comparison movies.
If I can just get some help on how to select two different movies from the search results, and compare those, I think I could work out a way to do the same with two separate movie searches. Thanks for any help!
edit: here's what I have so far on pythonanywhere -
Assuming you want to be able to add movies from different searches (e.g. search for rambo, add rambo, search for south park, add south park), you can probably do something like:
#require_POST
def save_compare(request, pk):
""" Async endpoint to add movies to comparison list """
movies = request.session.get('comparing_moviews', [])
movies.append(movies)
# if you only need the data the API served originally, you could also save that to a data-base when you read it from the API and use it here
request.session['comparing_movies'] = movies
return JsonResponse("")
def show_comparison(request):
""" show differing stats like "1 hour 3 mins longer """
mov1, mov2 = request.session.get('comparing_movies') # need to handle wrong number of selected
# not sure from the description how to look up the movies by id
mov1 = tmdb.Search().search(pk=mov1)
mov2 = tmdb.Search().search(pk=mov2)
return render_to_response("comparison.html", context={
"mov1": mov1, "mov2": mov2,
"length_diff": mov1['length'] - mov2['length']})
to give you the comparison stats you want. You'll also need session middleware installed.
From the search results page, you'll add a button that triggers an async js post to save_compare which starts accumulating a comparison list. When they're done, they can click "compare" or something on that list, which will render "comparison.html" with the two movies and the diff however you like it.
As an example of some calculation for your "comparison.html", you can grab the hours and minutes from the length_diff and render them as "X hours and y minutes longer."
I worked all day on it and impossible to figure it out so I need some help :)
I try to add some text on each variant line of my "Size" dropdown on my product page :
if the product quantity > 0 : Size + Quick Delivery
else : Size + 2-3 weeks delivery
Just want to display it to the customer before the click, so I don't want it just on the selectedVariant.
I tried to change it by my script.js, I was thinking to :
copy each variant quantity (I didn't find the way to do it)
copy my Dropdown in a list value/key + add text (Quick/2-3 weeks) depending of the variant quantity
var optionsSizes = {};
var optionsSizes = {};
$('#mySizes option').each(function(){
optionsSizes[$(this).val()] = $(this).text() + variant.inventory_quantity;
});
//console.log(optionsSizes);
var $el = $("#mySizes");
$el.empty(); // remove old options
$.each(optionsSizes, function(value,key) {
$el.append($("<option></option>")
.attr("value", value).text(key));
});
The copy/paste of the dropdown work but that's all.
It was easy to do it on variantSelected but that's not what I want.
Please feel free to ask if you have any question.
Cheers,
bkseen
('#product-select-5916642311-option-0') and $('#mySizes') this select elements are not in your theme by default. A Shopify or theme script adds these two elements based on the product's JSON information available via Shopify. Hence there is no direct way to get the desired result.
Here's the trick that can achieve what you desire.
Load all the variants and their required properties into a JSON object. To do this add <script>variantsJSON = {}</script> at the top of the liquid file.
Now load the variants in the following structure:
variantsJSON = {
"variant_option1" : {
"variant_option2_1_title" : "variant_option2_1_quantity",
"variant_option2_2_title" : "variant_option2_2_quantity",
} ....
}
To get the above structure, you need to add the following script inside {% for variant in product.variants %} or a similar loop in your liquid file.
<script>
if (!variantsJSON['{{ variant.option1 }}']){
variantsJSON['{{ variant.option1 }}'] = {}
}
{% assign availability = 'QUICK DELIVERY' %}
{% if variant.inventory_quantity == 0 %}{% assign availability = '2-3 WEEKS DELIVERY' %}{% endif %}
if (!variantsJSON['{{ variant.option1 }}']['{{ variant.option2 }}']){
variantsJSON['{{ variant.option1 }}']['{{ variant.option2 }}'] = '{{ availability }}'
}
</script>
The above snippet (possible refinement required) will load all the variants and their availability into the JSON object
Now all you need to do is trigger a function on change of $('#product-select-{{ product.id }}-option-0') which will clear all <li>s in $('#mySizes'), then populates them using the values stored in variantsJSON's variant_option2 & variant_availability of the selected variant_option1
P.S. Feel free to format my answer. I'm somehow unable to get the right formatting.
To answer Hymnz on
That's tricky but I think I can help. How are you changing the span with the classes product__inventory-count product__form-message--success ? – HymnZ 10 hours ago
Blockquote
if (variant) {
{% if settings.product_show_shipping %}
var myCount = variant['inventory_quantity'];
var myPath = this.element.find('.product__inventory-count');
if (myCount < 1){
myPath.removeClass('product__form-message--success');
myPath.addClass('product__form-message--error');
myPath.text("2-3 weeks delivery");
}else{
//myPath.toggleClass('product__form-message--success').siblings().removeClass('checked');
myPath.removeClass('product__form-message--error');
myPath.addClass('product__form-message--success');
myPath.text("Quick delivery");
}
{% endif %}
The thing is, variant is the variant selected and not going through all the product's variants.
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', ...]
So I'm working with Django on a website that I am playing around with, and have been trying to research how I could get the following list in my views.py and be able to reference it in my javascript? I'm working on creating an ajax call and the tutorials I am coming accross are a bit confusing.
#lines 6 - 8 of my code.
def catalog_home(request):
item_list = item.objects.order_by('name') #item is the model name
note: the item model containts a name, description, overview and icon column.
Is it possible for me to use the list above (item_list) and be able to write a javascript function that does something similar to this? :
$(document).ready(function() {
$("#showmorebutton").click(function() {
$("table").append("<tr></tr>");
for (var i = 0; i < 3; i++) {
var itemdescription = item.description;
var itemName = item.name;
var icon = item.icon;
$("table tr:last").append(generateCard(itemName,
itemdescription,
icon));
}
function generateCard(itemNameC, itemdescriptionC, iconC) {
var card = "<td class='tablecells'><a class='tabletext' href='#'><span class='fa "
+ iconC
+ " concepticons'></span><h2 class='header'>"
+ itemNameC
+ "</h2><p>"
+ itemdescripionC
+ "<span class='fa fa-chevron-circle-right'></span></p></a></td>";
return card;
}
I don't mean to crowd source the answer to this, I just would appreciate any feedback/advice for me to handle this task, as I am fairly new to coding.
You should absolutely be able to do this. The trick is to understand Django templates. You showed part of the view but you will need to render to a template. Inside the template, you can just do something like
HTML code for page goes here (if you're mixing js and html)
<script>
var items = [{% for d in data %}
{% if forloop.last %}
{{ d }}
{% else %}
{{ d }},
{% endif %}
{% endfor %}];
// code that uses items
</script>
Note there's a little bit of work required to make sure you have the right # of commas [taken from this SO answer and your code should handle the case where the array is empty.
Alternatively, you could do an ajax call from static javascript to your django server using something like django rest framework to get the list of items as a json object.