Browse Source

- Beginning of threads functionality
- Threads permissions and ACL builder
- Redirects work

Ralfp 12 years ago
parent
commit
402d57b2f5

+ 3 - 3
misago/forums/acl.py

@@ -10,8 +10,8 @@ def make_forum_form(request, role, form):
     form.layout.append((
     form.layout.append((
                         _("Forums Permissions"),
                         _("Forums Permissions"),
                         (
                         (
-                         ('can_see_forum', {'label': _("Can see this forum")}),
-                         ('can_see_forum_contents', {'label': _("Can see this forum's contents")}),
+                         ('can_see_forum', {'label': _("Can see forum")}),
+                         ('can_see_forum_contents', {'label': _("Can see forum contents")}),
                         ),
                         ),
                        ))
                        ))
     
     
@@ -74,4 +74,4 @@ def cleanup(acl, perms, forums):
                 try:
                 try:
                     del acl.forums.acl['can_browse'][acl.forums.acl['can_browse'].index(forum.pk)]
                     del acl.forums.acl['can_browse'][acl.forums.acl['can_browse'].index(forum.pk)]
                 except ValueError:
                 except ValueError:
-                    pass            
+                    pass

+ 0 - 0
misago/posts/__init__.py


+ 0 - 10
misago/posts/fixtures.py

@@ -1,10 +0,0 @@
-from misago.monitor.fixtures import load_monitor_fixture
-
-monitor_fixtures = {
-                  'threads': 0,
-                  'posts': 0,
-                  }
-
-
-def load_fixtures():
-    load_monitor_fixture(monitor_fixtures)

+ 0 - 39
misago/posts/models.py

@@ -1,39 +0,0 @@
-from django.db import models
-from django.utils.translation import ugettext_lazy as _
-
-class PostManager(models.Manager):
-    def filter_stats(self, start, end):
-        return self.filter(date__gte=start).filter(date__lte=end)
-    
-
-class Post(models.Model):
-    forum = models.ForeignKey(Forum, related_name='+')
-    thread = models.ForeignKey(Thread, related_name='+')
-    user = models.ForeignKey('users.User', related_name='+', null=True, blank=True)
-    user_name = models.CharField(max_length=255)
-    ip = models.GenericIPAddressField()
-    agent = models.CharField(max_length=255)
-    post = models.TextField()
-    post_preparsed = models.TextField()
-    upvotes = models.IntegerField(default=0)
-    downvotes = models.IntegerField(default=0)
-    date = models.DateTimeField()
-    attachments = models.BooleanField(default=False)
-    attachments_list = models.CommaSeparatedIntegerField(max_length=255)
-    edited = models.BooleanField(default=False)
-    edits = models.PositiveIntegerField(default=0)
-    edit_date = models.DateTimeField(null=True, blank=True)
-    edit_reason = models.CharField(max_length=255, null=True, blank=True)
-    edit_user = models.ForeignKey('users.User', related_name='+', null=True)
-    edit_user_name = models.CharField(max_length=255, null=True, blank=True)
-    edit_user_slug = models.SlugField(max_length=255, null=True, blank=True)
-    reported = models.BooleanField(default=False)
-    hidden = models.BooleanField(default=False)
-    protected = models.BooleanField(default=False)
-    
-    objects = PostManager()
-    
-    statistics_name = _('New Posts')
-    
-    def get_date(self):
-        return self.date

+ 0 - 1
misago/posts/views.py

@@ -1 +0,0 @@
-# Create your views here.

+ 185 - 0
misago/threads/acl.py

