Commits

Luke Plant  committed 2a0f029 Merge

Merged from default

  • Participants
  • Parent commits 40de9dd, 80ea29b
  • Branches live

Comments (0)

Files changed (37)

File cciw/cciwmain/admin.py

-from cciw.cciwmain.models import Site, Person, Camp, MenuLink, HtmlChunk
+from cciw.cciwmain.models import Site, Person, Camp
 from django.contrib import admin
 
 class SiteAdmin(admin.ModelAdmin):
     filter_horizontal = ('leaders', 'admins')
     date_hierarchy = 'start_date'
 
-class MenuLinkAdmin(admin.ModelAdmin):
-    list_display = ('title', 'url', 'listorder','visible','parent_item')
-
-class HtmlChunkAdmin(admin.ModelAdmin):
-    list_display = ('name', 'page_title', 'menu_link')
-
 admin.site.register(Site, SiteAdmin)
 admin.site.register(Person, PersonAdmin)
 admin.site.register(Camp, CampAdmin)
-admin.site.register(MenuLink, MenuLinkAdmin)
-admin.site.register(HtmlChunk, HtmlChunkAdmin)
-
-from django.contrib.auth import admin

File cciw/cciwmain/common.py

         # instances.
         return context
 
-    from cciw.cciwmain.models import MenuLink
+    from cciw.sitecontent.models import MenuLink
     thisyear = get_thisyear()
     context['thisyear'] = thisyear
     assert type(request.path) is unicode

File cciw/cciwmain/fixtures/basic.json

             "url": "/", 
             "listorder": 0
         }, 
-        "model": "cciwmain.menulink", 
+        "model": "sitecontent.menulink", 
         "pk": "1"
     }, 
     {

File cciw/cciwmain/fixtures/contact.json

             "html": "\n<p>If you have an enquiry to make to the CCIW directors,\nplease use the form below and your message will be forwarded \nto the relevant person, who will reply by e-mail.</p>\n", 
             "page_title": ""
         }, 
-        "model": "cciwmain.htmlchunk", 
+        "model": "sitecontent.htmlchunk", 
         "pk": "feedback_intro"
     }, 
     {
             "html": "", 
             "page_title": ""
         }, 
-        "model": "cciwmain.htmlchunk", 
+        "model": "sitecontent.htmlchunk", 
         "pk": "feedback_outro"
     }
 ]

File cciw/cciwmain/fixtures/htmlchunks.json

             "url": "/", 
             "listorder": 0
         }, 
-        "model": "cciwmain.menulink", 
+        "model": "sitecontent.menulink", 
         "pk": 1
     }, 
     {
             "html": "<p>CCIW is a charitable company that runs outdoor camps on a number of sites in Wales.\r\n The camps provide great outdoor fun with God and the Bible at the centre of each day.</p>\r\n<p>The detailed format of the day varies from camp to camp, but all of them \r\ninclude a morning service as a camp, activities in the afternoon and \r\nbible studies in the evening in tent groups.  The kind of activities we do \r\ninclude canoeing, raft-building, swimming, walking, rock-climbing, and gorge-walking, \r\nas well as football, rounders and other camp site games.  Below is \r\na list of other pages to find out more about the camps.</p>\r\n\r\n<h2>Contents:</h2>\r\n<ul>\r\n<li><a href=\"/news/\">News</a><br/>\r\nThe latest news, particularly about what is available on the website,\r\n is here, with the opportunity to add your own comments.\r\n\r\n<p>See also:</p>\r\n<ul><li><a href=\"/posts/\">Recent message board posts</a></li>\r\n<li><a href=\"/topics/\">Recently started topics</a></li>\r\n</ul>\r\n<br/><br/>\r\n</li>\r\n<li><a href=\"/thisyear/\">Camps {{thisyear}}</a><br/>\r\nInformation for camps in {{thisyear}}, including dates, leadership teams,\r\n and how to book.<br/><br/>\r\n</li>\r\n\r\n<li><a href=\"/camps/\">Camp forums and photos</a><br/>\r\nHere you can find photos and messages boards from the CCIW camps that have run in the past.\r\nHopefully these will give you an idea of what we do, or help you stay in contact with\r\nfriends you met on camp. <br/><br/>\r\nThe leaders and committee members have also lead and organised camps with churches and\r\nother organisations (particularly <a href=\"http://www.emw.org.uk/\">The Evangelical Movement of Wales</a>) for many years, and some photos of these camps\r\ncan be found here. <br/><br/>\r\n</li>\r\n\r\n<li><a href=\"/sites/\">Camp sites</a><br/>\r\nInformation on the different sites we run camps at.<br/><br/>\r\n</li>\r\n\r\n\r\n<li><a href=\"/members/\">Members</a><br/>\r\nList of currently signed up members to the website.  Look for your friends here!<br/><br/>\r\n</li>\r\n\r\n<li><a href=\"/info/\">About CCIW</a><br/>\r\nCompany information, including the doctrinal basis of CCIW, legal information, and the names and addresses of the directors. \r\n<br/>\r\n<br/></li>\r\n\r\n\r\n<li><a href=\"/website/\">About website</a><br/>\r\nInformation about the website, including message boards about the different features.<br/><br/>\r\n</li>\r\n\r\n<li><a href=\"/contact/\">Contact us</a><br/>\r\nAny comments about camp, suggestions, or other reasons to get in contact with us - leave us a message.<br/>\r\n<br/>\r\n</li>\r\n\r\n</ul>\r\n<br/>\r\n<p>CCIW is a limited company (reg. No. 3900844), and a Registered Charity (No:1079713)</p>", 
             "page_title": "Christian Camps in Wales"
         }, 
-        "model": "cciwmain.htmlchunk", 
+        "model": "sitecontent.htmlchunk", 
         "pk": "home_page"
     }
 ]

File cciw/cciwmain/fixtures/users.json

             "last_login": "2007-12-17 18:49:13", 
             "groups": [], 
             "user_permissions": [
-                ["add_htmlchunk", "cciwmain", "htmlchunk"],
-                ["change_htmlchunk", "cciwmain", "htmlchunk"],
-                ["delete_htmlchunk", "cciwmain", "htmlchunk"]
+                ["add_htmlchunk", "sitecontent", "htmlchunk"],
+                ["change_htmlchunk", "sitecontent", "htmlchunk"],
+                ["delete_htmlchunk", "sitecontent", "htmlchunk"]
             ], 
             "password": "sha1$72713$4d655a356d9dfde29f74d04e8c40a6e412d26d0a", 
             "email": "editor@somewhere.com", 

File cciw/cciwmain/migrations/0008_move_models_to_sitecontent_app.py

