compare two results (of many) from api data with django/python - javascript

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."

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

How to count blog categories in django and display them in a template

am new in django am developing a blog which has functions post_list,category_detail and post detail am stuck
in post_list function which renders a blog.html i want to display the blog category which are python and hardware together with the total number of posts in that category
i have tried my ways it shows the wrong way since there should be six posts in python category and one post in hardware category see the picture here please check the codes and help out
views.py
def post_list(request):
object_list=Post.objects.filter(status='Published').order_by("-created")
recent_post=object_list[:4]
category_list_count=Post.objects.annotate(num_category=Count('Category'))
page = request.GET.get('page', 1)
paginator = Paginator(object_list, 3)
try:
items = paginator.page(page)
except PageNotAnInteger:
items = paginator.page(1)
except EmptyPage:
items = paginator.page(paginator.num_pages)
context={
'items':items,
'recent_post':recent_post,
'category_list_count':category_list_count,
}
return render(request,"blog.html",context)
blog.html
<div class="widget">
<h3 class="badge">CATEGORIES</h3>
{% for obj in category_list_count %}
<ul>
<li>{{obj.Category}} <span class="badge">{{obj.num_category}}</span>
</li>
</ul>
{% endfor %}
Try this:
# You should change post in Count function based on your model.
categories = Category.objects.all().annotate(posts_count=Count('post'))
Then you will have access to number of posts for each category:
for category in categories:
print(category.posts_count)
In your template:
{% for category in categories %}
<p>Number of posts: {{category.posts_count}}</p>
{% endfor %}

HTML Dropdown Menu / Typeaheads (From Python to JavaScript)

My plan is to create a website where people can write diaries and read others' diaries and I am currently in the middle of implementing a search box where the user may search usernames to read others' diaries (like how you may look up a friend on Facebook), so I wrote the following code for the search box, with the Python code (contains SQLite statements) extracting search results from the database and with the JavaScript constantly performing the search action behind the scene, to output to search.html as a dynamic dropdown menu. However, the drop-down part is not working.
Application.py
#app.route("/search", methods=["GET", "POST"])
#login_required
def search():
"""Search published diaries from others."""
# if user reached route via POST (as by submitting a form via POST)
if request.method == "POST":
# ensure friends' usernames were submitted
if not request.form.get("search"):
return dumbo("No username entered")
# search user's friends from database
# append % to q so that user doesn't have to input city/state/postal code in the URL completely/accurately
# https://www.tutorialspoint.com/sqlite/sqlite_like_clause.htm
friend = request.form.get("search") + "%"
searches = db.execute("SELECT username FROM writers WHERE username LIKE :friend", friend=friend)
# output a JSON array of objects, each represents a row from TABLE writers that matches what the user looks for
return jsonify(searches)
# else if user reached route via GET (as by clicking a link or via redirect)
else:
return render_template("search.html")
scripts.js
$("#friend").keydown(function() {
// configure typeahead
$("#friend").typeahead({
// If true, when suggestions are rendered, pattern matches for the current query in text nodes
// will be wrapped in a strong element with its class set to {{classNames.highlight}}. Defaults to false.
highlight: false,
// The minimum character length needed before suggestions start getting rendered. Defaults to 1.
minLength: 1
},
{
display: function(suggestion) { return null; },
limit: 5,
source: search,
templates: {
suggestion: Handlebars.compile(
"<div>" +
"<div>{{username}}</div>" +
"</div>"
)
}
});
// action after friend is selected from drop-down
$("#friend").on("typeahead:selected", function(eventObject, suggestion, name) {
// visit friend's diaries (to be continued)
});
});
/**
* Searches database for typeahead's suggestions.
*/
// search calls asyncResults after searching is done (asynchronously)
function search(query, syncResults, asyncResults)
{
// get usernames matching query (asynchronously)
var parameters = {
friend: $("#friend").val()
};
$.getJSON(Flask.url_for("search"), parameters)
.done(function(data, textStatus, jqXHR) {
// call typeahead's callback with search results (i.e., friends)
asyncResults(data);
})
.fail(function(jqXHR, textStatus, errorThrown) {
// log error to browser's console
console.log(errorThrown.toString());
// call typeahead's callback with no results
asyncResults([]);
});
}
search.html
{% extends "layout.html" %}
{% block title %}
Search
{% endblock %}
{% block main %}
<form action="{{ url_for('search') }}" method="post">
<fieldset>
<div class="form-group">
<input autocomplete="off" autofocus class="typeahead" id="friend" name="search" placeholder="Search friends" type="text"/>
</div>
<div class="form-group">
<button class="btn btn-default" type="submit">Search</button>
</div>
<!-- https://www.mkyong.com/javascript/how-to-link-an-external-javascript-file/ -->
<script type="text/javascript" src="static/scripts.js"></script>
</fieldset>
</form>
{% endblock %}
I literally just started learning CS, so please advise as if I am an idiot, thank you!!!!
In your Application.py, POST method returns jsonify(searches) ,
so I think should use $.post instead of $.getJSON in
$.getJSON(Flask.url_for("search"), parameters)
...
Update:
If you get friend from "search" in server,
friend = request.form.get("search") + "%"
then should change friend to search in parameters in search function
var parameters = {
//friend: $("#friend").val()
search: $("#friend").val()
};

