Most common way to structure similar but unique ajax requests that depend on Django pk? - ajax

(I'm using jquery and django)
Let's say I have a todo list, with each item having a unique primary key (pk). In the template, I list these items with a for loop so that each item is clickable, to display item details on another portion of the page (without reloading the entire page of course).
What is the generally accepted best way to do this?
I have tried multiple ways of "uniqueifying" each div, and multiple ways of passing this pk along in the ajax request.
Part 1 - getting unique divs:
I put the pk as the suffix to each item div's id. e.g. {% for item
in todolist %} stuff
$('body').on('click',"#item_{{item.pk}}", function(){var id = {{item.pk}}, .ajax stuff })
{% endfor %}
instead of suffixing pk, I add a custom html attribute called
data-id. This allows me to use .attr("data-id") to get the pk from
the clicked div and to remove the js from the for loop.
instead of adding a custom html attribute, I add a hidden form field.
Part 2 - passing pk to ajax request:
in the $.ajax() body, I set url: id+"/details" with the data field empty. In Django, I grab the id from the url regex definition.
in the $.ajax() body, I set url: "/details" with data: {id:id}. In Django, I grab the id from inside views.py with request.POST['id'].
in js, I just submit the form. Django gets the id as a form field.
So... with so many (probably bad) ways of doing this, which would you use?

I will go for a single ajax function and it should be:
{% for item in todolist %}
<div id="{{item.id}}" class="item_ajax" >{{item.name}}</div>
{% endfor %}
<script>
$('.item_ajax').click(function(){
$.ajax({
type: 'POST',
url: '{% url item_url %}',
data: {'id': $(this).attr('id'), 'csrfmiddlewaretoken': '{{csrf_token}}'},
dataType: "text",
success: function(response) {
// do something
},
error: function(rs, e) {
alert(rs.responseText);
}
});
});
</script>
I have used a class selector, obviously you can choose what ever you like e.g. having a custom attribute but it should be common.

Related

Use AJAX to update django if-else template

In template I have:
{% for item in items %}
{% if item.public is True%}
<a href="{% url 'motifapp:article_public_edit' article.id %}">
show icon1
</a>
{% else %}
<a href="{% url 'motifapp:article_public_edit' article.id %}">
show icon2
</a>
{% endif %}
{endfor}
I use ajax to handle the request here:
$(anchor).click(function(e){
e.preventDefault();
var href = $(this).attr('href')
$.ajax({
type: 'POST',
url: href,
data: {csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),},
success: function (response) {
//refresh a div ?
}
});
});
Ajax handle the post, and in view it reverse a boolean value 'public'.
Question is, how do I update the template? I can pass the opposite icons and value, but it seems very redundant. Is there a way just to refresh that div with anchor and let the if else statement takes care of what's showing ?
Thanks
You are 99% close to answer your own question.
First, you you must wrap the {% for %} in a div and save it as a separate file, say:
<!-- injection.html -->
<div id="some-id>
{% for item in items %}
same code here
{% endfor %}
</div>
Now in your main template you should include that file like this:
<div id="wrapper">
{% include 'templates/injection.html' %}
</div>
Now, once you make an AJAX request to your views function then this view should render that div (which is a separate html file) but with a different items value. Like this:
# views.py
def article_public_edit(request, id):
if request.is_ajax():
# new calculation of the items QuerySet
# depending on the data passed through the $.ajax
return render(request, 'injection.html', locals())
Finally, you can do this inside the $.ajax() success function:
$.ajax({
type: 'POST',
url: href,
data: {csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),},
success: function (response) {
// replace the inside of #wrapper div with the injection.html (that has the new items values)
$("#wrapper").replaceWith(response);
}
});
With ajax calls there is no way the Django template tags will be re-evaluated. They only get evaluated on initial page load.
I think the best way to go is to have the ajax view return the boolean value and then set the image in your front-end code depending on that value.

remove item from django-carton with Ajax

