Implement a basic forum

This commit is contained in:
Trever Fischer
2012-02-18 15:31:13 -05:00
parent d5a4b83a74
commit db6d0c29d8
20 changed files with 334 additions and 0 deletions

0
forums/__init__.py Normal file
View File

5
forums/admin.py Normal file
View File

@@ -0,0 +1,5 @@
import models
from django.contrib import admin
admin.site.register(models.Forum)
admin.site.register(models.Topic)
admin.site.register(models.Post)

12
forums/forms.py Normal file
View File

@@ -0,0 +1,12 @@
from django import forms
import models
class ReplyForm(forms.ModelForm):
class Meta:
model = models.Post
exclude = ('user', 'parent')
class TopicForm(forms.ModelForm):
class Meta:
model = models.Topic
exclude = ('forum', 'rootPost')

93
forums/models.py Normal file
View File

@@ -0,0 +1,93 @@
from django.db import models
from django.contrib.auth.models import User
from mptt.models import MPTTModel, TreeForeignKey
def unique_slug(item,slug_source,slug_field):
"""Ensures a unique slug field by appending an integer counter to duplicate slugs.
The item's slug field is first prepopulated by slugify-ing the source field. If that value already exists, a counter is appended to the slug, and the counter incremented upward until the value is unique.
For instance, if you save an object titled Daily Roundup, and the slug daily-roundup is already taken, this function will try daily-roundup-2, daily-roundup-3, daily-roundup-4, etc, until a unique value is found.
Call from within a model's custom save() method like so:
unique_slug(item, slug_source='field1', slug_field='field2')
where the value of field slug_source will be used to prepopulate the value of slug_field.
"""
if not getattr(item, slug_field): # if it's already got a slug, do nothing.
from django.template.defaultfilters import slugify
slug = slugify(getattr(item,slug_source))
itemModel = item.__class__
# the following gets all existing slug values
allSlugs = [sl.values()[0] for sl in itemModel.objects.values(slug_field)]
if slug in allSlugs:
import re
counterFinder = re.compile(r'-\d+$')
counter = 2
slug = "%s-%i" % (slug, counter)
while slug in allSlugs:
slug = re.sub(counterFinder,"-%i" % counter, slug)
counter += 1
setattr(item,slug_field,slug)
class Forum(MPTTModel):
parent = TreeForeignKey('self', blank=True, null=True, related_name='children')
name = models.CharField(max_length=30)
slug = models.SlugField(blank=True)
def save(self, *args, **kwargs):
unique_slug(self, slug_source='name', slug_field='slug')
super(Forum, self).save(*args, **kwargs)
def topicCount(self):
return self.topic_set.count()
def latestTopic(self):
return self.topic_set.extra(order_by = ['created'])
@models.permalink
def get_absolute_url(self):
return ('forums.views.forum', [self.slug])
def __unicode__(self):
return self.name
class Topic(models.Model):
forum = models.ForeignKey(Forum)
title = models.CharField(max_length=100)
rootPost = models.OneToOneField('Post')
created = models.DateTimeField(editable=False, auto_now_add=True)
updated = models.DateTimeField(editable=False, auto_now=True)
slug = models.SlugField(editable=False, blank=True)
def save(self, *args, **kwargs):
unique_slug(self, slug_source='title', slug_field='slug')
super(Topic, self).save(*args, **kwargs)
def lastPost(self):
return self.rootPost.get_descendants(True).extra(order_by = ['updated'])
@models.permalink
def get_absolute_url(self):
return ('forums.views.topic', [], {"forumSlug":self.forum.slug, "topicSlug":self.slug, "topicID":self.id})
def __unicode__(self):
return self.title
class Post(MPTTModel):
parent = TreeForeignKey('self', blank=True, null=True, related_name='children')
user = models.ForeignKey(User)
body = models.TextField()
created = models.DateTimeField(editable=False, auto_now_add=True)
updated = models.DateTimeField(editable=False, auto_now=True)
def forum(self):
if parent is None:
return Forum.objects.get(rootTopic=self.id)
return parent.forum()
@models.permalink
def get_absolute_url(self):
return ('forums.views.post', [self.id])
def __unicode__(self):
return self.body

