IGN:Initial import of blog code and improve colors of new website

This commit is contained in:
Kovid Goyal 2009-03-28 02:40:51 -07:00
parent ee17107ff8
commit 9f30d9d090
52 changed files with 2622 additions and 2 deletions

View File

@ -0,0 +1,85 @@
changes:
date: 2008-09-17
change: Enabled the ability to override the default template names.
date: 2008-08-26
change: Upgraded post_detail.html to now use new Django refactored comments. Sidenote: basic.remarks have been removed.
date: 2008-07-14
change: Removed get_query_set from Blog manager to fix a problem where saving a post marked as Draft would not save.
change: Added a get_previous_post and get_next_post method for front end template. These will not return Draft posts.
date: 2008-06-17
change: BlogPostFeed is now BlogPostsFeed and there is a new BlogPostsByCategory.
date: 2008-05-18
change: Converted everything to 4 space tabs and made a few other changes to comply with Python Style Guide.
date: 2008-04-23
change: Added an inline admin interface helper for choosing inlines to go into posts.
change: The inline app is now a dependancy of the blog.
date: 2008-04-22
change: Removed the 'render_inlines' filter from the Blog template tags. The tag is now in an app called inlines which can be used with any django app.
date: 2008-02-27
change: Added 'allow_comments' field to the Post model.
change: Removed 'Closed' choice from status field of Post model
date: 2008-02-18
fix: Fixed feed pointing to hardcoded url.
date: 2008-02-15
change: Internationalized models
date: 2008-02-04
change: Added 'get_links' template filter.
change: Templates: added a {% block content_title %}
date: 2008-02-02
change: Added a sitemap
date: 2008-01-30
change: Renamed 'do_inlines' filter to 'render_inlines'
date: 2008-01-29
change: BeautifulSoup is no longer a dependancy unless you want to use the do_inlines filter.
date: 2008-01-27
fix: removed 'tagging.register(Post)' from model. It was causing too many unnecessary SQL JOINS.
change: Changed the inlines tag to a filter. (Example: {{ object.text|do_inlines }})
date: 2008-01-22
change: Registered the Post model with the tagging app
date: 2008-01-19
change: Renamed the 'list' class to 'link_list'
date: 2008-01-09
change: Changed urls.py so you can have /posts/page/2/ or /posts/?page=2
date: 2008-01-07
change: Removed PublicPostManager in favor of ManagerWithPublished.
change: Made wrappers for generic views.
date: 2008-01-06
fix: In blog.py changed 'beautifulsoup' to 'BeautifulSoup'
date: 2007-12-31
change: Changed some syntax in managers.py to hopefully fix a bug.
change: Removed an inline template that didn't belong.
date: 2007-12-21
change: Added markup tag that formats inlines.
date: 2007-12-12
change: Cleaned up unit tests.
date: 2007-12-11
change: Add documentation to templatetags and views.
change: Smartened up the previous/next blog part of the post_detail.html template.
date: 2007-12-09
change: Added feed templates and wrapped up feeds.py.
change: Changed Post.live manager to Post.public
change: Added a search view along with templates

View File

