Adam Knight avatar Adam Knight committed cc8a362

Tabs -> Soft Tabs (4)

Comments (0)

Files changed (17)

 ## ACTIONS ##
 
 def action_mark_draft(model_admin, request, query_set):
-	query_set.update(status=ENTRY_STATUS_DRAFT)
+    query_set.update(status=ENTRY_STATUS_DRAFT)
 action_mark_draft.short_description = "Change status of selected items to Draft"
 
 def action_mark_published(model_admin, request, query_set):
-	query_set.update(status=ENTRY_STATUS_PUBLISHED)
+    query_set.update(status=ENTRY_STATUS_PUBLISHED)
 action_mark_published.short_description = "Change status of selected items to Published"
 
 def action_open_comments(model_admin, request, query_set):
-	query_set.update(allow_comments=COMMENTS_ENABLED)
+    query_set.update(allow_comments=COMMENTS_ENABLED)
 action_open_comments.short_description = "Allow comments on the selected items"
 
 def action_close_comments(model_admin, request, query_set):
-	query_set.update(allow_comments=COMMENTS_CLOSED)
+    query_set.update(allow_comments=COMMENTS_CLOSED)
 action_close_comments.short_description = "Close comments on the selected items"
 
 def action_disable_comments(model_admin, request, query_set):
-	query_set.update(allow_comments=COMMENTS_DISABLED)
+    query_set.update(allow_comments=COMMENTS_DISABLED)
 action_disable_comments.short_description = "Disable and hide comments on the selected items"
 
 ### PAGES ###
 
 class PageForm(forms.ModelForm):
-	url = forms.RegexField(label=_("URL"), max_length=255, regex=r'^[-\w/]+$',
-		help_text	  = _("Example: '/about/contact/'. Make sure to have leading"
-					  " and trailing slashes."),
-		error_message = _("This value must contain only letters, numbers,"
-						  " underscores, dashes or slashes."))
-	class Meta:
-		model = Page
+    url = forms.RegexField(label=_("URL"), max_length=255, regex=r'^[-\w/]+$',
+        help_text      = _("Example: '/about/contact/'. Make sure to have leading"
+                      " and trailing slashes."),
+        error_message = _("This value must contain only letters, numbers,"
+                          " underscores, dashes or slashes."))
+    class Meta:
+        model = Page
 
 
 class PageAdmin(admin.ModelAdmin):
-	form		   = PageForm
-	list_display   = ('title', 'absolute_url', 'visible', 'status', 'date_created', 'date_modified', 'date_published', 'date_hidden')
-	list_editable  = ('status',)
-	list_filter	   = ('date_published', 'date_modified', 'status')
-	date_hierarchy = 'date_published'
-	search_fields  = ('title', 'url')
-	actions		   = [action_mark_draft, action_mark_published]
-	fieldsets	   = [
-		('',				{'fields': ['title', 'url', 'sites']}),
-		(_('Content'),		{'fields': ['content_format', 'content', 'template_name']}),
-		(_('Availability'), {'fields': ['status', 'date_published', 'date_hidden'], 'classes':('collapse',)}),
-	]
-	
-	def absolute_url(self, obj):
-		return obj.get_absolute_url()
+    form           = PageForm
+    list_display   = ('title', 'absolute_url', 'visible', 'status', 'date_created', 'date_modified', 'date_published', 'date_hidden')
+    list_editable  = ('status',)
+    list_filter       = ('date_published', 'date_modified', 'status')
+    date_hierarchy = 'date_published'
+    search_fields  = ('title', 'url')
+    actions           = [action_mark_draft, action_mark_published]
+    fieldsets       = [
+        ('',                {'fields': ['title', 'url', 'sites']}),
+        (_('Content'),        {'fields': ['content_format', 'content', 'template_name']}),
+        (_('Availability'), {'fields': ['status', 'date_published', 'date_hidden'], 'classes':('collapse',)}),
+    ]
+    
+    def absolute_url(self, obj):
+        return obj.get_absolute_url()
 
 admin.site.register(Page, PageAdmin)
 
 ### STORIES ###
 
 class StoryAdmin(admin.ModelAdmin):
-	list_display = ('title', 'tags', 'status', 'allow_comments', 'date_published', 'date_hidden', 'date_modified')
-	list_editable = ('status',)
-	list_filter = ('date_published', 'date_hidden', 'date_modified', 'status')
-	date_hierarchy = 'date_published'
-	search_fields = ('title', '^user__username', 'slug')
-	prepopulated_fields = {"slug": ("title",)}
-	actions = [action_mark_draft, action_mark_published, action_open_comments, action_close_comments, action_disable_comments]
-	fieldsets = [
-		('',				{'fields': ['user', 'title', 'slug', 'tags', 'section']}),
-		('Content',			{'fields': ['teaser_format', 'teaser', 'content_format', 'content']}),
-		('Availability',	{'fields': ['status', 'date_published', 'date_hidden', 'allow_comments', 'sites'], 'classes':('collapse',)}),
-		]
+    list_display = ('title', 'tags', 'status', 'allow_comments', 'date_published', 'date_hidden', 'date_modified')
+    list_editable = ('status',)
+    list_filter = ('date_published', 'date_hidden', 'date_modified', 'status')
+    date_hierarchy = 'date_published'
+    search_fields = ('title', '^user__username', 'slug')
+    prepopulated_fields = {"slug": ("title",)}
+    actions = [action_mark_draft, action_mark_published, action_open_comments, action_close_comments, action_disable_comments]
+    fieldsets = [
+        ('',                {'fields': ['user', 'title', 'slug', 'tags', 'section']}),
+        ('Content',            {'fields': ['teaser_format', 'teaser', 'content_format', 'content']}),
+        ('Availability',    {'fields': ['status', 'date_published', 'date_hidden', 'allow_comments', 'sites'], 'classes':('collapse',)}),
+        ]
 
 admin.site.register(Story, StoryAdmin)
 
 class ContentHistoryAdmin(admin.ModelAdmin):
-	list_display = ('title', 'content_type', 'object_id', 'field_name', 'date_created')
-	list_filter = ('field_name', 'date_created')
-	date_hierarchy = 'date_created'
-	search_fields = ('field_name', 'content')
-	
-	def title(self, obj):
-		return obj.__unicode__()
-	title.short_description = "Item Name"
-	
+    list_display = ('title', 'content_type', 'object_id', 'field_name', 'date_created')
+    list_filter = ('field_name', 'date_created')
+    date_hierarchy = 'date_created'
+    search_fields = ('field_name', 'content')
+    
+    def title(self, obj):
+        return obj.__unicode__()
+    title.short_description = "Item Name"
+    
 admin.site.register(ContentHistory, ContentHistoryAdmin)
 
 ### SECTIONS ###
 
 class SectionAdmin(admin.ModelAdmin):
-	list_display = ("name", "slug")
-	prepopulated_fields = {"slug": ("name",)}
+    list_display = ("name", "slug")
+    prepopulated_fields = {"slug": ("name",)}
 
 admin.site.register(Section, SectionAdmin)
-	
+    
 ### REDIRECTION ###
 
 class RedirectAdmin(admin.ModelAdmin):
-	list_display = ("original", "current", "content_type", "object_id", "permanent", "date_modified", "date_created")
-	fieldsets = (
-		("Source", {"fields": ["site", "original"]}),
-		("Destination", {"fields": ["current", "content_type", "object_id"]}),
-		("Options", {"fields": ["permanent"]})
-	)
+    list_display = ("original", "current", "content_type", "object_id", "permanent", "date_modified", "date_created")
+    fieldsets = (
+        ("Source", {"fields": ["site", "original"]}),
+        ("Destination", {"fields": ["current", "content_type", "object_id"]}),
+        ("Options", {"fields": ["permanent"]})
+    )
 admin.site.register(Redirect, RedirectAdmin)

context_preprocessors/__init__.py

 from django.contrib.sites.models import Site
 
 def general(request):
-	return {
-		"site": Site.objects.get_current(),
-	}
+    return {
+        "site": Site.objects.get_current(),
+    }

drupal_support/management/commands/convertdrupal.py

 from threadedcomments.models import ThreadedComment
 
 def vancode2int(vancode):
-	if len(vancode):
-		result = int(vancode[1:], 36)
-	else:
-		result = None
-	return result
+    if len(vancode):
+        result = int(vancode[1:], 36)
+    else:
+        result = None
+    return result
 
 class Command(BaseCommand):
