Remove the calibre planet website source code from the tree. Will now be maintained separately.

This commit is contained in:
Kovid Goyal 2009-08-10 21:25:50 -06:00
parent 1be3d0782a
commit 63439090b4
93 changed files with 0 additions and 5022 deletions

View File

@ -1,10 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

View File

@ -1,10 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

View File

@ -1,85 +0,0 @@
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

@ -1,18 +0,0 @@
===========================================
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

@ -1,17 +0,0 @@
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

@ -1,42 +0,0 @@
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

@ -1,9 +0,0 @@
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

@ -1,80 +0,0 @@
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 tagging.fields import TagField
from calibre.www.apps.blog.managers import PublicManager
import 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

@ -1,13 +0,0 @@
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

@ -1,56 +0,0 @@
{% 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

@ -1,21 +0,0 @@
<!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

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

View File

@ -1,25 +0,0 @@
{% 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

@ -1,20 +0,0 @@
{% 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

@ -1,23 +0,0 @@
{% 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

@ -1,23 +0,0 @@
{% 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

@ -1,21 +0,0 @@
{% 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

@ -1,67 +0,0 @@
{% 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

@ -1,35 +0,0 @@
{% 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

@ -1,37 +0,0 @@
{% 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

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

View File

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

View File

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

View File

@ -1,103 +0,0 @@
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

@ -1,66 +0,0 @@
"""
>>> 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

@ -1,41 +0,0 @@
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

@ -1,160 +0,0 @@
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

@ -1,8 +0,0 @@
# -*- coding: utf-8 -*-
"""
feedjack
Gustavo Picón
__init__.py
"""

View File

@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
"""
feedjack
Gustavo Picón
admin.py
"""
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from calibre.www.apps.feedjack import models
class LinkAdmin(admin.ModelAdmin):
pass
class SiteAdmin(admin.ModelAdmin):
list_display = ('url', 'name')
filter_vertical = ('links',)
class FeedAdmin(admin.ModelAdmin):
list_display = ('name', 'feed_url', 'title', 'last_modified', \
'is_active')
fieldsets = (
(None,
{'fields':('feed_url', 'name', 'shortname', 'is_active')}),
(_('Fields updated automatically by Feedjack'),
{'classes':('collapse',),
'fields':('title', 'tagline', 'link', 'etag', 'last_modified',
'last_checked'),
})
)
search_fields = ['feed_url', 'name', 'title']
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'link', 'author', 'date_modified')
search_fields = ['link', 'title']
date_hierarchy = 'date_modified'
filter_vertical = ('tags',)
class SubscriberAdmin(admin.ModelAdmin):
list_display = ('name', 'site', 'feed')
list_filter = ('site',)
admin.site.register(models.Link, LinkAdmin)
admin.site.register(models.Site, SiteAdmin)
admin.site.register(models.Feed, FeedAdmin)
admin.site.register(models.Post, PostAdmin)
admin.site.register(models.Subscriber, SubscriberAdmin)
#~

View File

@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
"""
feedjack
Gustavo Picón
fjcache.py
"""
import md5
from django.core.cache import cache
from django.conf import settings
T_HOST = 1
T_ITEM = 2
T_META = 3
def str2md5(key):
""" Returns the md5 hash of a string.
"""
ctx = md5.new()
ctx.update(key.encode('utf-8'))
return ctx.hexdigest()
def getkey(stype, site_id=None, key=None):
""" Returns the cache key depending on it's type.
"""
base = '%s.feedjack' % (settings.CACHE_MIDDLEWARE_KEY_PREFIX)
if stype == T_HOST:
return '%s.hostcache' % base
elif stype == T_ITEM:
return '%s.%d.item.%s' % (base, site_id, str2md5(key))
elif stype == T_META:
return '%s.%d.meta' % (base, site_id)
def hostcache_get():
""" Retrieves the hostcache dictionary
"""
return cache.get(getkey(T_HOST))
def hostcache_set(value):
""" Sets the hostcache dictionary
"""
cache.set(getkey(T_HOST), value)
def cache_get(site_id, key):
""" Retrieves cache data from a site.
"""
return cache.get(getkey(T_ITEM, site_id, key))
def cache_set(site, key, data):
""" Sets cache data for a site.
All keys related to a site are stored in a meta key. This key is per-site.
"""
tkey = getkey(T_ITEM, site.id, key)
mkey = getkey(T_META, site.id)
tmp = cache.get(mkey)
longdur = 365*24*60*60
if not tmp:
tmp = [tkey]
cache.set(mkey, [tkey], longdur)
elif tkey not in tmp:
tmp.append(tkey)
cache.set(mkey, tmp, longdur)
cache.set(tkey, data, site.cache_duration)
def cache_delsite(site_id):
""" Removes all cache data from a site.
"""
mkey = getkey(T_META, site_id)
tmp = cache.get(mkey)
if not tmp:
return
for tkey in tmp:
cache.delete(tkey)
cache.delete(mkey)

View File

@ -1,93 +0,0 @@
# -*- coding: utf-8 -*-
"""
feedjack
Gustavo Picón
fjcloud.py
"""
import math
from calibre.www.apps.feedjack import fjlib, fjcache
def getsteps(levels, tagmax):
""" Returns a list with the max number of posts per "tagcloud level"
"""
ntw = levels
if ntw < 2:
ntw = 2
steps = [(stp, 1 + (stp * int(math.ceil(tagmax * 1.0 / ntw - 1))))
for stp in range(ntw)]
# just to be sure~
steps[-1] = (steps[-1][0], tagmax+1)
return steps
def build(site, tagdata):
""" Returns the tag cloud for a list of tags.
"""
tagdata.sort()
# we get the most popular tag to calculate the tags' weigth
tagmax = 0
for tagname, tagcount in tagdata:
if tagcount > tagmax:
tagmax = tagcount
steps = getsteps(site.tagcloud_levels, tagmax)
tags = []
for tagname, tagcount in tagdata:
weight = [twt[0] \
for twt in steps if twt[1] >= tagcount and twt[1] > 0][0]+1
tags.append({'tagname':tagname, 'count':tagcount, 'weight':weight})
return tags
def cloudata(site):
""" Returns a dictionary with all the tag clouds related to a site.
"""
tagdata = fjlib.getquery("""
SELECT feedjack_post.feed_id, feedjack_tag.name, COUNT(*)
FROM feedjack_post, feedjack_subscriber, feedjack_tag,
feedjack_post_tags
WHERE feedjack_post.feed_id=feedjack_subscriber.feed_id AND
feedjack_post_tags.tag_id=feedjack_tag.id AND
feedjack_post_tags.post_id=feedjack_post.id AND
feedjack_subscriber.site_id=%d
GROUP BY feedjack_post.feed_id, feedjack_tag.name
ORDER BY feedjack_post.feed_id, feedjack_tag.name""" % site.id)
tagdict = {}
globaldict = {}
cloudict = {}
for feed_id, tagname, tagcount in tagdata:
if feed_id not in tagdict:
tagdict[feed_id] = []
tagdict[feed_id].append((tagname, tagcount))
try:
globaldict[tagname] += tagcount
except KeyError:
globaldict[tagname] = tagcount
tagdict[0] = globaldict.items()
for key, val in tagdict.items():
cloudict[key] = build(site, val)
return cloudict
def getcloud(site, feed_id=None):
""" Returns the tag cloud for a site or a site's subscriber.
"""
cloudict = fjcache.cache_get(site.id, 'tagclouds')
if not cloudict:
cloudict = cloudata(site)
fjcache.cache_set(site, 'tagclouds', cloudict)
# A subscriber's tag cloud has been requested.
if feed_id:
feed_id = int(feed_id)
if feed_id in cloudict:
return cloudict[feed_id]
return []
# The site tagcloud has been requested.
return cloudict[0]

View File

@ -1,280 +0,0 @@
# -*- coding: utf-8 -*-
"""
feedjack
Gustavo Picón
fjlib.py
"""
from django.db import connection
from django.core.paginator import Paginator, InvalidPage
from django.http import Http404
from django.utils.encoding import smart_unicode
from calibre.www.apps.feedjack import models, fjcache
class PageNotAnInteger(ValueError):
pass
# this is taken from django, it was removed in r8191
class ObjectPaginator(Paginator):
"""
Legacy ObjectPaginator class, for backwards compatibility.
Note that each method on this class that takes page_number expects a
zero-based page number, whereas the new API (Paginator/Page) uses one-based
page numbers.
"""
def __init__(self, query_set, num_per_page, orphans=0):
Paginator.__init__(self, query_set, num_per_page, orphans)
import warnings
warnings.warn("The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.", DeprecationWarning)
# Keep these attributes around for backwards compatibility.
self.query_set = query_set
self.num_per_page = num_per_page
self._hits = self._pages = None
def validate_page_number(self, page_number):
try:
page_number = int(page_number) + 1
except ValueError:
raise PageNotAnInteger
return self.validate_number(page_number)
def get_page(self, page_number):
try:
page_number = int(page_number) + 1
except ValueError:
raise PageNotAnInteger
return self.page(page_number).object_list
def has_next_page(self, page_number):
return page_number < self.pages - 1
def has_previous_page(self, page_number):
return page_number > 0
def first_on_page(self, page_number):
"""
Returns the 1-based index of the first object on the given page,
relative to total objects found (hits).
"""
page_number = self.validate_page_number(page_number)
return (self.num_per_page * (page_number - 1)) + 1
def last_on_page(self, page_number):
"""
Returns the 1-based index of the last object on the given page,
relative to total objects found (hits).
"""
page_number = self.validate_page_number(page_number)
if page_number == self.num_pages:
return self.count
return page_number * self.num_per_page
# The old API called it "hits" instead of "count".
hits = Paginator.count
# The old API called it "pages" instead of "num_pages".
pages = Paginator.num_pages
def sitefeeds(siteobj):
""" Returns the active feeds of a site.
"""
return siteobj.subscriber_set.filter(is_active=True).select_related()
#return [subscriber['feed'] \
# for subscriber \
# in siteobj.subscriber_set.filter(is_active=True).values('feed')]
def getquery(query):
""" Performs a query and get the results.
"""
try:
conn = connection.cursor()
conn.execute(query)
data = conn.fetchall()
conn.close()
except:
data = []
return data
def get_extra_content(site, sfeeds_ids, ctx):
""" Returns extra data useful to the templates.
"""
# get the subscribers' feeds
if sfeeds_ids:
basefeeds = models.Feed.objects.filter(id__in=sfeeds_ids)
try:
ctx['feeds'] = basefeeds.order_by('name').select_related()
except:
ctx['feeds'] = []
# get the last_checked time
try:
ctx['last_modified'] = basefeeds.filter(\
last_checked__isnull=False).order_by(\
'-last_checked').select_related()[0].last_checked.ctime()
except:
ctx['last_modified'] = '??'
else:
ctx['feeds'] = []
ctx['last_modified'] = '??'
ctx['site'] = site
def get_posts_tags(object_list, sfeeds_obj, user_id, tag_name):
""" Adds a qtags property in every post object in a page.
Use "qtags" instead of "tags" in templates to avoid unnecessary DB hits.
"""
tagd = {}
user_obj = None
tag_obj = None
tags = models.Tag.objects.extra(\
select={'post_id':'%s.%s' % (\
connection.ops.quote_name('feedjack_post_tags'), \
connection.ops.quote_name('post_id'))}, \
tables=['feedjack_post_tags'], \
where=[\
'%s.%s=%s.%s' % (\
connection.ops.quote_name('feedjack_tag'), \
connection.ops.quote_name('id'), \
connection.ops.quote_name('feedjack_post_tags'), \
connection.ops.quote_name('tag_id')), \
'%s.%s IN (%s)' % (\
connection.ops.quote_name('feedjack_post_tags'), \
connection.ops.quote_name('post_id'), \
', '.join([str(post.id) for post in object_list]))])
for tag in tags:
if tag.post_id not in tagd:
tagd[tag.post_id] = []
tagd[tag.post_id].append(tag)
if tag_name and tag.name == tag_name:
tag_obj = tag
subd = {}
for sub in sfeeds_obj:
subd[sub.feed.id] = sub
for post in object_list:
if post.id in tagd:
post.qtags = tagd[post.id]
else:
post.qtags = []
post.subscriber = subd[post.feed.id]
if user_id and int(user_id) == post.feed.id:
user_obj = post.subscriber
return user_obj, tag_obj
def getcurrentsite(http_post, path_info, query_string):
""" Returns the site id and the page cache key based on the request.
"""
url = u'http://%s/%s' % (smart_unicode(http_post.rstrip('/')), \
smart_unicode(path_info.lstrip('/')))
pagecachekey = '%s?%s' % (smart_unicode(path_info), \
smart_unicode(query_string))
hostdict = fjcache.hostcache_get()
if not hostdict:
hostdict = {}
if url not in hostdict:
default, ret = None, None
for site in models.Site.objects.all():
if url.startswith(site.url):
ret = site
break
if not default or site.default_site:
default = site
if not ret:
if default:
ret = default
else:
# Somebody is requesting something, but the user didn't create
# a site yet. Creating a default one...
ret = models.Site(name='Default Feedjack Site/Planet', \
url='www.feedjack.org', \
title='Feedjack Site Title', \
description='Feedjack Site Description. ' \
'Please change this in the admin interface.')
ret.save()
hostdict[url] = ret.id
fjcache.hostcache_set(hostdict)
return hostdict[url], pagecachekey
def get_paginator(site, sfeeds_ids, page=0, tag=None, user=None):
""" Returns a paginator object and a requested page from it.
"""
if tag:
try:
localposts = models.Tag.objects.get(name=tag).post_set.filter(\
feed__in=sfeeds_ids)
except:
raise Http404
else:
localposts = models.Post.objects.filter(feed__in=sfeeds_ids)
if user:
try:
localposts = localposts.filter(feed=user)
except:
raise Http404
if site.order_posts_by == 2:
localposts = localposts.order_by('-date_created', '-date_modified')
else:
localposts = localposts.order_by('-date_modified')
paginator = ObjectPaginator(localposts.select_related(), \
site.posts_per_page)
try:
object_list = paginator.get_page(page)
except InvalidPage:
if page == 0:
object_list = []
else:
raise Http404
return (paginator, object_list)
def page_context(request, site, tag=None, user_id=None, sfeeds=None):
""" Returns the context dictionary for a page view.
"""
sfeeds_obj, sfeeds_ids = sfeeds
try:
page = int(request.GET.get('page', 0))
except ValueError:
page = 0
paginator, object_list = get_paginator(site, sfeeds_ids, \
page=page, tag=tag, user=user_id)
if object_list:
# This will hit the DB once per page instead of once for every post in
# a page. To take advantage of this the template designer must call
# the qtags property in every item, instead of the default tags
# property.
user_obj, tag_obj = get_posts_tags(object_list, sfeeds_obj, \
user_id, tag)
else:
user_obj, tag_obj = None, None
ctx = {
'object_list': object_list,
'is_paginated': paginator.pages > 1,
'results_per_page': site.posts_per_page,
'has_next': paginator.has_next_page(page),
'has_previous': paginator.has_previous_page(page),
'page': page + 1,
'next': page + 1,
'previous': page - 1,
'pages': paginator.pages,
'hits' : paginator.hits,
}
get_extra_content(site, sfeeds_ids, ctx)
from calibre.www.apps.feedjack import fjcloud
ctx['tagcloud'] = fjcloud.getcloud(site, user_id)
ctx['user_id'] = user_id
ctx['user'] = user_obj
ctx['tag'] = tag_obj
ctx['subscribers'] = sfeeds_obj
return ctx

View File

@ -1,202 +0,0 @@
# -*- coding: utf-8 -*-
# pylint: disable-msg=W0232, R0903, W0131
"""
feedjack
Gustavo Picón
models.py
"""
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode
from calibre.www.apps.feedjack import fjcache
SITE_ORDERBY_CHOICES = (
(1, _('Date published.')),
(2, _('Date the post was first obtained.'))
)
class Link(models.Model):
name = models.CharField(_('name'), max_length=100, unique=True)
link = models.URLField(_('link'), verify_exists=True)
class Meta:
verbose_name = _('link')
verbose_name_plural = _('links')
class Admin:
pass
def __unicode__(self):
return u'%s (%s)' % (self.name, self.link)
class Site(models.Model):
name = models.CharField(_('name'), max_length=100)
url = models.CharField(_('url'),
max_length=100,
unique=True,
help_text=u'%s: %s, %s' % (smart_unicode(_('Example')),
u'http://www.planetexample.com',
u'http://www.planetexample.com:8000/foo'))
title = models.CharField(_('title'), max_length=200)
description = models.TextField(_('description'))
welcome = models.TextField(_('welcome'), null=True, blank=True)
greets = models.TextField(_('greets'), null=True, blank=True)
default_site = models.BooleanField(_('default site'), default=False)
posts_per_page = models.IntegerField(_('posts per page'), default=20)
order_posts_by = models.IntegerField(_('order posts by'), default=1,
choices=SITE_ORDERBY_CHOICES)
tagcloud_levels = models.IntegerField(_('tagcloud level'), default=5)
show_tagcloud = models.BooleanField(_('show tagcloud'), default=True)
use_internal_cache = models.BooleanField(_('use internal cache'), default=True)
cache_duration = models.IntegerField(_('cache duration'), default=60*60*24,
help_text=_('Duration in seconds of the cached pages and data.') )
links = models.ManyToManyField(Link, verbose_name=_('links'),
null=True, blank=True)
template = models.CharField(_('template'), max_length=100, null=True,
blank=True,
help_text=_('This template must be a directory in your feedjack '
'templates directory. Leave blank to use the default template.') )
class Meta:
verbose_name = _('site')
verbose_name_plural = _('sites')
ordering = ('name',)
def __unicode__(self):
return self.name
def save(self):
if not self.template:
self.template = 'default'
# there must be only ONE default site
defs = Site.objects.filter(default_site=True)
if not defs:
self.default_site = True
elif self.default_site:
for tdef in defs:
if tdef.id != self.id:
tdef.default_site = False
tdef.save()
self.url = self.url.rstrip('/')
fjcache.hostcache_set({})
super(Site, self).save()
class Feed(models.Model):
feed_url = models.URLField(_('feed url'), unique=True)
name = models.CharField(_('name'), max_length=100)
shortname = models.CharField(_('shortname'), max_length=50)
is_active = models.BooleanField(_('is active'), default=True,
help_text=_('If disabled, this feed will not be further updated.') )
title = models.CharField(_('title'), max_length=200, blank=True)
tagline = models.TextField(_('tagline'), blank=True)
link = models.URLField(_('link'), blank=True)
# http://feedparser.org/docs/http-etag.html
etag = models.CharField(_('etag'), max_length=50, blank=True)
last_modified = models.DateTimeField(_('last modified'), null=True, blank=True)
last_checked = models.DateTimeField(_('last checked'), null=True, blank=True)
class Meta:
verbose_name = _('feed')
verbose_name_plural = _('feeds')
ordering = ('name', 'feed_url',)
def __unicode__(self):
return u'%s (%s)' % (self.name, self.feed_url)
def save(self):
super(Feed, self).save()
class Tag(models.Model):
name = models.CharField(_('name'), max_length=50, unique=True)
class Meta:
verbose_name = _('tag')
verbose_name_plural = _('tags')
ordering = ('name',)
def __unicode__(self):
return self.name
def save(self):
super(Tag, self).save()
class Post(models.Model):
feed = models.ForeignKey(Feed, verbose_name=_('feed'), null=False, blank=False)
title = models.CharField(_('title'), max_length=255)
link = models.URLField(_('link'), )
content = models.TextField(_('content'), blank=True)
date_modified = models.DateTimeField(_('date modified'), null=True, blank=True)
guid = models.CharField(_('guid'), max_length=200, db_index=True)
author = models.CharField(_('author'), max_length=50, blank=True)
author_email = models.EmailField(_('author email'), blank=True)
comments = models.URLField(_('comments'), blank=True)
tags = models.ManyToManyField(Tag, verbose_name=_('tags'))
date_created = models.DateField(_('date created'), auto_now_add=True)
class Meta:
verbose_name = _('post')
verbose_name_plural = _('posts')
ordering = ('-date_modified',)
unique_together = (('feed', 'guid'),)
def __unicode__(self):
return self.title
def save(self):
super(Post, self).save()
def get_absolute_url(self):
return self.link
class Subscriber(models.Model):
site = models.ForeignKey(Site, verbose_name=_('site') )
feed = models.ForeignKey(Feed, verbose_name=_('feed') )
name = models.CharField(_('name'), max_length=100, null=True, blank=True,
help_text=_('Keep blank to use the Feed\'s original name.') )
shortname = models.CharField(_('shortname'), max_length=50, null=True,
blank=True,
help_text=_('Keep blank to use the Feed\'s original shortname.') )
is_active = models.BooleanField(_('is active'), default=True,
help_text=_('If disabled, this subscriber will not appear in the site or '
'in the site\'s feed.') )
class Meta:
verbose_name = _('subscriber')
verbose_name_plural = _('subscribers')
ordering = ('site', 'name', 'feed')
unique_together = (('site', 'feed'),)
def __unicode__(self):
return u'%s in %s' % (self.feed, self.site)
def get_cloud(self):
from calibre.www.apps.feedjack import fjcloud
return fjcloud.getcloud(self.site, self.feed.id)
def save(self):
if not self.name:
self.name = self.feed.name
if not self.shortname:
self.shortname = self.feed.shortname
super(Subscriber, self).save()

View File

@ -1,506 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
feedjack
Gustavo Picón
update_feeds.py
"""
import os
import time
import optparse
import datetime
import socket
import traceback
import sys
import feedparser
try:
import threadpool
except ImportError:
threadpool = None
VERSION = '0.9.16'
URL = 'http://www.feedjack.org/'
USER_AGENT = 'Feedjack %s - %s' % (VERSION, URL)
SLOWFEED_WARNING = 10
ENTRY_NEW, ENTRY_UPDATED, ENTRY_SAME, ENTRY_ERR = range(4)
FEED_OK, FEED_SAME, FEED_ERRPARSE, FEED_ERRHTTP, FEED_ERREXC = range(5)
def encode(tstr):
""" Encodes a unicode string in utf-8
"""
if not tstr:
return ''
# this is _not_ pretty, but it works
try:
return tstr.encode('utf-8', "xmlcharrefreplace")
except UnicodeDecodeError:
# it's already UTF8.. sigh
return tstr.decode('utf-8').encode('utf-8')
def prints(tstr):
""" lovely unicode
"""
sys.stdout.write('%s\n' % (tstr.encode(sys.getdefaultencoding(),
'replace')))
sys.stdout.flush()
def mtime(ttime):
""" datetime auxiliar function.
"""
return datetime.datetime.fromtimestamp(time.mktime(ttime))
class ProcessEntry:
def __init__(self, feed, options, entry, postdict, fpf):
self.feed = feed
self.options = options
self.entry = entry
self.postdict = postdict
self.fpf = fpf
def get_tags(self):
""" Returns a list of tag objects from an entry.
"""
from calibre.www.apps.feedjack import models
fcat = []
if self.entry.has_key('tags'):
for tcat in self.entry.tags:
if tcat.label != None:
term = tcat.label
else:
term = tcat.term
qcat = term.strip()
if ',' in qcat or '/' in qcat:
qcat = qcat.replace(',', '/').split('/')
else:
qcat = [qcat]
for zcat in qcat:
tagname = zcat.lower()
while ' ' in tagname:
tagname = tagname.replace(' ', ' ')
tagname = tagname.strip()
if not tagname or tagname == ' ':
continue
if not models.Tag.objects.filter(name=tagname):
cobj = models.Tag(name=tagname)
cobj.save()
fcat.append(models.Tag.objects.get(name=tagname))
return fcat
def get_entry_data(self):
""" Retrieves data from a post and returns it in a tuple.
"""
try:
link = self.entry.link
except AttributeError:
link = self.feed.link
try:
title = self.entry.title
except AttributeError:
title = link
guid = self.entry.get('id', title)
if self.entry.has_key('author_detail'):
author = self.entry.author_detail.get('name', '')
author_email = self.entry.author_detail.get('email', '')
else:
author, author_email = '', ''
if not author:
author = self.entry.get('author', self.entry.get('creator', ''))
if not author_email:
# this should be optional~
author_email = 'nospam@nospam.com'
try:
content = self.entry.content[0].value
except:
content = self.entry.get('summary',
self.entry.get('description', ''))
if self.entry.has_key('modified_parsed'):
date_modified = mtime(self.entry.modified_parsed)
else:
date_modified = None
fcat = self.get_tags()
comments = self.entry.get('comments', '')
return (link, title, guid, author, author_email, content,
date_modified, fcat, comments)
def process(self):
""" Process a post in a feed and saves it in the DB if necessary.
"""
from calibre.www.apps.feedjack import models
(link, title, guid, author, author_email, content, date_modified,
fcat, comments) = self.get_entry_data()
if False and self.options.verbose:
prints(u'[%d] Entry\n' \
u' title: %s\n' \
u' link: %s\n' \
u' guid: %s\n' \
u' author: %s\n' \
u' author_email: %s\n' \
u' tags: %s' % (
self.feed.id,
title, link, guid, author, author_email,
u' '.join(tcat.name for tcat in fcat)))
if guid in self.postdict:
tobj = self.postdict[guid]
if tobj.content != content or (date_modified and
tobj.date_modified != date_modified):
retval = ENTRY_UPDATED
if self.options.verbose:
prints('[%d] Updating existing post: %s' % (
self.feed.id, link))
if not date_modified:
# damn non-standard feeds
date_modified = tobj.date_modified
tobj.title = title
tobj.link = link
tobj.content = content
tobj.guid = guid
tobj.date_modified = date_modified
tobj.author = author
tobj.author_email = author_email
tobj.comments = comments
tobj.tags.clear()
[tobj.tags.add(tcat) for tcat in fcat]
tobj.save()
else:
retval = ENTRY_SAME
if self.options.verbose:
prints('[%d] Post has not changed: %s' % (self.feed.id,
link))
else:
retval = ENTRY_NEW
if self.options.verbose:
prints('[%d] Saving new post: %s' % (self.feed.id, link))
if not date_modified and self.fpf:
# if the feed has no date_modified info, we use the feed
# mtime or the current time
if self.fpf.feed.has_key('modified_parsed'):
date_modified = mtime(self.fpf.feed.modified_parsed)
elif self.fpf.has_key('modified'):
date_modified = mtime(self.fpf.modified)
if not date_modified:
date_modified = datetime.datetime.now()
tobj = models.Post(feed=self.feed, title=title, link=link,
content=content, guid=guid, date_modified=date_modified,
author=author, author_email=author_email,
comments=comments)
tobj.save()
[tobj.tags.add(tcat) for tcat in fcat]
return retval
class ProcessFeed:
def __init__(self, feed, options):
self.feed = feed
self.options = options
self.fpf = None
def process_entry(self, entry, postdict):
""" wrapper for ProcessEntry
"""
entry = ProcessEntry(self.feed, self.options, entry, postdict,
self.fpf)
ret_entry = entry.process()
del entry
return ret_entry
def process(self):
""" Downloads and parses a feed.
"""
from calibre.www.apps.feedjack import models
ret_values = {
ENTRY_NEW:0,
ENTRY_UPDATED:0,
ENTRY_SAME:0,
ENTRY_ERR:0}
prints(u'[%d] Processing feed %s' % (self.feed.id,
self.feed.feed_url))
# we check the etag and the modified time to save bandwith and
# avoid bans
try:
self.fpf = feedparser.parse(self.feed.feed_url,
agent=USER_AGENT,
etag=self.feed.etag)
except:
prints('! ERROR: feed cannot be parsed')
return FEED_ERRPARSE, ret_values
if hasattr(self.fpf, 'status'):
if self.options.verbose:
prints(u'[%d] HTTP status %d: %s' % (self.feed.id,
self.fpf.status,
self.feed.feed_url))
if self.fpf.status == 304:
# this means the feed has not changed
if self.options.verbose:
prints('[%d] Feed has not changed since ' \
'last check: %s' % (self.feed.id,
self.feed.feed_url))
return FEED_SAME, ret_values
if self.fpf.status >= 400:
# http error, ignore
prints('[%d] !HTTP_ERROR! %d: %s' % (self.feed.id,
self.fpf.status,
self.feed.feed_url))
return FEED_ERRHTTP, ret_values
if hasattr(self.fpf, 'bozo') and self.fpf.bozo:
prints('[%d] !BOZO! Feed is not well formed: %s' % (
self.feed.id, self.feed.feed_url))
# the feed has changed (or it is the first time we parse it)
# saving the etag and last_modified fields
self.feed.etag = self.fpf.get('etag', '')
# some times this is None (it never should) *sigh*
if self.feed.etag is None:
self.feed.etag = ''
try:
self.feed.last_modified = mtime(self.fpf.modified)
except:
pass
self.feed.title = self.fpf.feed.get('title', '')[0:254]
self.feed.tagline = self.fpf.feed.get('tagline', '')
self.feed.link = self.fpf.feed.get('link', '')
self.feed.last_checked = datetime.datetime.now()
if False and self.options.verbose:
prints(u'[%d] Feed info for: %s\n' \
u' title %s\n' \
u' tagline %s\n' \
u' link %s\n' \
u' last_checked %s' % (
self.feed.id, self.feed.feed_url, self.feed.title,
self.feed.tagline, self.feed.link, self.feed.last_checked))
guids = []
for entry in self.fpf.entries:
if entry.get('id', ''):
guids.append(entry.get('id', ''))
elif entry.title:
guids.append(entry.title)
elif entry.link:
guids.append(entry.link)
self.feed.save()
if guids:
postdict = dict([(post.guid, post)
for post in models.Post.objects.filter(
feed=self.feed.id).filter(guid__in=guids)])
else:
postdict = {}
for entry in self.fpf.entries:
try:
ret_entry = self.process_entry(entry, postdict)
except:
(etype, eobj, etb) = sys.exc_info()
print '[%d] ! -------------------------' % (self.feed.id,)
print traceback.format_exception(etype, eobj, etb)
traceback.print_exception(etype, eobj, etb)
print '[%d] ! -------------------------' % (self.feed.id,)
ret_entry = ENTRY_ERR
ret_values[ret_entry] += 1
self.feed.save()
return FEED_OK, ret_values
class Dispatcher:
def __init__(self, options, num_threads):
self.options = options
self.entry_stats = {
ENTRY_NEW:0,
ENTRY_UPDATED:0,
ENTRY_SAME:0,
ENTRY_ERR:0}
self.feed_stats = {
FEED_OK:0,
FEED_SAME:0,
FEED_ERRPARSE:0,
FEED_ERRHTTP:0,
FEED_ERREXC:0}
self.entry_trans = {
ENTRY_NEW:'new',
ENTRY_UPDATED:'updated',
ENTRY_SAME:'same',
ENTRY_ERR:'error'}
self.feed_trans = {
FEED_OK:'ok',
FEED_SAME:'unchanged',
FEED_ERRPARSE:'cant_parse',
FEED_ERRHTTP:'http_error',
FEED_ERREXC:'exception'}
self.entry_keys = sorted(self.entry_trans.keys())
self.feed_keys = sorted(self.feed_trans.keys())
if threadpool:
self.tpool = threadpool.ThreadPool(num_threads)
else:
self.tpool = None
self.time_start = datetime.datetime.now()
def add_job(self, feed):
""" adds a feed processing job to the pool
"""
if self.tpool:
req = threadpool.WorkRequest(self.process_feed_wrapper,
(feed,))
self.tpool.putRequest(req)
else:
# no threadpool module, just run the job
self.process_feed_wrapper(feed)
def process_feed_wrapper(self, feed):
""" wrapper for ProcessFeed
"""
start_time = datetime.datetime.now()
try:
pfeed = ProcessFeed(feed, self.options)
ret_feed, ret_entries = pfeed.process()
del pfeed
except:
(etype, eobj, etb) = sys.exc_info()
print '[%d] ! -------------------------' % (feed.id,)
print traceback.format_exception(etype, eobj, etb)
traceback.print_exception(etype, eobj, etb)
print '[%d] ! -------------------------' % (feed.id,)
ret_feed = FEED_ERREXC
ret_entries = {}
delta = datetime.datetime.now() - start_time
if delta.seconds > SLOWFEED_WARNING:
comment = u' (SLOW FEED!)'
else:
comment = u''
prints(u'[%d] Processed %s in %s [%s] [%s]%s' % (
feed.id, feed.feed_url, unicode(delta),
self.feed_trans[ret_feed],
u' '.join(u'%s=%d' % (self.entry_trans[key],
ret_entries[key]) for key in self.entry_keys),
comment))
self.feed_stats[ret_feed] += 1
for key, val in ret_entries.items():
self.entry_stats[key] += val
return ret_feed, ret_entries
def poll(self):
""" polls the active threads
"""
if not self.tpool:
# no thread pool, nothing to poll
return
while True:
try:
time.sleep(0.2)
self.tpool.poll()
except KeyboardInterrupt:
prints('! Cancelled by user')
break
except threadpool.NoResultsPending:
prints(u'* DONE in %s\n* Feeds: %s\n* Entries: %s' % (
unicode(datetime.datetime.now() - self.time_start),
u' '.join(u'%s=%d' % (self.feed_trans[key],
self.feed_stats[key])
for key in self.feed_keys),
u' '.join(u'%s=%d' % (self.entry_trans[key],
self.entry_stats[key])
for key in self.entry_keys)
))
break
def main():
""" Main function. Nothing to see here. Move along.
"""
parser = optparse.OptionParser(usage='%prog [options]',
version=USER_AGENT)
parser.add_option('--settings',
help='Python path to settings module. If this isn\'t provided, ' \
'the DJANGO_SETTINGS_MODULE enviroment variable will be used.')
parser.add_option('-f', '--feed', action='append', type='int',
help='A feed id to be updated. This option can be given multiple ' \
'times to update several feeds at the same time ' \
'(-f 1 -f 4 -f 7).')
parser.add_option('-s', '--site', type='int',
help='A site id to update.')
parser.add_option('-v', '--verbose', action='store_true',
dest='verbose', default=False, help='Verbose output.')
parser.add_option('-t', '--timeout', type='int', default=10,
help='Wait timeout in seconds when connecting to feeds.')
parser.add_option('-w', '--workerthreads', type='int', default=10,
help='Worker threads that will fetch feeds in parallel.')
options = parser.parse_args()[0]
if options.settings:
os.environ["DJANGO_SETTINGS_MODULE"] = options.settings
from calibre.www.apps.feedjack import models, fjcache
# settting socket timeout (default= 10 seconds)
socket.setdefaulttimeout(options.timeout)
# our job dispatcher
disp = Dispatcher(options, options.workerthreads)
prints('* BEGIN: %s' % (unicode(datetime.datetime.now()),))
if options.feed:
feeds = models.Feed.objects.filter(id__in=options.feed)
known_ids = []
for feed in feeds:
known_ids.append(feed.id)
disp.add_job(feed)
for feed in options.feed:
if feed not in known_ids:
prints('! Unknown feed id: %d' % (feed,))
elif options.site:
try:
site = models.Site.objects.get(pk=int(options.site))
except models.Site.DoesNotExist:
site = None
prints('! Unknown site id: %d' % (options.site,))
if site:
feeds = [sub.feed for sub in site.subscriber_set.all()]
for feed in feeds:
disp.add_job(feed)
else:
for feed in models.Feed.objects.filter(is_active=True):
disp.add_job(feed)
disp.poll()
# removing the cached data in all sites, this will only work with the
# memcached, db and file backends
[fjcache.cache_delsite(site.id) for site in models.Site.objects.all()]
if threadpool:
tcom = u'%d threads' % (options.workerthreads,)
else:
tcom = u'no threadpool module available, no parallel fetching'
prints('* END: %s (%s)' % (unicode(datetime.datetime.now()), tcom))
if __name__ == '__main__':
main()

View File

@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
"""
feedjack
Gustavo Picón
urls.py
"""
from django.conf.urls.defaults import patterns
from django.views.generic.simple import redirect_to
from calibre.www.apps.feedjack import views
urlpatterns = patterns('',
(r'^rss20.xml$', redirect_to,
{'url':'/feed/rss/'}),
(r'^feed/$', redirect_to,
{'url':'/feed/atom/'}),
(r'^feed/rss/$', views.rssfeed),
(r'^feed/atom/$', views.atomfeed),
(r'^feed/user/(?P<user>\d+)/tag/(?P<tag>.*)/$', redirect_to,
{'url':'/feed/atom/user/%(user)s/tag/%(tag)s/'}),
(r'^feed/user/(?P<user>\d+)/$', redirect_to,
{'url':'/feed/atom/user/%(user)s/'}),
(r'^feed/tag/(?P<tag>.*)/$', redirect_to,
{'url':'/feed/atom/tag/%(tag)s/'}),
(r'^feed/atom/user/(?P<user>\d+)/tag/(?P<tag>.*)/$', views.atomfeed),
(r'^feed/atom/user/(?P<user>\d+)/$', views.atomfeed),
(r'^feed/atom/tag/(?P<tag>.*)/$', views.atomfeed),
(r'^feed/rss/user/(?P<user>\d+)/tag/(?P<tag>.*)/$', views.rssfeed),
(r'^feed/rss/user/(?P<user>\d+)/$', views.rssfeed),
(r'^feed/rss/tag/(?P<tag>.*)/$', views.rssfeed),
(r'^user/(?P<user>\d+)/tag/(?P<tag>.*)/$', views.mainview),
(r'^user/(?P<user>\d+)/$', views.mainview),
(r'^tag/(?P<tag>.*)/$', views.mainview),
(r'^opml/$', views.opml),
(r'^foaf/$', views.foaf),
(r'^$', views.mainview),
)
#~

View File

@ -1,152 +0,0 @@
# -*- coding: utf-8 -*-
"""
feedjack
Gustavo Picón
views.py
"""
from django.utils import feedgenerator
from django.shortcuts import render_to_response
from django.http import HttpResponse
from django.utils.cache import patch_vary_headers
from django.template import RequestContext, loader
from calibre.www.apps.feedjack import models, fjlib, fjcache
def initview(request):
""" Retrieves the basic data needed by all feeds (host, feeds, etc)
Returns a tuple of:
1. A valid cached response or None
2. The current site object
3. The cache key
4. The subscribers for the site (objects)
5. The feeds for the site (ids)
"""
site_id, cachekey = fjlib.getcurrentsite(request.META.get('HTTP_HOST',
'planet.calibre-ebook.com'), \
request.META.get('REQUEST_URI', request.META.get('PATH_INFO', '/')), \
request.META['QUERY_STRING'])
response = fjcache.cache_get(site_id, cachekey)
if response:
return response, None, cachekey, [], []
site = models.Site.objects.get(pk=site_id)
sfeeds_obj = fjlib.sitefeeds(site)
sfeeds_ids = [subscriber.feed.id for subscriber in sfeeds_obj]
return None, site, cachekey, sfeeds_obj, sfeeds_ids
def blogroll(request, btype):
""" View that handles the generation of blogrolls.
"""
response, site, cachekey, sfeeds_obj, sfeeds_ids = initview(request)
if response:
return response
# for some reason this isn't working:
#
#response = render_to_response('feedjack/%s.xml' % btype, \
# fjlib.get_extra_content(site, sfeeds_ids))
#response.mimetype = 'text/xml; charset=utf-8'
#
# so we must use this:
template = loader.get_template('feedjack/%s.xml' % btype)
ctx = {}
fjlib.get_extra_content(site, sfeeds_ids, ctx)
ctx = RequestContext(request, ctx)
response = HttpResponse(template.render(ctx) , \
mimetype='text/xml; charset=utf-8')
patch_vary_headers(response, ['Host'])
fjcache.cache_set(site, cachekey, response)
return response
def foaf(request):
""" View that handles the generation of the FOAF blogroll.
"""
return blogroll(request, 'foaf')
def opml(request):
""" View that handles the generation of the OPML blogroll.
"""
return blogroll(request, 'opml')
def buildfeed(request, feedclass, tag=None, user=None):
""" View that handles the feeds.
"""
response, site, cachekey, sfeeds_obj, sfeeds_ids = initview(request)
if response:
return response
object_list = fjlib.get_paginator(site, sfeeds_ids, page=0, tag=tag, \
user=user)[1]
feed = feedclass(\
title=site.title,
link=site.url,
description=site.description,
feed_url='%s/%s' % (site.url, '/feed/rss/'))
for post in object_list:
feed.add_item( \
title = '%s: %s' % (post.feed.name, post.title), \
link = post.link, \
description = post.content, \
author_email = post.author_email, \
author_name = post.author, \
pubdate = post.date_modified, \
unique_id = post.link, \
categories = [tag.name for tag in post.tags.all()])
response = HttpResponse(mimetype=feed.mime_type)
# per host caching
patch_vary_headers(response, ['Host'])
feed.write(response, 'utf-8')
if site.use_internal_cache:
fjcache.cache_set(site, cachekey, response)
return response
def rssfeed(request, tag=None, user=None):
""" Generates the RSS2 feed.
"""
return buildfeed(request, feedgenerator.Rss201rev2Feed, tag, user)
def atomfeed(request, tag=None, user=None):
""" Generates the Atom 1.0 feed.
"""
return buildfeed(request, feedgenerator.Atom1Feed, tag, user)
def mainview(request, tag=None, user=None):
""" View that handles all page requests.
"""
response, site, cachekey, sfeeds_obj, sfeeds_ids = initview(request)
if response:
return response
ctx = fjlib.page_context(request, site, tag, user, (sfeeds_obj, \
sfeeds_ids))
response = render_to_response('feedjack/%s/post_list.html' % \
(site.template), ctx, context_instance=RequestContext(request))
# per host caching, in case the cache middleware is enabled
patch_vary_headers(response, ['Host'])
if site.use_internal_cache:
fjcache.cache_set(site, cachekey, response)
return response
#~

View File

@ -1,10 +0,0 @@
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

@ -1,27 +0,0 @@
==============================================
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

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

View File

@ -1,17 +0,0 @@
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

@ -1,92 +0,0 @@
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

@ -1,81 +0,0 @@
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

@ -1,11 +0,0 @@
#!/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

@ -1,49 +0,0 @@
# 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',
'django.contrib.markup',
'calibre.www.apps.inlines',
'tagging',
'calibre.www.apps.blog',
)

View File

@ -1,26 +0,0 @@
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')),
(r'', include('calibre.www.apps.blog.urls')),
)
if settings.DEBUG:
urlpatterns += patterns('',
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT}),
)

View File

@ -1,12 +0,0 @@
Test
=====
Calibre planet can be run either in development mode or deployment mode. For testing,
it should be run in development mode as follows:
* Install django
* ``cd test && ./test``
* Planet is at `http://localhost:8000`

View File

@ -1,11 +0,0 @@
#!/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

@ -1,42 +0,0 @@
# 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
if not DEBUG:
MEDIA_URL = 'http://planet.calibre-ebook.com/site_media/'
ADMIN_MEDIA_PREFIX = 'http://planet.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/planet.db' # Or path to database file if using sqlite3.
else:
DATABASE_ENGINE = 'mysql'
DATABASE_NAME = 'calibre_planet'
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/planet.calibre-ebook.com/django_secret_key').read().strip()
ROOT_URLCONF = 'calibre.www.planet.urls'
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'calibre.www.apps.feedjack',
)

View File

@ -1,4 +0,0 @@
#!/bin/sh
cp planet.db /tmp
cd ..
python manage.py runserver

View File

@ -1,24 +0,0 @@
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'', include('calibre.www.apps.feedjack.urls')),
)
if settings.DEBUG:
urlpatterns += patterns('',
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT}),
)