@ -0,0 +1,18 @@
===========================================
Django Basic Blog
http://code.google.com/p/django-basic-apps/
===========================================
A simple blog application for Django projects.
To install this app, simply create a folder somewhere in
your PYTHONPATH named 'basic' and place the 'blog'
app inside. Then add 'basic.blog' to your projects
INSTALLED_APPS list in your settings.py file.
=== Dependancies ===
* Basic Inlines
* [http://www.djangoproject.com/documentation/add_ons/#comments Django Comments]
* [http://code.google.com/p/django-tagging Django Tagging]
* [http://www.djangoproject.com/documentation/add_ons/#markup Markup]
* [http://www.crummy.com/software/BeautifulSoup/ BeautifulSoup] - only if you want to use the [http://code.google.com/p/django-basic-blog/wiki/BlogInlinesProposal render_inlines] filter, otherwise it's not necessary.

View File

View File

@ -0,0 +1,17 @@
from django.contrib import admin
from calibre.www.apps.blog.models import *
class CategoryAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('title',)}
admin.site.register(Category, CategoryAdmin)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'publish', 'status')
list_filter = ('publish', 'categories', 'status')
search_fields = ('title', 'body')
prepopulated_fields = {'slug': ('title',)}
admin.site.register(Post, PostAdmin)

View File

@ -0,0 +1,42 @@
from django.contrib.syndication.feeds import FeedDoesNotExist
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.sites.models import Site
from django.contrib.syndication.feeds import Feed
from django.core.urlresolvers import reverse
from calibre.www.apps.blog.models import Post, Category
class BlogPostsFeed(Feed):
_site = Site.objects.get_current()
title = '%s feed' % _site.name
description = '%s posts feed.' % _site.name
def link(self):
return reverse('blog_index')
def items(self):
return Post.objects.published()[:10]
def item_pubdate(self, obj):
return obj.publish
class BlogPostsByCategory(Feed):
_site = Site.objects.get_current()
title = '%s posts category feed' % _site.name
def get_object(self, bits):
if len(bits) != 1:
raise ObjectDoesNotExist
return Category.objects.get(slug__exact=bits[0])
def link(self, obj):
if not obj:
raise FeedDoesNotExist
return obj.get_absolute_url()
def description(self, obj):
return "Posts recently categorized as %s" % obj.title
def items(self, obj):
return obj.post_set.published()[:10]

View File

@ -0,0 +1,9 @@
from django.db.models import Manager
import datetime
class PublicManager(Manager):
"""Returns published posts that are not in the future."""
def published(self):
return self.get_query_set().filter(status__gte=2, publish__lte=datetime.datetime.now())

View File

@ -0,0 +1,80 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.db.models import permalink
from django.contrib.auth.models import User
from calibre.www.apps.tagging.fields import TagField
from calibre.www.apps.blog.managers import PublicManager
import calibre.www.apps.tagging as tagging
class Category(models.Model):
"""Category model."""
title = models.CharField(_('title'), max_length=100)
slug = models.SlugField(_('slug'), unique=True)
class Meta:
verbose_name = _('category')
verbose_name_plural = _('categories')
db_table = 'blog_categories'
ordering = ('title',)
class Admin:
pass
def __unicode__(self):
return u'%s' % self.title
@permalink
def get_absolute_url(self):
return ('blog_category_detail', None, {'slug': self.slug})
class Post(models.Model):
"""Post model."""
STATUS_CHOICES = (
(1, _('Draft')),
(2, _('Public')),
)
title = models.CharField(_('title'), max_length=200)
slug = models.SlugField(_('slug'), unique_for_date='publish')
author = models.ForeignKey(User, blank=True, null=True)
body = models.TextField(_('body'))
tease = models.TextField(_('tease'), blank=True)
status = models.IntegerField(_('status'), choices=STATUS_CHOICES, default=2)
allow_comments = models.BooleanField(_('allow comments'), default=True)
publish = models.DateTimeField(_('publish'))
created = models.DateTimeField(_('created'), auto_now_add=True)
modified = models.DateTimeField(_('modified'), auto_now=True)
categories = models.ManyToManyField(Category, blank=True)
tags = TagField()
objects = PublicManager()
class Meta:
verbose_name = _('post')
verbose_name_plural = _('posts')
db_table = 'blog_posts'
ordering = ('-publish',)
get_latest_by = 'publish'
class Admin:
list_display = ('title', 'publish', 'status')
list_filter = ('publish', 'categories', 'status')
search_fields = ('title', 'body')
def __unicode__(self):
return u'%s' % self.title
@permalink
def get_absolute_url(self):
return ('blog_detail', None, {
'year': self.publish.year,
'month': self.publish.strftime('%b').lower(),
'day': self.publish.day,
'slug': self.slug
})
def get_previous_post(self):
return self.get_previous_by_publish(status__gte=2)
def get_next_post(self):
return self.get_next_by_publish(status__gte=2)

View File

@ -0,0 +1,13 @@
from django.contrib.sitemaps import Sitemap
from calibre.www.apps.blog.models import Post
class BlogSitemap(Sitemap):
changefreq = "never"
priority = 0.5
def items(self):
return Post.objects.published()
def lastmod(self, obj):
return obj.publish

View File

@ -0,0 +1,56 @@
{% extends "admin/change_form.html" %}
{% block extrahead %}
{% load adminmedia inlines %}
{{ block.super }}
<script type="text/javascript">
function InlineInit() {
var body_div = document.getElementById('id_body').parentNode;
var content = ''
content += '{% get_inline_types as inline_list %}'
content += '<label>Body inlines:</label>'
content += '<strong>Inline type:</strong> '
content += '<select id="id_inline_content_type" onchange="document.getElementById(\'lookup_id_inline\').href = \'../../../\'+this.value+\'/\';" style="margin-right:20px;">'
content += ' <option>----------</option>'
content += ' {% for inline in inline_list %}'
content += ' <option value="{{ inline.content_type.app_label }}/{{ inline.content_type.model }}">{{ inline.content_type.app_label|capfirst }}: {{ inline.content_type.model|capfirst }}</option>'
content += ' {% endfor %}'
content += '</select> '
content += '<strong>Object:</strong> '
content += '<input type="text" class="vIntegerField" id="id_inline" size="10" /> '
content += '<a id="lookup_id_inline" href="#" class="related-lookup" onclick="if(document.getElementById(\'id_inline_content_type\').value != \'----------\') { return showRelatedObjectLookupPopup(this); }" style="margin-right:20px;"><img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Loopup" /></a> '
content += '<strong>Class:</strong> '
content += '<select id="id_inline_class">'
content += ' <option value="small_left">Small left</option>'
content += ' <option value="small_right">Small right</option>'
content += ' <option value="medium_left">Medium left</option>'
content += ' <option value="medium_right">Medium right</option>'
content += ' <option value="large_left">Large left</option>'
content += ' <option value="large_right">Large right</option>'
content += ' <option value="full">Full</option>'
content += '</select>'
content += '<input type="button" value="Add" style="margin-left:10px;" onclick="return insertInline(document.getElementById(\'id_inline_content_type\').value, document.getElementById(\'id_inline\').value, document.getElementById(\'id_inline_class\').value)" />'
content += '<p class="help">Insert inlines into your body by choosing an inline type, then an object, then a class.</p>'
var div = document.createElement('div');
div.setAttribute('style', 'margin-top:10px;');
div.innerHTML = content;
body_div.insertBefore(div);
}
function insertInline(type, id, classname) {
if (type != '----------' && id != '') {
inline = '<inline type="'+type.replace('/', '.')+'" id="'+id+'" class="'+classname+'" />';
body = document.getElementById('id_body');
body.value = body.value + inline + '\n';
}
}
addEvent(window, 'load', InlineInit);
</script>
{% endblock %}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{% block title %}{% endblock %}</title>
</head>
<body id="{% block body_id %}{% endblock %}">
<div id="body">
{% block body %}
<div>
{% block content_title %}{% endblock %}
</div>
<div class="content">
{% block content %}{% endblock %}
</div>
{% endblock %}
</div>
</body>
</html>

View File

@ -0,0 +1,4 @@
{% extends "base.html" %}
{% block body_class %}blog{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends "blog/base_blog.html" %}
{% block title %}Posts for {{ category.title }}{% endblock %}
{% block body_class %}{{ block.super }} category_detail{% endblock %}
{% block body_id %}category_{{ category.id }}{% endblock %}
{% block content_title %}
<h2>Posts for {{ category.title }}</h2>
{% endblock %}
{% block content %}
{% load markup %}
<div class="post_list">
{% for post in object_list %}
<div>
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
<p class="date">{{ post.publish|date:"Y F d" }}</p>
<p class="tease">{{ post.tease }}</p>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends "blog/base_blog.html" %}
{% block title %}Post categories{% endblock %}
{% block body_class %}{{ block.super }} category_list{% endblock %}
{% block content_title %}
<h2>Post categories</h2>
{% endblock %}
{% block content %}
{% load markup %}
<ul class="link_list">
{% for category in object_list %}
<li><a href="{{ category.get_absolute_url }}">{{ category }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "blog/base_blog.html" %}
{% block title %}Post archive for {{ day|date:"d F Y" }}{% endblock %}
{% block body_class %}{{ block.super }} post_archive_day{% endblock %}
{% block content_title %}
<h2>Post archive for {{ day|date:"d F Y" }}</h2>
{% endblock %}
{% block content %}
<div class="post_list">
{% for post in object_list %}
<div>
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
<p class="date">{{ post.publish|date:"Y F d" }}</p>
<p class="tease">{{ post.tease }}</p>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "blog/base_blog.html" %}
{% block title %}Post archive for {{ month|date:"F Y" }}{% endblock %}
{% block body_class %}{{ block.super }} post_archive_month{% endblock %}
{% block content_title %}
<h2>Post archive for {{ month|date:"F Y" }}</h2>
{% endblock %}
{% block content %}
<div class="post_list">
{% for post in object_list %}
<div>
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
<p class="date">{{ post.publish|date:"Y F d" }}</p>
<p class="tease">{{ post.tease }}</p>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "blog/base_blog.html" %}
{% block title %}Post archive for {{ year }}{% endblock %}
{% block body_class %}{{ block.super }} post_archive_year{% endblock %}
{% block content_title %}
<h2>Post archive for {{ year }}</h2>
{% endblock %}
{% block content %}
{% load markup %}
<ul class="link_list">
{% for month in date_list %}
<li><a href="{% url blog_index %}{{ year }}/{{ month|date:"b" }}/">{{ month|date:"F" }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,67 @@
{% extends "blog/base_blog.html" %}
{% block title %}{{ object.title }}{% endblock %}
{% block body_class %}{{ block.super }} post_detail{% endblock %}
{% block body_id %}post_{{ object.id }}{% endblock %}
{% block content_title %}
<h2>{{ object.title }}</h2>
<p class="other_posts">
{% if object.get_previous_by_publish %}
<a class="previous" href="{{ object.get_previous_post.get_absolute_url }}">&laquo; {{ object.get_previous_post }}</a>
{% endif %}
{% if object.get_next_by_publish %}
| <a class="next" href="{{ object.get_next_post.get_absolute_url }}">{{ object.get_next_post }} &raquo;</a>
{% endif %}
</p>
{% endblock %}
{% block content %}
{% load blog markup comments tagging_tags %}
<p class="date">{{ object.publish|date:"j F Y" }}</p>
<div class="body">
{{ object.body|markdown:"safe" }}
</div>
{% tags_for_object object as tag_list %}
{% if tag_list %}
<p class="inline_tag_list"><strong>Related tags:</strong>
{% for tag in tag_list %}
{{ tag }}{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
{% endif %}
{% get_comment_list for object as comment_list %}
{% if comment_list %}
<div id="comments">
<a name="comments"></a>
<h3 class="comments_title">Comments</h3>
{% for comment in comment_list %}
{% if comment.is_public %}
<div class="comment">
<h5 class="name">
<a name="c{{ comment.id }}" href="{{ comment.get_absolute_url }}" title="Permalink to {{ comment.person_name }}'s comment" class="count">{{ forloop.counter }}</a>
{% if comment.user_url %}<a href="{{ comment.user_url }}">{{ comment.user_name }}</a>{% else %}{{ comment.user_name }}{% endif %} says...
</h5>
{{ comment.comment|urlizetrunc:"60"|markdown:"safe" }}
<p class="date">Posted at {{ comment.submit_date|date:"P" }} on {{ comment.submit_date|date:"F j, Y" }}</p>
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% if object.allow_comments %}
{% render_comment_form for object %}
{% else %}
<div id="comment_form">
<h3>Comments are closed.</h3>
<p>Comments have been close for this post.</p>
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,35 @@
{% extends "blog/base_blog.html" %}
{% block title %}Post archive{% endblock %}
{% block body_class %}{{ block.super }} post_list{% endblock %}
{% block content_title %}
<h2>Post archive</h2>
{% endblock %}
{% block content %}
<div class="post_list">
{% for post in object_list %}
<div>
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
<p class="date">{{ post.publish|date:"Y F d" }}</p>
<p class="tease">{{ post.tease }}</p>
</div>
{% endfor %}
</div>
{% if is_paginated %}
<p class="pagination">
{% if has_next %}
<a class="older" href="?page={{ next }}">Older</a>
{% endif %}
{% if has_next and has_previous %} | {% endif %}
{% if has_previous %}
<a class="newer" href="?page={{ previous }}">Newer</a>
{% endif %}
</p>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,37 @@
{% extends "blog/base_blog.html" %}
{% block title %}Post search{% endblock %}
{% block body_class %}{{ block.super }} post_search{% endblock %}
{% block content_title %}
<h2>Search</h2>
{% endblock %}
{% block content %}
<form action="." method="get" id="post_search_form">
<p>
<input type="text" name="q" value="{{ search_term }}" id="search">
<input type="submit" class="button" value="Search">
</p>
</form>
{% if message %}
<p class="message">{{ message }}</p>
{% endif %}
{% if object_list %}
<div class="post_list">
{% for post in object_list %}
<div>
<h3 class="title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h3>
<p class="date">{{ post.publish|date:"Y F d" }}</p>
<p class="tease">{{ post.tease }}</p>
<p class="comments">{% if comment_count %}{{ comment_count }} comment{{ comment_count|pluralize }}{% endif %}</p>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1 @@
{{ obj.tease }}

View File

@ -0,0 +1 @@
{{ obj.title }}

View File

@ -0,0 +1,7 @@
{% if object %}
{{ object }}
{% else %}
{% for object in object_list %}
{{ object }}
{% endfor %}
{% endif %}

View File

@ -0,0 +1,103 @@
from django import template
from django.conf import settings
from django.db import models
import re
Post = models.get_model('blog', 'post')
Category = models.get_model('blog', 'category')
register = template.Library()
class LatestPosts(template.Node):
def __init__(self, limit, var_name):
self.limit = limit
self.var_name = var_name
def render(self, context):
posts = Post.objects.published()[:int(self.limit)]
if posts and (int(self.limit) == 1):
context[self.var_name] = posts[0]
else:
context[self.var_name] = posts
return ''
@register.tag
def get_latest_posts(parser, token):
"""
Gets any number of latest posts and stores them in a varable.
Syntax::
{% get_latest_posts [limit] as [var_name] %}
Example usage::
{% get_latest_posts 10 as latest_post_list %}
"""
try:
tag_name, arg = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0]
m = re.search(r'(.*?) as (\w+)', arg)
if not m:
raise template.TemplateSyntaxError, "%s tag had invalid arguments" % tag_name
format_string, var_name = m.groups()
return LatestPosts(format_string, var_name)
class BlogCategories(template.Node):
def __init__(self, var_name):
self.var_name = var_name
def render(self, context):
categories = Category.objects.all()
context[self.var_name] = categories
return ''
@register.tag
def get_blog_categories(parser, token):
"""
Gets all blog categories.
Syntax::
{% get_blog_categories as [var_name] %}
Example usage::
{% get_blog_categories as category_list %}
"""
try:
tag_name, arg = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0]
m = re.search(r'as (\w+)', arg)
if not m:
raise template.TemplateSyntaxError, "%s tag had invalid arguments" % tag_name
var_name = m.groups()[0]
return BlogCategories(var_name)
@register.filter
def get_links(value):
"""
Extracts links from a ``Post`` body and returns a list.
Template Syntax::
{{ post.body|markdown:"safe"|get_links }}
"""
try:
try:
from BeautifulSoup import BeautifulSoup
except ImportError:
from beautifulsoup import BeautifulSoup
soup = BeautifulSoup(value)
return soup.findAll('a')
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError, "Error in 'get_links' filter: BeautifulSoup isn't installed."
return value