-	# option_list = BaseCommand.option_list + (
-	# 	make_option('--format', default='json', dest='format', help='Specifies the output serialization format for fixtures.'),
-	# 	make_option('--indent', default=None, dest='indent', type='int', help='Specifies the indent level to use when pretty-printing output'),
-	# 	make_option('-e', '--exclude', dest='exclude',action='append', default=[], help='App to exclude (use multiple --exclude to exclude multiple apps).'),
-	# 	make_option('-v', '--verbose', dest='verbose', action="store", default="0", type="choice", choices=['0','1','2'], help='Verbose output'),
-	# )
-	help = 'Convert Drupal 6 tables to SP objects.	The tables must be in the same database as SP.'
-	# args = '[appname ...]'
-	
-	def handle(self, *app_labels, **options):
-		show_traceback = options.get('traceback', False)
-		verbose = int(options.get('verbosity', 0))
-		debug = (verbose == 2)
-		user = User.objects.get(pk=1)
-		
-		if verbose: print "Starting Drupal conversion."
-		if debug: print "* Debug logging enabled."
-		if debug: print "* Importing data with %s as the content owner." % user.username
-		
-		try:
-			# Get a list of nodes together and filter out blog nodes.
-			node_list = DrupalNode.objects.filter(type__in=("blog", "story", "page")).order_by("nid")
-			if verbose: print "Found %d nodes." % len(node_list)
-			
-			if len(node_list) == 0:
-				raise CommandError("No Drupal nodes found in the current database.")
-			
-			# Create some sections
-			(blog_section, c) = Section.objects.get_or_create(name="blog")
-			if c:
-				if debug: print "* Created blog section."
-				blog_section.save()
-			
-			(story_section, c) = Section.objects.get_or_create(name="story")
-			if c:
-				if debug: print "* Created story section."
-				story_section.save()
-			
-			for node in node_list:
-				# Our document object
-				obj = None
-				
-				# See if we've imported this node before
-				node_aliases = Redirect.objects.filter(original='/node/%d' % node.nid)
-				if node_aliases.count() > 0:
-					if verbose: print "Skipping node %d" % node.nid
-					continue
-				
-				# We haven't imported this.  Let's begin...
-				if verbose: print "Processing node (%d) %s" % (node.nid, node.title)
-				
-				if node.type == "blog" or node.type == "story":
-					if node.type == "blog":
-						if debug: print "Node is a blog."
-						section = blog_section
-					else:
-						if debug: print "Node is a story."
-						section = story_section
-					
-					# Create an Story for the node
-					obj = Story(
-						user = user,
-						title = node.title,
-						date_created = datetime.datetime.fromtimestamp(node.created),
-						date_modified = datetime.datetime.fromtimestamp(node.changed),
-						date_published = datetime.datetime.fromtimestamp(node.created),
-						slug = slugify(node.title),
-						allow_comments = node.comment,
-						status = node.status,
-						section = section,
-						)
-				
-					# Set the current content value
-					try:
-						contents = node.current_revision.get_parsed_contents()
-					
-						obj.teaser = contents['teaser']
-						obj.content = contents['body']
-					except DrupalNodeRevision.DoesNotExist, e:
-						if verbose: print "WARNING: No body content found for %s (%d)" % (obj.title, obj.id)
-				
-					# Save the story
-					obj.save()
-					
-					# Add to the current site
-					obj.sites.add(Site.objects.get_current())
-					
-					# Import terms as tags
-					terms = node.current_revision.terms.all()
-					tags = []
-					for term in terms:
-						tags.append(term.name)
-					obj.tags = ','.join(tags) + ','
-					if debug: print "* Set tags to: %s" % obj.tags
-					
-					# Ensure the modification date is proper
-					obj.date_modified = datetime.datetime.fromtimestamp(node.changed)
-					obj.save()
-					
-				elif node.type == "page":
-					if debug: print "Node is a page."
-					
-					# Create an object for the node
-					obj = Page(
-						title = node.title,
-						date_created = datetime.datetime.fromtimestamp(node.created),
-						date_modified = datetime.datetime.fromtimestamp(node.changed),
-						date_published = datetime.datetime.fromtimestamp(node.created),
-						status = node.status,
-						)
-					
-					# Set the current content value
-					try:
-						obj.content = node.current_revision.body
-					except DrupalNodeRevision.DoesNotExist, e:
-						if verbose: print "WARNING: No body content found for %s (%d)" % (obj.title, obj.id)
-					
-					# Save
-					obj.save()
-					
-					# Add to the current site
-					obj.sites.add(Site.objects.get_current())
-					
-					# Find the most recent URL for this object and assign it
-					aliases = DrupalUrlAlias.objects.filter(src='node/%d' % node.nid).order_by('-pid')
-					if aliases.count():
-						if debug: print "* Page aliases:", aliases
-						obj.url = '/' + aliases[0].dst
-					
-					# Ensure the modification date is proper
-					obj.date_modified = datetime.datetime.fromtimestamp(node.changed)
-					obj.save()
-				
-				else:
-					# Bail if we don't have something to work on
-					if verbose: print "Unsupported type:", node.type
-					continue
-				
-				# Lookup any URL aliases for this node and create redirects
-				aliases = DrupalUrlAlias.objects.filter(src='node/%d' % node.nid)
-				
-				# First, the core "node/1" links should still work
-				r = Redirect(original="/node/%d" % node.nid, target_object=obj)
-				r.save()
-				
-				# Now we get any others that were made
-				for alias in aliases:
-					dst = "/" + alias.dst
-					if obj.__class__ == Page and dst == obj.url: continue
-					try:
-						r = Redirect(original=dst, target_object=obj)
-						r.save()
-						if debug: print " Created redirect from", dst
-					except Exception, e:
-						if verbose: print "* Failed to create redirect from", dst, "to", obj, ":", e
-						continue
-				
-				# Create a ContentHistory for each old body value
-				if node.revisions.count() > 1:
-					for revision in node.revisions.all()[1:]:
-						date = datetime.datetime.fromtimestamp(revision.timestamp)
-						
-						contents = revision.get_parsed_contents()
-						
-						ch = ContentHistory(
-							date_created = date,
-							date_modified = date,
-							owner = obj,
-							field_name = "teaser",
-							content = contents['teaser'],
-						)
-						ch.save()
-						
-						ch = ContentHistory(
-							date_created = date,
-							date_modified = date,
-							owner = obj,
-							field_name = "content",
-							content = contents['body'],
-						)
-						ch.save()
-						
-						if debug: print " Added revision %s" % (ch.date_created)
-				
-				# Bring over the comments
-				comments = self.get_comments_for_node(node)
-				if debug: print "  Comments:", comments.all()
-				for comment in comments:
-					if debug: print "  Importing comment: ", comment.thread, comment.subject
-					
-					# To handle the nested comments:
-					# 	Break up the thread property: 01.01.01 -> [1,1,1]
-					# 	Pop off the last item, as that is the location of the current comment (which we cannot directly set)
-					# 	For each remaining level, get the right relationship in a loop:
-					# 		Set an object to the first listed comment.
-					# 		Pop that value off the array.
-					# 		If there're more items left, set the loop object to that child of the current object.
-					# 		When we run out of list items, we found the parent.
-					
-					# 01.00.05.07/ to 01.00.05.07 to [01,00,05,07]
-					parents = comment.thread[:-1].split('.')
-					# [1,0,5,7]
-					parents = map(vancode2int, parents)
-					# [1,0,5]
-					parents = parents[:-1]
-					if len(parents):
-						# [0,0,5]
-						parents[0] = parents[0] - 1
-					
-					if debug: print "** Parents:", parents, "(%s)" % comment.thread
-					
-					c_obj = None
-					if len(parents):
-						ct = ContentType.objects.get_for_model(obj.__class__)
-						obj_comments = ThreadedComment.objects.filter(content_type=ct, object_pk=str(obj.id)).order_by('submit_date')
-						if debug:
-							print "* Object comments (%d):" % obj_comments.count(), obj_comments
-							print "* Parents (%d):" % len(parents), parents
-						try:
-							c_obj = obj_comments[parents[0]]
-						except IndexError, e:
-							if verbose: print "*** Parent of comment subtree not found.  This can happen if a thread has a deleted comment."
-						parents = parents[1:]
-						while c_obj and len(parents):
-							if c_obj.children.count() > parents[0]:
-								c_obj = c_obj.children.all()[parents[0]]
-								parents = parents[1:]
-							else:
-								if verbose: print "** Object has %d comments, but the parent should be item %d" % (c_obj.children.count(), parents[0])
-								break
-						if debug and c_obj: print "** Suspected parent:", c_obj.title
-					
-					# Actually create the comment now
-					c = ThreadedComment(
-						content_object = obj,
-						title = unicode(comment.subject)[:200],
-						ip_address = unicode(comment.hostname)[:15],
-						user_name = unicode(comment.name)[:50],
-						user_email = unicode(comment.mail)[:75],
-						user_url = unicode(comment.homepage)[:200],
-						submit_date = datetime.datetime.fromtimestamp(comment.timestamp),
-						comment = unicode(comment.comment),
-						site = Site.objects.get_current(),
-						is_public = (not comment.status),
-					)
-					c.save()
-					
-					# Now that the comment exists, we can set the parent.
-					c.parent = c_obj
-					c.save()
-					
-					if verbose: print " Imported comment (%d) %s" % (comment.cid, comment.subject)
-				
-				if debug: print " Finished node: %s (%s)" % (obj.title, obj.get_absolute_url())
-			
-		except Exception, e:
-			if show_traceback:
-				raise
-			raise CommandError("Error: %s" % e)
-		
-		if verbose: print "Done."
-	
-	def get_comments_for_node(self, node):
-		'''
-		This generally requires a "real" database like Postgresql or MySQL.  Sqlite does not support SUBSTRING.
-		But, that's okay.  Drupal required MySQL anyway, so do the conversion on a copy of the DB there, then
-		migrate to whatever else you want to use.
-		'''
-		from django.db import connection
-		cursor = connection.cursor()
-		
-		cursor.execute("SELECT cid FROM comments as c WHERE nid = %s ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))", [node.nid])
-		rows = cursor.fetchall()
-		ids = [c[0] for c in rows]
-		comments = DrupalComment.objects.filter(pk__in=ids)
-		
-		return comments
+    # option_list = BaseCommand.option_list + (
+    #     make_option('--format', default='json', dest='format', help='Specifies the output serialization format for fixtures.'),
+    #     make_option('--indent', default=None, dest='indent', type='int', help='Specifies the indent level to use when pretty-printing output'),
+    #     make_option('-e', '--exclude', dest='exclude',action='append', default=[], help='App to exclude (use multiple --exclude to exclude multiple apps).'),
+    #     make_option('-v', '--verbose', dest='verbose', action="store", default="0", type="choice", choices=['0','1','2'], help='Verbose output'),
+    # )
+    help = 'Convert Drupal 6 tables to SP objects.    The tables must be in the same database as SP.'
+    # args = '[appname ...]'
+    
+    def handle(self, *app_labels, **options):
+        show_traceback = options.get('traceback', False)
+        verbose = int(options.get('verbosity', 0))
+        debug = (verbose == 2)
+        user = User.objects.get(pk=1)
+        
+        if verbose: print "Starting Drupal conversion."
+        if debug: print "* Debug logging enabled."
+        if debug: print "* Importing data with %s as the content owner." % user.username
+        
+        try:
+            # Get a list of nodes together and filter out blog nodes.
+            node_list = DrupalNode.objects.filter(type__in=("blog", "story", "page")).order_by("nid")
+            if verbose: print "Found %d nodes." % len(node_list)
+            
+            if len(node_list) == 0:
+                raise CommandError("No Drupal nodes found in the current database.")
+            
+            # Create some sections
+            (blog_section, c) = Section.objects.get_or_create(name="blog")
+            if c:
+                if debug: print "* Created blog section."
+                blog_section.save()
+            
+            (story_section, c) = Section.objects.get_or_create(name="story")
+            if c:
+                if debug: print "* Created story section."
+                story_section.save()
+            
+            for node in node_list:
+                # Our document object
+                obj = None
+                
+                # See if we've imported this node before
+                node_aliases = Redirect.objects.filter(original='/node/%d' % node.nid)
+                if node_aliases.count() > 0:
+                    if verbose: print "Skipping node %d" % node.nid
+                    continue
+                
+                # We haven't imported this.  Let's begin...
+                if verbose: print "Processing node (%d) %s" % (node.nid, node.title)
+                
+                if node.type == "blog" or node.type == "story":
+                    if node.type == "blog":
+                        if debug: print "Node is a blog."
+                        section = blog_section
+                    else:
+                        if debug: print "Node is a story."
+                        section = story_section
+                    
+                    # Create an Story for the node
+                    obj = Story(
+                        user = user,
+                        title = node.title,
+                        date_created = datetime.datetime.fromtimestamp(node.created),
+                        date_modified = datetime.datetime.fromtimestamp(node.changed),
+                        date_published = datetime.datetime.fromtimestamp(node.created),
+                        slug = slugify(node.title),
+                        allow_comments = node.comment,
+                        status = node.status,
+                        section = section,
+                        )
+                
+                    # Set the current content value
+                    try:
+                        contents = node.current_revision.get_parsed_contents()
+                    
+                        obj.teaser = contents['teaser']
+                        obj.content = contents['body']
+                    except DrupalNodeRevision.DoesNotExist, e:
+                        if verbose: print "WARNING: No body content found for %s (%d)" % (obj.title, obj.id)
+                
+                    # Save the story
+                    obj.save()
+                    
+                    # Add to the current site
+                    obj.sites.add(Site.objects.get_current())
+                    
+                    # Import terms as tags
+                    terms = node.current_revision.terms.all()
+                    tags = []
+                    for term in terms:
+                        tags.append(term.name)
+                    obj.tags = ','.join(tags) + ','
+                    if debug: print "* Set tags to: %s" % obj.tags
+                    
+                    # Ensure the modification date is proper
+                    obj.date_modified = datetime.datetime.fromtimestamp(node.changed)
+                    obj.save()
+                    
+                elif node.type == "page":
+                    if debug: print "Node is a page."
+                    
+                    # Create an object for the node
+                    obj = Page(
+                        title = node.title,
+                        date_created = datetime.datetime.fromtimestamp(node.created),
+                        date_modified = datetime.datetime.fromtimestamp(node.changed),
+                        date_published = datetime.datetime.fromtimestamp(node.created),
+                        status = node.status,
+                        )
+                    
+                    # Set the current content value
+                    try:
+                        obj.content = node.current_revision.body
+                    except DrupalNodeRevision.DoesNotExist, e:
+                        if verbose: print "WARNING: No body content found for %s (%d)" % (obj.title, obj.id)
+                    
+                    # Save
+                    obj.save()
+                    
+                    # Add to the current site
+                    obj.sites.add(Site.objects.get_current())
+                    
+                    # Find the most recent URL for this object and assign it
+                    aliases = DrupalUrlAlias.objects.filter(src='node/%d' % node.nid).order_by('-pid')
+                    if aliases.count():
+                        if debug: print "* Page aliases:", aliases
+                        obj.url = '/' + aliases[0].dst
+                    
+                    # Ensure the modification date is proper
+                    obj.date_modified = datetime.datetime.fromtimestamp(node.changed)
+                    obj.save()
+                
+                else:
+                    # Bail if we don't have something to work on
+                    if verbose: print "Unsupported type:", node.type
+                    continue
+                
+                # Lookup any URL aliases for this node and create redirects
+                aliases = DrupalUrlAlias.objects.filter(src='node/%d' % node.nid)
+                
+                # First, the core "node/1" links should still work
+                r = Redirect(original="/node/%d" % node.nid, target_object=obj)
+                r.save()
+                
+                # Now we get any others that were made
+                for alias in aliases:
+                    dst = "/" + alias.dst
+                    if obj.__class__ == Page and dst == obj.url: continue
+                    try:
+                        r = Redirect(original=dst, target_object=obj)
+                        r.save()
+                        if debug: print " Created redirect from", dst
+                    except Exception, e:
+                        if verbose: print "* Failed to create redirect from", dst, "to", obj, ":", e
+                        continue
+                
+                # Create a ContentHistory for each old body value
+                if node.revisions.count() > 1:
+                    for revision in node.revisions.all()[1:]:
+                        date = datetime.datetime.fromtimestamp(revision.timestamp)
+                        
+                        contents = revision.get_parsed_contents()
+                        
+                        ch = ContentHistory(
+                            date_created = date,
+                            date_modified = date,
+                            owner = obj,
+                            field_name = "teaser",
+                            content = contents['teaser'],
+                        )
+                        ch.save()
+                        
+                        ch = ContentHistory(
+                            date_created = date,
+                            date_modified = date,
+                            owner = obj,
+                            field_name = "content",
+                            content = contents['body'],
+                        )
+                        ch.save()
+                        
+                        if debug: print " Added revision %s" % (ch.date_created)
+                
+                # Bring over the comments
+                comments = self.get_comments_for_node(node)
+                if debug: print "  Comments:", comments.all()
+                for comment in comments:
+                    if debug: print "  Importing comment: ", comment.thread, comment.subject
+                    
+                    # To handle the nested comments:
+                    #     Break up the thread property: 01.01.01 -> [1,1,1]
+                    #     Pop off the last item, as that is the location of the current comment (which we cannot directly set)
+                    #     For each remaining level, get the right relationship in a loop:
+                    #         Set an object to the first listed comment.
+                    #         Pop that value off the array.
+                    #         If there're more items left, set the loop object to that child of the current object.
+                    #         When we run out of list items, we found the parent.
+                    
+                    # 01.00.05.07/ to 01.00.05.07 to [01,00,05,07]
+                    parents = comment.thread[:-1].split('.')
+                    # [1,0,5,7]
+                    parents = map(vancode2int, parents)
+                    # [1,0,5]
+                    parents = parents[:-1]
+                    if len(parents):
+                        # [0,0,5]
+                        parents[0] = parents[0] - 1
+                    
+                    if debug: print "** Parents:", parents, "(%s)" % comment.thread
+                    
+                    c_obj = None
+                    if len(parents):
+                        ct = ContentType.objects.get_for_model(obj.__class__)
+                        obj_comments = ThreadedComment.objects.filter(content_type=ct, object_pk=str(obj.id)).order_by('submit_date')
+                        if debug:
+                            print "* Object comments (%d):" % obj_comments.count(), obj_comments
+                            print "* Parents (%d):" % len(parents), parents
+                        try:
+                            c_obj = obj_comments[parents[0]]
+                        except IndexError, e:
+                            if verbose: print "*** Parent of comment subtree not found.  This can happen if a thread has a deleted comment."
+                        parents = parents[1:]
+                        while c_obj and len(parents):
+                            if c_obj.children.count() > parents[0]:
+                                c_obj = c_obj.children.all()[parents[0]]
+                                parents = parents[1:]
+                            else:
+                                if verbose: print "** Object has %d comments, but the parent should be item %d" % (c_obj.children.count(), parents[0])
+                                break
+                        if debug and c_obj: print "** Suspected parent:", c_obj.title
+                    
+                    # Actually create the comment now
+                    c = ThreadedComment(
+                        content_object = obj,
+                        title = unicode(comment.subject)[:200],
+                        ip_address = unicode(comment.hostname)[:15],
+                        user_name = unicode(comment.name)[:50],
+                        user_email = unicode(comment.mail)[:75],
+                        user_url = unicode(comment.homepage)[:200],
+                        submit_date = datetime.datetime.fromtimestamp(comment.timestamp),
+                        comment = unicode(comment.comment),
+                        site = Site.objects.get_current(),
+                        is_public = (not comment.status),
+                    )
+                    c.save()
+                    
+                    # Now that the comment exists, we can set the parent.
+                    c.parent = c_obj
+                    c.save()
+                    
+                    if verbose: print " Imported comment (%d) %s" % (comment.cid, comment.subject)
+                
+                if debug: print " Finished node: %s (%s)" % (obj.title, obj.get_absolute_url())
+            
+        except Exception, e:
+            if show_traceback:
+                raise
+            raise CommandError("Error: %s" % e)
+        
+        if verbose: print "Done."
+    
+    def get_comments_for_node(self, node):
+        '''
+        This generally requires a "real" database like Postgresql or MySQL.  Sqlite does not support SUBSTRING.
+        But, that's okay.  Drupal required MySQL anyway, so do the conversion on a copy of the DB there, then
+        migrate to whatever else you want to use.
+        '''
+        from django.db import connection
+        cursor = connection.cursor()
+        
+        cursor.execute("SELECT cid FROM comments as c WHERE nid = %s ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))", [node.nid])
+        rows = cursor.fetchall()
+        ids = [c[0] for c in rows]
+        comments = DrupalComment.objects.filter(pk__in=ids)
+        
+        return comments

