Package: django-classfaves
Overview of django-classfaves-----------------------------This app provides everything that you need to add favoriting abilities toyour site!  There are just a few steps that you need to go through to addthis functionality to your site.Installation at a Glance========================1.  Create a favorite model that has a ``ForeignKey`` to the domain objectthat you would like to be able to favorite or unfavorite.2.  Instantiate ``CreateFavorite``, ``DeleteFavorite``, and ``UserFavorites``in one of your url configurations, and map them into your urlpatterns.3.  Integrate into your domain object's templates and views.Installation Example: Arcade Site=================================This installation example comes from `Radiosox`_, for which this applicationwas originally written.  The site is a free online arcade site that allowsusers to mark certain games as their favorite ones.  They are allowed toun-favorite those games if they change their mind.  Finally, they are allowedto see a list of their favorite games.Step 1: Creating our Model~~~~~~~~~~~~~~~~~~~~~~~~~~We already have a domain object that looks a little bit like this:.. sourcecode:: pythonfrom django.db import modelsclass Game(models.Model):name = models.CharField(max_length=64)slug = models.SlugField(max_length=64)description = models.TextField(blank=True)# ... there are many more fields hereNow what we want to do is in that same models.py file, we will create ourfavorite model, like shown here:.. sourcecode:: pythonfrom classfaves.models import FavoriteBaseclass GameFavorite(FavoriteBase):game = models.ForeignKey(Game)What this does is use our ``FavoriteBase`` abstract base class, provide bydjango-classfaves, and ensure that it's got a link to our ``Game`` domainobject.  This model is equivalent to manually writing this:.. sourcecode:: pythonimport datetimefrom django.db import modelsfrom django.contrib.auth.models import Userclass GameFavorite(models.Model):game = models.ForeignKey(Game)user = models.ForeignKey(User)date_created = models.DateTimeField(default=datetime.datetime.now)In fact, you could write that model if you prefer.  I prefer to subclass``FavoriteBase``, since it's easier.Step 2: Attaching our Views~~~~~~~~~~~~~~~~~~~~~~~~~~~In our ``urls.py`` file for that arcade app whose models we were using in theprevious step, we have something that, right now, looks like this:.. sourcecode:: pythonfrom django.conf.urls.defaults import patterns, urlurlpatterns = patterns('arcade.views',url(r'^popular/$', 'popular', name='arcade_popular'),url(r'^new/$', 'new', name='arcade_new'),# ... there are more URLs here)What we want to do first is instantiate our views provided by thedjango-classfaves app:.. sourcecode:: pythonfrom arcade.models import GameFavorite, Gamefrom classfaves.views import CreateFavorite, DeleteFavorite, UserFavoritescreate_favorite = CreateFavorite(GameFavorite, Game)delete_favorite = DeleteFavorite(GameFavorite, Game)public_games = lambda qs: qs.filter(game__enabled=True)user_favorites = UserFavorites(GameFavorite, Game, extra_filter=public_games)What this does is give us views to create, delete, and get user favorites forall games in the system.  You'll note that we're passing an argument named``extra_filter`` to ``UserFavorites`` which limits the view to only showinggames with ``enabled`` set to ``True``.  This can be used to limit the``QuerySet`` arbitrarily.  Kinda cool, huh?Anyway, the next step is to modify our ``urlpatterns`` so that it maps to theseviews:..sourcecode:: pythonurlpatterns = patterns('arcade.views',url(r'^popular/$', 'popular', name='arcade_popular'),url(r'^new/$', 'new', name='arcade_new'),# ... vvv THE NEW URLS ARE BELOW vvvurl(r'^favorites/create/(?P<pk>\d+)/$', create_favorite, name='arcade_favorite_create'),url(r'^favorites/delete/(?P<pk>\d+)/$', delete_favorite, name='arcade_favorite_delete'),url(r'^favorites/list/$', user_favorites, name='arcade_my_favorites'),url(r'^favorites/list/(?P<username>[a-zA-Z0-9_-]+)/$', user_favorites, name='arcade_user_favorites'),)Note how we were able to give the new views proper URL names, and how we wereable to easily place them where they logically fit in the URL structure--underthe URL space of the arcade app.  Also note that we've ensured to have a ``pk``for the create and delete views.Step 3: Integration~~~~~~~~~~~~~~~~~~~Admittedly, this part is the part where django-classfaves helps you out theleast.  Well, basically, django-classfaves doesn't help you out at all.  Thereason for this is by design: we don't know how your app is structured or howyou want to use it, so we want to leave this bit completely up to you.So here's how we did it.  First, on the page where you actually play the game,we want you to be able to decide that you like it and favorite it.  We alsowanted to make sure that you could do this while you are still playing thegame, so it needed to be asynchronous using JavaScript.Our first step was to modify the view function for the game playing page...sourcecode:: pythonfrom arcade.models import GameFavoritedef play(request, game_slug=None):# ... some of our view code herefavorite = Falseif request.user.is_authenticated():favorite = GameFavorite.objects.filter(user=request.user,game=game).count() > 0context = {# ... other context here'favorite': favorite,}# ... the rest of our view code hereJust a few lines of code, and we now know whether the user has a favorite onthat specific domain object (the game) or not.Now, in the template, we do this with the information:..sourcecode:: python{% if user.is_authenticated %}{% if favorite %}<a href="#" id="favorite_{{ game.id }}" class="favorite fav unfave">Remove as Favorite</a>{% else %}<a href="#" id="favorite_{{ game.id }}" class="favorite fav fave">Add as Favorite</a>{% endif %}{% else %}<a href="{% url arcade_favorite_create game.id %}" class="fav" id="favorite_{{ game.id }}" class="favorite">Add as Favorite</a>{% endif %}In other words, based on whether the user is authenticated and based on whetheror not they have already favorited the game, we set some classes and urls andmessages on the links.We also have a bit of JavaScript, that looks like this:..sourcecode:: javascriptvar add_favorite_handlers = function(base_create, base_delete) {$('a.favorite.fave').live('click', function(e) {var pk = $(this).attr('id').replace('favorite_', '');var url = base_create + pk + '/';$.getJSON(url, function(data, textStatus) {$('#favorite_' + pk).removeClass('fave').addClass('unfave').text('Remove as Favorite');});return false;});$('a.favorite.unfave').live('click', function(e) {var pk = $(this).attr('id').replace('favorite_', '');var url = base_delete + pk + '/';$.getJSON(url, function(data, textStatus) {$('#favorite_' + pk).removeClass('unfave').addClass('fave').text('Add as Favorite');});return false;});};I'm not going to go into too much detail about this JavaScript code, except tosay that it takes the base URL for the create and the delete pages, and turnslinks with certain classes into AJAX calls into the create and delete views.Finally, at the bottom of the page, we initialize this JavaScript like so:.. sourcecode:: html<script type="text/javascript">$(function() {add_favorite_handlers('/games/favorites/create/', '/games/favorites/delete/');});</script>And with that, we're done!  Not too bad, huh?  You can browse around `Radisox`and play a game to see it in action.  Look up at the top right of any gamepage to see the favorite/un-favorite button... _`Radiosox`: http://radiosox.com/