This is driving me crazy. I am building an ecommerce app, with the cart in Django-carton. When I add an item to the cart, I can get the item's id from the context into the store, and pass it into my Ajax call and to the view when a customer adds the item with a button click.
I want the customer to be able to delete and edit quantities in the cart using a button, and am now trying to create my delete and my edit quantity functions. I'm stuck because I don't understand how to pass the id the the view in Ajax. The id isn't in the item context object. I can get the id in the view by printing ids = request.session['CART'], but it does not have the current id. The items in context are limited to the following:
self.product = product
self.quantity = int(quantity)
self.price = Decimal(str(price))
The example in Django-carton's documentation has this example, which doesn't use Javascript:
views:
def remove(request):
cart = Cart(request.session)
product = Product.objects.get(id=request.GET.get('id'))
cart.remove(product)
return HttpResponse("Removed")
urls:
u`rl(r'^remove/$', 'remove', name='shopping-cart-remove'),`
In my view, I can get the ids of all of the objects in the cart with
cart = Cart(request.session)
ids = request.session['CART']
which gives me the following object:
{u'meal_pk': 15, u'price': u'5', u'quantity': 39}
But this doesn't actually seem helpful. This is my first encounter with sessions. I've been reading through the code here https://github.com/lazybird/django-carton/blob/master/carton/cart.py How can I edit or delete an item in my cart?
You can still call the remove view via AJAX quite easily with Javascript; unless otherwise specified, the view does not care if the request is submitted via AJAX. So, we can set that up easily w/ JQuery.
So, in a template showing the shopping cart, for example:
{% load carton_tags %}
{% get_cart as cart %}
<script type="text/javascript" src="path/to/jquery.js">/script>
{% for item in cart.items %}
<a onclick='AjaxRemove("{% url 'shopping-cart-remove' %}?id={{ item.product.id }}")'>Remove this item</a>
{% endfor %}
<script type="text/javascript">
function AjaxRemove(remove_url) {
$.ajax({
url: remove_url,
success: function(response) {alert(response);},
error: function() {alert("Couldn't remove item");}
})
</script>
will remove the item and give an alert if the AJAX request responds with success.
You can further customize the view response to respond differently to AJAX requests using request.is_ajax():
def remove(request):
cart = Cart(request.session)
product = Product.objects.get(id=request.GET.get('id'))
cart.remove(product)
if request.is_ajax():
# do something, respond differently
return HttpResponse("Removed (via AJAX)")
return HttpResponseRedirect(reverse('shopping-cart-show'))

How to update a modelForm in Django with Ajax

I would like to update a template with Ajax.
My problem is:
I select a client in a list on the form and display only the corresponding data in an another list on the same page in a second list
At this time, I can not update my template with protocols corresponding to the client
In my views, I try to create a list with a queryset (it works)
but I cannot update my template with the new list
I retrieve the selected client but when I post with render_to_request
it does not update the template
Is there any possibility to do that and how can I update my list with the ajax part of the program.
You can use something like this:
(based on https://stackoverflow.com/a/21762215/5244995)
second_list.tmpl (template)
{% for value in corresponding_data %}
<li>{{ value }} (replace with your own templating)</li>
{% endfor %}
views.py
def update_second_list(request, ob_id):
# (get the data here)
return render('second_list.tmpl', {'corresponding_data': ...}
JS script on main page (uses jQuery)
$.ajax({url:"", dataType:"text", success: function(html) {
var newDoc = $.parseHTML(html, document, false); // false to prevent scripts from being parsed.
var secondList = $(newDoc).filter(".secondList").add($(newDoc).find(".secondList"));
$(".secondList").replaceWith(secondList); // only replace second list.
// other processing
}});

How do I render a view after POSTing data via AJAX?

I've built an app that works, and uses forms to submit data. Once submitted, the view then redirects back to display the change. Cool. Django 101. Now, instead of using forms, I'm using Ajax to submit the data via a POST call. This successfully saves the data to the database.
Now, the difficult (or maybe not, just hard to find) part is whether or not it's possible to tell Django to add the new item that has been submitted (via Ajax) to the current page, without a page refresh. At the moment, my app saves the data, and the item shows up on the page after a refresh, but this obviously isn't the required result.
If possible, I'd like to use exactly the same view and templates I'm using at the moment - essentially I'd like to know if there's a way to replace a normal HTTP request (which causes page refresh) with an Ajax call, and get the same result (using jQuery). I've hacked away at this for most of today, so any help would be appreciated, before I pull all of my hair out.
I had a very similar issue and this is how I got it working...
in views.py
from django.utils import simplejson
...
ctx = {some data to be returned to the page}
if ajax == True:
return HttpResponse(simplejson.dumps(ctx), mimetype='json')
then in the javascript
jQuery.ajax({
target: '#id_to_be_updated',
type: "POST",
url: "/",
dataType: 'json',
contentType: "text/javascript; charset=\"utf-8\"",
data: {
'foo':foo,
'bar':bar,
},
success: function(data){
$("#id_to_be_updated").append(data.foo);
}
});
Here's how I did it:
The page that has the form includes the form like so
contact.html
{% include "contact_form.html" %}
This way it's reusable.
Next I setup my view code (this view code assumes the contact form needs to be save to the db, hence the CreateView):
class ContactView(CreateView):
http_method_names = ['post']
template_name = "contact_form.html"
form_class = ContactForm
success_url = "contact_form_succes.html"
There are a few things to note here,
This view only accepts pots methods, because the form will be received through the contact.html page. For this view I've setup another template which is what we included in contact.html, the bare form.
contact_form.html
<form method="POST" action="/contact">{% crsf_token %}
{{ form.as_p }}
</form>
Now add the javascript to the contact.html page:
$("body").on("submit", 'form', function(event) {
event.preventDefault();
$("#contact").load($(this).attr("action"),
$(this).serializeArray(),
function(responseText, responseStatus) {
// response callback
});
});
This POSTS the form to the ContactView and replaces whatever is in between #contact, which is our form. You could not use jquery's .load function to achieve some what more fancy replacement of the html.
This code is based on an existing working project, but slightly modified to make explaining what happens easier.

Symfony 2.0 updating select options with JS?

I've been googling for hours but surprisingly I didn't find any topic on that subject.
I have the following Form
class propertyType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('city')
->add('district', 'entity', array('class'=>'FlatShanghaidefaultBundle:district',
'property'=>'name',
'query_builder' => function ($repository) {
$qb = $repository->createQueryBuilder('district');
$qb->add('where', 'city = :city');
$qb->setParameter('city', 1);
return $qb;
}
public function getName()
{
return 'property';
}
}
When the user choose a City in the form, I want the options of district to be dynamically updated and limited to that city. With Ajax or JS?
What would be the best practice? Do you know a tutorial on that topic?
If someone can put me on the right tracks, that would help a lot..
Thanks!
The query builder will not solve your problem, you can remove it altogether.
That query is run when the form gets built, once you have it on your browser you need to use javascript to populate the options.
You can have the options stored in a javascript variable, or pull them from the server as needed with ajax (you will need a controller to handle these ajax requests).
You will probably want to use some jquery plugin to handle the cascading logic between the select elements, there are a couple available:
I use this one, but it seems to be offline: http://devlicio.us/blogs/mike_nichols/archive/2008/05/25/jquery-cascade-cascading-values-from-forms.aspx
And there is this one, which I never used really: http://code.google.com/p/jquery-cascade/
There is also at least this Bundle I know of: https://github.com/genemu/GenemuFormBundle, which has ajax field types available for several jquery plugins. This may save you writing the ajax part to handle the data, as it comes built in (it's probably easier to implement the controller your self anyway). I haven't tried this one, and I don't know if it has cascading support.
Jbm is right about the query builder. And his approach is perfecly valid.
Another option could be to dispense the cascade select in favor of an autocomplete field.
Assuming that you save the countries, cities and districts as entities and have a relation between them, you do not even need to save what city/country has been selected because you can just call:
$district->getCity()->getCountry();
I have implemented a similar thing for country/city selection and will link here to the the main involved files.
First, create a custom form type to encapsulate all form stuff, it contains a hidden field to store the selected id and a text field to serve as input for the autocomplete logic:
https://github.com/roomthirteen/Room13GeoBundle/blob/master/Form/LocationFieldType.php
Then theme the form type:
https://github.com/roomthirteen/Room13GeoBundle/blob/master/Resources/views/Form/fields.html.twig
The url of the autocomplete source is passed as data attribute so no JS will be smutching the html code.
Last but not least, the JS functions have to be implemented:
https://github.com/roomthirteen/Room13GeoBundle/blob/master/Resources/public/jquery.ui.location-autocomplete.js
The result can be seen in the image below, see that for clarity the country name will be displayed in braces behind the city name:
--
I favor this solution much more that using cascade selects because the actual value can be selected in one step.
cheers
I'm doing this myself on a form.
I change a field (a product) and the units in which the quantity can be measured are updated.
I am using a macro with parameters to adapt it more easily.
The macro :
{% macro javascript_filter_unit(event, selector) %}
<script>
$(function(){
$('#usersection')
.on('{{ event }}', '{{ selector }}', function(e){
e.preventDefault();
if (!$(this).val()) return;
$.ajax({
$parent: $(this).closest('.child_collection'),
url: $(this).attr('data-url'),
type: "get",
dataType: "json",
data: {'id' : $(this).val(), 'repo': $(this).attr('data-repo'), parameter: $(this).attr('data-parameter')},
success: function (result) {
if (result['success'])
{
var units = result['units'];
this.$parent.find('.unit').eq(0).html(units);
}
}
});
})
});
</script>
{% endmacro %}
The ajax returns an array : array('success' => $value, 'units' => $html). You use the $html code and put it in place of the select you want to change.
Of course the javascript code of the ajax call need to be modfied to match your fields.
You call the macro like you would normally do:
{% import ':Model/Macros:_macros.html.twig' as macros %}
{{ macros.javascript_filter_unit('change', '.unitTrigger') }}
So I have two arguments : the event, often a change of a select. and a selector, the one whose change triggers the ajax call.
I hope that helps.

Resources