drupal_support/models.py

 from django.contrib import admin
 
 class DrupalComment(models.Model):
-	cid = models.IntegerField(primary_key=True)
-	pid = models.IntegerField(null=True, blank=True, default=0)
-	nid = models.ForeignKey("DrupalNode", db_column="nid")
-	uid = models.ForeignKey("DrupalUser", db_column="uid")
-	subject = models.CharField(null=True, blank=True, max_length=192)
-	comment = models.TextField(null=True, blank=True, default="")
-	hostname = models.CharField(null=True, blank=True, max_length=384)
-	timestamp = models.IntegerField(null=True, blank=True, default=0)
-	status = models.IntegerField(null=True, blank=True, default=0)
-	format = models.IntegerField(null=True, blank=True, default=0)
-	thread = models.CharField(null=True, blank=True, max_length=765)
-	name = models.CharField(null=True, blank=True, max_length=180)
-	mail = models.CharField(null=True, blank=True, max_length=192)
-	homepage = models.CharField(null=True, blank=True, max_length=765)
-	class Meta:
-		db_table = u'comments'
+    cid = models.IntegerField(primary_key=True)
+    pid = models.IntegerField(null=True, blank=True, default=0)
+    nid = models.ForeignKey("DrupalNode", db_column="nid")
+    uid = models.ForeignKey("DrupalUser", db_column="uid")
+    subject = models.CharField(null=True, blank=True, max_length=192)
+    comment = models.TextField(null=True, blank=True, default="")
+    hostname = models.CharField(null=True, blank=True, max_length=384)
+    timestamp = models.IntegerField(null=True, blank=True, default=0)
+    status = models.IntegerField(null=True, blank=True, default=0)
+    format = models.IntegerField(null=True, blank=True, default=0)
+    thread = models.CharField(null=True, blank=True, max_length=765)
+    name = models.CharField(null=True, blank=True, max_length=180)
+    mail = models.CharField(null=True, blank=True, max_length=192)
+    homepage = models.CharField(null=True, blank=True, max_length=765)
+    class Meta:
+        db_table = u'comments'
 
 class DrupalNode(models.Model):
