diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index acfff9eb..d32560e5 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -18,6 +18,7 @@ from subscription.models import Subscription from counter.models import Customer, ProductType, Product, Counter from com.models import Sith, Weekmail from election.models import Election, Role, Candidature, ElectionList +from forum.models import Forum, ForumMessage class Command(BaseCommand): @@ -429,3 +430,13 @@ Welcome to the wiki page! cand = Candidature(role=pres, user=sli, election_list=listeT, program="En fait j'aime pas l'info, je voulais faire GMC") cand.save() + # Forum + room = Forum(name="Salon de discussions", description="Pour causer de tout", is_category=True) + room.save() + Forum(name="AE", description="Réservé au bureau AE", parent=room).save() + Forum(name="BdF", description="Réservé au bureau BdF", parent=room).save() + Forum(name="Hall de discussions", description="Pour toutes les discussions", parent=room).save() + various = Forum(name="Divers", description="Pour causer de rien", is_category=True) + various.save() + Forum(name="Promos", description="Réservé aux Promos", parent=various).save() + diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index cc9207c2..542e9f21 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -91,7 +91,7 @@ {% trans %}Matmatronch{% endtrans %} {% trans %}Wiki{% endtrans %} {% trans %}SAS{% endtrans %} - {% trans %}Forum{% endtrans %} + {% trans %}Forum{% endtrans %} {% trans %}Services{% endtrans %} {% trans %}Files{% endtrans %} {% trans %}Sponsors{% endtrans %} diff --git a/forum/__init__.py b/forum/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/forum/admin.py b/forum/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/forum/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/forum/migrations/0001_initial.py b/forum/migrations/0001_initial.py new file mode 100644 index 00000000..da8dbdfc --- /dev/null +++ b/forum/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('core', '0019_preferences_receive_weekmail'), + ] + + operations = [ + migrations.CreateModel( + name='Forum', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), + ('name', models.CharField(verbose_name='name', max_length=64)), + ('description', models.CharField(default='', verbose_name='description', max_length=256)), + ('is_category', models.BooleanField(default=False, verbose_name='is a category')), + ('edit_groups', models.ManyToManyField(to='core.Group', blank=True, related_name='editable_forums')), + ('owner_group', models.ForeignKey(to='core.Group', default=4, related_name='owned_forums')), + ('parent', models.ForeignKey(blank=True, null=True, to='forum.Forum', related_name='children')), + ('view_groups', models.ManyToManyField(to='core.Group', blank=True, related_name='viewable_forums')), + ], + ), + migrations.CreateModel( + name='ForumMessage', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), + ('title', models.CharField(default='', blank=True, verbose_name='title', max_length=64)), + ('message', models.TextField(verbose_name='message', default='')), + ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='forum_messages')), + ], + ), + migrations.CreateModel( + name='ForumTopic', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), + ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='forum_topics')), + ('forum', models.ForeignKey(to='forum.Forum', related_name='topics')), + ], + ), + migrations.AddField( + model_name='forummessage', + name='topic', + field=models.ForeignKey(to='forum.ForumTopic', related_name='messages'), + ), + ] diff --git a/forum/migrations/0002_forumtopic_title.py b/forum/migrations/0002_forumtopic_title.py new file mode 100644 index 00000000..17407dad --- /dev/null +++ b/forum/migrations/0002_forumtopic_title.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('forum', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='forumtopic', + name='title', + field=models.CharField(verbose_name='title', max_length=64, default=''), + ), + ] diff --git a/forum/migrations/0003_auto_20170121_0311.py b/forum/migrations/0003_auto_20170121_0311.py new file mode 100644 index 00000000..f66151e2 --- /dev/null +++ b/forum/migrations/0003_auto_20170121_0311.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('forum', '0002_forumtopic_title'), + ] + + operations = [ + migrations.AlterModelOptions( + name='forumtopic', + options={'ordering': ['messages__date', '-id']}, + ), + migrations.AddField( + model_name='forummessage', + name='date', + field=models.DateTimeField(verbose_name='date', default=django.utils.timezone.now), + ), + ] diff --git a/forum/migrations/0004_forumtopic_description.py b/forum/migrations/0004_forumtopic_description.py new file mode 100644 index 00000000..b1e3fe96 --- /dev/null +++ b/forum/migrations/0004_forumtopic_description.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('forum', '0003_auto_20170121_0311'), + ] + + operations = [ + migrations.AddField( + model_name='forumtopic', + name='description', + field=models.CharField(max_length=256, verbose_name='description', default=''), + ), + ] diff --git a/forum/migrations/__init__.py b/forum/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/forum/models.py b/forum/models.py new file mode 100644 index 00000000..a9953066 --- /dev/null +++ b/forum/models.py @@ -0,0 +1,79 @@ +from django.db import models +from django.core import validators +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError +from django.db import IntegrityError, transaction +from django.core.urlresolvers import reverse +from django.utils import timezone + +from core.models import User, MetaGroup, Group, SithFile + +class Forum(models.Model): + """ + The Forum class, made as a tree to allow nice tidy organization + """ + name = models.CharField(_('name'), max_length=64) + description = models.CharField(_('description'), max_length=256, default="") + is_category = models.BooleanField(_('is a category'), default=False) + parent = models.ForeignKey('Forum', related_name='children', null=True, blank=True) + owner_group = models.ForeignKey(Group, related_name="owned_forums", + default=settings.SITH_GROUP_COM_ADMIN_ID) + edit_groups = models.ManyToManyField(Group, related_name="editable_forums", blank=True) + view_groups = models.ManyToManyField(Group, related_name="viewable_forums", blank=True) + + def check_loop(self): + """Raise a validation error when a loop is found within the parent list""" + objs = [] + cur = self + while cur.parent is not None: + if cur in objs: + raise ValidationError(_('You can not make loops in forums')) + objs.append(cur) + cur = cur.parent + + def clean(self): + self.check_loop() + + def __str__(self): + return "%s" % (self.name) + + def get_absolute_url(self): + return reverse('forum:view_forum', kwargs={'forum_id': self.id}) + + def get_parent_list(self): + l = [] + p = self.parent + while p is not None: + l.append(p) + p = p.parent + return l + +class ForumTopic(models.Model): + forum = models.ForeignKey(Forum, related_name='topics') + author = models.ForeignKey(User, related_name='forum_topics') + title = models.CharField(_("title"), default="", max_length=64) + description = models.CharField(_('description'), max_length=256, default="") + + class Meta: + ordering = ['-id'] + + def get_absolute_url(self): + return reverse('forum:view_topic', kwargs={'topic_id': self.id}) + +class ForumMessage(models.Model): + """ + "A ForumMessage object is a message in the forum" Cpt. Obvious + """ + topic = models.ForeignKey(ForumTopic, related_name='messages') + author = models.ForeignKey(User, related_name='forum_messages') + title = models.CharField(_("title"), default="", max_length=64, blank=True) + message = models.TextField(_("message"), default="") + date = models.DateTimeField(_('date'), default=timezone.now) + + class Meta: + ordering = ['-id'] + + def get_absolute_url(self): + return self.topic.get_absolute_url() + diff --git a/forum/templates/forum/forum.jinja b/forum/templates/forum/forum.jinja new file mode 100644 index 00000000..6b0fa851 --- /dev/null +++ b/forum/templates/forum/forum.jinja @@ -0,0 +1,44 @@ +{% extends "core/base.jinja" %} +{% from 'forum/macros.jinja' import display_forum %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block content %} +

