Implement a basic forum
This commit is contained in:
0
forums/__init__.py
Normal file
0
forums/__init__.py
Normal file
5
forums/admin.py
Normal file
5
forums/admin.py
Normal 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
12
forums/forms.py
Normal 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
93
forums/models.py
Normal 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
16
forums/tests.py
Normal 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
12
forums/urls.py
Normal 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
60
forums/views.py
Normal 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))
|
@@ -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>
|
||||
|
2
templates/forum_base.html
Normal file
2
templates/forum_base.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{% extends "base.html" %}
|
||||
{% block sectiontitle %}Forums{% endblock %}
|
1
templates/forums/_breadcrumb.html
Normal file
1
templates/forums/_breadcrumb.html
Normal file
@@ -0,0 +1 @@
|
||||
{%block breadcrumb %}
|
15
templates/forums/_forum_table.html
Normal file
15
templates/forums/_forum_table.html
Normal 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>
|
11
templates/forums/_post.html
Normal file
11
templates/forums/_post.html
Normal 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>
|
13
templates/forums/_post_children.html
Normal file
13
templates/forums/_post_children.html
Normal 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>
|
29
templates/forums/forum.html
Normal file
29
templates/forums/forum.html
Normal 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 %}
|
||||
> <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 %}
|
7
templates/forums/forum_base.html
Normal file
7
templates/forums/forum_base.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% block breadcrumb %}
|
||||
{% endblock breadcrumb %}
|
||||
|
||||
{% endblock content%}
|
7
templates/forums/index.html
Normal file
7
templates/forums/index.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% extends "forum_base.html" %}
|
||||
|
||||
{%block title %}Forums{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{%include "forums/_forum_table.html" with forums=forums%}
|
||||
{%endblock%}
|
17
templates/forums/newTopic.html
Normal file
17
templates/forums/newTopic.html
Normal 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 %}
|
||||
> <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 %}
|
8
templates/forums/reply.html
Normal file
8
templates/forums/reply.html
Normal 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%}
|
17
templates/forums/topic.html
Normal file
17
templates/forums/topic.html
Normal 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>
|
||||
|
||||
> <a href="{{topic.forum.get_absolute_url}}">{{topic.forum}}</a>
|
||||
{%for f in topic.forum.get_ancestors %}
|
||||
> <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%}
|
Reference in New Issue
Block a user