-	nid = models.IntegerField(primary_key=True)
-	type = models.CharField(max_length=96, default="")
-	title = models.CharField(max_length=765, default="")
-	uid = models.ForeignKey("DrupalUser", db_column="uid")
-	status = models.IntegerField(default=0)
-	created = models.IntegerField(default=0)
-	changed = models.IntegerField(default=0)
-	comment = models.IntegerField(default=0)
-	promote = models.IntegerField(default=0)
-	moderate = models.IntegerField(default=0)
-	sticky = models.IntegerField(default=0)
-	current_revision = models.ForeignKey("DrupalNodeRevision", db_column="vid")
-	language = models.CharField(max_length=36, default="")
-	tnid = models.IntegerField(default=0)
-	translate = models.IntegerField(default=0)
-	
-	terms = models.ManyToManyField('DrupalTermData', through="DrupalTermNode", blank=True)
+    nid = models.IntegerField(primary_key=True)
+    type = models.CharField(max_length=96, default="")
+    title = models.CharField(max_length=765, default="")
+    uid = models.ForeignKey("DrupalUser", db_column="uid")
+    status = models.IntegerField(default=0)
+    created = models.IntegerField(default=0)
+    changed = models.IntegerField(default=0)
+    comment = models.IntegerField(default=0)
+    promote = models.IntegerField(default=0)
+    moderate = models.IntegerField(default=0)
+    sticky = models.IntegerField(default=0)
+    current_revision = models.ForeignKey("DrupalNodeRevision", db_column="vid")
+    language = models.CharField(max_length=36, default="")
+    tnid = models.IntegerField(default=0)
+    translate = models.IntegerField(default=0)
+    
+    terms = models.ManyToManyField('DrupalTermData', through="DrupalTermNode", blank=True)
 
-	class Meta:
-		db_table = u'node'
+    class Meta:
+        db_table = u'node'
 
 class DrupalNodeRevision(models.Model):
-	nid = models.ForeignKey("DrupalNode", db_column="nid", related_name="revisions")
-	vid = models.IntegerField(primary_key=True, default=0)
-	uid = models.ForeignKey("DrupalUser", db_column="uid")
-	title = models.CharField(max_length=765, default="")
-	body = models.TextField(default="")
-	teaser = models.TextField(default="")
-	timestamp = models.IntegerField(default=0)
-	format = models.IntegerField(default=0)
-	log = models.TextField(null=True, blank=True, default="")
-	
-	terms = models.ManyToManyField('DrupalTermData', through="DrupalTermNode", blank=True)
-	
-	class Meta:
-		db_table = u'node_revisions'
-	
-	def get_parsed_contents(self):
-		'''
-		Drupal stores a teaser and a body, but the teaser is almost always a substr of the body.
-		So, we need to see if that's the case.	If it is, then we break it up so we have a teaser and body.
-		If it's not, then we store each separately.
-		But first, we check for the easy case of "<!-- break -->"
-		'''
-		break_re = re.compile('(?P<before>.*?)<!--\s*break\s*-->(?P<after>.*)', re.S|re.I)
-		results = break_re.search(self.body)
-		
-		p_teaser = None
-		p_content = None
-		
-		if results:
-			# Found a break marker, so we're golden.
-			p_teaser = results.group('before')
-			p_content = results.group('after')
-		else:
-			# Did not find a break marker.	So we do some string math.
-			# If the teaser is found in the body, it is deleted.  If it is not found,
-			# then it's left alone.	 Should work...
-			p_teaser = self.teaser
-			p_content = re.sub(re.escape(self.teaser), '', self.body, 1)
-		
-		return {'teaser':p_teaser, 'body':p_content}
+    nid = models.ForeignKey("DrupalNode", db_column="nid", related_name="revisions")
+    vid = models.IntegerField(primary_key=True, default=0)
+    uid = models.ForeignKey("DrupalUser", db_column="uid")
+    title = models.CharField(max_length=765, default="")
+    body = models.TextField(default="")
+    teaser = models.TextField(default="")
+    timestamp = models.IntegerField(default=0)
+    format = models.IntegerField(default=0)
+    log = models.TextField(null=True, blank=True, default="")
+    
+    terms = models.ManyToManyField('DrupalTermData', through="DrupalTermNode", blank=True)
+    
+    class Meta:
+        db_table = u'node_revisions'
+    
+    def get_parsed_contents(self):
+        '''
+        Drupal stores a teaser and a body, but the teaser is almost always a substr of the body.
+        So, we need to see if that's the case.    If it is, then we break it up so we have a teaser and body.
+        If it's not, then we store each separately.
+        But first, we check for the easy case of "<!-- break -->"
+        '''
+        break_re = re.compile('(?P<before>.*?)<!--\s*break\s*-->(?P<after>.*)', re.S|re.I)
+        results = break_re.search(self.body)
+        
+        p_teaser = None
+        p_content = None
+        
+        if results:
+            # Found a break marker, so we're golden.
+            p_teaser = results.group('before')
+            p_content = results.group('after')
+        else:
+            # Did not find a break marker.    So we do some string math.
+            # If the teaser is found in the body, it is deleted.  If it is not found,
+            # then it's left alone.     Should work...
+            p_teaser = self.teaser
+            p_content = re.sub(re.escape(self.teaser), '', self.body, 1)
+        
+        return {'teaser':p_teaser, 'body':p_content}
 
 class DrupalUrlAlias(models.Model):
-	pid = models.IntegerField(primary_key=True, default="")
-	src = models.CharField(max_length=255, default="")
-	dst = models.CharField(unique=True, max_length=255, default="")
-	language = models.CharField(max_length=36, default="")
+    pid = models.IntegerField(primary_key=True, default="")
+    src = models.CharField(max_length=255, default="")
+    dst = models.CharField(unique=True, max_length=255, default="")
+    language = models.CharField(max_length=36, default="")
 
-	def __unicode__(self):
-		return "%s to %s" % (self.src, self.dst)
+    def __unicode__(self):
+        return "%s to %s" % (self.src, self.dst)
 
-	class Meta:
-		db_table = u'url_alias'
+    class Meta:
+        db_table = u'url_alias'
 
 class DrupalUser(models.Model):
-	uid = models.IntegerField(primary_key=True)
-	name = models.CharField(null=True, unique=True, max_length=180, default="")
-	pass_field = models.CharField(null=True, max_length=96, db_column='pass', default="") # Field renamed because it was a Python reserved word. Field name made lowercase.
-	mail = models.CharField(null=True, max_length=192, blank=True, default="")
-	mode = models.IntegerField(null=True, default=0)
-	sort = models.IntegerField(null=True, blank=True, default=0)
-	threshold = models.IntegerField(null=True, blank=True, default=0)
-	theme = models.CharField(null=True, max_length=765, default="")
-	signature = models.CharField(null=True, max_length=765, default="")
-	created = models.IntegerField(null=True, default=0)
-	access = models.IntegerField(null=True, default=0)
-	status = models.IntegerField(null=True, default=0)
-	timezone = models.CharField(null=True, max_length=24, blank=True, default="")
-	language = models.CharField(null=True, max_length=36, default="")
-	picture = models.CharField(null=True, max_length=765, default="")
-	init = models.CharField(null=True, max_length=192, blank=True, default="")
-	data = models.TextField(null=True, blank=True, default="")
-	login = models.IntegerField(null=True, default=0)
-	class Meta:
-		db_table = u'users'
+    uid = models.IntegerField(primary_key=True)
+    name = models.CharField(null=True, unique=True, max_length=180, default="")
+    pass_field = models.CharField(null=True, max_length=96, db_column='pass', default="") # Field renamed because it was a Python reserved word. Field name made lowercase.
+    mail = models.CharField(null=True, max_length=192, blank=True, default="")
+    mode = models.IntegerField(null=True, default=0)
+    sort = models.IntegerField(null=True, blank=True, default=0)
+    threshold = models.IntegerField(null=True, blank=True, default=0)
+    theme = models.CharField(null=True, max_length=765, default="")
+    signature = models.CharField(null=True, max_length=765, default="")
+    created = models.IntegerField(null=True, default=0)
+    access = models.IntegerField(null=True, default=0)
+    status = models.IntegerField(null=True, default=0)
+    timezone = models.CharField(null=True, max_length=24, blank=True, default="")
+    language = models.CharField(null=True, max_length=36, default="")
+    picture = models.CharField(null=True, max_length=765, default="")
+    init = models.CharField(null=True, max_length=192, blank=True, default="")
+    data = models.TextField(null=True, blank=True, default="")
+    login = models.IntegerField(null=True, default=0)
+    class Meta:
+        db_table = u'users'
 
 admin.site.register(DrupalComment)
 admin.site.register(DrupalNode)
 admin.site.register(DrupalUser)
 
 class DrupalTermData(models.Model):
-	'''The actual terms'''
-	tid = models.IntegerField(primary_key=True)
-	vid = models.IntegerField()
-	name = models.CharField(max_length=765)
-	description = models.TextField(blank=True)
-	weight = models.IntegerField()
-	class Meta:
-		db_table = u'term_data'
-	def __unicode__(self):
-		return self.name
+    '''The actual terms'''
+    tid = models.IntegerField(primary_key=True)
+    vid = models.IntegerField()
+    name = models.CharField(max_length=765)
+    description = models.TextField(blank=True)
+    weight = models.IntegerField()
+    class Meta:
+        db_table = u'term_data'
+    def __unicode__(self):
+        return self.name
 
 class DrupalTermHierarchy(models.Model):
-	'''Term parents/children'''
-	tid = models.ForeignKey(DrupalTermData, primary_key=True, related_name="tid_set", db_column="tid")
-	parent = models.ForeignKey(DrupalTermData, primary_key=True, db_column="parent")
-	class Meta:
-		db_table = u'term_hierarchy'
+    '''Term parents/children'''
+    tid = models.ForeignKey(DrupalTermData, primary_key=True, related_name="tid_set", db_column="tid")
+    parent = models.ForeignKey(DrupalTermData, primary_key=True, db_column="parent")
+    class Meta:
+        db_table = u'term_hierarchy'
 
 class DrupalTermNode(models.Model):
