I am converting a project from php to Django and have run into the issue of filtered menus. I have a form:
class SearchForm(forms.Form):
genus = forms.CharField(max_length=100)
# species
species = forms.CharField(max_length=100)
# island group
island_group = forms.ModelChoiceField(queryset=Localitymayor.objects.values_list('islandgroup', flat=True).distinct('islandgroup').exclude(islandgroup="n/a").order_by('islandgroup'), empty_label=_("Not Specified"))
# island name
island_name = forms.ModelChoiceField(queryset=Localitymayor.objects.values_list('islandname', flat=True).distinct('islandname').exclude(islandname="n/a").order_by('islandname'), empty_label=_("Not Specified"))
my template is along the lines of:
<form action="{% url cdrs_search %}" method="post">{% csrf_token %}
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
</form>
Now I want to filter the output of the island_name depending on the selection of island_group. In my php project I managed this with an ajax onChange call to another php script. However, I am a little lost how to do this in Django. As this is my first time working with ajax in Django I would appreciate any suggestions as to the best practice way of dealing with this simple but common filtered menu problem. Thanks in advance.
you need to have a view for your ajax script to hit to ask for a list of names based on a group, e.g.
# views.py
def ajax_endpoint(request):
# leaving error checking to you
group = request.GET.get('group')
names_list = avalable_names(group) # some function or db call
return HttpResponse(json.dumps(names_list))
One option is to do this in javascript. You can make a call to a separate view using an ajax request from jQuery. The job of this separate view is to handle the server side sorting of your model data (island)name based on the user selected island_group). Then you can use javascript to repopulate your forms using the response from the view. Some good examples on how to do this can be found in this blog (it is a little dense, but is very useful), in an article on how to do this with javascript, and in this tutorial (highly recommended).
There is also a good SO post that explains why it is necessary to do it this way, it may be a bit pedantic to include but it helped clarify things for me while creating filtered forms. Just look at the accepted answer for that question.
Here a modification of code from the ticket form of betspire.com. This code snippet depends on having jQuery loaded.
Here's the javascript you need:
function update_select(select, data) {
select.find('option').remove();
select.append($('<option value="">-------</option>'));
for (var i in data) {
select.append($('<option value="'+data[i][0]+'">'+data[i][1]+'</option>'));
}
}
$('select[name=island_group]').live('change', function(e) {
$.get(
'{% url island_name_choices_for_island_group %}',
{
'island_group': $(this).val(),
},
function(data, textStatus, jqXHR) {
update_select($('select[name=island_name]'), data);
},
'json'
);
});
Add to urls:
url(
r'island_name/choices/$',
'island_name_choices_for_island_group', {
}, 'island_name_choices_for_island_group',
),
Add to views:
from django.utils import simplejson
from models import *
def island_name_choices_for_island_group(request, qs=None):
if qs is None:
# Change the default QS to your needs
# (you didn't specify it)
qs = Island.objects.all()
if request.GET.get('island_group'):
# assuming your Island model has an FK named island_group to model IslandGroup
qs = qs.filter(island_group__pk=request.GET.get('island_group'))
results = []
for choice in qs:
results.append((choice.pk, choice.name))
return http.HttpResponse(simplejson.dumps(results))
Please let me know if you run in any troubble.
Related
My question is best described by example.
Consider a table of students. When I click on a student, a pop-up should be opened with comprehensive info on the student. At first, each time the user clicks on a student, I did a GET-request via Ajax, and populated the pop-up with the fetched response. In other words, I did something like this:
def my_view(request):
if request.method == 'GET':
model_instance = MyModel.objects.get(id = request.POST['id']):
context = {'my_form': MyForm(instance = model_instance)}
msg = render_to_string('my_template.html', context, request = request)
return JsonResponse({'object': msg}, safe = False)
else:
# POST-request for saving input data to db
And then in the JS code:
$.get(....., function(response){
$(response.object).modal('show');
}
The problem with this code is that the pop-up appears with a delay. Well, yes, it's half a second, and yet I would like the response to be instant. Especially, if the user has slow internet, user experience is even worse.
What is the best-practice here ? On alternative that occurs to me is the following: when rendering the main page (with student tables), pass an empty form (or as Django doc calls it, unbound form by doing my_form = MyForm()), and then populate it with JavaScript when the user clicks on a student. Well, this approach yields super-fast pop-up rendering, and yet the approach is not DRY, the case with ForeignKey fields is very nasty here, and in general, this approach seems junk code
Populating the form with JavaScript doesn't have to be ugly. I've done something like this before using HTML data- attributes to help keep things clean. If you're using jQuery, it could look something like this:
{% for student in students %}
<div class="student" data-student-id="{{ student.id }}" data-student-first-name="{{ student.first_name }}">
{{ student.first_name }}
</div>
{% endfor %}
<!-- Pretend this is inside a modal -->
{{ form.as_p }}
<script>
$('.student').click(function() {
var studentId = $(this).data('student-id');
var firstName = $(this).data('student-first-name');
$('#id_id').value = studentId;
$('#id_first_name').value = firstName;
});
</script>
This assumes that Student.id is a HiddenInput Widget.
It doesn't take much JS to get you a pretty good result.
I just started a web app using Django and HTML/Javascript templates.
My Django spot app contains a Spot model that is sent to a HTML template - to be used with the Google Map Api. I've encountered a problem when looping over the variable spots containing Spot.objects.all().
It seems the problem comes from the way I send the data to the HMTL file.
----------------------------------------- Spot Django-app : models.py --------------------------------------------
class Spot(models.Model):
idn = models.IntegerField(unique = True)
name = models.CharField(max_length = 200)
longitude = models.FloatField()
latitude = models.FloatField()
------------------------------------------------- HTML / JS -----------------------------------------------
<script type="text/javascript">
var IDs = []
var names = []
var lat = []
var lng = []
{ % for spot in spots % }
IDS.push( {{spot.idn}} );
names.push( {{spot.name}} );
lat.push( {{spot.latitude}} );
lng.push( {{spot.longitude}} );
{ % endfor % }
Then, the lists do not contain any data that can be used afterwards. Worse, the HTML file does not work if the names.push( {{spot.name}} ) is un-commented.
----------------------------------------- Spot Django-app : views.py --------------------------------------------
from spots.models import Spot
def index(request):
return render(request, 'index.html', {'spots':Spot.objects.all()})
Thanks to the other stackoverflow questions (listed below), I also tried to serialize the Spot.objects.all() either with django.core.serializers.serialize("json", Spot.objects.all() ) or by creating my own serializer (thanks to Django_REST). The problem remains the same. So is the problem in the way I parse my data with JS?
I've look the following link :
Returning JSON array from a Django view to a template
django for loop in a .html template page (newbie)
Django FOR LOOP in JavaScript
with no success. So if the answer is included or related to these topics, would you mind explaining me something I've been working around for days ...
EDIT:
The problem was plural:
Serializing the data (or not ; I did not for now but everyone who answered agreed to say that it's better to)
Adding the quotes from {{ spot.name }} to '{{ spot.name }}', only to non Integer/Float models (i.e. only the models.CharFields fields)
Google Maps Api may return errors for some (longitude, latitude) tuples even if they are well-defined
Django will not recognize those template tags because you have spaces between the brace and the percent. So, there is no looping being done at all. You need to write them in the correct format:
{% for spot in spots %}
...
{% endfor %}
Once you do that, you'll start getting all sorts of JS syntax errors because you have not wrapped any of your data in quotes. But, as the comments say, doing this as JSON would be much better.
Even that I think that serializing your data into Json is much better idea. Your javascript code does not work because e.g. {{ spot.name }} will render raw string so for javascript to understand it you need to put it in quotes (and of course semicolon after each line).
names.push('{{spot.name}}');
I have a list of id all_entries_user. They basically serve as a part of url for rest service that I have developed using TastyPie. Inside my Django template i want to use them by iterating all_entries_user
function ajaxCall(){
$.getJSON("http://localhost:8000/api/Location/" + {{ all_entries_user.pop }} + "/?format=json",
function(json) {
convert(json,"googleMapUser");
}
);
}
Using this I am getting values from Service and this happens in a continous interval .
interval = startInterval(ajaxCall, 3000);
The value of url must change for each call and it must be taken from list
all_entries_user
gives me the same id every time.
I tried to pop values but each time gives me same value
I havent found an efficient way to iterate through this.
Suggestions and Help Please
Why don't you use the for construct?
{% for user in all_entries_user %}
Do your thing with {{ user }}
{% endfor %}
It's very confusing when you mix two languages. What's happening is your javascript is getting "Built" once when the template is rendered so you only have one URL regardless of how many times javascript hits that method. You can verify this by using view source and checking out what your javascript looks like to the client.
If you want the client to call a different URL each time, you will have to send all the IDs down to the client first. One way to do this is by using json serializer or just a simple home grown javascript array builder.
Something like this might work:
<script>
var allEntries = [{% for entry in all_entries_user %}{{ entry.id }},{% endfor %}];
for (entryId in allEntries) {
doSomethingWith(entryId);
}
</script>
This isn't the best way to populate an array of javascript from django, but it works in a hurry.
let's say one of my urlpatterns looks like this.
url('^objects/update/(?P<pk>\d+)$', views.UpdateView.as_view(), name = 'update-object'),
I need to redirect user to the update page depending on the selected object (the list of objects is populated using Ajax). So I'd like to pass that named url pattern to the JavaScript, in order to build the actual url on the client side.
Example of what I want to achieve:
pass the name 'update-objects' to the function
get the actual url pattern, replace (?P<pk>..) with {pk}
pass the result to the javascript, resulting in : objects/update/{pk}
any tips?
thanks
to make it more clear: at the moment of rendering, I can't do url reverse because the PK is not known yet. I need to make kind of javascript-urlpattern which will later be converted to the real url (i.e. my JS code will replace {pk} part with the actual pk value)
The actual URL reversing must happen on the server side. There are several ways to do this, and the most elegant of these probably depends on how exactly your script and markup are set up for this. One thing I've done recently is to attach the URL to a logical element using HTML5 data attributes, which are easy to retrieve using jQuery. If you're not using jQuery, I'll leave it up to you to translate to pure JS. You haven't provided any code or specifics for your client-side, so I'm kind of shooting in the dark here, but maybe this will give you the idea:
Django HTML template:
<ul class="object-list">
{% for object in objectList %}
<li data-update-url="{% url update-objects object.pk %}">object.name</li>
{% endfor %}
</ul>
JS:
$('.object-list').on('click', 'li' function () {
var updateUrl = $(this).data('update-url')
...
});
It sounds like you need to make an additional ajax call once the object has actually been selected. Don't try and second guess your url.conf by trying to work out the url on the client side - you'd just be making trouble for yourself later. Wait till you can get a pk, then use django's reverse function to give you your url (doing anything else violates DRY).
How about creating a simple view that returns the url -
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseBadRequest
def get_url(request):
if request.is_ajax() and request.method == 'POST':
obj_id = request.POST['obj_id']
url = reverse('object-update', kwargs{'pk': obj_id})
return HttpResponse(obj_id)
return HttpResponseBadRequest()
Then write a javascript function that gets the url using an ajax call to your new view and then redirects. You'd call this function as soon as the object's been selected. I would suggest using JQuery to do this, pure javascript will require you to write more code, and probably write browser specific code (depending on your target). Also it supports dealing with django's csrf protection (you'll need to implement this for ajax calls if you haven't already).
var redirect = function(obj) {
$.ajax({
url: '/your-get-url-view/',
method: 'post',
data: {'obj_id': obj},
success: function(url){
window.location = url;
}
});
}
I'm afraid I don't know how you're getting from the selected object to the pk (For simplicity I've assumed it's available to the redirect function) - you may have to do some processing in the view to get there.
I haven't tested the above code, but it should give you an idea of what I'm suggesting.
Try this one:
Reverse method for generating Django urls
https://github.com/mlouro/django-js-utils
One more
https://github.com/Dimitri-Gnidash/django-js-utils
If you have a URL that only has one PK field in it, you could resolve it with any number (e.g. 0), then substitute the number as required.
In my scenario my URL had a pk then an upload_id, so I had to replace on the right most instance of a 0, with <upload_id>, which the JS would replace this string occurance as required:
detele_url_upload_id_0 = reverse(f'{APP_NAME}:api_upload_delete', args=[pk, 0])
prefix, suffix = detele_url_upload_id_0.rsplit('0', 1)
context['generic_delete_url'] = prefix + '<upload_id>' + suffix
Then in the JS:
const deleteUrl = genericDeleteUrl.replace('<upload_id>', uploadId)
I am writing a django app where the user wants to click a button and have a partial page change. Data needs passed from the server to the web page without a needing a complete page refresh. That task sounded like a job for ajax. However, I can't make Ajax work in my app.
I cannot get the call into my server-side function. Below is the code the subject matter is regarding missed calls. My intent is to get the server side to return a list of missed calls and display it to the user without having to refresh the page.
When I click the button, I get a popup that says "Something goes wrong" using firebug, I traced this to a DAJAXICE_EXCEPTION but I don't know anything else about it.
What's going on here? How do I make this work? Also if there's an easier way to do this that doesn't require the Dajax library please advise. And any step-by-step examples would be very helpful.
Server side function
-------- /jim/ajax.py---------
#dajaxice_register
def missedCalls(request, user):
print "Ajax:missedCalls" #never prints...
missedCalls = ScheduledCall.objects.filter(status__exact='Missed')
render = render_to_string('examples/pagination_page.html', { 'missedCalls': missedCalls })
dajax = Dajax()
dajax.assign('#calls','innerHTML', render)
return dajax.json()
-------page.html---------
<script type='text/javascript'>
function missed_calls_callback(data){
# The dajax library wants a function as a return call.
# Have no idea what I'm supposed to do with this part of the function.
# what is supposed to go here?
alert(data.message);
}
</script>
<!-- Button -->
<input type="button" name="calltest" value="JQuery Test"
id="calltest" onclick="Dajaxice.jim.missedCalls(missed_calls_callback, {'user':{{ user }}})">
<div id="calls">
{% include "calls.html" %}
</div>
--------calls.html--------
<h2> Missed Calls</h2>
<ul>
{% for i in missedCalls.object_list %}
<li>{{ i }}</li>
{% endfor %}
</ul>
Before you start using a library, if might be helpful to do manually (to see what's going on).
An ajax request is a HTTP request like any other except that it happens asynchronously (i.e. outside the normal request/response cycle) and it usually returns json or xml (although you can return html if you like).
This means that to accept an AJAX request you just create an url and view as you would normally.
urls.py
...
url(r"^/my/ajax/path/$", myapp.views.ajax_view, name="do-something-ajaxy"),
...
views.py
def ajax_view(self, request):
# Django's Request objects have a method is_ajax()*
# which checks the header to see if it's an 'ajax' request
if request.is_ajax():
raise Http404
missedCalls = ScheduledCall.objects.filter(status__exact='Missed')
# You can return a dictionary-like json object which can be manipulated by the javascript when it receives it
return HttpResponse(simplejson.dumps(missedCalls), mimetype='application/javascript')
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.is_ajax
And using jquery to carry out the ajax request:
(function($){
$.ajax({
type: 'GET',
url: '/my/ajax/path/',
success: function(data){
for call in data:
/* Do something with each missed call */
},
});
});