View File

@ -1,4 +0,0 @@
#!/bin/sh
ssh divok "cd /usr/local/calibre && bzr up"
ssh divok /etc/init.d/apache2 graceful

View File

@ -1,86 +0,0 @@
# Django settings
# Import base settings from here into the site specific settings files
import socket, os
DEBUG = socket.gethostname() != 'divok'
TEMPLATE_DEBUG = DEBUG
ADMINS = (
('Kovid Goyal', 'kovid@kovidgoyal.net'),
)
MANAGERS = ADMINS
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
if DEBUG:
MEDIA_ROOT = os.path.join(os.path.dirname(__file__), 'static/')
else:
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
if DEBUG:
MEDIA_URL = 'http://127.0.0.1:8000/site_media/'
else:
MEDIA_URL = 'http://planet.calibre-ebook.com/site_media/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/Los_Angeles'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
# 'django.template.loaders.eggs.load_template_source',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
ROOT_URLCONF = 'planet.urls'
if DEBUG:
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(os.path.dirname(__file__), 'templates'),
)
else:
TEMPLATE_DIRS = (
'/usr/local/calibre/src/calibre/www/templates',
)
TEMPLATE_CONTEXT_PROCESSORS = (
"django.core.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media"
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,40 +0,0 @@
body {
font-family: sansserif;
background-color: #f1fff1;
}
img {
border:0 none;
}
#_header {
width: 100%;
border-bottom: 1px solid black;
margin-bottom: 20px;
height: 100px;
background-color: #A2B964;
overflow: hidden;
}
#_header > h1 {
margin: 0px; padding: 0px;
font-size: 60px;
font-family: sansserif;
font-weight: normal;
}
#_logo {
vertical-align: middle;
}
#_footer {
font-size: small;
text-align: right;
width: 100%;
border-top: 1px solid black;
font-style: italic;
padding-top: 10px;
padding-bottom: 5px;
}
#_footer img { vertical-align: middle; }