View File

@ -0,0 +1,66 @@
"""
>>> from django.test import Client
>>> from calibre.www.apps.blog.models import Post, Category
>>> import datetime
>>> from django.core.urlresolvers import reverse
>>> client = Client()
>>> category = Category(title='Django', slug='django')
>>> category.save()
>>> category2 = Category(title='Rails', slug='rails')
>>> category2.save()
>>> post = Post(title='DJ Ango', slug='dj-ango', body='Yo DJ! Turn that music up!', status=2, publish=datetime.datetime(2008,5,5,16,20))
>>> post.save()
>>> post2 = Post(title='Where my grails at?', slug='where', body='I Can haz Holy plez?', status=2, publish=datetime.datetime(2008,4,2,11,11))
>>> post2.save()
>>> post.categories.add(category)
>>> post2.categories.add(category2)
>>> response = client.get(reverse('blog_index'))
>>> response.context[-1]['object_list']
[<Post: DJ Ango>, <Post: Where my grails at?>]
>>> response.status_code
200
>>> response = client.get(reverse('blog_category_list'))
>>> response.context[-1]['object_list']
[<Category: Django>, <Category: Rails>]
>>> response.status_code
200
>>> response = client.get(category.get_absolute_url())
>>> response.context[-1]['object_list']
[<Post: DJ Ango>]
>>> response.status_code
200
>>> response = client.get(post.get_absolute_url())
>>> response.context[-1]['object']
<Post: DJ Ango>
>>> response.status_code
200
>>> response = client.get(reverse('blog_search'), {'q': 'DJ'})
>>> response.context[-1]['object_list']
[<Post: DJ Ango>]
>>> response.status_code
200
>>> response = client.get(reverse('blog_search'), {'q': 'Holy'})
>>> response.context[-1]['object_list']
[<Post: Where my grails at?>]
>>> response.status_code
200
>>> response = client.get(reverse('blog_search'), {'q': ''})
>>> response.context[-1]['message']
'Search term was too vague. Please try again.'
>>> response = client.get(reverse('blog_detail', args=[2008, 'apr', 2, 'where']))
>>> response.context[-1]['object']
<Post: Where my grails at?>
>>> response.status_code
200
"""

View File

@ -0,0 +1,41 @@
from django.conf.urls.defaults import *
from calibre.www.apps.blog import views as blog_views
urlpatterns = patterns('',
url(r'^(?P<year>\d{4})/(?P<month>\w{3})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$',
view=blog_views.post_detail,
name='blog_detail'),
url(r'^(?P<year>\d{4})/(?P<month>\w{3})/(?P<day>\d{1,2})/$',
view=blog_views.post_archive_day,
name='blog_archive_day'),
url(r'^(?P<year>\d{4})/(?P<month>\w{3})/$',
view=blog_views.post_archive_month,
name='blog_archive_month'),
url(r'^(?P<year>\d{4})/$',
view=blog_views.post_archive_year,
name='blog_archive_year'),
url(r'^categories/(?P<slug>[-\w]+)/$',
view=blog_views.category_detail,
name='blog_category_detail'),
url (r'^categories/$',
view=blog_views.category_list,
name='blog_category_list'),
url (r'^search/$',
view=blog_views.search,
name='blog_search'),
url(r'^page/(?P<page>\w)/$',
view=blog_views.post_list,
name='blog_index_paginated'),
url(r'^$',
view=blog_views.post_list,
name='blog_index'),
)

View File

@ -0,0 +1,160 @@
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.http import Http404
from django.views.generic import date_based, list_detail
from django.db.models import Q
from calibre.www.apps.blog.models import *
import datetime
import re
def post_list(request, page=0, **kwargs):
return list_detail.object_list(
request,
queryset = Post.objects.published(),
paginate_by = 20,
page = page,
**kwargs
)
post_list.__doc__ = list_detail.object_list.__doc__
def post_archive_year(request, year, **kwargs):
return date_based.archive_year(
request,
year = year,
date_field = 'publish',
queryset = Post.objects.published(),
make_object_list = True,
**kwargs
)
post_archive_year.__doc__ = date_based.archive_year.__doc__
def post_archive_month(request, year, month, **kwargs):
return date_based.archive_month(
request,
year = year,
month = month,
date_field = 'publish',
queryset = Post.objects.published(),
**kwargs
)
post_archive_month.__doc__ = date_based.archive_month.__doc__
def post_archive_day(request, year, month, day, **kwargs):
return date_based.archive_day(
request,
year = year,
month = month,
day = day,
date_field = 'publish',
queryset = Post.objects.published(),
**kwargs
)
post_archive_day.__doc__ = date_based.archive_day.__doc__
def post_detail(request, slug, year, month, day, **kwargs):
return date_based.object_detail(
request,
year = year,
month = month,
day = day,
date_field = 'publish',
slug = slug,
queryset = Post.objects.published(),
**kwargs
)
post_detail.__doc__ = date_based.object_detail.__doc__
def category_list(request, template_name = 'blog/category_list.html', **kwargs):
"""
Category list
Template: ``blog/category_list.html``
Context:
object_list
List of categories.
"""
return list_detail.object_list(
request,
queryset = Category.objects.all(),
template_name = template_name,
**kwargs
)
def category_detail(request, slug, template_name = 'blog/category_detail.html', **kwargs):
"""
Category detail
Template: ``blog/category_detail.html``
Context:
object_list
List of posts specific to the given category.
category
Given category.
"""
category = get_object_or_404(Category, slug__iexact=slug)
return list_detail.object_list(
request,
queryset = category.post_set.published(),
extra_context = {'category': category},
template_name = template_name,
**kwargs
)
# Stop Words courtesy of http://www.dcs.gla.ac.uk/idom/ir_resources/linguistic_utils/stop_words
STOP_WORDS = r"""\b(a|about|above|across|after|afterwards|again|against|all|almost|alone|along|already|also|
although|always|am|among|amongst|amoungst|amount|an|and|another|any|anyhow|anyone|anything|anyway|anywhere|are|
around|as|at|back|be|became|because|become|becomes|becoming|been|before|beforehand|behind|being|below|beside|
besides|between|beyond|bill|both|bottom|but|by|call|can|cannot|cant|co|computer|con|could|couldnt|cry|de|describe|
detail|do|done|down|due|during|each|eg|eight|either|eleven|else|elsewhere|empty|enough|etc|even|ever|every|everyone|
everything|everywhere|except|few|fifteen|fify|fill|find|fire|first|five|for|former|formerly|forty|found|four|from|
front|full|further|get|give|go|had|has|hasnt|have|he|hence|her|here|hereafter|hereby|herein|hereupon|hers|herself|
him|himself|his|how|however|hundred|i|ie|if|in|inc|indeed|interest|into|is|it|its|itself|keep|last|latter|latterly|
least|less|ltd|made|many|may|me|meanwhile|might|mill|mine|more|moreover|most|mostly|move|much|must|my|myself|name|
namely|neither|never|nevertheless|next|nine|no|nobody|none|noone|nor|not|nothing|now|nowhere|of|off|often|on|once|
one|only|onto|or|other|others|otherwise|our|ours|ourselves|out|over|own|part|per|perhaps|please|put|rather|re|same|
see|seem|seemed|seeming|seems|serious|several|she|should|show|side|since|sincere|six|sixty|so|some|somehow|someone|
something|sometime|sometimes|somewhere|still|such|system|take|ten|than|that|the|their|them|themselves|then|thence|
there|thereafter|thereby|therefore|therein|thereupon|these|they|thick|thin|third|this|those|though|three|through|
throughout|thru|thus|to|together|too|top|toward|towards|twelve|twenty|two|un|under|until|up|upon|us|very|via|was|
we|well|were|what|whatever|when|whence|whenever|where|whereafter|whereas|whereby|wherein|whereupon|wherever|whether|
which|while|whither|who|whoever|whole|whom|whose|why|will|with|within|without|would|yet|you|your|yours|yourself|
yourselves)\b"""
def search(request, template_name='blog/post_search.html'):
"""
Search for blog posts.
This template will allow you to setup a simple search form that will try to return results based on
given search strings. The queries will be put through a stop words filter to remove words like
'the', 'a', or 'have' to help imporve the result set.
Template: ``blog/post_search.html``
Context:
object_list
List of blog posts that match given search term(s).
search_term
Given search term.
"""
context = {}
if request.GET:
stop_word_list = re.compile(STOP_WORDS, re.IGNORECASE)
search_term = '%s' % request.GET['q']
cleaned_search_term = stop_word_list.sub('', search_term)
cleaned_search_term = cleaned_search_term.strip()
if len(cleaned_search_term) != 0:
post_list = Post.objects.published().filter(Q(body__icontains=cleaned_search_term) | Q(tags__icontains=cleaned_search_term) | Q(categories__title__icontains=cleaned_search_term))
context = {'object_list': post_list, 'search_term':search_term}
else:
message = 'Search term was too vague. Please try again.'
context = {'message':message}
return render_to_response(template_name, context, context_instance=RequestContext(request))

View File

@ -0,0 +1,10 @@
changes:
date: 2008-05-18
change: Converted everything to 4 space tabs and made a few other changes to comply with Python Style Guide.
date: 2008-04-23
change: Added a mode called InlineType and a template tag that returns this models objects.
date: 2008-04-22
change: Added an 'extract_inlines' filter so you can loop over a list of inlines in a body of text.
change: Creating new inlines app.

View File

@ -0,0 +1,27 @@
==============================================
Django Basic Inlines
http://code.google.com/p/django-basic-apps/
==============================================
A simple book library application for Django projects.
To install this app, simply create a folder somewhere in
your PYTHONPATH named 'basic' and place the 'inlines'
app inside. Then add 'basic.inlines' to your projects
INSTALLED_APPS list in your settings.py file.
Inlines is a template filter that can be used in
conjunction with inline markup to insert content objects
into other pieces of content. An example would be inserting
a photo into a blog post body.
An example of the markup is:
<inline type="media.photo" id="1" />
The type attribute is app_name.model_name and the id is
the object id. Pretty simple.
In your template you would say:
{% load inlines %}
{{ post.body|render_inlines }}

