29

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?

irl_irl
  • 3,785
  • 9
  • 49
  • 60

4 Answers4

39

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
  • 3,501
  • 5
  • 27
  • 26
  • Thanks for that. Where do you put the jquery code? – irl_irl Oct 07 '09 at 11:27
  • 2
    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 Oct 07 '09 at 23:17
  • tried your code, but i couldn't get the buttons to be clickable! – irl_irl Oct 07 '09 at 23:56
  • 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 Oct 08 '09 at 01:39
  • 6
    Thanks. I love seeing full solutions +1 – DrBloodmoney Oct 08 '09 at 02:10
  • Any reason why you would use s POST request here instead of a GET request? EDIT: @c_harm has the answer in this thread – Paul Feb 21 '20 at 17:22
14

Just plug and play:

RedditStyleVoting
Implementing reddit style voting for any Model with django-voting
http://code.google.com/p/django-voting/wiki/RedditStyleVoting

ramayac
  • 5,173
  • 10
  • 50
  • 58
panchicore
  • 11,451
  • 12
  • 74
  • 100
  • This could be useful. Just looking at it now. Do you know what from devdocs.apps.kb.models import Link should be changed to? – irl_irl Oct 07 '09 at 13:20
  • 2
    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 Oct 07 '09 at 23:20
  • 3
    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 Oct 07 '09 at 23:22
11

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

8

As a link, button or something else?

Something else, what about an image?

When they click it, it performs the method and refreshes the page?

Perhaps you could better use ajax to invoke a method to save the vote, and not refresh anything at all.

This is what comes to my mind.

enter image description here

Undo
  • 25,519
  • 37
  • 106
  • 129
OscarRyz
  • 196,001
  • 113
  • 385
  • 569