Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

first commit

  • Loading branch information...
commit 47c69970ed9bd20f59c743abe5e2a221e3b0e4c6 0 parents
ashok raavi authored
Showing with 2,763 additions and 0 deletions.
  1. 0  example/__init__.py
  2. +28 −0 example/blogango/__init__.py
  3. +16 −0 example/blogango/admin.py
  4. +372 −0 example/blogango/akismet.py
  5. 0  example/blogango/conf/__init__.py
  6. +5 −0 example/blogango/conf/settings.py
  7. +27 −0 example/blogango/context_processors.py
  8. +43 −0 example/blogango/feeds.py
  9. +1 −0  example/blogango/fixtures/initial_data.json
  10. +46 −0 example/blogango/forms.py
  11. +151 −0 example/blogango/models.py
  12. +32 −0 example/blogango/search.py
  13. +9 −0 example/blogango/templates/b-404.html
  14. +9 −0 example/blogango/templates/b-500.html
  15. +120 −0 example/blogango/templates/base.html
  16. +23 −0 example/blogango/templates/blogango/archive_view.html
  17. +20 −0 example/blogango/templates/blogango/author.html
  18. +22 −0 example/blogango/templates/blogango/blogroll.html
  19. +8 −0 example/blogango/templates/blogango/comment.html
  20. +5 −0 example/blogango/templates/blogango/commentcontrols.html
  21. +24 −0 example/blogango/templates/blogango/create.html
  22. +29 −0 example/blogango/templates/blogango/details.html
  23. +20 −0 example/blogango/templates/blogango/edit_preferences.html
  24. +1 −0  example/blogango/templates/blogango/eggs.html
  25. +6 −0 example/blogango/templates/blogango/entry_controls.html
  26. +26 −0 example/blogango/templates/blogango/install.html
  27. +29 −0 example/blogango/templates/blogango/mainpage.html
  28. +27 −0 example/blogango/templates/blogango/manage.html
  29. +32 −0 example/blogango/templates/blogango/manage_entries.html
  30. +34 −0 example/blogango/templates/blogango/mod_comment.html
  31. +20 −0 example/blogango/templates/blogango/tag_details.html
  32. +7 −0 example/blogango/templates/blogango/topbar.html
  33. +9 −0 example/blogango/templates/entry_footer.html
  34. +29 −0 example/blogango/templates/registration/login.html
  35. +17 −0 example/blogango/templates/registration/logout.html
  36. +25 −0 example/blogango/templates/registration/password_change.html
  37. +18 −0 example/blogango/templates/search.html
  38. 0  example/blogango/templatetags/__init__.py
  39. +12 −0 example/blogango/templatetags/filters.py
  40. +44 −0 example/blogango/templatetags/gravatar.py
  41. +59 −0 example/blogango/urls.py
  42. +359 −0 example/blogango/views.py
  43. +12 −0 example/doc.txt
  44. +11 −0 example/manage.py
  45. +101 −0 example/settings.py
  46. +114 −0 example/site_media/blogango/css/agiliqblog.css
  47. +1 −0  example/site_media/blogango/css/prettify.css
  48. BIN  example/site_media/blogango/images/bg.png
  49. BIN  example/site_media/blogango/images/blog.png
  50. BIN  example/site_media/blogango/images/date_icon.png
  51. BIN  example/site_media/blogango/images/icon_smile.gif
  52. BIN  example/site_media/blogango/images/linesbg.png
  53. BIN  example/site_media/blogango/images/linesbg2.png
  54. +23 −0 example/site_media/blogango/js/prettify.js
  55. +1 −0  example/subdomain_admin
  56. 0  example/subdomains/__init__.py
  57. 0  example/subdomains/conf/__init__.py
  58. +5 −0 example/subdomains/conf/settings.py
  59. +6 −0 example/subdomains/context_processors.py
  60. +57 −0 example/subdomains/decorators.py
  61. +31 −0 example/subdomains/forms.py
  62. +85 −0 example/subdomains/middleware.py
  63. +82 −0 example/subdomains/models.py
  64. +48 −0 example/subdomains/requestfactory.py
  65. +10 −0 example/subdomains/templates/subdomains/base.html
  66. +14 −0 example/subdomains/templates/subdomains/create_subdomain.html
  67. +147 −0 example/subdomains/tests.py
  68. +7 −0 example/subdomains/urls.py
  69. +35 −0 example/subdomains/views.py
  70. BIN  example/tmp/subdomain_admin.db
  71. +27 −0 example/urls.py
  72. 0  subdomain_admin/__init__.py
  73. +36 −0 subdomain_admin/admin.py
  74. +66 −0 subdomain_admin/forms.py
  75. +24 −0 subdomain_admin/models.py
  76. +12 −0 subdomain_admin/templates/subdomain_admin/createsite.html
  77. +23 −0 subdomain_admin/tests.py
  78. +21 −0 subdomain_admin/views.py
