10 February 2008

Online games portal in Django - tutorial, part 5

This time we are going to create, views, url configuration for game portal (templates will be in last part). When doing it we will also fix some of earlier files. Lets begin with extending current models.py file. To shorten code, I'm using ... for common parts.

...
from django.conf import settings
import datetime
...

class Category(models.Model):
...

@models.permalink
def get_absolute_url(self):
return ('gampor.views.category_page', (), { 'slug': self.url_name })

class ActiveSubcategoriesManager(models.Manager):
def get_query_set(self):
return super(ActiveSubcategoriesManager, self).get_query_set().filter(category__sites__exact=settings.SITE_ID)

def get_most_popular(self):
from django.db import connection
cursor = connection.cursor()
cursor.execute("""SELECT gs.subcategory_id AS sid, SUM(g.hit_count) as ghc
FROM gampor_game g JOIN gampor_game_subcategories gs ON g.id=gs.game_id
GROUP BY gs.subcategory_id
ORDER BY ghc DESC
LIMIT 10""")
ids = [int(row[0]) for row in cursor.fetchall()]
return self.filter(pk__in=ids)

class Subcategory(models.Model):
...
objects = models.Manager()
active_subcategories = ActiveSubcategoriesManager()
...
@models.permalink
def get_absolute_url(self):
return ('gampor.views.subcategory_page', (), { 'slug': self.url_name })

class ActiveGamesManager(models.Manager):
def get_query_set(self):
return super(ActiveGamesManager, self).get_query_set().filter(subcategories__category__sites__exact=settings.SITE_ID, active=True, publish_date__lte=datetime.date.today())

class Game(models.Model):
...
objects = models.Manager()
active_games = ActiveGamesManager()
...
@models.permalink
def get_absolute_url(self):
if self.game_type == 'E':
return ('gampor.views.ext_redirect', (self.id,))
return ('gampor.views.game_page', (), { 'slug': self.url_name })

I think you will get the general idea what to change. Additions are about two things. First, get_absolute_url() method with decorator enables to create URLs with respect to DRY principle. Using this code the real absolute URL for every model is contained in only one place: urls.py file.

Second, I added two custom managers to limit the data returned for games and subcategories. Using this filtering I can easily get games only active games for current site and with publish date in the past. I'm also using custom SQL to get most popular subcategories but then return them as standard ORM objects. Notice that I'm not using most efficient method (2 queries). We can shrink it to one if we really want.

Add below line to the settings.py file. It shows how to customize pagination.

PAGINATION = 9

Add below lines to the end of main urls.py file to enable application URLs.

urlpatterns += patterns('',
(r'', include('games4all.gampor.urls')),
)

It means that all URLs not matched earlier will go to our application. Now open urls.py from gampor and insert below code with URLs for game portal. This should be self-explanatory.

from django.conf.urls.defaults import *

urlpatterns = patterns('',
(r'^$', 'gampor.views.home'),
(r'^new/$', 'gampor.views.new_games'),
(r'^popular/$', 'gampor.views.popular_games'),
(r'^search/$', 'gampor.views.search_games'),
(r'^category/(?P[\w-]+)/$', 'gampor.views.category_page'),
(r'^subcategory/(?P[\w-]+)/$', 'gampor.views.subcategory_page'),
(r'^game/(?P[\w-]+)/$', 'gampor.views.game_page'),
(r'^external/(\d+)/$', 'gampor.views.ext_redirect'),
)

Every match will call function mentioned at second position. Functions are in views.py which will be our last Python file needed for game portal (we still must create templates). Open it and insert below code.

from models import Category, Subcategory, Game
from django.contrib.sites.models import Site
from django.conf import settings
from django.shortcuts import render_to_response, get_object_or_404
from django.views.generic.list_detail import object_list
from django.views.generic.simple import redirect_to
from django.views.decorators.cache import cache_page
from django.core.cache import cache

TYPE_TO_TEMPLATE = { 'F': 'flash.html', 'S': 'shockwave.html', 'E': 'external.html' }

@cache_page(600)
def home(request):
data = sidebox_data()
data.update({
'new_games': Game.active_games.order_by('-publish_date')[:8],
'popular_games': Game.active_games.order_by('-hit_count')[:8],
})
return render_to_response('index.html', data)

def gampor_object_list(request, queryset, template_name, extra_context={}, empty=False):
extra_context.update(sidebox_data())
return object_list(
request,
queryset = queryset,
template_name = template_name,
template_object_name = "main",
paginate_by = settings.PAGINATION,
allow_empty = empty,
extra_context = extra_context
)

def new_games(request):
return gampor_object_list(request, Game.active_games.order_by('-publish_date'), "main_list.html", { 'subtitle': "New games" })

def popular_games(request):
return gampor_object_list(request, Game.active_games.order_by('-hit_count'), "main_list.html", { 'subtitle': "Popular games" })

def search_games(request):
searched_name = request.REQUEST.get('s', '')
if searched_name != '':
results = Game.active_games.filter(title__contains=searched_name)
else:
results = Game.objects.none()
return gampor_object_list(request, results, "search_list.html", { 'searched_name': searched_name }, True)

def category_page(request, slug):
category = get_object_or_404(Category, url_name=slug)
return gampor_object_list(request, Subcategory.active_subcategories.filter(category=category), "main_list.html", { 'subtitle': category.title_name })

def subcategory_page(request, slug):
subcategory = get_object_or_404(Subcategory, url_name=slug)
return gampor_object_list(request, Game.active_games.filter(subcategories=subcategory), "main_list.html", { 'subtitle': subcategory.title_name })

def game_page(request, slug):
game = get_object_or_404(Game, url_name=slug)
if game.game_type != 'E':
game.hit_count += 1
game.save()
data = sidebox_data()
data.update({
'game': game,
'type_template': TYPE_TO_TEMPLATE[game.game_type],
})
return render_to_response('game.html', data)

def ext_redirect(request, id):
game = get_object_or_404(Game, pk=id , game_type='E')
game.hit_count += 1
game.save()
return redirect_to(request, url=game.url)

# Helper
def sidebox_data(site_id=settings.SITE_ID):
data = cache.get('sidebox_data_%d' % site_id)
if data == None:
data = {
'MEDIA_URL': settings.MEDIA_URL,
'categories': Category.objects.filter(sites__exact=site_id),
'popular_subcategories': Subcategory.active_subcategories.get_most_popular(),
}
cache.set('sidebox_data_%d' % site_id, data, 1200)
return data

It is really hard to explain everything going on here (but the code is rather simple), so I'm only going to say what I'm using and you should look into details at Django Documentation:

  • caching for home page and sidebox
  • generic list view with own wrapper
  • get_object_or_404 for getting object from db or return 404 code
  • incrementing hits and redirect

The only thing we still need are templates, but this must wait to next post...

0 komentarze:

Post a Comment