From e689f7f1db4eae04b673e008ea042cbbab007b2b Mon Sep 17 00:00:00 2001 From: Skia Date: Sat, 20 May 2017 12:36:18 +0200 Subject: [PATCH 01/16] Add index and query reduction in clubs --- club/migrations/0008_auto_20170515_2214.py | 19 +++++++++++++++++++ club/models.py | 13 ++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 club/migrations/0008_auto_20170515_2214.py diff --git a/club/migrations/0008_auto_20170515_2214.py b/club/migrations/0008_auto_20170515_2214.py new file mode 100644 index 00000000..48f5b3b6 --- /dev/null +++ b/club/migrations/0008_auto_20170515_2214.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('club', '0007_auto_20170324_0917'), + ] + + operations = [ + migrations.AlterField( + model_name='club', + name='id', + field=models.AutoField(primary_key=True, serialize=False, db_index=True), + ), + ] diff --git a/club/models.py b/club/models.py index 587dd56c..4f1f4852 100644 --- a/club/models.py +++ b/club/models.py @@ -39,6 +39,7 @@ class Club(models.Model): """ The Club class, made as a tree to allow nice tidy organization """ + id = models.AutoField(primary_key=True, db_index=True) name = models.CharField(_('name'), max_length=64) parent = models.ForeignKey('Club', related_name='children', null=True, blank=True) unix_name = models.CharField(_('unix name'), max_length=30, unique=True, @@ -151,11 +152,21 @@ class Club(models.Model): return False return sub.is_subscribed + _memberships = {} def get_membership_for(self, user): """ Returns the current membership the given user """ - return self.members.filter(user=user.id).filter(end_date=None).first() + try: + return Club._memberships[self.id][user.id] + except: + m = self.members.filter(user=user.id).filter(end_date=None).first() + try: + Club._memberships[self.id][user.id] = m + except: + Club._memberships[self.id] = {} + Club._memberships[self.id][user.id] = m + return m class Membership(models.Model): """ From cf062a35d302f4da32e62b0e2193b3962c6f4b47 Mon Sep 17 00:00:00 2001 From: Skia Date: Sat, 20 May 2017 12:37:18 +0200 Subject: [PATCH 02/16] Add query reductions in core --- core/models.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/core/models.py b/core/models.py index 809224a8..dcf1202e 100644 --- a/core/models.py +++ b/core/models.py @@ -223,14 +223,25 @@ class User(AbstractBaseUser): s = self.subscriptions.last() return s.is_valid_now() if s is not None else False + _club_memberships = {} + _group_names = {} + _group_ids = {} def is_in_group(self, group_name): """If the user is in the group passed in argument (as string or by id)""" group_id = 0 g = None if isinstance(group_name, int): # Handle the case where group_name is an ID - g = Group.objects.filter(id=group_name).first() + if group_name in User._group_ids.keys(): + g = User._group_ids[group_name] + else: + g = Group.objects.filter(id=group_name).first() + User._group_ids[group_name] = g else: - g = Group.objects.filter(name=group_name).first() + if group_name in User._group_names.keys(): + g = User._group_names[group_name] + else: + g = Group.objects.filter(name=group_name).first() + User._group_names[group_name] = g if g: group_name = g.name group_id = g.id @@ -245,18 +256,26 @@ class User(AbstractBaseUser): if group_name == settings.SITH_MAIN_MEMBERS_GROUP: # We check the subscription if asked return self.is_subscribed if group_name[-len(settings.SITH_BOARD_SUFFIX):] == settings.SITH_BOARD_SUFFIX: - from club.models import Club name = group_name[:-len(settings.SITH_BOARD_SUFFIX)] - c = Club.objects.filter(unix_name=name).first() - mem = c.get_membership_for(self) + if name in User._club_memberships.keys(): + mem = User._club_memberships[name] + else: + from club.models import Club + c = Club.objects.filter(unix_name=name).first() + mem = c.get_membership_for(self) + User._club_memberships[name] = mem if mem: return mem.role > settings.SITH_MAXIMUM_FREE_ROLE return False if group_name[-len(settings.SITH_MEMBER_SUFFIX):] == settings.SITH_MEMBER_SUFFIX: - from club.models import Club name = group_name[:-len(settings.SITH_MEMBER_SUFFIX)] - c = Club.objects.filter(unix_name=name).first() - mem = c.get_membership_for(self) + if name in User._club_memberships.keys(): + mem = User._club_memberships[name] + else: + from club.models import Club + c = Club.objects.filter(unix_name=name).first() + mem = c.get_membership_for(self) + User._club_memberships[name] = mem if mem: return True return False From ec307cd5df3d461933134590ef7cb843ebd35305 Mon Sep 17 00:00:00 2001 From: Skia Date: Sat, 20 May 2017 12:37:51 +0200 Subject: [PATCH 03/16] Add db index in counter --- counter/migrations/0012_auto_20170515_2202.py | 19 +++++++++++++++++++ counter/models.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 counter/migrations/0012_auto_20170515_2202.py diff --git a/counter/migrations/0012_auto_20170515_2202.py b/counter/migrations/0012_auto_20170515_2202.py new file mode 100644 index 00000000..8c2ead76 --- /dev/null +++ b/counter/migrations/0012_auto_20170515_2202.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('counter', '0011_auto_20161004_2039'), + ] + + operations = [ + migrations.AlterField( + model_name='permanency', + name='end', + field=models.DateTimeField(db_index=True, verbose_name='end date', null=True), + ), + ] diff --git a/counter/models.py b/counter/models.py index f45debd6..73582464 100644 --- a/counter/models.py +++ b/counter/models.py @@ -431,7 +431,7 @@ class Permanency(models.Model): user = models.ForeignKey(User, related_name="permanencies", verbose_name=_("user")) counter = models.ForeignKey(Counter, related_name="permanencies", verbose_name=_("counter")) start = models.DateTimeField(_('start date')) - end = models.DateTimeField(_('end date'), null=True) + end = models.DateTimeField(_('end date'), null=True, db_index=True) activity = models.DateTimeField(_('last activity date'), auto_now=True) class Meta: From d7135e4d2745926d1eb492c15c7a6ecc62e3a5dd Mon Sep 17 00:00:00 2001 From: Skia Date: Sat, 20 May 2017 12:40:30 +0200 Subject: [PATCH 04/16] Make a lot of pimp in the Forum --- core/static/core/style.scss | 218 +++++++++++++++--------- forum/templates/forum/forum.jinja | 12 +- forum/templates/forum/last_unread.jinja | 10 +- forum/templates/forum/macros.jinja | 84 ++++----- forum/templates/forum/main.jinja | 25 ++- forum/templates/forum/topic.jinja | 2 + forum/views.py | 14 +- 7 files changed, 231 insertions(+), 134 deletions(-) diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 5db187ad..dc5e4eeb 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -48,8 +48,8 @@ a { .ib { display: inline-block; - padding: 2px; - margin: 2px; + padding: 1px; + margin: 1px; } .w_big { @@ -57,11 +57,11 @@ a { } .w_medium { - width: 45%; + width: 47%; } .w_small { - width: 20%; + width: 23%; } /*--------------------------------HEADER-------------------------------*/ @@ -271,11 +271,15 @@ code { } blockquote { - margin: 10px; - padding: 5px; + margin: 5px; + padding: 2px; border: solid 1px $black-color; } +blockquote h5:first-child { + font-size: 100%; +} + .edit-bar { display: block; margin: 4px; @@ -498,86 +502,132 @@ textarea { /*------------------------------FORUM----------------------------------*/ -.topic a, .forum a, .category a { - color: $black-color; -} - -.topic a:hover, .forum a:hover, .category a:hover { - color: #424242; - text-decoration: underline; -} - -.topic { - border: solid $primary-neutral-color 1px; - padding: 2px; - margin: 2px; -} - -.forum { - background: $primary-neutral-light-color; - padding: 2px; - margin: 2px; -} - -.category { - background: $secondary-color; -} - -.message { - padding: 2px; - margin: 2px; - background: $white-color; - &:nth-child(odd) { - background: $primary-neutral-light-color; - } - h5 { - font-size: 100%; - } - &.unread { - background: #d8e7f3; - } -} - -.msg_author.deleted { - background: #ffcfcf; -} - -.msg_content { - &.deleted { - background: #ffefef; - } - display: inline-block; - width: 80%; - vertical-align: top; -} - -.msg_author { - display: inline-block; - width: 19%; - text-align: center; - background: $primary-light-color; - img { - max-width: 70%; - margin: 0px auto; - } -} - -.msg_meta { - font-size: small; - list-style-type: none; - li { - padding: 2px; - margin: 2px; - } -} - -.forum_signature { - color: #C0C0C0; - border-top: 1px solid #C0C0C0; +#forum { a { + color: $black-color; + } + + a:hover { + color: #424242; + text-decoration: underline; + } + + .topic { + border: solid $primary-neutral-color 1px; + padding: 1px; + margin: 1px; + p { + margin: 1px; + font-size: smaller; + } + } + + .tools { + font-size: x-small; + border: none; + a { + padding: 1px; + } + } + + .title { + font-size: small; + font-weight: bold; + padding: 2px; + } + + .last_message date { + white-space: nowrap; + } + + .last_message span { + white-space: nowrap; + text-overflow: ellipsis; + overflow:hidden; + width: 100%; + display: block; + } + + .forum { + background: $primary-neutral-light-color; + padding: 1px; + margin: 1px; + p { + margin: 1px; + font-size: smaller; + } + } + + .category { + margin-top: 5px; + background: $secondary-color; + .title { + text-transform: uppercase; + } + } + + .message { + padding: 1px; + margin: 1px; + background: $white-color; + &:nth-child(odd) { + background: $primary-neutral-light-color; + } + .title { + font-size: 100%; + } + &.unread { + background: #d8e7f3; + } + } + + .msg_author.deleted { + background: #ffcfcf; + } + + .msg_content { + &.deleted { + background: #ffefef; + } + display: inline-block; + width: 80%; + vertical-align: top; + } + + .msg_author { + display: inline-block; + width: 19%; + text-align: center; + background: $primary-light-color; + img { + max-width: 70%; + margin: 0px auto; + } + } + + .msg_header { + display: inline-block; + width: 100%; + font-size: small; + } + + .msg_meta { + font-size: small; + list-style-type: none; + li { + padding: 1px; + margin: 1px; + } + } + + .forum_signature { color: #C0C0C0; - &:hover { - text-decoration: underline; + border-top: 1px solid #C0C0C0; + a { + color: #C0C0C0; + &:hover { + text-decoration: underline; + } } } } diff --git a/forum/templates/forum/forum.jinja b/forum/templates/forum/forum.jinja index f6e4e1ae..6d4886fe 100644 --- a/forum/templates/forum/forum.jinja +++ b/forum/templates/forum/forum.jinja @@ -6,13 +6,14 @@ {% endblock %} {% block content %} -
+
{% trans %}Forum{% endtrans %} {% for f in forum.get_parent_list()|reverse %} > {{ f }} {% endfor %} > {{ forum }} -
+
+

{{ forum.name }}

{% if user.is_in_group(settings.SITH_GROUP_FORUM_ADMIN_ID) or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %} @@ -34,6 +35,7 @@

+ {{ display_forum(forum, user, True) }} {% for f in forum.children.all() %} {{ display_forum(f, user) }} {% endfor %} @@ -58,7 +60,13 @@ {% for t in topics %} {{ display_topic(t, user) }} {% endfor %} +

+ {% for p in topics.paginator.page_range %} + {{ p }} + {% endfor %} +

{% endif %} + {% endblock %} diff --git a/forum/templates/forum/last_unread.jinja b/forum/templates/forum/last_unread.jinja index 51fb1b7c..7489db2b 100644 --- a/forum/templates/forum/last_unread.jinja +++ b/forum/templates/forum/last_unread.jinja @@ -15,9 +15,17 @@ {% trans %}Mark all as read{% endtrans %} {% trans %}Refresh{% endtrans %}

- {% for t in forumtopic_list %} + {% for t in page_obj.object_list %} {{ display_topic(t, user, True) }} {% endfor %} + +

+ {% for p in paginator.page_range %} + + {{ p }} + + {% endfor %} +

{% endblock %} diff --git a/forum/templates/forum/macros.jinja b/forum/templates/forum/macros.jinja index d8b529f7..b89ce4c2 100644 --- a/forum/templates/forum/macros.jinja +++ b/forum/templates/forum/macros.jinja @@ -1,37 +1,43 @@ {% from 'core/macros.jinja' import user_profile_link %} -{% macro display_forum(forum, user) %} -
+{% macro display_forum(forum, user, is_root=False) %} +
- {% if not forum.is_category %} + {% if not is_root %}
-
+

{{ forum.topic_number }} -

-
+

+ @@ -48,33 +54,33 @@ {% else %} {% endif %} -
{{ topic.title }}
+
{{ topic.title }}

{{ topic.description }}

{% if user.can_edit(topic) %} - -
+
-
+

{{ user_profile_link(topic.author) }} -

-
+

+

{{ topic.messages.count() }} -

+

- +

{% endmacro %} @@ -92,26 +98,24 @@ {{ m.author.get_short_name() }}
-
- {% 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) }} +
+
{{ m.title }}
+
+ + {% 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 }}

+

{{ topic.description }}

{% trans %}Reply{% endtrans %}

@@ -62,6 +63,7 @@ {{ p }} {% endfor %}

+
{% 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 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 %}
{% csrf_token %} + Doku + BBCode diff --git a/core/templates/core/user_tools.jinja b/core/templates/core/user_tools.jinja index 878d5759..7de1a633 100644 --- a/core/templates/core/user_tools.jinja +++ b/core/templates/core/user_tools.jinja @@ -108,7 +108,7 @@

{% trans %}Other tools{% endtrans %}

diff --git a/core/urls.py b/core/urls.py index b5541dea..71d3ab01 100644 --- a/core/urls.py +++ b/core/urls.py @@ -28,7 +28,7 @@ from core.views import * urlpatterns = [ url(r'^$', index, name='index'), - url(r'^doku_to_markdown$', DokuToMarkdownView.as_view(), name='doku_to_markdown'), + url(r'^to_markdown$', ToMarkdownView.as_view(), name='to_markdown'), url(r'^notifications$', NotificationList.as_view(), name='notification_list'), url(r'^notification/(?P[0-9]+)$', notification, name='notification'), diff --git a/core/utils.py b/core/utils.py index c0266821..87e22f0d 100644 --- a/core/utils.py +++ b/core/utils.py @@ -66,6 +66,7 @@ def exif_auto_rotate(image): return image def doku_to_markdown(text): + """This is a quite correct doku translator""" text = re.sub(r'([^:]|^)\/\/(.*?)\/\/', r'*\2*', text) # Italic (prevents protocol:// conflict) text = re.sub(r'(.*?)<\/del>', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) text = re.sub(r'(.*?)<\/sup>', r'^\1^', text) # Superscript (multiline not supported, because almost never used) @@ -133,3 +134,44 @@ def doku_to_markdown(text): return "\n".join(new_text) +def bbcode_to_markdown(text): + """This is a very basic BBcode translator""" + text = re.sub(r'\[b\](.*?)\[\/b\]', r'**\1**', text, flags=re.DOTALL) # Bold + text = re.sub(r'\[i\](.*?)\[\/i\]', r'*\1*', text, flags=re.DOTALL) # Italic + text = re.sub(r'\[u\](.*?)\[\/u\]', r'__\1__', text, flags=re.DOTALL) # Underline + text = re.sub(r'\[s\](.*?)\[\/s\]', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) + text = re.sub(r'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2 + + text = re.sub(r'article://', r'page://', text) + text = re.sub(r'dfile://', r'file://', text) + + text = re.sub(r'\[url=(.*?)\](.*)\[\/url\]', r'[\2](\1)', text) # Links + text = re.sub(r'\[url\](.*)\[\/url\]', r'\1', text) # Links 2 + text = re.sub(r'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images + + new_text = [] + quote_level = 0 + for line in text.splitlines(): # Tables and quotes + enter = re.finditer(r'\[quote(=(.+?))?\]', line) + quit = re.finditer(r'\[/quote\]', line) + if enter or quit: # Quote part + for quote in enter: # Enter quotes (support multiple at a time) + quote_level += 1 + try: + new_text.append("> " * quote_level + "##### " + quote.group(2)) + 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) + + return "\n".join(new_text) + diff --git a/core/views/site.py b/core/views/site.py index f0f99096..e14571c7 100644 --- a/core/views/site.py +++ b/core/views/site.py @@ -37,7 +37,7 @@ from itertools import chain from haystack.query import SearchQuerySet from core.models import User, Notification -from core.utils import doku_to_markdown +from core.utils import doku_to_markdown, bbcode_to_markdown from club.models import Club def index(request, context=None): @@ -98,17 +98,20 @@ def search_json(request): } return JsonResponse(result) -class DokuToMarkdownView(TemplateView): - template_name = "core/doku_to_markdown.jinja" +class ToMarkdownView(TemplateView): + template_name = "core/to_markdown.jinja" def post(self, request, *args, **kwargs): self.text = request.POST['text'] - self.text_md = doku_to_markdown(self.text) + if request.POST['syntax'] == "doku": + self.text_md = doku_to_markdown(self.text) + else: + self.text_md = bbcode_to_markdown(self.text) context = self.get_context_data(**kwargs) return self.render_to_response(context) def get_context_data(self, **kwargs): - kwargs = super(DokuToMarkdownView, self).get_context_data(**kwargs) + kwargs = super(ToMarkdownView, self).get_context_data(**kwargs) try: kwargs['text'] = self.text kwargs['text_md'] = self.text_md diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 315cc8c0..902d7278 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-05-14 03:16+0200\n" +"POT-Creation-Date: 2017-05-30 19:28+0200\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -17,8 +17,8 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: accounting/models.py:60 accounting/models.py:108 accounting/models.py:135 -#: accounting/models.py:194 club/models.py:42 counter/models.py:99 -#: counter/models.py:124 counter/models.py:159 forum/models.py:49 +#: accounting/models.py:194 club/models.py:43 counter/models.py:100 +#: counter/models.py:125 counter/models.py:160 forum/models.py:50 #: launderette/models.py:37 launderette/models.py:82 launderette/models.py:107 #: stock/models.py:38 stock/models.py:54 stock/models.py:77 stock/models.py:97 msgid "name" @@ -64,9 +64,9 @@ msgstr "IBAN" msgid "account number" msgstr "numero de compte" -#: accounting/models.py:111 accounting/models.py:136 club/models.py:172 -#: com/models.py:64 com/models.py:152 counter/models.py:133 -#: counter/models.py:160 +#: accounting/models.py:111 accounting/models.py:136 club/models.py:183 +#: com/models.py:64 com/models.py:152 counter/models.py:134 +#: counter/models.py:161 msgid "club" msgstr "club" @@ -87,12 +87,12 @@ msgstr "Compte club" msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:192 club/models.py:173 counter/models.py:428 +#: accounting/models.py:192 club/models.py:184 counter/models.py:433 #: election/models.py:18 launderette/models.py:144 msgid "start date" msgstr "date de début" -#: accounting/models.py:193 club/models.py:174 counter/models.py:429 +#: accounting/models.py:193 club/models.py:185 counter/models.py:434 #: election/models.py:19 msgid "end date" msgstr "date de fin" @@ -105,8 +105,8 @@ msgstr "est fermé" msgid "club account" msgstr "compte club" -#: accounting/models.py:197 accounting/models.py:253 counter/models.py:52 -#: counter/models.py:268 +#: accounting/models.py:197 accounting/models.py:253 counter/models.py:53 +#: counter/models.py:273 msgid "amount" msgstr "montant" @@ -126,18 +126,18 @@ msgstr "numéro" msgid "journal" msgstr "classeur" -#: accounting/models.py:254 core/models.py:577 core/models.py:948 -#: core/models.py:988 counter/models.py:271 counter/models.py:319 -#: counter/models.py:445 eboutic/models.py:39 eboutic/models.py:72 -#: forum/models.py:194 forum/models.py:249 stock/models.py:76 +#: accounting/models.py:254 core/models.py:596 core/models.py:967 +#: core/models.py:1007 counter/models.py:276 counter/models.py:324 +#: counter/models.py:450 eboutic/models.py:39 eboutic/models.py:72 +#: forum/models.py:238 forum/models.py:309 stock/models.py:76 msgid "date" msgstr "date" -#: accounting/models.py:255 counter/models.py:446 stock/models.py:79 +#: accounting/models.py:255 counter/models.py:451 stock/models.py:79 msgid "comment" msgstr "commentaire" -#: accounting/models.py:256 counter/models.py:272 counter/models.py:320 +#: accounting/models.py:256 counter/models.py:277 counter/models.py:325 #: subscription/models.py:53 msgid "payment method" msgstr "méthode de paiement" @@ -163,7 +163,7 @@ msgid "accounting type" msgstr "type comptable" #: accounting/models.py:265 accounting/models.py:364 accounting/models.py:390 -#: accounting/models.py:413 counter/models.py:311 +#: accounting/models.py:413 counter/models.py:316 msgid "label" msgstr "étiquette" @@ -242,7 +242,7 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:359 counter/models.py:128 +#: accounting/models.py:359 counter/models.py:129 msgid "code" msgstr "code" @@ -335,7 +335,7 @@ msgstr "Compte en banque : " #: counter/templates/counter/last_ops.jinja:59 #: election/templates/election/election_detail.jinja:280 #: election/templates/election/election_detail.jinja:329 -#: forum/templates/forum/macros.jinja:20 forum/templates/forum/macros.jinja:110 +#: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:113 #: launderette/templates/launderette/launderette_admin.jinja:16 #: launderette/views.py:178 sas/templates/sas/album.jinja:26 #: sas/templates/sas/moderation.jinja:18 sas/templates/sas/picture.jinja:74 @@ -379,8 +379,8 @@ msgstr "Nouveau compte club" #: election/templates/election/election_detail.jinja:279 #: election/templates/election/election_detail.jinja:326 #: election/templates/election/election_detail.jinja:374 -#: forum/templates/forum/macros.jinja:19 forum/templates/forum/macros.jinja:56 -#: forum/templates/forum/macros.jinja:104 +#: forum/templates/forum/macros.jinja:20 forum/templates/forum/macros.jinja:62 +#: forum/templates/forum/macros.jinja:107 #: launderette/templates/launderette/launderette_list.jinja:16 #: sas/templates/sas/album.jinja:18 sas/templates/sas/picture.jinja:100 #: trombi/templates/trombi/detail.jinja:9 @@ -797,11 +797,11 @@ msgstr "Opérations sans étiquette" msgid "Refound this account" msgstr "Rembourser ce compte" -#: club/models.py:44 +#: club/models.py:45 msgid "unix name" msgstr "nom unix" -#: club/models.py:48 +#: club/models.py:49 msgid "" "Enter a valid unix name. This value may contain only letters, numbers ./-/_ " "characters." @@ -809,52 +809,52 @@ msgstr "" "Entrez un nom UNIX valide. Cette valeur peut contenir uniquement des " "lettres, des nombres, et les caractères ./-/_" -#: club/models.py:53 +#: club/models.py:54 msgid "A club with that unix name already exists." msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:56 core/models.py:189 +#: club/models.py:57 core/models.py:189 msgid "address" msgstr "Adresse" -#: club/models.py:62 core/models.py:150 +#: club/models.py:63 core/models.py:150 msgid "home" msgstr "home" -#: club/models.py:74 +#: club/models.py:75 msgid "You can not make loops in clubs" msgstr "Vous ne pouvez pas faire de boucles dans les clubs" -#: club/models.py:88 +#: club/models.py:89 msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:171 counter/models.py:426 counter/models.py:443 +#: club/models.py:182 counter/models.py:431 counter/models.py:448 #: eboutic/models.py:38 eboutic/models.py:71 election/models.py:130 #: launderette/models.py:111 launderette/models.py:148 sas/models.py:156 msgid "user" msgstr "nom d'utilisateur" -#: club/models.py:175 core/models.py:169 election/models.py:129 +#: club/models.py:186 core/models.py:169 election/models.py:129 #: election/models.py:145 msgid "role" msgstr "rôle" -#: club/models.py:177 core/models.py:61 counter/models.py:100 -#: counter/models.py:125 election/models.py:15 election/models.py:82 -#: election/models.py:131 forum/models.py:50 forum/models.py:149 +#: club/models.py:188 core/models.py:61 counter/models.py:101 +#: counter/models.py:126 election/models.py:15 election/models.py:82 +#: election/models.py:131 forum/models.py:51 forum/models.py:183 msgid "description" msgstr "description" -#: club/models.py:182 +#: club/models.py:193 msgid "User must be subscriber to take part to a club" msgstr "L'utilisateur doit être cotisant pour faire partie d'un club" -#: club/models.py:184 +#: club/models.py:195 msgid "User is already member of that club" msgstr "L'utilisateur est déjà membre de ce club" -#: club/models.py:188 +#: club/models.py:199 msgid "past member" msgstr "Anciens membres" @@ -1108,7 +1108,8 @@ msgid "Call" msgstr "Appel" #: com/models.py:60 com/models.py:102 com/models.py:149 election/models.py:14 -#: election/models.py:81 election/models.py:118 forum/models.py:192 +#: election/models.py:81 election/models.py:118 forum/models.py:185 +#: forum/models.py:236 msgid "title" msgstr "titre" @@ -1120,7 +1121,7 @@ msgstr "résumé" msgid "content" msgstr "contenu" -#: com/models.py:63 core/models.py:987 launderette/models.py:84 +#: com/models.py:63 core/models.py:1006 launderette/models.py:84 #: launderette/models.py:109 launderette/models.py:145 stock/models.py:59 #: stock/models.py:98 msgid "type" @@ -1130,7 +1131,7 @@ msgstr "type" msgid "author" msgstr "auteur" -#: com/models.py:66 core/models.py:578 +#: com/models.py:66 core/models.py:597 msgid "is moderated" msgstr "est modéré" @@ -1202,8 +1203,8 @@ msgstr "Type" #: com/templates/com/news_admin_list.jinja:15 #: com/templates/com/news_admin_list.jinja:50 #: com/templates/com/weekmail.jinja:19 com/templates/com/weekmail.jinja:48 -#: forum/templates/forum/forum.jinja:26 forum/templates/forum/forum.jinja:44 -#: forum/views.py:127 +#: forum/templates/forum/forum.jinja:27 forum/templates/forum/forum.jinja:46 +#: forum/templates/forum/main.jinja:25 forum/views.py:141 msgid "Title" msgstr "Titre" @@ -1215,7 +1216,7 @@ msgstr "Résumé" #: com/templates/com/news_admin_list.jinja:18 #: com/templates/com/news_admin_list.jinja:53 #: com/templates/com/weekmail.jinja:17 com/templates/com/weekmail.jinja:46 -#: forum/templates/forum/forum.jinja:48 +#: forum/templates/forum/forum.jinja:50 msgid "Author" msgstr "Auteur" @@ -1663,124 +1664,124 @@ msgstr "adresse des parents" msgid "is subscriber viewable" msgstr "profil visible par les cotisants" -#: core/models.py:330 +#: core/models.py:349 msgid "A user with that username already exists" msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" -#: core/models.py:455 core/templates/core/macros.jinja:17 +#: core/models.py:474 core/templates/core/macros.jinja:17 #: core/templates/core/user_detail.jinja:14 #: core/templates/core/user_detail.jinja:16 #: core/templates/core/user_edit.jinja:17 #: election/templates/election/election_detail.jinja:316 -#: forum/templates/forum/macros.jinja:87 forum/templates/forum/macros.jinja:89 +#: forum/templates/forum/macros.jinja:93 forum/templates/forum/macros.jinja:95 #: forum/templates/forum/reply.jinja:36 forum/templates/forum/reply.jinja:38 #: trombi/templates/trombi/user_tools.jinja:43 msgid "Profile" msgstr "Profil" -#: core/models.py:535 +#: core/models.py:554 msgid "Visitor" msgstr "Visiteur" -#: core/models.py:540 +#: core/models.py:559 msgid "do you want to receive the weekmail" msgstr "voulez-vous recevoir le Weekmail" -#: core/models.py:545 +#: core/models.py:564 msgid "define if we show a users stats" msgstr "Definit si l'on montre les statistiques de l'utilisateur" -#: core/models.py:547 +#: core/models.py:566 msgid "Show your account statistics to others" msgstr "Montrez vos statistiques de compte aux autres" -#: core/models.py:566 +#: core/models.py:585 msgid "file name" msgstr "nom du fichier" -#: core/models.py:567 core/models.py:775 +#: core/models.py:586 core/models.py:794 msgid "parent" msgstr "parent" -#: core/models.py:568 core/models.py:584 +#: core/models.py:587 core/models.py:603 msgid "file" msgstr "fichier" -#: core/models.py:569 +#: core/models.py:588 msgid "compressed file" msgstr "version allégée" -#: core/models.py:570 +#: core/models.py:589 msgid "thumbnail" msgstr "miniature" -#: core/models.py:571 core/models.py:579 +#: core/models.py:590 core/models.py:598 msgid "owner" msgstr "propriétaire" -#: core/models.py:572 core/models.py:781 core/views/files.py:146 +#: core/models.py:591 core/models.py:800 core/views/files.py:146 msgid "edit group" msgstr "groupe d'édition" -#: core/models.py:573 core/models.py:782 core/views/files.py:147 +#: core/models.py:592 core/models.py:801 core/views/files.py:147 msgid "view group" msgstr "groupe de vue" -#: core/models.py:574 +#: core/models.py:593 msgid "is folder" msgstr "est un dossier" -#: core/models.py:575 +#: core/models.py:594 msgid "mime type" msgstr "type mime" -#: core/models.py:576 +#: core/models.py:595 msgid "size" msgstr "taille" -#: core/models.py:580 +#: core/models.py:599 msgid "asked for removal" msgstr "retrait demandé" -#: core/models.py:581 +#: core/models.py:600 msgid "is in the SAS" msgstr "est dans le SAS" -#: core/models.py:620 +#: core/models.py:639 msgid "Character '/' not authorized in name" msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier" -#: core/models.py:623 core/models.py:628 +#: core/models.py:642 core/models.py:647 msgid "Loop in folder tree" msgstr "Boucle dans l'arborescence des dossiers" -#: core/models.py:632 +#: core/models.py:651 msgid "You can not make a file be a children of a non folder file" msgstr "" "Vous ne pouvez pas mettre un fichier enfant de quelque chose qui n'est pas " "un dossier" -#: core/models.py:636 +#: core/models.py:655 msgid "Duplicate file" msgstr "Un fichier de ce nom existe déjà" -#: core/models.py:650 +#: core/models.py:669 msgid "You must provide a file" msgstr "Vous devez fournir un fichier" -#: core/models.py:716 +#: core/models.py:735 msgid "Folder: " msgstr "Dossier : " -#: core/models.py:718 +#: core/models.py:737 msgid "File: " msgstr "Fichier : " -#: core/models.py:766 +#: core/models.py:785 msgid "page unix name" msgstr "nom unix de la page" -#: core/models.py:770 +#: core/models.py:789 msgid "" "Enter a valid page name. This value may contain only unaccented letters, " "numbers and ./+/-/_ characters." @@ -1788,51 +1789,51 @@ msgstr "" "Entrez un nom de page correct. Uniquement des lettres non accentuées, " "numéros, et ./+/-/_" -#: core/models.py:778 +#: core/models.py:797 msgid "page name" msgstr "nom de la page" -#: core/models.py:779 +#: core/models.py:798 msgid "owner group" msgstr "groupe propriétaire" -#: core/models.py:783 +#: core/models.py:802 msgid "lock user" msgstr "utilisateur bloquant" -#: core/models.py:784 +#: core/models.py:803 msgid "lock_timeout" msgstr "décompte du déblocage" -#: core/models.py:811 +#: core/models.py:830 msgid "Duplicate page" msgstr "Une page de ce nom existe déjà" -#: core/models.py:817 +#: core/models.py:836 msgid "Loop in page tree" msgstr "Boucle dans l'arborescence des pages" -#: core/models.py:945 +#: core/models.py:964 msgid "revision" msgstr "révision" -#: core/models.py:946 +#: core/models.py:965 msgid "page title" msgstr "titre de la page" -#: core/models.py:947 +#: core/models.py:966 msgid "page content" msgstr "contenu de la page" -#: core/models.py:985 +#: core/models.py:1004 msgid "url" msgstr "url" -#: core/models.py:986 +#: core/models.py:1005 msgid "param" msgstr "param" -#: core/models.py:989 +#: core/models.py:1008 msgid "viewed" msgstr "vue" @@ -1898,7 +1899,7 @@ msgstr "SAS" #: core/templates/core/base.jinja:94 forum/templates/forum/forum.jinja:10 #: forum/templates/forum/last_unread.jinja:12 #: forum/templates/forum/main.jinja:6 forum/templates/forum/main.jinja.py:11 -#: forum/templates/forum/main.jinja:13 forum/templates/forum/reply.jinja:15 +#: forum/templates/forum/main.jinja:14 forum/templates/forum/reply.jinja:15 #: forum/templates/forum/topic.jinja:30 msgid "Forum" msgstr "Forum" @@ -1973,22 +1974,6 @@ msgstr "Confirmation" msgid "Cancel" msgstr "Annuler" -#: core/templates/core/doku_to_markdown.jinja:4 -msgid "Doku to Markdown" -msgstr "Doku vers Markdown" - -#: core/templates/core/doku_to_markdown.jinja:13 -msgid "Convert" -msgstr "Convertir" - -#: core/templates/core/doku_to_markdown.jinja:16 -msgid "Markdown" -msgstr "Markdown" - -#: core/templates/core/doku_to_markdown.jinja:20 -msgid "Render" -msgstr "Rendu" - #: core/templates/core/edit.jinja:5 core/templates/core/edit.jinja.py:13 #: core/templates/core/file_edit.jinja:4 #: counter/templates/counter/cash_register_summary.jinja:4 @@ -2357,6 +2342,22 @@ msgstr "Utilisateurs" msgid "Clubs" msgstr "Clubs" +#: core/templates/core/to_markdown.jinja:4 +msgid "To Markdown" +msgstr "Vers Markdown" + +#: core/templates/core/to_markdown.jinja:15 +msgid "Convert" +msgstr "Convertir" + +#: core/templates/core/to_markdown.jinja:18 +msgid "Markdown" +msgstr "Markdown" + +#: core/templates/core/to_markdown.jinja:22 +msgid "Render" +msgstr "Rendu" + #: core/templates/core/user_account.jinja:8 msgid "Year" msgstr "Année" @@ -2768,84 +2769,84 @@ msgstr "Photos" msgid "User already has a profile picture" msgstr "L'utilisateur a déjà une photo de profil" -#: counter/models.py:51 +#: counter/models.py:52 msgid "account id" msgstr "numéro de compte" -#: counter/models.py:55 +#: counter/models.py:56 msgid "customer" msgstr "client" -#: counter/models.py:56 +#: counter/models.py:57 msgid "customers" msgstr "clients" -#: counter/models.py:76 counter/templates/counter/counter_click.jinja:48 +#: counter/models.py:77 counter/templates/counter/counter_click.jinja:48 #: counter/templates/counter/counter_click.jinja:82 msgid "Not enough money" msgstr "Solde insuffisant" -#: counter/models.py:104 counter/models.py:126 +#: counter/models.py:105 counter/models.py:127 msgid "product type" msgstr "type du produit" -#: counter/models.py:129 +#: counter/models.py:130 msgid "purchase price" msgstr "prix d'achat" -#: counter/models.py:130 +#: counter/models.py:131 msgid "selling price" msgstr "prix de vente" -#: counter/models.py:131 +#: counter/models.py:132 msgid "special selling price" msgstr "prix de vente spécial" -#: counter/models.py:132 +#: counter/models.py:133 msgid "icon" msgstr "icône" -#: counter/models.py:134 +#: counter/models.py:135 msgid "limit age" msgstr "âge limite" -#: counter/models.py:135 +#: counter/models.py:136 msgid "tray price" msgstr "prix plateau" -#: counter/models.py:136 +#: counter/models.py:137 msgid "parent product" msgstr "produit parent" -#: counter/models.py:138 +#: counter/models.py:139 msgid "buying groups" msgstr "groupe d'achat" -#: counter/models.py:139 +#: counter/models.py:140 msgid "archived" msgstr "archivé" -#: counter/models.py:142 counter/models.py:526 +#: counter/models.py:143 counter/models.py:531 msgid "product" msgstr "produit" -#: counter/models.py:161 +#: counter/models.py:162 msgid "products" msgstr "produits" -#: counter/models.py:162 +#: counter/models.py:163 msgid "counter type" msgstr "type de comptoir" -#: counter/models.py:164 +#: counter/models.py:165 msgid "Bar" msgstr "Bar" -#: counter/models.py:164 +#: counter/models.py:165 msgid "Office" msgstr "Bureau" -#: counter/models.py:164 counter/templates/counter/counter_list.jinja:11 +#: counter/models.py:165 counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:24 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:8 @@ -2854,62 +2855,62 @@ msgstr "Bureau" msgid "Eboutic" msgstr "Eboutic" -#: counter/models.py:165 +#: counter/models.py:166 msgid "sellers" msgstr "vendeurs" -#: counter/models.py:168 launderette/models.py:147 +#: counter/models.py:169 launderette/models.py:147 msgid "token" msgstr "jeton" -#: counter/models.py:171 counter/models.py:427 counter/models.py:444 +#: counter/models.py:172 counter/models.py:432 counter/models.py:449 #: launderette/models.py:38 stock/models.py:39 msgid "counter" msgstr "comptoir" -#: counter/models.py:274 +#: counter/models.py:279 msgid "bank" msgstr "banque" -#: counter/models.py:276 counter/models.py:322 +#: counter/models.py:281 counter/models.py:327 msgid "is validated" msgstr "est validé" -#: counter/models.py:279 +#: counter/models.py:284 msgid "refilling" msgstr "rechargement" -#: counter/models.py:315 eboutic/models.py:127 +#: counter/models.py:320 eboutic/models.py:127 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:316 counter/models.py:516 eboutic/models.py:128 +#: counter/models.py:321 counter/models.py:521 eboutic/models.py:128 msgid "quantity" msgstr "quantité" -#: counter/models.py:321 +#: counter/models.py:326 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:321 sith/settings.py:354 sith/settings.py:359 +#: counter/models.py:326 sith/settings.py:354 sith/settings.py:359 #: sith/settings.py:381 msgid "Credit card" msgstr "Carte bancaire" -#: counter/models.py:325 +#: counter/models.py:330 msgid "selling" msgstr "vente" -#: counter/models.py:344 +#: counter/models.py:349 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:345 +#: counter/models.py:350 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:347 counter/models.py:359 +#: counter/models.py:352 counter/models.py:364 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -2918,51 +2919,51 @@ msgstr "" "Vous avez acheté un Eticket pour l'événement %(event)s.\n" "Vous pouvez le télécharger sur cette page: %(url)s" -#: counter/models.py:430 +#: counter/models.py:435 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:433 +#: counter/models.py:438 msgid "permanency" msgstr "permanence" -#: counter/models.py:447 +#: counter/models.py:452 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:450 +#: counter/models.py:455 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:514 +#: counter/models.py:519 msgid "cash summary" msgstr "relevé" -#: counter/models.py:515 +#: counter/models.py:520 msgid "value" msgstr "valeur" -#: counter/models.py:517 +#: counter/models.py:522 msgid "check" msgstr "chèque" -#: counter/models.py:520 +#: counter/models.py:525 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:527 +#: counter/models.py:532 msgid "banner" msgstr "bannière" -#: counter/models.py:528 +#: counter/models.py:533 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:529 +#: counter/models.py:534 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:530 +#: counter/models.py:535 msgid "secret" msgstr "secret" @@ -3488,7 +3489,7 @@ msgstr "Les votes ouvriront " #: election/templates/election/election_list.jinja:34 #: election/templates/election/election_list.jinja:39 #: election/templates/election/election_list.jinja:42 -#: forum/templates/forum/macros.jinja:125 +#: forum/templates/forum/macros.jinja:129 msgid " at " msgstr " à " @@ -3583,68 +3584,81 @@ msgstr "Début des candidatures" msgid "End candidature" msgstr "Fin des candidatures" -#: forum/models.py:51 +#: forum/models.py:52 msgid "is a category" msgstr "est une catégorie" -#: forum/models.py:53 +#: forum/models.py:54 msgid "owner club" msgstr "club propriétaire" -#: forum/models.py:59 +#: forum/models.py:60 msgid "number to choose a specific forum ordering" msgstr "numéro spécifiant l'ordre d'affichage" -#: forum/models.py:103 +#: forum/models.py:61 forum/models.py:184 +msgid "the last message" +msgstr "le dernier message" + +#: forum/models.py:62 +msgid "number of topics" +msgstr "nombre de sujets" + +#: forum/models.py:137 msgid "You can not make loops in forums" msgstr "Vous ne pouvez pas faire de boucles dans les forums" -#: forum/models.py:193 +#: forum/models.py:237 msgid "message" msgstr "message" -#: forum/models.py:195 +#: forum/models.py:239 msgid "readers" msgstr "lecteurs" -#: forum/models.py:241 +#: forum/models.py:240 +msgid "is deleted" +msgstr "est supprimé" + +#: forum/models.py:301 msgid "Message edited by" msgstr "Message édité par" -#: forum/models.py:242 +#: forum/models.py:302 msgid "Message deleted by" msgstr "Message supprimé par" -#: forum/models.py:243 +#: forum/models.py:303 msgid "Message undeleted by" msgstr "Message restauré par" -#: forum/models.py:250 +#: forum/models.py:310 msgid "action" msgstr "action" -#: forum/models.py:259 +#: forum/models.py:325 msgid "last read date" msgstr "dernière date de lecture" -#: forum/templates/forum/forum.jinja:19 forum/templates/forum/main.jinja:19 +#: forum/templates/forum/forum.jinja:20 forum/templates/forum/main.jinja:20 msgid "New forum" msgstr "Nouveau forum" -#: forum/templates/forum/forum.jinja:21 forum/templates/forum/reply.jinja:8 +#: forum/templates/forum/forum.jinja:22 forum/templates/forum/reply.jinja:8 #: forum/templates/forum/reply.jinja:25 msgid "New topic" msgstr "Nouveau sujet" -#: forum/templates/forum/forum.jinja:30 +#: forum/templates/forum/forum.jinja:31 forum/templates/forum/main.jinja:29 msgid "Topics" msgstr "Sujets" -#: forum/templates/forum/forum.jinja:33 forum/templates/forum/forum.jinja:54 +#: forum/templates/forum/forum.jinja:34 forum/templates/forum/forum.jinja:56 +#: forum/templates/forum/main.jinja:32 msgid "Last message" msgstr "Dernier message" -#: forum/templates/forum/forum.jinja:51 +#: forum/templates/forum/forum.jinja:53 msgid "Messages" msgstr "Messages" @@ -3657,36 +3671,36 @@ msgstr "Derniers messages non lus" msgid "Refresh" msgstr "Rafraîchir" -#: forum/templates/forum/macros.jinja:102 +#: forum/templates/forum/macros.jinja:105 msgid "Reply as quote" msgstr "Répondre en citant" -#: forum/templates/forum/macros.jinja:108 +#: forum/templates/forum/macros.jinja:111 msgid "Undelete" msgstr "Restaurer" -#: forum/templates/forum/macros.jinja:126 +#: forum/templates/forum/macros.jinja:130 msgid " the " msgstr " le " -#: forum/templates/forum/macros.jinja:138 +#: forum/templates/forum/macros.jinja:142 msgid "Deleted or unreadable message." msgstr "Message supprimé ou non-visible." -#: forum/templates/forum/main.jinja:15 +#: forum/templates/forum/main.jinja:16 msgid "View last unread messages" msgstr "Voir les derniers messages non lus" #: forum/templates/forum/reply.jinja:6 forum/templates/forum/reply.jinja:23 -#: forum/templates/forum/topic.jinja:39 forum/templates/forum/topic.jinja:58 +#: forum/templates/forum/topic.jinja:40 forum/templates/forum/topic.jinja:59 msgid "Reply" msgstr "Répondre" -#: forum/views.py:92 +#: forum/views.py:97 msgid "Apply rights and club owner recursively" msgstr "Appliquer les droits et le club propriétaire récursivement" -#: forum/views.py:230 +#: forum/views.py:253 #, python-format msgid "%(author)s said" msgstr "Citation de %(author)s" @@ -4583,3 +4597,6 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée." #, python-format msgid "Maximum characters: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s" + +#~ msgid "Doku to Markdown" +#~ msgstr "Doku vers Markdown" diff --git a/migrate.py b/migrate.py index a0c420eb..ea2e860c 100644 --- a/migrate.py +++ b/migrate.py @@ -45,7 +45,7 @@ from django.core.files import File from core.models import User, SithFile -from core.utils import doku_to_markdown +from core.utils import doku_to_markdown, bbcode_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 @@ -1279,7 +1279,7 @@ def migrate_forum(): if r['syntaxengine_message'] == "doku": msg.message = doku_to_markdown(to_unicode(r['contenu_message'])) else: - msg.message = to_unicode(r['contenu_message']) + msg.message = bbcode_to_markdown(to_unicode(r['contenu_message'])) msg.save() except Exception as e: print(" FAIL to migrate message: %s" % (repr(e))) From 463e0b7055b99fd98f7fbe78dfcea16bfcc79edb Mon Sep 17 00:00:00 2001 From: Skia Date: Wed, 31 May 2017 00:41:39 +0200 Subject: [PATCH 11/16] Again some small forum improvements Signed-off-by: Skia --- forum/templates/forum/last_unread.jinja | 6 ++++-- forum/templates/forum/macros.jinja | 14 +++++++++++--- forum/views.py | 6 ++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/forum/templates/forum/last_unread.jinja b/forum/templates/forum/last_unread.jinja index 7489db2b..83bcffb0 100644 --- a/forum/templates/forum/last_unread.jinja +++ b/forum/templates/forum/last_unread.jinja @@ -6,9 +6,10 @@ {% endblock %} {% block content %} -

+

Forum > -

+

+

{% 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 @@
-
{{ m.title }}
+
+ #{{ m.id }} +
{% trans %}Reply as quote{% endtrans %} {% if user.can_edit(m) %} @@ -113,8 +123,6 @@ {% trans %}Delete{% endtrans %} {% endif %} {% endif %} -
- {{ m.date|localtime|date(DATETIME_FORMAT) }} {{ m.date|localtime|time(DATETIME_FORMAT) }}

diff --git a/forum/views.py b/forum/views.py index 89374e36..f2f1aec0 100644 --- a/forum/views.py +++ b/forum/views.py @@ -67,7 +67,8 @@ class ForumLastUnread(ListView): 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') + .select_related('_last_message__author', 'author')\ + .prefetch_related('forum__edit_groups') return topic_list class ForumForm(forms.ModelForm): @@ -122,7 +123,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__author', 'author')\ + 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) From e5ce9658ee26eec75ce6a4f4993c8ee686f9d0e8 Mon Sep 17 00:00:00 2001 From: Skia Date: Wed, 31 May 2017 19:04:00 +0200 Subject: [PATCH 12/16] Fix images and links parsing in doku_to_markdown Signed-off-by: Skia --- core/utils.py | 6 ++++-- migrate.py | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/utils.py b/core/utils.py index 87e22f0d..07bc7bcb 100644 --- a/core/utils.py +++ b/core/utils.py @@ -94,8 +94,10 @@ def doku_to_markdown(text): text = re.sub(r'\\{2,}[\s]', r' \n', text) # Carriage return - text = re.sub(r'\[\[(.*?)(\|(.*?))?\]\]', r'[\3](\1)', text) # Links - text = re.sub(r'{{(.*?)(\|(.*?))?}}', r'![\3](\1 "\3")', text) # Images + text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text) # Links + text = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', text) # Links 2 + text = re.sub(r'{{(.*?)\|(.*?)}}', r'![\2](\1 "\2")', text) # Images + text = re.sub(r'{{(.*?)(\|(.*?))?}}', r'![\1](\1 "\1")', text) # Images 2 text = re.sub(r'{\[(.*?)(\|(.*?))?\]}', r'[\1](\1)', text) # Video (transform to classic links, since we can't integrate them) text = re.sub(r'###(\d*?)###', r'[[[\1]]]', text) # Progress bar diff --git a/migrate.py b/migrate.py index ea2e860c..ad310a1a 100644 --- a/migrate.py +++ b/migrate.py @@ -1276,10 +1276,13 @@ def migrate_forum(): 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 = bbcode_to_markdown(to_unicode(r['contenu_message'])) + try: + if r['syntaxengine_message'] == "doku": + msg.message = doku_to_markdown(to_unicode(r['contenu_message'])) + else: + msg.message = bbcode_to_markdown(to_unicode(r['contenu_message'])) + except: + msg.message = to_unicode(r['contenu_message']) msg.save() except Exception as e: print(" FAIL to migrate message: %s" % (repr(e))) From 22330e6d9f1fad8319e02d8ecef3c5068f02e462 Mon Sep 17 00:00:00 2001 From: Skia Date: Wed, 31 May 2017 19:50:41 +0200 Subject: [PATCH 13/16] forum: add on_delete=models.SET_NULL on _last_message fields Signed-off-by: Skia --- ...70530_2058.py => 0004_auto_20170531_1949.py} | 17 +++++++++-------- forum/models.py | 6 ++++-- 2 files changed, 13 insertions(+), 10 deletions(-) rename forum/migrations/{0004_auto_20170530_2058.py => 0004_auto_20170531_1949.py} (60%) diff --git a/forum/migrations/0004_auto_20170530_2058.py b/forum/migrations/0004_auto_20170531_1949.py similarity index 60% rename from forum/migrations/0004_auto_20170530_2058.py rename to forum/migrations/0004_auto_20170531_1949.py index d236d5ac..0ae64560 100644 --- a/forum/migrations/0004_auto_20170530_2058.py +++ b/forum/migrations/0004_auto_20170531_1949.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -22,41 +23,41 @@ class Migration(migrations.Migration): migrations.AddField( model_name='forum', name='_last_message', - field=models.ForeignKey(related_name='forums_where_its_last', null=True, to='forum.ForumMessage', verbose_name='the last message'), + field=models.ForeignKey(verbose_name='the last message', to='forum.ForumMessage', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='forums_where_its_last'), ), migrations.AddField( model_name='forum', name='_topic_number', - field=models.IntegerField(verbose_name='number of topics', default=0), + field=models.IntegerField(default=0, verbose_name='number of topics'), ), migrations.AddField( model_name='forummessage', name='_deleted', - field=models.BooleanField(verbose_name='is deleted', default=False), + field=models.BooleanField(default=False, verbose_name='is deleted'), ), migrations.AddField( model_name='forumtopic', name='_last_message', - field=models.ForeignKey(related_name='+', null=True, to='forum.ForumMessage', verbose_name='the last message'), + field=models.ForeignKey(verbose_name='the last message', to='forum.ForumMessage', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+'), ), migrations.AddField( model_name='forumtopic', name='_message_number', - field=models.IntegerField(verbose_name='number of messages', default=0), + field=models.IntegerField(default=0, verbose_name='number of messages'), ), migrations.AddField( model_name='forumtopic', name='_title', - field=models.CharField(max_length=64, verbose_name='title', blank=True), + field=models.CharField(max_length=64, blank=True, verbose_name='title'), ), migrations.AlterField( model_name='forum', name='description', - field=models.CharField(max_length=512, verbose_name='description', default=''), + field=models.CharField(max_length=512, default='', verbose_name='description'), ), migrations.AlterField( model_name='forum', name='id', - field=models.AutoField(serialize=False, db_index=True, primary_key=True), + field=models.AutoField(primary_key=True, serialize=False, db_index=True), ), ] diff --git a/forum/models.py b/forum/models.py index 9f0f76e5..b7f0c8d8 100644 --- a/forum/models.py +++ b/forum/models.py @@ -58,7 +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) + _last_message = models.ForeignKey('ForumMessage', related_name="forums_where_its_last", + verbose_name=_("the last message"), null=True, on_delete=models.SET_NULL) _topic_number = models.IntegerField(_("number of topics"), default=0) class Meta: @@ -181,7 +182,8 @@ 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) + _last_message = models.ForeignKey('ForumMessage', related_name="+", verbose_name=_("the last message"), + null=True, on_delete=models.SET_NULL) _title = models.CharField(_('title'), max_length=64, blank=True) _message_number = models.IntegerField(_("number of messages"), default=0) From 978b8911377ea52981d8a21f2c150ec7ea0a9c87 Mon Sep 17 00:00:00 2001 From: Skia Date: Wed, 31 May 2017 21:47:13 +0200 Subject: [PATCH 14/16] Add a cache clearing mechanism to fix rights update problems Signed-off-by: Skia --- core/__init__.py | 1 + core/apps.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 core/apps.py diff --git a/core/__init__.py b/core/__init__.py index 0a9419f8..ffb9c1e7 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -22,3 +22,4 @@ # # +default_app_config = 'core.apps.SithConfig' diff --git a/core/apps.py b/core/apps.py new file mode 100644 index 00000000..88702ff5 --- /dev/null +++ b/core/apps.py @@ -0,0 +1,57 @@ +# -*- coding:utf-8 -* +# +# Copyright 2017 +# - Skia +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# + +from django.apps import AppConfig +from django.dispatch import receiver +from django.db.models.signals import pre_save, post_save, m2m_changed + +class SithConfig(AppConfig): + name = 'core' + verbose_name = "Core app of the Sith" + + def ready(self): + from core.models import User, Group + from club.models import Club, Membership + from forum.models import Forum + + def clear_cached_groups(sender, **kwargs): + if kwargs['model'] == Group: + User._group_ids = {} + User._group_name = {} + + def clear_cached_memberships(sender, **kwargs): + User._club_memberships = {} + Club._memberships = {} + Forum._club_memberships = {} + + print("Connecting signals!") + m2m_changed.connect(clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups") + post_save.connect(clear_cached_memberships, weak=False, sender=Membership, # Membership is cached + dispatch_uid="clear_cached_memberships_membership") + post_save.connect(clear_cached_memberships, weak=False, sender=Club, # Club has a cache of Membership + dispatch_uid="clear_cached_memberships_club") + post_save.connect(clear_cached_memberships, weak=False, sender=Forum, # Forum has a cache of Membership + dispatch_uid="clear_cached_memberships_forum") + # TODO: there may be a need to add more cache clearing + From 11d20f43e5f4950cebfb983a5d888380058acaf5 Mon Sep 17 00:00:00 2001 From: Skia Date: Wed, 31 May 2017 23:43:22 +0200 Subject: [PATCH 15/16] Add some missing translations Signed-off-by: Skia --- locale/fr/LC_MESSAGES/django.po | 122 ++++++++++-------- subscription/models.py | 1 - .../templates/subscription/subscription.jinja | 2 +- 3 files changed, 69 insertions(+), 56 deletions(-) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 902d7278..53dae701 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-05-30 19:28+0200\n" +"POT-Creation-Date: 2017-05-31 23:42+0200\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -129,7 +129,7 @@ msgstr "classeur" #: accounting/models.py:254 core/models.py:596 core/models.py:967 #: core/models.py:1007 counter/models.py:276 counter/models.py:324 #: counter/models.py:450 eboutic/models.py:39 eboutic/models.py:72 -#: forum/models.py:238 forum/models.py:309 stock/models.py:76 +#: forum/models.py:236 forum/models.py:308 stock/models.py:76 msgid "date" msgstr "date" @@ -335,11 +335,12 @@ msgstr "Compte en banque : " #: counter/templates/counter/last_ops.jinja:59 #: election/templates/election/election_detail.jinja:280 #: election/templates/election/election_detail.jinja:329 -#: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:113 +#: forum/templates/forum/macros.jinja:21 +#: forum/templates/forum/macros.jinja:123 #: launderette/templates/launderette/launderette_admin.jinja:16 #: launderette/views.py:178 sas/templates/sas/album.jinja:26 #: sas/templates/sas/moderation.jinja:18 sas/templates/sas/picture.jinja:74 -#: sas/templates/sas/picture.jinja.py:124 +#: sas/templates/sas/picture.jinja:124 #: stock/templates/stock/stock_shopping_list.jinja:43 #: stock/templates/stock/stock_shopping_list.jinja:69 #: trombi/templates/trombi/detail.jinja:28 @@ -380,7 +381,7 @@ msgstr "Nouveau compte club" #: election/templates/election/election_detail.jinja:326 #: election/templates/election/election_detail.jinja:374 #: forum/templates/forum/macros.jinja:20 forum/templates/forum/macros.jinja:62 -#: forum/templates/forum/macros.jinja:107 +#: forum/templates/forum/macros.jinja:117 #: launderette/templates/launderette/launderette_list.jinja:16 #: sas/templates/sas/album.jinja:18 sas/templates/sas/picture.jinja:100 #: trombi/templates/trombi/detail.jinja:9 @@ -490,8 +491,8 @@ msgstr "Non" #: accounting/templates/accounting/club_account_details.jinja:56 #: com/templates/com/news_admin_list.jinja:38 -#: com/templates/com/news_admin_list.jinja:71 core/templates/core/file.jinja:36 -#: core/templates/core/page.jinja:28 +#: com/templates/com/news_admin_list.jinja:71 +#: core/templates/core/file.jinja:36 core/templates/core/page.jinja:28 msgid "View" msgstr "Voir" @@ -842,7 +843,7 @@ msgstr "rôle" #: club/models.py:188 core/models.py:61 counter/models.py:101 #: counter/models.py:126 election/models.py:15 election/models.py:82 -#: election/models.py:131 forum/models.py:51 forum/models.py:183 +#: election/models.py:131 forum/models.py:51 forum/models.py:184 msgid "description" msgstr "description" @@ -858,7 +859,8 @@ msgstr "L'utilisateur est déjà membre de ce club" msgid "past member" msgstr "Anciens membres" -#: club/templates/club/club_list.jinja:4 club/templates/club/club_list.jinja:24 +#: club/templates/club/club_list.jinja:4 +#: club/templates/club/club_list.jinja:24 msgid "Club list" msgstr "Liste des clubs" @@ -920,13 +922,14 @@ msgstr "Du" msgid "To" msgstr "Au" -#: club/templates/club/club_sellings.jinja:5 club/views.py:84 club/views.py:247 -#: counter/templates/counter/counter_main.jinja:19 +#: club/templates/club/club_sellings.jinja:5 club/views.py:84 +#: club/views.py:247 counter/templates/counter/counter_main.jinja:19 #: counter/templates/counter/last_ops.jinja:35 msgid "Sellings" msgstr "Ventes" -#: club/templates/club/club_sellings.jinja:9 club/templates/club/stats.jinja:19 +#: club/templates/club/club_sellings.jinja:9 +#: club/templates/club/stats.jinja:19 #: counter/templates/counter/cash_summary_list.jinja:15 msgid "Show" msgstr "Montrer" @@ -1108,8 +1111,8 @@ msgid "Call" msgstr "Appel" #: com/models.py:60 com/models.py:102 com/models.py:149 election/models.py:14 -#: election/models.py:81 election/models.py:118 forum/models.py:185 -#: forum/models.py:236 +#: election/models.py:81 election/models.py:118 forum/models.py:187 +#: forum/models.py:234 msgid "title" msgstr "titre" @@ -1184,8 +1187,9 @@ msgid "News admin" msgstr "Administration des nouvelles" #: com/templates/com/news_admin_list.jinja:9 -#: com/templates/com/news_detail.jinja:5 com/templates/com/news_detail.jinja:11 -#: com/templates/com/news_list.jinja:4 com/templates/com/news_list.jinja:28 +#: com/templates/com/news_detail.jinja:5 +#: com/templates/com/news_detail.jinja:11 com/templates/com/news_list.jinja:4 +#: com/templates/com/news_list.jinja:28 msgid "News" msgstr "Nouvelles" @@ -1204,7 +1208,7 @@ msgstr "Type" #: com/templates/com/news_admin_list.jinja:50 #: com/templates/com/weekmail.jinja:19 com/templates/com/weekmail.jinja:48 #: forum/templates/forum/forum.jinja:27 forum/templates/forum/forum.jinja:46 -#: forum/templates/forum/main.jinja:25 forum/views.py:141 +#: forum/templates/forum/main.jinja:25 forum/views.py:144 msgid "Title" msgstr "Titre" @@ -1441,6 +1445,11 @@ msgstr "Supprimer et sauver pour regénérer" msgid "Weekmail of the " msgstr "Weekmail du " +#: com/views.py:378 +msgid "" +"You must be a board member of the selected club to post in the Weekmail." +msgstr "" + #: core/models.py:57 msgid "meta group status" msgstr "status du meta-groupe" @@ -1866,7 +1875,8 @@ msgstr "S'enregister" msgid "View more" msgstr "Voir plus" -#: core/templates/core/base.jinja:62 forum/templates/forum/last_unread.jinja:15 +#: core/templates/core/base.jinja:62 +#: forum/templates/forum/last_unread.jinja:16 msgid "Mark all as read" msgstr "Marquer tout commme lu" @@ -1897,8 +1907,8 @@ msgid "SAS" msgstr "SAS" #: core/templates/core/base.jinja:94 forum/templates/forum/forum.jinja:10 -#: forum/templates/forum/last_unread.jinja:12 -#: forum/templates/forum/main.jinja:6 forum/templates/forum/main.jinja.py:11 +#: forum/templates/forum/last_unread.jinja:13 +#: forum/templates/forum/main.jinja:6 forum/templates/forum/main.jinja:11 #: forum/templates/forum/main.jinja:14 forum/templates/forum/reply.jinja:15 #: forum/templates/forum/topic.jinja:30 msgid "Forum" @@ -2112,11 +2122,13 @@ msgstr "login" msgid "Lost password?" msgstr "Mot de passe perdu ?" -#: core/templates/core/macros.jinja:27 core/templates/core/user_detail.jinja:27 +#: core/templates/core/macros.jinja:27 +#: core/templates/core/user_detail.jinja:27 msgid "Born: " msgstr "Né le : " -#: core/templates/core/macros.jinja:31 core/templates/core/user_detail.jinja:48 +#: core/templates/core/macros.jinja:31 +#: core/templates/core/user_detail.jinja:48 msgid "Promo: " msgstr "Promo : " @@ -2695,8 +2707,8 @@ msgid "Other tools" msgstr "Autres outils" #: core/templates/core/user_tools.jinja:111 -msgid "Convert dokuwiki syntax to Markdown" -msgstr "Convertir de la syntaxe dokuwiki vers Markdown" +msgid "Convert dokuwiki/BBcode syntax to Markdown" +msgstr "Convertir de la syntaxe dokuwiki/BBcode vers Markdown" #: core/templates/core/user_tools.jinja:112 msgid "Trombi tools" @@ -3489,7 +3501,7 @@ msgstr "Les votes ouvriront " #: election/templates/election/election_list.jinja:34 #: election/templates/election/election_list.jinja:39 #: election/templates/election/election_list.jinja:42 -#: forum/templates/forum/macros.jinja:129 +#: forum/templates/forum/macros.jinja:137 msgid " at " msgstr " à " @@ -3596,47 +3608,51 @@ msgstr "club propriétaire" msgid "number to choose a specific forum ordering" msgstr "numéro spécifiant l'ordre d'affichage" -#: forum/models.py:61 forum/models.py:184 +#: forum/models.py:62 forum/models.py:185 msgid "the last message" msgstr "le dernier message" -#: forum/models.py:62 +#: forum/models.py:63 msgid "number of topics" msgstr "nombre de sujets" -#: forum/models.py:137 +#: forum/models.py:138 msgid "You can not make loops in forums" msgstr "Vous ne pouvez pas faire de boucles dans les forums" -#: forum/models.py:237 +#: forum/models.py:188 +msgid "number of messages" +msgstr "nombre de messages" + +#: forum/models.py:235 msgid "message" msgstr "message" -#: forum/models.py:239 +#: forum/models.py:237 msgid "readers" msgstr "lecteurs" -#: forum/models.py:240 +#: forum/models.py:238 msgid "is deleted" msgstr "est supprimé" -#: forum/models.py:301 +#: forum/models.py:300 msgid "Message edited by" msgstr "Message édité par" -#: forum/models.py:302 +#: forum/models.py:301 msgid "Message deleted by" msgstr "Message supprimé par" -#: forum/models.py:303 +#: forum/models.py:302 msgid "Message undeleted by" msgstr "Message restauré par" -#: forum/models.py:310 +#: forum/models.py:309 msgid "action" msgstr "action" -#: forum/models.py:325 +#: forum/models.py:324 msgid "last read date" msgstr "dernière date de lecture" @@ -3663,27 +3679,27 @@ msgid "Messages" msgstr "Messages" #: forum/templates/forum/last_unread.jinja:5 -#: forum/templates/forum/last_unread.jinja:13 +#: forum/templates/forum/last_unread.jinja:14 msgid "Last unread messages" msgstr "Derniers messages non lus" -#: forum/templates/forum/last_unread.jinja:16 +#: forum/templates/forum/last_unread.jinja:17 msgid "Refresh" msgstr "Rafraîchir" -#: forum/templates/forum/macros.jinja:105 +#: forum/templates/forum/macros.jinja:115 msgid "Reply as quote" msgstr "Répondre en citant" -#: forum/templates/forum/macros.jinja:111 +#: forum/templates/forum/macros.jinja:121 msgid "Undelete" msgstr "Restaurer" -#: forum/templates/forum/macros.jinja:130 +#: forum/templates/forum/macros.jinja:138 msgid " the " msgstr " le " -#: forum/templates/forum/macros.jinja:142 +#: forum/templates/forum/macros.jinja:150 msgid "Deleted or unreadable message." msgstr "Message supprimé ou non-visible." @@ -3696,11 +3712,11 @@ msgstr "Voir les derniers messages non lus" msgid "Reply" msgstr "Répondre" -#: forum/views.py:97 +#: forum/views.py:98 msgid "Apply rights and club owner recursively" msgstr "Appliquer les droits et le club propriétaire récursivement" -#: forum/views.py:253 +#: forum/views.py:257 #, python-format msgid "%(author)s said" msgstr "Citation de %(author)s" @@ -4340,23 +4356,23 @@ msgstr "début de la cotisation" msgid "subscription end" msgstr "fin de la cotisation" -#: subscription/models.py:55 -msgid "Eboutic is reserved to specific users. In doubt, don't use it." -msgstr "" -"Eboutic est réservé à des cas particuliers. Dans le doute, ne l'utilisez pas." - -#: subscription/models.py:58 +#: subscription/models.py:57 msgid "location" msgstr "lieu" -#: subscription/models.py:67 +#: subscription/models.py:66 msgid "You can not subscribe many time for the same period" msgstr "Vous ne pouvez pas cotiser plusieurs fois pour la même période" -#: subscription/models.py:71 +#: subscription/models.py:70 msgid "Subscription error" msgstr "Erreur de cotisation" +#: subscription/templates/subscription/subscription.jinja:22 +msgid "Eboutic is reserved to specific users. In doubt, don't use it." +msgstr "" +"Eboutic est réservé à des cas particuliers. Dans le doute, ne l'utilisez pas." + #: subscription/views.py:74 msgid "A user with that email address already exists" msgstr "Un utilisateur avec cette adresse email existe déjà" @@ -4598,5 +4614,3 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée." msgid "Maximum characters: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s" -#~ msgid "Doku to Markdown" -#~ msgstr "Doku vers Markdown" diff --git a/subscription/models.py b/subscription/models.py index 7c51339d..a1146a10 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -52,7 +52,6 @@ class Subscription(models.Model): subscription_end = models.DateField(_('subscription end')) payment_method = models.CharField(_('payment method'), max_length=255, - help_text=_('Eboutic is reserved to specific users. In doubt, don\'t use it.'), choices=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD) location = models.CharField(choices=settings.SITH_SUBSCRIPTION_LOCATIONS, max_length=20, verbose_name=_('location')) diff --git a/subscription/templates/subscription/subscription.jinja b/subscription/templates/subscription/subscription.jinja index 944d8a5e..a080f58c 100644 --- a/subscription/templates/subscription/subscription.jinja +++ b/subscription/templates/subscription/subscription.jinja @@ -19,7 +19,7 @@

{{ form.subscription_type.errors }} {{ form.subscription_type }}

{{ form.payment_method.errors }} {{ form.payment_method }}

-

{{ form.payment_method.help_text }}

+

{% trans %}Eboutic is reserved to specific users. In doubt, don't use it.{% endtrans %}

{{ form.location.errors }} {{ form.location }}

From f3c1ab4ae48c255f62232dd08e605ab0762c64b4 Mon Sep 17 00:00:00 2001 From: Skia Date: Wed, 31 May 2017 23:56:47 +0200 Subject: [PATCH 16/16] forum: use short names Signed-off-by: Skia --- core/templates/core/macros.jinja | 4 ++++ forum/templates/forum/macros.jinja | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/templates/core/macros.jinja b/core/templates/core/macros.jinja index 6239c695..244617c5 100644 --- a/core/templates/core/macros.jinja +++ b/core/templates/core/macros.jinja @@ -2,6 +2,10 @@ {{ user.get_display_name() }} {%- endmacro %} +{% macro user_profile_link_short_name(user) -%} +{{ user.get_short_name() }} +{%- endmacro %} + {% macro user_link_with_pict(user) -%} {{ user.get_mini_item()|safe }} diff --git a/forum/templates/forum/macros.jinja b/forum/templates/forum/macros.jinja index 59e13196..c8331d1d 100644 --- a/forum/templates/forum/macros.jinja +++ b/forum/templates/forum/macros.jinja @@ -1,4 +1,4 @@ -{% from 'core/macros.jinja' import user_profile_link %} +{% from 'core/macros.jinja' import user_profile_link_short_name %} {% macro display_forum(forum, user, is_root=False) %}