-	'''Mapping of terms to nodes'''
-	nid = models.ForeignKey(DrupalNode, primary_key=True, db_column="nid")
-	tid = models.ForeignKey(DrupalTermData, primary_key=True, db_column="tid")
-	vid = models.ForeignKey(DrupalNodeRevision, db_column='vid')
-	class Meta:
-		db_table = u'term_node'
+    '''Mapping of terms to nodes'''
+    nid = models.ForeignKey(DrupalNode, primary_key=True, db_column="nid")
+    tid = models.ForeignKey(DrupalTermData, primary_key=True, db_column="tid")
+    vid = models.ForeignKey(DrupalNodeRevision, db_column='vid')
+    class Meta:
+        db_table = u'term_node'
 
 admin.site.register(DrupalTermData)
 admin.site.register(DrupalTermHierarchy)
 admin.site.register(DrupalTermNode)
 
 # class DrupalTermRelation(models.Model):
-#	  tid1 = models.IntegerField(unique=True)
-#	  tid2 = models.IntegerField()
-#	  trid = models.IntegerField(primary_key=True)
-#	  class Meta:
-#		  db_table = u'term_relation'
+#      tid1 = models.IntegerField(unique=True)
+#      tid2 = models.IntegerField()
+#      trid = models.IntegerField(primary_key=True)
+#      class Meta:
+#          db_table = u'term_relation'
 # 
 # class DrupalTermSynonym(models.Model):
-#	  tid = models.IntegerField()
-#	  name = models.CharField(max_length=765)
-#	  tsid = models.IntegerField(primary_key=True)
-#	  class Meta:
-#		  db_table = u'term_synonym'
+#      tid = models.IntegerField()
+#      name = models.CharField(max_length=765)
+#      tsid = models.IntegerField(primary_key=True)
+#      class Meta:
+#          db_table = u'term_synonym'
 # 
 # class DrupalVocabulary(models.Model):
-#	  vid = models.IntegerField(primary_key=True)
-#	  name = models.CharField(max_length=765)
-#	  description = models.TextField(blank=True)
-#	  help = models.CharField(max_length=765)
-#	  relations = models.IntegerField()
-#	  hierarchy = models.IntegerField()
-#	  multiple = models.IntegerField()
-#	  required = models.IntegerField()
-#	  weight = models.IntegerField()
-#	  module = models.CharField(max_length=765)
-#	  tags = models.IntegerField()
-#	  class Meta:
-#		  db_table = u'vocabulary'
+#      vid = models.IntegerField(primary_key=True)
+#      name = models.CharField(max_length=765)
+#      description = models.TextField(blank=True)
+#      help = models.CharField(max_length=765)
+#      relations = models.IntegerField()
+#      hierarchy = models.IntegerField()
+#      multiple = models.IntegerField()
+#      required = models.IntegerField()
+#      weight = models.IntegerField()
+#      module = models.CharField(max_length=765)
+#      tags = models.IntegerField()
+#      class Meta:
+#          db_table = u'vocabulary'
 import datetime
 
 class PublishedManager (models.Manager):
-	def get_query_set(self):
-		return super(PublishedManager,self).get_query_set().filter(
-			Q(date_published=None) | Q(date_published__lte=datetime.datetime.now),
-			Q(date_hidden=None) | Q(date_hidden__gt =datetime.datetime.now),
-			status = True,
-			sites__in=[Site.objects.get_current()]
-		)
+    def get_query_set(self):
+        return super(PublishedManager,self).get_query_set().filter(
+            Q(date_published=None) | Q(date_published__lte=datetime.datetime.now),
+            Q(date_hidden=None) | Q(date_hidden__gt =datetime.datetime.now),
+            status = True,
+            sites__in=[Site.objects.get_current()]
+        )
 import xmlrpclib # import DateTime
 
 def authenticated(pos=1):
-	"""
-	A decorator for functions that require authentication.
-	Assumes that the username & password are the second & third parameters.
-	Doesn't perform real authorization (yet), it just checks that the
-	user is_superuser.
-	"""
-	
-	def _decorate(func):
-		def _wrapper(*args, **kwargs):
-			username = args[pos+0]
-			password = args[pos+1]
-			args = args[0:pos]+args[pos+2:]
-			try:
-				user = User.objects.get(username__exact=username)
-			except User.DoesNotExist:
-				raise ValueError("Authentication Failure")
-			if not user.check_password(password):
-				raise ValueError("Authentication Failure")
-			if not user.is_superuser:
-				raise ValueError("Authorization Failure")
-			return func(user, *args, **kwargs)
-		
-		return _wrapper
-	return _decorate
+    """
+    A decorator for functions that require authentication.
+    Assumes that the username & password are the second & third parameters.
+    Doesn't perform real authorization (yet), it just checks that the
+    user is_superuser.
+    """
+    
+    def _decorate(func):
+        def _wrapper(*args, **kwargs):
+            username = args[pos+0]
+            password = args[pos+1]
+            args = args[0:pos]+args[pos+2:]
+            try:
+                user = User.objects.get(username__exact=username)
+            except User.DoesNotExist:
+                raise ValueError("Authentication Failure")
+            if not user.check_password(password):
+                raise ValueError("Authentication Failure")
+            if not user.is_superuser:
+                raise ValueError("Authorization Failure")
+            return func(user, *args, **kwargs)
+        
+        return _wrapper
+    return _decorate
 
 def full_url(url):
-	return urlparse.urljoin(settings.SITE_URL, url)
+    return urlparse.urljoin(settings.SITE_URL, url)
 
 # example... this is what wordpress returns:
 # {'permaLink': 'http://gabbas.wordpress.com/2006/05/09/hello-world/',
 #  'mt_allow_pings': 1}
 
 def format_date(d):
-	if not d: return None
-	return xmlrpclib.DateTime(d.isoformat())
+    if not d: return None
+    return xmlrpclib.DateTime(d.isoformat())
 
 def post_struct(post):
-	link = full_url(post.get_absolute_url())
-	struct = {
-		'postid': post.id,
-		'title': post.title,
-		'mt_basename': post.slug,
-		'link': link,
-		'permaLink': link,
-		'description': post.teaser,
-		'mt_tags': post.tags,
-		'userid': post.user.id,
-		# 'mt_excerpt': post.teaser,
-		'mt_text_more': post.content,
-		# 'mt_allow_comments': 1,
-		# 'mt_allow_pings': 1
-		'mt_convert_breaks': str(post.content_format), #Sadly, we have to use one format for both components.
-		}
-	if post.date_published:
-		struct['dateCreated'] = format_date(post.date_published)
-	
-	return struct
+    link = full_url(post.get_absolute_url())
+    struct = {
+        'postid': post.id,
+        'title': post.title,
+        'mt_basename': post.slug,
+        'link': link,
+        'permaLink': link,
+        'description': post.teaser,
+        'mt_tags': post.tags,
+        'userid': post.user.id,
+        # 'mt_excerpt': post.teaser,
+        'mt_text_more': post.content,
+        # 'mt_allow_comments': 1,
+        # 'mt_allow_pings': 1
+        'mt_convert_breaks': str(post.content_format), #Sadly, we have to use one format for both components.
+        }
+    if post.date_published:
+        struct['dateCreated'] = format_date(post.date_published)
+    
+    return struct
 
 def tag_struct(tag):
-	struct = {
-		'categoryId': tag.id,
-		'categoryName': tag.name,
-	}
-	return struct
+    struct = {
+        'categoryId': tag.id,
+        'categoryName': tag.name,
+    }
+    return struct
 
 def _update_post(post, struct):
-	if struct.get('dateCreated', None):
-		post.date_published = struct['dateCreated']
-	if struct.has_key('title'):
-		post.title = struct['title']
-	if struct.has_key('description'):
-		post.teaser = struct['description']
-	if struct.has_key('mt_text_more'):
-		post.content = struct['mt_text_more']
-	if struct.has_key('mt_tags'):
-		post.tags = struct['mt_tags']
-	elif struct.has_key('mt_keywords'):
-		post.tags = struct('mt_keywords')
-	if struct.has_key('mt_basename'):
-		post.slug = struct['mt_basename']
+    if struct.get('dateCreated', None):
+        post.date_published = struct['dateCreated']
+    if struct.has_key('title'):
+        post.title = struct['title']
+    if struct.has_key('description'):
+        post.teaser = struct['description']
+    if struct.has_key('mt_text_more'):
+        post.content = struct['mt_text_more']
+    if struct.has_key('mt_tags'):
+        post.tags = struct['mt_tags']
+    elif struct.has_key('mt_keywords'):
+        post.tags = struct('mt_keywords')
+    if struct.has_key('mt_basename'):
+        post.slug = struct['mt_basename']
 
 ''' Blogger API V1 '''
 
 @public
 @authenticated(pos=2)
 def blogger_newPost(user, appkey, blogid, content, publish):
-	pass
+    pass
 
 @public
 @authenticated(pos=2)
 def blogger_editPost(user, appkey, postid, content, publish):
-	pass
+    pass
 
 @public
 @authenticated(pos=2)
 def blogger_deletePost(user, appkey, postid, publish):
-	post = Story.objects.get(id=postid)
-	post.delete()
-	return True
+    post = Story.objects.get(id=postid)
+    post.delete()
+    return True
 
 @public
 @authenticated()
 def blogger_getUsersBlogs(user, appkey):
-	"""
-	an array of <struct>'s containing the ID (blogid), name
-	(blogName), and URL (url) of each blog.
-	"""
-	print "entered"
-	sections = Section.objects.all()
-	print "got sections"
-	result = [{
-			'blogid': section.slug,
-			'blogName': section.name,
-			'url': settings.SITE_URL + reverse('story-archive', kwargs={'section':section.slug})
-			} for section in sections]
-	print "results:", result
-	return result
+    """
+    an array of <struct>'s containing the ID (blogid), name
+    (blogName), and URL (url) of each blog.
+    """
+    print "entered"
+    sections = Section.objects.all()
+    print "got sections"
+    result = [{
+            'blogid': section.slug,
+            'blogName': section.name,
+            'url': settings.SITE_URL + reverse('story-archive', kwargs={'section':section.slug})
+            } for section in sections]
+    print "results:", result
+    return result
 
 @public
 @authenticated()
 def blogger_getUserInfo(user, appkey):