View File

View File

@ -0,0 +1,5 @@
from django.contrib import admin
from calibre.www.apps.inlines.models import *
admin.site.register(InlineType)

View File

@ -0,0 +1,17 @@
from django.db import models
from django.contrib.contenttypes.models import ContentType
class InlineType(models.Model):
""" InlineType model """
title = models.CharField(max_length=200)
content_type = models.ForeignKey(ContentType)
class Meta:
db_table = 'inline_types'
class Admin:
pass
def __unicode__(self):
return self.title

View File

@ -0,0 +1,92 @@
from django.template import TemplateSyntaxError
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.http import Http404
from django.utils.encoding import smart_unicode
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
def inlines(value, return_list=False):
try:
from BeautifulSoup import BeautifulStoneSoup
except ImportError:
from beautifulsoup import BeautifulStoneSoup
content = BeautifulStoneSoup(value, selfClosingTags=['inline','img','br','input','meta','link','hr'])
inline_list = []
if return_list:
for inline in content.findAll('inline'):
rendered_inline = render_inline(inline)
inline_list.append(rendered_inline['context'])
return inline_list
else:
for inline in content.findAll('inline'):
rendered_inline = render_inline(inline)
inline.replaceWith(render_to_string(rendered_inline['template'], rendered_inline['context']))
return mark_safe(content)
def render_inline(inline):
"""
Replace inline markup with template markup that matches the
appropriate app and model.
"""
# Look for inline type, 'app.model'
try:
app_label, model_name = inline['type'].split('.')
except:
if settings.DEBUG:
raise TemplateSyntaxError, "Couldn't find the attribute 'type' in the <inline> tag."
else:
return ''
# Look for content type
try:
content_type = ContentType.objects.get(app_label=app_label, model=model_name)
model = content_type.model_class()
except ContentType.DoesNotExist:
if settings.DEBUG:
raise TemplateSyntaxError, "Inline ContentType not found."
else:
return ''
# Check for an inline class attribute
try:
inline_class = smart_unicode(inline['class'])
except:
inline_class = ''
try:
try:
id_list = [int(i) for i in inline['ids'].split(',')]
obj_list = model.objects.in_bulk(id_list)
obj_list = list(obj_list[int(i)] for i in id_list)
context = { 'object_list': obj_list, 'class': inline_class }
except ValueError:
if settings.DEBUG:
raise ValueError, "The <inline> ids attribute is missing or invalid."
else:
return ''
except KeyError:
try:
obj = model.objects.get(pk=inline['id'])
context = { 'content_type':"%s.%s" % (app_label, model_name), 'object': obj, 'class': inline_class, 'settings': settings }
except model.DoesNotExist:
if settings.DEBUG:
raise model.DoesNotExist, "Object matching '%s' does not exist"
else:
return ''
except:
if settings.DEBUG:
raise TemplateSyntaxError, "The <inline> id attribute is missing or invalid."
else:
return ''
template = ["inlines/%s_%s.html" % (app_label, model_name), "inlines/default.html"]
rendered_inline = {'template':template, 'context':context}
return rendered_inline

View File

@ -0,0 +1,81 @@
from django import template
from calibre.www.apps.inlines.parser import inlines
from calibre.www.apps.inlines.models import InlineType
import re
register = template.Library()
@register.filter
def render_inlines(value):
"""
Renders inlines in a ``Post`` by passing them through inline templates.
Template Syntax::
{{ post.body|render_inlines|markdown:"safe" }}
Inline Syntax (singular)::
<inline type="<app_name>.<model_name>" id="<id>" class="med_left" />
Inline Syntax (plural)::
<inline type="<app_name>.<model_name>" ids="<id>, <id>, <id>" />
An inline template will be used to render the inline. Templates will be
locaed in the following maner:
``inlines/<app_name>_<model_name>.html``
The template will be passed the following context:
``object``
An object for the corresponding passed id.
or
``object_list``
A list of objects for the corresponding ids.
It would be wise to anticipate both object_list and object unless
you know for sure one or the other will only be present.
"""
return inlines(value)
@register.filter
def extract_inlines(value):
return inlines(value, True)
class InlineTypes(template.Node):
def __init__(self, var_name):
self.var_name = var_name
def render(self, context):
types = InlineType.objects.all()
context[self.var_name] = types
return ''
@register.tag(name='get_inline_types')
def do_get_inline_types(parser, token):
"""
Gets all inline types.
Syntax::
{% get_inline_types as [var_name] %}
Example usage::
{% get_inline_types as inline_list %}
"""
try:
tag_name, arg = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0]
m = re.search(r'as (\w+)', arg)
if not m:
raise template.TemplateSyntaxError, "%s tag had invalid arguments" % tag_name
var_name = m.groups()[0]
return InlineTypes(var_name)

View File

@ -0,0 +1,30 @@
from django.utils.translation import ugettext as _
from calibre.www.apps.tagging.managers import ModelTaggedItemManager, TagDescriptor
VERSION = (0, 3, 'pre')
class AlreadyRegistered(Exception):
"""
An attempt was made to register a model more than once.
"""
pass
registry = []
def register(model, tag_descriptor_attr='tags',
tagged_item_manager_attr='tagged'):
"""
Sets the given model class up for working with tags.
"""
if model in registry:
raise AlreadyRegistered(
_('The model %s has already been registered.') % model.__name__)
registry.append(model)
# Add tag descriptor
setattr(model, tag_descriptor_attr, TagDescriptor())
# Add custom manager
ModelTaggedItemManager().contribute_to_class(model,
tagged_item_manager_attr)

View File

@ -0,0 +1,5 @@
from django.contrib import admin
from calibre.www.apps.tagging.models import Tag, TaggedItem
admin.site.register(TaggedItem)
admin.site.register(Tag)

View File

@ -0,0 +1,107 @@
"""
A custom Model Field for tagging.
"""
from django.db.models import signals
from django.db.models.fields import CharField
from django.utils.translation import ugettext_lazy as _
from calibre.www.apps.tagging import settings
from calibre.www.apps.tagging.models import Tag
from calibre.www.apps.tagging.utils import edit_string_for_tags
class TagField(CharField):
"""
A "special" character field that actually works as a relationship to tags
"under the hood". This exposes a space-separated string of tags, but does
the splitting/reordering/etc. under the hood.
"""
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 255)
kwargs['blank'] = kwargs.get('blank', True)
super(TagField, self).__init__(*args, **kwargs)
def contribute_to_class(self, cls, name):
super(TagField, self).contribute_to_class(cls, name)
# Make this object the descriptor for field access.
setattr(cls, self.name, self)
# Save tags back to the database post-save
signals.post_save.connect(self._save, cls, True)
def __get__(self, instance, owner=None):
"""
Tag getter. Returns an instance's tags if accessed on an instance, and
all of a model's tags if called on a class. That is, this model::
class Link(models.Model):
...
tags = TagField()
Lets you do both of these::
>>> l = Link.objects.get(...)
>>> l.tags
'tag1 tag2 tag3'
>>> Link.tags
'tag1 tag2 tag3 tag4'
"""
# Handle access on the model (i.e. Link.tags)
if instance is None:
return edit_string_for_tags(Tag.objects.usage_for_model(owner))
tags = self._get_instance_tag_cache(instance)
if tags is None:
if instance.pk is None:
self._set_instance_tag_cache(instance, '')
else:
self._set_instance_tag_cache(
instance, edit_string_for_tags(Tag.objects.get_for_object(instance)))
return self._get_instance_tag_cache(instance)
def __set__(self, instance, value):
"""
Set an object's tags.
"""
if instance is None:
raise AttributeError(_('%s can only be set on instances.') % self.name)
if settings.FORCE_LOWERCASE_TAGS and value is not None:
value = value.lower()
self._set_instance_tag_cache(instance, value)
def _save(self, **kwargs): #signal, sender, instance):
"""
Save tags back to the database
"""
tags = self._get_instance_tag_cache(kwargs['instance'])
if tags is not None:
Tag.objects.update_tags(kwargs['instance'], tags)
def __delete__(self, instance):
"""
Clear all of an object's tags.
"""
self._set_instance_tag_cache(instance, '')
def _get_instance_tag_cache(self, instance):
"""
Helper: get an instance's tag cache.
"""
return getattr(instance, '_%s_cache' % self.attname, None)
def _set_instance_tag_cache(self, instance, tags):
"""
Helper: set an instance's tag cache.
"""
setattr(instance, '_%s_cache' % self.attname, tags)
def get_internal_type(self):
return 'CharField'
def formfield(self, **kwargs):
from calibre.www.apps.tagging import forms
defaults = {'form_class': forms.TagField}
defaults.update(kwargs)
return super(TagField, self).formfield(**defaults)

View File

@ -0,0 +1,40 @@
"""
Tagging components for Django's form library.
"""
from django import forms
from django.utils.translation import ugettext as _
from calibre.www.apps.tagging import settings
from calibre.www.apps.tagging.models import Tag
from calibre.www.apps.tagging.utils import parse_tag_input
class AdminTagForm(forms.ModelForm):
class Meta:
model = Tag
def clean_name(self):
value = self.cleaned_data['name']
tag_names = parse_tag_input(value)
if len(tag_names) > 1:
raise ValidationError(_('Multiple tags were given.'))
elif len(tag_names[0]) > settings.MAX_TAG_LENGTH:
raise forms.ValidationError(
_('A tag may be no more than %s characters long.') %
settings.MAX_TAG_LENGTH)
return value
class TagField(forms.CharField):
"""
A ``CharField`` which validates that its input is a valid list of
tag names.
"""
def clean(self, value):
value = super(TagField, self).clean(value)
if value == u'':
return value
for tag_name in parse_tag_input(value):
if len(tag_name) > settings.MAX_TAG_LENGTH:
raise forms.ValidationError(
_('Each tag may be no more than %s characters long.') %
settings.MAX_TAG_LENGTH)
return value