{{ forum.get_parent_list() }}

+

{{ forum.name }}

+ New forum + {% for f in forum.children.all() %} + {{ display_forum(f) }} + {% endfor %} + {% for t in forum.topics.all() %} +
+

+ View + Edit +

+
{{ t.title }}
+

{{ t.description }}

+
+ {% endfor %} + New topic +{% endblock %} + + + diff --git a/forum/templates/forum/macros.jinja b/forum/templates/forum/macros.jinja new file mode 100644 index 00000000..cda278ea --- /dev/null +++ b/forum/templates/forum/macros.jinja @@ -0,0 +1,14 @@ +{% macro display_forum(forum) %} +
+

+ {% if not forum.is_category %} + View + {% endif %} + Edit +

+
{{ forum.name }}
+

{{ forum.description }}

+
+{% endmacro %} + + diff --git a/forum/templates/forum/main.jinja b/forum/templates/forum/main.jinja new file mode 100644 index 00000000..0477e50f --- /dev/null +++ b/forum/templates/forum/main.jinja @@ -0,0 +1,33 @@ +{% extends "core/base.jinja" %} +{% from 'core/macros.jinja' import user_profile_link %} +{% from 'forum/macros.jinja' import display_forum %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block content %} +

{% trans %}Forum{% endtrans %}

+ New forum + {% for f in forum_list %} +
+ {{ display_forum(f) }} + {% for c in f.children.all() %} + {{ display_forum(c) }} + {% endfor %} +
+ {% endfor %} +{% endblock %} + + + diff --git a/forum/templates/forum/topic.jinja b/forum/templates/forum/topic.jinja new file mode 100644 index 00000000..9005d62b --- /dev/null +++ b/forum/templates/forum/topic.jinja @@ -0,0 +1,38 @@ +{% extends "core/base.jinja" %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block content %} +

{{ topic.title }}

+

{{ topic.description }}

+

Reply

+ {% for m in topic.messages.all() %} +
+
{{ m.title }}
+

{{ m.author.get_display_name() }} - {{ m.date|date(DATETIME_FORMAT) }} + {{ m.date|time(DATETIME_FORMAT) }} - + Reply as quote

+

{{ m.message|markdown }}