16
forums/tests.py Normal file
View File

@@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

12
forums/urls.py Normal file
View File

@@ -0,0 +1,12 @@
from django.conf.urls.defaults import patterns, include, url
urlpatterns = patterns('forums',
url(r'^$', 'views.index'),
url(r'^forum/(?P<forum>.*)/$', 'views.forum'),
url(r'^forum/(?P<forumID>.*)/new$', 'views.newTopic'),
url(r'^forum/(?P<forumSlug>.*)/(?P<topicID>.*)/(?P<topicSlug>.*)$', 'views.topic'),
url(r'^topic/(?P<topicID>.*)$', 'views.topic'),
url(r'^reply/(?P<topicID>.*)', 'views.reply'),
url(r'^reply$', 'views.reply'),
url(r'^post/(?P<id>.*)$', 'views.post'),
)

60
forums/views.py Normal file
View File

@@ -0,0 +1,60 @@
import models
import forms
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django.template import RequestContext
from django.core.urlresolvers import reverse
def index(request):
forums = models.Forum.objects.filter(parent=None)
return render_to_response('forums/index.html', {"forums":forums}, context_instance = RequestContext(request))
def forum(request, forum):
forum = models.Forum.objects.get(slug=forum)
topics = forum.topic_set.all()
return render_to_response('forums/forum.html', {"forum":forum, "topics":topics}, context_instance = RequestContext(request))
def topic(request, topicID, forumSlug=None, topicSlug=None):
topic = models.Topic.objects.get(id=topicID)
return render_to_response('forums/topic.html', {"topic":topic}, context_instance = RequestContext(request))
def post(request, id):
post = models.Post.objects.get(id=id)
rootPost = post.get_root()
return HttpResponseRedirect(reverse('forums.views.topic', kwargs={"topicID":rootPost.topic.id})+"#reply-"+id)
def reply(request, topicID=None):
parentPost = models.Post.objects.get(id__exact=topicID)
if request.method == 'POST':
form = forms.ReplyForm(request.POST)
else:
form = forms.ReplyForm()
if form.is_valid():
reply = models.Post()
reply.parent = parentPost
reply.body = form.cleaned_data['body']
reply.user = request.user
reply.save()
return HttpResponseRedirect(reverse('forums.views.post', kwargs={"id":reply.id}))
return render_to_response('forums/reply.html', {"post":parentPost, "form":form}, context_instance = RequestContext(request))
def newTopic(request, forumID=None):
parentForum = models.Forum.objects.get(id__exact=forumID)
if request.method == 'POST':
replyForm = forms.ReplyForm(request.POST, prefix='reply')
topicForm = forms.TopicForm(request.POST, prefix='topic')
else:
replyForm = forms.ReplyForm(prefix='reply')
topicForm = forms.TopicForm(prefix='topic')
if replyForm.is_valid() and topicForm.is_valid():
topic = models.Topic()
topic.title = topicForm.cleaned_data['title']
topic.forum = parentForum
reply = models.Post()
reply.body = replyForm.cleaned_data['body']
reply.user = request.user
reply.save()
topic.rootPost = reply
topic.save()
return HttpResponseRedirect(reverse('forums.views.post', kwargs={'id': reply.id}))
return render_to_response('forums/newTopic.html', {"forum":parentForum, "replyForm":replyForm, "topicForm": topicForm}, context_instance = RequestContext(request))

View File

@@ -32,7 +32,15 @@
</div>
</div>
<div class="clear"></div>
<div id="nav" class="grid_12">
<ul>
<li><a href="/">Home</a></li>
<li><a href="{%url forums.views.index%}">Forums</a></li>
</ul>
</div>
<div id="content" class="grid_12">
<h1>{%block sectiontitle %}Caminus{%endblock%}</h1>
{% block breadcrumb %}{% endblock %}
{% block content %}
{% endblock %}
</div>

View File

@@ -0,0 +1,2 @@
{% extends "base.html" %}
{% block sectiontitle %}Forums{% endblock %}

View File

@@ -0,0 +1 @@
{%block breadcrumb %}

View File

