diff --git a/forum/models.py b/forum/models.py index 0483fb40..8e29b98f 100644 --- a/forum/models.py +++ b/forum/models.py @@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError from django.db import IntegrityError, transaction from django.core.urlresolvers import reverse from django.utils import timezone +from django.utils.functional import cached_property from datetime import datetime import pytz @@ -16,6 +17,10 @@ from club.models import Club class Forum(models.Model): """ The Forum class, made as a tree to allow nice tidy organization + + owner_club allows club members to moderate there own topics + edit_groups allows to put any group as a forum admin + view_groups allows some groups to view a forum """ name = models.CharField(_('name'), max_length=64) description = models.CharField(_('description'), max_length=256, default="") @@ -77,6 +82,10 @@ class Forum(models.Model): def get_absolute_url(self): return reverse('forum:view_forum', kwargs={'forum_id': self.id}) + @cached_property + def parent_list(self): + return self.get_parent_list() + def get_parent_list(self): l = [] p = self.parent @@ -85,22 +94,27 @@ class Forum(models.Model): p = p.parent return l + @cached_property + def topic_number(self): + return self.get_topic_number() + def get_topic_number(self): number = self.topics.all().count() for c in self.children.all(): - number += c.get_topic_number() + number += c.topic_number return number + @cached_property + def last_message(self): + return self.get_last_message() + def get_last_message(self): last_msg = None - for m in ForumMessage.objects.order_by('-id'): + for m in ForumMessage.objects.select_related('topic__forum', 'author').order_by('-id'): forum = m.topic.forum - if self in (forum.get_parent_list() + [forum]): + if self in (forum.parent_list + [forum]): return m last_msg = m - try: - pass - except: pass return last_msg class ForumTopic(models.Model): @@ -145,26 +159,34 @@ class ForumMessage(models.Model): ordering = ['id'] def __str__(self): - return "%s" % (self.title) + return "%s - %s" % (self.id, self.title) - def is_owned_by(self, user): - return self.topic.is_owned_by(user) or user.id == self.author.id + def is_owned_by(self, user): # Anyone can create a topic: it's better to + # check the rights at the forum level, since it's more controlled + return self.topic.forum.is_owned_by(user) or user.id == self.author.id def can_be_edited_by(self, user): - return user.is_owner(self.topic) + return user.can_edit(self.topic.forum) def can_be_viewed_by(self, user): return user.can_view(self.topic) def can_be_moderated_by(self, user): - return user.is_in_group(settings.SITH_GROUP_FORUM_ADMIN_ID) + return self.topic.forum.is_owned_by(user) def get_absolute_url(self): - return self.topic.get_absolute_url() + "#first_unread" + return self.topic.get_absolute_url() + "#msg_" + str(self.id) def mark_as_read(self, user): self.readers.add(user) + def is_read(self, user): + return (self.date < user.forum_infos.last_read_date) or (user in self.readers.all()) + + @cached_property + def deleted(self): + return self.is_deleted() + def is_deleted(self): meta = self.metas.exclude(action="EDIT").order_by('-date').first() if meta: @@ -184,7 +206,12 @@ class ForumMessageMeta(models.Model): action = models.CharField(_("action"), choices=MESSAGE_META_ACTIONS, max_length=16) class ForumUserInfo(models.Model): - user = models.OneToOneField(User, related_name="_forum_infos") + """ + This currently stores only the last date a user clicked "Mark all as read". + However, this can be extended with lot of user preferences dedicated to a + user, such as the favourite topics, the signature, and so on... + """ + user = models.OneToOneField(User, related_name="_forum_infos") # TODO: see to move that to the User class in order to reduce the number of db queries last_read_date = models.DateTimeField(_('last read date'), default=datetime(year=settings.SITH_SCHOOL_START_YEAR, month=1, day=1, tzinfo=pytz.UTC)) diff --git a/forum/templates/forum/macros.jinja b/forum/templates/forum/macros.jinja index 5bc40ed9..7c5fbc2b 100644 --- a/forum/templates/forum/macros.jinja +++ b/forum/templates/forum/macros.jinja @@ -23,13 +23,12 @@ {% if not forum.is_category %}
- {{ forum.get_topic_number() }} + {{ forum.topic_number }}
- {% set last_msg = forum.get_last_message() %} - {% if last_msg %} - {{ last_msg.author }}
- {{ last_msg.date|date(DATETIME_FORMAT) }} {{ last_msg.date|time(DATETIME_FORMAT) }} + {% if forum.last_message %} + {{ forum.last_message.author }}
+ {{ forum.last_message.date|date(DATETIME_FORMAT) }} {{ forum.last_message.date|time(DATETIME_FORMAT) }} {% endif %}
@@ -64,7 +63,7 @@
- {% set last_msg = topic.messages.order_by('id').last() %} + {% set last_msg = topic.messages.order_by('id').select_related('author').last() %} {{ user_profile_link(last_msg.author) }}
{{ last_msg.date|date(DATETIME_FORMAT) }} {{ last_msg.date|time(DATETIME_FORMAT) }}
diff --git a/forum/templates/forum/topic.jinja b/forum/templates/forum/topic.jinja index 41ceb1c4..bf199cca 100644 --- a/forum/templates/forum/topic.jinja +++ b/forum/templates/forum/topic.jinja @@ -37,16 +37,13 @@