View File

@ -1,115 +0,0 @@
/* codebox header */
.wp_codebox_msgheader {
width: 100%;
border: 1px solid #DEDEB8;
border-bottom: 0;
font-weight: bold;
background: #F3F8D7 url(../images/arrow-square.gif) no-repeat right 5px;
color: #000000;
}
.wp_codebox_msgheader.active {
background-position: right -51px;
}
.wp_codebox_msgheader .right {
float: right;
text-align: right;
padding: 5px;
margin-right: 20px;
}
.wp_codebox_msgheader .right a {
font: 12px Arial, Tahoma !important;
font: 11px Arial, Tahoma;
}
.wp_codebox_msgheader .left,.wp_codebox_msgheader .left2 {
float: left;
/* background-color:#FFFFFF;
border:1px solid #DCDCDC;
padding:8px 0px 2px 8px;*/
font-family: tahoma, arial, verdana;
/* display: block;
width:50%;
margin: 0 auto;*/
padding: 5px 5px 5px 20px;
margin-left: 5px;
}
.wp_codebox_msgheader .left {
background: url(../images/view_code.png) no-repeat left;
}
.wp_codebox_msgheader .left2 {
background: url(../images/down.gif) no-repeat left;
}
.wp_codebox_msgheader .left a { /* margin:0px 5px 0px 10px;*/
font-weight: bold;
}
.wp_codebox_msgheader .left2 a { /* margin:0px 5px 0px 5px;*/
font-weight: bold;
}
.wp_codebox_msgheader .codebox_clear {
clear: both;
}
/* codebox */
.wp_codebox {
color: #100;
background-color: #f9f9f9;
border: 1px solid silver;
margin: 0 0 1.5em 0;
overflow: auto;
}
/* IE FIX */
.wp_codebox {
overflow-x: auto;
overflow-y: hidden;
padding-bottom: expression(this.scrollWidth > this.offsetWidth ? 15 : 0)
;
width: 100%;
}
.wp_codebox table {
border-collapse: collapse;
border: none;
margin: 0px;
}
.wp_codebox div,.wp_codebox td {
vertical-align: top;
padding: 2px 4px;
}
.wp_codebox td.line_numbers {
text-align: right;
background-color: #def;
color: #666;
overflow: visible;
border-right: 1px solid #B0BEC7;
table-layout: auto;
width: 15px;
}
/* potential overrides for other styles */
.wp_codebox pre {
border: none;
background: none;
margin: 0;
padding: 0;
width: auto;
float: none;
clear: none;
overflow: visible;
font-size: 12px;
line-height: 1.333;
}
.line_numbers pre {
padding-left: 10px;
}

