-
- {% if m.title %}
-
{{ m.title }}
- {% endif %}
-
-
-
- {% trans %}Reply as quote{% endtrans %}
- {% if user.can_edit(m) %}
-
{% trans %}Edit{% endtrans %}
- {% endif %}
- {% if m.can_be_moderated_by(user) %}
- {% if m.deleted %}
-
{% trans %}Undelete{% endtrans %}
- {% else %}
-
{% trans %}Delete{% endtrans %}
- {% endif %}
- {% endif %}
-
-
{{ m.date|localtime|date(DATETIME_FORMAT) }} {{ m.date|localtime|time(DATETIME_FORMAT) }}
+
diff --git a/forum/templates/forum/main.jinja b/forum/templates/forum/main.jinja
index 243cbb12..ccca49cb 100644
--- a/forum/templates/forum/main.jinja
+++ b/forum/templates/forum/main.jinja
@@ -7,9 +7,10 @@
{% endblock %}
{% block content %}
-
- {% trans %}Forum{% endtrans %} >
-
+
+{% trans %}Forum{% endtrans %} >
+
+
{% trans %}Forum{% endtrans %}
{% trans %}View last unread messages{% endtrans %}
@@ -19,14 +20,28 @@
{% trans %}New forum{% endtrans %}
{% endif %}
+
+
+ {% trans %}Title{% endtrans %}
+
+
+
+ {% trans %}Topics{% endtrans %}
+
+
+ {% trans %}Last message{% endtrans %}
+
+
+
{% for f in forum_list %}
-
- {{ display_forum(f, user) }}
+
+ {{ display_forum(f, user, True) }}
{% for c in f.children.all() %}
{{ display_forum(c, user) }}
{% endfor %}
{% endfor %}
+
{% endblock %}
diff --git a/forum/templates/forum/topic.jinja b/forum/templates/forum/topic.jinja
index 80c246b1..0e02b99e 100644
--- a/forum/templates/forum/topic.jinja
+++ b/forum/templates/forum/topic.jinja
@@ -35,6 +35,7 @@
>
{{ topic }}
{{ topic.title }}
+
{% endblock %}
diff --git a/forum/views.py b/forum/views.py
index 5ef4ff5b..87005b87 100644
--- a/forum/views.py
+++ b/forum/views.py
@@ -59,6 +59,7 @@ class ForumMarkAllAsRead(RedirectView):
class ForumLastUnread(ListView):
model = ForumTopic
template_name = "forum/last_unread.jinja"
+ paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2
def get_queryset(self):
l = ForumMessage.objects.exclude(readers=self.request.user).filter(
@@ -117,7 +118,16 @@ class ForumDetailView(CanViewMixin, DetailView):
def get_context_data(self, **kwargs):
kwargs = super(ForumDetailView, self).get_context_data(**kwargs)
- kwargs['topics'] = self.object.topics.annotate(models.Max('messages__date')).order_by('-messages__date__max')
+ qs = self.object.topics.order_by('-_last_message__date').select_related('_last_message')
+ paginator = Paginator(qs,
+ settings.SITH_FORUM_PAGE_LENGTH)
+ page = self.request.GET.get('topic_page')
+ try:
+ kwargs["topics"] = paginator.page(page)
+ except PageNotAnInteger:
+ kwargs["topics"] = paginator.page(1)
+ except EmptyPage:
+ kwargs["topics"] = paginator.page(paginator.num_pages)
return kwargs
class TopicForm(forms.ModelForm):
@@ -164,7 +174,7 @@ class ForumTopicDetailView(CanViewMixin, DetailView):
kwargs['first_unread_message_id'] = msg.id
except:
kwargs['first_unread_message_id'] = float("inf")
- paginator = Paginator(self.object.messages.select_related('author__avatar_pict').all(),
+ paginator = Paginator(self.object.messages.select_related('author__avatar_pict').order_by('date'),
settings.SITH_FORUM_PAGE_LENGTH)
page = self.request.GET.get('page')
try:
From 97a39b0652477581671d4bdfd51ca5bd3296714e Mon Sep 17 00:00:00 2001
From: Skia
Date: Sat, 20 May 2017 12:47:48 +0200
Subject: [PATCH 05/16] Make **a lot** of Forum improvements in reducing the
number of queries per page
---
forum/admin.py | 1 +
forum/migrations/0004_auto_20170520_1235.py | 57 +++++++++
forum/models.py | 128 ++++++++++++++------
forum/urls.py | 1 +
forum/views.py | 21 +++-
5 files changed, 170 insertions(+), 38 deletions(-)
create mode 100644 forum/migrations/0004_auto_20170520_1235.py
diff --git a/forum/admin.py b/forum/admin.py
index 5db34b10..148e47ce 100644
--- a/forum/admin.py
+++ b/forum/admin.py
@@ -29,3 +29,4 @@ from forum.models import *
admin.site.register(Forum)
admin.site.register(ForumTopic)
admin.site.register(ForumMessage)
+admin.site.register(ForumUserInfo)
diff --git a/forum/migrations/0004_auto_20170520_1235.py b/forum/migrations/0004_auto_20170520_1235.py
new file mode 100644
index 00000000..9dcb408d
--- /dev/null
+++ b/forum/migrations/0004_auto_20170520_1235.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('forum', '0003_auto_20170510_1754'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='forummessage',
+ options={'ordering': ['-date']},
+ ),
+ migrations.AlterModelOptions(
+ name='forumtopic',
+ options={'ordering': ['-_last_message__date']},
+ ),
+ migrations.AddField(
+ model_name='forum',
+ name='_last_message',
+ field=models.ForeignKey(related_name='forums_where_its_last', to='forum.ForumMessage', null=True, verbose_name='the last message'),
+ ),
+ migrations.AddField(
+ model_name='forum',
+ name='_topic_number',
+ field=models.IntegerField(default=0, verbose_name='number of topics'),
+ ),
+ migrations.AddField(
+ model_name='forummessage',
+ name='_deleted',
+ field=models.BooleanField(default=False, verbose_name='is deleted'),
+ ),
+ migrations.AddField(
+ model_name='forumtopic',
+ name='_last_message',
+ field=models.ForeignKey(related_name='+', to='forum.ForumMessage', null=True, verbose_name='the last message'),
+ ),
+ migrations.AddField(
+ model_name='forumtopic',
+ name='_title',
+ field=models.CharField(max_length=64, blank=True, verbose_name='title'),
+ ),
+ migrations.AlterField(
+ model_name='forum',
+ name='description',
+ field=models.CharField(max_length=512, default='', verbose_name='description'),
+ ),
+ migrations.AlterField(
+ model_name='forum',
+ name='id',
+ field=models.AutoField(primary_key=True, serialize=False, db_index=True),
+ ),
+ ]
diff --git a/forum/models.py b/forum/models.py
index 234b477d..dad33469 100644
--- a/forum/models.py
+++ b/forum/models.py
@@ -46,8 +46,9 @@ class Forum(models.Model):
edit_groups allows to put any group as a forum admin
view_groups allows some groups to view a forum
"""
+ id = models.AutoField(primary_key=True, db_index=True)
name = models.CharField(_('name'), max_length=64)
- description = models.CharField(_('description'), max_length=256, default="")
+ description = models.CharField(_('description'), max_length=512, default="")
is_category = models.BooleanField(_('is a category'), default=False)
parent = models.ForeignKey('Forum', related_name='children', null=True, blank=True)
owner_club = models.ForeignKey(Club, related_name="owned_forums", verbose_name=_("owner club"),
@@ -57,6 +58,8 @@ class Forum(models.Model):
view_groups = models.ManyToManyField(Group, related_name="viewable_forums", blank=True,
default=[settings.SITH_GROUP_PUBLIC_ID])
number = models.IntegerField(_("number to choose a specific forum ordering"), default=1)
+ _last_message = models.ForeignKey('ForumMessage', related_name="forums_where_its_last", verbose_name=_("the last message"), null=True)
+ _topic_number = models.IntegerField(_("number of topics"), default=0)
class Meta:
ordering = ['number']
@@ -72,6 +75,28 @@ class Forum(models.Model):
if copy_rights:
self.copy_rights()
+ def set_topic_number(self):
+ self._topic_number = self.get_topic_number()
+ self.save()
+ if self.parent:
+ self.parent.set_topic_number()
+
+ def set_last_message(self):
+ topic = ForumTopic.objects.filter(forum__id=self.id).exclude(_last_message=None).order_by('-_last_message__id').first()
+ forum = Forum.objects.filter(parent__id=self.id).exclude(_last_message=None).order_by('-_last_message__id').first()
+ if topic and forum:
+ if topic._last_message_id < forum._last_message_id:
+ self._last_message_id = forum._last_message_id
+ else:
+ self._last_message_id = topic._last_message_id
+ elif topic:
+ self._last_message_id = topic._last_message_id
+ elif forum:
+ self._last_message_id = forum._last_message_id
+ self.save()
+ if self.parent:
+ self.parent.set_last_message()
+
def apply_rights_recursively(self):
children = self.children.all()
for c in children:
@@ -86,10 +111,19 @@ class Forum(models.Model):
self.view_groups = self.parent.view_groups.all()
self.save()
+ _club_memberships = {} # This cache is particularly efficient:
+ # divided by 3 the number of requests on the main forum page
+ # after the first load
def is_owned_by(self, user):
if user.is_in_group(settings.SITH_GROUP_FORUM_ADMIN_ID):
return True
- m = self.owner_club.get_membership_for(user)
+ try: m = Forum._club_memberships[self.id][user.id]
+ except:
+ m = self.owner_club.get_membership_for(user)
+ try: Forum._club_memberships[self.id][user.id] = m
+ except:
+ Forum._club_memberships[self.id] = {}
+ Forum._club_memberships[self.id][user.id] = m
if m:
return m.role > settings.SITH_MAXIMUM_FREE_ROLE
return False
@@ -122,9 +156,9 @@ class Forum(models.Model):
p = p.parent
return l
- @cached_property
+ @property
def topic_number(self):
- return self.get_topic_number()
+ return self._topic_number
def get_topic_number(self):
number = self.topics.all().count()
@@ -134,31 +168,29 @@ class Forum(models.Model):
@cached_property
def last_message(self):
- return self.get_last_message()
+ return self._last_message
- forum_list = {} # Class variable used for cache purpose
- def get_last_message(self):
- last_msg = None
- for m in ForumMessage.objects.select_related('topic__forum').order_by('-id'):
- if m.topic.forum.id in Forum.forum_list.keys(): # The object is already in Python's memory,
- # so there's no need to query it again
- forum = Forum.forum_list[m.topic.forum.id]
- else: # Query the forum object and store it in the class variable for further use.
- # Keeping the same object allows the @cached_property to work properly.
-# This trick divided by 4 the number of DB queries in the main forum page, and about the same on many other forum pages.
-# This also divided by 4 the amount of CPU usage for thoses pages, according to Django Debug Toolbar.
- forum = m.topic.forum
- Forum.forum_list[forum.id] = forum
- if self in (forum.parent_list + [forum]) and not m.deleted:
- return m
+ def get_children_list(self):
+ l = [self.id]
+ for c in self.children.all():
+ l.append(c.id)
+ l += c.get_children_list()
+ return l
class ForumTopic(models.Model):
forum = models.ForeignKey(Forum, related_name='topics')
author = models.ForeignKey(User, related_name='forum_topics')
description = models.CharField(_('description'), max_length=256, default="")
+ _last_message = models.ForeignKey('ForumMessage', related_name="+", verbose_name=_("the last message"), null=True)
+ _title = models.CharField(_('title'), max_length=64, blank=True)
class Meta:
- ordering = ['-id'] # TODO: add date message ordering
+ ordering = ['-_last_message__date']
+
+ def save(self, *args, **kwargs):
+ super(ForumTopic, self).save(*args, **kwargs)
+ self.forum.set_topic_number() # Recompute the cached value
+ self.forum.set_last_message()
def is_owned_by(self, user):
return self.forum.is_owned_by(user)
@@ -184,13 +216,16 @@ class ForumTopic(models.Model):
@cached_property
def last_message(self):
- for msg in self.messages.order_by('id').select_related('author').order_by('-id').all():
- if not msg.deleted:
- return msg
+ return self._last_message
- @property
+ @cached_property
def title(self):
- return self.messages.order_by('date').first().title
+ if self._title:
+ return self._title
+ else:
+ self._title = self.messages.order_by('id').first().title
+ self.save()
+ return self._title
class ForumMessage(models.Model):
"""
@@ -202,12 +237,29 @@ class ForumMessage(models.Model):
message = models.TextField(_("message"), default="")
date = models.DateTimeField(_('date'), default=timezone.now)
readers = models.ManyToManyField(User, related_name="read_messages", verbose_name=_("readers"))
+ _deleted = models.BooleanField(_('is deleted'), default=False)
class Meta:
- ordering = ['id']
+ ordering = ['-date']
def __str__(self):
- return "%s - %s" % (self.id, self.title)
+ return "%s (%s) - %s" % (self.id, self.author, self.title)
+
+ def save(self, *args, **kwargs):
+ self._deleted = self.is_deleted() # Recompute the cached value
+ super(ForumMessage, self).save(*args, **kwargs)
+ if self.is_last_in_topic():
+ self.topic._last_message_id = self.id
+ self.topic.save()
+ if self.is_first_in_topic():
+ self.topic._title = self.title
+ self.topic.save()
+
+ def is_first_in_topic(self):
+ return bool(self.id == self.topic.messages.order_by('date').first().id)
+
+ def is_last_in_topic(self):
+ return bool(self.id == self.topic.messages.order_by('date').last().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
@@ -217,12 +269,15 @@ class ForumMessage(models.Model):
return user.can_edit(self.topic.forum)
def can_be_viewed_by(self, user):
- return (not self.deleted and user.can_view(self.topic))
+ return not self._deleted # No need to check the real rights since it's already done by the Topic view
def can_be_moderated_by(self, user):
return self.topic.forum.is_owned_by(user) or user.id == self.author.id
def get_absolute_url(self):
+ return reverse('forum:view_message', kwargs={'message_id': self.id})
+
+ def get_url(self):
return self.topic.get_absolute_url() + "?page=" + str(self.get_page()) + "#msg_" + str(self.id)
def get_page(self):
@@ -236,10 +291,6 @@ class ForumMessage(models.Model):
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:
@@ -258,13 +309,22 @@ class ForumMessageMeta(models.Model):
date = models.DateTimeField(_('date'), default=timezone.now)
action = models.CharField(_("action"), choices=MESSAGE_META_ACTIONS, max_length=16)
+ def save(self, *args, **kwargs):
+ super(ForumMessageMeta, self).save(*args, **kwargs)
+ self.message._deleted = self.message.is_deleted()
+ self.message.save()
+
+
class ForumUserInfo(models.Model):
"""
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
+ user = models.OneToOneField(User, related_name="_forum_infos")
last_read_date = models.DateTimeField(_('last read date'), default=datetime(year=settings.SITH_SCHOOL_START_YEAR,
month=1, day=1, tzinfo=pytz.UTC))
+ def __str__(self):
+ return str(self.user)
+
diff --git a/forum/urls.py b/forum/urls.py
index 7fabc688..9096e3cb 100644
--- a/forum/urls.py
+++ b/forum/urls.py
@@ -38,6 +38,7 @@ urlpatterns = [
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'^message/(?P[0-9]+)$', ForumMessageView.as_view(), name='view_message'),
url(r'^message/(?P[0-9]+)/edit$', ForumMessageEditView.as_view(), name='edit_message'),
url(r'^message/(?P[0-9]+)/delete$', ForumMessageDeleteView.as_view(), name='delete_message'),
url(r'^message/(?P[0-9]+)/undelete$', ForumMessageUndeleteView.as_view(), name='undelete_message'),
diff --git a/forum/views.py b/forum/views.py
index 87005b87..157ab624 100644
--- a/forum/views.py
+++ b/forum/views.py
@@ -41,7 +41,7 @@ from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMi
from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta
class ForumMainView(ListView):
- queryset = Forum.objects.filter(parent=None)
+ queryset = Forum.objects.filter(parent=None).select_related("_last_message__author", "_last_message__topic___title")
template_name = "forum/main.jinja"
class ForumMarkAllAsRead(RedirectView):
@@ -53,6 +53,8 @@ class ForumMarkAllAsRead(RedirectView):
fi = request.user.forum_infos
fi.last_read_date = timezone.now()
fi.save()
+ for m in request.user.read_messages.filter(date__lt=fi.last_read_date):
+ m.readers.remove(request.user) # Clean up to keep table low in data
except: pass
return super(ForumMarkAllAsRead, self).get(request, *args, **kwargs)
@@ -62,9 +64,11 @@ class ForumLastUnread(ListView):
paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2
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').select_related('author')
+ topic_list = self.model.objects.filter(_last_message__date__gt=self.request.user.forum_infos.last_read_date)\
+ .exclude(_last_message__readers=self.request.user)\
+ .order_by('-_last_message__date')\
+ .select_related('_last_message__author__avatar_pict')
+ return topic_list
class ForumForm(forms.ModelForm):
class Meta:
@@ -185,6 +189,15 @@ class ForumTopicDetailView(CanViewMixin, DetailView):
kwargs["msgs"] = paginator.page(paginator.num_pages)
return kwargs
+class ForumMessageView(SingleObjectMixin, RedirectView):
+ model = ForumMessage
+ pk_url_kwarg = "message_id"
+ permanent = False
+
+ def get_redirect_url(self, *args, **kwargs):
+ self.object = self.get_object()
+ return self.object.get_url()
+
class ForumMessageEditView(CanEditMixin, UpdateView):
model = ForumMessage
fields = ['title', 'message']
From 1f0a34fb6c56d67dd82bac145659edea576cbc40 Mon Sep 17 00:00:00 2001
From: Skia
Date: Sat, 20 May 2017 12:48:06 +0200
Subject: [PATCH 06/16] Finish the Forum migrate script
---
migrate.py | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 151 insertions(+), 1 deletion(-)
diff --git a/migrate.py b/migrate.py
index e0ab9269..e44bcc91 100644
--- a/migrate.py
+++ b/migrate.py
@@ -45,12 +45,14 @@ from django.core.files import File
from core.models import User, SithFile
+from core.utils import doku_to_markdown
from club.models import Club, Membership
from counter.models import Customer, Counter, Selling, Refilling, Product, ProductType, Permanency, Eticket
from subscription.models import Subscription
from eboutic.models import Invoice, InvoiceItem
from accounting.models import BankAccount, ClubAccount, GeneralJournal, Operation, AccountingType, Company, SimplifiedAccountingType, Label
from sas.models import Album, Picture, PeoplePictureRelation
+from forum.models import Forum, ForumTopic, ForumMessage, ForumMessageMeta, ForumUserInfo
db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS)
start = datetime.datetime.now()
@@ -1181,6 +1183,152 @@ def reset_sas_moderators():
except Exception as e:
print(repr(e))
+def migrate_forum():
+ print("Migrating forum")
+ def migrate_forums():
+ cur = db.cursor(MySQLdb.cursors.SSDictCursor)
+ print(" Cleaning up forums")
+ Forum.objects.all().delete()
+ cur.execute("""
+ SELECT *
+ FROM frm_forum
+ WHERE id_forum <> 1
+ """)
+ print(" Migrating forums")
+ for r in cur:
+ try:
+ # parent = Forum.objects.filter(id=r['id_forum_parent']).first()
+ club = Club.objects.filter(id=r['id_asso']).first()
+ ae = Club.objects.filter(id=settings.SITH_MAIN_CLUB_ID).first()
+ forum = Forum(
+ id=r['id_forum'],
+ name=to_unicode(r['titre_forum']),
+ description=to_unicode(r['description_forum'])[:511],
+ is_category=bool(r['categorie_forum']),
+ # parent=parent,
+ owner_club=club or ae,
+ number=r['ordre_forum'],
+ )
+ forum.save()
+ except Exception as e:
+ print(" FAIL to migrate forum: %s" % (repr(e)))
+ cur.execute("""
+ SELECT *
+ FROM frm_forum
+ WHERE id_forum_parent <> 1
+ """)
+ for r in cur:
+ parent = Forum.objects.filter(id=r['id_forum_parent']).first()
+ forum = Forum.objects.filter(id=r['id_forum']).first()
+ forum.parent = parent
+ forum.save()
+ cur.close()
+ print(" Forums migrated at %s" % datetime.datetime.now())
+ print(" Running time: %s" % (datetime.datetime.now()-start))
+
+ def migrate_topics():
+ cur = db.cursor(MySQLdb.cursors.SSDictCursor)
+ print(" Cleaning up topics")
+ ForumTopic.objects.all().delete()
+ cur.execute("""
+ SELECT *
+ FROM frm_sujet
+ """)
+ print(" Migrating topics")
+ for r in cur:
+ try:
+ parent = Forum.objects.filter(id=r['id_forum']).first()
+ saloon = Forum.objects.filter(id=3).first()
+ author = User.objects.filter(id=r['id_utilisateur']).first()
+ root = User.objects.filter(id=0).first()
+ topic = ForumTopic(
+ id=r['id_sujet'],
+ author=author or root,
+ forum=parent or saloon,
+ _title=to_unicode(r['titre_sujet']),
+ description=to_unicode(r['soustitre_sujet']),
+ )
+ topic.save()
+ except Exception as e:
+ print(" FAIL to migrate topic: %s" % (repr(e)))
+ cur.close()
+ print(" Topics migrated at %s" % datetime.datetime.now())
+ print(" Running time: %s" % (datetime.datetime.now()-start))
+
+ def migrate_messages():
+ cur = db.cursor(MySQLdb.cursors.SSDictCursor)
+ print(" Cleaning up messages")
+ ForumMessage.objects.all().delete()
+ cur.execute("""
+ SELECT *
+ FROM frm_message
+ """)
+ print(" Migrating messages")
+ for r in cur:
+ try:
+ topic = ForumTopic.objects.filter(id=r['id_sujet']).first()
+ author = User.objects.filter(id=r['id_utilisateur']).first()
+ root = User.objects.filter(id=0).first()
+ msg = ForumMessage(
+ id=r['id_message'],
+ topic=topic,
+ author=author or root,
+ title=to_unicode(r['titre_message'])[:63],
+ date=r['date_message'].replace(tzinfo=timezone('Europe/Paris')),
+ )
+ if r['syntaxengine_message'] == "doku":
+ msg.message = doku_to_markdown(to_unicode(r['contenu_message']))
+ else:
+ msg.message = to_unicode(r['contenu_message'])
+ msg.save()
+ except Exception as e:
+ print(" FAIL to migrate message: %s" % (repr(e)))
+ cur.close()
+ print(" Messages migrated at %s" % datetime.datetime.now())
+ print(" Running time: %s" % (datetime.datetime.now()-start))
+
+ def migrate_message_infos():
+ cur = db.cursor(MySQLdb.cursors.SSDictCursor)
+ print(" Cleaning up message meta")
+ ForumMessageMeta.objects.all().delete()
+ cur.execute("""
+ SELECT *
+ FROM frm_modere_info
+ """)
+ print(" Migrating message meta")
+ ACTIONS = {
+ "EDIT": "EDIT",
+ "AUTOEDIT": "EDIT",
+ "UNDELETE": "UNDELETE",
+ "DELETE": "DELETE",
+ "DELETEFIRST": "DELETE",
+ "AUTODELETE": "DELETE",
+ }
+ for r in cur:
+ try:
+ msg = ForumMessage.objects.filter(id=r['id_message']).first()
+ author = User.objects.filter(id=r['id_utilisateur']).first()
+ root = User.objects.filter(id=0).first()
+ meta = ForumMessageMeta(
+ message=msg,
+ user=author or root,
+ date=r['modere_date'].replace(tzinfo=timezone('Europe/Paris')),
+ action=ACTIONS[r['modere_action']],
+ )
+ meta.save()
+ except Exception as e:
+ print(" FAIL to migrate message meta: %s" % (repr(e)))
+ cur.close()
+ print(" Messages meta migrated at %s" % datetime.datetime.now())
+ print(" Running time: %s" % (datetime.datetime.now()-start))
+
+ migrate_forums()
+ migrate_topics()
+ migrate_messages()
+ migrate_message_infos()
+ print("Forum migrated at %s" % datetime.datetime.now())
+ print("Running time: %s" % (datetime.datetime.now()-start))
+
def main():
print("Start at %s" % start)
# Core
@@ -1199,7 +1347,9 @@ def main():
# reset_index('core', 'club', 'subscription', 'accounting', 'eboutic', 'launderette', 'counter')
# migrate_sas()
# reset_index('core', 'sas')
- reset_sas_moderators()
+ # reset_sas_moderators()
+ migrate_forum()
+ reset_index('forum')
end = datetime.datetime.now()
print("End at %s" % end)
print("Running time: %s" % (end-start))
From ba65dc5d46a65fc0d779503881f60c3b699ecb8a Mon Sep 17 00:00:00 2001
From: Skia
Date: Tue, 30 May 2017 18:42:01 +0200
Subject: [PATCH 07/16] Fix doku_to_markdown
Signed-off-by: Skia
---
core/utils.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/core/utils.py b/core/utils.py
index 970ba9e0..c0266821 100644
--- a/core/utils.py
+++ b/core/utils.py
@@ -117,14 +117,17 @@ def doku_to_markdown(text):
quote_level += 1
try:
new_text.append("> " * quote_level + "##### " + quote.group(2))
- line = line.replace(quote.group(0), '')
except:
new_text.append("> " * quote_level)
+ line = line.replace(quote.group(0), '')
final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration
+ final_newline = False
for quote in quit: # Quit quotes (support multiple at a time)
line = line.replace(quote.group(0), '')
quote_level -= 1
+ final_newline = True
new_text.append("> " * final_quote_level + line) # Finally append the line
+ if final_newline: new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text
else:
new_text.append(line)
From 06b67f1d27b72b54d4ff08d09d5d20ae5429aad1 Mon Sep 17 00:00:00 2001
From: Skia
Date: Tue, 30 May 2017 22:10:50 +0200
Subject: [PATCH 08/16] Still reducing the number of queries on the Forum
Signed-off-by: Skia
---
...520_1235.py => 0004_auto_20170530_2058.py} | 19 ++++++++++++-------
forum/models.py | 15 ++++++---------
forum/templates/forum/forum.jinja | 2 +-
forum/templates/forum/macros.jinja | 2 +-
forum/views.py | 8 +++++---
migrate.py | 2 +-
6 files changed, 26 insertions(+), 22 deletions(-)
rename forum/migrations/{0004_auto_20170520_1235.py => 0004_auto_20170530_2058.py} (62%)
diff --git a/forum/migrations/0004_auto_20170520_1235.py b/forum/migrations/0004_auto_20170530_2058.py
similarity index 62%
rename from forum/migrations/0004_auto_20170520_1235.py
rename to forum/migrations/0004_auto_20170530_2058.py
index 9dcb408d..d236d5ac 100644
--- a/forum/migrations/0004_auto_20170520_1235.py
+++ b/forum/migrations/0004_auto_20170530_2058.py
@@ -22,36 +22,41 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='forum',
name='_last_message',
- field=models.ForeignKey(related_name='forums_where_its_last', to='forum.ForumMessage', null=True, verbose_name='the last message'),
+ field=models.ForeignKey(related_name='forums_where_its_last', null=True, to='forum.ForumMessage', verbose_name='the last message'),
),
migrations.AddField(
model_name='forum',
name='_topic_number',
- field=models.IntegerField(default=0, verbose_name='number of topics'),
+ field=models.IntegerField(verbose_name='number of topics', default=0),
),
migrations.AddField(
model_name='forummessage',
name='_deleted',
- field=models.BooleanField(default=False, verbose_name='is deleted'),
+ field=models.BooleanField(verbose_name='is deleted', default=False),
),
migrations.AddField(
model_name='forumtopic',
name='_last_message',
- field=models.ForeignKey(related_name='+', to='forum.ForumMessage', null=True, verbose_name='the last message'),
+ field=models.ForeignKey(related_name='+', null=True, to='forum.ForumMessage', verbose_name='the last message'),
+ ),
+ migrations.AddField(
+ model_name='forumtopic',
+ name='_message_number',
+ field=models.IntegerField(verbose_name='number of messages', default=0),
),
migrations.AddField(
model_name='forumtopic',
name='_title',
- field=models.CharField(max_length=64, blank=True, verbose_name='title'),
+ field=models.CharField(max_length=64, verbose_name='title', blank=True),
),
migrations.AlterField(
model_name='forum',
name='description',
- field=models.CharField(max_length=512, default='', verbose_name='description'),
+ field=models.CharField(max_length=512, verbose_name='description', default=''),
),
migrations.AlterField(
model_name='forum',
name='id',
- field=models.AutoField(primary_key=True, serialize=False, db_index=True),
+ field=models.AutoField(serialize=False, db_index=True, primary_key=True),
),
]
diff --git a/forum/models.py b/forum/models.py
index dad33469..2ddcc061 100644
--- a/forum/models.py
+++ b/forum/models.py
@@ -183,6 +183,7 @@ class ForumTopic(models.Model):
description = models.CharField(_('description'), max_length=256, default="")
_last_message = models.ForeignKey('ForumMessage', related_name="+", verbose_name=_("the last message"), null=True)
_title = models.CharField(_('title'), max_length=64, blank=True)
+ _message_number = models.IntegerField(_("number of messages"), default=0)
class Meta:
ordering = ['-_last_message__date']
@@ -220,12 +221,7 @@ class ForumTopic(models.Model):
@cached_property
def title(self):
- if self._title:
- return self._title
- else:
- self._title = self.messages.order_by('id').first().title
- self.save()
- return self._title
+ return self._title
class ForumMessage(models.Model):
"""
@@ -250,10 +246,10 @@ class ForumMessage(models.Model):
super(ForumMessage, self).save(*args, **kwargs)
if self.is_last_in_topic():
self.topic._last_message_id = self.id
- self.topic.save()
if self.is_first_in_topic():
self.topic._title = self.title
- self.topic.save()
+ self.topic._message_number = self.topic.messages.count()
+ self.topic.save()
def is_first_in_topic(self):
return bool(self.id == self.topic.messages.order_by('date').first().id)
@@ -285,7 +281,8 @@ class ForumMessage(models.Model):
def mark_as_read(self, user):
try: # Need the try/except because of AnonymousUser
- self.readers.add(user)
+ if not self.is_read(user):
+ self.readers.add(user)
except: pass
def is_read(self, user):
diff --git a/forum/templates/forum/forum.jinja b/forum/templates/forum/forum.jinja
index 6d4886fe..f3c3c8c6 100644
--- a/forum/templates/forum/forum.jinja
+++ b/forum/templates/forum/forum.jinja
@@ -36,7 +36,7 @@
{{ display_forum(forum, user, True) }}
- {% for f in forum.children.all() %}
+ {% for f in forum.children.all().select_related("_last_message__author", "_last_message__topic") %}
{{ display_forum(f, user) }}
{% endfor %}
{% endif %}
diff --git a/forum/templates/forum/macros.jinja b/forum/templates/forum/macros.jinja
index b89ce4c2..c423bba4 100644
--- a/forum/templates/forum/macros.jinja
+++ b/forum/templates/forum/macros.jinja
@@ -69,7 +69,7 @@
{{ user_profile_link(topic.author) }}
- {{ topic.messages.count() }}
+ {{ topic._message_number }}
diff --git a/forum/views.py b/forum/views.py
index 157ab624..89374e36 100644
--- a/forum/views.py
+++ b/forum/views.py
@@ -41,7 +41,7 @@ from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMi
from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta
class ForumMainView(ListView):
- queryset = Forum.objects.filter(parent=None).select_related("_last_message__author", "_last_message__topic___title")
+ queryset = Forum.objects.filter(parent=None).prefetch_related("children___last_message__author", "children___last_message__topic")
template_name = "forum/main.jinja"
class ForumMarkAllAsRead(RedirectView):
@@ -122,7 +122,8 @@ class ForumDetailView(CanViewMixin, DetailView):
def get_context_data(self, **kwargs):
kwargs = super(ForumDetailView, self).get_context_data(**kwargs)
- qs = self.object.topics.order_by('-_last_message__date').select_related('_last_message')
+ qs = self.object.topics.order_by('-_last_message__date').select_related('_last_message__author', 'author')\
+ .prefetch_related("forum__edit_groups")
paginator = Paginator(qs,
settings.SITH_FORUM_PAGE_LENGTH)
page = self.request.GET.get('topic_page')
@@ -178,7 +179,8 @@ class ForumTopicDetailView(CanViewMixin, DetailView):
kwargs['first_unread_message_id'] = msg.id
except:
kwargs['first_unread_message_id'] = float("inf")
- paginator = Paginator(self.object.messages.select_related('author__avatar_pict').order_by('date'),
+ paginator = Paginator(self.object.messages.select_related('author__avatar_pict')\
+ .prefetch_related('topic__forum__edit_groups', 'readers').order_by('date'),
settings.SITH_FORUM_PAGE_LENGTH)
page = self.request.GET.get('page')
try:
diff --git a/migrate.py b/migrate.py
index e44bcc91..a0c420eb 100644
--- a/migrate.py
+++ b/migrate.py
@@ -1245,7 +1245,7 @@ def migrate_forum():
id=r['id_sujet'],
author=author or root,
forum=parent or saloon,
- _title=to_unicode(r['titre_sujet']),
+ _title=to_unicode(r['titre_sujet'])[:64],
description=to_unicode(r['soustitre_sujet']),
)
topic.save()
From 32ac6640ab181d01227f51e37e3d67599d0b1fe4 Mon Sep 17 00:00:00 2001
From: Skia
Date: Tue, 30 May 2017 23:23:51 +0200
Subject: [PATCH 09/16] Small fix with forum topic titles
Signed-off-by: Skia
---
forum/models.py | 2 +-
forum/templates/forum/macros.jinja | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/forum/models.py b/forum/models.py
index 2ddcc061..9f0f76e5 100644
--- a/forum/models.py
+++ b/forum/models.py
@@ -246,7 +246,7 @@ class ForumMessage(models.Model):
super(ForumMessage, self).save(*args, **kwargs)
if self.is_last_in_topic():
self.topic._last_message_id = self.id
- if self.is_first_in_topic():
+ if self.is_first_in_topic() and self.title:
self.topic._title = self.title
self.topic._message_number = self.topic.messages.count()
self.topic.save()
diff --git a/forum/templates/forum/macros.jinja b/forum/templates/forum/macros.jinja
index c423bba4..159a1cbf 100644
--- a/forum/templates/forum/macros.jinja
+++ b/forum/templates/forum/macros.jinja
@@ -54,7 +54,7 @@
{% else %}
{% endif %}
- {{ topic.title }}
+ {{ topic.title or topic.messages.first().title }}
{{ topic.description }}
{% if user.can_edit(topic) %}
From 136d0f3fa0f4f714945f73df54558bf35b1b1271 Mon Sep 17 00:00:00 2001
From: Skia
Date: Tue, 30 May 2017 19:29:35 +0200
Subject: [PATCH 10/16] Add basic BBcode translator
Signed-off-by: Skia
---
...ku_to_markdown.jinja => to_markdown.jinja} | 4 +-
core/templates/core/user_tools.jinja | 2 +-
core/urls.py | 2 +-
core/utils.py | 42 ++
core/views/site.py | 13 +-
locale/fr/LC_MESSAGES/django.po | 359 +++++++++---------
migrate.py | 4 +-
7 files changed, 245 insertions(+), 181 deletions(-)
rename core/templates/core/{doku_to_markdown.jinja => to_markdown.jinja} (70%)
diff --git a/core/templates/core/doku_to_markdown.jinja b/core/templates/core/to_markdown.jinja
similarity index 70%
rename from core/templates/core/doku_to_markdown.jinja
rename to core/templates/core/to_markdown.jinja
index ede16ee5..85ef0a71 100644
--- a/core/templates/core/doku_to_markdown.jinja
+++ b/core/templates/core/to_markdown.jinja
@@ -1,12 +1,14 @@
{% extends "core/base.jinja" %}
{% block title %}
-{% trans %}Doku to Markdown{% endtrans %}
+{% trans %}To Markdown{% endtrans %}
{% endblock %}
{% block content %}
+
{% trans %}Forum{% endtrans %}
{% trans %}Last unread messages{% endtrans %}
@@ -26,6 +27,7 @@
{% endfor %}
+
{% endblock %}
diff --git a/forum/templates/forum/macros.jinja b/forum/templates/forum/macros.jinja
index 159a1cbf..59e13196 100644
--- a/forum/templates/forum/macros.jinja
+++ b/forum/templates/forum/macros.jinja
@@ -99,8 +99,18 @@