View File

@ -0,0 +1,40 @@
from django.contrib.contenttypes.models import ContentType
def fetch_content_objects(tagged_items, select_related_for=None):
"""
Retrieves ``ContentType`` and content objects for the given list of
``TaggedItems``, grouping the retrieval of content objects by model
type to reduce the number of queries executed.
This results in ``number_of_content_types + 1`` queries rather than
the ``number_of_tagged_items * 2`` queries you'd get by iterating
over the list and accessing each item's ``object`` attribute.
A ``select_related_for`` argument can be used to specify a list of
of model names (corresponding to the ``model`` field of a
``ContentType``) for which ``select_related`` should be used when
retrieving model instances.
"""
if select_related_for is None: select_related_for = []
# Group content object pks by their content type pks
objects = {}
for item in tagged_items:
objects.setdefault(item.content_type_id, []).append(item.object_id)
# Retrieve content types and content objects in bulk
content_types = ContentType._default_manager.in_bulk(objects.keys())
for content_type_pk, object_pks in objects.iteritems():
model = content_types[content_type_pk].model_class()
if content_types[content_type_pk].model in select_related_for:
objects[content_type_pk] = model._default_manager.select_related().in_bulk(object_pks)
else:
objects[content_type_pk] = model._default_manager.in_bulk(object_pks)
# Set content types and content objects in the appropriate cache
# attributes, so accessing the 'content_type' and 'object'
# attributes on each tagged item won't result in further database
# hits.
for item in tagged_items:
item._object_cache = objects[item.content_type_id][item.object_id]
item._content_type_cache = content_types[item.content_type_id]

View File

@ -0,0 +1,68 @@
"""
Custom managers for Django models registered with the tagging
application.
"""
from django.contrib.contenttypes.models import ContentType
from django.db import models
from calibre.www.apps.tagging.models import Tag, TaggedItem
class ModelTagManager(models.Manager):
"""
A manager for retrieving tags for a particular model.
"""
def get_query_set(self):
ctype = ContentType.objects.get_for_model(self.model)
return Tag.objects.filter(
items__content_type__pk=ctype.pk).distinct()
def cloud(self, *args, **kwargs):
return Tag.objects.cloud_for_model(self.model, *args, **kwargs)
def related(self, tags, *args, **kwargs):
return Tag.objects.related_for_model(tags, self.model, *args, **kwargs)
def usage(self, *args, **kwargs):
return Tag.objects.usage_for_model(self.model, *args, **kwargs)
class ModelTaggedItemManager(models.Manager):
"""
A manager for retrieving model instances based on their tags.
"""
def related_to(self, obj, queryset=None, num=None):
if queryset is None:
return TaggedItem.objects.get_related(obj, self.model, num=num)
else:
return TaggedItem.objects.get_related(obj, queryset, num=num)
def with_all(self, tags, queryset=None):
if queryset is None:
return TaggedItem.objects.get_by_model(self.model, tags)
else:
return TaggedItem.objects.get_by_model(queryset, tags)
def with_any(self, tags, queryset=None):
if queryset is None:
return TaggedItem.objects.get_union_by_model(self.model, tags)
else:
return TaggedItem.objects.get_union_by_model(queryset, tags)
class TagDescriptor(object):
"""
A descriptor which provides access to a ``ModelTagManager`` for
model classes and simple retrieval, updating and deletion of tags
for model instances.
"""
def __get__(self, instance, owner):
if not instance:
tag_manager = ModelTagManager()
tag_manager.model = owner
return tag_manager
else:
return Tag.objects.get_for_object(instance)
def __set__(self, instance, value):
Tag.objects.update_tags(instance, value)
def __delete__(self, instance):
Tag.objects.update_tags(instance, None)

View File