Dynamically update Django form field options using Ajax to GET new queryset

I'm new to coding and django and I'm struggling to find the solution to the following problem having reviewed the answers I've found.
Im creating a search form with multiple fields. When the user selects the first field category (and before hitting search) I would like to dynamically change the queryset for the second field sub_category such that only related values are shown.
I have models.py as follows:
class Product(models.Model):
category = models.ForeignKey("Category")
sub_category = models.ForeignKey("SubCategory")
class Category(models.Model):
name = models.CharField(max_length=256)
class SubCategory(models.Model):
category = models.ForeignKey("Category")
name = models.CharField(max_length=256)
And my forms.py includes:
class BasicSearchForm(forms.Form):
category = forms.ModelChoiceField(
label='Category',
queryset=Category.objects.all(),
to_field_name="name",
empty_label=None,
initial="Red")
sub_category = forms.ModelMultipleChoiceField(
required=False,
label='Type',
queryset= SubCategory.objects.all(),
to_field_name="name",
widget=forms.Select)
And my views.py includes:
def search(request):
if request.method == 'POST':
form = BasicSearchForm(request.POST)
if form.is_valid():
category = form.cleaned_data['category']
sub_category = form.cleaned_data['sub_category']
return render(request, 'myapp/search.html', {'form': form})
else:
form = BasicSearchForm()
return render(request, 'myapp/search.html', {'form': form})
And finally the search.html includes:
<form class="search-form" role="search" action="/search/" method="get">
{{ form }}
<input type="submit" value="Search" />
</form>
I've played around with a few answers but nothing seems to work. I'd really appreciate some help. Thanks in advance!
Update:
Thanks for the feedback. As a result I updated the following:
In my urls.py:
urlpatterns = [
url(r'^ajax/update_subcategories/$', views.update_subcategories, name='update_subcategories'),
And in my views.py:
def update_subcategories(request):
category = request.GET.get('category', None)
sub_category = list(SubCategory.objects.filter(category__name__exact=category).values('name'))
return JsonResponse(sub_category, safe=False)
And I have this in my myapp/search.html:
{% block javascript %}
<script>
$("#id_category").change(function () {
var category = $(this).val();
$.ajax({
url: '{% url "myapp:update_subcategories" %}',
data: {
'category': category,
},
success: function (response) {
var new_options = response;
alert(new_options[0].name); // works
$('#id_sub_category').empty();
$.each(new_options, function(key, value) {
$('#id_sub_category')
.append($('<option>', { value : key })
.text(value.name));
});
}
});
</script>
{% endblock %}
Update: The sub_category options were showing as [object Object] until I changed value to value.name and it looks like it's working. I'll test it out and close unless there are any comments.
Update: Im still having an issue with the browser back button. When a user clicks back the dropdown values have changed back to the original queryset rather than the updated version.
You can't do this from Django views side, ie, backend. You could try an ajax request for implementing this kind of requests, by sending a GET request to the server for populating the drop-down or whatever you are into.
For a simple example, you could refer
here
How do I POST with jQuery/Ajax in Django?
EDIT
def update_subcategories(request):
category = request.GET.get('category', None)
sub_category = list(SubCategory.objects.filter(category__name__exact=category).values('name'))
return JsonResponse(dict(sub_category=sub_category))
Then in ajax response you could grab it like response.data.sub_category
Use ajax to send the category and retrieve subcategory elements.
For the category, send it via get request, and using the orm return the subcategories in a json format which you can show using jQuery.

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