View File

@ -1,313 +0,0 @@
/*
* Feedjack LostWoods theme
**************************
* Simple and green (where's the brown? -brown doesn't count)
*
* Copyright Diego Escalante Urrelo <diegoe@gnome.org>
*
*/
body {
font-size: 0.8em;
font-family: verdana;
margin: 0;
}
div {
/*border: 1px solid blue;
padding: 5px;
margin: 5px;*/
}
/*
* Structure
*/
#logo {
padding-top: 3px;
padding-left: 1em;
}
#tags {
overflow: auto;
padding: 5px;
text-align: right;
}
#paginate {
margin-bottom: 15px;
margin-left: 0px;
margin-top: 5px;
padding-left: 0;
text-align: left;
vertical-align: middle;
float: left;
width: 60%;
color: black;
}
#buttons {
text-align: right;
float: right;
vertical-align: middle;
color: #aaa;
width: 40%;
line-height: 1.7em;
padding:0;
padding-bottom: 10px;
}
#usertags {
clear: both;
margin: 5px;
padding: 5px;
width: 75%;
text-align: center;
}
#post_list {
clear : both;
width: 75%;
}
#sidebar {
width: 20%;
position: absolute;
right:0;
top: 170px;
border-left: 10px outset #6E9C60;
padding-left: 10px;
padding-right: 10px;
}
div.date {
font-size: x-large;
text-align: center;
font-style: italic;
padding: 5px;
color: black;
border: 2px solid #6E9C60;
border-right: 0;
border-left: 0;
margin-bottom: 10px;
}
/*
* Post structure
*/
div.post {
overflow: auto;
margin-bottom: 50px;
padding-bottom: 30px;
border-bottom: 12px solid #6E9C60;
border-right: 1px inset #6E9C60;
padding-right: 10px;
}
div.avatar {
float: right;
width: 15%;
text-align: center;
}
div.post-title {
text-align: left;
font-size: 180%;
font-family: trebuchet ms;
font-weight: bold;
padding: 5px;
width: 75%;
}
div.post-content {
width: 72%;
overflow: auto;
text-align: justify;
font-size: 90%;
line-height: 1.8em;
padding-left: 4em;
padding-right: 4em;
border-right: 1px dotted #ccc;
}
div.post-content li {
line-height: 130%;
margin-bottom: 0.6em;
}
div.post-content table{
border: 0;
margin-left: 50px;
}
div.post-content td {
border: 2px solid #ccc;
}
div.post-meta {
color: #666;
margin-top: 20px;
border-top: 1px solid #ccc;
width: 100%;
text-align: right;
}
div.tags {
margin-top: 10px;
}
/*
* Elements
*/
blockquote {
color: #777;
margin: 15px 30px 0px 10px;
padding: 20px;
border: 1px solid #ddd;
border-left: 7px solid #ddd;
}
a:link {
color: #4C6B46;
}
a:hover {
color: #33408A;
}
h1 a:link {
text-decoration: none;
color: inherit;
}
#buttons img {
vertical-align: middle;
}
#head h1 {
font-style: italic;
font-size: xx-large;
border-bottom: 3px solid #6E9C60;
margin-bottom: 5px;
margin-top: 10px;
}
#head a:link, a:visited, #head a:active {
color: inherit;
}
.love_feedjack {
font-size: 145%;
font-weight: bold;
font-style: italic;
}
.cloud_1 {
font-size: 50%;
}
.cloud_2 {
font-size: 100%;
}
.cloud_3 {
font-size: 120%;
font-weight: bold;
}
.cloud_4 {
font-size: 140%;
font-weight: bold;
}
.cloud_5 {
font-size: 160%;
font-weight: bold;
}
#paginate ul {
margin:0;
padding:0;
}
#paginate li {
display: inline;
margin: 2px;
padding: 10px;
background-color: #fbfbfb;
border: 2px solid #ddd;
line-height: 1.7em;
margin-left: 3px;
margin-right: 3px;
text-align: center;
vertical-align: middle;
}
#paginate li.tagname {
font-weight: bold;
font-size: 140%;
}
#paginate a:link {
color: #4C6B46;
}
#paginate a { text-decoration: none; }
img {
border: 0;
}
#sidebar ul {
list-style-type: none;
padding: 0;
margin: 0;
}
#sidebar li {
display: block;
clear: both;
line-height: 25px;
}
#sidebar a.nombre {
display: inline;
color: #33408A;
vertical-align: middle;
margin-left: 2px;
margin-right: 2px;
}
#sidebar img.face {
vertical-align: middle;
margin-right: 5px;
}
#sidebar h4 {
font-style: italic;
border-bottom: 1px solid #ccc;
}
#sidebar h4 + p {
text-align: justify;
}
#tags ul, #usertags ul {
background-color: #fbfbfb;
padding: 5px;
border: 2px solid #ddd;
margin:0;
text-align: center;
}
#tags li, #usertags li {
display: inline;
margin: 0;
line-height: 1.7em;
margin-left: 3px;
margin-right: 3px;
}
#tags a:link, #usertags a:link {
color: #4C6B46;
}
span.name {
color: #333;
}
span.nick {
color: #555;
}
span.url a {
color: #bbb;
}
span.url a:hover {
color: #777;
}
div.tags ul {
background-color: #fbfbfb;
padding: 5px;
border: 2px solid #ddd;
margin:0;
text-align: center;
}
div.tags li {
display: inline;
margin: 0;
line-height: 1.7em;
margin-left: 3px;
margin-right: 3px;
}
#planet_donate
{
text-align: center;
border-top: 1px solid gray;
border-bottom: 1px solid gray;
padding-top: 5px; padding-bottom: 5px;
}
#planet_ads {
text-align: center;
}

