Commits

Anonymous committed 2de617e

Added sections, updated importer, and recreated a Page derivative

--HG--
branch : testing

Comments (0)

Files changed (10)

 * URL management should be central.
 * When an object is saved, it should save a redirect with its current URL and its content type.
 * If the URL ever changes, that redirect will start to get hit and serve as a safety net for the object.  It will always redirect to the object's current URL as returned by get_absolute_url.
-
-
-*Notes*
-Remove Page type, flatpages works well for this.
-Remove hard-coded dated URLs.  That's so 2007.  New hotness is slug-only.  However, use string.Template and a settings.py key to let people make their own.  Then have an Article's get_absolute_url return path and have save() update the path with the template on save, creating Redirects as needed.  Then urls.py can just say (?P<path>) and be done with it (the final one, of course).
-Remove News, add Sections (articles go in sections, and users can create a URL scheme that includes them if they want).  Sections are not categories, they are sections like in a news site.  There can be a "news" section and a "blog" section and so on.  In fact, they work like a multi-blog site, really.  Articles go in sections (or none at all), and the index for that section only shows those articles.  Woo.
-
-What do section URLs really look like?
-
-/blog/a-title
-/a-title
-/blog/ (index?) or /sections/blog/ ?
-
-Should we expose to the user that these are "section" objects, or just setup the URL for them?  Just begin the URL with it.  Users don't have to use Sections, but if they do, we shouldn't presume they're using it for any other reason than URL categorization ("/blog/foo", "/news/foo").
-What happens when a Resource has the same URL?  The Resource loses.
-Does the section always win? Yep.
-What if we don't want a section to have an index? Then link it to a Resource to display instead.
-What about using a different base template for a section, or some other way to do templates for it?  Add a section prefix to the template search path?  ["/sections/$section/document.html", etc.]  Sounds workable.
-What about Pages?  Flatpages does real pages better, and we can do the same with Resource by allowing the author information to be turned off.
-Won't they show in indexes?  Copy the Drupal "promote" feature for this, then.
-Isn't this just copying Drupal?  Well, isn't that the point?
-Not that I recall.  It was to have something to hack on that wasn't bloated with age. -- And that still had the features you've been using.  So, it was the point.
-Oook.  What about blocks?  How to do dynamic blocks?  Context processors.
 MIDDLEWARE_CLASSES = (
 	'django.middleware.gzip.GZipMiddleware',
 	'django.middleware.http.ConditionalGetMiddleware',
-	'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
 	'sitepoet.middleware.RedirectionMiddleware',
+	'sitepoet.middleware.PageMiddleware',
 	'django.middleware.common.CommonMiddleware',
 	'django.contrib.sessions.middleware.SessionMiddleware',
 	'django.contrib.auth.middleware.AuthenticationMiddleware',
 INSTALLED_APPS = (
 	'django.contrib.auth',
 	'django.contrib.contenttypes',
-	'django.contrib.flatpages',
 	'django.contrib.sessions',
 	'django.contrib.sites',
 	'django.contrib.admin',

sitepoet/admin.py

-from models import *
+from django import forms
 from django.contrib import admin
+from django.utils.translation import ugettext_lazy as _
+
+from sitepoet.models import *
 
 ## ACTIONS ##
 
 
 ### 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
+
+
 class PageAdmin(admin.ModelAdmin):
-	list_display = ('title', 'absolute_url', 'visibility', 'status', 'date_published', 'date_modified')
-	list_editable = ('status',)
-	list_filter = ('date_published', 'date_modified', 'status')
+	form		   = PageForm
+	list_display   = ('title', 'absolute_url', 'visibility', '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', 'slug')
-	actions = [action_mark_draft, action_mark_published]
-	fieldsets = [
-		('',				{'fields': ['title', 'path']}),
-		('Content',			{'fields': ['content', 'content_format']}),
-		('Availability',	{'fields': ['date_published', 'status', 'sites'], 'classes':('collapse',)}),
-		]
+	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()
 	
 	def visibility(self, obj):
 		f = Page.published.filter(pk=obj.id)
-		return ("Not Visible", "Visible")[(obj in f)]
+		return (_("Not Visible"), _("Visible"))[(obj in f)]
 
 admin.site.register(Page, PageAdmin)
 
-
 ### STORIES ###
 
 class StoryAdmin(admin.ModelAdmin):
 	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',)}),
+		('Availability',	{'fields': ['status', 'date_published', 'date_hidden', 'allow_comments', 'sites'], 'classes':('collapse',)}),
 		]
 
 admin.site.register(Story, StoryAdmin)
-admin.site.register(ContentHistory)
+
+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"
+	
+admin.site.register(ContentHistory, ContentHistoryAdmin)
 
 ### SECTIONS ###
 

sitepoet/drupal_support/management/commands/convertdrupal.py

 				print "No Drupal nodes found."
 			
 			# Create some sections
-			blog_section = Section(name="blog")
-			blog_section.save()
+			(blog_section, c) = Section.objects.get_or_create(name="blog")
+			if c:
+				blog_section.save()
 
-			story_section = Section(name="story")
-			story_section.save()
+			(story_section, c) = Section.objects.get_or_create(name="story")
+			if c:
+				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..." % node.nid
+				
 				if node.type == "blog" or node.type == "story":
-					# See if we've imported this node before
+					if node.type == "blog":
+						section = blog_section
+					else:
+						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:
-						obj = Story.objects.get(pk=node.nid, title=node.title)
-						if verbose: print "Skipping node %d" % node.nid
-						continue
-					except Story.DoesNotExist:
-						if verbose: print "Processing node %d..." % node.nid
-						if node.type == "blog":
-							section = blog_section
-						else:
-							section = story_section
-						
-						# Create an Story for the node
-						obj = Story(
-							user = user,
-							id = node.nid,
-							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,
-							)
+						contents = node.current_revision.get_parsed_contents()
 					
-						# 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:
-							print "WARNING: No body content found for %s (%d)" % (obj.title, obj.id)
+						obj.teaser = contents['teaser']
+						obj.content = contents['body']
+					except DrupalNodeRevision.DoesNotExist, e:
+						print "WARNING: No body content found for %s (%d)" % (obj.title, obj.id)
+				
+					# Save the story
+					obj.save()
+				
+					# Ensure the modification date is proper
+					obj.date_modified = datetime.datetime.fromtimestamp(node.changed)
+					obj.save()
+				
+					# Add to the current site
+					obj.sites.add(Site.objects.get_current())
+				elif node.type == "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,
+						)
 					