@@ -0,0 +1,185 @@
+from django.utils.translation import ugettext_lazy as _
+from django import forms
+from misago.acl.builder import BaseACL
+from misago.acl.utils import ACLError403, ACLError404
+from misago.forms import YesNoSwitch
+
+def make_forum_form(request, role, form):
+    form.base_fields['can_read_threads'] = forms.ChoiceField(choices=(
+                                                                     ('0', _("No")),
+                                                                     ('1', _("Yes, owned")),
+                                                                     ('2', _("Yes, all")),
+                                                                     ))
+    form.base_fields['can_start_threads'] = forms.ChoiceField(choices=(
+                                                                       ('0', _("No")),
+                                                                       ('1', _("Yes, with moderation")),
+                                                                       ('2', _("Yes")),
+                                                                       ))
+    form.base_fields['can_edit_own_threads'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_soft_delete_own_threads'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_write_posts'] = forms.ChoiceField(choices=(
+                                                                     ('0', _("No")),
+                                                                     ('1', _("Yes, with moderation")),
+                                                                     ('2', _("Yes")),
+                                                                     ))
+    form.base_fields['can_edit_own_posts'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_soft_delete_own_posts'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_upvote_posts'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_downvote_posts'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_see_posts_scores'] = forms.ChoiceField(choices=(
+                                                                          ('0', _("No")),
+                                                                          ('1', _("Yes, final score")),
+                                                                          ('2', _("Yes, both up and down-votes")),
+                                                                          ))
+    form.base_fields['can_see_votes'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_make_polls'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_vote_in_polls'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_see_votes'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_see_attachments'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_upload_attachments'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_download_attachments'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['attachment_size'] = forms.IntegerField(min_value=0,initial=100)
+    form.base_fields['attachment_limit'] = forms.IntegerField(min_value=0,initial=3)
+    form.base_fields['can_approve'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_edit_labels'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_see_changelog'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_make_annoucements'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_pin_threads'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_edit_threads_posts'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_move_threads_posts'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_close_threads'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_protect_posts'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    form.base_fields['can_delete_threads'] = forms.ChoiceField(choices=(
+                                                                        ('0', _("No")),
+                                                                        ('1', _("Yes, soft-delete")),
+                                                                        ('2', _("Yes, hard-delete")),
+                                                                        ))
+    form.base_fields['can_delete_posts'] = forms.ChoiceField(choices=(
+                                                                      ('0', _("No")),
+                                                                      ('1', _("Yes, soft-delete")),
+                                                                      ('2', _("Yes, hard-delete")),
+                                                                      ))
+    form.base_fields['can_delete_polls'] = forms.ChoiceField(choices=(
+                                                                      ('0', _("No")),
+                                                                      ('1', _("Yes, soft-delete")),
+                                                                      ('2', _("Yes, hard-delete")),
+                                                                      ))
+    form.base_fields['can_delete_attachments'] = forms.BooleanField(widget=YesNoSwitch,initial=False,required=False)
+    
+    form.layout.append((
+                        _("Threads"),
+                        (
+                         ('can_read_threads', {'label': _("Can read threads")}),
+                         ('can_start_threads', {'label': _("Can start new threads")}),
+                         ('can_edit_own_threads', {'label': _("Can edit own threads")}),
+                         ('can_soft_delete_own_threads', {'label': _("Can soft-delete own threads")}),
+                        ),
+                       ),)
+    form.layout.append((
+                        _("Posts"),
+                        (
+                         ('can_write_posts', {'label': _("Can write posts")}),
+                         ('can_edit_own_posts', {'label': _("Can edit own posts")}),
+                         ('can_edit_own_posts', {'label': _("Can soft-delete own posts")}),
+                        ),
+                       ),)
+    form.layout.append((
+                        _("Karma"),
+                        (
+                         ('can_upvote_posts', {'label': _("Can upvote posts")}),
+                         ('can_downvote_posts', {'label': _("Can downvote posts")}),
+                         ('can_see_posts_scores', {'label': _("Can see post score")}),
+                         ('can_see_votes', {'label': _("Can see who voted on post")}),
+                        ),
+                       ),)
+    form.layout.append((
+                        _("Polls"),
+                        (
+                         ('can_make_polls', {'label': _("Can make polls")}),
+                         ('can_vote_in_polls', {'label': _("Can vote in polls")}),
+                         ('can_see_votes', {'label': _("Can see who voted in poll")}),
+                        ),
+                       ),)
+    form.layout.append((
+                        _("Attachments"),
+                        (
+                         ('can_see_attachments', {'label': _("Can see attachments")}),
+                         ('can_upload_attachments', {'label': _("Can upload attachments")}),
+                         ('can_download_attachments', {'label': _("Can download attachments")}),
+                         ('attachment_size', {'label': _("Max size of single attachment (in Kb)"), 'help_text': _("Enter zero for no limit.")}),
+                         ('attachment_limit', {'label': _("Max number of attachments per post"), 'help_text': _("Enter zero for no limit.")}),
+                        ),
+                       ),)
+    form.layout.append((
+                        _("Moderation"),
+                        (
+                         ('can_approve', {'label': _("Can accept threads and posts")}),
+                         ('can_edit_labels', {'label': _("Can edit thread labels")}),
+                         ('can_see_changelog', {'label': _("Can see edits history")}),
+                         ('can_make_annoucements', {'label': _("Can make annoucements")}),
+                         ('can_pin_threads', {'label': _("Can pin threads")}),
+                         ('can_edit_threads_posts', {'label': _("Can edit threads and posts")}),
+                         ('can_move_threads_posts', {'label': _("Can move, merge and split threads and posts")}),
+                         ('can_close_threads', {'label': _("Can close threads")}),
+                         ('can_protect_posts', {'label': _("Can protect posts"), 'help_text': _("Protected posts cannot be changed by their owners.")}),
+                         ('can_delete_threads', {'label': _("Can delete threads")}),
+                         ('can_delete_posts', {'label': _("Can delete posts")}),
+                         ('can_delete_polls', {'label': _("Can delete polls")}),
+                         ('can_delete_attachments', {'label': _("Can delete attachments")}),
+                        ),
+                       ),)
+
+
+class ThreadsACL(BaseACL):
+    pass
+
+ 
+def build_forums(acl, perms, forums, forum_roles):
+    acl.threads = ThreadsACL()
+    for forum in forums:
+        forum_role = {
+                     'can_read_threads': 0,
+                     'can_start_threads': 0,
+                     'can_edit_own_threads': False,
+                     'can_soft_delete_own_threads': False,
+                     'can_write_posts': 0,
+                     'can_edit_own_posts': False,
+                     'can_soft_delete_own_posts': False,
+                     'can_upvote_posts': False,
+                     'can_downvote_posts': False,
+                     'can_see_posts_scores': 0,
+                     'can_see_votes': False,
+                     'can_make_polls': False,
+                     'can_vote_in_polls': False,
+                     'can_see_votes': False,
+                     'can_see_attachments': False,
+                     'can_upload_attachments': False,
+                     'can_download_attachments': False,
+                     'attachment_size': 100,
+                     'attachment_limit': 3,
+                     'can_approve': False,
+                     'can_edit_labels': False,
+                     'can_see_changelog': False,
+                     'can_make_annoucements': False,
+                     'can_pin_threads': False,
+                     'can_edit_threads_posts': False,
+                     'can_move_threads_posts': False,
+                     'can_close_threads': False,
+                     'can_protect_posts': False,
+                     'can_delete_threads': 0,
+                     'can_delete_posts': 0,
+                     'can_delete_polls': 0,
+                     'can_delete_attachments': False,
+                     }
+        for perm in perms:
+            try:
+                role = forum_roles[perm['forums'][forum.pk]]
+                for p in forum_role:
+                    if p in ['attachment_size', 'attachment_limit'] and role[p] == 0:
+                        forum_role[p] = 0
+                    elif role[p] > forum_role[p]:
+                        forum_role[p] = role[p]
+            except KeyError:
+                pass
+        acl.threads.acl[forum.pk] = forum_role
+            

+ 56 - 15
misago/threads/models.py

@@ -7,25 +7,29 @@ class ThreadManager(models.Manager):
 
 
 
 
 class Thread(models.Model):
 class Thread(models.Model):
-    forum = models.ForeignKey(Forum, related_name='+')
+    forum = models.ForeignKey('forums.Forum')
+    weight = models.PositiveIntegerField(default=0,db_index=True)
+    type = models.PositiveIntegerField(default=0)
     name = models.CharField(max_length=255)
     name = models.CharField(max_length=255)
     slug = models.SlugField(max_length=255)
     slug = models.SlugField(max_length=255)
-    replies = models.PositiveIntegerField()
-    views = models.PositiveIntegerField(default=0)
-    start = models.DateTimeField(default=0)
-    start_post = models.ForeignKey('Post', related_name='+', null=True, blank=True)
-    start_poster = models.ForeignKey('users.User', related_name='+', null=True, blank=True)
+    replies = models.PositiveIntegerField(default=0)
+    score = models.PositiveIntegerField(default=0,db_index=True)
+    upvotes = models.PositiveIntegerField(default=0)
+    downvotes = models.PositiveIntegerField(default=0)
+    start = models.DateTimeField(db_index=True)
+    start_post = models.ForeignKey('Post',related_name='+',null=True,blank=True)
+    start_poster = models.ForeignKey('users.User',null=True,blank=True)
     start_poster_name = models.CharField(max_length=255)
     start_poster_name = models.CharField(max_length=255)
     start_poster_slug = models.SlugField(max_length=255)
     start_poster_slug = models.SlugField(max_length=255)
     start_poster_style = models.CharField(max_length=255)
     start_poster_style = models.CharField(max_length=255)
-    last = models.DateTimeField()
-    last_post = models.ForeignKey('Post', related_name='+', null=True, blank=True)
-    last_poster = models.ForeignKey('users.User', related_name='+', null=True, blank=True)
-    last_poster_name = models.CharField(max_length=255, null=True, blank=True)
-    last_poster_slug = models.SlugField(max_length=255, null=True, blank=True)
-    last_poster_style = models.CharField(max_length=255, null=True, blank=True)
-    poster_styles_list = models.CharField(max_length=255, null=True, blank=True)
-    hidden = models.BooleanField(default=False)
+    last = models.DateTimeField(db_index=True)
+    last_post = models.ForeignKey('Post',related_name='+',null=True,blank=True)
+    last_poster = models.ForeignKey('users.User',related_name='+',null=True,blank=True)
+    last_poster_name = models.CharField(max_length=255,null=True,blank=True)
+    last_poster_slug = models.SlugField(max_length=255,null=True,blank=True)
+    last_poster_style = models.CharField(max_length=255,null=True,blank=True)
+    moderated = models.BooleanField(default=False,db_index=True)
+    hidden = models.BooleanField(default=False,db_index=True)
     closed = models.BooleanField(default=False)
     closed = models.BooleanField(default=False)
     
     
     objects = ThreadManager()
     objects = ThreadManager()
@@ -33,4 +37,41 @@ class Thread(models.Model):
     statistics_name = _('New Threads')
     statistics_name = _('New Threads')
         
         
     def get_date(self):
     def get_date(self):
-        return self.start
+        return self.start
+    
+
+class PostManager(models.Manager):
+    def filter_stats(self, start, end):
+        return self.filter(date__gte=start).filter(date__lte=end)
+    
+
+class Post(models.Model):
+    forum = models.ForeignKey('forums.Forum',related_name='+')
+    thread = models.ForeignKey(Thread)
+    user = models.ForeignKey('users.User',null=True,blank=True)
+    user_name = models.CharField(max_length=255)
+    ip = models.GenericIPAddressField()
+    agent = models.CharField(max_length=255)
+    post = models.TextField()
+    post_preparsed = models.TextField()
+    upvotes = models.PositiveIntegerField(default=0)
+    downvotes = models.PositiveIntegerField(default=0)
+    date = models.DateTimeField()
+    edited = models.BooleanField(default=False)
+    edits = models.PositiveIntegerField(default=0)
+    edit_date = models.DateTimeField(null=True,blank=True)
+    edit_reason = models.CharField(max_length=255,null=True,blank=True)
+    edit_user = models.ForeignKey('users.User',related_name='+',null=True)
+    edit_user_name = models.CharField(max_length=255,null=True,blank=True)
+    edit_user_slug = models.SlugField(max_length=255,null=True,blank=True)
+    reported = models.BooleanField(default=False)
+    moderated = models.BooleanField(default=False,db_index=True)
+    hidden = models.BooleanField(default=False,db_index=True)
+    protected = models.BooleanField(default=False)
+    
+    objects = PostManager()
+    
+    statistics_name = _('New Posts')
+    
+    def get_date(self):
+        return self.date

+ 9 - 0
misago/threads/urls.py

@@ -0,0 +1,9 @@
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('misago.threads.views',
+    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/$', 'threads', name="forum"),
+    url(r'^forum/(?P<slug>(\w|-)+)-(?P<forum>\d+)/(?P<page>\d+)/$', 'threads', name="forum"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/$', 'thread', name="thread"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>\d+)/$', 'thread', name="topic"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'thread', name="topic_reply"),
+)

+ 25 - 1
misago/threads/views.py

@@ -1 +1,25 @@
-# Create your views here.
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from misago.acl.utils import acl_errors
+from misago.forums.models import Forum
+from misago.threads.models import Thread, Post
+
+@acl_errors
+def threads(request, slug=None, forum=None, page=0):
+    request.acl.forums.check_forum(forum)
+    return request.theme.render_to_response('threads/list.html',
+                                            context_instance=RequestContext(request));
+    
+
+@acl_errors
+def thread(request, slug=None, thread=None, page=0):
+    return request.theme.render_to_response('threads/thread.html',
+                                            context_instance=RequestContext(request));
+    
+
+@acl_errors
+def reply(request, slug=None, thread=None):
+    return request.theme.render_to_response('threads/reply.html',
+                                            context_instance=RequestContext(request));

+ 2 - 0
misago/urls.py

@@ -11,6 +11,8 @@ urlpatterns = patterns('',
     (r'^register/', include('misago.register.urls')),
     (r'^register/', include('misago.register.urls')),
     (r'^activate/', include('misago.activation.urls')),
     (r'^activate/', include('misago.activation.urls')),
     (r'^reset-password/', include('misago.resetpswd.urls')),
     (r'^reset-password/', include('misago.resetpswd.urls')),
+    (r'^', include('misago.threads.urls')),
+    url(r'^redirect/(?P<slug>(\w|-)+)-(?P<forum>\d+)/$', 'misago.views.redirection', name="redirect"),
     url(r'^$', 'misago.views.home', name="index"),
     url(r'^$', 'misago.views.home', name="index"),
 )
 )
 
 

+ 1 - 1
misago/usercp/acl.py

@@ -24,7 +24,7 @@ def make_form(request, role, form):
                             ))
                             ))
 
 
 
 