0  example/__init__.py
No changes.
28 example/blogango/__init__.py
@@ -0,0 +1,28 @@
+
+from datetime import date, datetime, time
+from time import strptime
+
+from django.core.urlresolvers import reverse
+from django.db.models import signals
+
+from pingback import create_ping_func
+from pingback.client import ping_external_links, ping_directories
+from django_xmlrpc import xmlrpcdispatcher
+
+from blogango.models import BlogEntry
+
+def pingback_blog_handler(year, month, slug, **kwargs):
+ return BlogEntry.objects.get(created_on__year=year,
+ created_on__month=month,
+ slug=slug,
+ is_published=True)
+
+
+ping_details = {'blogango_details': pingback_blog_handler}
+
+ping_func = create_ping_func(**ping_details)
+
+xmlrpcdispatcher.register_function(ping_func, 'pingback.ping')
+
+signals.post_save.connect(ping_external_links(content_attr='text', url_attr='get_absolute_url'), sender=BlogEntry, weak=False)
+
16 example/blogango/admin.py
@@ -0,0 +1,16 @@
+from django.contrib import admin
+
+from blogango.models import Blog, BlogEntry, Comment, BlogRoll
+
+from subdomain_admin.admin import SubdomainAdmin
+
+class BlogEntryAdmin(SubdomainAdmin):
+ prepopulated_fields = {'slug': ('title',)}
+
+class BlogRollAdmin(SubdomainAdmin):
+ pass
+
+admin.site.register(Blog)
+admin.site.register(BlogEntry, BlogEntryAdmin)
+admin.site.register(Comment)
+admin.site.register(BlogRoll, BlogRollAdmin)
372 example/blogango/akismet.py
@@ -0,0 +1,372 @@
+# Version 0.2.0
+# 2009/06/18
+
+# Copyright Michael Foord 2005-2009
+# akismet.py
+# Python interface to the akismet API
+# E-mail fuzzyman@voidspace.org.uk
+
+# http://www.voidspace.org.uk/python/modules.shtml
+# http://akismet.com
+
+# Released subject to the BSD License
+# See http://www.voidspace.org.uk/python/license.shtml
+
+
+"""
+A python interface to the `Akismet <http://akismet.com>`_ API.
+This is a web service for blocking SPAM comments to blogs - or other online
+services.
+
+You will need a Wordpress API key, from `wordpress.com <http://wordpress.com>`_.
+
+You should pass in the keyword argument 'agent' to the name of your program,
+when you create an Akismet instance. This sets the ``user-agent`` to a useful
+value.
+
+The default is : ::
+
+ Python Interface by Fuzzyman | akismet.py/0.2.0
+
+Whatever you pass in, will replace the *Python Interface by Fuzzyman* part.
+**0.2.0** will change with the version of this interface.
+
+Usage example::
+
+ from akismet import Akismet
+
+ api = Akismet(agent='Test Script')
+ # if apikey.txt is in place,
+ # the key will automatically be set
+ # or you can call api.setAPIKey()
+ #
+ if api.key is None:
+ print "No 'apikey.txt' file."
+ elif not api.verify_key():
+ print "The API key is invalid."
+ else:
+ # data should be a dictionary of values
+ # They can all be filled in with defaults
+ # from a CGI environment
+ if api.comment_check(comment, data):
+ print 'This comment is spam.'
+ else:
+ print 'This comment is ham.'
+"""
+
+
+import os, sys
+from urllib import urlencode
+
+import socket
+if hasattr(socket, 'setdefaulttimeout'):
+ # Set the default timeout on sockets to 5 seconds
+ socket.setdefaulttimeout(5)
+
+__version__ = '0.2.0'
+
+__all__ = (
+ '__version__',
+ 'Akismet',
+ 'AkismetError',
+ 'APIKeyError',
+ )
+
+__author__ = 'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>'
+
+__docformat__ = "restructuredtext en"
+
+user_agent = "%s | akismet.py/%s"
+DEFAULTAGENT = 'Python Interface by Fuzzyman/%s'
+
+isfile = os.path.isfile
+
+urllib2 = None
+try:
+ from google.appengine.api import urlfetch
+except ImportError:
+ import urllib2
+
+if urllib2 is None:
+ def _fetch_url(url, data, headers):
+ req = urlfetch.fetch(url=url, payload=data, method=urlfetch.POST, headers=headers)
+ if req.status_code == 200:
+ return req.content
+ raise Exception('Could not fetch Akismet URL: %s Response code: %s' %
+ (url, req.status_code))
+else:
+ def _fetch_url(url, data, headers):
+ req = urllib2.Request(url, data, headers)
+ h = urllib2.urlopen(req)
+ resp = h.read()
+ return resp
+
+
+class AkismetError(Exception):
+ """Base class for all akismet exceptions."""
+
+class APIKeyError(AkismetError):
+ """Invalid API key."""
+
+class Akismet(object):
+ """A class for working with the akismet API"""
+
+ baseurl = 'rest.akismet.com/1.1/'
+
+ def __init__(self, key=None, blog_url=None, agent=None):
+ """Automatically calls ``setAPIKey``."""
+ if agent is None:
+ agent = DEFAULTAGENT % __version__
+ self.user_agent = user_agent % (agent, __version__)
+ self.setAPIKey(key, blog_url)
+
+
+ def _getURL(self):
+ """
+ Fetch the url to make requests to.
+
+ This comprises of api key plus the baseurl.
+ """
+ return 'http://%s.%s' % (self.key, self.baseurl)
+
+
+ def _safeRequest(self, url, data, headers):
+ try:
+ resp = _fetch_url(url, data, headers)
+ except Exception, e:
+ raise AkismetError(str(e))
+ return resp
+
+
+ def setAPIKey(self, key=None, blog_url=None):
+ """
+ Set the wordpress API key for all transactions.
+
+ If you don't specify an explicit API ``key`` and ``blog_url`` it will
+ attempt to load them from a file called ``apikey.txt`` in the current
+ directory.
+
+ This method is *usually* called automatically when you create a new
+ ``Akismet`` instance.
+ """
+ if key is None and isfile('apikey.txt'):
+ the_file = [l.strip() for l in open('apikey.txt').readlines()
+ if l.strip() and not l.strip().startswith('#')]
+ try:
+ self.key = the_file[0]
+ self.blog_url = the_file[1]
+ except IndexError:
+ raise APIKeyError("Your 'apikey.txt' is invalid.")
+ else:
+ self.key = key
+ self.blog_url = blog_url
+
+
+ def verify_key(self):
+ """
+ This equates to the ``verify-key`` call against the akismet API.
+
+ It returns ``True`` if the key is valid.
+
+ The docs state that you *ought* to call this at the start of the
+ transaction.
+
+ It raises ``APIKeyError`` if you have not yet set an API key.
+
+ If the connection to akismet fails, it allows the normal ``HTTPError``
+ or ``URLError`` to be raised.
+ (*akismet.py* uses `urllib2 <http://docs.python.org/lib/module-urllib2.html>`_)
+ """
+ if self.key is None:
+ raise APIKeyError("Your have not set an API key.")
+ data = { 'key': self.key, 'blog': self.blog_url }
+ # this function *doesn't* use the key as part of the URL
+ url = 'http://%sverify-key' % self.baseurl
+ # we *don't* trap the error here
+ # so if akismet is down it will raise an HTTPError or URLError
+ headers = {'User-Agent' : self.user_agent}
+ resp = self._safeRequest(url, urlencode(data), headers)
+ if resp.lower() == 'valid':
+ return True
+ else:
+ return False
+
+ def _build_data(self, comment, data):
+ """
+ This function builds the data structure required by ``comment_check``,
+ ``submit_spam``, and ``submit_ham``.
+
+ It modifies the ``data`` dictionary you give it in place. (and so
+ doesn't return anything)
+
+ It raises an ``AkismetError`` if the user IP or user-agent can't be
+ worked out.
+ """
+ data['comment_content'] = comment
+ if not 'user_ip' in data:
+ try:
+ val = os.environ['REMOTE_ADDR']
+ except KeyError:
+ raise AkismetError("No 'user_ip' supplied")
+ data['user_ip'] = val
+ if not 'user_agent' in data:
+ try:
+ val = os.environ['HTTP_USER_AGENT']
+ except KeyError:
+ raise AkismetError("No 'user_agent' supplied")
+ data['user_agent'] = val
+ #
+ data.setdefault('referrer', os.environ.get('HTTP_REFERER', 'unknown'))
+ data.setdefault('permalink', '')
+ data.setdefault('comment_type', 'comment')
+ data.setdefault('comment_author', '')
+ data.setdefault('comment_author_email', '')
+ data.setdefault('comment_author_url', '')
+ data.setdefault('SERVER_ADDR', os.environ.get('SERVER_ADDR', ''))
+ data.setdefault('SERVER_ADMIN', os.environ.get('SERVER_ADMIN', ''))
+ data.setdefault('SERVER_NAME', os.environ.get('SERVER_NAME', ''))
+ data.setdefault('SERVER_PORT', os.environ.get('SERVER_PORT', ''))
+ data.setdefault('SERVER_SIGNATURE', os.environ.get('SERVER_SIGNATURE',
+ ''))
+ data.setdefault('SERVER_SOFTWARE', os.environ.get('SERVER_SOFTWARE',
+ ''))
+ data.setdefault('HTTP_ACCEPT', os.environ.get('HTTP_ACCEPT', ''))
+ data.setdefault('blog', self.blog_url)
+
+
+ def comment_check(self, comment, data=None, build_data=True, DEBUG=False):
+ """
+ This is the function that checks comments.
+
+ It returns ``True`` for spam and ``False`` for ham.
+
+ If you set ``DEBUG=True`` then it will return the text of the response,
+ instead of the ``True`` or ``False`` object.
+
+ It raises ``APIKeyError`` if you have not yet set an API key.
+
+ If the connection to Akismet fails then the ``HTTPError`` or
+ ``URLError`` will be propogated.
+
+ As a minimum it requires the body of the comment. This is the
+ ``comment`` argument.
+
+ Akismet requires some other arguments, and allows some optional ones.
+ The more information you give it, the more likely it is to be able to
+ make an accurate diagnosise.
+
+ You supply these values using a mapping object (dictionary) as the
+ ``data`` argument.
+
+ If ``build_data`` is ``True`` (the default), then *akismet.py* will
+ attempt to fill in as much information as possible, using default
+ values where necessary. This is particularly useful for programs
+ running in a {acro;CGI} environment. A lot of useful information
+ can be supplied from evironment variables (``os.environ``). See below.
+
+ You *only* need supply values for which you don't want defaults filled
+ in for. All values must be strings.
+
+ There are a few required values. If they are not supplied, and
+ defaults can't be worked out, then an ``AkismetError`` is raised.
+
+ If you set ``build_data=False`` and a required value is missing an
+ ``AkismetError`` will also be raised.
+
+ The normal values (and defaults) are as follows : ::
+
+ 'user_ip': os.environ['REMOTE_ADDR'] (*)
+ 'user_agent': os.environ['HTTP_USER_AGENT'] (*)
+ 'referrer': os.environ.get('HTTP_REFERER', 'unknown') [#]_
+ 'permalink': ''
+ 'comment_type': 'comment' [#]_
+ 'comment_author': ''
+ 'comment_author_email': ''
+ 'comment_author_url': ''
+ 'SERVER_ADDR': os.environ.get('SERVER_ADDR', '')
+ 'SERVER_ADMIN': os.environ.get('SERVER_ADMIN', '')
+ 'SERVER_NAME': os.environ.get('SERVER_NAME', '')
+ 'SERVER_PORT': os.environ.get('SERVER_PORT', '')
+ 'SERVER_SIGNATURE': os.environ.get('SERVER_SIGNATURE', '')
+ 'SERVER_SOFTWARE': os.environ.get('SERVER_SOFTWARE', '')
+ 'HTTP_ACCEPT': os.environ.get('HTTP_ACCEPT', '')
+
+ (*) Required values
+
+ You may supply as many additional 'HTTP_*' type values as you wish.
+ These should correspond to the http headers sent with the request.
+
+ .. [#] Note the spelling "referrer". This is a required value by the
+ akismet api - however, referrer information is not always
+ supplied by the browser or server. In fact the HTTP protocol
+ forbids relying on referrer information for functionality in
+ programs.
+ .. [#] The `API docs <http://akismet.com/development/api/>`_ state that this value
+ can be " *blank, comment, trackback, pingback, or a made up value*
+ *like 'registration'* ".
+ """
+ if self.key is None:
+ raise APIKeyError("Your have not set an API key.")
+ if data is None:
+ data = {}
+ if build_data:
+ self._build_data(comment, data)
+ if 'blog' not in data:
+ data['blog'] = self.blog_url
+ url = '%scomment-check' % self._getURL()
+ # we *don't* trap the error here
+ # so if akismet is down it will raise an HTTPError or URLError
+ headers = {'User-Agent' : self.user_agent}
+ resp = self._safeRequest(url, urlencode(data), headers)
+ if DEBUG:
+ return resp
+ resp = resp.lower()
+ if resp == 'true':
+ return True
+ elif resp == 'false':
+ return False
+ else:
+ # NOTE: Happens when you get a 'howdy wilbur' response !
+ raise AkismetError('missing required argument.')
+
+
+ def submit_spam(self, comment, data=None, build_data=True):
+ """
+ This function is used to tell akismet that a comment it marked as ham,
+ is really spam.
+
+ It takes all the same arguments as ``comment_check``, except for
+ *DEBUG*.
+ """
+ if self.key is None:
+ raise APIKeyError("Your have not set an API key.")
+ if data is None:
+ data = {}
+ if build_data:
+ self._build_data(comment, data)
+ url = '%ssubmit-spam' % self._getURL()
+ # we *don't* trap the error here
+ # so if akismet is down it will raise an HTTPError or URLError
+ headers = {'User-Agent' : self.user_agent}
+ self._safeRequest(url, urlencode(data), headers)
+
+
+ def submit_ham(self, comment, data=None, build_data=True):
+ """
+ This function is used to tell akismet that a comment it marked as spam,
+ is really ham.
+
+ It takes all the same arguments as ``comment_check``, except for
+ *DEBUG*.
+ """
+ if self.key is None:
+ raise APIKeyError("Your have not set an API key.")
+ if data is None:
+ data = {}
+ if build_data:
+ self._build_data(comment, data)
+ url = '%ssubmit-ham' % self._getURL()
+ # we *don't* trap the error here
+ # so if akismet is down it will raise an HTTPError or URLError
+ headers = {'User-Agent' : self.user_agent}
+ self._safeRequest(url, urlencode(data), headers)
0  example/blogango/conf/__init__.py
No changes.
5 example/blogango/conf/settings.py
@@ -0,0 +1,5 @@
+
+from django.conf import settings
+
+AKISMET_API_KEY = getattr(settings, 'AKISMET_API_KEY', '')
+AKISMET_COMMENT = bool(AKISMET_API_KEY)
27 example/blogango/context_processors.py
@@ -0,0 +1,27 @@
+
+from django.conf import settings
+from django.core.urlresolvers import reverse
+
+from taggit.models import Tag
+
+from blogango.models import Blog, BlogRoll
+from blogango.views import _get_archive_months
+
+def extra_context(request):
+ blog_rolls = BlogRoll.objects.filter(is_published=True)
+ tags = Tag.objects.all()
+ feed_url = getattr(settings, 'FEED_URL', reverse('blogango_feed', args=['latest']))
+
+ archive_months = _get_archive_months()
+
+ blog = Blog.objects.all()
+ blog = blog[0] if blog.count() > 0 else None
+
+ return {'blog': blog,
+ 'blog_rolls': blog_rolls,
+ 'tags': tags,
+ 'canonical_url': request.build_absolute_uri(),
+ 'feed_url': feed_url,
+ 'pingback_xmlrpc_url': request.build_absolute_uri(reverse('xmlrpc')),
+ 'archive_months': archive_months}
+
43 example/blogango/feeds.py
@@ -0,0 +1,43 @@
+from django.contrib.syndication.feeds import Feed
+from django.core.urlresolvers import reverse
+from django.core.exceptions import ObjectDoesNotExist
+
+from blogango.models import Blog, BlogEntry
+from taggit.models import Tag
+
+class main_feed(Feed):
+ try:
+ blog = Blog.objects.all()[0]
+ except:
+ class DummyBlog:
+ def __init__ (self):
+ self.title = ''
+ self.tag_line = ''
+ blog = DummyBlog()
+
+ title = blog.title
+ link = "/rss/latest/"
+ description = blog.tag_line
+
+ def items (self):
+ return BlogEntry.objects.filter(is_published=True)[:10]
+
+class CatFeed(Feed):
+ def get_object(self, bits):
+ if len(bits) != 1:
+ raise ObjectDoesNotExist
+ return Tag.objects.get(name__iexact=bits[0])
+
+ def title(self, obj):
+ return "%s" % obj.name
+
+ def link(self, obj):
+ if not obj:
+ raise FeedDoesNotExist
+ return reverse('blogango_tag_details', args=[obj.slug])
+
+ def description(self, obj):
+ return "Category: %s" % obj.name
+
+ def items(self, obj):
+ return BlogEntry.objects.filter(is_published=True, tags__in=[obj])
1  example/blogango/fixtures/initial_data.json
@@ -0,0 +1 @@
+[{"pk": 1, "model": "blogango.blog", "fields": {"recent_comments": 5, "recents": 5, "tag_line": "subdomain admin", "entries_per_page": 10, "title": "agiliq"}}]
46 example/blogango/forms.py
@@ -0,0 +1,46 @@
+
+from django import forms
+
+from blogango.models import Blog, BlogRoll
+
+class WideTextArea(forms.Textarea):
+ def __init__(self, *args, **kwargs):
+ kwargs.setdefault('attrs',{}).update({'rows': '20', 'cols':'80'})
+ super(WideTextArea, self).__init__(*args, **kwargs)
+
+class EntryForm(forms.Form):
+ title = forms.CharField(max_length=100, required=False)
+ text = forms.CharField(widget=WideTextArea, label='Entry')
+ slug = forms.CharField(max_length=100, required=False)
+ tags = forms.CharField(max_length=100, required=False)
+ is_page = forms.BooleanField(initial=False, label='Is it a page?', required=False)
+ comments_allowed = forms.BooleanField(initial=True, label='Are comments allowed?', required=False)
+ is_rte = forms.BooleanField(initial=True, label='Use Rick Text?', required=False)
+
+class CommentForm(forms.Form):
+ text = forms.CharField(widget=forms.Textarea(attrs={'class': 'textarea'}), label='Comment')
+ name = forms.CharField(max_length=100, widget=forms.TextInput(attrs={'class': 'textfield'}))
+ url = forms.URLField(required=False, widget=forms.TextInput(attrs={'class': 'textfield'}))
+ email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'textfield'}))
+
+class TagForm(forms.Form):
+ tag_txt = forms.CharField(max_length=100, label='Tag as')
+
+class InstallForm(forms.ModelForm):
+
+ class Meta:
+ model = Blog
+ exclude = ('entries_per_page', 'recents', 'recent_comments')
+
+class PreferencesForm(forms.ModelForm):
+
+ class Meta:
+ model = Blog
+ exclude = ('title', 'tag_line')
+
+class BlogForm(forms.ModelForm):
+
+ class Meta:
+ model = BlogRoll
+ exclude = ('is_published',)
+
151 example/blogango/models.py
@@ -0,0 +1,151 @@
+from django.db import models
+from django.db.models import permalink
+from django.contrib.auth.models import User
+
+from taggit.managers import TaggableManager
+
+from subdomain_admin.models import SubModel
+
+class Blog(models.Model):
+ """Blog wide settings.
+ title:title of the Blog.
+ tag_line: Tagline/subtitle of the blog. This two are genearlly displayed on each page's header.
+ entries_per_page=Number of entries to display on each page.
+ recents: Number of recent entries to display in the sidebar.
+ recent_comments: Number of recent comments to display in the sidebar.
+ """
+
+ title = models.CharField(max_length=100)
+ tag_line = models.CharField(max_length=100)
+ entries_per_page = models.IntegerField(default=10)
+ recents = models.IntegerField(default=5)
+ recent_comments = models.IntegerField(default=5)
+
+ def __unicode__(self):
+ return self.title
+
+ def save(self):
+ """There should not be more than one Blog object"""
+ if Blog.objects.count() > 1 and self.id:
+ raise Exception("Only one blog object allowed.")
+ super(Blog, self).save() # Call the "real" save() method.
+
+
+
+class BlogEntry(SubModel):
+ """Each blog entry.
+ Title: Post title.
+ Slug: Post slug. These two if not given are inferred directly from entry text.
+ text = The main data for the post.
+ summary = The summary for the text. probably can can be derived from text, bit we dont want do do that each time main page is displayed.
+ created_on = The date this entry was created. Defaults to now.
+ Created by: The user who wrote this.
+ is_page: IS this a page or a post? Pages are the more important posts, which might be displayed differently. Defaults to false.
+ is_published: Is this page published. If yes then we would display this on site, otherwise no. Default to true.
+ comments_allowed: Are comments allowed on this post? Default to True
+ is_rte: Was this post done using a Rich text editor?"""
+
+ title = models.CharField(max_length=100)
+ slug = models.SlugField()
+ text = models.TextField()
+ summary = models.TextField()
+ created_on = models.DateTimeField(auto_now_add=True)
+ created_by = models.ForeignKey(User, unique=False)
+ is_page = models.BooleanField(default=False)
+ is_published = models.BooleanField(default=True)
+ comments_allowed = models.BooleanField(default=True)
+ is_rte = models.BooleanField(default=False)
+
+ meta_keywords = models.TextField(blank=True, null=True)
+ meta_description = models.TextField(blank=True, null=True)
+
+ tags = TaggableManager()
+
+ class Meta:
+ ordering = ['-created_on']
+ verbose_name_plural = 'Blog entries'
+
+ def __unicode__(self):
+ return self.title
+
+ def save(self):
+ if self.title == None or self.title == '':
+ self.title = _infer_title_or_slug(self.text)
+ if self.slug == None or self.slug == '':
+ self.slug = _infer_title_or_slug(self.text)
+ self.summary = _generate_summary(self.text)
+ if not self.meta_keywords:
+ self.meta_keywords = self.summary
+ if not self.meta_description:
+ self.meta_description = self.summary
+
+ super(BlogEntry, self).save() # Call the "real" save() method.
+
+ @permalink
+ def get_absolute_url(self):
+ return ('blogango_details', (), {'year': self.created_on.strftime('%Y'),
+ 'month': self.created_on.strftime('%m'),
+ 'slug': self.slug})
+
+ @permalink
+ def get_edit_url(self):
+ return ('blogango.views.edit_entry', [self.id])
+
+ def get_num_comments(self):
+ cmnt_count = Comment.objects.filter(comment_for=self).count()
+ return cmnt_count
+
+ # @property
+ # def blog_title(self):
+ # return self.title
+
+class Comment(models.Model):
+ """Comments for each blog.
+ text: The comment text.
+ comment_for: the Post/Page this comment is created for.
+ created_on: The date this comment was written on.
+ created_by: THe user who wrote this comment.
+ user_name = If created_by is null, this comment was by anonymous user. Name in that case.
+ email_id: Email-id, as in user_name.
+ is_spam: Is comment marked as spam? We do not display the comment in those cases."""
+
+ text = models.TextField()
+ comment_for = models.ForeignKey(BlogEntry)
+ created_on = models.DateTimeField(auto_now_add=True)
+ created_by = models.ForeignKey(User, unique=False, blank=True, null=True)
+ user_name = models.CharField(max_length=100)
+ user_url = models.CharField(max_length=100)
+ email_id = models.EmailField()
+ is_spam = models.BooleanField(default=False)
+
+ class Meta:
+ ordering = ['created_on']
+
+ def __unicode__(self):
+ return self.text
+
+ @permalink
+ def get_absolute_url (self):
+ #return '/comment/%s/' %self.id
+ return ('comment_details', self.id)
+
+
+class BlogRoll(SubModel):
+ url = models.URLField(unique=True)
+ text = models.CharField(max_length=100)
+ is_published = models.BooleanField(default=True)
+
+ def __unicode__(self):
+ return self.text
+
+ def get_absolute_url(self):
+ return self.url
+
+
+#Helper methods
+def _infer_title_or_slug(text):
+ return '-'.join(text.split()[:5])
+
+def _generate_summary(text):
+ return ' '.join(text.split()[:100])
+
32 example/blogango/search.py
@@ -0,0 +1,32 @@
+import urllib2, urllib
+from django.utils import simplejson
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+import views
+
+
+site = 'blogango.com'
+
+def search (request):
+ query_term = ""
+ for term in request.GET['q']:
+ query_term += term
+ results = get_search_results('YLPjx2rV34F4hXcTnJYqYJUj9tANeqax76Ip2vADl9kKuByRNHgC4qafbATFoQ', query_term, site = site)
+ print [result['Url'] for result in results]
+ payload = {'results':results}
+ return views.render('search.html', request, payload)
+
+def get_search_results(appid, query, region ='us', type = 'all', results = 10, start = 0, format ='any', adult_ok = "", similar_ok = "", language = "", country = "", site = "", subscription = "", license = ''):
+ base_url = u'http://search.yahooapis.com/WebSearchService/V1/webSearch?'
+ params = locals()
+ result = _query_yahoo(base_url, params)
+ return result['ResultSet']['Result']
+
+def _query_yahoo(base_url, params):
+ params['output'] = 'json'
+ payload = urllib.urlencode(params)
+ url = base_url + payload
+ print url
+ response = urllib2.urlopen(url)
+ result = simplejson.load(response)
+ return result
9 example/blogango/templates/b-404.html
@@ -0,0 +1,9 @@
+{% extends 'base.html' %}
+
+{% block main %}
+<div id="columnA_2columns">
+<div id="message">
+<p>404 - Page does not exist!
+</div>
+</div>
+{% endblock %}
9 example/blogango/templates/b-500.html
@@ -0,0 +1,9 @@
+{% extends 'base.html' %}
+
+{% block main %}
+<div id="columnA_2columns">
+<div id="message">
+<p>500 - Internal server error!
+</div>
+</div>
+{% endblock %}
120 example/blogango/templates/base.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>Blog {% block title %} {% endblock %}</title>
+<link href="{{ MEDIA_URL }}blogango/css/agiliqblog.css" rel="stylesheet" type="text/css" />
+<link href="{{ MEDIA_URL }}blogango/css/prettify.css" rel="stylesheet" type="text/css" />
+<link href='http://fonts.googleapis.com/css?family=Droid+Sans+Mono' rel='stylesheet' type='text/css' />
+
+{% if feed_url %}
+ <link rel="alternate" type="application/rss+xml" title="The Agiliq Blog - Django Web Development RSS Feed" href="{{feed_url}}" />
+{% endif %}
+{% if canonical_url %}
+ <link rel="canonical" href='{{ canonical_url }}' />
+{% endif %}
+{# <link rel="pingback" href="{% url xmlrpc %}" /> #}
+<link rel="pingback" href="{{ pingback_xmlrpc_url }}" />
+
+<script type="text/javascript" src="{{ MEDIA_URL }}blogango/js/prettify.js"></script>
+
+</head>
+<body>
+
+
+<div id="container">
+ <div id="header">
+ <a href="{% url blogango_index %}">{{ blog.title }}</a> {{ blog.tag_line }}
+ </div>
+
+ <!--LEFT PANEL STARTS-->
+ <div id="leftpanel">
+ {% block content %}
+ <!--BLOG POST STARTS-->
+ {% for entry in entries %}
+ <div class="leftblock">
+ <h1>{{ entry.title }}</h1>
+ <div class="filed">Filed in : <a href="#">Django Forms</a></div>
+ <div class="postdetails">
+ <img src="{{ MEDIA_URL }}blogango/images/blog.png" width="16" height="16" alt="Written by" />
+ <a href="#"> {{ entry.created_by }}</a> &nbsp;&nbsp;&nbsp;
+ <img src="{{ MEDIA_URL }}blogango/images/date_icon.png" width="16" height="16" alt="Date" />
+ &nbsp;26th May,2010.
+ </div>
+ <div class="postcontent">
+ <p>{{ entry.text|truncatewords_html:50|safe }}</p>
+ </div>
+ <div class="moreinfo"><a href="{{ entry.get_absolute_url }}">read more </a></div>
+ </div>
+ {% empty %}
+ <div class="leftblock">
+ You don't have any entries in your blog.
+ </div>
+ {% endfor %}
+ <!--BLOG POST ENDS-->
+ {% endblock %}
+ </div>
+ <!-- LEFT PANEL ENDS -->
+
+ <!--RIGHT PANEL STARTS-->
+ <div id="rightpanel">
+ {% if recents %}
+ <!--POSTS PANEL STARTS-->
+ <div class="rightblock">
+ <h1>BLOGPOSTS</h1>
+ <ul>
+ {% for recent in recents %}
+ <li><a href="{{ recent.get_absolute_url }}">{{ recent.title }}</a></li>
+ {% endfor %}
+ </ul>
+ </div>
+ <!--POSTS PANEL ENDS-->
+ {% endif %}
+
+ <!--FRIENDS PANEL STARTS-->
+ <div class="rightblock">
+ {% if blog_rolls %}
+ <h1 class="orange">BLOG ROLL</h1>
+ <ul>
+ {% for roll in blog_rolls %}
+ <li><a href="{{ roll.url }}">{{ roll.text }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ <div class="clear"></div>
+ </div>
+ <!--FRIENDS PANEL ENDS-->
+
+ {% if tags %}
+ <!--CATEGORIES PANEL STARTS-->
+ <div class="rightblock">
+ <h1 class="brown">CATEGORIES</h1>
+ <ul>
+ {% for tag in tags %}
+ <li><a href="{% url blogango_tag_details tag.slug %}">{{ tag.name }}</a></li>
+ {% endfor %}
+ </ul>
+ </div>
+ <!--CATEGORIES PANEL ENDS-->
+ {% endif %}
+
+ {% if archive_months %}
+ <!--ARCHIVES PANEL STARTS-->
+ <div class="rightblock">
+ <h1 class="blue">ARCHIVES</h1>
+ <ul>
+ {% for month in archive_months %}
+ <li><a href="{% url blogango_archives month|date:'Y' month|date:'b' %}">{{ month|date:'F Y' }}</a></li>
+ {% endfor %}
+ </ul>
+ </div>
+ <!--ARCHIVES PANEL ENDS-->
+ {% endif %}
+ </div>
+ <!--RIGHT PANEL ENDS-->
+
+ <div class="clear"></div>
+</div>
+
+</body>
+</html>
23 example/blogango/templates/blogango/archive_view.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}
+- Archives
+{% endblock %}
+
+{% block content %}
+
+{% for entry in object_list %}
+ <div class="leftblock">
+ <h1><a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a></h1>
+ {% include 'blogango/entry_controls.html' %}
+
+ <div class="postcontent">
+ <p>{{ entry.text|truncatewords_html:50|safe }}</p>
+ </div>
+ <div class="moreinfo"><a href="{{ entry.get_absolute_url }}">read more </a></div>
+ </div>
+
+{% endfor %}
+
+{% endblock %}
+
20 example/blogango/templates/blogango/author.html
@@ -0,0 +1,20 @@
+{% extends 'base.html' %}
+
+{% block title %}
+- Author {{ author }}
+{% endblock %}
+
+{% block content %}
+<div class="leftblock">
+ <h1>{{ author }}</h1>
+ <div class="postdetails postlist">
+ <h2> Posts by this author</h2>
+ <ul>
+ {% for post in author.blogentry_set.all %}
+ <li><a href="{{ post.get_absolute_url }}">{{ post.title }} ({{ post.created_on|date:'n/j/Y' }})</a></li>
+ {% endfor %}
+ </ul>
+ </div>
+ <div class="moreinfo"> <br/> </div>
+</div>
+{% endblock %}
22 example/blogango/templates/blogango/blogroll.html
@@ -0,0 +1,22 @@
+{% extends "base.html" %}
+
+{% block title %}
+{{block.super}} - Blogroll
+{% endblock %}
+
+{% block main %}
+
+<div id="message">
+Add links to the blogroll.
+</div>
+
+<div id="columnA_2columns">
+<form action="." method="post">
+<table>
+{{blogroll_form}}
+</table>
+<input type="submit" class="button" value="Add to blogroll" />
+</form>
+</div>
+
+{% endblock %}
8 example/blogango/templates/blogango/comment.html
@@ -0,0 +1,8 @@
+{% extends 'base.html' %}
+
+{% block main %}
+<div id="columnA_2columns">
+{{comment.text}}
+{% include 'blogango/commentcontrols.html' %}
+</div>
+{% endblock %}
5 example/blogango/templates/blogango/commentcontrols.html
@@ -0,0 +1,5 @@
+{% load humanize %}
+{% load markup %}
+<div class="commentcontrols">
+By {{comment.user_name}} on {{comment.created_on|naturalday}}
+</div>
24 example/blogango/templates/blogango/create.html
@@ -0,0 +1,24 @@
+{% extends "base.html" %}
+
+{% block title %}
+{{block.super}} - Create
+{% endblock %}
+
+{% block main %}
+<div id="columnA_2columns">
+
+<div id="message">
+Create a new entry.
+</div>
+
+<form action="." method="post">
+{% csrf_token %}
+<table>
+{{create_form}}
+</table>
+<input type="submit" class="button" value="Post it" name="post" />
+<input type="submit" class="button" value="Save as draft" name="save" />
+</form>
+</div>
+
+{% endblock %}
29 example/blogango/templates/blogango/details.html
@@ -0,0 +1,29 @@
+{% extends 'base.html' %}
+{% load gravatar %}
+
+{% block title %}
+ {{block.super}} - {{entry.title}}
+{% endblock %}
+
+{% block content %}
+<div class="leftblock">
+ <h1>{{ entry.title }}</h1>
+
+ {% include 'blogango/entry_controls.html' %}
+
+ <div class="postcontent">
+ <p>{{ entry.text|safe }}</p>
+ </div>
+ <div class="moreinfo"> <br/> </div>
+</div>
+
+<div class="tags">
+{% for tag in tags %}
+ <a href="{{tag.get_absolute_url}}">{{tag.tag_txt}}</a>
+{% endfor %}
+</div>
+
+
+
+{% endblock %}
+
20 example/blogango/templates/blogango/edit_preferences.html
@@ -0,0 +1,20 @@
+{% extends "base.html" %}
+
+{% block title %}
+{{block.super}} - Install
+{% endblock %}
+
+{% block main %}
+<div id="columnA_2columns">
+<div id="message">
+<p>Edit your preferences, and watch the magic happen.</p>
+</div>
+<form action="." method="post">
+<table>
+{{install_form}}
+</table>
+<input name="install" type="submit" value="Update preferences" class="button"/>
+</form>
+</div>
+
+{% endblock %}
1  example/blogango/templates/blogango/eggs.html
@@ -0,0 +1 @@
+This is a template.
6 example/blogango/templates/blogango/entry_controls.html
@@ -0,0 +1,6 @@
+<div class="postdetails">
+ <img src="{{ MEDIA_URL }}blogango/images/blog.png" width="16" height="16" alt="Written by" />
+ <a href="{% url blogango_author entry.created_by %}"> {{ entry.created_by }}</a> &nbsp;&nbsp;&nbsp;
+ <img src="{{ MEDIA_URL }}blogango/images/date_icon.png" width="16" height="16" alt="Date" />
+ &nbsp;{{ entry.created_on|date:'jS N, Y' }}.
+</div>
26 example/blogango/templates/blogango/install.html
@@ -0,0 +1,26 @@
+{% extends "base.html" %}
+
+{% block title %}
+{{block.super}} - Install
+{% endblock %}
+
+{% block content %}
+<div class="leftblock">
+ <h1>Blog not created </h1>
+
+<div class="postcontent">
+ <p>Create a blog in '/admin/'</p>
+</div>
+
+{% comment %}
+<form action="." method="post">
+ <table>
+ {{install_form}}
+ </table>
+ <input name="install" type="submit" value="Install Blog" class="button"/>
+</form>
+{% endcomment %}
+
+</div>
+
+{% endblock %}
29 example/blogango/templates/blogango/mainpage.html
@@ -0,0 +1,29 @@
+{% extends 'base.html' %}
+
+{% block content%}
+
+<!--BLOG POST STARTS-->
+{% for entry in entries %}
+ <div class="leftblock">
+ <h1><a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a></h1>
+ {% include 'blogango/entry_controls.html' %}
+
+ <div class="postcontent">
+ <p>{{ entry.text|truncatewords_html:50|safe }}</p>
+ </div>
+ <div class="moreinfo"><a href="{{ entry.get_absolute_url }}">read more </a></div>
+ </div>
+{% empty %}
+ <div class="leftblock">
+ <h1>No posts in blog</h1>
+ <div class="postcontent">
+ <p>
+ <br/>You don't have any entries in your blog,
+ you can post to your blog from site admin.
+ </p>
+ </div>
+ </div>
+{% endfor %}
+<!--BLOG POST ENDS-->
+
+{% endblock %}
27 example/blogango/templates/blogango/manage.html
@@ -0,0 +1,27 @@
+{% extends 'base.html' %}
+
+{% block title %}
+{{block.super}} - Manage
+{% endblock %}
+
+{% block main %}
+<div id="columnA_2columns">
+<ul>
+<li>
+<a href="{% url blogango_create %}">Write an entry.</a>
+</li>
+<li>
+<a href="{% url blogango_mod_entries %}">Edit previous entries.</a>
+</li>
+<li>
+<a href="{% url blogango_mod_comments %}">Moderate comments.</a>
+</li>
+<li>
+<a href="{% url blogango_blogroll %}">Add to blogroll.</a>
+</li>
+<li>
+<a href="{% url blogango_edit_preferences %}">Edit preferences.</a>
+</li>
+</ul>
+</div>
+{% endblock %}
32 example/blogango/templates/blogango/manage_entries.html
@@ -0,0 +1,32 @@
+{% extends 'base.html' %}
+
+{% block main %}
+<div id="columnA_2columns">
+
+<div id="message">
+Manage previous entries. Delete or unpublish.
+</div>
+
+<form action="." method="post">
+<table summary="" border="0" class="entries">
+<tr>
+<th>Entry</th>
+<th>View</th>
+<th>Edit</th>
+<th>Select</th>
+</tr>
+{% for entry in entries %}
+<tr>
+<td>{{entry.title}}</td>
+<td><a href="{{entry.get_absolute_url}}">view</a></td>
+<td><a href="{{entry.get_edit_url}}">edit</a></td>
+<td><input type="checkbox" name="entries" value="{{entry.id}}" /></td>
+</tr>
+{% endfor %}
+</table>
+
+<input type="submit" class="button" value="Delete" name="del" />
+<input type="submit" class="button" value="Unpublish" name="unpublish" />
+</form>
+</div>
+{% endblock %}
34 example/blogango/templates/blogango/mod_comment.html
@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+{% load markup %}
+
+{% block title %}
+{{block.super}} - Comments
+{% endblock %}
+
+{% block main %}
+<div id="columnA_2columns">
+<div id="message">
+Manage comments. Down with spammers.
+</div>
+Comments
+<form action="." method="post">
+
+<table summary="" border="0" class="comments">
+{% for comment in comments %}
+<tr>
+<th>Comment</th>
+<th>Delete</th>
+<th>Mark as spam</th>
+</tr>
+<tr>
+<td>{{comment.text|textile}}</td>
+<td><input type="checkbox" name="delete" value="{{comment.id}}" /></td>
+<td><input type="checkbox" name="spam" value="{{comment.id}}" /></td>
+</tr>
+{% endfor %}
+</table>
+
+<input type="submit" class="button" value="Manage Comments" />
+</form>
+</div>
+{% endblock %}
20 example/blogango/templates/blogango/tag_details.html
@@ -0,0 +1,20 @@
+{% extends 'base.html' %}
+
+{% block title %}
+ {{block.super}} - Tags
+{% endblock %}
+
+{% block content %}
+<div class="leftblock">
+ <h1>Post with tag: {{ tag.name }}</h1>
+ <div class="postdetails">
+ <ul>
+ {% for entry in entries %}
+ <li>
+ <a href="{{entry.get_absolute_url}}">{{entry.title}}</a> <br />
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+</div>
+{% endblock %}
7 example/blogango/templates/blogango/topbar.html
@@ -0,0 +1,7 @@
+<div id="topbar">
+{% block topbar %}
+{% for page in pages %}
+<a href="{{page.get_absolute_url}}">{{page.title}}</a> <br />
+{% endfor %}
+{% endblock %}
+</div>
9 example/blogango/templates/entry_footer.html
@@ -0,0 +1,9 @@
+{% block entry_footer %}
+
+<ul class="post_footer">
+<li>
+<a href="{{entry.get_absolute_url}}">Read more</a>
+</li>
+</ul>
+
+{% endblock %}
29 example/blogango/templates/registration/login.html
@@ -0,0 +1,29 @@
+{% extends "base.html" %}
+
+{% block title %}
+{{block.super}} - Login
+{% endblock %}
+
+{% block main %}
+<div id="columnA_2columns">
+<div class="message">
+Login!
+</div>
+{% if form.has_errors %}
+<p>Your username and password didn't match. Please try again.</p>
+{% endif %}
+
+<form method="post" action=".">
+<table>
+<tr><td><label for="id_username">Username:</label></td><td>{{ form.username }}</td></tr>
+<tr><td><label for="id_password">Password:</label></td><td>{{ form.password }}</td></tr>
+</table>
+
+<input type="submit" value="login" />
+<input type="hidden" name="next" value="{{ next }}" />
+</form>
+</div>
+{% endblock %}
+
+{% block sidebar %}
+{% endblock %}
17 example/blogango/templates/registration/logout.html
@@ -0,0 +1,17 @@
+{% extends "base.html" %}
+
+{% block title %}
+{{block.super}} - Logout
+{% endblock %}
+
+{% block main %}
+<div id="columnA_2columns">
+<div class="message">
+Logged out.
+<a href="{% url auth_login %}">Login again</a>
+</div>
+</div>
+{% endblock %}
+
+{% block sidebar %}
+{% endblock %}
25 example/blogango/templates/registration/password_change.html
@@ -0,0 +1,25 @@
+{% extends "base.html" %}
+
+{% block title %}
+{{block.super}} - L
+{% endblock %}
+
+{% block main %}
+<div id="columnA_2columns">
+<div class="message">
+Change password
+</div>
+{% if form.has_errors %}
+<p>Your username and password didn't match. Please try again.</p>
+{% endif %}
+
+<form method="post" action=".">
+<table>
+{{form.manipulator}}
+</table>
+
+<input type="submit" value="login" />
+<input type="hidden" name="next" value="{{ next }}" />
+</form>
+</div>
+{% endblock %}
18 example/blogango/templates/search.html
@@ -0,0 +1,18 @@
+{% extends 'base.html' %}
+
+{% block main %}
+<div id="columnA_2columns">
+{% if not results %}
+<div id="message">
+There are no results to display! Change search terms to get the posts you are looking for.
+</div>
+{% endif %}
+{% for result in results %}
+<ul>
+<li><a href="{{result.Url}}">{{result.Title}}</a><br class="clear"/>
+{{result.Summary}}
+</li>
+</ul>
+{% endfor %}
+</div>
+{% endblock %}
0  example/blogango/templatetags/__init__.py
No changes.
12 example/blogango/templatetags/filters.py
@@ -0,0 +1,12 @@
+
+from django import template
+
+register = template.Library()
+
+from lib import ttp
+
+@register.filter
+def twitterify(tweet):
+ parse = ttp.Parser()
+ result = parse.parse(tweet)
+ return result.html
44 example/blogango/templatetags/gravatar.py
@@ -0,0 +1,44 @@
+
+### gravatar.py ###############
+### place inside a 'templatetags' directory inside the top level of a Django app (not project, must be inside an app)
+### at the top of your page template include this:
+### {% load gravatar %}
+### and to use the url do this:
+### <img src="{% gravatar_url 'someone@somewhere.com' %}"/>
+### or
+### <img src="{% gravatar_url sometemplatevariable %}"/>
+### just make sure to update the "default" image path below
+
+import urllib, hashlib
+
+from django import template
+
+register = template.Library()
+
+class GravatarUrlNode(template.Node):
+ def __init__(self, email):
+ self.email = template.Variable(email)
+
+ def render(self, context):
+ try:
+ email = self.email.resolve(context)
+ except template.VariableDoesNotExist:
+ return ''
+
+ default = "http://www.gravatar.com/avatar"
+ size = 50
+
+ gravatar_url = "http://www.gravatar.com/avatar/" + hashlib.md5(email).hexdigest() + "?"
+ gravatar_url += urllib.urlencode({'default':default, 'size':str(size)})
+
+ return gravatar_url
+
+@register.tag
+def gravatar_url(parser, token):
+ try:
+ tag_name, email = token.split_contents()
+
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents.split()[0]
+
+ return GravatarUrlNode(email)
59 example/blogango/urls.py
@@ -0,0 +1,59 @@
+from django.conf.urls.defaults import *
+from django.contrib.auth.views import login, logout, password_change, password_reset
+from django.contrib.sitemaps import GenericSitemap
+
+from blogango import feeds
+from blogango.models import BlogEntry
+
+blog_info_dict = {
+ 'queryset': BlogEntry.objects.filter(is_published=True),
+ 'date_field': 'created_on',
+}
+sitemaps = {
+ 'blog': GenericSitemap(blog_info_dict, priority=0.5)
+}
+
+urlpatterns = patterns('blogango.views',
+ url(r'^welcome/$', 'welcome', name='blogango_welcome'),
+ url(r'^install/$', 'install_blog', name='blogango_install'),
+ url(r'^preferences/$', 'edit_preferences', name='blogango_edit_preferences'),
+
+ url(r'^$', 'index', name='blogango_index'),
+ url(r'^page/(?P<page>\d+)/$', 'index', name='blogango_page'),
+ url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[-\w]+)/$', 'details', name='blogango_details'),
+ url(r'^new/$', 'create_entry', name='blogango_create'),
+ url(r'^blogroll/$', 'create_blogroll', name='blogango_blogroll'),
+ url(r'^moderate/$', 'moderate_comments', name='blogango_mod_comments'),
+ url(r'^entries/$', 'mod_entries', name='blogango_mod_entries'),
+ url(r'^tag/(?P<tag_slug>\w+)/$','tag_details', name='blogango_tag_details'),
+ url(r'^manage/$', 'manage', name='blogango_manage'),
+ url(r'^edit/(?P<entry_id>\d+)/$', 'edit_entry', name='blogango_edit_entry'),
+ url(r'^comment/(?P<comment_id>\d+)/$', 'comment_details', name='blogango_comment_details'),
+ url(r'^author/(?P<username>[\w.@+-]+)/$', 'author', name='blogango_author'),
+
+)
+
+#search view
+urlpatterns += patterns('blogango.search',
+ url(r'^search/$', 'search', name = 'search'),
+)
+
+# sitemap.xml
+urlpatterns += patterns('django.contrib.sitemaps.views',
+ url(r'^sitemap\.xml$', 'sitemap', {'sitemaps': sitemaps}),
+)
+
+# Archive view
+urlpatterns += patterns('blogango.views',
+ url(r'^archive/(?P<year>\d+)/(?P<month>\w+)/$', 'monthly_view', name='blogango_archives')
+)
+
+urlpatterns += patterns('django_xmlrpc.views',
+ url(r'^xmlrpc/$', 'handle_xmlrpc', {}, name='xmlrpc'),
+)
+
+feeds = {'latest': feeds.main_feed, 'tag':feeds.CatFeed}
+# feeds = {'latest': feeds.main_feed}
+urlpatterns += patterns('',
+ url(r'^rss/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}, name='blogango_feed')
+)
359 example/blogango/views.py
@@ -0,0 +1,359 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponse, HttpResponseRedirect
+from django.conf import settings
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
+from django.template import RequestContext
+from django.core.paginator import Paginator
+from django.core.urlresolvers import reverse
+from django.views.generic.date_based import archive_month
+from django.http import Http404
+from django.core.exceptions import ObjectDoesNotExist
+
+from blogango.models import Blog, BlogEntry, Comment, BlogRoll
+from blogango import forms as bforms
+
+from blogango.conf.settings import AKISMET_COMMENT, AKISMET_API_KEY
+
+def welcome(request):
+ return render_to_response('mainpage.html', {})
+
+
+def handle404(view_function):
+ def wrapper(*args, **kwargs):
+ try:
+ return view_function(*args, **kwargs)
+ except ObjectDoesNotExist:
+ raise Http404
+ return wrapper
+
+
+def index(request, page = 1):
+ if not _is_blog_installed():
+ return HttpResponseRedirect(reverse('blogango_install'))
+ page = int(page)
+ blog = Blog.objects.all()[0]
+ entries = BlogEntry.objects.filter(is_page=False, is_published=True).order_by('-created_on')
+ paginator = Paginator(entries, blog.entries_per_page)
+ page_ = paginator.page(page)
+ entries = page_.object_list
+ has_next = page_.has_next()
+ has_prev = page_.has_previous()
+ next = page + 1
+ prev = page - 1
+ payload = locals()
+ return render('blogango/mainpage.html', request, payload)
+
+
+def check_comment_spam(request, comment):
+ from blogango.akismet import Akismet, APIKeyError
+ api = Akismet(AKISMET_API_KEY, request.get_host(), request.META['HTTP_USER_AGENT'])
+
+ if api.verify_key():
+ akismet_data = {'user_ip': request.META['REMOTE_ADDR'],
+ 'user_agent': request.META['HTTP_USER_AGENT'],
+ 'comment_author': comment.user_name,
+ 'comment_author_email': comment.email_id,
+ 'comment_author_url': comment.user_url,
+ 'comment_type': 'comment'}
+
+ return api.comment_check(comment.text, akismet_data)
+ raise APIKeyError("Akismet API key is invalid.")
+
+
+@handle404
+def details(request, year, month, slug):
+ if not _is_blog_installed():
+ return HttpResponseRedirect(reverse('blogango_install'))
+
+ entry = BlogEntry.objects.get(created_on__year=year,
+ created_on__month=month,
+ slug=slug,
+ is_published=True)
+
+ if not entry.is_published:
+ raise Http404
+
+ if request.method == 'POST':
+ comment_f = bforms.CommentForm(request.POST)
+ if comment_f.is_valid():
+ comment_by = request.user if request.user.is_authenticated() else None
+ comment = Comment(text=comment_f.cleaned_data['text'],
+ created_by=comment_by,
+ comment_for=entry,
+ user_name=comment_f.cleaned_data['name'],
+ user_url=comment_f.cleaned_data['url'],
+ email_id=comment_f.cleaned_data['email'])
+ if AKISMET_COMMENT:
+ comment.is_spam = check_comment_spam(request, comment)
+ comment.save()
+ return HttpResponseRedirect('.')
+ else:
+ comment_f = bforms.CommentForm()
+
+ comments = Comment.objects.filter(comment_for=entry, is_spam=False)
+ # tags = Tag.objects.filter(tag_for=entry)
+ payload = {'entry': entry, 'comments': comments, 'comment_form': comment_f}
+ return render('blogango/details.html', request, payload)
+
+
+@handle404
+def comment_details(request, comment_id):
+ comment = Comment.objects.get(id=comment_id)
+ payload = locals()
+ return render('blogango/comment.html', request, payload)
+
+
+def tag_details(request, tag_slug):
+ from taggit.models import Tag
+ if Tag.objects.filter(slug=tag_slug).count() == 0:
+ raise Http404
+ tag = Tag.objects.get(slug=tag_slug)
+ entries = BlogEntry.objects.filter(is_published=True, tags__in=[tag])
+ feed_url = getattr(settings, 'FEED_URL', reverse('blogango_feed', args=['tag']) + tag.slug + '/')
+ payload = {'tag': tag, 'entries': entries, 'feed_url': feed_url}
+ return render('blogango/tag_details.html', request, payload)
+
+
+@login_required
+def create_entry(request):
+ if request.method == 'GET':
+ create = bforms.EntryForm()
+ elif request.method == 'POST':
+ create = bforms.EntryForm(request.POST)
+ if create.is_valid():
+ if request.POST.has_key('save'):
+ publish = False
+ elif request.POST.has_key('post'):
+ publish = True
+ entry = BlogEntry(created_by=request.user,
+ text=create.cleaned_data['text'],
+ title=create.cleaned_data['title'],
+ slug=create.cleaned_data['slug'],
+ is_page=create.cleaned_data['is_page'],
+ is_published=publish,
+ is_rte=create.cleaned_data['is_rte'])
+ entry.save()
+ tags = create.cleaned_data['tags']
+ tag_list = tags.split()
+ for tag in tag_list:
+ tag_, created = Tag.objects.get_or_create(tag_txt=tag.strip())
+ tag_.save()
+ entry.tag_set.add(tag_)
+ if request.POST.has_key('save'):
+ return HttpResponseRedirect('.')
+ elif request.POST.has_key('post'):
+ return HttpResponseRedirect(entry.get_absolute_url())
+ payload = {'create_form': create,}
+ return render('blogango/create.html', request, payload)
+
+
+@login_required
+def edit_entry(request, entry_id):
+ if request.method == 'GET':
+ entry = BlogEntry.objects.filter(id=entry_id).values()[0]
+ entry_ = BlogEntry.objects.get(id=entry_id)
+ tags = Tag.objects.filter(tag_for=entry_)
+ tags = [tag.tag_txt for tag in tags]
+ tag_ = " ".join(tags)
+ entry['tags'] = tag_
+ create = bforms.EntryForm(entry)
+ elif request.method == 'POST':
+ create = bforms.EntryForm(request.POST)
+ if create.is_valid():
+ entry = BlogEntry.objects.get(id=entry_id)
+ entry.text = create.cleaned_data['text']
+ entry.title = create.cleaned_data['title']
+ entry.slug = create.cleaned_data['slug']
+ entry.is_page = create.cleaned_data['is_page']
+ entry.comments_allowed = create.cleaned_data['comments_allowed']
+ if request.POST.has_key('save'):
+ publish = False
+ elif request.POST.has_key('post'):
+ publish = True
+ entry.is_published = publish
+ entry.save()
+ tags = Tag.objects.filter(tag_for=entry)
+ for tag in tags:
+ entry.tag_set.remove(tag)
+ tags_data = create.cleaned_data['tags']
+ tag_list = tags_data.split(' ')
+ for tag in tag_list:
+ tag_, created = Tag.objects.get_or_create(tag_txt=tag.strip())
+ tag_.save()
+ entry.tag_set.add(tag_)
+ return HttpResponseRedirect(entry.get_absolute_url())
+ payload = {'create_form': create,}
+ return render('blogango/create.html', request, payload)
+
+
+@login_required
+def mod_entries(request):
+ if request.method == 'GET':
+ entries = BlogEntry.objects.all()
+ payload = locals()
+ return render('blogango/manage_entries.html', request, payload)
+ if request.method == 'POST':
+ if request.POST.has_key("unpublish"):
+ entry_ids = request.POST['entries']
+ entries = BlogEntry.objects.filter(id__in=entry_ids)
+ for entry in entries:
+ entry.is_published = False
+ entry.save()
+ elif request.POST.has_key("del"):
+ entry_ids = request.POST['entries']
+ # print request.POST
+ # print entry_ids
+ BlogEntry.objects.filter(id__in=entry_ids).delete()
+ return HttpResponseRedirect('.')
+
+
+@login_required
+def moderate_comments(request):
+ if request.method == 'GET':
+ comments = Comment.objects.filter()
+ payload = {"comments": comments}
+ return render('blogango/mod_comment.html', request, payload)
+ elif request.method == 'POST':
+ if request.POST.has_key('spam'):
+ spammeds = request.POST['spam']
+ else:
+ spammeds = {}
+ for spammed in spammeds:
+ comment = Comment.objects.get(id = spammed)
+ comment.is_spam = True
+ comment.save()
+ if request.POST.has_key('delete'):
+ deleteds = request.POST['delete']
+ else:
+ deleteds = {}
+ for deleted in deleteds:
+ Comment.objects.get(id = deleted).delete()
+ return HttpResponseRedirect('.')
+
+
+@login_required
+def install_blog(request):
+ if _is_blog_installed():
+ return HttpResponseRedirect(reverse('blogango_index'))
+
+ if request.method == 'GET':
+ install_form = bforms.InstallForm()
+ if request.method == 'POST':
+ install_form = bforms.InstallForm(request.POST)
+ if install_form.is_valid():
+ install_form.save()
+ return HttpResponseRedirect(reverse('blogango_index'))
+ payload = {"install_form": install_form}
+ return render('blogango/install.html', request, payload)
+
+
+@login_required
+def create_blogroll(request):
+ if request.method == 'GET':
+ blogroll_form = bforms.BlogForm()
+ if request.method == 'POST':
+ blogroll_form = bforms.BlogForm(request.POST)
+ if blogroll_form.is_valid():
+ blogroll_form.save()
+ return HttpResponseRedirect('.')
+ payload = {"blogroll_form": blogroll_form}
+ return render('blogango/blogroll.html', request, payload)
+
+
+@login_required
+def edit_preferences(request):
+ if request.method == 'GET':
+ prefs_form = bforms.PreferencesForm(Blog.objects.all().values()[0])
+ if request.method == 'POST':
+ prefs_form = bforms.PreferencesForm(request.POST)
+ if prefs_form.is_valid():
+ blog = Blog.objects.all()[0]
+ # print blog.id
+ blog.entries_per_page = prefs_form.cleaned_data['entries_per_page']
+ blog.recents = prefs_form.cleaned_data['recents']
+ blog.recent_comments = prefs_form.cleaned_data['recents']
+ blog.save()
+ return HttpResponseRedirect('.')
+ payload = {"install_form": prefs_form}
+ return render('blogango/edit_preferences.html', request, payload)
+
+
+@login_required
+def manage(request):
+ return render('blogango/manage.html', request, {})
+
+
+def author(request, username):
+ author = get_object_or_404(User, username=username)
+ return render('blogango/author.html', request, {'author': author})
+
+
+def monthly_view(request, year, month):
+ # print year, month
+ queryset = BlogEntry.objects.filter(is_page=False, is_published=True)
+ return archive_month(request=request,
+ template_name='blogango/archive_view.html',
+ year=year,
+ month=month,
+ queryset=queryset,
+ date_field='created_on',
+ extra_context=_get_sidebar_objects(request))
+
+
+#Helper methods.
+def _is_blog_installed():
+ if Blog.objects.count() == 0:
+ return False
+ return True
+
+
+def render (template, request, payload):
+ """Wrapper on render_to_response.
+ Adds sidebar objects. Adds RequestContext"""
+ payload.update(_get_sidebar_objects(request))
+ return render_to_response(template, payload, context_instance=RequestContext(request),)
+
+
+def _get_sidebar_objects (request):
+ """Gets the objects which are always displayed in the sidebar"""
+ try:
+ blog = Blog.objects.all()[0]
+ except:
+ return {}
+ recents = BlogEntry.objects.filter(is_page = False, is_published = True).order_by('-created_on')[:blog.recents]
+ blogroll = BlogRoll.objects.all()
+ pages = BlogEntry.objects.filter(is_page = True, is_published = True)
+ recent_comments = Comment.objects.all().order_by('-created_on')[:blog.recent_comments]
+ # date_list = _get_archive_months()
+ return {'blog':blog, 'recents':recents, 'pages':pages, 'blogroll':blogroll, 'recent_comments':recent_comments}
+
+
+def _get_archive_months():
+ """Get the months for which at least one entry exists"""
+ dates = BlogEntry.objects.dates('created_on', 'month', order='DESC')
+ # print dates
+ # date_list = []
+ # for date in dates:
+ # date_list.append((date.strftime('%Y/%b'), date.strftime('%B %y')))
+ return dates
+
+
+def _generic_form_display(request, form_class):
+ if request.method == 'GET':
+ form_inst = form_class()
+ if request.method == 'POST':
+ form_inst = form_class(request.POST)
+ if form_inst.is_valid():
+ form_inst.save()
+ return HttpResponseRedirect('.')
+ payload = {"install_form": form_inst}
+ return render('blogango/install.html', request, payload)
+
+
+def generic(request): # A generic form processor.
+ if request.method == 'GET':
+ pass
+ if request.method == 'POST':
+ pass
+
12 example/doc.txt
@@ -0,0 +1,12 @@
+
+add these apps to INSTALLED_APPS
+ 'subdomains',
+ 'subdomain_admin',
+
+* Every user registerd on the site is member of the group 'SubUser'
+
+models inherited from `subdomain_admin.models.SubModel` are
+editable by the user belonging to the group 'SubUser'
+
+`from subdomain_admin.admin import SubdomainAdmin`
+
11 example/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
101 example/settings.py
@@ -0,0 +1,101 @@
+# Django settings for example project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': 'tmp/subdomain_admin.db', # Or path to database file if using sqlite3.
+ 'USER': '', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ }
+}
+
+# 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.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = 'site_media/'
+
+# 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/"
+MEDIA_URL = '/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/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 't)bs4)owsu*!2_84^14i_a&$j)x7$cl&!cnlu)h!c1cu4bsif9'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+
+ 'subdomains.middleware.GetSubdomainMiddleware',
+ 'subdomains.middleware.ThreadLocals',
+ 'subdomains.middleware.RedirectOnInvalidSubdomain',
+)
+
+ROOT_URLCONF = 'example.urls'
+
+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.
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.admin',
+
+ 'subdomains',
+ 'subdomain_admin',
+ 'blogango',
+)
114 example/site_media/blogango/css/agiliqblog.css
@@ -0,0 +1,114 @@
+@charset "utf-8";
+/* CSS Document */
+/**************************************/
+/* GLOBALS */
+/**************************************/
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
+}
+ol, ul {
+ list-style: none;
+}
+
+body{ background:url(../images/bg.png) repeat-x; background-color:#4E4E4E; font:14px Helvetica, Arial, sans-serif; color:#000; line-height:22px;}
+#header a{font: bold 32px Helvetica, Arial, sans-serif; color:#585858;text-decoration: none;text-transform:uppercase;}
+h1{font:bold 18px Helvetica, Arial, sans-serif; color:#ffffff; line-height:50px; background-color:#8FB83C; padding-left:25px; }
+h1 a {color:#ffffff; text-decoration: none;}
+h1.green{font:bold 18px Helvetica, Arial, sans-serif; color:#ffffff; line-height:50px; background-color:#8FB83C; padding-left:25px;}
+h1.orange{font:bold 18px Helvetica, Arial, sans-serif; ; line-height:50px; background-color:#FF7800; padding-left:25px; }
+h1.brown{font:bold 18px Helvetica, Arial, sans-serif; line-height:50px; background-color:#7E4221; padding-left:25px; }
+h1.red{font:bold 18px Helvetica, Arial, sans-serif; line-height:50px; background-color:#A1001B; padding-left:25px; }
+h1.purple{font:bold 18px Helvetica, Arial, sans-serif; line-height:50px; background-color:#530072; padding-left:25px; }
+h1.blue{font:bold 18px Helvetica, Arial, sans-serif; line-height:50px; background-color:#00ADEF; padding-left:25px; }
+
+h2{font:bold 15px Helvetica, Arial, sans-serif; line-height:40px;}
+p.post{padding-bottom:20px;border-bottom:1px solid #A4A4A4; }
+
+#container{width:960px; margin:0px auto;}
+#header{width:940px; height:45px; display:block; margin:10px 0px 10px 0px; padding:35px 0px 0px 20px;}
+#leftpanel{width:600px; float:left;}
+#rightpanel{width:320px; float:right;}
+
+.general_block{ display:block; background-color:#FFF; padding:20px;}
+.comment_block{ display:block; background-color:#FFF; padding:20px 30px 20px 30px; margin:10px 0px 10px 0px;}
+.comment_block .leftpanel{ display:block; width:430px; float:left;}
+.comment_block .rightpanel{ display:block; width:100px; float:right; text-align:right; }
+.commentor{font:bold 14px Arial, Helvetica, sans-serif;}
+.commentor a {text-decoration: none;color:#0975B5;}
+.postdate{font:12px Arial, Helvetica, sans-serif; color:#666; padding-left:20px;}
+.comment_post { margin-top:10px; font:12px Arial, Helvetica, sans-serif; line-height:18px; }
+
+.rightblock{width:320px; display:block; background-color:#FFF; margin-bottom:20px;}
+.rightblock ul{ padding:25px 0px 25px 25px;}
+.rightblock li{ font:bold 11px Tahoma, Geneva, sans-serif; line-height:24px; color:#5E5E5E; text-decoration:none; }
+.rightblock li a{ font:bold 11px Tahoma, Geneva, sans-serif; line-height:24px; color:#5E5E5E; text-decoration:none; border-bottom:1px dashed #999; }
+.rightblock li a:hover{ font:bold 11px Tahoma, Geneva, sans-serif; line-height:24px; text-decoration:none; color:#000; }
+
+.rightblock_divided{width:320px; display:block; margin-bottom:20px;}
+.rightblock_divided .left{width:150px; float:left;background-color:#FFF;}
+.rightblock_divided .right{width:150px; float:right;background-color:#FFF;}
+.rightblock_divided ul{ padding:25px 0px 25px 25px;}
+.rightblock_divided li{ font:bold 11px Tahoma, Geneva, sans-serif; line-height:24px; color:#5E5E5E; text-decoration:none; }
+.rightblock_divided li a{ font:bold 11px Tahoma, Geneva, sans-serif; line-height:24px; color:#5E5E5E; text-decoration:none; border-bottom:1px dashed #999; }
+.rightblock_divided li a:hover{ font:bold 11px Tahoma, Geneva, sans-serif; line-height:24px; text-decoration:none; color:#000; }
+
+
+.leftblock{width:600px; display:block; background-color:#FFF; margin-bottom:10px;}
+
+
+.filed{ font:bold 12px Arial, Helvetica, sans-serif; color:#000; line-height:25px; padding-left:25px;}
+.filed a{font:12px Arial, Helvetica, sans-serif; line-height:25px;text-decoration:none; color:#5E5E5E; border-bottom:1px dashed #999;}
+.filed a:hover{font:12px Arial, Helvetica, sans-serif; line-height:25px;text-decoration:none; color:#000; border-bottom:1px dashed #999;}
+.postdetails{font:bold 12px Arial, Helvetica, sans-serif; color:#424242; line-height:25px; margin:0px 25px 0px 25px; padding-bottom:15px; background:url(../images/linesbg.png) repeat-x bottom left;}
+.postdetails a{font:bold 12px Arial, Helvetica, sans-serif; color:#000; line-height:25px; text-decoration:none; color:#424242; border-bottom:1px dashed #999;}
+.postdetails a:hover{font:bold 12px Arial, Helvetica, sans-serif; color:#000; line-height:25px; text-decoration:none; color:#000; }
+
+.postlist ul li { list-style-type: square;}
+
+.postcontent{font:14px Helvetica, Arial, sans-serif; color:#000; line-height:22px; margin:20px 25px 0px 25px; background:url(../images/linesbg2.png) repeat-x bottom left; display:block; padding-bottom:20px; }
+.postcontent ul{ margin:10px 0px 20px 0px; font-weight:bold;}
+.postcontent li{font:14px Helvetica, Arial, sans-serif; color:#000; line-height:22px; margin:0px 0px 0px 15px; list-style-type:disc;}
+
+.post_icon{ width:30px; height:30px;}
+
+.moreinfo{font:bold 11px Tahoma, Geneva, sans-serif; color:#666; line-height:40px; text-decoration:none; margin:0px 25px 0px 25px; }
+.moreinfo a{font:bold 11px Tahoma, Geneva, sans-serif; color:#666; line-height:40px; text-decoration:none; text-align:right; border-bottom:1px dashed #999; }
+
+.moreinfo a:hover{font:bold 11px Tahoma, Geneva, sans-serif; color:#000; line-height:40px; text-decoration:none; border-bottom:1px dashed #000; }
+
+/**************************************/
+/* FORMS */
+/**************************************/
+.textfield{width:495px; background-color:#F0F0F0; border:1px solid #D1D1D1; padding:5px;}
+.textarea{width:495px; background-color:#F0F0F0; border:1px solid #D1D1D1; padding:5px; margin-bottom:5px;}
+fieldset{border:3px solid #EAEAEA; padding:20px; font:bold 11px Tahoma, Geneva, sans-serif; line-height:30px; margin-top:10px;}
+legend{font:16px Arial, Helvetica, sans-serif; line-height:24px;}
+.but_right{float:right;}
+
+.submit{ text-align:right; padding:3px;}
+.footer{ clear:both; font:bold 11px Tahoma, Geneva, sans-serif; color:#FFF; line-height:40px; text-align:center;}
+.clear{clear:both;}
+#pre {display: block;margin: 2em 0;white-space: pre;overflow: auto;width: 500px;line-height: 1.4;border: 3px solid #ccc;background: #F1F1F1;padding:20px;font-family: 'Droid Sans Mono', arial, serif; font-size:12px;}
+
+ul.errorlist {
+ color: red;
+ display: inline;
+}
+ul.errorlist li {
+ display: inline;
+}
1  example/site_media/blogango/css/prettify.css
@@ -0,0 +1 @@
+.str{color:#0C9}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun{color:#660}.pln{color:#000}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec{color:#606}pre.prettyprint{padding:2px;border:1px solid #888}@media print{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun{color:#440}.pln{color:#000}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}
BIN  example/site_media/blogango/images/bg.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/site_media/blogango/images/blog.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/site_media/blogango/images/date_icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/site_media/blogango/images/icon_smile.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/site_media/blogango/images/linesbg.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/site_media/blogango/images/linesbg2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 example/site_media/blogango/js/prettify.js
@@ -0,0 +1,23 @@
+function H(){var x=navigator&&navigator.userAgent&&/\bMSIE 6\./.test(navigator.userAgent);H=function(){return x};return x}(function(){function x(b){b=b.split(/ /g);var a={};for(var c=b.length;--c>=0;){var d=b[c];if(d)a[d]=null}return a}var y="break continue do else for if return while ",U=y+"auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile ",D=U+"catch class delete false import new operator private protected public this throw true try ",
+I=D+"alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename typeof using virtual wchar_t where ",J=D+"boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient ",V=J+"as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var ",
+K=D+"debugger eval export function get null set undefined var with Infinity NaN ",L="caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END ",M=y+"and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None ",N=y+"alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END ",
+O=y+"case done elif esac eval fi function in local set then until ",W=I+V+K+L+M+N+O;function X(b){return b>="a"&&b<="z"||b>="A"&&b<="Z"}function u(b,a,c,d){b.unshift(c,d||0);try{a.splice.apply(a,b)}finally{b.splice(0,2)}}var Y=(function(){var b=["!","!=","!==","#","%","%=","&","&&","&&=","&=","(","*","*=","+=",",","-=","->","/","/=",":","::",";","<","<<","<<=","<=","=","==","===",">",">=",">>",">>=",">>>",">>>=","?","@","[","^","^=","^^","^^=","{","|","|=","||","||=","~","break","case","continue",
+"delete","do","else","finally","instanceof","return","throw","try","typeof"],a="(?:(?:(?:^|[^0-9.])\\.{1,3})|(?:(?:^|[^\\+])\\+)|(?:(?:^|[^\\-])-)";for(var c=0;c<b.length;++c){var d=b[c];a+=X(d.charAt(0))?"|\\b"+d:"|"+d.replace(/([^=<>:&])/g,"\\$1")}a+="|^)\\s*$";return new RegExp(a)})(),P=/&/g,Q=/</g,R=/>/g,Z=/\"/g;function $(b){return b.replace(P,"&amp;").replace(Q,"&lt;").replace(R,"&gt;").replace(Z,"&quot;")}function E(b){return b.replace(P,"&amp;").replace(Q,"&lt;").replace(R,"&gt;")}var aa=
+/&lt;/g,ba=/&gt;/g,ca=/&apos;/g,da=/&quot;/g,ea=/&amp;/g,fa=/&nbsp;/g;function ga(b){var a=b.indexOf("&");if(a<0)return b;for(--a;(a=b.indexOf("&#",a+1))>=0;){var c=b.indexOf(";",a);if(c>=0){var d=b.substring(a+3,c),g=10;if(d&&d.charAt(0)==="x"){d=d.substring(1);g=16}var e=parseInt(d,g);if(!isNaN(e))b=b.substring(0,a)+String.fromCharCode(e)+b.substring(c+1)}}return b.replace(aa,"<").replace(ba,">").replace(ca,"'").replace(da,'"').replace(ea,"&").replace(fa," ")}function S(b){return"XMP"===b.tagName}
+function z(b,a){switch(b.nodeType){case 1:var c=b.tagName.toLowerCase();a.push("<",c);for(var d=0;d<b.attributes.length;++d){var g=b.attributes[d];if(!g.specified)continue;a.push(" ");z(g,a)}a.push(">");for(var e=b.firstChild;e;e=e.nextSibling)z(e,a);if(b.firstChild||!/^(?:br|link|img)$/.test(c))a.push("</",c,">");break;case 2:a.push(b.name.toLowerCase(),'="',$(b.value),'"');break;case 3:case 4:a.push(E(b.nodeValue));break}}var F=null;function ha(b){if(null===F){var a=document.createElement("PRE");
+a.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));F=!/</.test(a.innerHTML)}if(F){var c=b.innerHTML;if(S(b))c=E(c);return c}var d=[];for(var g=b.firstChild;g;g=g.nextSibling)z(g,d);return d.join("")}function ia(b){var a=0;return function(c){var d=null,g=0;for(var e=0,h=c.length;e<h;++e){var f=c.charAt(e);switch(f){case "\t":if(!d)d=[];d.push(c.substring(g,e));var i=b-a%b;a+=i;for(;i>=0;i-=" ".length)d.push(" ".substring(0,i));g=e+1;break;
+case "\n":a=0;break;default:++a}}if(!d)return c;d.push(c.substring(g));return d.join("")}}var ja=/(?:[^<]+|<!--[\s\S]*?--\>|<!\[CDATA\[([\s\S]*?)\]\]>|<\/?[a-zA-Z][^>]*>|<)/g,ka=/^<!--/,la=/^<\[CDATA\[/,ma=/^<br\b/i;function na(b){var a=b.match(ja),c=[],d=0,g=[];if(a)for(var e=0,h=a.length;e<h;++e){var f=a[e];if(f.length>1&&f.charAt(0)==="<"){if(ka.test(f))continue;if(la.test(f)){c.push(f.substring(9,f.length-3));d+=f.length-12}else if(ma.test(f)){c.push("\n");++d}else g.push(d,f)}else{var i=ga(f);
+c.push(i);d+=i.length}}return{source:c.join(""),tags:g}}function v(b,a){var c={};(function(){var g=b.concat(a);for(var e=g.length;--e>=0;){var h=g[e],f=h[3];if(f)for(var i=f.length;--i>=0;)c[f.charAt(i)]=h}})();var d=a.length;return function(g,e){e=e||0;var h=[e,"pln"],