-						# Save the story
-						obj.save()
+					# Set the current content value
+					try:
+						obj.content = node.current_revision.body
+					except DrupalNodeRevision.DoesNotExist, e:
+						print "WARNING: No body content found for %s (%d)" % (obj.title, obj.id)
 					
-						# Ensure the modification date is proper
-						obj.date_modified = datetime.datetime.fromtimestamp(node.changed)
-						obj.save()
+					# Save
+					obj.save()
 					
-						# Add to the current site
-						obj.sites.add(Site.objects.get_current())
-				elif node.type == "page":
-					# Import as page
-					# See if we've imported this node before
-					try:
-						obj = Page.objects.get(pk=node.nid, title=node.title)
-						if verbose: print "Skipping node %d" % node.nid
-						continue
-					except Page.DoesNotExist:
-						if verbose: print "Processing node %d..." % node.nid
+					# Add to the current site
+					obj.sites.add(Site.objects.get_current())
 					
-						# Create an object for the node
-						obj = Page(
-							id = node.nid,
-							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:
-							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():
-							print aliases
-							obj.path = aliases[0].dst
-						
-						# Ensure the modification date is proper
-						obj.date_modified = datetime.datetime.fromtimestamp(node.changed)
-						obj.save()
+					# 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():
+						print "Page aliases:", aliases
+						obj.url = '/' + aliases[0].dst
 					
-					else:
-						# Bail if we don't have something to work on
-						print "Unsupported type:", node.type
-						continue
+					# 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
+					print "Unsupported type:", node.type
+					continue
 				
 				# Lookup any URL aliases for this node and create redirects
 				try:
 					
 					# Now we get any others that were made
 					for alias in aliases:
-						if obj.__class__ == Page and alias.dst == obj.path: continue
-						r = Redirect(original="/" + alias.dst, target_object=obj)
-						r.save()
-						if verbose == 2: print " Created redirect from", alias.dst
+						dst = "/" + alias.dst
+						if obj.__class__ == Page and dst == obj.url: continue
+						try:
+							r = Redirect(original=dst, target_object=obj)
+							r.save()
+							if verbose == 2: print " Created redirect from", dst
+						except Exception, e:
+							if verbose == 1: print "*Failed to create redirect from", dst, "to", obj, ":", e
+							continue
 						
 				except DrupalUrlAlias.DoesNotExist, e:
 					alias = ''

sitepoet/middleware/__init__.py

 from django.http import HttpResponsePermanentRedirect, HttpResponseRedirect, HttpResponseGone
-from sitepoet.models import Redirect
 from django.conf import settings
+from django.http import Http404
+
+from sitepoet.models import Redirect
+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]
-			
 			# Try without the trailing slash
 			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 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.
 				# 410 Gone
 				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