+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        pass
+
+    def backwards(self, orm):
+        pass
+
+    depends_on = [
+        ('sitecontent', '0001_initial'),
+        ]
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
+        },
+        'cciwmain.camp': {
+            'Meta': {'ordering': "['-year', 'number']", 'unique_together': "(('year', 'number'),)", 'object_name': 'Camp'},
+            'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'camps_as_admin'", 'blank': 'True', 'null': 'True', 'to': "orm['auth.User']"}),
+            'age': ('django.db.models.fields.CharField', [], {'max_length': '3'}),
+            'chaplain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'camps_as_chaplain'", 'blank': 'True', 'null': 'True', 'to': "orm['cciwmain.Person']"}),
+            'end_date': ('django.db.models.fields.DateField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'leaders': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'camps_as_leader'", 'blank': 'True', 'null': 'True', 'to': "orm['cciwmain.Person']"}),
+            'number': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'officers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'through': "orm['officers.Invitation']", 'symmetrical': 'False'}),
+            'online_applications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'previous_camp': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'next_camps'", 'blank': 'True', 'null': 'True', 'to': "orm['cciwmain.Camp']"}),
+            'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cciwmain.Site']"}),
+            'start_date': ('django.db.models.fields.DateField', [], {}),
+            'year': ('django.db.models.fields.PositiveSmallIntegerField', [], {})
+        },
+        'cciwmain.person': {
+            'Meta': {'ordering': "('name',)", 'object_name': 'Person'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'cciwmain.site': {
+            'Meta': {'object_name': 'Site'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {}),
+            'long_name': ('django.db.models.fields.CharField', [], {'max_length': "'50'"}),
+            'short_name': ('django.db.models.fields.CharField', [], {'max_length': "'25'", 'unique': 'True'}),
+            'slug_name': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': "'25'", 'unique': 'True', 'blank': 'True'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'officers.invitation': {
+            'Meta': {'ordering': "('-camp__year', 'officer__first_name', 'officer__last_name')", 'unique_together': "(('officer', 'camp'),)", 'object_name': 'Invitation'},
+            'camp': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cciwmain.Camp']"}),
+            'date_added': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'notes': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'officer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        }
+    }
+
+    complete_apps = ['cciwmain']

File cciw/cciwmain/models.py

 import datetime
 
-from django.contrib.admin.views.main import quote
 from django.contrib.auth.models import User
 from django.db import models
 from django.utils.safestring import mark_safe
 
 from cciw.cciwmain import signals
-from cciw.cciwmain.common import standard_subs
-import cciw.middleware.threadlocals as threadlocals
 
 
 class Site(models.Model):
         unique_together = (('year', 'number'),)
 
 
-class MenuLink(models.Model):
-    title = models.CharField("title", max_length=50)
-    url = models.CharField("URL", max_length=100)
-    extra_title = models.CharField("Disambiguation title", max_length=100, blank=True)
-    listorder = models.SmallIntegerField("order in list")
-    visible = models.BooleanField("Visible", default=True)
-    parent_item = models.ForeignKey("self", null=True, blank=True,
-        verbose_name="Parent item (none = top level)",
-        related_name="child_links")
-
-    def __unicode__(self):
-        return  u"%s [%s]" % (self.url, standard_subs(self.title))
-
-    def get_visible_children(self, request):
-        """Gets a list of child menu links that should be visible given the current url"""
-        if request.path == self.url:
-            return self.child_links
-        else:
-            return []
-
-    class Meta:
-        # put top level items at top of list, others into groups, for the admin
-        ordering = ('-parent_item__id', 'listorder')
-
-
-class HtmlChunk(models.Model):
-    name = models.SlugField("name", primary_key=True, db_index=True)
-    html = models.TextField("HTML")
-    menu_link = models.ForeignKey(MenuLink, verbose_name="Associated URL",
-        null=True, blank=True)
-    page_title = models.CharField("page title (for chunks that are pages)", max_length=100,
-        blank=True)
-
-    def __unicode__(self):
-        return self.name
-
-    def render(self, request):
-        """Render the HTML chunk as HTML, with replacements
-        made and any member specific adjustments."""
-        html = standard_subs(self.html)
-        user = threadlocals.get_current_user()
-        if user and not user.is_anonymous() and user.is_staff \
-            and user.has_perm('cciwmain.change_htmlchunk'):
-            html += (u"""<div class="editChunkLink">&laquo;
-                        <a href="/admin/cciwmain/htmlchunk/%s/">Edit %s</a> &raquo;
-                        </div>""" % (quote(self.name), self.name))
-        return mark_safe(html)
-
-    class Meta:
-        verbose_name = "HTML chunk"
 
 
 import cciw.cciwmain.hooks

File cciw/cciwmain/templatetags/standardpage.py

 from django.utils.http import urlquote, urlencode
 from django import template
-from cciw.cciwmain.models import HtmlChunk
+from cciw.sitecontent.models import HtmlChunk
 from cciw.forums.models import Member, Post, Topic, Photo
 from cciw.cciwmain.common import standard_subs, get_current_domain
 from cciw.cciwmain.utils import obfuscate_email

File cciw/cciwmain/tests/client.py

     def member_logout(self):
         self.cookies.clear()
 
-def get_context_var(context_list, var, default=None):
-    """Returns a context variable from the Django response.context object"""
-    if isinstance(context_list, list):
-        for d in reversed(context_list):
-            if d.has_key(var):
-                return d[var]
-    else:
-        if context_list.has_key(var):
-            return context_list[var]
-    return default
-
 
 class RequestFactory(client.RequestFactory):
 

File cciw/cciwmain/tests/decorators.py

 from BeautifulSoup import BeautifulSoup
 from django.test import TestCase
-from cciw.cciwmain.tests.client import CciwClient, get_context_var
+from cciw.cciwmain.tests.client import CciwClient
 from cciw.cciwmain.tests.forums import CreatePollPage, ADD_POLL_URL
 from cciw.cciwmain.decorators import LOGIN_FORM_KEY
 from cciw.cciwmain.tests import members

File cciw/cciwmain/tests/forums.py

 from django.test import TestCase
 from cciw.forums.models import Topic, Member, Poll, Forum, Post, Photo, Gallery
 from cciw.cciwmain.tests.utils import init_query_caches, FuzzyInt
-from cciw.cciwmain.views import forums
+from cciw.forums.views import forums as forums_views
 from django.core.urlresolvers import reverse
 from datetime import datetime
 from cciw.cciwmain import decorators
 
         request = self.factory.get(self.forum.get_absolute_url())
         with self.assertNumQueries(FuzzyInt(1, 5)):
-            resp = forums.topicindex(request, title="Title", forum=self.forum)
+            resp = forums_views.topicindex(request, title="Title", forum=self.forum)
             resp.render()
             expected_count = settings.FORUM_PAGINATE_TOPICS_BY * 2
             self.assertContains(resp, "<a title=\"Information about",
 
         request = self.factory.get(self.forum.get_absolute_url(), {'format':'atom'})
         with self.assertNumQueries(FuzzyInt(1, 3)):
-            forums.topicindex(request, title="Title", forum=self.forum)
+            forums_views.topicindex(request, title="Title", forum=self.forum)
 
 
 class AllTopicsPage(TestCase):
         init_query_caches()
 
     def path(self):
-        return reverse("cciw.cciwmain.views.forums.all_topics")
+        return reverse("cciw.forums.views.forums.all_topics")
 
     def test_get(self):
         response = self.client.get(self.path())
 
         request = self.factory.get(self.path())
         with self.assertNumQueries(FuzzyInt(3, 4)):
-            resp = forums.all_topics(request)
+            resp = forums_views.all_topics(request)
             resp.render()
             expected_count = settings.FORUM_PAGINATE_TOPICS_BY
             self.assertContains(resp, "<a title=\"Information about user",
 
         request = self.factory.get(self.path(), {'format':'atom'})
         with self.assertNumQueries(FuzzyInt(1, 2)):
-            forums.all_topics(request)
+            forums_views.all_topics(request)
 
 
 class TopicPage(TestCase):
 
         request = self.factory.get(self.topic.get_absolute_url())
         with self.assertNumQueries(FuzzyInt(1, 6)):
-            forums.topic(request, title_start="Title", topicid=self.topic.id).render()
+            forums_views.topic(request, title_start="Title", topicid=self.topic.id).render()
 
         request = self.factory.get(self.topic.get_absolute_url(), {'format':'atom'})
         with self.assertNumQueries(FuzzyInt(1, 2)):
-            forums.topic(request, title_start="Title", topicid=self.topic.id)
+            forums_views.topic(request, title_start="Title", topicid=self.topic.id)
 
 
 class PhotoIndexPage(TestCase):
 
         request = self.factory.get(self.gallery.get_absolute_url())
         with self.assertNumQueries(3):
-            forums.photoindex(request, self.gallery, {'title':'test'}, ['']).render()
+            forums_views.photoindex(request, self.gallery, {'title':'test'}, ['']).render()
 
         request = self.factory.get(self.gallery.get_absolute_url(), {'format':'atom'})
         with self.assertNumQueries(1):
-            forums.photoindex(request, self.gallery, {'title':'test'}, [''])
+            forums_views.photoindex(request, self.gallery, {'title':'test'}, [''])
 
 
 class PhotoPage(TestCase):
 
         request = self.factory.get(self.photo.get_absolute_url())
         with self.assertNumQueries(5):
-            forums.photo(request, self.photo, {}, ['']).render()
+            forums_views.photo(request, self.photo, {}, ['']).render()
 
         request = self.factory.get(self.photo.get_absolute_url(), {'format':'atom'})
         with self.assertNumQueries(1):
-            forums.photo(request, self.photo, {}, [''])
+            forums_views.photo(request, self.photo, {}, [''])
 
 
 class AllPostsPage(TestCase):
         init_query_caches()
 
     def path(self):
-        return reverse("cciw.cciwmain.views.forums.all_posts")
+        return reverse("cciw.forums.views.forums.all_posts")
 
     def test_get(self):
         response = self.client.get(self.path())
 
         request = self.factory.get(self.path())
         with self.assertNumQueries(FuzzyInt(1, 4)):
-            resp = forums.all_posts(request)
+            resp = forums_views.all_posts(request)
             resp.render()
             expected_count = settings.FORUM_PAGINATE_POSTS_BY
             self.assertContains(resp, "<a title=\"Information about user",
 
         request = self.factory.get(self.path(), {'format':'atom'})
         with self.assertNumQueries(FuzzyInt(1, 2)):
-            forums.all_posts(request)
+            forums_views.all_posts(request)
 
     def test_query_count_photos(self):
         """
 
         request = self.factory.get(self.path())
         with self.assertNumQueries(FuzzyInt(1, 3)):
-            resp = forums.all_posts(request)
+            resp = forums_views.all_posts(request)
             resp.render()
             expected_count = settings.FORUM_PAGINATE_POSTS_BY
             self.assertContains(resp, "<a title=\"Information about user",
 
         request = self.factory.get(self.path(), {'format':'atom'})
         with self.assertNumQueries(FuzzyInt(1, 2)):
-            forums.all_posts(request)
+            forums_views.all_posts(request)
 
 
 class CreatePollPage(TestCase):

File cciw/cciwmain/tests/members.py

 from cciw.cciwmain.tests.utils import init_query_caches, FuzzyInt
 from cciw.utils.tests.twillhelpers import TwillMixin, make_twill_url
 import cciw.cciwmain.decorators
-import cciw.cciwmain.views.members
 
 
 # created by fixture
             Member.objects.create(user_name="NewMember%d" % i,
                                   date_joined=datetime.datetime.now())
 
-        from cciw.cciwmain.views.members import index
+        from cciw.forums.views.members import index
 
         request = self.factory.get(reverse('cciwmain.members.index'))
         with self.assertNumQueries(FuzzyInt(1, 4)):

File cciw/cciwmain/tests/news.py

 from cciw.cciwmain.tests.members import TEST_MEMBER_USERNAME
 from cciw.cciwmain.tests.client import CciwClient, RequestFactory
 from cciw.cciwmain.tests.utils import init_query_caches, FuzzyInt
-from cciw.cciwmain.views import forums
+from cciw.forums.views import forums as forums_views
 
 
 class NewsPage(TestCase):
 
         request = factory.get(path)
         with self.assertNumQueries(FuzzyInt(1, 5)):
-            resp = forums.news(request)
+            resp = forums_views.news(request)
             resp.render()
             expected_count = settings.FORUM_PAGINATE_NEWS_BY
             self.assertContains(resp, "<a title=\"Information about",
 
         request = factory.get(path, {'format':'atom'})
         with self.assertNumQueries(2):
-            response = forums.news(request)
+            response = forums_views.news(request)
             resp.render()
             self.assertTrue(response['Content-Type'].startswith('application/atom+xml'))

File cciw/cciwmain/urls.py

 from django.conf.urls.defaults import patterns, url
-import cciw.cciwmain.common as cciw_common
-from cciw.cciwmain.common import DefaultMetaData
-from cciw.cciwmain.models import Site
-from cciw.forums.models import Award
 from django.conf import settings
-from django.views.generic.list import ListView
-from django.views.generic.detail import DetailView
-
-class AwardList(DefaultMetaData, ListView):
-    metadata_title = "Website Awards"
-    template_name = "cciw/awards/index.html"
-    queryset = Award.objects.order_by('-year', '-value')
-
-class SiteList(DefaultMetaData, ListView):
-    metadata_title = "Camp sites"
-    template_name='cciw/sites/index.html'
-    queryset = Site.objects.all()
-
-class SiteDetail(DefaultMetaData, DetailView):
-    queryset = Site.objects.all()
-    slug_field = 'slug_name'
-    template_name = 'cciw/sites/detail.html'
-
 
 # Forums and news items are tightly integrated (read: tangled) into the main
-# site, and always have been, so URLs and view code for forums are part of the
-# 'cciwmain' app rather than the 'forums' app. Some view code could be easily
-# moved into forums, some not so easily.
+# site, and always have been, so URLs and some view code for forums are part of
+# the 'cciwmain' app rather than the 'forums' app.
 
 urlpatterns = \
-patterns('',
-         url(r'^awards/$', AwardList.as_view(), name="cciwmain.awards.index"),
-         url(r'^sites/$', SiteList.as_view(), name="cciwmain.sites.index"),
-         url(r'^sites/(?P<slug>.*)/$', SiteDetail.as_view(), name="cciwmain.sites.detail"),
-) + \
 patterns('cciw.cciwmain.views',
-    # Members
-    (r'^login/$', 'members.login'),
-    url(r'^members/$', 'members.index', name="cciwmain.members.index"),
-    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/$', 'members.detail', name="cciwmain.members.detail"),
-    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/posts/$', 'members.posts', name="cciwmain.members.posts"),
-    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/messages/$', 'members.send_message', name="cciwmain.members.send_message"),
-    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/messages/inbox/$', 'members.inbox', name="cciwmain.members.inbox"),
-    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/messages/archived/$', 'members.archived_messages', name="cciwmain.members.archived_messages"),
-    url(r'^signup/$', 'memberadmin.signup', name="cciwmain.memberadmin.signup"),
-    (r'^memberadmin/change-password/$', 'memberadmin.change_password'),
-    (r'^memberadmin/change-email/$', 'memberadmin.change_email'),
-    url(r'^memberadmin/preferences/$', 'memberadmin.preferences', name="cciwmain.memberadmin.preferences"),
-    url(r'^help/logging-in/$', 'memberadmin.help_logging_in', name="cciwmain.memberadmin.help_logging_in"),
-
     # Camps
     (r'^thisyear/$', 'camps.thisyear'),
     (r'^thisyear/bookingform/$', 'misc.bookingform'),
     (r'^camps/(?P<year>.*)/(?P<galleryname>.*)/photos/$', 'camps.oldcampgallery'),
     (r'^camps/(?P<year>.*)/(?P<galleryname>.*)/photos/(?P<photonumber>\d+)/$', 'camps.oldcampphoto'),
 
+    # Sites
+    url(r'^sites/$', 'sites.index', name="cciwmain.sites.index"),
+    url(r'^sites/(?P<slug>.*)/$', 'sites.detail', name="cciwmain.sites.detail"),
+
+    # Services
+    (r'^services/esv_passage/$', 'services.esv_passage'),
+
+    # Feedback form
+    url(r'^contact/$', 'misc.feedback', name="cciwmain.misc.feedback"),
+    url(r'^contact/done/$', 'misc.feedback_done', name="cciwmain.misc.feedback_done"),
+
+) + patterns('cciw.forums.views',
+
+    # Members
+    (r'^login/$', 'members.login'),
+    url(r'^members/$', 'members.index', name="cciwmain.members.index"),
+    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/$', 'members.detail', name="cciwmain.members.detail"),
+    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/posts/$', 'members.posts', name="cciwmain.members.posts"),
+    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/messages/$', 'members.send_message', name="cciwmain.members.send_message"),
+    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/messages/inbox/$', 'members.inbox', name="cciwmain.members.inbox"),
+    url(r'^members/(?P<user_name>[A-Za-z0-9_]+)/messages/archived/$', 'members.archived_messages', name="cciwmain.members.archived_messages"),
+    url(r'^signup/$', 'memberadmin.signup', name="cciwmain.memberadmin.signup"),
+    (r'^memberadmin/change-password/$', 'memberadmin.change_password'),
+    (r'^memberadmin/change-email/$', 'memberadmin.change_email'),
+    url(r'^memberadmin/preferences/$', 'memberadmin.preferences', name="cciwmain.memberadmin.preferences"),
+    url(r'^help/logging-in/$', 'memberadmin.help_logging_in', name="cciwmain.memberadmin.help_logging_in"),
+
     # News
     url(r'^news/$', 'forums.news', name= 'cciwmain.site-news-index'),
     (r'^news/(?P<topicid>\d+)/$', 'forums.topic', {'title_start': 'News'},
     (r'^website/forum/(?P<topicid>\d+)/$', 'forums.topic', {'title_start': 'Website forum',
         'breadcrumb_extra': ['<a href="/website/">About website</a>']}),
 
+    # Awards
+    url(r'^awards/$', 'awards.index'),
+
     # Shortcuts
     (r'^posts/$', 'forums.all_posts'),
     (r'^posts/(?P<id>\d+)/$', 'forums.post'),
     (r'^topics/$', 'forums.all_topics'),
 
-    # Services
-    (r'^services/esv_passage/$', 'services.esv_passage'),
-
-    # Feedback form
-    url(r'^contact/$', 'misc.feedback', name="cciwmain.misc.feedback"),
-    url(r'^contact/done/$', 'misc.feedback_done', name="cciwmain.misc.feedback_done"),
+) + patterns('cciw.sitecontent.views',
 
     # Fallback -- allows any other URL to be defined as arbitary pages.
     # htmlchunk.find will throw a 404 for any URL not defined.
-    (r'^(?:.*)/$|^$', 'htmlchunk.find'),
+    (r'^(?:.*)/$|^$', 'find'),
 )
 

File cciw/cciwmain/views/camps.py

 from django.http import HttpResponse, Http404, HttpResponseRedirect
 from django.conf import settings
 
-from cciw.cciwmain.models import Camp, HtmlChunk
+from cciw.cciwmain.models import Camp
 from cciw.forums.models import Forum, Gallery, Photo
+from cciw.forums.views import forums as forums_views
 from cciw.cciwmain.common import create_breadcrumb, get_thisyear, standard_subs
 from cciw.cciwmain.decorators import member_required
 from cciw.cciwmain.templatetags import bbcode
+from cciw.sitecontent.models import HtmlChunk
 import cciw.cciwmain.utils as utils
-import cciw.cciwmain.views.forums as forums_views
 
 
 def index(request, year=None):

File cciw/cciwmain/views/forums.py

-from datetime import datetime, date
-import string
-
-from django.views.generic.edit import ModelFormMixin
-from django.http import Http404, HttpResponseForbidden, HttpResponseRedirect
-from django.shortcuts import render
-from django.conf import settings
-from django import forms
-from django.forms import widgets
-from django.shortcuts import get_object_or_404
-from django.utils.safestring import mark_safe
-
-from cciw.forums.models import Forum, Topic, Photo, Post, Member, VoteInfo, NewsItem, Permission, Poll, PollOption
-from cciw.cciwmain.common import create_breadcrumb, get_order_option, object_list, DefaultMetaData, AjaxyFormView
-from cciw.middleware.threadlocals import get_current_member
-from cciw.cciwmain.decorators import login_redirect
-from django.utils.html import escape
-from cciw.cciwmain import utils
-from cciw.cciwmain.templatetags import bbcode
-from cciw.cciwmain.decorators import member_required, member_required_for_post
-from cciw.cciwmain import feeds
-from cciw.cciwmain import forms as cciwforms
-
-
-# Utility functions for breadcrumbs
-def topicindex_breadcrumb(forum):
-    return [u"Topics"]
-
-def photoindex_breadcrumb(gallery):
-    return [u"Photos"]
-
-def topic_breadcrumb(forum, topic):
-    return [u'<a href="%s">Topics</a>' % forum.get_absolute_url()]
-
-def photo_breadcrumb(gallery, photo):
-    prev_and_next = u''
-    try:
-        previous_photo = Photo.objects.filter(id__lt=photo.id, \
-            gallery__id__exact = photo.gallery_id).order_by('-id')[0]
-        prev_and_next += u'<a href="%s" title="Previous photo">&laquo;</a> ' % previous_photo.get_absolute_url()
-    except IndexError:
-        prev_and_next += u'&laquo; '
-
-    try:
-        next_photo = Photo.objects.filter(id__gt=photo.id, \
-            gallery__id__exact = photo.gallery_id).order_by('id')[0]
-        prev_and_next += u'<a href="%s" title="Next photo">&raquo;</a> ' % next_photo.get_absolute_url()
-    except IndexError:
-        prev_and_next += u'&raquo; '
-
-    return [u'<a href="%s">Photos</a>' % gallery.get_absolute_url(), unicode(photo.id), prev_and_next]
-
-# Called directly as a view for /news/ and /website/forum/, and used by other views
-def topicindex(request, title=None, extra_context=None, forum=None,
-    template_name='cciw/forums/topicindex.html', breadcrumb_extra=None,
-    paginate_by=settings.FORUM_PAGINATE_TOPICS_BY, default_order=('-last_post_at',)):
-    "Displays an index of topics in a forum"
-
-    ### FORUM ###
-    forum = _get_forum_or_404(request.path, '')
-
-    ### TOPICS ###
-    topics = forum.topics.all().select_related('forum', 'news_item', 'started_by', 'last_post_by', 'news_item__created_by')
-
-    ### FEED ###
-    resp = feeds.handle_feed_request(request, feeds.forum_topic_feed(forum), query_set=topics)
-    if resp: return resp
-
-    # Title could be either in extra_context or in title
-    if extra_context is None:
-        extra_context = {}
-    if title is not None:
-        extra_context['title'] = title
-
-    extra_context['forum'] = forum
-    extra_context['atom_feed_title'] = u"Atom feed for new topics on this board."
-
-    ### BREADCRUMB ###
-    if breadcrumb_extra is None:
-        breadcrumb_extra = []
-    extra_context['breadcrumb'] = create_breadcrumb(breadcrumb_extra + topicindex_breadcrumb(forum))
-
-    ### ORDERING ###
-    order_by = get_order_option(
-        {'aca': ('created_at', 'id'),
-        'dca': ('-created_at', '-id'),
-        'apc': ('post_count',),
-        'dpc': ('-post_count',),
-        'alp': ('last_post_at',),
-        'dlp': ('-last_post_at',),
-        },
-        request, default_order)
-
-    extra_context['default_order'] = 'dlp' # corresponds = '-last_post_at'
-    topics = topics.order_by(*order_by)
-
-    ### PERMISSIONS ###
-    if request.user.has_perm('cciwmain.edit_topic'):
-        extra_context['moderator'] = True
-
-    return object_list(request, topics, extra_context=extra_context,
-                       template_name=template_name, paginate_by=paginate_by)
-
-def _get_forum_or_404(path, suffix):
-    """Returns a forum from the supplied path (minus the suffix)
-    or throws a 404 if it can't be found."""
-    if suffix:
-        location = path[1:-len(suffix)] # strip 'add/' or 'add_news/' bit
-    else:
-        location = path[1:]
-    try:
-        return Forum.objects.get(location=location)
-    except Forum.DoesNotExist:
-        raise Http404
-
-
-# Called directly as a view for /website/forum/, and used by other views
-@member_required
-def add_topic(request, breadcrumb_extra=None):
-    """
-    Displays a page for adding a topic to a forum
-    """
-
-    forum = _get_forum_or_404(request.path, 'add/')
-
-    cur_member = get_current_member()
-    context = dict(title='Add topic')
-
-    if not forum.open:
-        context['message'] = u'This forum is closed - new topics cannot be added.'
-    else:
-        context['forum'] = forum
-        context['show_form'] = True
-
-    errors = []
-    # PROCESS POST
-    if forum.open and request.POST.has_key('post') or request.POST.has_key('preview'):
-        subject = request.POST.get('subject', '').strip()
-        msg_text = request.POST.get('message', '').strip()
-
-        if subject == '':
-            errors.append(u'You must enter a subject')
-
-        if msg_text == '':
-            errors.append(u'You must enter a message.')
-
-        context['message_text'] = bbcode.correct(msg_text)
-        context['subject_text'] = subject
-        if not errors:
-            if request.POST.has_key('post'):
-                topic = Topic.create_topic(cur_member, subject, forum)
-                post = Post.create_post(cur_member, msg_text, topic, None)
-                return HttpResponseRedirect('../%s/' % topic.id)
-            else:
-                context['preview'] = mark_safe(bbcode.bb2xhtml(msg_text))
-
-    context['errors'] = errors
-    if breadcrumb_extra is None:
-        breadcrumb_extra = []
-    context['breadcrumb'] = create_breadcrumb(breadcrumb_extra + topic_breadcrumb(forum, None))
-    return render(request, 'cciw/forums/add_topic.html', context)
-
-# Called directly as a view for /website/forum/, and used by other views
-@member_required
-def add_news(request, breadcrumb_extra=None):
-    """
-    Displays a page for adding a short news item to a forum.
-    """
-
-    forum = _get_forum_or_404(request.path, 'add_news/')
-
-    cur_member = get_current_member()
-    if not cur_member.has_perm(Permission.NEWS_CREATOR):
-        return HttpResponseForbidden("Permission denied")
-
-    context = dict(title='Add short news item')
-
-    if not forum.open:
-        context['message'] = 'This forum is closed - new news items cannot be added.'
-    else:
-        context['forum'] = forum
-        context['show_form'] = True
-
-    errors = []
-    # PROCESS POST
-    if forum.open and request.POST.has_key('post') or request.POST.has_key('preview'):
-        subject = request.POST.get('subject', '').strip()
-        msg_text = request.POST.get('message', '').strip()
-
-        if subject == u'':
-            errors.append(u'You must enter a subject.')
-
-        if msg_text == u'':
-            errors.append(u'You must enter the short news item.')
-
-        context['message_text'] = bbcode.correct(msg_text)
-        context['subject_text'] = subject
-        if not errors:
-            if request.POST.has_key('post'):
-                newsitem = NewsItem.create_item(cur_member, subject, msg_text)
-                topic = Topic.create_topic(cur_member, subject, forum, commit=False)
-                topic.news_item_id = newsitem.id
-                topic.save()
-                return HttpResponseRedirect('../%s/' % topic.id)
-            else:
-                context['preview'] = mark_safe(bbcode.bb2xhtml(msg_text))
-
-    context['errors'] = errors
-    if breadcrumb_extra is None:
-        breadcrumb_extra = []
-    context['breadcrumb'] = create_breadcrumb(breadcrumb_extra + topic_breadcrumb(forum, None))
-    return render(request, 'cciw/forums/add_news.html', context)
-
-def update_poll_options(poll, new_option_list):
-    """Takes a Poll object and a list of strings,
-    and updates the PollOptions related to the Poll."""
-    existing_options = list(poll.poll_options.order_by('listorder'))
-
-    # This assumes order has not been messed with (as the user was instructed)
-
-    if len(new_option_list) == len(existing_options):
-        # indicates changes to the text only
-        for existing_opt, new_text in zip(existing_options, new_option_list):
-            existing_opt.text = new_text
-            existing_opt.save()
-    elif len(new_option_list) > len(existing_options):
-        # Addition
-        new_list = []
-        ex_option, new_option_t = None, None
-
-        while len(new_option_list) > 0:
-            if len(existing_options) > 0:
-                ex_option = existing_options[-1]
-            else:
-                ex_option = None
-
-            new_option_t = new_option_list[-1]
-
-            if ex_option is not None and ex_option.text == new_option_t:
-                # Same as before
-                new_list.insert(0, existing_options.pop())
-                # throw away what we've dealt with
-                new_option_list.pop()
-            else: # ex_option is None or the text values are different
-                # This is an addition
-                text = new_option_list.pop()
-                new_option = PollOption(text=text, poll=poll, total=0)
-                new_list.insert(0, new_option)
-
-        for i, po in enumerate(new_list):
-            po.listorder = i
-            po.save()
-
-    elif len(existing_options) > len(new_option_list):
-         # Removal
-        new_list = []
-        ex_option, new_option_t = None, None
-
-        while len(existing_options) > 0:
-            ex_option = existing_options[-1]
-
-            if len(new_option_list) > 0:
-                new_option_t = new_option_list[-1]
-            else:
-                new_option_t = None
-
-            if new_option_t is not None and ex_option.text == new_option_t:
-                # Same as before
-                new_list.insert(0, existing_options.pop())
-                # throw away what we've dealt with
-                new_option_list.pop()
-            else: # new_option_t is None or the text values are different
-                # This is a removal
-                old_option = existing_options.pop()
-                old_option.delete()
-
-        for i, po in enumerate(new_list):
-            po.listorder = i
-            po.save()
-
-class PollOptionListField(forms.CharField):
-    widget = widgets.Textarea
-    def clean(self, value):
-        """Parses a string containing multiple lines of text,
-        and returns a list of poll options or raises ValidationError"""
-        value = super(PollOptionListField, self).clean(value)
-        l = filter(lambda opt: len(opt) > 0, map(string.strip, value.split("\n")))
-
-        if len(l) == 0:
-            raise forms.ValidationError(u"At least one option must be entered")
-
-        max_length = PollOption._meta.get_field('text').max_length
-        if len(filter(lambda opt: len(opt) > max_length, l)) > 0:
-            raise forms.ValidationError(u"Options may not be more than %s chars long" % max_length)
-
-        return l
-
-class CreatePollForm(cciwforms.CciwFormMixin, forms.ModelForm):
-    voting_starts = forms.SplitDateTimeField(widget=widgets.SplitDateTimeWidget,
-                                             label="Voting starts")
-    voting_ends = forms.SplitDateTimeField(widget=widgets.SplitDateTimeWidget,
-                                           label="Voting ends")
-    polloptions = PollOptionListField(label='Options (one per line)')
-
-    class Meta:
-        model = Poll
-        fields = ['title', 'intro_text', 'outro_text', 'voting_starts', 'voting_ends', 'rules', 'rule_parameter']
-
-    def __init__(self, *args, **kwargs):
-        instance = kwargs.get('instance', None)
-        initial = kwargs.pop('initial', {})
-        if instance is not None:
-            initial['polloptions'] = '\n'.join(po.text for po in instance.poll_options.order_by('listorder'))
-        kwargs['initial'] = initial
-        super(CreatePollForm, self).__init__(*args, **kwargs)
-
-# Overriding CreatePollForm to get the fields in the right order
-# is currently rather tricky.  Easiest hack is this:
-CreatePollForm.base_fields.keyOrder = ['title', 'intro_text', 'polloptions', 'outro_text', 'voting_starts', 'voting_ends', 'rules', 'rule_parameter']
-
-
-class EditPoll(DefaultMetaData, AjaxyFormView, ModelFormMixin):
-    form_class = CreatePollForm
-    template_name = 'cciw/forums/edit_poll.html'
-
-    def dispatch(self, request, *args, **kwargs):
-        poll_id = kwargs.get('poll_id', None)
-        if poll_id is None:
-            self.suffix = 'add_poll/'
-            self.metadata_title = u"Create poll"
-            self.object = None
-        else:
-            self.suffix = '/'.join(request.path.split('/')[-3:]) # 'edit_poll/xx/'
-            self.metadata_title = u"Edit poll"
-
-            self.object = get_object_or_404(Poll.objects.filter(id=poll_id))
-
-        current_member = get_current_member()
-        if not current_member.has_perm(Permission.POLL_CREATOR):
-            return HttpResponseForbidden("Permission denied")
-        if self.object and self.object.created_by != current_member:
-            return HttpResponseForbidden("Access denied.")
-
-        self.current_member = current_member
-        self.forum = _get_forum_or_404(request.path, self.suffix)
-        self.context['breadcrumb'] = create_breadcrumb(kwargs.get('breadcrumb_extra',[]) +
-                                                       topic_breadcrumb(self.forum, None))
-        return super(EditPoll, self).dispatch(request, *args, **kwargs)
-
-    def get_initial(self):
-        today = datetime.today()
-        today = datetime(today.year, today.month, today.day)
-        return dict(voting_starts=today)
-
-    def form_valid(self, form):
-        new_poll = form.save(commit=False)
-        new_poll.created_by = self.current_member
-        new_poll.save()
-
-        if self.object is None:
-            # new poll, create a topic to go with it
-            topic = Topic.create_topic(self.current_member, new_poll.title, self.forum,
-                                       commit=False)
-            topic.poll_id = new_poll.id
-            topic.save()
-        else:
-            # It will already have a topic associated
-            topic = new_poll.topics.all()[0]
-            topic.subject = new_poll.title
-            topic.save()
-
-        update_poll_options(new_poll, form.cleaned_data['polloptions'])
-        # avoid ModelFormMixin.form_valid()
-        return HttpResponseRedirect(topic.get_absolute_url())
-
-edit_poll = member_required(EditPoll.as_view())
-
-
-# Used as part of a view function
-def process_post(request, topic, photo, context):
-    """Processes a posted message for a photo or a topic.
-    One of 'photo' or 'topic' should be set.
-    context is the context dictionary of the page, to which
-    'errors' or 'message' might be added."""
-
-    cur_member = get_current_member()
-    if cur_member is None:
-        # silently failing is OK, should never get here
-        return
-
-    if not request.POST.has_key('post') and \
-       not request.POST.has_key('preview'):
-        return # they didn't try to post
-
-    errors = []
-    if (topic and not topic.open) or \
-        (photo and not photo.open):
-        # Only get here if the topic was closed
-        # while they were adding a message
-        errors.append('This thread is closed, sorry.')
-        # For this error, there is nothing more to say so return immediately
-        context['errors'] = errors
-        return None
-
-    msg_text = request.POST.get('message', '').strip()
-    if msg_text == '':
-        errors.append('You must enter a message.')
-
-    context['errors'] = errors
-
-    # Preview
-    if request.POST.has_key('preview'):
-        context['message_text'] = bbcode.correct(msg_text)
-        if not errors:
-            context['preview'] = mark_safe(bbcode.bb2xhtml(msg_text))
-
-    # Post
-    if not errors and request.POST.has_key('post'):
-        post = Post.create_post(cur_member, msg_text, topic, photo)
-        return HttpResponseRedirect(post.get_forum_url())
-
-def process_vote(request, topic, context):
-    """Processes any votes posted on the topic.
-    topic is the topic that might have a poll.
-    context is the context dictionary of the page, to which
-    voting_errors or voting_message might be added."""
-
-    if topic.poll_id is None:
-        # No poll
-        return
-
-    poll = topic.poll
-
-    cur_member = get_current_member()
-    if cur_member is None:
-        # silently failing is OK, should never get here
-        return
-
-    try:
-        polloption_id = int(request.POST['polloption'])
-    except (ValueError, KeyError):
-        return # they didn't try to vote, or invalid input
-
-    errors = []
-    if not poll.can_anyone_vote():
-        # Only get here if the poll was closed
-        # while they were voting
-        errors.append(u'This poll is closed for voting, sorry.')
-        context['voting_errors'] = errors
-        return
-
-    if not poll.can_vote(cur_member):
-        errors.append(u'You cannot vote on this poll.  Please check the voting rules.')
-        context['voting_errors'] = errors
-
-    if not polloption_id in (po.id for po in poll.poll_options.all()):
-        errors.append(u'Invalid option chosen')
-        context['voting_errors'] = errors
-
-    if not errors:
-        voteinfo = VoteInfo(poll_option_id=polloption_id,
-                            member=cur_member,
-                            date=datetime.now())
-        voteinfo.save()
-        context['voting_message'] = u'Vote registered, thank you.'
-
-@member_required_for_post
-def topic(request, title_start=None, template_name='cciw/forums/topic.html', topicid=0,
-        introtext=None, breadcrumb_extra=None):
-    """Displays a topic"""
-    if title_start is None:
-        raise Exception("No title provided for page")
-
-    ### TOPIC AND POSTS ###
-    try:
-        topic = Topic.objects.get(id=int(topicid))
-    except Topic.DoesNotExist:
-        raise Http404
-
-    posts = topic.posts.all().select_related('posted_by')
-
-    ### Feed: ###
-    # Requires 'topic' and 'posts'
-    resp = feeds.handle_feed_request(request, feeds.topic_post_feed(topic), query_set=posts)
-    if resp: return resp
-
-    ### GENERAL CONTEXT ###
-    cur_member = get_current_member()
-
-    # Add additional title
-    title = topic.subject[0:40]
-    if len(title_start) > 0:
-        title = title_start + u": " + title
-    extra_context = dict(title=title)
-
-    if breadcrumb_extra is None:
-        breadcrumb_extra = []
-    extra_context['breadcrumb'] = create_breadcrumb(breadcrumb_extra + topic_breadcrumb(topic.forum, topic))
-
-    if introtext:
-        extra_context['introtext'] = introtext
-
-    extra_context['atom_feed_title'] = u"Atom feed for posts in this topic."
-
-    ### PROCESSING ###
-    # Process any message that they added.
-    resp = process_post(request, topic, None, extra_context)
-    if resp is not None:
-        return resp
-    process_vote(request, topic, extra_context)
-
-    ### TOPIC ###
-    extra_context['topic'] = topic
-    if topic.open:
-        if get_current_member() is not None:
-            extra_context['show_message_form'] = True
-        else:
-            extra_context['login_link'] = login_redirect(request.get_full_path() + '#messageform')
-
-    ### NEWS ITEM ###
-    if not topic.news_item_id is None:
-        extra_context['news_item'] = topic.news_item
-
-    ### POLL ###
-    if topic.poll_id is not None:
-        poll = topic.poll
-        extra_context['poll'] = poll
-
-        if request.GET.get('showvotebox', None):
-            extra_context['show_vote_box'] = True
-        else:
-            extra_context['show_poll_results'] = True
-
-        extra_context['allow_voting_box'] = \
-            (cur_member is None and poll.can_anyone_vote()) or \
-            (cur_member is not None and poll.can_vote(cur_member))
-
-    ### PERMISSIONS ###
-    if request.user.has_perm('cciwmain.edit_post'):
-        extra_context['moderator'] = True
-
-    return object_list(request, posts,
-        extra_context=extra_context, template_name=template_name,
-        paginate_by=settings.FORUM_PAGINATE_POSTS_BY)
-
-def photoindex(request, gallery, extra_context, breadcrumb_extra):
-    "Displays an a gallery of photos"
-
-    ### PHOTOS ###
-    photos = gallery.photos.all().select_related('gallery', 'last_post_by')
-
-    ### FEED ###
-    resp = feeds.handle_feed_request(request,
-        feeds.gallery_photo_feed(u"CCIW - %s" % extra_context['title']), query_set=photos)
-    if resp is not None: return resp
-
-    extra_context['atom_feed_title'] = u"Atom feed for photos in this gallery."
-    extra_context['gallery'] = gallery
-    extra_context['breadcrumb'] =   create_breadcrumb(breadcrumb_extra + photoindex_breadcrumb(gallery))
-
-    order_by = get_order_option(
-        {'aca': ('created_at','id'),
-        'dca': ('-created_at','-id'),
-        'apc': ('post_count',),
-        'dpc': ('-post_count',),
-        'alp': ('last_post_at',),
-        'dlp': ('-last_post_at',)},
-        request, ('created_at', 'id'))
-    extra_context['default_order'] = 'aca'
-    photos = photos.order_by(*order_by)
-
-    return object_list(request, photos,
-        extra_context=extra_context, template_name='cciw/forums/photoindex.html',
-        paginate_by=settings.FORUM_PAGINATE_PHOTOS_BY)
-
-@member_required_for_post
-def photo(request, photo, extra_context, breadcrumb_extra):
-    "Displays a photo"
-
-    ## POSTS ###
-    posts = photo.posts.all().select_related('posted_by')
-
-    ### Feed: ###
-    resp = feeds.handle_feed_request(request, feeds.photo_post_feed(photo), query_set=posts)
-    if resp: return resp
-
-    extra_context['atom_feed_title'] = u"Atom feed for posts on this photo."
-
-    extra_context['breadcrumb'] = create_breadcrumb(breadcrumb_extra + photo_breadcrumb(photo.gallery, photo))
-    extra_context['photo'] = photo
-
-    if photo.open:
-        if get_current_member() is not None:
-            extra_context['show_message_form'] = True
-        else:
-            extra_context['login_link'] = login_redirect(request.get_full_path() + '#messageform')
-
-    ### PROCESSING ###
-    process_post(request, None, photo, extra_context)
-
-    ### PERMISSIONS ###
-    if request.user.has_perm('cciwmain.edit_post'):
-        extra_context['moderator'] = True
-
-    return object_list(request, posts,
-        extra_context=extra_context, template_name='cciw/forums/photo.html',
-        paginate_by=settings.FORUM_PAGINATE_POSTS_BY)
-
-def all_posts(request):
-    context = dict(title=u"Recent posts")
-    posts = Post.objects.exclude(posted_at__isnull=True).order_by('-posted_at').select_related('topic__forum', 'photo__gallery', 'posted_by')
-
-    resp = feeds.handle_feed_request(request, feeds.PostFeed, query_set=posts)
-    if resp: return resp
-
-    context['atom_feed_title'] = u"Atom feed for all posts on CCIW message boards."
-
-    return object_list(request, posts,
-        extra_context=context, template_name='cciw/forums/posts.html',
-        paginate_by=settings.FORUM_PAGINATE_POSTS_BY)
-
-def post(request, id):
-    try:
-        post = Post.objects.get(pk=id)
-    except Post.DoesNotExist:
-        raise Http404()
-    try:
-        url = post.get_forum_url()
-    except Topic.DoesNotExist:
-        return HttpResponseForbidden("<h1>Access denied</h1><p>Topic is hidden.</p>")
-    return HttpResponseRedirect(url)
-
-def all_topics(request):
-    context = dict(title=u"Recent new topics")
-    topics = Topic.objects.exclude(created_at__isnull=True).order_by('-created_at').select_related('forum', 'started_by')
-
-    resp = feeds.handle_feed_request(request, feeds.TopicFeed, query_set=topics)
-    if resp: return resp
-
-    context['atom_feed_title'] = u"Atom feed for all new topics."
-
-    return object_list(request, topics,
-        extra_context=context, template_name='cciw/forums/topics.html',
-        paginate_by=settings.FORUM_PAGINATE_TOPICS_BY)
-
-def news(request):
-    return topicindex(request,
-                      title='News',
-                      template_name='cciw/forums/newsindex.html',
-                      paginate_by=settings.FORUM_PAGINATE_NEWS_BY,
-                      default_order= ('-created_at',)
-                      )

File cciw/cciwmain/views/htmlchunk.py

-from django.shortcuts import render
-from django.http import Http404
-
-from cciw.cciwmain.common import *
-from cciw.cciwmain.models import HtmlChunk, MenuLink
-
-def find(request):
-    try:
-        link = MenuLink.objects.get(url=request.path)
-    except MenuLink.DoesNotExist:
-        raise Http404()
-
-    try:
-        chunk = link.htmlchunk_set.filter()[0]
-    except IndexError:
-        raise Http404()
-
-    c = dict(title=chunk.page_title)
-    c['contentBody'] = chunk.render(request)
-    return render(request, 'cciw/standard.html', c)

File cciw/cciwmain/views/memberadmin.py

-"""Administrative views for members (signup, password change etc)"""
-from django.shortcuts import render
-from django.core import mail
-from django.contrib import messages
-from django.contrib.sites.models import Site
-from django.conf import settings
-from django.core.urlresolvers import reverse
-from django.core.validators import email_re
-from django.forms import widgets
-from django.http import Http404, HttpResponseRedirect
-from django.utils.crypto import salted_hmac
-from django.views.generic.edit import ModelFormMixin
-from django import forms
-from cciw.cciwmain.common import DefaultMetaData, AjaxyFormView, member_username_re
-from cciw.forums.models import Member
-from cciw.middleware.threadlocals import set_member_session, get_current_member
-from cciw.cciwmain.decorators import member_required
-from cciw.cciwmain import common
-from cciw.cciwmain import imageutils
-from cciw.cciwmain.forms import CciwFormMixin
-import urllib
-import re
-import datetime
-import string
-import random
-import base64
-
-password_re = re.compile(r'^[A-Za-z0-9]{5,15}$')
-
-# The number of days a new password must be activated within
-# (this is to stop an old e-mail being used to reset a password,
-# in the scenario where an attacker has temporary access to
-# a user's e-mails).
-NEW_PASSWORD_EXPIRY = 5
-
-
-class ValidationError(Exception):
-    pass
-
-
-# Ideally would add synchronize lock here, but YAGNI with any imaginable amount of traffic
-def create_user(user_name, password1, password2):
-    if member_username_re.match(user_name) is None:
-        raise ValidationError("The user name is invalid, please check and try again")
-    elif Member.all_objects.filter(user_name__iexact=user_name).exists():
-        # Can't just try to create it and catch exceptions,
-        # since the ORM checks to see if the primary key is already used
-        # and does an 'update' instead of 'insert' in that case.  Also
-        # we want to catch usernames that vary only by case
-        raise ValidationError("The user name is already used.  Please choose another.")
-    elif password_re.match(password1) is None:
-        raise ValidationError("The password entered does not match the requirements, please try again.")
-    elif password2 !=  password1:
-        raise ValidationError("The passwords do not match")
-    else:
-        iconfilename = user_name + "." + settings.DEFAULT_MEMBER_ICON.split('.')[-1]
-        m = Member(user_name=user_name,
-                   last_seen=datetime.datetime.now(),
-                   date_joined=datetime.datetime.now(),
-                   password=Member.encrypt_password(password1),
-                   icon="%s/%s" % (settings.MEMBER_ICON_PATH, iconfilename))
-        m.save()
-
-        # Copy default member icon
-        import shutil
-        shutil.copy("%s/%s" % (settings.STATIC_ROOT, settings.DEFAULT_MEMBER_ICON),
-                    "%s/%s/%s" % (settings.MEDIA_ROOT, settings.MEMBER_ICON_PATH, iconfilename))
-        return m
-
-
-def email_hash(email):
-    """Gets a hash of an email address, to be used in the signup process"""
-    # Use every other character to make it shorter and friendlier
-    return salted_hmac("cciw.cciwmain.memberadmin.signupemail", email).hexdigest()[::2]
-
-
-def email_address_used(email):
-    return Member.all_objects.filter(email__iexact=email).count() != 0
-
-
-def random_password():
-    chars = list(string.lowercase)
-    random.shuffle(chars)
-    return ''.join(chars[0:8])
-
-
-def validate_email_and_hash(email, hash):
-    if email_address_used(email):
-        # in reality shouldn't get here, unless user has
-        # started two signup processes in parallel
-        return (False, """The e-mail address is already used.  You must start the
- sign-up procedure again with a different e-mail address.""")
-    elif email_hash(email) != hash:
-        return (False, """The e-mail address was not confirmed.  Please
- ensure you have copied the URL from the e-mail correctly.""")
-    else:
-        return (True, '')
-
-
-def email_and_username_hash(email, user_name):
-    """Gets a hash of an email address + user_name"""
-    # Use every other character to make it shorter and friendlier
-    return salted_hmac("cciw.cciwmain.memberadmin.changeemail", email + ":" + user_name).hexdigest()[::2]
-
-def validate_email_username_and_hash(email, user_name, hash):
-    if email_address_used(email):
-        return (False, """The e-mail address is already in use.""")
-    elif email_and_username_hash(email, user_name) != hash:
-        return (False, """The e-mail address was not confirmed.  Please
- ensure you have copied the URL from the e-mail correctly.""")
-    else:
-        return (True, '')
-
-
-def send_signup_mail(email):
-    mail.send_mail("CCIW - Sign-up instructions",
-"""Thank you for beginning the sign-up process on the CCIW website
-
-To confirm the e-mail address you used is genuine and continue the
-sign-up process, please click on the link below:
-
-https://%(domain)s/signup/?email=%(email)s&h=%(hash)s
-
-If clicking on the link does not do anything, please copy and paste
-the link into your web browser.
-
-----
-If you did not attempt to sign up on the CCIW web-site, you can just
-ignore this e-mail.
-
-""" % {'domain': common.get_current_domain(), 'email': urllib.quote(email), 'hash': email_hash(email)},
-"website@cciw.co.uk", [email])
-
-
-def send_username_reminder(member):
-    mail.send_mail("CCIW - user name reminder",
-"""You requested a user name reminder on the CCIW website.
-Your user name is: %(user_name)s
-
-You can log in at:
-https://%(domain)s/login/
-
-Thanks.
-""" % {'domain': common.get_current_domain(), 'user_name': member.user_name },
-    "website@cciw.co.uk", [member.email])
-
-
-def send_newpassword_email(member):
-    # Create a new password
-    password = random_password()
-    hash = create_new_password_hash(password, member.user_name)
-
-    mail.send_mail("CCIW - new password.",
-"""You have requested a new password for your login on the CCIW website.
-Your new password is:
-
-    %(password)s
-
-In order to activate this new password, please click on the link below:
-
-https://%(domain)s/memberadmin/change-password/?u=%(user_name)s&h=%(hash)s
-
-If clicking on the link does not do anything, please copy and paste the
-entire link into your web browser.
-
-After activating the password, it is suggested that you log in using the
-above password and then change your password to one more memorable.
-
-If you did not request a new password on the CCIW website, then do not click
-on the link:  this e-mail has been triggered by someone else entering your
-e-mail addess and asking for a new password.  The password will not actually
-be changed until you click the link, so you can safely ignore this e-mail.
-
-""" % {'domain': common.get_current_domain(), 'user_name': member.user_name,
-       'password': password, 'hash': hash},
-    "website@cciw.co.uk", [member.email])
-
-
-def create_new_password_hash(password, user_name):
-    # Create string used to verify user_name and date.
-    hash_str = u':'.join([datetime.date.today().isoformat(), user_name, password])
-    return base64.urlsafe_b64encode(hash_str.encode("utf-8"))
-
-
-def extract_new_password(hash, user_name):
-    """Extracts the new password from the hash, throwing a ValidationError
-    containing an error message if it fails."""
-    invalid_url_msg = "The URL hash was invalid -- please check that you " + \
-        "copied the entire URL from the e-mail"
-
-    try:
-        hash_str = base64.urlsafe_b64decode(hash.encode("ascii"))
-    except TypeError:
-        raise ValidationError(invalid_url_msg)
-    try:
-        date_str, h_user_name, password = hash_str.split(':')
-    except ValueError:
-        raise ValidationError(invalid_url_msg)
-
-    try:
-        year, month, day = map(int, date_str.split('-'))
-        email_date = datetime.date(year, month, day)
-    except ValueError: # catches unpacking, int, and datetime.date
-        raise ValidationError(invalid_url_msg)
-
-    if (datetime.date.today() - email_date).days > NEW_PASSWORD_EXPIRY:
-        raise ValidationError("The new password has expired.  Please request a new password again.")
-
-    if (h_user_name != user_name):
-        # hack attempt?
-        raise ValidationError("This URL has been tampered with.  Password not changed.")
-
-    return password
-
-
-def send_newemail_email(member, new_email):
-    mail.send_mail("CCIW - E-mail change",
-"""You have changed your e-mail address on the CCIW website.
-
-To confirm that your new e-mail address is genuine and update our records,
-please click on the link below:
-
-https://%(domain)s/memberadmin/change-email/?email=%(email)s&u=%(user_name)s&h=%(hash)s
-
-If clicking on the link does not do anything, please copy and paste
-the entire link into your web browser.
-
-""" % {'domain': common.get_current_domain(), 'email': urllib.quote(new_email),
-       'user_name': urllib.quote(member.user_name),
-       'hash': email_and_username_hash(new_email, member.user_name)},
-    "website@cciw.co.uk", [new_email])
-
-
-#################  VIEW FUNCTIONS #####################
-
-def signup(request):
-    c = dict(title="Sign up")
-
-    if not request.POST and not request.GET:
-        ######## 1. START #########
-        c['stage'] = "start"
-
-    if "agreeterms" in request.POST:
-        ######## 2. ENTER EMAIL #########
-        c['stage'] = "email"
-
-    elif "email" in request.POST or "submit_email" in request.POST:
-        ######## 3. CHECK ADDRESS AND SEND EMAIL #########
-        email = request.POST['email'].strip()
-        c['email'] = email
-        if email_re.search(email):
-            if email_address_used(email):
-                c['stage'] = "email"
-                c['alreadyused'] = True
-            else:
-                try:
-                    send_signup_mail(email)
-                except Exception, e:
-                    c['error_message'] = \
-                        """E-mail could not be sent for the following reason: %s
-                        If the error persists, please contact the webmaster.""" % str(e)
-                    c['stage'] = "email"
-                else:
-                    c['stage'] = "emailsubmitted"
-        else:
-            c['stage'] = "email"
-            c['error_message'] = "Please enter a valid e-mail address"
-
-    elif "email" in request.GET:
-        ######## 4. USERNAME AND PASSWORD #########
-        email = request.GET['email']
-        hash = request.GET.get('h', '')
-        valid, msg = validate_email_and_hash(email, hash)
-        if valid:
-            c['stage'] = "user_name"
-            c['confemail'] = email
-            c['confhash'] = hash
-        else:
-            c['stage'] = 'invalid'
-            c['error_message'] = msg
-
-    elif "user_name" in request.POST or "submit_user_name" in request.POST:
-        ######## 5. CREATE ACCOUNT #########
-        # First, re-check email and hash in case of
-        # tampering with hidden form values
-        email = request.POST.get('confemail', '')
-        hash = request.POST.get('confhash', '')
-        valid_email, msg = validate_email_and_hash(email, hash)
-        if valid_email:
-            # Check username and password
-            user_name = request.POST.get('user_name', '')
-            try:
-                m = create_user(user_name,
-                                request.POST.get('password1', ''),
-                                request.POST.get('password2', ''))
-            except ValidationError, e:
-                c['stage'] = "user_name"
-                c['confemail'] = email
-                c['confhash'] = hash
-                c['user_name'] = user_name
-                c['error_message'] = str(e)
-            else:
-                m.email = email
-                m.save()
-                set_member_session(request, m)
-                c['stage'] = 'end'
-        else:
-            c['stage'] = 'invalid'
-            c['error_message'] = msg
-
-    # RequestContext should be created at the end, so that the
-    # context_processors are executed after set_member_session
-    return render(request, 'cciw/members/signup.html', c)
-
-
-def help_logging_in(request):
-    """View that has reset password and username reminder functionality."""
-    c = dict(title="Logging in problems.")
-    if request.method == 'POST':
-        # Check e-mail
-        email = request.POST.get('email', '').strip()
-        c['email'] = email
-        cont = True
-        if not email_re.search(email):
-            c['error_message'] = "The e-mail address is not valid.  Please check and try again."
-            cont = False
-
-        # Check e-mail in db
-        if cont:
-            # Temporary - use [0] instead of .get() because of some bad data
-            try:
-                member = Member.objects.filter(email__iexact=email)[0]
-            except IndexError:
-                c['error_message'] = "A member with that e-mail address could not be found."
-                cont = False
-
-        if cont:
-            if request.POST.has_key('usernamereminder'):
-                send_username_reminder(member)
-                c['success_message'] = "An e-mail has been sent with a reminder of your user name."
-            elif request.POST.has_key('newpassword'):
-                send_newpassword_email(member)
-                c['success_message'] = "An e-mail has been sent to you with a new password."
-
-    return render(request, 'cciw/members/help_logging_in.html', c)
-
-
-def change_password(request):
-    """View that handles password changes, with a form and from
-    'new password' emails."""
-    user_name = request.GET.get('u', '')
-    hash = request.GET.get('h', '')
-
-    c = dict(title="Change password")
-    if user_name:
-        # New password from e-mail
-        try:
-            password = extract_new_password(hash, user_name)
-        except ValidationError, e:
-            c['error_message'] = e.args[0]
-        else:
-            try:
-                member = Member.objects.get(user_name=user_name)
-            except Member.DoesNotExist:
-                # unlikely!
-                raise Http404
-            member.password = Member.encrypt_password(password)
-            member.save()
-            c['success_message'] = "Password changed."
-    else:
-        # form for logged in member
-        current_member = get_current_member()
-        if current_member is None:
-            return HttpResponseRedirect("/login/?r=%s" % request.path)
-        c['show_form'] = True
-        if request.method == 'POST':
-            new_password = request.POST.get('new_password', '')
-            new_password2 = request.POST.get('new_password2', '')
-            error_message = ''
-            if not (20 >= len(new_password) >= 5):
-                error_message = "Your password must be between 5 and 20 characters."
-            elif new_password != new_password2:
-                error_message = "The two passwords do not match."
-            if not error_message:
-                current_member.password = Member.encrypt_password(new_password)
-                current_member.save()
-                c['success_message'] = "Password changed."
-            else:
-                c['error_message'] = error_message
-
-    return render(request, 'cciw/members/change_password.html', c)
-
-
-def change_email(request):
-    """View that responds to links in the 'change e-mail' emails."""
-    c = dict(title="Change email")
-
-    user_name = request.GET.get('u')
-    email = request.GET.get('email', '')
-    hash = request.GET.get('h', '')
-    valid, msg = validate_email_username_and_hash(email, user_name, hash)
-    if valid:
-        try:
-            member = Member.objects.filter(user_name=user_name)[0]
-        except IndexError:
-            c['error_message'] = "The user name is unknown"
-        else:
-            member.email = email
-            member.save()
-        c['success_message'] = "New email address confirmed, thank you."
-    else:
-        c['error_message'] = msg
-
-    return render(request, 'cciw/members/change_email.html', c)
-
-
-preferences_fields = ["real_name", "email", "show_email", "comments", "message_option", "icon"]
-class PreferencesForm(CciwFormMixin, forms.ModelForm):
-    real_name = forms.CharField(widget=forms.TextInput(attrs={'size':str(Member._meta.get_field('real_name').max_length)}),
-                                label="'Real' name", required=False,
-                                max_length=Member._meta.get_field('real_name').max_length)
-    email = forms.EmailField(widget=forms.TextInput(attrs={'size':'40'}))
-    message_option = forms.ChoiceField(choices=Member.MESSAGE_OPTIONS,
-                                       widget=forms.RadioSelect,
-                                       label="Message storing")
-    icon = forms.FileField(widget=widgets.FileInput,
-                           label="Icon", required=False)
-
-    class Meta:
-        model = Member
-        fields = preferences_fields
-
-PreferencesForm.base_fields.keyOrder = preferences_fields
-
-
-class Preferences(DefaultMetaData, AjaxyFormView, ModelFormMixin):
-    metadata_title = u"Preferences"
-    form_class = PreferencesForm
-    template_name = 'cciw/members/preferences.html'
-
-    def get_success_url(self):
-        return reverse("cciwmain.memberadmin.preferences")
-
-    def dispatch(self, request):
-        current_member = get_current_member()
-        self.orig_email = current_member.email # before update
-        self.object = current_member
-        self.context['member'] = current_member
-        return super(Preferences, self).dispatch(request)
-
-    def form_valid(self, form):
-        # E-mail changes require verification, so frig it here
-        current_member = form.save(commit=False)
-        new_email = current_member.email # from posted data
-
-        # Save with original email
-        current_member.email = self.orig_email
-        current_member.save()
-
-        if self.request.FILES:
-            try:
-                imageutils.fix_member_icon(current_member, self.request.FILES['icon'])
-            except imageutils.ValidationError, e:
-                self.context['image_error'] = e.args[0]
-                return self.form_invalid(form)
-
-        # E-mail change:
-        if new_email != self.orig_email:
-            # We check for duplicate e-mail address in change_email view,
-            # so don't really need to do it here.
-            send_newemail_email(current_member, new_email)
-            messages.info(self.request, "To confirm the change of e-mail address, an e-mail " + \
-               "has been sent to your new address with further instructions.")
-        else:
-            messages.info(self.request, "Changes saved.")
-
-        # NB - AjaxyFormView not Preferences, because we want to skip the
-        # behaviour of ModelFormMixin here.
-        return super(AjaxyFormView, self).form_valid(form)
-
-preferences = member_required(Preferences.as_view())
-

File cciw/cciwmain/views/members.py

-from django.conf import settings
-from django.contrib import messages
-from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden
-from django.views.generic.list import ListView
-from django.views.generic.base import TemplateView
-from django.utils.safestring import mark_safe
-
-from cciw.forums.models import Member, Message
-from cciw.cciwmain.common import get_order_option, create_breadcrumb, DefaultMetaData, FeedHandler, get_member_link
-from cciw.middleware.threadlocals import get_current_member, remove_member_session
-from cciw.cciwmain.decorators import member_required, member_required_for_post, _display_login_form
-import cciw.cciwmain.templatetags.bbcode as bbcode
-from cciw.cciwmain import feeds
-
-from datetime import datetime, timedelta
-import re
-import math
-
-
-class MemberList(DefaultMetaData, FeedHandler, ListView):
-    metadata_title = u"Members"
-    feed_class = feeds.MemberFeed
-    template_name = "cciw/members/index.html"
-    paginate_by = 50
-    extra_context = {
-        'default_order': 'aun',
-        'atom_feed_title': u"Atom feed for new members"
-        }
-
-    def get_queryset(self):
-        members = Member.objects.filter(dummy_member=False)
-        if self.is_feed_request():
-            return members
-
-        if self.request.GET.has_key('online'):
-            members = members.filter(last_seen__gte=(datetime.now() - timedelta(minutes=3)))
-        order_by = get_order_option(
-            {'adj': ('date_joined',),
-             'ddj': ('-date_joined',),
-             'aun': ('user_name',),
-             'dun': ('-user_name',),
-             'arn': ('real_name',),
-             'drn': ('-real_name',),
-             'als': ('last_seen',),
-             'dls': ('-last_seen',)},
-            self.request, ('user_name',))
-        members = members.order_by(*order_by)
-
-        search = self.request.GET.get('search', '')
-        if len(search) > 0:
-            members = (members.filter(user_name__icontains=search) | members.filter(real_name__icontains=search))
-
-        return members
-
-index = MemberList.as_view()
-
-
-class MemberDetail(DefaultMetaData, TemplateView):
-
-    template_name = 'cciw/members/detail.html'
-
-    def get(self, request, user_name):
-        try:
-            member = Member.objects.get(user_name=user_name)
-        except Member.DoesNotExist:
-            raise Http404
-        self.context['member'] = member
-        self.context['awards'] = member.personal_awards.all()
-        self.metadata_title = u"Member: %s" % member.user_name
-        return super(MemberDetail, self).get(request, user_name)
-
-    def post(self, request, user_name):
-        if request.POST.has_key('logout'):
-            remove_member_session(request)
-            return HttpResponseRedirect(request.path)
-        else:
-            return self.get(request, user_name)
-
-detail = MemberDetail.as_view()
-
-
-# The real work here is done in member_required_for_post,
-# and _display_login_form, after that it is just redirecting
-@member_required_for_post
-def login(request):
-    member = get_current_member()
-    if member is not None:
-        redirect = request.GET.get('redirect', None)
-        if not redirect:
-            redirect = member.get_absolute_url()
-        return HttpResponseRedirect(redirect)
-    else:
-        return _display_login_form(request, login_page=True)
-
-
-class SendMessage(DefaultMetaData, TemplateView):
-    """
-    View function that handles the 'send message' form
-    """
-    # Two modes:
-    #  - if user_name is current user, there is a field that
-    #    allows them to enter the recipient.
-    #  - otherwise, the page is a 'leave message for {{ user_name }}' page.
-    # It is easier to handle this without a Django 'Form'.
-
-    template_name = 'cciw/members/messages/send.html'
-
-    def dispatch(self, request, user_name=None):
-        # General setup
-        self.request = request
-        current_member = get_current_member()
-
-        try:
-            member = Member.objects.get(user_name=user_name)
-        except Member.DoesNotExist:
-            raise Http404
-
-        c = self.context
-
-        # Handle input:
-        errors = []
-        message_sent = False
-        preview = None
-        message_text = None
-
-        no_messages = False
-        to_name = u''
-
-        to = None
-        if current_member.user_name != member.user_name:
-            to = member
-            if to.message_option == Member.MESSAGES_NONE: