commit 539bdb04ac7091b4398d718fe8d1a14c2faf06f4 Author: Jeremy Carbaugh Date: Fri Mar 27 13:38:19 2009 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e99e36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc \ No newline at end of file diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown new file mode 100644 index 0000000..034bbad --- /dev/null +++ b/CHANGELOG.markdown @@ -0,0 +1,5 @@ +# Changelog for django-wordpress + +## 0.1.0 + +* initial release \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..48ec32b --- /dev/null +++ b/LICENSE @@ -0,0 +1,10 @@ +Copyright (c) 2009, Sunlight Foundation +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Sunlight Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..eb4a2e4 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include LICENSE +include README.markdown +recursive-include wordpress *.py +recursive-include wordpress/templates *.html \ No newline at end of file diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..201b284 --- /dev/null +++ b/README.markdown @@ -0,0 +1,54 @@ +# django-wordpress + +Models and views for reading a WordPress database. Compatible with WordPress version 2.6.1. + +These models are meant to be read-only. Writing is enabled by adding *WP_READ_ONLY = False* to settings.py. None of the WordPress specific logic is included while writing to the database so there is a good chance you will break your WordPress install if you enable writing. + +The default table prefix is *wp*. To change the table prefix, add *WP_TABLE_PREFIX = 'yourprefix'* to settings.py. + +Default templates are provided only for development purposes! Please override these with customized templates for your application. + +django-wordpress is a project of Sunlight Foundation (c) 2009. +Writen by Jeremy Carbaugh + +All code is under a BSD-style license, see LICENSE for details. + +Source: http://github.com/sunlightlabs/django-wordpress/ + + +## Requirements + +python >= 2.4 + +django >= 1.0 + + +## Installation + +To install run + + python setup.py install + +which will install the application into python's site-packages directory. + + +## Quick Setup + + +### settings.py + +Add to INSTALLED_APPS: + + wordpress + + +### urls.py + +Include the following in urls.py. + + url(r'^path/to/blog/', include('')), + + +## Help! + +The term/taxonomy support is quite shoddy. Any help in that area would be greatly appreciated. \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..72e6de9 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +from distutils.core import setup + +long_description = open('README.markdown').read() + +setup( + name='django-wordpress', + version="0.1", + description='Django models and views for a WordPress database', + long_description=long_description, + author='Jeremy Carbaugh', + author_email='jcarbaugh@sunlightfoundation.com', + url='http://github.com/sunlightlabs/django-wordpress/', + packages=['wordpress'], + package_data={'wordpress': ['templates/wordpress/*.html']}, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Environment :: Web Environment', + ], + license='BSD License', + platforms=["any"], +) \ No newline at end of file diff --git a/wordpress/__init__.py b/wordpress/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wordpress/admin.py b/wordpress/admin.py new file mode 100644 index 0000000..715c50c --- /dev/null +++ b/wordpress/admin.py @@ -0,0 +1,45 @@ +from django.contrib import admin +from wordpress.models import Comment, Link, Post, PostMeta, Taxonomy, Term, User, UserMeta + +class CommentAdmin(admin.ModelAdmin): + list_display = ('id','post','author_name','post_date') + list_filter = ('comment_type','approved') + search_fields = ('author_name','author_email','post__title') + +class LinkAdmin(admin.ModelAdmin): + list_display = ('id','name','url','description') + list_filter = ('visible',) + search_fields = ('name','url','description') + +class PostMetaInline(admin.TabularInline): + model = PostMeta + +class PostAdmin(admin.ModelAdmin): + inlines = (PostMetaInline,) + list_display = ('id','title','author','post_date') + list_filter = ('status','post_type','comment_status','ping_status','author') + search_fields = ('title',) + +class UserMetaInline(admin.TabularInline): + model = UserMeta + +class UserAdmin(admin.ModelAdmin): + inlines = (UserMetaInline,) + list_display = ('id','display_name','email','status') + list_filter = ('status',) + search_fields = ('login','username','display_name','email') + +class TaxonomyAdmin(admin.ModelAdmin): + list_display = ('id','name','term') + list_filter = ('name',) + +class TermAdmin(admin.ModelAdmin): + list_display = ('id','name') + search_fields = ('name',) + +admin.site.register(Comment, CommentAdmin) +admin.site.register(Link, LinkAdmin) +admin.site.register(Post, PostAdmin) +admin.site.register(Taxonomy, TaxonomyAdmin) +admin.site.register(Term, TermAdmin) +admin.site.register(User, UserAdmin) \ No newline at end of file diff --git a/wordpress/models.py b/wordpress/models.py new file mode 100644 index 0000000..f8efb3d --- /dev/null +++ b/wordpress/models.py @@ -0,0 +1,362 @@ +from django.conf import settings +from django.core.urlresolvers import reverse +from django.db import connection, models +from django.db.models import signals +from django.http import HttpResponseRedirect +import re + +STATUS_CHOICES = ( + ('closed', 'closed'), + ('open', 'open'), +) + +POST_STATUS_CHOICES = ( + ('draft', 'draft'), + ('inherit', 'inherit'), + ('private', 'private'), + ('publish', 'publish'), +) + +POST_TYPE_CHOICES = ( + ('attachment','attachment'), + ('page','page'), + ('post','post'), + ('revision','revision'), +) + +USER_STATUS_CHOICES = ( + (0, "active"), +) + +READ_ONLY = getattr(settings, "WP_READ_ONLY", True) +TABLE_PREFIX = getattr(settings, "WP_TABLE_PREFIX", "wp") + +# +# Exceptions +# + +class WordPressException(Exception): + """ + Exception that is thrown when attempting to save a read-only object. + """ + pass + +# +# Base models +# + +class WordPressModel(models.Model): + """ + Base model for all WordPress objects. + Overrides save and delete methods to enforce read-only setting. + """ + class Meta: + abstract = True + + def _get_object(self, model, obj_id): + try: + return model.objects.get(pk=obj_id) + except model.DoesNotExist: + pass + + def save(self, override=False): + if READ_ONLY and not override: + raise WordPressException, "object is read-only" + super(WordPressModel, self).save() + + def delete(self, override=False): + if READ_ONLY and not override: + raise WordPressException, "object is read-only" + super(WordPressModel, self).delete() + +# +# WordPress models +# + +class User(WordPressModel): + """ + User object. Referenced by Posts, Comments, and Links + """ + login = models.CharField(max_length=60, db_column='user_login') + password = models.CharField(max_length=64, db_column='user_pass') + username = models.CharField(max_length=255, db_column='user_nicename') + email = models.CharField(max_length=100, db_column='user_email') + url = models.URLField(max_length=100, db_column='user_url', verify_exists=False) + date_registered = models.DateTimeField(auto_now_add=True, db_column='user_registered') + activation_key = models.CharField(max_length=60, db_column='user_activation_key') + status = models.IntegerField(default=0, choices=USER_STATUS_CHOICES, db_column='user_status') + display_name = models.CharField(max_length=255, db_column='display_name') + + class Meta: + db_table = '%s_users' % TABLE_PREFIX + ordering = ["display_name"] + + def __unicode__(self): + return self.display_name + +class UserMeta(WordPressModel): + """ + Meta information about a user. + """ + id = models.IntegerField(db_column='umeta_id', primary_key=True) + user = models.ForeignKey(User, related_name="meta", db_column='user_id') + key = models.CharField(max_length=255, db_column='meta_key') + value = models.TextField(db_column='meta_value') + + class Meta: + db_table = '%s_usermeta' % TABLE_PREFIX + + def __unicode__(self): + return u"%s: %s" % (self.key, self.value) + +class Link(WordPressModel): + """ + An external link. + """ + id = models.IntegerField(db_column='link_id', primary_key=True) + url = models.URLField(max_length=255, verify_exists=False, db_column='link_url') + name = models.CharField(max_length=255, db_column='link_name') + image = models.CharField(max_length=255, db_column='link_image') + target = models.CharField(max_length=25, db_column='link_target') + category_id = models.IntegerField(default=0, db_column='link_category') + description = models.CharField(max_length=255, db_column='link_description') + visible = models.CharField(max_length=20, db_column='link_visible') + owner = models.ForeignKey(User, related_name='links', db_column='link_owner') + rating = models.IntegerField(default=0, db_column='link_rating') + updated = models.DateTimeField(blank=True, null=True, db_column='link_updated') + rel = models.CharField(max_length=255, db_column='link_rel') + notes = models.TextField(db_column='link_notes') + rss = models.CharField(max_length=255, db_column='link_rss') + + class Meta: + db_table = '%s_links' % TABLE_PREFIX + + def __unicode__(self): + return u"%s %s" % (self.name, self.url) + + def is_visible(self): + return self.visible == 'Y' + +class PostManager(models.Manager): + """ + Provides convenience methods for filtering posts by status. + """ + + def _by_status(self, status, post_type='post'): + return Post.objects.filter(status=status, post_type=post_type) + + def drafts(self, post_type='post'): + return self._by_status('draft', post_type) + + def private(self, post_type='post'): + return self._by_status('private', post_type) + + def published(self, post_type='post'): + return self._by_status('publish', post_type) + + def term(self, term, taxonomy='post_tag'): + tx = Taxonomy.objects.get(name=taxonomy, term__name=term) + table = '%s_term_relationships' % TABLE_PREFIX + sql = """SELECT object_id FROM """ + table + """ WHERE term_taxonomy_id = %s""" + cursor = connection.cursor() + cursor.execute(sql, [tx.pk,]) + pids = [row[0] for row in cursor.fetchall()] + return Post.objects.published().filter(pk__in=pids) + +class Post(WordPressModel): + """ + The mother lode. + The WordPress post. + """ + + objects = PostManager() + + # post data + guid = models.CharField(max_length=255) + post_type = models.CharField(max_length=20, choices=POST_TYPE_CHOICES) + status = models.CharField(max_length=20, db_column='post_status', choices=POST_STATUS_CHOICES) + title = models.TextField(db_column='post_title') + slug = models.SlugField(max_length=200, db_column="post_name") + author = models.ForeignKey(User, related_name='posts', db_column='post_author') + excerpt = models.TextField(db_column='post_excerpt') + content = models.TextField(db_column='post_content') + content_filtered = models.TextField(db_column='post_content_filtered') + post_date = models.DateTimeField(db_column='post_date_gmt') + modified = models.DateTimeField(db_column='post_modified_gmt') + + # comment stuff + comment_status = models.CharField(max_length=20, choices=STATUS_CHOICES) + comment_count = models.IntegerField(default=0) + + # ping stuff + ping_status = models.CharField(max_length=20, choices=STATUS_CHOICES) + to_ping = models.TextField() + pinged = models.TextField() + + # statuses + password = models.CharField(max_length=20, db_column="post_password") + category_id = models.IntegerField(db_column='post_category') + + # other various lame fields + parent = models.ForeignKey('self', related_name="children", db_column="post_parent", blank=True, null=True) + menu_order = models.IntegerField(default=0) + mime_type = models.CharField(max_length=100, db_column='post_mime_type') + + category_cache = None + tag_cache = None + + class Meta: + db_table = '%s_posts' % TABLE_PREFIX + ordering = ["-post_date"] + + def __unicode__(self): + return self.title + + def categories(self): + if not self.category_cache: + taxonomy = "category" + self.category_cache = self._get_terms(taxonomy) + return self.category_cache + + def get_absolute_url(self): + year = self.post_date.year + month = self.post_date.month + day = self.post_date.day + slug = self.slug + print reverse('wp_object_detail', args=(year, month, day, slug)) + return reverse('wp_object_detail', args=(year, month, day, slug)) + + """ + @models.permalink + def get_absolute_url(self): + params = { + "year": self.post_date.year, + "month": self.post_date.month, + "day": self.post_date.day, + "slug": self.slug, + } + return ('wp_object_detail', (), params) + """ + + """ + def parent(self): + return self._get_object(Post, self.parent_id) + """ + + def tags(self): + if not self.tag_cache: + taxonomy = "post_tag" + self.tag_cache = self._get_terms(taxonomy) + return self.tag_cache + + def _get_terms(self, taxonomy): + table = '%s_term_relationships' % TABLE_PREFIX + sql = """SELECT term_taxonomy_id FROM """ + table + """ WHERE object_id = %s ORDER BY term_order""" + cursor = connection.cursor() + cursor.execute(sql, [self.id,]) + ttids = [row[0] for row in cursor.fetchall()] + return Term.objects.filter(taxonomies__name=taxonomy, taxonomies__pk__in=ttids) + +class PostMeta(WordPressModel): + """ + Post meta data. + """ + id = models.IntegerField(db_column='meta_id', primary_key=True) + post = models.ForeignKey(Post, related_name='meta', db_column='post_id') + key = models.CharField(max_length=255, db_column='meta_key') + value = models.TextField(db_column='meta_value') + + class Meta: + db_table = '%s_postmeta' % TABLE_PREFIX + + def __unicode__(self): + return u"%s: %s" % (self.key, self.value) + +class Comment(WordPressModel): + """ + Comments to Posts. + """ + id = models.IntegerField(db_column='comment_id', primary_key=True) + post = models.ForeignKey(Post, related_name="comments", db_column="comment_post_id") + user_id = models.IntegerField(db_column='user_id', default=0) + #user = models.ForeignKey(User, related_name="comments", blank=True, null=True, default=0 ) + parent_id = models.IntegerField(default=0, db_column='comment_parent') + + # author fields + author_name = models.CharField(max_length=255, db_column='comment_author') + author_email = models.EmailField(max_length=100, db_column='comment_author_email') + author_url = models.URLField(verify_exists=False, db_column='comment_author_url') + author_ip = models.IPAddressField(db_column='comment_author_ip') + + # comment data + post_date = models.DateTimeField(db_column='comment_date_gmt') + content = models.TextField(db_column='comment_content') + karma = models.IntegerField(default=0, db_column='comment_karma') + approved = models.CharField(max_length=20, db_column='comment_approved') + + # other stuff + agent = models.CharField(max_length=255, db_column='comment_agent') + comment_type = models.CharField(max_length=20) + + class Meta: + db_table = '%s_comments' % TABLE_PREFIX + ordering = ['-post_date'] + + def __unicode__(self): + return u"%s on %s" % (self.author_name, self.post.title) + + def get_absolute_url(self): + return "%s#comment-%i" % (self.post.get_absolute_url(), self.pk) + + def parent(self): + return self._get_object(Comment, self.parent_id) + + """ + def user(self): + return self._get_object(User, self.user_id) + """ + + def is_approved(self): + return self.approved == '1' + + def is_spam(self): + return self.approved == 'spam' + +class Term(WordPressModel): + id = models.IntegerField(db_column='term_id', primary_key=True) + name = models.CharField(max_length=200) + slug = models.SlugField(max_length=200) + group = models.IntegerField(default=0, db_column='term_group') + + class Meta: + db_table = '%s_terms' % TABLE_PREFIX + ordering = ['name',] + + def __unicode__(self): + return self.name + +class Taxonomy(WordPressModel): + id = models.IntegerField(db_column='term_taxonomy_id', primary_key=True) + term = models.ForeignKey(Term, related_name='taxonomies', blank=True, null=True) + #term_id = models.IntegerField() + name = models.CharField(max_length=32, db_column='taxonomy') + description = models.TextField() + parent_id = models.IntegerField(default=0, db_column='parent') + count = models.IntegerField(default=0) + + class Meta: + db_table = '%s_term_taxonomy' % TABLE_PREFIX + ordering = ['name',] + + def __unicode__(self): + try: + term = self.term + except Term.DoesNotExist: + term = '' + return u"%s: %s" % (self.name, term) + + def parent(self): + return self._get_object(Taxonomy, self.parent_id) + + #def term(self): + # return self._get_object(Term, self.term_id) \ No newline at end of file diff --git a/wordpress/templates/wordpress/post_archive.html b/wordpress/templates/wordpress/post_archive.html new file mode 100644 index 0000000..65ba12e --- /dev/null +++ b/wordpress/templates/wordpress/post_archive.html @@ -0,0 +1 @@ +{{ latest }} \ No newline at end of file diff --git a/wordpress/templates/wordpress/post_archive_day.html b/wordpress/templates/wordpress/post_archive_day.html new file mode 100644 index 0000000..fc4f0b5 --- /dev/null +++ b/wordpress/templates/wordpress/post_archive_day.html @@ -0,0 +1 @@ +{{ post_list }} \ No newline at end of file diff --git a/wordpress/templates/wordpress/post_archive_month.html b/wordpress/templates/wordpress/post_archive_month.html new file mode 100644 index 0000000..fc4f0b5 --- /dev/null +++ b/wordpress/templates/wordpress/post_archive_month.html @@ -0,0 +1 @@ +{{ post_list }} \ No newline at end of file diff --git a/wordpress/templates/wordpress/post_archive_year.html b/wordpress/templates/wordpress/post_archive_year.html new file mode 100644 index 0000000..9e52c42 --- /dev/null +++ b/wordpress/templates/wordpress/post_archive_year.html @@ -0,0 +1,5 @@ +
    + {% for date in date_list %} +
  1. {{ date|date:"F" }}
  2. + {% endfor %} +