sitepoet/models.py

 from django.db import models
 from django.utils.safestring import mark_safe 
 from tagging.fields import TagField
+
 from sitepoet.managers import *
 from sitepoet.templatetags.slugify import slugify
 
 		self._original_state = self._as_dict()
 
 
-### DOCUMENTS ###
+### Model Objects ###
 
 class Resource(SPObject):
 	# Properties
 	
 	def __init__(self, *args, **kwargs):
 		super(Resource, self).__init__(*args, **kwargs)
-		self._original_url = self.get_absolute_url()
+		if self.id != None:
+			self._original_url = self.get_absolute_url()
+		else:
+			self._original_url = ""
 	
 	def __unicode__(self):
 		return self.title
 		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.
 				}
 			)
 			if c: r.save()
-		
-		super(Resource, self).save(*args, **kwargs)
-		
-		self._original_url = self.get_absolute_url()
+			self._original_url = self.get_absolute_url()
 		
 		if update_teaser_history:
 			ch = ContentHistory(owner=self, field_name="teaser", content=self.teaser)
 			ch.save()
 
 
-class Page(Resource):
-	path = models.CharField("URL Path", max_length=255, null=True, blank=True)
-		
-	@models.permalink
-	def get_absolute_url(self):
-		return ('page-detail', (), {'path': self.path})
-
-
 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.")
+	# 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
 		return ('section-archive',(), {'section':self.name})
 
 
+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'")
+
+	class Meta:
+		ordering = ('url',)
+		verbose_name = "page"
+		verbose_name_plural = "pages"
+	
+	def __unicode__(self):
+		return "%s; %s" % (self.url, self.title)
+
+	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)
 	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"
+	
 	def show_comments(self):
 		return (self.allow_comments == COMMENTS_ENABLED | self.allow_comments == COMMENTS_CLOSED)
 	
 		return self.allow_comments == COMMENTS_ENABLED
 	
 	def get_absolute_url(self):
-		if self.id:
-			return reverse('story-detail', kwargs = {
-				'section': self.section.name,
-				'year':    self.date_published.year,
-				'month':   "%02d"%self.date_published.month,
-				'day':     "%02d"%self.date_published.day,
-				'slug':    self.slug,
-			})
-		else:
-			return ""
+		kwargs = {
+			'section': self.section.name,
+			'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 ###
 	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):
 	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)

sitepoet/templates/comment.html

 <div class="comment" style="margin-left: {{comment.depth}}pc;">
+	<h3>{{comment.user_name}}</h3>
 	{% if comment.title %}<p class="title" /> {{comment.title}}{% endif %}
 	<p class="content" /> {{comment.comment}}
 	<ul class="links">

sitepoet/templates/story_comment.html

-<div class="comment">
-	<h3>{{comment.user_name}}</h3>
-	<p>{{comment.comment}}</p>
-</div>
 		kwargs = { "teasers": True },
 		name   = "story-archive"
 	),