-	return {
-		'nickname':user.username,
-		'userid':user.id,
-		'url':settings.SITE_URL,
-		'email':user.email,
-		'lastname':user.last_name,
-		'firstname':user.first_name,
-		}
+    return {
+        'nickname':user.username,
+        'userid':user.id,
+        'url':settings.SITE_URL,
+        'email':user.email,
+        'lastname':user.last_name,
+        'firstname':user.first_name,
+        }
 
 @public
 @authenticated()
 def metaWeblog_getCategories(user, blogid):
-	tags = Tag.objects.all()
-	result = [{'description':tag.name} for tag in tags]
-	return result
+    tags = Tag.objects.all()
+    result = [{'description':tag.name} for tag in tags]
+    return result
 
 @public
 @authenticated()
 def metaWeblog_getPost(user, postid):
-	post = Story.objects.get(id=postid)
-	return post_struct(post)
+    post = Story.objects.get(id=postid)
+    return post_struct(post)
 
 @public
 @authenticated()
 def metaWeblog_getRecentPosts(user, blogid, num_posts):
-	posts = Story.objects.filter(section__name__exact=blogid).order_by('-date_published')[:int(num_posts)]
-	return [post_struct(post) for post in posts]
+    posts = Story.objects.filter(section__name__exact=blogid).order_by('-date_published')[:int(num_posts)]
+    return [post_struct(post) for post in posts]
 
 @public
 @authenticated()
 def metaWeblog_newPost(user, blogid, struct, publish):
-	post = Story(status = publish)
-	post.user = user
-	_update_post(post, struct)
-	section = Section.objects.get(name__exact=blogid)
-	post.section = blogid
-	post.save()
-	return post.id
+    post = Story(status = publish)
+    post.user = user
+    _update_post(post, struct)
+    section = Section.objects.get(name__exact=blogid)
+    post.section = blogid
+    post.save()
+    return post.id
 
 @public
 @authenticated()
 def metaWeblog_editPost(user, postid, struct, publish):
-	post = Story.objects.get(id=postid)
-	post.status = publish
-	_update_post(post, struct)
-	post.save()
-	return True
+    post = Story.objects.get(id=postid)
+    post.status = publish
+    _update_post(post, struct)
+    post.save()
+    return True
 
 # http://qoli.de/blog/2007/nov/19/implementing-metaweblog-api/
 @public
 @authenticated()
-def metaWeblog_newMediaObject(user, blogid, struct):		
-	bits = b64decode(struct['bits'])
-	name = struct['name']
-	mime = struct['type']
+def metaWeblog_newMediaObject(user, blogid, struct):        
+    bits = b64decode(struct['bits'])
+    name = struct['name']
+    mime = struct['type']
 
-	attachment= Attachment(content= bits,
-				   filename= name,
-				   contenttype= mime)
-	attachment.save_content_file(name, bits)
-	attachment.save()
+    attachment= Attachment(content= bits,
+                   filename= name,
+                   contenttype= mime)
+    attachment.save_content_file(name, bits)
+    attachment.save()
 
-	return {'url': attachment.get_content_url()}
+    return {'url': attachment.get_content_url()}
 
 @public
 @authenticated()
 def mt_getPostCategories(user, postid):
-	post = Story.objects.get(id=int(postid))
-	if (post.tags):
-		tags = [tag_struct(t) for t in Tag.objects.filter(name__in=parse_tag_input(post.tags))]
-	else:
-		tags = []
-	return tags
+    post = Story.objects.get(id=int(postid))
+    if (post.tags):
+        tags = [tag_struct(t) for t in Tag.objects.filter(name__in=parse_tag_input(post.tags))]
+    else:
+        tags = []
+    return tags
 
 @public
 @authenticated()
 def mt_getCategoryList(user, blogid):
-	return [tag_struct(t) for t in Tag.objects.all()]
+    return [tag_struct(t) for t in Tag.objects.all()]
 
 @public
 def mt_supportedTextFilters():
-	return [{'label': f[1], 'key': str(f[0])} for f in CONTENT_FORMATTERS]
+    return [{'label': f[1], 'key': str(f[0])} for f in CONTENT_FORMATTERS]

middleware/__init__.py

 from sitepoet.views import view_page
 
 class RedirectionMiddleware(object):
-	def process_response(self, request, response):
-		# If the response found something, return it.
-		if response.status_code != 404:
-			return response
-		try:
-			# Look for an exact redirect
-			path = request.path
-			paths = [path]
-			
-			# Also try without the trailing slash if someone tacked it on for us
-			if settings.APPEND_SLASH:
-				paths.append(path[:path.rfind('/')] + path[path.rfind('/')+1:])
-			
-			r = Redirect.objects.get(site__id__exact=settings.SITE_ID, original__in=paths)
-			
-			# If the destination is not visible, let the 404 shine.
-			if hasattr(r.target_object, 'visible') and r.target_object.visible == False:
-				return response
-			
-			# If there's a redirect, process it
-			destination = r.destination()
-			
-			# If we're about to tell someone to go where we already are, stop and think...
-			if destination == request.path_info:
-				return response
-			
-			if destination == None:
-				# No destination means it's been removed, so make it a dead end.
-				context = RequestContext(request, {
-					'title':'410 Gone',
-					'content':'the resource you seek / has been taken by time / seek answers within'
-				})
-				template = loader.get_template("error.html")
-				return HttpResponseGone(template.render(context))
-			
-			if r.permanent == True:
-				# 301 Moved
-				return HttpResponsePermanentRedirect(destination)
-			else:
-				# 302 Found
-				return HttpResponseRedirect(destination)
-		
-		except Redirect.DoesNotExist, e:
-			# Otherwise, return the original response
-			return response
-		except:
-			if settings.DEBUG:
-				raise
-		# If we're here, we didn't catch something -- just pass along the 404.
-		return response
+    def process_response(self, request, response):
+        # If the response found something, return it.
+        if response.status_code != 404:
+            return response
+        try:
+            # Look for an exact redirect
+            path = request.path
+            paths = [path]
+            
+            # Also try without the trailing slash if someone tacked it on for us
+            if settings.APPEND_SLASH:
+                paths.append(path[:path.rfind('/')] + path[path.rfind('/')+1:])
+            
+            r = Redirect.objects.get(site__id__exact=settings.SITE_ID, original__in=paths)
+            
+            # If the destination is not visible, let the 404 shine.
+            if hasattr(r.target_object, 'visible') and r.target_object.visible == False:
+                return response
+            
+            # If there's a redirect, process it
+            destination = r.destination()
+            
+            # If we're about to tell someone to go where we already are, stop and think...
+            if destination == request.path_info:
+                return response
+            
+            if destination == None:
+                # No destination means it's been removed, so make it a dead end.
+                context = RequestContext(request, {
+                    'title':'410 Gone',
+                    'content':'the resource you seek / has been taken by time / seek answers within'
+                })
+                template = loader.get_template("error.html")
+                return HttpResponseGone(template.render(context))
+            
+            if r.permanent == True:
+                # 301 Moved
+                return HttpResponsePermanentRedirect(destination)
+            else:
+                # 302 Found
+                return HttpResponseRedirect(destination)
+        
+        except Redirect.DoesNotExist, e:
+            # Otherwise, return the original response
+            return response
+        except:
+            if settings.DEBUG:
+                raise
+        # If we're here, we didn't catch something -- just pass along the 404.
+        return response
 
 
 class PageMiddleware(object):
-	def process_response(self, request, response):
-		# If the response found something, return it.
-		if response.status_code != 404:
-			return response
-		# Otherwise, see if we can get a response from the view
-		try:
-			return view_page(request, request.path_info)
-		# If the view couldn't find a page, then return the original response (don't pollute)
-		except Http404:
-			return response
-		# If anything else goes wrong here, just ignore it and return the original response (again, don't pollute)
-		except:
-			if settings.DEBUG:
-				raise
-			return response
+    def process_response(self, request, response):
+        # If the response found something, return it.
+        if response.status_code != 404:
+            return response
+        # Otherwise, see if we can get a response from the view
+        try:
+            return view_page(request, request.path_info)
+        # If the view couldn't find a page, then return the original response (don't pollute)
+        except Http404:
+            return response
+        # If anything else goes wrong here, just ignore it and return the original response (again, don't pollute)
+        except:
+            if settings.DEBUG:
+                raise
+            return response
 ### IMPORTS ###
 
 try:
-	import markdown
+    import markdown
 except:
-	markdown = None
+    markdown = None
 
 try:
-	import textile
+    import textile
 except:
-	textile = None
+    textile = None
 
 import datetime
 import string
 COMMENTS_ENABLED =2
 
 COMMENT_STATES = (
-	(COMMENTS_DISABLED, 'Disabled'),
-	(COMMENTS_CLOSED, 'Closed'),
-	(COMMENTS_ENABLED, 'Enabled'),
+    (COMMENTS_DISABLED, 'Disabled'),
+    (COMMENTS_CLOSED, 'Closed'),
+    (COMMENTS_ENABLED, 'Enabled'),
 )
 
 
 ENTRY_STATUS_PUBLISHED = 1
 
 ENTRY_STATUSES = (
-	(ENTRY_STATUS_DRAFT, "Draft"),
-	(ENTRY_STATUS_PUBLISHED, "Published"),
+    (ENTRY_STATUS_DRAFT, "Draft"),
+    (ENTRY_STATUS_PUBLISHED, "Published"),
 )
 
 
 CONTENT_FORMAT_MARKDOWN = 2
 
 CONTENT_FORMATTERS = (
-	(CONTENT_FORMAT_NONE, "None"),
-	(CONTENT_FORMAT_TEXTILE, "Textile"),
-	(CONTENT_FORMAT_MARKDOWN, "Markdown"),
+    (CONTENT_FORMAT_NONE, "None"),
+    (CONTENT_FORMAT_TEXTILE, "Textile"),
+    (CONTENT_FORMAT_MARKDOWN, "Markdown"),
 )
 
 
 ### GLOBAL FUNCTIONS ###
 
 def format_text(format, text):
