views:

1233

answers:

4

I am making a small app that lets users vote items either up or down. I'm using Django (and new to it!).

I am just wondering, what is the best way to present the upvote link to the user. As a link, button or something else?

I have already done something like this in php with a different framework but I'm not sure if I can do it the same way. Should I have a method for up/down vote and then display a link to the user to click. When they click it, it performs the method and refreshes the page?

+2  A: 
OscarRyz
+5  A: 

Whatever you do, make sure that it's submitted by POST and not GET; GET requests should never alter database information.

cpharmston
He's right, and this principle rules out using a simple image link to reload the page.
Matt Miller
+2  A: 

Just plug and play:

RedditStyleVoting  
Implementing reddit style voting for any Model with django-voting
http://code.google.com/p/django-voting/wiki/RedditStyleVoting
panchicore
This could be useful. Just looking at it now. Do you know whatfrom devdocs.apps.kb.models import Linkshould be changed to?
GreenRails
The advantage of this over the code in my answer is progressive enhancement - it will work without Javascript, but you can add AJAX on top to make a better user experience.
Matt Miller
replace devdocs.apps.kb.models with the path to your models.py file where you define Link. It will be something like yourprojectname.yourappname.models.
Matt Miller
+5  A: 

Here's the gist of my solution. I use images with jQuery/AJAX to handle clicks. Strongly influenced by this site. There's some stuff that could use some work (error handling in the client, for example -- and much of it could probably be refactored) but hopefully the code is useful to you.

The HTML:

        <div class="vote-buttons">
        {% ifequal thisUserUpVote 0 %}
        <img class="vote-up" src = "images/vote-up-off.png" title="Vote this thread UP. (click again to undo)" />
        {% else %}
        <img class="vote-up selected" src = "images/vote-up-on.png" title="Vote this thread UP. (click again to undo)" />
        {% endifequal %}
        {% ifequal thisUserDownVote 0 %}
        <img class="vote-down" src = "images/vote-down-off.png" title="Vote this thread DOWN if it is innapropriate or incorrect. (click again to undo)" />
        {% else %}
        <img class="vote-down selected" src = "images/vote-down-on.png" title="Vote this thread DOWN if it is innapropriate or incorrect. (click again to undo)" />
        {% endifequal %}
        </div> <!-- .votebuttons -->

The jQuery:

$(document).ready(function() {

    $('div.vote-buttons img.vote-up').click(function() {

        var id = {{ thread.id }};
        var vote_type = 'up';

        if ($(this).hasClass('selected')) {
            var vote_action = 'recall-vote'
            $.post('/ajax/thread/vote', {id:id, type:vote_type, action:vote_action}, function(response) {
                if (isInt(response)) {
                    $('img.vote-up').removeAttr('src')
                        .attr('src', 'images/vote-up-off.png')
                        .removeClass('selected');
                    $('div.vote-tally span.num').html(response);
                }
            });
        } else {

            var vote_action = 'vote'
            $.post('/ajax/thread/vote', {id:id, type:vote_type, action:vote_action}, function(response) {
                if (isInt(response)) {
                    $('img.vote-up').removeAttr('src')
                        .attr('src', 'images/vote-up-on.png')
                        .addClass('selected');
                    $('div.vote-tally span.num').html(response);
                }
            });
        }
    });

The Django view that handles the AJAX request:

def vote(request):
   thread_id = int(request.POST.get('id'))
   vote_type = request.POST.get('type')
   vote_action = request.POST.get('action')

   thread = get_object_or_404(Thread, pk=thread_id)

   thisUserUpVote = thread.userUpVotes.filter(id = request.user.id).count()
   thisUserDownVote = thread.userDownVotes.filter(id = request.user.id).count()

   if (vote_action == 'vote'):
      if (thisUserUpVote == 0) and (thisUserDownVote == 0):
         if (vote_type == 'up'):
            thread.userUpVotes.add(request.user)
         elif (vote_type == 'down'):
            thread.userDownVotes.add(request.user)
         else:
            return HttpResponse('error-unknown vote type')
      else:
         return HttpResponse('error - already voted', thisUserUpVote, thisUserDownVote)
   elif (vote_action == 'recall-vote'):
      if (vote_type == 'up') and (thisUserUpVote == 1):
         thread.userUpVotes.remove(request.user)
      elif (vote_type == 'down') and (thisUserDownVote ==1):
         thread.userDownVotes.remove(request.user)
      else:
         return HttpResponse('error - unknown vote type or no vote to recall')
   else:
      return HttpResponse('error - bad action')


   num_votes = thread.userUpVotes.count() - thread.userDownVotes.count()

   return HttpResponse(num_votes)

And the relevant parts of the Thread model:

class Thread(models.Model):
    # ...
    userUpVotes = models.ManyToManyField(User, blank=True, related_name='threadUpVotes')
    userDownVotes = models.ManyToManyField(User, blank=True, related_name='threadDownVotes')
Matt Miller
Thanks for that. Where do you put the jquery code?
GreenRails
You include the jquery.js file with a script tag in the header, then you can stick this inside script tags anywhere in the page. I usually end up putting it in the same django template include file as the HTML for that part of the page so they stay together.jquery.com is a great resource if you're going to start doing AJAX-y stuff.
Matt Miller
tried your code, but i couldn't get the buttons to be clickable!
GreenRails
Are you loading the jQuery library before the script tag that containts the javascript above? The javascript is responsible for setting the click handlers on HTML elements that match the CSS selector in the third line ('div.vote-buttons img.vote-up'.) You might try putting an alert('Did this execute?'); in the second line of the javascript to make sure the code is executing after the document is loaded.
Matt Miller
Thanks. I love seeing full solutions +1
DrBloodmoney