Commit 59dfcbd5 authored by Skia's avatar Skia

Merge branch 'forum' into 'master'

Forum

See merge request !49
parents 5897318c dea234f9
Pipeline #764 passed with stage
in 3 minutes and 13 seconds
......@@ -28,7 +28,7 @@ already.
You can check all of them with:
```
sudo apt install libmysqlclient-dev libssl-dev libjpeg-dev zlib1g-dev python3-dev
sudo apt install libmysqlclient-dev libssl-dev libjpeg-dev zlib1g-dev python3-dev libffi-dev
```
The development is done with sqlite, but it is advised to set a more robust DBMS for production (Postgresql for example)
......
......@@ -122,7 +122,7 @@ class Club(models.Model):
sub = User.objects.filter(pk=user.pk).first()
if sub is None:
return False
return sub.is_subscribed()
return sub.is_subscribed
def get_membership_for(self, user):
"""
......@@ -151,7 +151,7 @@ class Membership(models.Model):
def clean(self):
sub = User.objects.filter(pk=self.user.pk).first()
if sub is None or not sub.is_subscribed():
if sub is None or not sub.is_subscribed:
raise ValidationError(_('User must be subscriber to take part to a club'))
if Membership.objects.filter(user=self.user).filter(club=self.club).filter(end_date=None).exists():
raise ValidationError(_('User is already member of that club'))
......
......@@ -18,6 +18,7 @@ from subscription.models import Subscription
from counter.models import Customer, ProductType, Product, Counter
from com.models import Sith, Weekmail
from election.models import Election, Role, Candidature, ElectionList
from forum.models import Forum, ForumMessage, ForumTopic
class Command(BaseCommand):
......@@ -37,7 +38,9 @@ class Command(BaseCommand):
Site(id=4000, domain=settings.SITH_URL, name=settings.SITH_NAME).save()
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
Group(name="Root").save()
Group(name="Not registered users").save()
Group(name="Public").save()
Group(name="Subscribers").save()
Group(name="Old subscribers").save()
Group(name="Accounting admin").save()
Group(name="Communication admin").save()
Group(name="Counter admin").save()
......@@ -45,6 +48,7 @@ class Command(BaseCommand):
Group(name="Banned from counters").save()
Group(name="Banned to subscribe").save()
Group(name="SAS admin").save()
Group(name="Forum admin").save()
self.reset_index("core", "auth")
root = User(id=0, username='root', last_name="", first_name="Bibou",
email="ae.info@utbm.fr",
......@@ -429,3 +433,15 @@ Welcome to the wiki page!
cand = Candidature(role=pres, user=sli, election_list=listeT, program="En fait j'aime pas l'info, je voulais faire GMC")
cand.save()
# Forum
room = Forum(name="Salon de discussions", description="Pour causer de tout", is_category=True)
room.save()
Forum(name="AE", description="Réservé au bureau AE", parent=room).save()
Forum(name="BdF", description="Réservé au bureau BdF", parent=room).save()
hall = Forum(name="Hall de discussions", description="Pour toutes les discussions", parent=room)
hall.save()
various = Forum(name="Divers", description="Pour causer de rien", is_category=True)
various.save()
Forum(name="Promos", description="Réservé aux Promos", parent=various).save()
ForumTopic(forum=hall)
......@@ -7,9 +7,102 @@ class SithRenderer(Renderer):
def file_link(self, id, suffix):
return reverse('core:file_detail', kwargs={'file_id': id}) + suffix
def exposant(self, text):
return """<sup>%s</sup>""" % text
def indice(self, text):
return """<sub>%s</sub>""" % text
def underline(self, text):
return """<span class="underline">%s</span>""" % text
class SithInlineGrammar(InlineGrammar):
double_emphasis = re.compile(
r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word**
)
emphasis = re.compile(
r'^\*((?:\*\*|[^\*])+?)\*(?!\*)' # *word*
)
underline = re.compile(
r'^_{2}([\s\S]+?)_{2}(?!_)' # __word__
)
exposant = re.compile( # FIXME Does not work for now
r'^\^([\s\S]+?)\^' # ^text^
# r'|' # FIXME doesn't properly works like this
# r'^\^(\S+)' # ^word
)
indice = re.compile(
r'^_([\s\S]+?)_' # _text_ (^` hack, because no other solution were found :/ this sadly prevent code in indices)
# r'|' # FIXME doesn't properly works like this
# r'^_(\S+)' # _word
)
class SithInlineLexer(InlineLexer):
grammar_class = SithInlineGrammar
default_rules = [
'escape',
'inline_html',
'autolink',
'url',
'footnote',
'link',
'reflink',
'nolink',
'exposant',
'double_emphasis',
'emphasis',
'underline',
'indice',
'code',
'linebreak',
'strikethrough',
'text',
]
inline_html_rules = [
'escape',
'autolink',
'url',
'link',
'reflink',
'nolink',
'exposant',
'double_emphasis',
'emphasis',
'underline',
'indice',
'code',
'linebreak',
'strikethrough',
'text',
]
def output_underline(self, m):
text = m.group(1)
return self.renderer.underline(text)
def output_exposant(self, m):
text = m.group(1)
return self.renderer.exposant(text)
def output_indice(self, m):
text = m.group(1)
return self.renderer.indice(text)
# Double emphasis rule changed
def output_double_emphasis(self, m):
text = m.group(1)
text = self.output(text)
return self.renderer.double_emphasis(text)
# Emphasis rule changed
def output_emphasis(self, m):
text = m.group(1)
text = self.output(text)
return self.renderer.emphasis(text)
def _process_link(self, m, link, title=None):
try:
try: # Add page:// support for links
page = re.compile(
r'^page://(\S*)' # page://nom_de_ma_page
)
......@@ -17,7 +110,7 @@ class SithInlineLexer(InlineLexer):
page = match.group(1) or ""
link = reverse('core:page', kwargs={'page_name': page})
except: pass
try:
try: # Add file:// support for links
file_link = re.compile(
r'^file://(\d*)/?(\S*)?' # file://4000/download
)
......@@ -28,30 +121,48 @@ class SithInlineLexer(InlineLexer):
except: pass
return super(SithInlineLexer, self)._process_link(m, link, title)
# def enable_file_link(self):
# # add file_link rules
# self.rules.file_link = re.compile(
# r'dfile://(\d*)/?(\S*)?' # dfile://4000/download
# )
# # Add file_link parser to default rules
# # you can insert it some place you like
# # but place matters, maybe 2 is not good
# self.default_rules.insert(0, 'file_link')
# def output_file_link(self, m):
# id = m.group(1)
# suffix = m.group(2) or ""
# # you can create an custom render
# # you can also return the html if you like
# # return directly html like this:
# # return reverse('core:file_detail', kwargs={'file_id': id}) + suffix
# return self.renderer.file_link(id, suffix)
renderer = SithRenderer()
renderer = SithRenderer(escape=True)
inline = SithInlineLexer(renderer)
# enable the features
# inline.enable_file_link()
markdown = Markdown(renderer, inline=inline)
if __name__ == "__main__":
print(markdown.inline.default_rules)
print(markdown.inline.inline_html_rules)
text = """
## Basique
* Mettre le texte en **gras** : `**texte**`
* Mettre le texte en *italique* : `*texte*`
* __Souligner__ le texte : `__texte__`
* ~~Barrer du texte~~ : `~~texte~~`
* Mettre ^du texte^ en ^exposant^ : `^mot` ou `^texte^`
* _Mettre du texte_ en _indice_ : `_mot` ou `_texte_`
* Pied de page [^en pied de page]
## Blocs de citations
Un bloc de citation se crée ainsi :
```
> Ceci est
> un bloc de
> citation
```
> Ceci est
> un bloc de
> citation
Il est possible d'intégrer de la syntaxe Markdown-AE dans un tel bloc.
Petit *test* _sur_ ^une^ **seule** ^ligne pour voir^
"""
print(markdown(text))
......@@ -10,6 +10,8 @@ from django.conf import settings
from django.db import transaction
from django.contrib.staticfiles.storage import staticfiles_storage
from django.utils.html import escape
from django.utils.functional import cached_property
from phonenumber_field.modelfields import PhoneNumberField
from datetime import datetime, timedelta, date
......@@ -182,9 +184,11 @@ class User(AbstractBaseUser):
def to_dict(self):
return self.__dict__
@cached_property
def was_subscribed(self):
return self.subscriptions.exists()
@cached_property
def is_subscribed(self):
s = self.subscriptions.last()
return s.is_valid_now() if s is not None else False
......@@ -204,8 +208,12 @@ class User(AbstractBaseUser):
return False
if group_id == settings.SITH_GROUP_PUBLIC_ID:
return True
if group_id == settings.SITH_GROUP_SUBSCRIBERS_ID:
return self.is_subscribed
if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID:
return self.was_subscribed
if group_name == settings.SITH_MAIN_MEMBERS_GROUP: # We check the subscription if asked
return self.is_subscribed()
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)]
......@@ -226,25 +234,25 @@ class User(AbstractBaseUser):
return True
return self.groups.filter(name=group_name).exists()
@property
@cached_property
def is_root(self):
return self.is_superuser or self.groups.filter(id=settings.SITH_GROUP_ROOT_ID).exists()
@property
@cached_property
def is_board_member(self):
from club.models import Club
return Club.objects.filter(unix_name=settings.SITH_MAIN_CLUB['unix_name']).first().get_membership_for(self)
@property
@cached_property
def is_launderette_manager(self):
from club.models import Club
return Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first().get_membership_for(self)
@property
@cached_property
def is_banned_alcohol(self):
return self.is_in_group(settings.SITH_GROUP_BANNED_ALCOHOL_ID)
@property
@cached_property
def is_banned_counter(self):
return self.is_in_group(settings.SITH_GROUP_BANNED_COUNTER_ID)
......@@ -418,10 +426,20 @@ class User(AbstractBaseUser):
escape(self.get_display_name()),
)
@property
@cached_property
def subscribed(self):
return self.is_in_group(settings.SITH_MAIN_MEMBERS_GROUP)
@cached_property
def forum_infos(self):
try:
return self._forum_infos
except:
from forum.models import ForumUserInfo
infos = ForumUserInfo(user=self)
infos.save()
return infos
class AnonymousUser(AuthAnonymousUser):
def __init__(self, request):
super(AnonymousUser, self).__init__()
......@@ -652,12 +670,12 @@ class SithFile(models.Model):
else:
return super(SithFile, self).__getattribute__(attr)
@property
@cached_property
def as_picture(self):
from sas.models import Picture
return Picture.objects.filter(id=self.id).first()
@property
@cached_property
def as_album(self):
from sas.models import Album
return Album.objects.filter(id=self.id).first()
......
......@@ -11,6 +11,20 @@ a {
}
a:hover { color: #7FDBFF; }
a:active { color: #007BE6; }
.ib {
display: inline-block;
padding: 2px;
margin: 2px;
}
.w_big {
width: 75%;
}
.w_medium {
width: 45%;
}
.w_small {
width: 20%;
}
/*--------------------------------HEADER-------------------------------*/
#logo {
margin-left: 5%;
......@@ -189,7 +203,11 @@ ul, ol {
code {
font-family: monospace;
}
blockquote {
margin: 10px;
padding: 5px;
border: solid 1px black;
}
.edit-bar {
display: block;
margin: 4px;
......@@ -372,6 +390,47 @@ textarea {
display: inline;
}
/*------------------------------FORUM----------------------------------*/
.topic a, .forum a, .category a {
color: black;
}
.topic a:hover, .forum a:hover, .category a:hover {
color: #424242;
text-decoration: underline;
}
.topic {
border: solid skyblue 1px;
padding: 2px;
margin: 2px;
}
.forum {
background: lightblue;
padding: 2px;
margin: 2px;
}
.category {
background: skyblue;
}
.message {
padding: 2px;
margin: 2px;
background: skyblue;
}
.unread {
background: #e6ddad;
}
.message h5 {
font-size: 100%;
}
.msg_author {
display: inline-block;
width: 19%;
text-align: center;
}
.msg_author img {
max-width: 80%;
margin: 0px auto;
}
/*------------------------------SAS------------------------------------*/
.album {
display: inline-block;
......
......@@ -91,7 +91,7 @@
<a href="https://ae.utbm.fr/matmatronch/">{% trans %}Matmatronch{% endtrans %}</a>
<a href="{{ url('core:page', page_name="Index") }}">{% trans %}Wiki{% endtrans %}</a>
<a href="{{ url('sas:main') }}">{% trans %}SAS{% endtrans %}</a>
<a href="https://ae.utbm.fr/forum2/">{% trans %}Forum{% endtrans %}</a>
<a href="{{ url('forum:main') }}">{% trans %}Forum{% endtrans %}</a>
<a href="{{ url('core:page', "services") }}">{% trans %}Services{% endtrans %}</a>
<a href="{{ url('core:file_list') }}">{% trans %}Files{% endtrans %}</a>
<a href="https://ae.utbm.fr/article.php?name=liens">{% trans %}Sponsors{% endtrans %}</a>
......
......@@ -54,7 +54,7 @@
{% if user.memberships.filter(end_date=None).exists() or user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user == profile %}
{# if the user is member of a club, he can view the subscription state #}
<p>
{% if profile.is_subscribed() %}
{% if profile.is_subscribed %}
{% if user == profile or user.is_root or user.is_board_member %}
{{ user_subscription(profile) }}
{% endif %}
......
......@@ -91,7 +91,7 @@
<h4>{% trans %}Elections{% endtrans %}</h4>
<ul>
<li><a href="{{ url('election:list') }}">{% trans %}See available elections{% endtrans %}</a></li>
{%- if user.is_subscribed() -%}
{%- if user.is_subscribed -%}
<li><a href="{{ url('election:create') }}">{% trans %}Create a new election{% endtrans %}</a></li>
{%- endif -%}
</ul>
......
......@@ -10,7 +10,7 @@ register = template.Library()
@register.filter(is_safe=False)
@stringfilter
def markdown(text):
return mark_safe(md(escape(text)))
return mark_safe(md(text))
@register.filter()
@stringfilter
......
......@@ -253,8 +253,8 @@ http://git.an
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'}))
self.assertTrue(response.status_code == 200)
self.assertTrue('<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n' +
'<h1>Swag</h1>\\n<p>&lt;guy&gt;Bibou&lt;/guy&gt;</p>\\n' +
'<p>&lt;script&gt;alert(&#39;Guy&#39;);&lt;/script&gt;</p>' in str(response.content))
'<h1>Swag</h1>\\n&lt;guy&gt;Bibou&lt;/guy&gt;' +
"&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;" in str(response.content))
#TODO: many tests on the pages:
# - renaming a page
......
......@@ -347,7 +347,7 @@ class Selling(models.Model):
self.customer.save()
self.is_validated = True
u = User.objects.filter(id=self.customer.user.id).first()
if u.was_subscribed():
if u.was_subscribed:
if self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER:
sub = Subscription(
member=u,
......
......@@ -80,7 +80,7 @@ class EbouticMain(TemplateView):
kwargs['basket'] = self.basket
kwargs['eboutic'] = Counter.objects.filter(type="EBOUTIC").first()
kwargs['categories'] = ProductType.objects.all()
if not self.request.user.was_subscribed():
if not self.request.user.was_subscribed:
kwargs['categories'] = kwargs['categories'].exclude(id=settings.SITH_PRODUCTTYPE_SUBSCRIPTION)
return kwargs
......
......@@ -271,7 +271,7 @@ class ElectionCreateView(CanCreateMixin, CreateView):
template_name = 'core/create.jinja'
def dispatch(self, request, *args, **kwargs):
if not request.user.is_subscribed():
if not request.user.is_subscribed:
raise PermissionDenied
return super(ElectionCreateView, self).dispatch(request, *args, **kwargs)
......
from django.contrib import admin
from forum.models import *
admin.site.register(Forum)
admin.site.register(ForumTopic)
admin.site.register(ForumMessage)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('core', '0019_preferences_receive_weekmail'),
]
operations = [
migrations.CreateModel(
name='Forum',
fields=[
('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)),
('name', models.CharField(verbose_name='name', max_length=64)),
('description', models.CharField(default='', verbose_name='description', max_length=256)),
('is_category', models.BooleanField(default=False, verbose_name='is a category')),
('edit_groups', models.ManyToManyField(default=[4], related_name='editable_forums', blank=True, to='core.Group')),
('owner_group', models.ForeignKey(default=12, related_name='owned_forums', to='core.Group')),
('parent', models.ForeignKey(null=True, related_name='children', blank=True, to='forum.Forum')),
('view_groups', models.ManyToManyField(default=[2], related_name='viewable_forums', blank=True, to='core.Group')),
],
),
migrations.CreateModel(
name='ForumMessage',
fields=[
('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)),
('title', models.CharField(default='', verbose_name='title', blank=True, max_length=64)),
('message', models.TextField(default='', verbose_name='message')),
('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date')),
('author', models.ForeignKey(related_name='forum_messages', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['id'],
},
),
migrations.CreateModel(
name='ForumTopic',
fields=[
('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)),
('title', models.CharField(default='', verbose_name='title', max_length=64)),
('description', models.CharField(default='', verbose_name='description', max_length=256)),
('author', models.ForeignKey(related_name='forum_topics', to=settings.AUTH_USER_MODEL)),
('forum', models.ForeignKey(related_name='topics', to='forum.Forum')),
],
options={
'ordering': ['-id'],
},
),
migrations.AddField(
model_name='forummessage',
name='topic',
field=models.ForeignKey(related_name='messages', to='forum.ForumTopic'),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('forum', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='forum',
name='owner_group',
),
migrations.RemoveField(
model_name='forumtopic',
name='title',
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('club', '0006_auto_20161229_0040'),
('forum', '0002_auto_20170128_1958'),
]
operations = [
migrations.AddField(
model_name='forum',
name='owner_club',
field=models.ForeignKey(related_name='owned_forums', verbose_name='owner club', to='club.Club', default=1),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
from django.utils.timezone import utc
import datetime
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('forum', '0003_forum_owner_club'),
]
operations = [
migrations.CreateModel(
name='ForumUserInfo',
fields=[
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
('last_read_date', models.DateTimeField(verbose_name='last read date', default=datetime.datetime(1999, 1, 1, 0, 0, tzinfo=utc))),
('user', models.OneToOneField(related_name='_forum_infos', to=settings.AUTH_USER_MODEL)),
],
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('forum', '0004_forumuserinfo'),
]
operations = [
migrations.AddField(
model_name='forumuserinfo',
name='read_messages',
field=models.ManyToManyField(to='forum.ForumMessage', related_name='readers', verbose_name='read messages'),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('forum', '0005_forumuserinfo_read_messages'),
]
operations = [
migrations.RemoveField(
model_name='forumuserinfo',
name='read_messages',
),
migrations.AddField(
model_name='forummessage',
name='readers',
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, related_name='read_messages', verbose_name='readers'),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
from django.conf import settings