-	
-	# Pages
-	url(
-		regex  = r'^(?P<path>.*)$',
-		view   = 'page_detail',
-		name   = "page-detail"
-	),
 )
 
-urlpatterns += patterns('',
-	# MetaWeblog access point
-	url(r'^xmlrpc/', 'sitepoet.xmlrpc.view', kwargs={'module':'sitepoet.metaweblog'}),
-)
+urlpatterns += patterns('sitepoet.views',
+	(r'^(?P<url>.*)$', 'view_page'),
+)
+
+# urlpatterns += patterns('',
+# 	# MetaWeblog access point
+# 	url(r'^xmlrpc/', 'sitepoet.xmlrpc.view', kwargs={'module':'sitepoet.metaweblog'}),
+# )

sitepoet/views.py

-from models import *
 from django.core.paginator import Paginator, InvalidPage, EmptyPage
 from django.core.xheaders import populate_xheaders
-from django.http import HttpResponse, Http404
+from django.http import HttpResponse, HttpResponseRedirect, Http404
 from django.template import loader, Context, RequestContext
 from django.shortcuts import render_to_response, get_object_or_404
 from django.conf import settings
 from django.contrib.sites.models import *
 
-
-## Pages ###
-
-def page_detail(request, path):
-	paths = [path, "/" + path]
-	
-	# Look for the exact path
-	try:
-		page = Page.published.get(path__in=paths, sites__id__exact=settings.SITE_ID)
-	except Page.DoesNotExist:
-		if settings.APPEND_SLASH:
-			for p in paths:
-				try:
-					page = Page.published.get(path=p+"/", sites__id__exact=settings.SITE_ID)
-					return HttpResponseRedirect(p)
-				except Page.DoesNotExist:
-					continue
-		raise Http404
-	
-	# Prepare the context
-	context = {
-		"title":  page.title,
-		"object": page
-	}
-	
-	# Serve page.
-	response = render_to_response("page.html", context, context_instance=RequestContext(request))
-	populate_xheaders(request, response, Page, page.id)
-	return response
+from sitepoet.models import *
 
 
 ### Stories ###
 
 
 def story_archive(request, section=None, year=None, month=None, day=None, teasers=False):
+	# If there's a section, try and load it then filter the stories by it
+	if section:
+		obj = get_object_or_404(Section, name=section)
+		stories = Story.published.filter(section=obj)
+	else:
+		stories = Story.published.all()
+	
 	# Fetch a list of stories
-	stories = Story.published.all()
 	if year:
 		stories = stories.filter(date_published__year=int(year))
 		if month:
 	return render_to_response(templates, context, context_instance=RequestContext(request))
 
 
-### Sections ###
+def view_page(request, url):
+	# URLs begin with a slash, but a bad admin or urls.py can remove it.
+	if not url.startswith('/'):
+		url = "/" + url
 
-def section_index(request, section=None):
-	obj = get_object_or_404(Section, name=section)
+	try:
+		page = Page.published.get(url__exact=url, sites__id__exact=settings.SITE_ID)
 	
-	# Prepare the template data
+	except Page.DoesNotExist, e:
+		# If there's no trailing slash, and we're using it, then try that URL next.
+		if not url.endswith('/') and settings.APPEND_SLASH:
+			return HttpResponseRedirect("%s/" % request.path_info)
+		
+		# Otherwise, give up.
+		raise Http404
+	
+	except Page.MultipleObjectsReturned, e:
+		raise Http404 #What else to do?  You broke it, man.  One URL = One Object.
+
+	# Prepare the context
 	context = {
-		"title":  obj.name,
-		"object": obj
+		"title":  page.title,
+		"object": page,
+		"flatpage": page, #Let people use the same template to a degree
 	}
-	
-	response = render_to_response("section_detail.html", context, context_instance=RequestContext(request))
-	populate_xheaders(request, response, Section, obj.id)
+
+	# Create the template list
+	if page.template_name:
+		templates = [page.template_name, "page.html"]
+	else:
+		templates = ["page.html"]
+
+	response = render_to_response(templates, context, context_instance=RequestContext(request))
+	populate_xheaders(request, response, Page, page.id)
 	return response
-