-	output = text
-	if format == CONTENT_FORMAT_TEXTILE:
-		if textile != None:
-			output = textile.textile(text)
-		else:
-			print "No Textile support found."
-	elif format == CONTENT_FORMAT_MARKDOWN:
-		if markdown != None:
-			output = markdown.markdown(text)
-		else:
-			print "No Markdown support found."
-	return mark_safe(output)
+    output = text
+    if format == CONTENT_FORMAT_TEXTILE:
+        if textile != None:
+            output = textile.textile(text)
+        else:
+            print "No Textile support found."
+    elif format == CONTENT_FORMAT_MARKDOWN:
+        if markdown != None:
+            output = markdown.markdown(text)
+        else:
+            print "No Markdown support found."
+    return mark_safe(output)
 
 
 ### SPObject ###
 
 class SPObject (models.Model):
-	date_created = models.DateTimeField("Date Created", editable=False)
-	date_modified = models.DateTimeField("Date Modified", editable=False)
-	
-	class Meta:
-		abstract = True
-	
-	def __init__(self, *args, **kwargs):
-		super(SPObject, self).__init__(*args, **kwargs)
-		self._original_state = self._as_dict()
-	
-	def _as_dict(self):
-		'''
-		Returns the values of non-relationship model objects in a dictionary.
-		'''
-		return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel])
-	
-	def get_changed_fields(self):
-		'''
-		Returns a dictionary where the key is the field that changed and the value is the original value.
-		'''
-		new_state = self._as_dict()
-		return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])
-	
-	def is_dirty_field(self, field):
-		''' Returns True if a field has been changed since the object was last saved. '''
-		try:
-			return self.__dict__.get(field, None) != self._original_state[field]
-		except KeyError:
-			return True
-	
-	def save(self, *args, **kwargs):
-		if not self.date_created:
-			self.date_created = datetime.datetime.now()
-		
-		''' If the modification date was either not set or not manually changed since we were made, update it. '''
-		if not self.date_modified or not self.is_dirty_field('date_modified'):
-			self.date_modified = datetime.datetime.now()
-				
-		super(SPObject, self).save(*args, **kwargs)
-		
-		self._original_state = self._as_dict()
+    date_created = models.DateTimeField("Date Created", editable=False)
+    date_modified = models.DateTimeField("Date Modified", editable=False)
+    
+    class Meta:
+        abstract = True
+    
+    def __init__(self, *args, **kwargs):
+        super(SPObject, self).__init__(*args, **kwargs)
+        self._original_state = self._as_dict()
+    
+    def _as_dict(self):
+        '''
+        Returns the values of non-relationship model objects in a dictionary.
+        '''
+        return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel])
+    
+    def get_changed_fields(self):
+        '''
+        Returns a dictionary where the key is the field that changed and the value is the original value.
+        '''
+        new_state = self._as_dict()
+        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])
+    
+    def is_dirty_field(self, field):
+        ''' Returns True if a field has been changed since the object was last saved. '''
+        try:
+            return self.__dict__.get(field, None) != self._original_state[field]
+        except KeyError:
+            return True
+    
+    def save(self, *args, **kwargs):
+        if not self.date_created:
+            self.date_created = datetime.datetime.now()
+        
+        ''' If the modification date was either not set or not manually changed since we were made, update it. '''
+        if not self.date_modified or not self.is_dirty_field('date_modified'):
+            self.date_modified = datetime.datetime.now()
+                
+        super(SPObject, self).save(*args, **kwargs)
+        
+        self._original_state = self._as_dict()
 
 
 ### Model Objects ###
 
 class Resource(SPObject):
-	# Properties
-	sites = models.ManyToManyField(Site, default=[settings.SITE_ID], null=False, blank=False, help_text='The site(s) this item is accessible at.')
-	title = models.CharField("Title", max_length=255, null=False, blank=False)
-	
-	teaser = models.TextField(null=True, blank=True)
-	teaser_format = models.PositiveIntegerField("Teaser Format", choices=CONTENT_FORMATTERS, default=CONTENT_FORMAT_TEXTILE, help_text="The formatter the teaser should be run through when rendering.")
-	content = models.TextField("Content", null=True, blank=True, help_text='The content to display on the page.')
-	content_format = models.PositiveIntegerField("Content Format", choices=CONTENT_FORMATTERS, default=CONTENT_FORMAT_TEXTILE, help_text="The formatter the content should be run through when rendering.")
-	
-	# Publishing status
-	status = models.IntegerField("Status", choices=ENTRY_STATUSES, default=ENTRY_STATUS_PUBLISHED, help_text="Only published items will be visible on the site.")
-	date_published = models.DateTimeField("Date Published", null=True, blank=True, default=datetime.datetime.now, help_text="Item will become visible after this date.  Future posting is supported.")
-	date_hidden = models.DateTimeField("Date Hidden", null=True, blank=True, help_text="Item will be hidden past this date.  No value indicates a perpetual item (most common).")
-		
-	# Object managers
-	objects = models.Manager()
-	published = PublishedManager()
-	
-	class Meta:
-		abstract = True
-	
-	@property
-	def visible(self):
-		return (self.status == ENTRY_STATUS_PUBLISHED and self.date_published < datetime.datetime.now())
-	
-	def __init__(self, *args, **kwargs):
-		super(Resource, self).__init__(*args, **kwargs)
-		if self.id != None:
-			self._original_url = self.get_absolute_url()
-		else:
-			self._original_url = ""
-	
-	def __unicode__(self):
-		return self.title
-	
-	@property
-	def formatted_teaser(self):
-		return format_text(self.teaser_format, self.teaser)
-	
-	@property
-	def formatted_content(self):
-		return format_text(self.content_format, self.content)
-	
-	def save(self, *args, **kwargs):
-		update_teaser_history = (self.teaser and self.is_dirty_field("teaser"))
-		update_content_history = (self.content and self.is_dirty_field("content"))
-		
-		super(Resource, self).save(*args, **kwargs)
-		
-		'''
-		Create a redirect with the current URL if the path has changed.
-		We use the current URL so that if this object is ever deleted or hidden, we can display a 410/Gone message.
-		Since we create these from the start of life for the object, all previous URLs will be recorded EXCEPT
-		for the case when two objects will have shared the same URL at some point, at which case the original
-		object wins (in the automated fashion; admins can always update the Redirect object manually).
-		'''
-		if self._original_url != self.get_absolute_url():
-			(r, c) = Redirect.objects.get_or_create(
-				original = self.get_absolute_url(),
-				defaults = {
-					'target_object': self,
-					'permanent': True
-				}
-			)
-			if c: r.save()
-			self._original_url = self.get_absolute_url()
-		
-		if update_teaser_history:
-			ch = ContentHistory(owner=self, field_name="teaser", content=self.teaser)
-			ch.save()
-		
-		if update_content_history:
-			ch = ContentHistory(owner=self, field_name="content", content=self.content)
-			ch.save()
+    # Properties
+    sites = models.ManyToManyField(Site, default=[settings.SITE_ID], null=False, blank=False, help_text='The site(s) this item is accessible at.')
+    title = models.CharField("Title", max_length=255, null=False, blank=False)
+    
+    teaser = models.TextField(null=True, blank=True)
+    teaser_format = models.PositiveIntegerField("Teaser Format", choices=CONTENT_FORMATTERS, default=CONTENT_FORMAT_TEXTILE, help_text="The formatter the teaser should be run through when rendering.")
+    content = models.TextField("Content", null=True, blank=True, help_text='The content to display on the page.')
+    content_format = models.PositiveIntegerField("Content Format", choices=CONTENT_FORMATTERS, default=CONTENT_FORMAT_TEXTILE, help_text="The formatter the content should be run through when rendering.")
+    
+    # Publishing status
+    status = models.IntegerField("Status", choices=ENTRY_STATUSES, default=ENTRY_STATUS_PUBLISHED, help_text="Only published items will be visible on the site.")
+    date_published = models.DateTimeField("Date Published", null=True, blank=True, default=datetime.datetime.now, help_text="Item will become visible after this date.  Future posting is supported.")
+    date_hidden = models.DateTimeField("Date Hidden", null=True, blank=True, help_text="Item will be hidden past this date.  No value indicates a perpetual item (most common).")
+        
+    # Object managers
+    objects = models.Manager()
+    published = PublishedManager()
+    
+    class Meta:
+        abstract = True
+    
+    @property
+    def visible(self):
+        return (self.status == ENTRY_STATUS_PUBLISHED and self.date_published < datetime.datetime.now())
+    
+    def __init__(self, *args, **kwargs):
+        super(Resource, self).__init__(*args, **kwargs)
+        if self.id != None:
+            self._original_url = self.get_absolute_url()
+        else:
+            self._original_url = ""
+    
+    def __unicode__(self):
+        return self.title
+    
+    @property
+    def formatted_teaser(self):
+        return format_text(self.teaser_format, self.teaser)
+    
+    @property
+    def formatted_content(self):
+        return format_text(self.content_format, self.content)
+    
+    def save(self, *args, **kwargs):
+        update_teaser_history = (self.teaser and self.is_dirty_field("teaser"))
+        update_content_history = (self.content and self.is_dirty_field("content"))
+        
+        super(Resource, self).save(*args, **kwargs)
+        
+        '''
+        Create a redirect with the current URL if the path has changed.
+        We use the current URL so that if this object is ever deleted or hidden, we can display a 410/Gone message.
+        Since we create these from the start of life for the object, all previous URLs will be recorded EXCEPT
+        for the case when two objects will have shared the same URL at some point, at which case the original
+        object wins (in the automated fashion; admins can always update the Redirect object manually).
+        '''
+        if self._original_url != self.get_absolute_url():
+            (r, c) = Redirect.objects.get_or_create(
+                original = self.get_absolute_url(),
+                defaults = {
+                    'target_object': self,
+                    'permanent': True
+                }
+            )
+            if c: r.save()
+            self._original_url = self.get_absolute_url()
+        
+        if update_teaser_history:
+            ch = ContentHistory(owner=self, field_name="teaser", content=self.teaser)
+            ch.save()
+        
+        if update_content_history:
+            ch = ContentHistory(owner=self, field_name="content", content=self.content)
+            ch.save()
 
 
 class Section(SPObject):
