JohnJ
JohnJ

Reputation: 7056

ajax post data in django1.5 ListView

So, I have a small blog app(django newbie here) with blog articles, and I am looking to include a "rating" to each of these items (similar to up/down on stackoflow). So, what I have is a ajax based GET in a ListView - but I now realize I need POST to "modify" this ratings data. I have been looking to post data from a django ListView, but I am unable to see how this can be done. The current GET code, which is a listview of all blog articles (paginated) looks as follows:

#views.py
class AwesomeDisplayListView(JSONResponseMixin,ListView):
    model = blogposts
    template_name = "awesome_list.html"
    paginate_by = '15'
    context_object_name = "searchres"

    def get_context_data(self, **kwargs):
        print "this is get_context_data"
        context = super(SearchDisplayListView, self).get_context_data(**kwargs)
        q = self.request.GET.get('q')
        context['searchq'] = q
        return context

    def get_queryset(self):
        print "this is get_queryset"
                # get some queryset
        return queryset 

    def render_to_response(self, context):
        if self.request.is_ajax():
            obj = {'name':'ajax', 'birthday':'may'}
            return JSONResponseMixin.render_to_response(self, obj)
        else:
            return ListView.render_to_response(self, context)

The above code works fine to get data ("obj") into the template and I am able to render both the blogposts and the GET ajax, but, what I would like to do is post data so that: [1] the rating is increased from its current value by 1 [2] the particular action (clicking to vote), requires login_required.

I was wondering if someone could point me in the right direction to do this. I have googled the problem, and people seem to suggest combining Mixins - but, unsure if this should be the case.

Thanks.

Upvotes: 1

Views: 2115

Answers (1)

czarchaic
czarchaic

Reputation: 6328

I can't tell you how much I learned by looking at the class based view source code. Once you've seen it, it becomes very easy to modify your class object to do what you want. In your case, if you add a post method to your class, it will be called in the event of a post to the url mapped to the view.

def post( self, request, *args **kwargs ):
  # receive the post, process the rating, return your JSON response

In response to your comments

Since you're wanting users to be able to upvote/downvote from a ListView, you will need some way of identifying the post being voted on to the POST view which handles the request.

So if you were to send the id of the blogpost along with the vote, you could grab that model and update it accordingly.

assuming a model like this

class BlogPost( models.Model ):
  upvotes=models.IntegerField( default=0 )
  downvotes=models.IntegerField( default=0 )

  def get_score( self ):
    return self.upvotes - self.downvotes

  score=property( get_score )

yourtemplate.html

{% for post in object_list %}
 <div class="blogpost" data-pk="{{post.pk}}">
  <span class="score">{{post.score}}</span>
  <a class="upvote" href="#">Upvote</a>
  <a class="downvote" href="#">Downvote</a>
 </div>
{% endfor %}

yourjavascript.js

$('.blogpost').on( 'click', '.upvote,.downvote', function( e ){
  e.preventDefault();
  var id=$( this ).closest('.blogpost').data('pk'),
  upvote=$( this ).is('.upvote'),
  xhr=$.ajax({
    url: location.href, 
    type: 'post',
    data:{ id: id, upvote: upvote ? 1 : 0 },
    dataType: 'json'
  });
  xhr.done( function( data ){
     // successful submission
  });
});

views.py

class AwesomeDisplayListView(JSONResponseMixin,ListView):
  model = BlogPost
  template_name = "awesome_list.html"
  paginate_by = '15'
  context_object_name = "searchres" 

  def post( self, request, *args, **kwargs ):
    id=request.POST.get('id')
    upvote=int( request.POST.get('upvote') )
    blogpost=BlogPost.objects.get( pk=id )
    if upvote:
      blogpost.upvotes=blogpost.upvotes + 1
    else:
      blogpost.downvotes=blogpost.downvotes + 1
    blogpost.save()
    return http.HttpResponse( json.dumps({ 'score': blogpost.score }) )

To expand further on my last comment, a Rating model might make this easier.

class BlogPost( models.Model ):
  title=models.CharField( max_length=100 )
  def get_score( self ):
    return self.ratings.filter( up=True ).count() - self.ratings.filter( up=False ).count()

  score=property( get_score )


class Rating( models.Model ):
  blogpost=models.ForeignKey( BlogPost, related_name="ratings" )
  user=models.ForeignKey( User )
  up=models.BooleanField( default=True )

  class Meta:
    unique_together=('blogpost','user',)

Then in your post view

def post( self, request, *args, **kwargs ):
  id=request.POST.get('id')
  upvote=bool( int( request.POST.get('upvote') ) )
  blogpost=BlogPost.objects.get( pk=id )
  rating, is_new=Rating.objects.get_or_create( user=request.user, blogpost=blogpost )
  if rating.up != upvote:
    rating.up=upvote
    rating.save()

Upvotes: 2

Related Questions