@ -0,0 +1,480 @@
"""
Models and managers for generic tagging.
"""
# Python 2.3 compatibility
try:
set
except NameError:
from sets import Set as set
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import connection, models
from django.db.models.query import QuerySet
from django.utils.translation import ugettext_lazy as _
from calibre.www.apps.tagging import settings
from calibre.www.apps.tagging.utils import calculate_cloud, get_tag_list, get_queryset_and_model, parse_tag_input
from calibre.www.apps.tagging.utils import LOGARITHMIC
qn = connection.ops.quote_name
############
# Managers #
############
class TagManager(models.Manager):
def update_tags(self, obj, tag_names):
"""
Update tags associated with an object.
"""
ctype = ContentType.objects.get_for_model(obj)
current_tags = list(self.filter(items__content_type__pk=ctype.pk,
items__object_id=obj.pk))
updated_tag_names = parse_tag_input(tag_names)
if settings.FORCE_LOWERCASE_TAGS:
updated_tag_names = [t.lower() for t in updated_tag_names]
# Remove tags which no longer apply
tags_for_removal = [tag for tag in current_tags \
if tag.name not in updated_tag_names]
if len(tags_for_removal):
TaggedItem._default_manager.filter(content_type__pk=ctype.pk,
object_id=obj.pk,
tag__in=tags_for_removal).delete()
# Add new tags
current_tag_names = [tag.name for tag in current_tags]
for tag_name in updated_tag_names:
if tag_name not in current_tag_names:
tag, created = self.get_or_create(name=tag_name)
TaggedItem._default_manager.create(tag=tag, object=obj)
def add_tag(self, obj, tag_name):
"""
Associates the given object with a tag.
"""
tag_names = parse_tag_input(tag_name)
if not len(tag_names):
raise AttributeError(_('No tags were given: "%s".') % tag_name)
if len(tag_names) > 1:
raise AttributeError(_('Multiple tags were given: "%s".') % tag_name)
tag_name = tag_names[0]
if settings.FORCE_LOWERCASE_TAGS:
tag_name = tag_name.lower()
tag, created = self.get_or_create(name=tag_name)
ctype = ContentType.objects.get_for_model(obj)
TaggedItem._default_manager.get_or_create(
tag=tag, content_type=ctype, object_id=obj.pk)
def get_for_object(self, obj):
"""
Create a queryset matching all tags associated with the given
object.
"""
ctype = ContentType.objects.get_for_model(obj)
return self.filter(items__content_type__pk=ctype.pk,
items__object_id=obj.pk)
def _get_usage(self, model, counts=False, min_count=None, extra_joins=None, extra_criteria=None, params=None):
"""
Perform the custom SQL query for ``usage_for_model`` and
``usage_for_queryset``.
"""
if min_count is not None: counts = True
model_table = qn(model._meta.db_table)
model_pk = '%s.%s' % (model_table, qn(model._meta.pk.column))
query = """
SELECT DISTINCT %(tag)s.id, %(tag)s.name%(count_sql)s
FROM
%(tag)s
INNER JOIN %(tagged_item)s
ON %(tag)s.id = %(tagged_item)s.tag_id
INNER JOIN %(model)s
ON %(tagged_item)s.object_id = %(model_pk)s
%%s
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
%%s
GROUP BY %(tag)s.id, %(tag)s.name
%%s
ORDER BY %(tag)s.name ASC""" % {
'tag': qn(self.model._meta.db_table),
'count_sql': counts and (', COUNT(%s)' % model_pk) or '',
'tagged_item': qn(TaggedItem._meta.db_table),
'model': model_table,
'model_pk': model_pk,
'content_type_id': ContentType.objects.get_for_model(model).pk,
}
min_count_sql = ''
if min_count is not None:
min_count_sql = 'HAVING COUNT(%s) >= %%s' % model_pk
params.append(min_count)
cursor = connection.cursor()
cursor.execute(query % (extra_joins, extra_criteria, min_count_sql), params)
tags = []
for row in cursor.fetchall():
t = self.model(*row[:2])
if counts:
t.count = row[2]
tags.append(t)
return tags
def usage_for_model(self, model, counts=False, min_count=None, filters=None):
"""
Obtain a list of tags associated with instances of the given
Model class.
If ``counts`` is True, a ``count`` attribute will be added to
each tag, indicating how many times it has been used against
the Model class in question.
If ``min_count`` is given, only tags which have a ``count``
greater than or equal to ``min_count`` will be returned.
Passing a value for ``min_count`` implies ``counts=True``.
To limit the tags (and counts, if specified) returned to those
used by a subset of the Model's instances, pass a dictionary
of field lookups to be applied to the given Model as the
``filters`` argument.
"""
if filters is None: filters = {}
queryset = model._default_manager.filter()
for f in filters.items():
queryset.query.add_filter(f)
usage = self.usage_for_queryset(queryset, counts, min_count)
return usage
def usage_for_queryset(self, queryset, counts=False, min_count=None):
"""
Obtain a list of tags associated with instances of a model
contained in the given queryset.
If ``counts`` is True, a ``count`` attribute will be added to
each tag, indicating how many times it has been used against
the Model class in question.
If ``min_count`` is given, only tags which have a ``count``
greater than or equal to ``min_count`` will be returned.
Passing a value for ``min_count`` implies ``counts=True``.
"""
extra_joins = ' '.join(queryset.query.get_from_clause()[0][1:])
where, params = queryset.query.where.as_sql()
if where:
extra_criteria = 'AND %s' % where
else:
extra_criteria = ''
return self._get_usage(queryset.model, counts, min_count, extra_joins, extra_criteria, params)
def related_for_model(self, tags, model, counts=False, min_count=None):
"""
Obtain a list of tags related to a given list of tags - that
is, other tags used by items which have all the given tags.
If ``counts`` is True, a ``count`` attribute will be added to
each tag, indicating the number of items which have it in
addition to the given list of tags.
If ``min_count`` is given, only tags which have a ``count``
greater than or equal to ``min_count`` will be returned.
Passing a value for ``min_count`` implies ``counts=True``.
"""
if min_count is not None: counts = True
tags = get_tag_list(tags)
tag_count = len(tags)
tagged_item_table = qn(TaggedItem._meta.db_table)
query = """
SELECT %(tag)s.id, %(tag)s.name%(count_sql)s
FROM %(tagged_item)s INNER JOIN %(tag)s ON %(tagged_item)s.tag_id = %(tag)s.id
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tagged_item)s.object_id IN
(
SELECT %(tagged_item)s.object_id
FROM %(tagged_item)s, %(tag)s
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tag)s.id = %(tagged_item)s.tag_id
AND %(tag)s.id IN (%(tag_id_placeholders)s)
GROUP BY %(tagged_item)s.object_id
HAVING COUNT(%(tagged_item)s.object_id) = %(tag_count)s
)
AND %(tag)s.id NOT IN (%(tag_id_placeholders)s)
GROUP BY %(tag)s.id, %(tag)s.name
%(min_count_sql)s
ORDER BY %(tag)s.name ASC""" % {
'tag': qn(self.model._meta.db_table),
'count_sql': counts and ', COUNT(%s.object_id)' % tagged_item_table or '',
'tagged_item': tagged_item_table,
'content_type_id': ContentType.objects.get_for_model(model).pk,
'tag_id_placeholders': ','.join(['%s'] * tag_count),
'tag_count': tag_count,
'min_count_sql': min_count is not None and ('HAVING COUNT(%s.object_id) >= %%s' % tagged_item_table) or '',
}
params = [tag.pk for tag in tags] * 2
if min_count is not None:
params.append(min_count)
cursor = connection.cursor()
cursor.execute(query, params)
related = []
for row in cursor.fetchall():
tag = self.model(*row[:2])
if counts is True:
tag.count = row[2]
related.append(tag)
return related
def cloud_for_model(self, model, steps=4, distribution=LOGARITHMIC,
filters=None, min_count=None):
"""
Obtain a list of tags associated with instances of the given
Model, giving each tag a ``count`` attribute indicating how
many times it has been used and a ``font_size`` attribute for
use in displaying a tag cloud.
``steps`` defines the range of font sizes - ``font_size`` will
be an integer between 1 and ``steps`` (inclusive).
``distribution`` defines the type of font size distribution
algorithm which will be used - logarithmic or linear. It must
be either ``tagging.utils.LOGARITHMIC`` or
``tagging.utils.LINEAR``.
To limit the tags displayed in the cloud to those associated
with a subset of the Model's instances, pass a dictionary of
field lookups to be applied to the given Model as the
``filters`` argument.
To limit the tags displayed in the cloud to those with a
``count`` greater than or equal to ``min_count``, pass a value
for the ``min_count`` argument.
"""
tags = list(self.usage_for_model(model, counts=True, filters=filters,
min_count=min_count))
return calculate_cloud(tags, steps, distribution)
class TaggedItemManager(models.Manager):
"""
FIXME There's currently no way to get the ``GROUP BY`` and ``HAVING``
SQL clauses required by many of this manager's methods into
Django's ORM.
For now, we manually execute a query to retrieve the PKs of
objects we're interested in, then use the ORM's ``__in``
lookup to return a ``QuerySet``.
Now that the queryset-refactor branch is in the trunk, this can be
tidied up significantly.
"""
def get_by_model(self, queryset_or_model, tags):
"""
Create a ``QuerySet`` containing instances of the specified
model associated with a given tag or list of tags.
"""
tags = get_tag_list(tags)
tag_count = len(tags)
if tag_count == 0:
# No existing tags were given
queryset, model = get_queryset_and_model(queryset_or_model)
return model._default_manager.none()
elif tag_count == 1:
# Optimisation for single tag - fall through to the simpler
# query below.
tag = tags[0]
else:
return self.get_intersection_by_model(queryset_or_model, tags)
queryset, model = get_queryset_and_model(queryset_or_model)
content_type = ContentType.objects.get_for_model(model)
opts = self.model._meta
tagged_item_table = qn(opts.db_table)
return queryset.extra(
tables=[opts.db_table],
where=[
'%s.content_type_id = %%s' % tagged_item_table,
'%s.tag_id = %%s' % tagged_item_table,
'%s.%s = %s.object_id' % (qn(model._meta.db_table),
qn(model._meta.pk.column),
tagged_item_table)
],
params=[content_type.pk, tag.pk],
)
def get_intersection_by_model(self, queryset_or_model, tags):
"""
Create a ``QuerySet`` containing instances of the specified
model associated with *all* of the given list of tags.
"""
tags = get_tag_list(tags)
tag_count = len(tags)
queryset, model = get_queryset_and_model(queryset_or_model)
if not tag_count:
return model._default_manager.none()
model_table = qn(model._meta.db_table)
# This query selects the ids of all objects which have all the
# given tags.
query = """
SELECT %(model_pk)s
FROM %(model)s, %(tagged_item)s
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
AND %(model_pk)s = %(tagged_item)s.object_id
GROUP BY %(model_pk)s
HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % {
'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
'model': model_table,
'tagged_item': qn(self.model._meta.db_table),
'content_type_id': ContentType.objects.get_for_model(model).pk,
'tag_id_placeholders': ','.join(['%s'] * tag_count),
'tag_count': tag_count,
}
cursor = connection.cursor()
cursor.execute(query, [tag.pk for tag in tags])
object_ids = [row[0] for row in cursor.fetchall()]
if len(object_ids) > 0:
return queryset.filter(pk__in=object_ids)
else:
return model._default_manager.none()
def get_union_by_model(self, queryset_or_model, tags):
"""
Create a ``QuerySet`` containing instances of the specified
model associated with *any* of the given list of tags.
"""
tags = get_tag_list(tags)
tag_count = len(tags)
queryset, model = get_queryset_and_model(queryset_or_model)
if not tag_count:
return model._default_manager.none()
model_table = qn(model._meta.db_table)
# This query selects the ids of all objects which have any of
# the given tags.
query = """
SELECT %(model_pk)s
FROM %(model)s, %(tagged_item)s
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
AND %(model_pk)s = %(tagged_item)s.object_id
GROUP BY %(model_pk)s""" % {
'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
'model': model_table,
'tagged_item': qn(self.model._meta.db_table),
'content_type_id': ContentType.objects.get_for_model(model).pk,
'tag_id_placeholders': ','.join(['%s'] * tag_count),
}
cursor = connection.cursor()
cursor.execute(query, [tag.pk for tag in tags])
object_ids = [row[0] for row in cursor.fetchall()]
if len(object_ids) > 0:
return queryset.filter(pk__in=object_ids)
else:
return model._default_manager.none()
def get_related(self, obj, queryset_or_model, num=None):
"""
Retrieve a list of instances of the specified model which share
tags with the model instance ``obj``, ordered by the number of
shared tags in descending order.
If ``num`` is given, a maximum of ``num`` instances will be
returned.
"""
queryset, model = get_queryset_and_model(queryset_or_model)
model_table = qn(model._meta.db_table)
content_type = ContentType.objects.get_for_model(obj)
related_content_type = ContentType.objects.get_for_model(model)
query = """
SELECT %(model_pk)s, COUNT(related_tagged_item.object_id) AS %(count)s
FROM %(model)s, %(tagged_item)s, %(tag)s, %(tagged_item)s related_tagged_item
WHERE %(tagged_item)s.object_id = %%s
AND %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tag)s.id = %(tagged_item)s.tag_id
AND related_tagged_item.content_type_id = %(related_content_type_id)s
AND related_tagged_item.tag_id = %(tagged_item)s.tag_id
AND %(model_pk)s = related_tagged_item.object_id"""
if content_type.pk == related_content_type.pk:
# Exclude the given instance itself if determining related
# instances for the same model.
query += """
AND related_tagged_item.object_id != %(tagged_item)s.object_id"""
query += """
GROUP BY %(model_pk)s
ORDER BY %(count)s DESC
%(limit_offset)s"""
query = query % {
'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
'count': qn('count'),
'model': model_table,
'tagged_item': qn(self.model._meta.db_table),
'tag': qn(self.model._meta.get_field('tag').rel.to._meta.db_table),
'content_type_id': content_type.pk,
'related_content_type_id': related_content_type.pk,
# Hardcoding this for now just to get tests working again - this
# should now be handled by the query object.
'limit_offset': num is not None and 'LIMIT %s' or '',
}
cursor = connection.cursor()
params = [obj.pk]
if num is not None:
params.append(num)
cursor.execute(query, params)
object_ids = [row[0] for row in cursor.fetchall()]
if len(object_ids) > 0:
# Use in_bulk here instead of an id__in lookup, because id__in would
# clobber the ordering.
object_dict = queryset.in_bulk(object_ids)
return [object_dict[object_id] for object_id in object_ids \
if object_id in object_dict]
else:
return []
##########
# Models #
##########
class Tag(models.Model):
"""
A tag.
"""
name = models.CharField(_('name'), max_length=50, unique=True, db_index=True)
objects = TagManager()
class Meta:
ordering = ('name',)
verbose_name = _('tag')
verbose_name_plural = _('tags')
def __unicode__(self):
return self.name
class TaggedItem(models.Model):
"""
Holds the relationship between a tag and the item being tagged.
"""
tag = models.ForeignKey(Tag, verbose_name=_('tag'), related_name='items')
content_type = models.ForeignKey(ContentType, verbose_name=_('content type'))
object_id = models.PositiveIntegerField(_('object id'), db_index=True)
object = generic.GenericForeignKey('content_type', 'object_id')
objects = TaggedItemManager()
class Meta:
# Enforce unique tag association per object
unique_together = (('tag', 'content_type', 'object_id'),)
verbose_name = _('tagged item')
verbose_name_plural = _('tagged items')
def __unicode__(self):
return u'%s [%s]' % (self.object, self.tag)

View File

@ -0,0 +1,13 @@
"""
Convenience module for access of custom tagging application settings,
which enforces default settings when the main settings module does not
contain the appropriate settings.
"""
from django.conf import settings
# The maximum length of a tag's name.
MAX_TAG_LENGTH = getattr(settings, 'MAX_TAG_LENGTH', 50)
# Whether to force all tags to lowercase before they are saved to the
# database.
FORCE_LOWERCASE_TAGS = getattr(settings, 'FORCE_LOWERCASE_TAGS', False)

View File

@ -0,0 +1,231 @@
from django.db.models import get_model
from django.template import Library, Node, TemplateSyntaxError, Variable, resolve_variable
from django.utils.translation import ugettext as _
from tagging.models import Tag, TaggedItem
from tagging.utils import LINEAR, LOGARITHMIC
register = Library()
class TagsForModelNode(Node):
def __init__(self, model, context_var, counts):
self.model = model
self.context_var = context_var
self.counts = counts
def render(self, context):
model = get_model(*self.model.split('.'))
if model is None:
raise TemplateSyntaxError(_('tags_for_model tag was given an invalid model: %s') % self.model)
context[self.context_var] = Tag.objects.usage_for_model(model, counts=self.counts)
return ''
class TagCloudForModelNode(Node):
def __init__(self, model, context_var, **kwargs):
self.model = model
self.context_var = context_var
self.kwargs = kwargs
def render(self, context):
model = get_model(*self.model.split('.'))
if model is None:
raise TemplateSyntaxError(_('tag_cloud_for_model tag was given an invalid model: %s') % self.model)
context[self.context_var] = \
Tag.objects.cloud_for_model(model, **self.kwargs)
return ''
class TagsForObjectNode(Node):
def __init__(self, obj, context_var):
self.obj = Variable(obj)
self.context_var = context_var
def render(self, context):
context[self.context_var] = \
Tag.objects.get_for_object(self.obj.resolve(context))
return ''
class TaggedObjectsNode(Node):
def __init__(self, tag, model, context_var):
self.tag = Variable(tag)
self.context_var = context_var
self.model = model
def render(self, context):
model = get_model(*self.model.split('.'))
if model is None:
raise TemplateSyntaxError(_('tagged_objects tag was given an invalid model: %s') % self.model)
context[self.context_var] = \
TaggedItem.objects.get_by_model(model, self.tag.resolve(context))
return ''
def do_tags_for_model(parser, token):
"""
Retrieves a list of ``Tag`` objects associated with a given model
and stores them in a context variable.
Usage::
{% tags_for_model [model] as [varname] %}
The model is specified in ``[appname].[modelname]`` format.
Extended usage::
{% tags_for_model [model] as [varname] with counts %}
If specified - by providing extra ``with counts`` arguments - adds
a ``count`` attribute to each tag containing the number of
instances of the given model which have been tagged with it.
Examples::
{% tags_for_model products.Widget as widget_tags %}
{% tags_for_model products.Widget as widget_tags with counts %}
"""
bits = token.contents.split()
len_bits = len(bits)
if len_bits not in (4, 6):
raise TemplateSyntaxError(_('%s tag requires either three or five arguments') % bits[0])
if bits[2] != 'as':
raise TemplateSyntaxError(_("second argument to %s tag must be 'as'") % bits[0])
if len_bits == 6:
if bits[4] != 'with':
raise TemplateSyntaxError(_("if given, fourth argument to %s tag must be 'with'") % bits[0])
if bits[5] != 'counts':
raise TemplateSyntaxError(_("if given, fifth argument to %s tag must be 'counts'") % bits[0])
if len_bits == 4:
return TagsForModelNode(bits[1], bits[3], counts=False)
else:
return TagsForModelNode(bits[1], bits[3], counts=True)
def do_tag_cloud_for_model(parser, token):
"""
Retrieves a list of ``Tag`` objects for a given model, with tag
cloud attributes set, and stores them in a context variable.
Usage::
{% tag_cloud_for_model [model] as [varname] %}
The model is specified in ``[appname].[modelname]`` format.
Extended usage::
{% tag_cloud_for_model [model] as [varname] with [options] %}
Extra options can be provided after an optional ``with`` argument,
with each option being specified in ``[name]=[value]`` format. Valid
extra options are:
``steps``
Integer. Defines the range of font sizes.
``min_count``
Integer. Defines the minimum number of times a tag must have
been used to appear in the cloud.
``distribution``
One of ``linear`` or ``log``. Defines the font-size
distribution algorithm to use when generating the tag cloud.
Examples::
{% tag_cloud_for_model products.Widget as widget_tags %}
{% tag_cloud_for_model products.Widget as widget_tags with steps=9 min_count=3 distribution=log %}
"""
bits = token.contents.split()
len_bits = len(bits)
if len_bits != 4 and len_bits not in range(6, 9):
raise TemplateSyntaxError(_('%s tag requires either three or between five and seven arguments') % bits[0])
if bits[2] != 'as':
raise TemplateSyntaxError(_("second argument to %s tag must be 'as'") % bits[0])
kwargs = {}
if len_bits > 5:
if bits[4] != 'with':
raise TemplateSyntaxError(_("if given, fourth argument to %s tag must be 'with'") % bits[0])
for i in range(5, len_bits):
try:
name, value = bits[i].split('=')
if name == 'steps' or name == 'min_count':
try:
kwargs[str(name)] = int(value)
except ValueError:
raise TemplateSyntaxError(_("%(tag)s tag's '%(option)s' option was not a valid integer: '%(value)s'") % {
'tag': bits[0],
'option': name,
'value': value,
})
elif name == 'distribution':
if value in ['linear', 'log']:
kwargs[str(name)] = {'linear': LINEAR, 'log': LOGARITHMIC}[value]
else:
raise TemplateSyntaxError(_("%(tag)s tag's '%(option)s' option was not a valid choice: '%(value)s'") % {
'tag': bits[0],
'option': name,
'value': value,
})
else:
raise TemplateSyntaxError(_("%(tag)s tag was given an invalid option: '%(option)s'") % {
'tag': bits[0],
'option': name,
})
except ValueError:
raise TemplateSyntaxError(_("%(tag)s tag was given a badly formatted option: '%(option)s'") % {
'tag': bits[0],
'option': bits[i],
})
return TagCloudForModelNode(bits[1], bits[3], **kwargs)
def do_tags_for_object(parser, token):
"""
Retrieves a list of ``Tag`` objects associated with an object and
stores them in a context variable.
Usage::
{% tags_for_object [object] as [varname] %}
Example::
{% tags_for_object foo_object as tag_list %}
"""
bits = token.contents.split()
if len(bits) != 4:
raise TemplateSyntaxError(_('%s tag requires exactly three arguments') % bits[0])
if bits[2] != 'as':
raise TemplateSyntaxError(_("second argument to %s tag must be 'as'") % bits[0])
return TagsForObjectNode(bits[1], bits[3])
def do_tagged_objects(parser, token):
"""
Retrieves a list of instances of a given model which are tagged with
a given ``Tag`` and stores them in a context variable.
Usage::
{% tagged_objects [tag] in [model] as [varname] %}
The model is specified in ``[appname].[modelname]`` format.
The tag must be an instance of a ``Tag``, not the name of a tag.
Example::
{% tagged_objects comedy_tag in tv.Show as comedies %}
"""
bits = token.contents.split()
if len(bits) != 6:
raise TemplateSyntaxError(_('%s tag requires exactly five arguments') % bits[0])
if bits[2] != 'in':
raise TemplateSyntaxError(_("second argument to %s tag must be 'in'") % bits[0])
if bits[4] != 'as':
raise TemplateSyntaxError(_("fourth argument to %s tag must be 'as'") % bits[0])
return TaggedObjectsNode(bits[1], bits[3], bits[5])
register.tag('tags_for_model', do_tags_for_model)
register.tag('tag_cloud_for_model', do_tag_cloud_for_model)
register.tag('tags_for_object', do_tags_for_object)
register.tag('tagged_objects', do_tagged_objects)

View File

@ -0,0 +1,263 @@
"""
Tagging utilities - from user tag input parsing to tag cloud
calculation.
"""
import math
import types
from django.db.models.query import QuerySet
from django.utils.encoding import force_unicode
from django.utils.translation import ugettext as _
# Python 2.3 compatibility
try:
set
except NameError:
from sets import Set as set
def parse_tag_input(input):
"""
Parses tag input, with multiple word input being activated and
delineated by commas and double quotes. Quotes take precedence, so
they may contain commas.
Returns a sorted list of unique tag names.
"""
if not input:
return []
input = force_unicode(input)
# Special case - if there are no commas or double quotes in the
# input, we don't *do* a recall... I mean, we know we only need to
# split on spaces.
if u',' not in input and u'"' not in input:
words = list(set(split_strip(input, u' ')))
words.sort()
return words
words = []
buffer = []
# Defer splitting of non-quoted sections until we know if there are
# any unquoted commas.
to_be_split = []
saw_loose_comma = False
open_quote = False
i = iter(input)
try:
while 1:
c = i.next()
if c == u'"':
if buffer:
to_be_split.append(u''.join(buffer))
buffer = []
# Find the matching quote
open_quote = True
c = i.next()
while c != u'"':
buffer.append(c)
c = i.next()
if buffer:
word = u''.join(buffer).strip()
if word:
words.append(word)
buffer = []
open_quote = False
else:
if not saw_loose_comma and c == u',':
saw_loose_comma = True
buffer.append(c)
except StopIteration:
# If we were parsing an open quote which was never closed treat
# the buffer as unquoted.
if buffer:
if open_quote and u',' in buffer:
saw_loose_comma = True
to_be_split.append(u''.join(buffer))
if to_be_split:
if saw_loose_comma:
delimiter = u','
else:
delimiter = u' '
for chunk in to_be_split:
words.extend(split_strip(chunk, delimiter))
words = list(set(words))
words.sort()
return words
def split_strip(input, delimiter=u','):
"""
Splits ``input`` on ``delimiter``, stripping each resulting string
and returning a list of non-empty strings.
"""
if not input:
return []
words = [w.strip() for w in input.split(delimiter)]
return [w for w in words if w]
def edit_string_for_tags(tags):
"""
Given list of ``Tag`` instances, creates a string representation of
the list suitable for editing by the user, such that submitting the
given string representation back without changing it will give the
same list of tags.
Tag names which contain commas will be double quoted.
If any tag name which isn't being quoted contains whitespace, the
resulting string of tag names will be comma-delimited, otherwise
it will be space-delimited.
"""
names = []
use_commas = False
for tag in tags:
name = tag.name
if u',' in name:
names.append('"%s"' % name)
continue
elif u' ' in name:
if not use_commas:
use_commas = True
names.append(name)
if use_commas:
glue = u', '
else:
glue = u' '
return glue.join(names)
def get_queryset_and_model(queryset_or_model):
"""
Given a ``QuerySet`` or a ``Model``, returns a two-tuple of
(queryset, model).
If a ``Model`` is given, the ``QuerySet`` returned will be created
using its default manager.
"""
try:
return queryset_or_model, queryset_or_model.model
except AttributeError:
return queryset_or_model._default_manager.all(), queryset_or_model
def get_tag_list(tags):
"""
Utility function for accepting tag input in a flexible manner.
If a ``Tag`` object is given, it will be returned in a list as
its single occupant.
If given, the tag names in the following will be used to create a
``Tag`` ``QuerySet``:
* A string, which may contain multiple tag names.
* A list or tuple of strings corresponding to tag names.
* A list or tuple of integers corresponding to tag ids.
If given, the following will be returned as-is:
* A list or tuple of ``Tag`` objects.
* A ``Tag`` ``QuerySet``.
"""
from calibre.www.apps.tagging.models import Tag
if isinstance(tags, Tag):
return [tags]
elif isinstance(tags, QuerySet) and tags.model is Tag:
return tags
elif isinstance(tags, types.StringTypes):
return Tag.objects.filter(name__in=parse_tag_input(tags))
elif isinstance(tags, (types.ListType, types.TupleType)):
if len(tags) == 0:
return tags
contents = set()
for item in tags:
if isinstance(item, types.StringTypes):
contents.add('string')
elif isinstance(item, Tag):
contents.add('tag')
elif isinstance(item, (types.IntType, types.LongType)):
contents.add('int')
if len(contents) == 1:
if 'string' in contents:
return Tag.objects.filter(name__in=[force_unicode(tag) \
for tag in tags])
elif 'tag' in contents:
return tags
elif 'int' in contents:
return Tag.objects.filter(id__in=tags)
else:
raise ValueError(_('If a list or tuple of tags is provided, they must all be tag names, Tag objects or Tag ids.'))
else:
raise ValueError(_('The tag input given was invalid.'))
def get_tag(tag):
"""
Utility function for accepting single tag input in a flexible
manner.
If a ``Tag`` object is given it will be returned as-is; if a
string or integer are given, they will be used to lookup the
appropriate ``Tag``.
If no matching tag can be found, ``None`` will be returned.
"""
from calibre.www.apps.tagging.models import Tag
if isinstance(tag, Tag):
return tag
try:
if isinstance(tag, types.StringTypes):
return Tag.objects.get(name=tag)
elif isinstance(tag, (types.IntType, types.LongType)):
return Tag.objects.get(id=tag)
except Tag.DoesNotExist:
pass
return None
# Font size distribution algorithms
LOGARITHMIC, LINEAR = 1, 2
def _calculate_thresholds(min_weight, max_weight, steps):
delta = (max_weight - min_weight) / float(steps)
return [min_weight + i * delta for i in range(1, steps + 1)]
def _calculate_tag_weight(weight, max_weight, distribution):
"""
Logarithmic tag weight calculation is based on code from the
`Tag Cloud`_ plugin for Mephisto, by Sven Fuchs.
.. _`Tag Cloud`: http://www.artweb-design.de/projects/mephisto-plugin-tag-cloud
"""
if distribution == LINEAR or max_weight == 1:
return weight
elif distribution == LOGARITHMIC:
return math.log(weight) * max_weight / math.log(max_weight)
raise ValueError(_('Invalid distribution algorithm specified: %s.') % distribution)
def calculate_cloud(tags, steps=4, distribution=LOGARITHMIC):
"""
Add a ``font_size`` attribute to each tag according to the
frequency of its use, as indicated by its ``count``
attribute.
``steps`` defines the range of font sizes - ``font_size`` will
be an integer between 1 and ``steps`` (inclusive).
``distribution`` defines the type of font size distribution
algorithm which will be used - logarithmic or linear. It must be
one of ``tagging.utils.LOGARITHMIC`` or ``tagging.utils.LINEAR``.
"""
if len(tags) > 0:
counts = [tag.count for tag in tags]
min_weight = float(min(counts))
max_weight = float(max(counts))
thresholds = _calculate_thresholds(min_weight, max_weight, steps)
for tag in tags:
font_set = False
tag_weight = _calculate_tag_weight(tag.count, max_weight, distribution)
for i in range(steps):
if not font_set and tag_weight <= thresholds[i]:
tag.font_size = i + 1
font_set = True
return tags

View File

@ -0,0 +1,52 @@
"""
Tagging related views.
"""
from django.http import Http404
from django.utils.translation import ugettext as _
from django.views.generic.list_detail import object_list
from calibre.www.apps.tagging.models import Tag, TaggedItem
from calibre.www.apps.tagging.utils import get_tag, get_queryset_and_model
def tagged_object_list(request, queryset_or_model=None, tag=None,
related_tags=False, related_tag_counts=True, **kwargs):
"""
A thin wrapper around
``django.views.generic.list_detail.object_list`` which creates a
``QuerySet`` containing instances of the given queryset or model
tagged with the given tag.
In addition to the context variables set up by ``object_list``, a
``tag`` context variable will contain the ``Tag`` instance for the
tag.
If ``related_tags`` is ``True``, a ``related_tags`` context variable
will contain tags related to the given tag for the given model.
Additionally, if ``related_tag_counts`` is ``True``, each related
tag will have a ``count`` attribute indicating the number of items
which have it in addition to the given tag.
"""
if queryset_or_model is None:
try:
queryset_or_model = kwargs.pop('queryset_or_model')
except KeyError:
raise AttributeError(_('tagged_object_list must be called with a queryset or a model.'))
if tag is None:
try:
tag = kwargs.pop('tag')
except KeyError:
raise AttributeError(_('tagged_object_list must be called with a tag.'))
tag_instance = get_tag(tag)
if tag_instance is None:
raise Http404(_('No Tag found matching "%s".') % tag)
queryset = TaggedItem.objects.get_by_model(queryset_or_model, tag_instance)
if not kwargs.has_key('extra_context'):
kwargs['extra_context'] = {}
kwargs['extra_context']['tag'] = tag_instance
if related_tags:
kwargs['extra_context']['related_tags'] = \
Tag.objects.related_for_model(tag_instance, queryset_or_model,
counts=related_tag_counts)
return object_list(request, queryset, **kwargs)

View File

View File

@ -0,0 +1,11 @@
#!/usr/bin/env python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
execute_manager(settings)

View File

@ -0,0 +1,49 @@
# Django settings for planet project.
from calibre.www.settings import DEBUG, TEMPLATE_DEBUG, ADMINS, MANAGERS, \
TEMPLATE_LOADERS, TEMPLATE_DIRS, MIDDLEWARE_CLASSES, MEDIA_ROOT, \
MEDIA_URL, ADMIN_MEDIA_PREFIX, TEMPLATE_CONTEXT_PROCESSORS
FORCE_LOWERCASE_TAGS = False
MAX_TAG_LENGTH = 50
if not DEBUG:
MEDIA_URL = 'http://kovid.calibre-ebook.com/site_media/'
ADMIN_MEDIA_PREFIX = 'http://kovid.calibre-ebook.com/admin_media/'
MEDIA_ROOT = '/usr/local/calibre/src/calibre/www/static/'
if DEBUG:
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = '/tmp/kovid.db' # Or path to database file if using sqlite3.
else:
DATABASE_ENGINE = 'mysql'
DATABASE_NAME = 'calibre_kovid'
DATABASE_USER = 'calibre_django'
DATABASE_PASSWORD = open('/var/www/calibre-ebook.com/dbpass').read().strip()
SITE_ID = 1
# Make this unique, and don't share it with anybody.
if DEBUG:
SECRET_KEY = '06mv&t$cobjkijgg#0ndwm5#&90_(tm=oqi1bv-x^vii$*33n5'
else:
SECRET_KEY = open('/var/www/kovid.calibre-ebook.com/django_secret_key').read().strip()
ROOT_URLCONF = 'calibre.www.kovid.urls'
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.comments',
'calibre.www.apps.inlines',
'calibre.www.apps.tagging',
'calibre.www.apps.blog',
)

View File

@ -0,0 +1,24 @@
from django.conf.urls.defaults import patterns, include, handler404, handler500
from django.conf import settings
# Uncomment the next two lines to enable the admin:
#from django.contrib import admin
#admin.autodiscover()
urlpatterns = patterns('',
# (r'^admin/(.*)', admin.site.root),
(r'^comments/', include('django.contrib.comments.urls')),
)
if settings.DEBUG:
urlpatterns += patterns('',
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT}),
)

View File

@ -1,6 +1,6 @@
body {
font-family: sansserif;
background-color: #f6f6f6;
background-color: #f1fff1;
}
img {
@ -12,7 +12,7 @@ img {
border-bottom: 1px solid black;
margin-bottom: 20px;
height: 100px;
background-color: #d6d6d6;
background-color: #A2B964;
overflow: hidden;
}