View File

@ -1,12 +0,0 @@
{% extends "base.html" %}
{% block title %}Page not found{% endblock %}
{% block header_text %}Page not found{% endblock %}
{% block content %}
<p>Sorry, but the requested page could not be found.</p>
{% endblock %}
{% block footer_text %} {% endblock %}

View File

@ -1,16 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>Page unavailable</title>
</head>
<body>
<h1>Page unavailable</h1>
<p>Sorry, but the requested page is unavailable due to a
server hiccup.</p>
<p>Our engineers have been notified, so check back later.</p>
</body>
</html>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<!--
Base template that defines navigation/sidebars etc.
-->
<head>
<title>{% block title %}calibre - E-book management{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}/styles/base.css" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
{% block extra_header %} {% endblock %}
</head>
<body>
<div id="_header">
<h1>
<img id="_logo" alt="calibre" src="{{ MEDIA_URL }}/img/logo.png" />
{% block header_text %}e-book management{% endblock %}
</h1>
</div>
<div id="_content">
{% block content %}Hello world{% endblock %}
</div>
<div id="_footer">
{% block footer_text %}
Created by Kovid Goyal.
Powered by <a href="http://www.djangoproject.com">
<img alt="Django" src="{{ MEDIA_URL }}/img/button-django.png"/>
</a>&nbsp;&nbsp;
{% endblock %}
</div>
</body>
</html>