\ No newline at end of file diff --git a/wordpress/templates/wordpress/post_detail.html b/wordpress/templates/wordpress/post_detail.html new file mode 100644 index 0000000..200823b --- /dev/null +++ b/wordpress/templates/wordpress/post_detail.html @@ -0,0 +1 @@ +{{ post }} \ No newline at end of file diff --git a/wordpress/templates/wordpress/post_term.html b/wordpress/templates/wordpress/post_term.html new file mode 100644 index 0000000..fc4f0b5 --- /dev/null +++ b/wordpress/templates/wordpress/post_term.html @@ -0,0 +1 @@ +{{ post_list }} \ No newline at end of file diff --git a/wordpress/urls.py b/wordpress/urls.py new file mode 100644 index 0000000..ee570aa --- /dev/null +++ b/wordpress/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns('wordpress.views', + url(r'^taxonomy/(?Pterm|category)/(?P[\w-]+)/$', 'taxonomy', name='wp_taxonomy'), + url(r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/(?P[-\w]+)/$', 'object_detail', name='wp_object_detail'), + url(r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/$', 'archive_day', name='wp_archive_day'), + url(r'^(?P\d{4})/(?P\d{1,2})/$', 'archive_month', name='wp_archive_month'), + url(r'^(?P\d{4})/$', 'archive_year', name='wp_archive_year'), + url(r'^$', 'archive_index', name='wp_archive_index'), +) \ No newline at end of file diff --git a/wordpress/views.py b/wordpress/views.py new file mode 100644 index 0000000..5f84224 --- /dev/null +++ b/wordpress/views.py @@ -0,0 +1,43 @@ +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response +from django.views.generic import date_based +from wordpress.models import Post + +TAXONOMIES = { + 'term': 'post_tag', + 'category': 'category', + 'link_category': 'link_category', +} + +def object_detail(request, year, month, day, slug): + return date_based.object_detail(request, queryset=Post.objects.published(), + date_field='post_date', year=year, month=month, month_format="%m", + day=day, slug=slug, slug_field='slug', template_object_name='post') + +def archive_day(request, year, month, day): + return date_based.archive_day(request, queryset=Post.objects.published(), + date_field='post_date', year=year, month=month, month_format="%m", + day=day, template_object_name='post') + +def archive_month(request, year, month): + return date_based.archive_month(request, queryset=Post.objects.published(), + date_field='post_date', year=year, month=month, month_format="%m", + template_object_name='post') + +def archive_year(request, year): + return date_based.archive_year(request, queryset=Post.objects.published(), + date_field='post_date', year=year) + +def archive_index(request): + p = request.GET.get('p', None) + if p: + post = Post.objects.get(pk=p) + return HttpResponseRedirect(post.get_absolute_url()) + return date_based.archive_index(request, + queryset=Post.objects.published(), date_field='post_date') + +def taxonomy(request, taxonomy, term): + taxonomy = TAXONOMIES.get(taxonomy, None) + if taxonomy: + posts = Post.objects.term(term, taxonomy=taxonomy) + return render_to_response('wordpress/post_term.html', {'post_list': posts}) \ No newline at end of file