@@ -0,0 +1,15 @@
{% load mptt_tags %}
<table>
<tr class="header">
<th>Name</th>
<th>Topics</th>
<th>Last Post</th>
</tr>
{% for f in forums %}
<tr class="{% cycle 'even' 'odd' %}">
<td><a href="{{ f.get_absolute_url }}">{{ f.name }}</a></td>
<td>{{ f.topicCount }}</td>
<td>Today</td>
</tr>
{% endfor %}
</table>

View File

@@ -0,0 +1,11 @@
{% load mptt_tags %}
<div class="forum-post">
{% include "forums/_post_children.html" with post=post %}
{% recursetree post.children %}
<div class="forum-post">
{% include "forums/_post_children.html" with post=node %}
{{children}}
</div>
{%endrecursetree%}
</div>

View File

@@ -0,0 +1,13 @@
{% load markup %}
{% load avatar_tags %}
<a name="reply-{{post.id}}"></a>
<div class="forum-post-user">
{%avatar post.user%}
{{post.user}}
</div>
<div class="forum-post-content">
{{post.body|markdown:"safe"}}
<br style="clear:both"/>
</div>
<a href="{%url forums.views.reply post.id%}">Reply</a>

View File

@@ -0,0 +1,29 @@
{% extends "forum_base.html" %}
{% block breadcrumb %}
<a href="{% url forums.views.index%}">Forums</a>
{%for f in forum.get_ancestors %}
&gt; <a href="{{f.get_relative_url}}">{{f}}</a>
{%endfor%}
{% endblock %}
{% block content %}
<h2>{{ forum.name }}</h2>
{%include "forums/_forum_table.html" with forums=forums.children%}
<table>
<tr class="header">
<th>Title</th>
<th>Posted</th>
<th>Latest Activity</th>
</tr>
{% for topic in topics %}
<tr class="{%cycle 'even' 'odd' %}">
<td><a href="{{topic.get_absolute_url}}">{{ topic.title }}</a></td>
<td>{{ topic.created }} </td>
<td>{{topic.lastPost.created}} by {{ topic.lastPost.user }} </td>
</tr>
{% endfor %}
</table>
<a href="{%url forums.views.newTopic forumID=forum.id %}">New Topic</a>
{% endblock %}

View File

@@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block content %}
{% block breadcrumb %}
{% endblock breadcrumb %}
{% endblock content%}

View File

@@ -0,0 +1,7 @@
{% extends "forum_base.html" %}
{%block title %}Forums{% endblock %}
{% block content %}
{%include "forums/_forum_table.html" with forums=forums%}
{%endblock%}

View File

@@ -0,0 +1,17 @@
{% extends "forum_base.html" %}
{% block breadcrumb %}
<a href="{% url forums.views.index%}">Forums</a>
{%for f in forum.get_ancestors %}
&gt; <a href="{{f.get_relative_url}}">{{f}}</a>
{%endfor%}
{% endblock %}
{% block content %}
<form method="POST" action="{%url forums.views.newTopic forumID=forum.id %}">
{% csrf_token %}
{{topicForm.as_p}}
{{replyForm.as_p}}
<input type="submit" value="Submit"/>
</form>
{% endblock %}

View File

@@ -0,0 +1,8 @@
{% extends "forum_base.html" %}
{%block content%}
<form method="POST" action="{%url forums.views.reply post.id %}">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Submit"/>
</form>
{%endblock%}

View File

@@ -0,0 +1,17 @@
{% extends "forum_base.html" %}
{% load mptt_tags %}
{%block title %}{{topic.title}}{%endblock%}
{%block breadcrumb %}
<a href="{% url forums.views.index%}">Forums</a>
&gt; <a href="{{topic.forum.get_absolute_url}}">{{topic.forum}}</a>
{%for f in topic.forum.get_ancestors %}
&gt; <a href="{{f.get_relative_url}}">{{f}}</a>
{%endfor%}
{%endblock%}
{%block content %}
<h2>{{topic.title}}</h2>
{% include "forums/_post.html" with post=topic.rootPost %}
{%endblock%}

View File

@@ -18,6 +18,7 @@ urlpatterns = patterns('',
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
url(r'^forums/', include('forums.urls'))
)
urlpatterns += staticfiles_urlpatterns()