-class UserCPACL(BaseACL):
+class UserCPACL(BaseACL):        
     def show_username_change(self):
     def show_username_change(self):
         return self.acl['name_changes_allowed'] > 0
         return self.acl['name_changes_allowed'] > 0
     
     

+ 16 - 0
misago/views.py

@@ -1,6 +1,7 @@
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.template import RequestContext
 from django.template import RequestContext
+from django.utils.translation import ugettext as _
 from misago.sessions.models import Session
 from misago.sessions.models import Session
 from misago.forums.models import Forum
 from misago.forums.models import Forum
 
 
@@ -20,6 +21,21 @@ def home(request):
                                         context_instance=RequestContext(request));
                                         context_instance=RequestContext(request));
 
 
 
 
+def redirection(request, forum, slug):
+    if not request.acl.forums.can_see(forum):
+        return error404(request)
+    try:
+        forum = Forum.objects.get(pk=forum, type='redirect')
+        if not request.acl.forums.can_browse(forum):
+            return error403(request, _("You don't have permission to follow this redirect."))
+        forum.redirects += 1
+        forum.redirects_delta += 1
+        forum.save(force_update=True)
+        return redirect(forum.redirect)
+    except Forum.DoesNotExist:
+        return error404(request)
+
+
 def redirect_message(request, message, type='info', owner=None):
 def redirect_message(request, message, type='info', owner=None):
     request.messages.set_flash(message, type, owner)
     request.messages.set_flash(message, type, owner)
     return redirect(reverse('index'))
     return redirect(reverse('index'))