-	# TODO: Create admin class that auto-gens the slug
-	name = models.CharField("Section Name", max_length=255, null=False, blank=False)
-	slug = models.SlugField("Section Slug", max_length=255, null=False, blank=False)
-	# page = models.ForeignKey(Page, null=True, blank=True, help_text="A page to display instead of a section index.")
-	
-	class Meta:
-		verbose_name = "section"
-		verbose_name_plural = "sections"
-	
-	def __unicode__(self):
-		return self.name
-	
-	@models.permalink
-	def get_absolute_url(self):
-		return ('section-archive',(), {'section':self.slug})
+    # TODO: Create admin class that auto-gens the slug
+    name = models.CharField("Section Name", max_length=255, null=False, blank=False)
+    slug = models.SlugField("Section Slug", max_length=255, null=False, blank=False)
+    # page = models.ForeignKey(Page, null=True, blank=True, help_text="A page to display instead of a section index.")
+    
+    class Meta:
+        verbose_name = "section"
+        verbose_name_plural = "sections"
+    
+    def __unicode__(self):
+        return self.name
+    
+    @models.permalink
+    def get_absolute_url(self):
+        return ('section-archive',(), {'section':self.slug})
 
 
 class Page(Resource):
-	url = models.CharField("URL", max_length=255, db_index=True, help_text="The URL for this page. Ensure it begins and ends with a slash.")
-	template_name = models.CharField("Template", max_length=255, blank=True, help_text="If specified, this template will be used instead of 'page.html'")
+    url = models.CharField("URL", max_length=255, db_index=True, help_text="The URL for this page. Ensure it begins and ends with a slash.")
+    template_name = models.CharField("Template", max_length=255, blank=True, help_text="If specified, this template will be used instead of 'page.html'")
 
-	class Meta:
-		ordering = ('url',)
-		verbose_name = "page"
-		verbose_name_plural = "pages"
-		get_latest_by = 'date_published'
-		ordering = ['-date_published','-date_modified']
-	
-	def __unicode__(self):
-		return "%s; %s" % (self.url, self.title)
+    class Meta:
+        ordering = ('url',)
+        verbose_name = "page"
+        verbose_name_plural = "pages"
+        get_latest_by = 'date_published'
+        ordering = ['-date_published','-date_modified']
+    
+    def __unicode__(self):
+        return "%s; %s" % (self.url, self.title)
 
-	def get_absolute_url(self):
-		return self.url
+    def get_absolute_url(self):
+        return self.url
 
 
 class Story(Resource):
-	slug = models.SlugField("URL Slug", max_length=255, null=False, blank=False)
-	user = models.ForeignKey(User, null=False, blank=False)
-	tags = TagField()
-	section = models.ForeignKey(Section, null=True, blank=True, help_text="The section of the site this story will reside in.")
-	show_metadata = models.BooleanField("Show Metadata", default=True, help_text="Toggles the display of author and date information.")
-	allow_comments = models.IntegerField("Comments", choices=COMMENT_STATES, default=COMMENTS_ENABLED, help_text="If comments are disabled then existing comments will be hidden.  Choose 'Closed' to display existing comments and prevent new comments from being added.")
-	
-	class Meta:
-		verbose_name = "story"
-		verbose_name_plural = "stories"
-		get_latest_by = 'date_published'
-		ordering = ['-date_published','-date_modified']
-	
-	def show_comments(self):
-		return self.allow_comments != COMMENTS_DISABLED
-	
-	def allow_commenting(self):
-		return self.allow_comments == COMMENTS_ENABLED
-	
-	def get_absolute_url(self):
-		if self.section:
-			section = self.section.slug
-		else:
-			section = None
-		
-		kwargs = {
-			'section': section,
-			'year':    self.date_published.year,
-			'month':   "%02d"%self.date_published.month,
-			'day':     "%02d"%self.date_published.day,
-			'slug':    self.slug,
-		}
-		return reverse('story-detail', kwargs = kwargs)
+    slug = models.SlugField("URL Slug", max_length=255, null=False, blank=False)
+    user = models.ForeignKey(User, null=False, blank=False)
+    tags = TagField()
+    section = models.ForeignKey(Section, null=True, blank=True, help_text="The section of the site this story will reside in.")
+    show_metadata = models.BooleanField("Show Metadata", default=True, help_text="Toggles the display of author and date information.")
+    allow_comments = models.IntegerField("Comments", choices=COMMENT_STATES, default=COMMENTS_ENABLED, help_text="If comments are disabled then existing comments will be hidden.  Choose 'Closed' to display existing comments and prevent new comments from being added.")
+    
+    class Meta:
+        verbose_name = "story"
+        verbose_name_plural = "stories"
+        get_latest_by = 'date_published'
+        ordering = ['-date_published','-date_modified']
+    
+    def show_comments(self):
+        return self.allow_comments != COMMENTS_DISABLED
+    
+    def allow_commenting(self):
+        return self.allow_comments == COMMENTS_ENABLED
+    
+    def get_absolute_url(self):
+        if self.section:
+            section = self.section.slug
+        else:
+            section = None
+        
+        kwargs = {
+            'section': section,
+            'year':    self.date_published.year,
+            'month':   "%02d"%self.date_published.month,
+            'day':     "%02d"%self.date_published.day,
+            'slug':    self.slug,
+        }
+        return reverse('story-detail', kwargs = kwargs)
 
 ### REDIRECTION ###
 
 class Redirect(SPObject):
-	site = models.ForeignKey(Site, default=settings.SITE_ID, help_text='The site this redirect is applicable to.')
-	original = models.CharField("Original path", max_length=255, unique=True, db_index=True,
-		help_text='The original path for this resource.  This must be an absolute path starting from the root of the site.')
-	current = models.CharField("Current path", max_length=255, null=True, blank=True,
-		help_text='The current path to this resource, if it is not an object.')
-	
-	content_type = models.ForeignKey(ContentType, null=True, blank=True, help_text='If this redirect is to an object, select an object type.')
-	object_id = models.PositiveIntegerField("Object ID", null=True, blank=True, help_text='The ID of the object this redirect will point to.')
-	target_object = generic.GenericForeignKey()
-	
-	permanent = models.BooleanField(default=True, help_text='Is this redirect permanent (code 301)?')
-	
-	class Meta:
-		verbose_name = "redirect"
-		verbose_name_plural = "redirects"
-		ordering = ['-date_created']
-	
-	def __unicode__(self):
-		return "%s to (%s, %s)" % (self.original, self.current, self.target_object)
-	
-	def destination(self):
-		destination = None
-		if self.current and len(self.current):
-			destination = self.current
-		elif self.target_object != None:
-			try:
-				destination = self.target_object.get_absolute_url()
-			except Exception, e:
-				pass
-		return destination
+    site = models.ForeignKey(Site, default=settings.SITE_ID, help_text='The site this redirect is applicable to.')
+    original = models.CharField("Original path", max_length=255, unique=True, db_index=True,
+        help_text='The original path for this resource.  This must be an absolute path starting from the root of the site.')
+    current = models.CharField("Current path", max_length=255, null=True, blank=True,
+        help_text='The current path to this resource, if it is not an object.')
+    
+    content_type = models.ForeignKey(ContentType, null=True, blank=True, help_text='If this redirect is to an object, select an object type.')
+    object_id = models.PositiveIntegerField("Object ID", null=True, blank=True, help_text='The ID of the object this redirect will point to.')
+    target_object = generic.GenericForeignKey()
+    
+    permanent = models.BooleanField(default=True, help_text='Is this redirect permanent (code 301)?')
+    
+    class Meta:
+        verbose_name = "redirect"
+        verbose_name_plural = "redirects"
+        ordering = ['-date_created']
+    
+    def __unicode__(self):
+        return "%s to (%s, %s)" % (self.original, self.current, self.target_object)
+    
+    def destination(self):
+        destination = None
+        if self.current and len(self.current):
+            destination = self.current
+        elif self.target_object != None:
+            try:
+                destination = self.target_object.get_absolute_url()
+            except Exception, e:
+                pass
+        return destination
 
 
 ### HISTORY ###
 
 class ContentHistory(SPObject):
-	content_type = models.ForeignKey(ContentType, null=True, blank=True)
-	object_id = models.PositiveIntegerField(null=True, blank=True)
-	owner = generic.GenericForeignKey()
-	field_name = models.CharField(max_length=255)
-	content = models.TextField("Content", null=True, blank=True)
-	
-	class Meta:
-		verbose_name = "content history"
-		verbose_name_plural = "content histories"
-		ordering = ['-date_created', 'content_type', 'object_id']
-	
-	def __unicode__(self):
-		return "%s: %s" % (self.owner, self.field_name)
-	
-	@classmethod
-	def history(owner, field):
-		#FIXME: owner won't work here, need to search by ID and PK
-		return ContentHistory.objects.filter(owner=owner, field_name=field).order_by('-date_created')
-
-### COMMENTS ###
-
-from mptt.models import MPTTModel, TreeForeignKey
-
-class Comment(MPTTModel):
-    """ Threaded comments for blog posts """
-    post = models.ForeignKey(Post)
-    author = models.CharField(max_length=60)
-    comment = models.TextField()
-    added  = models.DateTimeField(default=datetime.now)
-    # a link to comment that is being replied, if one exists
-    parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
-
-    class MPTTMeta:
-        # comments on one level will be ordered by date of creation
-        order_insertion_by=['added']
+    content_type = models.ForeignKey(ContentType, null=True, blank=True)
+    object_id = models.PositiveIntegerField(null=True, blank=True)
+    owner = generic.GenericForeignKey()
+    field_name = models.CharField(max_length=255)
+    content = models.TextField("Content", null=True, blank=True)
+    
+    class Meta:
+        verbose_name = "content history"
+        verbose_name_plural = "content histories"
+        ordering = ['-date_created', 'content_type', 'object_id']
+    
+    def __unicode__(self):
+        return "%s: %s" % (self.owner, self.field_name)
+    
+    @classmethod
+    def history(owner, field):
+        #FIXME: owner won't work here, need to search by ID and PK
+        return ContentHistory.objects.filter(owner=owner, field_name=field).order_by('-date_created')

templates/base.html

 
 <html>
 <head>
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-	<title>{{site.name}}{% if title %} - {{title}}{% endif %}</title>
-	<style type="text/css">
-	* {
-		font-family: Helvetica, Arial, sans-serif;
-	}
-	#container {
-		margin:0 auto;
-		width: 950px;
-	}
-	
-	#header {
-		border-top: 3px solid gray;
-		border-bottom: 2px solid silver;
-	}
-	
-	#content {
-	}
-	
-	.post {
-		margin: 1pc 2pc;
-		border-bottom: 1px dotted silver;
-	}
-	
-	.post.teaser {
-		background-color: #f6f6f6;
-	}
-	
-	label {
<