Skip to content
This repository has been archived by the owner on Sep 10, 2023. It is now read-only.

Commit

Permalink
Initial revision.
Browse files Browse the repository at this point in the history
  • Loading branch information
Vic Fryzel committed Jan 8, 2011
0 parents commit 4e69758
Show file tree
Hide file tree
Showing 41 changed files with 7,837 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
@@ -0,0 +1,6 @@
*.pyc
*.swp
settings.py
feeds.py
themes/minimalbw-*
contrib/passenger_wsgi.py
246 changes: 246 additions & 0 deletions README.markdown
@@ -0,0 +1,246 @@
collective: blog engine with minimal core feature set
Written by [Vic Fryzel](http://www.vicfryzel.com/)

# Introduction and features

collective is a blog engine with a small core feature set. It is intended
to be used with other hosted services by embedding them into articles.
By itself, collective is able to manage articles and static pages, but has
no support for comments, file uploads, etc. It also doesn't have a GUI-based
installer, and requires a command-line installation process.

Out of the box, collective has support for:

* Articles and static pages in [Markdown](http://daringfireball.net/projects/markdown/) syntax
* Archives
* RSS and Atom feeds
* HTML5 and CSS3, [Google Fonts API](http://code.google.com/webfonts)
* Themes
* Full page caching
* Search via [Google Custom Search](http://www.google.com/cse/)
* Comments via [Disqus](http://disqus.com/)

Out of the box, collective does _not_ support:

* Automatic imports from other blog services
* File uploads, image uploads
* Categories, tags
* Article drafts, revisions, future publishing, private articles
* OpenID authentication


# Intended audience

To use this blog software, you should probably be at least a bit technical,
and have some experience deploying web applications on a web server. If you
are not familiar with programming at all, then you may want to try a hosted
blog engine, like [Posterous](http://posterous.com),
[Tumblr](http://tumblr.com), [Blogger](http://www.blogger.com/), etc.


# Dependencies

You must have the following dependencies installed and available on the
PYTHONPATH of your server. Your startup script must also know where these
are.

* [Python 2.5 or greater](http://www.python.org/) (does not work with Python 3)
* [Django 1.2](http://www.djangoproject.com/)
* [PyMarkdown 2.0](http://www.freewisdom.org/projects/python-markdown/)
* [Pygments 1.3](http://www.pygments.org)

You'll also need a web server capable of running a Django (Python) application
behind a web server. Examples include Apache 2 with FastCGI or Passenger.
This can be a bit difficult in a shared hosting environment, but this
application is known to work on [DreamHost](http://www.dreamhost.com/).


# Configuration

To get going, you must create a database and edit three files. Once you've
done this, collective should work. Expected configuration and deployment time
is about two hours, depending on how familiar you are with configuring a
Django application.

All fields you should edit are marked EDIT_ME. To find everything you should
edit to get going, run:

grep -R EDIT_ME ./*

## Create a database

The type of database you use is up to you. This blog engine supports all
databases that are supported natively in Django, as listed
[here](http://docs.djangoproject.com/en/1.2/ref/databases/).

## Edit settings.py

Copy settings.py.template to settings.py.

cp settings.py.template settings.py

In settings.py, edit the DATABASES, SECRET_KEY, TEMPLATE_DIRS, CACHE_BACKEND,
and CACHE_MIDDLEWARE_KEY_PREFIX variables. For help, see
[the documentation](http://docs.djangoproject.com/en/1.2/ref/settings/).

## Edit feeds.py

Copy feeds.py.template to feeds.py.

cp feeds.py.template feeds.py

In feeds.py, you must edit the various options to configure your feeds to your
liking.

## Edit themes/minimalbw-yourname/base.html or create your own theme

Copy themes/minimalbw to themes/minimalbw-yourname.

cp -R themes/minimalbw themes/minimalbw-yourname

In themes/minimalbw-yourname/base.html, you'll need to configure that base HTML
template so that it reflects your blog. This includes things like page title,
[Disqus](http://disqus.com/) keys, etc.

Speaking of Disqus, make sure you register a Disqus account, and use the
appropriate unique identifiers when editing base.html.


# Deployment

You must first create your database schema.

python manage.py syncdb

When prompted, be sure to create an admin user.

To deploy this application locally (e.g. while developing a theme,) just run

python manage.py runserver

This is documented [here](http://docs.djangoproject.com/en/1.2/ref/django-admin/#runserver-port-or-ipaddr-port).
You may have to edit settings.py appropriately to enable development settings.

To deploy this application to a Passenger instance, place a link to
contrib/passenger_wsgi.py in the appropriate location on your web server.
This will vary from server to server. To do this, you must first copy the
script template, and then edit its settings so that the paths match your setup.

cp contrib/passenger_wsgi.py.template contrib/passenger_wsgi.py
# Edit contrib/passenger_wsgi.py
ln -s /path/to/collective/contrib/passenger_wsgi.py /path/to/passenger/scripts/passenger_wsgi.py

To deploy this application with Apache 2 and mod_python, see
[here](http://docs.djangoproject.com/en/1.2/howto/deployment/modpython/).

Also, setup your web server to serve themes/minimalbw/static/ in a public
directory. You can do this by creating a symlink to that directory in your
public directory on your web server.

Besides the static directory, you will also need to copy relevant admin media
to the directory that is configured for admin media in your settings.py. This
is documented [here](http://docs.djangoproject.com/en/1.2/howto/deployment/modpython/#serving-the-admin-files).

The static directory also contains a .htaccess file that will enable useful
caching operations on an Apache 2 web server.


# Importing existing data

You are more or less on your own here at this time. You can look at the
database schema, and import from your existing data source accordingly.
Obviously this will require a bit of work on your part.


# Usage

After deploying collective, you should have an empty blank page. To add
articles or static pages, login to http://yourdomain.com/admin with the
username and password created previously, and add an Article.

Articles should be written in
[Markdown](http://daringfireball.net/projects/markdown/) with optional raw
HTML for the cases in which Markdown does not have an appropriate syntax.

Based on the value of CACHE_MIDDLEWARE_SECONDS in your settings.py, pages will
be cached until the cache clears. This is important to note, because if you
edit an article or flatpage, you will not see the updates reflected until you
either clear your cache or the given number of seconds has elapsed.

After collective is deployed, you must also add search support via
[Google Custom Search](http://www.google.com/cse/). To do this, create a new
flatpage, and add the embed code that Google Custom Search gives you after
creating a custom search engine. The flatpage must have the URI /search/.


# Creating your own theme

Everything you need to create your own theme is in the themes/ directory. You
could get going with a new theme pretty quickly just by copying
themes/minimalbw to themes/mytheme, and starting from there. Once you do
this, edit settings.py, and change your TEMPLATE_DIRS setting accordingly.


# Modifying/extending collective

collective itself only really manages static pages and articles. Even then, the
amount of data managed is very minimal. Most of the features of collective are
provided in the UI. [Disqus](http://disqus.com/) is used for comments by
default. File upload mechanisms are not provided by collective on purpose,
in favor of using other hosted mechanisms. Some examples are:

* [Picasa Web Albums](http://picasaweb.google.com/)
* [Flickr](http://www.flickr.com/)
* [Google Docs](http://docs.google.com/)
* [Baconfile](http://baconfile.com/)
* [Dropbox](http://www.dropbox.com/)
* etc.

The justification for this when writing collective was that all of
those services already do an amazing job in their specific focus areas, and
nothing added here would rival them without a substantial amount of work.

To extend collective, you essentially need to know how to embed a hosted
service into your posts or theme, or implement whichever service you need in
Django.

For instance, if you wanted to change Article syntax to
[Textile](http://textile.thresholdstate.com/), you would need to edit
themes/yourtheme/articles/article.html and apply a Textile filter to the
article body instead of the Markdown filter.

Adding a WYSIWYG editor would be a bit more involved. You'd have to edit
admin templates, and also accept an article body format produced by the
editor.


# Why another blog engine?

There aren't many stable, "complete" Django-based blog engines. This is rather
ironic, given how amazingly fast it is to build a blog in Django. For more
information on this topic, see my
[blog post](http://www.vicfryzel.com/2011/01/06/finally-blog-software-doesnt-suck).


# Where did the name _collective_ come from?

At first, I had wanted to name this blog engine 'picard', as a tribute to
[Patrick Stewart](http://en.wikipedia.org/wiki/Patrick_Stewart) as
[Jean-Luc Picard](http://en.wikipedia.org/wiki/Jean-Luc_Picard). However,
there are already some existing applications/libraries named picard, and I
didn't want to polute the open-source namespace. collective works well
because just as Jean-Luc was assimilated by the Borg, he is forever part of
the collective. That's right, a little piece of Jean-Luc/Locutus is in this
blog engine.

Also, this blog engine can stand on its own, but does much better when used in
conjunction with other hosted services.


# Further development

I may make changes here as I am able, but I don't have
official plans to actively develop collective over time. That said, I'd be
weary of anyone considering this a "dead" project, as it is stable and works
great. Don't let the fact that it is not actively maintained deter you from
using it! If you need help, I'm always [available](http://www.vicfryzel.com).
Empty file added __init__.py
Empty file.
Empty file added articles/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions articles/admin.py
@@ -0,0 +1,6 @@
from articles.models import Article
from django.contrib import admin

class ArticleAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
admin.site.register(Article, ArticleAdmin)
18 changes: 18 additions & 0 deletions articles/models.py
@@ -0,0 +1,18 @@
from django.db import models

class Article(models.Model):
slug = models.SlugField(max_length=200)
title = models.CharField(max_length=200)
body = models.TextField()
pub_date = models.DateTimeField()

@models.permalink
def get_absolute_url(self):
return ('articles.views.single', (),
{'year': '%04d' % self.pub_date.year,
'month': '%02d' % self.pub_date.month,
'day': '%02d' % self.pub_date.day,
'slug': str(self.slug)})

def __unicode__(self):
return u'%s' % self.title
23 changes: 23 additions & 0 deletions articles/tests.py
@@ -0,0 +1,23 @@
"""
This file demonstrates two different styles of tests (one doctest and one
unittest). These will both pass when you run "manage.py test".
Replace these with more appropriate tests for your application.
"""

from django.test import TestCase

class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.failUnlessEqual(1 + 1, 2)

__test__ = {"doctest": """
Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}

28 changes: 28 additions & 0 deletions articles/views.py
@@ -0,0 +1,28 @@
from articles.models import Article
from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.http import Http404
from django.shortcuts import render_to_response

def index(request, page=1):
articles = Article.objects.all().order_by('-pub_date')
paginator = Paginator(articles, 5)
try:
articles = paginator.page(page)
except (EmptyPage, InvalidPage):
articles = paginator.page(paginator.num_pages)

return render_to_response('articles/index.html', {'articles': articles})

def single(request, year, month, day, slug):
try:
article = Article.objects.get(pub_date__year=year, pub_date__month=month,
pub_date__day=day, slug=slug)
except Article.DoesNotExist:
raise Http404
except ValueError:
raise Http404
return render_to_response('articles/single.html', {'article': article})

def archives(request):
articles = Article.objects.all().order_by('-pub_date')
return render_to_response('articles/archives.html', {'articles': articles})
12 changes: 12 additions & 0 deletions contrib/passenger_wsgi.py.template
@@ -0,0 +1,12 @@
import sys, os
import django.core.handlers.wsgi
sys.path.insert(0, '/path/to/dir/containing/collective') # EDIT_ME
sys.path.append('/path/to/collective') # EDIT_ME
# If in a shared hosting environment, you will have to install dependencies
# yourself, so find the relevant egg files after installing with easy_install
# and put their paths here.
sys.path.append('/path/to/Markdown-2.0.3-py2.5.egg') # EDIT_ME
sys.path.append('/path/to/Pygments-1.4-py2.5.egg') # EDIT_ME
sys.path.append(os.getcwd())
os.environ['DJANGO_SETTINGS_MODULE'] = 'collective.settings'
application = django.core.handlers.wsgi.WSGIHandler()
29 changes: 29 additions & 0 deletions feeds.py.template
@@ -0,0 +1,29 @@
from collective.articles.models import Article
from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import Atom1Feed
import markdown

class RssArticlesFeed(Feed):
title = 'My Blog Articles' # EDIT_ME
description = 'My thoughts in digital form.' # EDIT_ME
author_name = 'Your Name' # EDIT_ME
author_link = 'http://www.mydomain.com' # EDIT_ME
item_author_name = 'Your Name' # EDIT_ME
feed_copyright = 'Copyright Your Name. All rights reserved.' # EDIT_ME
item_author_link = 'http://www.mydomain.com' # EDIT_ME

link = "/"

def items(self):
return Article.objects.order_by('-pub_date')[:5]

def item_description(self, item):
return markdown.markdown(item.body)

def item_pubdate(self, item):
return item.pub_date


class AtomArticlesFeed(RssArticlesFeed):
feed_type = Atom1Feed
subtitle = RssArticlesFeed.description
11 changes: 11 additions & 0 deletions manage.py
@@ -0,0 +1,11 @@
#!/usr/bin/env python2
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)
Empty file added middleware/__init__.py
Empty file.

0 comments on commit 4e69758

Please sign in to comment.