+ 2 - 1
static/sora/css/sora.css

@@ -937,9 +937,10 @@ th.table-sort.sort-desc a:hover{border-bottom:3px solid #eca09a;padding-bottom:5
 .nav-tabs .tab-search.tab-search-no-tabs{position:relative;bottom:12px;}
 .nav-tabs .tab-search.tab-search-no-tabs{position:relative;bottom:12px;}
 .nav-tabs button{padding-left:7px;padding-right:7px;}
 .nav-tabs button{padding-left:7px;padding-right:7px;}
 .forums-list{padding-top:4px;}.forums-list .category{margin-bottom:12px;}.forums-list .category h2{color:#666666;font-size:110%;margin-bottom:0px;}.forums-list .category h2 small{color:#a6a6a6;font-size:100%;}
 .forums-list{padding-top:4px;}.forums-list .category{margin-bottom:12px;}.forums-list .category h2{color:#666666;font-size:110%;margin-bottom:0px;}.forums-list .category h2 small{color:#a6a6a6;font-size:100%;}
-.forums-list .category-important .well-forum{border:1px solid #b3e5ff;-webkit-box-shadow:0px 0px 0px 3px #e6f7ff;-moz-box-shadow:0px 0px 0px 3px #e6f7ff;box-shadow:0px 0px 0px 3px #e6f7ff;}
+.forums-list .category-important .well-forum{border:1px solid #0099e6;-webkit-box-shadow:0px 0px 0px 3px #66ccff;-moz-box-shadow:0px 0px 0px 3px #66ccff;box-shadow:0px 0px 0px 3px #66ccff;}
 .forums-list .well-forum{margin:0px -8px;margin-bottom:14px;overflow:auto;padding:12px 8px;padding-bottom:8px;}.forums-list .well-forum .row .span3{margin-left:0px;padding-left:16px;}
 .forums-list .well-forum{margin:0px -8px;margin-bottom:14px;overflow:auto;padding:12px 8px;padding-bottom:8px;}.forums-list .well-forum .row .span3{margin-left:0px;padding-left:16px;}
 .forums-list .well-forum .forum-icon{background-color:#0088cc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;float:left;padding:3px 6px;position:relative;bottom:4px;margin-bottom:-4px;}
 .forums-list .well-forum .forum-icon{background-color:#0088cc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;float:left;padding:3px 6px;position:relative;bottom:4px;margin-bottom:-4px;}
+.forums-list .well-forum .redirect-icon{background-color:#46a546;}
 .forums-list .well-forum .forum-details{margin-left:36px;overflow:auto;}
 .forums-list .well-forum .forum-details{margin-left:36px;overflow:auto;}
 .forums-list .well-forum .forum-stat{margin-left:16px;margin-right:4px;padding:8px 12px;font-size:140%;}.forums-list .well-forum .forum-stat .muted{color:#999999;}
 .forums-list .well-forum .forum-stat{margin-left:16px;margin-right:4px;padding:8px 12px;font-size:140%;}.forums-list .well-forum .forum-stat .muted{color:#999999;}
 .forums-list .well-forum .forum-stat .stag,.forums-list .well-forum .forum-stat .positive{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;margin:-2px 0px;padding:2px 5px;text-shadow:0px 1px 0px #ffffff;}
 .forums-list .well-forum .forum-stat .stag,.forums-list .well-forum .forum-stat .positive{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;margin:-2px 0px;padding:2px 5px;text-shadow:0px 1px 0px #ffffff;}

+ 6 - 2
static/sora/css/sora/forums.less

@@ -20,8 +20,8 @@
   
   
   .category-important {
   .category-important {
     .well-forum {
     .well-forum {
-      border: 1px solid lighten(@linkColor, 45%);
-      .box-shadow(0px 0px 0px 3px lighten(@linkColor, 55%));
+      border: 1px solid lighten(@linkColor, 5%);
+      .box-shadow(0px 0px 0px 3px lighten(@linkColor, 30%));
     }
     }
   }
   }
   
   
@@ -49,6 +49,10 @@
       margin-bottom: -4px;
       margin-bottom: -4px;
     }
     }
     
     
+    .redirect-icon {
+      background-color: @green; 
+    }
+    
     .forum-details {
     .forum-details {
       margin-left: 36px;
       margin-left: 36px;
       overflow: auto;
       overflow: auto;

+ 27 - 17
templates/sora/index.html

@@ -10,32 +10,18 @@
       
       
 {% block content %}
 {% block content %}
 <div class="row">
 <div class="row">
-  <div class="span9">
+  <div class="span8">
     <div class="forums-list">
     <div class="forums-list">
       {% for category in forums_list %}{% if category.subforums %}
       {% for category in forums_list %}{% if category.subforums %}
       <div class="category{% if category.style %} {{ category.style }}{% endif %}">
       <div class="category{% if category.style %} {{ category.style }}{% endif %}">
         <h2>{{ category.name }}{% if category.description %} <small><strong>{{ category.description }}</strong></small>{% endif %}</h2>
         <h2>{{ category.name }}{% if category.description %} <small><strong>{{ category.description }}</strong></small>{% endif %}</h2>
         {% for forum in category.subforums %}
         {% for forum in category.subforums %}
-        <div class="well well-forum{% if forum.style %} {{ forum.style }}{% endif %}">
-          <div class="forum-icon"><i class="icon-comment icon-white"></i></div>
-          <div class="forum-details">
-            <div class="pull-left">
-              <h3><a href="#">{{ forum.name }}</a></h3>
-              {% if forum.description %}<div class="muted">{{ forum.description }}</div>{% endif %}
-            </div>
-            <div class="pull-right forum-stat stat-posts">
-              <span class="stat {% if forum.posts_delta > 0 %}positive{% else %}stag{% endif %}">{% if forum.posts_delta > 0 %}+{{ forum.posts_delta }}{% else %}{{ forum.posts }}{% endif %}</span> <span class="muted">{% trans %}posts{% endtrans %}</span>
-            </div>
-            <div class="pull-right forum-stat stat-topics">
-              <span class="stat {% if forum.threads_delta > 0 %}positive{% else %}stag{% endif %}">{% if forum.posts_delta > 0 %}+{{ forum.threads_delta }}{% else %}{{ forum.threads }}{% endif %}</span> <span class="muted">{% trans %}threads{% endtrans %}</span>
-            </div>
-          </div>
-        </div>
+        {{ draw_forum(category, forum) }}
         {% endfor %}
         {% endfor %}
       </div>{% endif %}{% endfor %}
       </div>{% endif %}{% endfor %}
     </div>
     </div>
   </div>
   </div>
-  <div class="span3 forum-list-side">
+  <div class="span4 forum-list-side">
     {% if team_online %}
     {% if team_online %}
     <h3>{% trans %}Team Online{% endtrans %}</h3>
     <h3>{% trans %}Team Online{% endtrans %}</h3>
     {% for user in team_online %}
     {% for user in team_online %}
@@ -55,3 +41,27 @@
   </div>
   </div>
 </div>
 </div>
 {% endblock %}
 {% endblock %}
+
+{% macro draw_forum(category, forum) %}
+        <div class="well well-forum{% if forum.style %} {{ forum.style }}{% endif %}">
+          <div class="forum-icon{% if forum.type == 'redirect' %} redirect-icon{% endif %}"><i class="icon-{% if forum.type == 'redirect' %}circle-arrow-right{% else %}comment{% endif %} icon-white"></i></div>
+          <div class="forum-details">
+            <div class="pull-left">
+              <h3><a href="{% if forum.type == 'redirect' %}{% url 'redirect' slug=forum.slug, forum=forum.id %}{% else %}{% url 'forum' slug=forum.slug, forum=forum.id %}{% endif %}">{{ forum.name }}</a></h3>
+              {% if forum.description %}<div class="muted">{{ forum.description }}</div>{% endif %}
+            </div>
+            {% if forum.type == 'redirect' %}
+            <div class="pull-right forum-stat stat-redirects">
+              <span class="stat {% if forum.redirects_delta > 0 %}positive{% else %}stag{% endif %}">{% if forum.redirects_delta > 0 %}+{{ forum.redirects_delta }}{% else %}{{ forum.redirects }}{% endif %}</span> <span class="muted">{% trans %}clicks{% endtrans %}</span>
+            </div>
+            {% else %}
+            <div class="pull-right forum-stat stat-posts">
+              <span class="stat {% if forum.posts_delta > 0 %}positive{% else %}stag{% endif %}">{% if forum.posts_delta > 0 %}+{{ forum.posts_delta }}{% else %}{{ forum.posts }}{% endif %}</span> <span class="muted">{% trans %}posts{% endtrans %}</span>
+            </div>
+            <div class="pull-right forum-stat stat-topics">
+              <span class="stat {% if forum.threads_delta > 0 %}positive{% else %}stag{% endif %}">{% if forum.posts_delta > 0 %}+{{ forum.threads_delta }}{% else %}{{ forum.threads }}{% endif %}</span> <span class="muted">{% trans %}threads{% endtrans %}</span>
+            </div>
+            {% endif %}
+          </div>
+        </div>
+{% endmacro %}

+ 1 - 0
templates/sora/threads/list.html

@@ -0,0 +1 @@
+<h1>THREADS LIST!</h1>

+ 1 - 0
templates/sora/threads/reply.html

@@ -0,0 +1 @@
+<h1>THREAD REPLY!!</h1>

+ 1 - 0
templates/sora/threads/thread.html

@@ -0,0 +1 @@
+<h1>THREAD VIEW!</h1>