View File

@ -1,190 +0,0 @@
{% extends "base.html" %}
{% block title%}Calibre Planet{% endblock %}
{% block header_text %}Planet{% endblock %}
{% block extra_header %}
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}/styles/planet.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}/styles/codebox.css" />
{% endblock %}
{% block content %}
<div id="paginate">
<ul>
{% if has_previous %}
<li><a href="?page={{ previous }}">&lt;&lt;</a></li>
{% endif %}
<li>
Page {{ page }} of {{ pages }} ({{ hits }} posts)
</li>
{% if has_next %}
<li><a href="?page={{ next }}">&gt;&gt;</a></li>
{% endif %}
{% if user %}
<li class="username"><a href="{{ user.feed.link }}">{{ user.name }}</a>talks about »</li>
{% endif %}
{% if tag %}
<li class="tagname">{{ tag.name }}</li>
{% endif %}
</ul>
</div> <!-- end paginate -->
<div id="buttons">
<a href="{{ site.url }}/feed/rss/" title="RSS 2.0 feed"><img src="{{ MEDIA_URL }}/img/button-rss.png"/></a> &bull;
<a href="{{ site.url }}/feed/atom/" title="Atom 1.0 feed"><img src="{{ MEDIA_URL }}/img/button-atom.png"/></a> &bull;
<a href="{{ site.url }}/opml/" title="OPML"><img src="{{ MEDIA_URL }}/img/button-opml.png"/></a> &bull;
<a href="{{ site.url }}/foaf/" title="FOAF"><img src="{{ MEDIA_URL }}/img/button-foaf.png"/></a>
</div> <!-- end buttons -->
<div id="post_list">
{% for item in object_list %}
{% ifchanged %}
<div class="date">{{ item.date_modified|date:"F j, Y" }}</div>
{% endifchanged %}
<div class="post">
{% ifchanged %}
<!-- {{ item.date_modified|date:"F j, Y" }} -->
<div class="avatar">
<a href="{{ item.feed.link }}">
<img
src="{{ MEDIA_URL }}/img/faces/{{ item.subscriber.shortname}}.png" alt="" />
<div class="url">
{{ item.feed.name|safe }}
</div>
</a>
</div>
{% endifchanged %}
{% if item.title %}
<div class="post-title">» {{ item.title }}</div>
{% else %}
<div class="post-title">» {{ item.subscriber.name }}</div>
{% endif %}
<div class="post-content">
<p>{{ item.content|safe }}</p>
<div class="post-meta">
<a href="{{ item.link }}">
{% if item.author %}by {{ item.author }} at{% endif %}
{{ item.date_modified|date:"g:i A" }}</a>
{% for tag in item.qtags %}
{% if forloop.first %}under{% endif %}
<a href="{{ site.url }}/tag/{{ tag.name }}">{{ tag.name }}</a>
{% if not forloop.last %}, {% endif %}
{% endfor %}
{% if item.comments %}
<a href="{{ item.comments }}">(Comments)</a>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<div id="sidebar">
<h4>{{ site.name }}</h4>
<p style="text-align:left">
Planet Calibre is a window into the world, work and lives of Calibre developers and contributors.
</p>
<p style="text-align:left">
If you have a question or would like your blog added to the planet, please email
<a href="mailto:kovid@kovidgoyal.net">Kovid Goyal</a>.
</p>
<div id="planet_donate">
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="3028915" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="Donate to support calibre development" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
<div>Donate to support the development of calibre.</div>
</form>
</div>
<div id="tags">
<ul id="cloud">
{% for tag in tagcloud %}
<li><a
{% if user_id %}
href="{{ site.url }}/user/{{ user_id }}/tag/{{ tag.tagname|urlencode }}/"
{% else %}
href="{{ site.url }}/tag/{{ tag.tagname|urlencode }}/"
{% endif %}
title="{{ tag.count }} posts"
class="cloud_{{ tag.weight }}">{{ tag.tagname }}</a></li>
{% endfor %}
</ul>
</div>
<div id='planet_ads' style="z-index:0">
<script type="text/javascript"><!--
google_ad_client = "pub-2595272032872519";
/* Calibre Planet */
google_ad_slot = "6940385240";
google_ad_width = 120;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>
<h4>Last update</h4>
<b>{{ last_modified }}</b>
<h4>People</h4>
<ul class="suscriptores">
{% for feed in subscribers %}
<li>
<a href="{{ feed.feed.feed_url }}"
{% if feed.feed.last_modified %}
title="feed (last modified: {{ feed.feed.last_modified }})"
{% else %}
title="feed"
{% endif %}
>
<img src="{{ MEDIA_URL }}/img/feed.png" alt="feed"></a>
<a class="nombre" href="{{ site.url }}/user/{{ feed.feed.id }}"
title="{{ feed.feed.title }}">{{ feed.name }}</a></li>
{% endfor %}
</ul>
</div>
<div id="paginate">
<ul>
{% if has_previous %}
<li><a href="?page={{ previous }}">&lt;&lt;</a></li>
{% endif %}
<li>
Page {{ page }} of {{ pages }} (
{{ hits }} posts
)
</li>
{% if has_next %}
<li><a href="?page={{ next }}">&gt;&gt;</a></li>
{% endif %}
{% if user %}
<li class="username"><a href="{{ user.feed.link }}">{{ user.name }}</a></li>
{% endif %}
{% if tag %}
<li class="tagname">{{ tag.name }}</li>
{% endif %}
</ul>
</div>
<div style="height:50px">&nbsp;</div>
{% endblock %}