{{ topic.description }}

Reply

- {% set vars = {'unread': False} %} {# ugly hack to counter Jinja scopes #} - {% for m in topic.messages.all() %} - {% if m.id == first_unread_message_id and vars.update({'unread': True}) %} {# idem #} -
- {% elif vars.unread %} -
+ {% for m in topic.messages.select_related('author__profile_pict').all() %} + {% if m.id >= first_unread_message_id %} +
{% else %} -
+
{% endif %} -
+
{% if m.author.profile_pict %} {% trans %}Profile{% endtrans %} {% else %} @@ -55,7 +52,7 @@
{{ user_profile_link(m.author) }}
-
+
{% if m.title %}
{{ m.title }}
@@ -68,7 +65,7 @@ {% trans %}Edit{% endtrans %} {% endif %} {% if m.can_be_moderated_by(user) %} - {% if m.is_deleted() %} + {% if m.deleted %} {% trans %}Undelete{% endtrans %} {% else %} {% trans %}Delete{% endtrans %} @@ -81,17 +78,20 @@
{{ m.message|markdown }}
+ {% if m.can_be_moderated_by(user) %}
    - {% for meta in m.metas.all() %} + {% for meta in m.metas.select_related('user').all() %}
  • {{ meta.get_action_display() }} {{ meta.user.get_display_name() }} {% trans %} at {% endtrans %}{{ meta.date|time(DATETIME_FORMAT) }} {% trans %} the {% endtrans %}{{ meta.date|date(DATETIME_FORMAT)}}
  • {% endfor %}
+ {% endif %}
{{ m.mark_as_read(user) or "" }} {% endfor %} +

Reply

{% endblock %} diff --git a/forum/views.py b/forum/views.py index e021e771..dd8b0a54 100644 --- a/forum/views.py +++ b/forum/views.py @@ -10,6 +10,8 @@ from django import forms from django.db import models from django.core.exceptions import PermissionDenied +from math import inf + from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta @@ -36,9 +38,7 @@ class ForumLastUnread(ListView): def get_queryset(self): l = ForumMessage.objects.exclude(readers=self.request.user).filter( date__gt=self.request.user.forum_infos.last_read_date).values_list('topic') # TODO try to do better - return self.model.objects.filter(id__in=l).annotate(models.Max('messages__date')).order_by('-messages__date__max') - # return self.model.objects.exclude(messages__readers=self.request.user).distinct().annotate( - # models.Max('messages__date')).order_by('-messages__date__max') + return self.model.objects.filter(id__in=l).annotate(models.Max('messages__date')).order_by('-messages__date__max').select_related('author') class ForumCreateView(CanCreateMixin, CreateView): model = Forum @@ -118,14 +118,15 @@ class ForumTopicDetailView(CanViewMixin, DetailView): pk_url_kwarg = "topic_id" template_name = "forum/topic.jinja" context_object_name = "topic" + queryset = ForumTopic.objects.select_related('forum__parent') def get_context_data(self, **kwargs): kwargs = super(ForumTopicDetailView, self).get_context_data(**kwargs) - msg = self.object.messages.exclude(readers=self.request.user).order_by('id').first() + msg = self.object.messages.exclude(readers=self.request.user).filter(date__gte=self.request.user.forum_infos.last_read_date).order_by('id').first() try: kwargs['first_unread_message_id'] = msg.id except: - kwargs['first_unread_message_id'] = -1 + kwargs['first_unread_message_id'] = inf return kwargs class ForumMessageEditView(CanEditMixin, UpdateView): @@ -144,6 +145,7 @@ class ForumMessageDeleteView(SingleObjectMixin, RedirectView): permanent = False def get_redirect_url(self, *args, **kwargs): + self.object = self.get_object() if self.object.can_be_moderated_by(self.request.user): ForumMessageMeta(message=self.object, user=self.request.user, action="DELETE").save() return self.object.get_absolute_url() @@ -154,6 +156,7 @@ class ForumMessageUndeleteView(SingleObjectMixin, RedirectView): permanent = False def get_redirect_url(self, *args, **kwargs): + self.object = self.get_object() if self.object.can_be_moderated_by(self.request.user): ForumMessageMeta(message=self.object, user=self.request.user, action="UNDELETE").save() return self.object.get_absolute_url() @@ -172,10 +175,14 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): 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') + message = ForumMessage.objects.select_related('author').filter(id=self.request.GET['quote_id']).first() + init['message'] = "> ##### %s\n" % (_("%(author)s said") % {'author': message.author.get_short_name()}) + init['message'] += "\n".join([ + "> " + line for line in message.message.split('\n') ]) - except: pass + init['message'] += "\n\n" + except Exception as e: + print(repr(e)) return init def form_valid(self, form): @@ -183,3 +190,4 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): form.instance.author = self.request.user return super(ForumMessageCreateView, self).form_valid(form) +