+
+ {% endfor %} +{% endblock %} + + + diff --git a/forum/tests.py b/forum/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/forum/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/forum/urls.py b/forum/urls.py new file mode 100644 index 00000000..8c55bd1c --- /dev/null +++ b/forum/urls.py @@ -0,0 +1,16 @@ +from django.conf.urls import url, include + +from forum.views import * + +urlpatterns = [ + url(r'^$', ForumMainView.as_view(), name='main'), + url(r'^new_forum$', ForumCreateView.as_view(), name='new_forum'), + url(r'^(?P[0-9]+)$', ForumDetailView.as_view(), name='view_forum'), + url(r'^(?P[0-9]+)/edit$', ForumEditView.as_view(), name='edit_forum'), + url(r'^(?P[0-9]+)/new_topic$', ForumTopicCreateView.as_view(), name='new_topic'), + url(r'^topic/(?P[0-9]+)$', ForumTopicDetailView.as_view(), name='view_topic'), + url(r'^topic/(?P[0-9]+)/edit$', ForumTopicEditView.as_view(), name='edit_topic'), + url(r'^topic/(?P[0-9]+)/new_message$', ForumMessageCreateView.as_view(), name='new_message'), + # url(r'^(?P[0-9]+)/tools$', ClubToolsView.as_view(), name='tools'), +] + diff --git a/forum/views.py b/forum/views.py new file mode 100644 index 00000000..bf642f66 --- /dev/null +++ b/forum/views.py @@ -0,0 +1,92 @@ +from django.shortcuts import render, get_object_or_404 +from django.views.generic import ListView, DetailView, RedirectView +from django.views.generic.edit import UpdateView, CreateView, DeleteView +from django.utils.translation import ugettext_lazy as _ +from django.core.urlresolvers import reverse, reverse_lazy +from django.utils import timezone +from django.conf import settings +from django import forms +from django.core.exceptions import PermissionDenied + +from forum.models import Forum, ForumMessage, ForumTopic + +class ForumMainView(ListView): + queryset = Forum.objects.filter(parent=None) + template_name = "forum/main.jinja" + +class ForumCreateView(CreateView): + model = Forum + fields = ['name', 'parent', 'is_category', 'owner_group', 'edit_groups', 'view_groups'] + template_name = "core/create.jinja" + + def get_initial(self): + init = super(ForumCreateView, self).get_initial() + parent = Forum.objects.filter(id=self.request.GET['parent']).first() + init['parent'] = parent + return init + +class ForumEditView(UpdateView): + model = Forum + pk_url_kwarg = "forum_id" + fields = ['name', 'parent', 'is_category', 'owner_group', 'edit_groups', 'view_groups'] + template_name = "core/edit.jinja" + success_url = reverse_lazy('forum:main') + +class ForumDetailView(DetailView): + model = Forum + template_name = "forum/forum.jinja" + pk_url_kwarg = "forum_id" + +class ForumTopicCreateView(CreateView): + model = ForumTopic + fields = ['title'] + template_name = "core/create.jinja" + + def dispatch(self, request, *args, **kwargs): + self.forum = get_object_or_404(Forum, id=self.kwargs['forum_id'], is_category=False) + if not request.user.can_view(self.forum): + raise PermissionDenied + return super(ForumTopicCreateView, self).dispatch(request, *args, **kwargs) + + def form_valid(self, form): + form.instance.forum = self.forum + form.instance.author = self.request.user + return super(ForumTopicCreateView, self).form_valid(form) + +class ForumTopicEditView(UpdateView): + model = ForumTopic + fields = ['title'] + pk_url_kwarg = "topic_id" + template_name = "core/edit.jinja" + +class ForumTopicDetailView(DetailView): + model = ForumTopic + pk_url_kwarg = "topic_id" + template_name = "forum/topic.jinja" + context_object_name = "topic" + +class ForumMessageCreateView(CreateView): + model = ForumMessage + fields = ['title', 'message'] + template_name = "core/create.jinja" + + def dispatch(self, request, *args, **kwargs): + self.topic = get_object_or_404(ForumTopic, id=self.kwargs['topic_id']) + if not request.user.can_view(self.topic): + raise PermissionDenied + return super(ForumMessageCreateView, self).dispatch(request, *args, **kwargs) + + def get_initial(self): + init = super(ForumMessageCreateView, self).get_initial() + try: + init['message'] = "\n".join([ + " > " + line for line in ForumMessage.objects.filter(id=self.request.GET['quote_id']).first().message.split('\n') + ]) + except: pass + return init + + def form_valid(self, form): + form.instance.topic = self.topic + form.instance.author = self.request.user + return super(ForumMessageCreateView, self).form_valid(form) + diff --git a/sith/settings.py b/sith/settings.py index 51abf596..c5696f4f 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -59,6 +59,7 @@ INSTALLED_APPS = ( 'sas', 'com', 'election', + 'forum', ) MIDDLEWARE_CLASSES = ( diff --git a/sith/urls.py b/sith/urls.py index d759979e..e632e6ef 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -40,6 +40,7 @@ urlpatterns = [ url(r'^sas/', include('sas.urls', namespace="sas", app_name="sas")), url(r'^api/v1/', include('api.urls', namespace="api", app_name="api")), url(r'^election/', include('election.urls', namespace="election", app_name="election")), + url(r'^forum/', include('forum.urls', namespace="forum", app_name="forum")), url(r'^admin/', include(admin.site.urls)), url(r'^ajax_select/', include(ajax_select_urls)), url(r'^i18n/', include('django.conf.urls.i18n')),