View File

@ -1,34 +0,0 @@
<?xml version="1.0"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:rss="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
>
<!-- based on http://www-128.ibm.com/developerworks/xml/library/x-pblog/ -->
<foaf:Group>
<foaf:name>{{ site.title }}</foaf:name>
<foaf:homepage>{{ site.url }}</foaf:homepage>
<rdfs:seeAlso rdf:resource="{{ site.url }}/foaf/" />
{% for feed in feeds %}
<foaf:member>
<foaf:Person>
<foaf:name>{{ feed.name }}</foaf:name>
<foaf:weblog>
<foaf:Document rdf:about="{{ feed.link }}">
<dc:title>{{ feed.title }}</dc:title>
<rdfs:seeAlso>
<rss:channel rdf:about="{{ feed.feed_url }}" />
</rdfs:seeAlso>
</foaf:Document>
</foaf:weblog>
</foaf:Person>
</foaf:member>
{% endfor %}
</foaf:Group>
</rdf:RDF>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf8"?>
<opml version="1.1">
<head>
<title>{{ site.title }}</title>
</head>
<body>
{% for feed in feeds %}
<outline type="rss" text="{{ feed.name }}" description="{{ feed.title }}" htmlUrl="{{ feed.link }}" xmlUrl="{{ feed.feed_url }}"/>
{% endfor %}
</body>
</opml>

View File

@ -1,30 +0,0 @@
from django.utils.translation import ugettext as _
from 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

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

View File

@ -1,107 +0,0 @@
"""
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 tagging import settings
from tagging.models import Tag
from 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 tagging import forms
defaults = {'form_class': forms.TagField}
defaults.update(kwargs)
return super(TagField, self).formfield(**defaults)

View File

@ -1,40 +0,0 @@
"""
Tagging components for Django's form library.
"""
from django import forms
from django.utils.translation import ugettext as _
from tagging import settings
from tagging.models import Tag
from 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

@ -1,40 +0,0 @@
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

@ -1,68 +0,0 @@
"""
Custom managers for Django models registered with the tagging
application.
"""
from django.contrib.contenttypes.models import ContentType
from django.db import models
from 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

@ -1,480 +0,0 @@
"""
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 tagging import settings
from tagging.utils import calculate_cloud, get_tag_list, get_queryset_and_model, parse_tag_input
from 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

@ -1,13 +0,0 @@
"""
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

@ -1,231 +0,0 @@
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

@ -1,263 +0,0 @@
"""
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 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 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

@ -1,52 +0,0 @@
"""
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 tagging.models import Tag, TaggedItem
from 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)