Ajout des commentaires de topics
On ne peut pas encore modifier le top comment ni commencer un topic à partir d'un thread externe, mais les bases sont là :)
This commit is contained in:
parent
089e851b4c
commit
662882cc15
|
@ -11,19 +11,27 @@ app.config.from_object(Config)
|
|||
db = SQLAlchemy(app)
|
||||
migrate = Migrate(app, db)
|
||||
|
||||
login = LoginManager(app)
|
||||
login.login_view = 'login'
|
||||
login.login_message = "Veuillez vous authentifier avant de continuer."
|
||||
|
||||
|
||||
from app.utils.converters import *
|
||||
app.url_map.converters['topicslug'] = TopicSlugConverter
|
||||
app.url_map.converters['forum'] = ForumConverter
|
||||
|
||||
|
||||
@app.before_request
|
||||
def request_time():
|
||||
g.request_start_time = time.time()
|
||||
g.request_time = lambda: "%.5fs" % (time.time() - g.request_start_time)
|
||||
|
||||
login = LoginManager(app)
|
||||
login.login_view = 'login'
|
||||
login.login_message = "Veuillez vous authentifier avant de continuer."
|
||||
|
||||
@app.context_processor
|
||||
def utilities_processor():
|
||||
return dict(
|
||||
len=len,
|
||||
enumerate=enumerate
|
||||
)
|
||||
|
||||
from app import models # IDK why this is here, but it works
|
||||
from app.models.comment import Comment
|
||||
|
@ -35,7 +43,7 @@ from app.models.notification import Notification
|
|||
from app.routes import index, search, users, tools # To load routes at initialization
|
||||
from app.routes.account import login, account, notification
|
||||
from app.routes.admin import index, groups, account, trophies, forums
|
||||
from app.routes.forum import index
|
||||
from app.routes.forum import index, topic
|
||||
|
||||
from app.utils import pluralize # To use pluralize into the templates
|
||||
from app.utils import date
|
||||
|
|
|
@ -9,44 +9,44 @@ import app.utils.validators as vd
|
|||
|
||||
class RegistrationForm(FlaskForm):
|
||||
username = StringField(
|
||||
'Pseudonyme',
|
||||
description='Ce nom est définitif !',
|
||||
'Pseudonyme',
|
||||
description='Ce nom est définitif !',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
vd.name_valid,
|
||||
DataRequired(),
|
||||
vd.name_valid,
|
||||
vd.name_available,
|
||||
],
|
||||
)
|
||||
email = StringField(
|
||||
'Adresse Email',
|
||||
'Adresse Email',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
Email(message="Addresse email invalide."),
|
||||
DataRequired(),
|
||||
Email(message="Addresse email invalide."),
|
||||
vd.email,
|
||||
],
|
||||
)
|
||||
password = PasswordField(
|
||||
'Mot de passe',
|
||||
'Mot de passe',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
DataRequired(),
|
||||
vd.password,
|
||||
],
|
||||
)
|
||||
password2 = PasswordField(
|
||||
'Répéter le mot de passe',
|
||||
'Répéter le mot de passe',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
DataRequired(),
|
||||
EqualTo('password', message="Les mots de passe doivent être identiques."),
|
||||
],
|
||||
)
|
||||
guidelines = BooleanField(
|
||||
"""J'accepte les <a href="#">CGU</a>""",
|
||||
"""J'accepte les <a href="#">CGU</a>""",
|
||||
validators=[
|
||||
DataRequired(),
|
||||
],
|
||||
)
|
||||
newsletter = BooleanField(
|
||||
'Inscription à la newsletter',
|
||||
'Inscription à la newsletter',
|
||||
description='Un mail par trimestre environ, pour être prévenu des concours, évènements et nouveautés.',
|
||||
)
|
||||
submit = SubmitField(
|
||||
|
@ -56,62 +56,62 @@ class RegistrationForm(FlaskForm):
|
|||
|
||||
class UpdateAccountForm(FlaskForm):
|
||||
avatar = FileField(
|
||||
'Avatar',
|
||||
'Avatar',
|
||||
validators=[
|
||||
Optional(),
|
||||
Optional(),
|
||||
vd.avatar,
|
||||
],
|
||||
)
|
||||
email = StringField(
|
||||
'Adresse email',
|
||||
'Adresse email',
|
||||
validators=[
|
||||
Optional(),
|
||||
Email(message="Addresse email invalide."),
|
||||
vd.email,
|
||||
Optional(),
|
||||
Email(message="Addresse email invalide."),
|
||||
vd.email,
|
||||
vd.old_password,
|
||||
],
|
||||
)
|
||||
password = PasswordField(
|
||||
'Mot de passe',
|
||||
'Mot de passe',
|
||||
validators=[
|
||||
Optional(),
|
||||
vd.password,
|
||||
Optional(),
|
||||
vd.password,
|
||||
vd.old_password,
|
||||
],
|
||||
)
|
||||
password2 = PasswordField(
|
||||
'Répéter le mot de passe',
|
||||
'Répéter le mot de passe',
|
||||
validators=[
|
||||
Optional(),
|
||||
Optional(),
|
||||
EqualTo('password', message="Les mots de passe doivent être identiques."),
|
||||
],
|
||||
)
|
||||
old_password = PasswordField(
|
||||
'Mot de passe actuel',
|
||||
'Mot de passe actuel',
|
||||
validators=[
|
||||
Optional(),
|
||||
],
|
||||
)
|
||||
birthday = DateField(
|
||||
'Anniversaire',
|
||||
'Anniversaire',
|
||||
validators=[
|
||||
Optional(),
|
||||
],
|
||||
)
|
||||
signature = TextAreaField(
|
||||
'Signature',
|
||||
'Signature',
|
||||
validators=[
|
||||
Optional(),
|
||||
]
|
||||
)
|
||||
biography = TextAreaField(
|
||||
'Présentation',
|
||||
'Présentation',
|
||||
validators=[
|
||||
Optional(),
|
||||
]
|
||||
)
|
||||
newsletter = BooleanField(
|
||||
'Inscription à la newsletter',
|
||||
'Inscription à la newsletter',
|
||||
description='Un mail par trimestre environ, pour être prévenu des concours, évènements et nouveautés.',
|
||||
)
|
||||
submit = SubmitField('Mettre à jour')
|
||||
|
@ -119,16 +119,16 @@ class UpdateAccountForm(FlaskForm):
|
|||
|
||||
class DeleteAccountForm(FlaskForm):
|
||||
delete = BooleanField(
|
||||
'Confirmer la suppression',
|
||||
'Confirmer la suppression',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
],
|
||||
],
|
||||
description='Attention, cette opération est irréversible !'
|
||||
)
|
||||
old_password = PasswordField(
|
||||
'Mot de passe',
|
||||
'Mot de passe',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
DataRequired(),
|
||||
vd.old_password,
|
||||
],
|
||||
)
|
||||
|
@ -139,66 +139,66 @@ class DeleteAccountForm(FlaskForm):
|
|||
|
||||
class AdminUpdateAccountForm(FlaskForm):
|
||||
username = StringField(
|
||||
'Pseudonyme',
|
||||
'Pseudonyme',
|
||||
validators=[
|
||||
Optional(),
|
||||
Optional(),
|
||||
vd.name_valid,
|
||||
],
|
||||
)
|
||||
avatar = FileField(
|
||||
'Avatar',
|
||||
'Avatar',
|
||||
validators=[
|
||||
Optional(),
|
||||
Optional(),
|
||||
vd.avatar,
|
||||
],
|
||||
)
|
||||
email = StringField(
|
||||
'Adresse email',
|
||||
'Adresse email',
|
||||
validators=[
|
||||
Optional(),
|
||||
Email(message="Addresse email invalide."),
|
||||
Optional(),
|
||||
Email(message="Addresse email invalide."),
|
||||
vd.email,
|
||||
],
|
||||
)
|
||||
email_validate = BooleanField(
|
||||
"Envoyer un email de validation à la nouvelle adresse",
|
||||
"Envoyer un email de validation à la nouvelle adresse",
|
||||
description="Si décoché, l'utilisateur devra demander explicitement un email "\
|
||||
"de validation, ou faire valider son adresse email par un administrateur.",
|
||||
)
|
||||
password = PasswordField(
|
||||
'Mot de passe',
|
||||
description="L'ancien mot de passe ne pourra pas être récupéré !",
|
||||
'Mot de passe',
|
||||
description="L'ancien mot de passe ne pourra pas être récupéré !",
|
||||
validators=[
|
||||
Optional(),
|
||||
Optional(),
|
||||
vd.password,
|
||||
],
|
||||
)
|
||||
xp = DecimalField(
|
||||
'XP',
|
||||
'XP',
|
||||
validators=[
|
||||
Optional(),
|
||||
]
|
||||
)
|
||||
birthday = DateField(
|
||||
'Anniversaire',
|
||||
'Anniversaire',
|
||||
validators=[
|
||||
Optional(),
|
||||
],
|
||||
)
|
||||
signature = TextAreaField(
|
||||
'Signature',
|
||||
'Signature',
|
||||
validators=[
|
||||
Optional(),
|
||||
],
|
||||
)
|
||||
biography = TextAreaField(
|
||||
'Présentation',
|
||||
'Présentation',
|
||||
validators=[
|
||||
Optional(),
|
||||
],
|
||||
)
|
||||
newsletter = BooleanField(
|
||||
'Inscription à la newsletter',
|
||||
'Inscription à la newsletter',
|
||||
description='Un mail par trimestre environ, pour être prévenu des concours, évènements et nouveautés.',
|
||||
)
|
||||
submit = SubmitField(
|
||||
|
@ -215,10 +215,10 @@ class AdminAccountEditTrophyForm(FlaskForm):
|
|||
|
||||
class AdminDeleteAccountForm(FlaskForm):
|
||||
delete = BooleanField(
|
||||
'Confirmer la suppression',
|
||||
'Confirmer la suppression',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
],
|
||||
],
|
||||
description='Attention, cette opération est irréversible !',
|
||||
)
|
||||
submit = SubmitField(
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import TextAreaField
|
||||
|
||||
class EditorForm(FlaskForm):
|
||||
"""
|
||||
A text editor with formatting buttons and help. A rendering macro is
|
||||
defined in the template widgets/editor.html.
|
||||
"""
|
||||
|
||||
# TODO: How to set DataRequired() dynamically?
|
||||
contents = TextAreaField()
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.contents.data
|
|
@ -1,9 +1,13 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, FormField, SubmitField
|
||||
from wtforms.validators import DataRequired
|
||||
from app.forms.editor import EditorForm
|
||||
from wtforms import StringField, FormField, SubmitField, TextAreaField
|
||||
from wtforms.validators import DataRequired, Length
|
||||
|
||||
class TopicCreationForm(FlaskForm):
|
||||
title = StringField('Nom du sujet', validators=[DataRequired()])
|
||||
message = FormField(EditorForm, 'Premier post')
|
||||
title = StringField('Nom du sujet',
|
||||
validators=[DataRequired(), Length(min=3, max=32)])
|
||||
message = TextAreaField('Message principal')
|
||||
submit = SubmitField('Créer le sujet')
|
||||
|
||||
class CommentForm(FlaskForm):
|
||||
message = TextAreaField('Commentaire')
|
||||
submit = SubmitField('Commenter')
|
||||
|
|
|
@ -29,6 +29,7 @@ class Comment(Post):
|
|||
|
||||
Post.__init__(self, author)
|
||||
self.thread = thread
|
||||
self.text = text
|
||||
|
||||
def edit(self, new_text):
|
||||
"""Edit a Comment's contents."""
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask_login import current_user
|
||||
from flask import request
|
||||
from flask import request, abort
|
||||
|
||||
from app.utils.render import render
|
||||
from app.forms.forum import TopicCreationForm
|
||||
|
@ -26,17 +26,17 @@ def forum_page(f):
|
|||
db.session.add(th)
|
||||
db.session.commit()
|
||||
|
||||
c = Comment(current_user, form.message.value, th)
|
||||
th.set_top_comment(c)
|
||||
t = Topic(f, current_user, form.title.data, th)
|
||||
|
||||
db.session.add(th)
|
||||
c = Comment(current_user, form.message.data, th)
|
||||
db.session.add(c)
|
||||
db.session.commit()
|
||||
|
||||
th.set_top_comment(c)
|
||||
db.session.merge(th)
|
||||
|
||||
t = Topic(f, current_user, form.title.data, th)
|
||||
db.session.add(t)
|
||||
db.session.commit()
|
||||
|
||||
return render('/forum/forum.html', f=f, form=form)
|
||||
print(th, c, t)
|
||||
|
||||
@app.route('/forum/<forum:f>/<topicslug:t>')
|
||||
def forum_topic(f, t):
|
||||
return render('/forum/topic.html', f=f, t=t)
|
||||
return render('/forum/forum.html', f=f, form=form)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
from flask_login import current_user
|
||||
from flask import request, redirect, url_for, flash
|
||||
|
||||
from app.utils.render import render
|
||||
from app.forms.forum import CommentForm
|
||||
from app.models.forum import Forum
|
||||
from app.models.topic import Topic
|
||||
from app.models.thread import Thread
|
||||
from app.models.comment import Comment
|
||||
from app import app, db
|
||||
|
||||
|
||||
@app.route('/forum/<forum:f>/<topicslug:t>', methods=['GET', 'POST'])
|
||||
def forum_topic(f, t):
|
||||
form = CommentForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
c = Comment(current_user, form.message.data, t.thread)
|
||||
db.session.add(c)
|
||||
db.session.commit()
|
||||
flash('Message envoyé', 'ok')
|
||||
# Redirect to empty the form
|
||||
return redirect(url_for('forum_topic', f=f, t=t))
|
||||
|
||||
# Update views
|
||||
t.views += 1
|
||||
db.session.merge(t)
|
||||
db.session.commit()
|
||||
|
||||
return render('/forum/topic.html', t=t, form=form)
|
|
@ -42,13 +42,22 @@ table.forumlist tr:nth-child(4n+3) {
|
|||
background: rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
|
||||
/* Topic table */
|
||||
|
||||
table.topiclist {
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
table.topiclist tr > *:nth-child(n+2) {
|
||||
/* This matches all children except the first column */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
table.forumlist th > td:last-child,
|
||||
table.forumlist tr > td:last-child,
|
||||
table.topiclist th > td:last-child,
|
||||
table.topiclist tr > td:last-child {
|
||||
width: 20%; text-align: center;
|
||||
}
|
||||
|
|
|
@ -10,25 +10,40 @@
|
|||
<p>{{ f.descr }}</p>
|
||||
|
||||
{% if f.topics %}
|
||||
<h2>Sujets</h2>
|
||||
<table class=topiclist>
|
||||
<tr><th>Sujet</th><th>Auteur</th><th>Date de création</th>
|
||||
<th>Commentaires</th><th>Vues</th></tr>
|
||||
|
||||
{% for t in f.topics %}
|
||||
<tr><td>{{ t.title }}</td>
|
||||
<td><a href='/user/{{ t.author.norm }}'>{{ t.author.name }}</a></td>
|
||||
<td>{{ t.date_created | date }}</td>
|
||||
<td>{{ t.comments | length }}</td>
|
||||
<td>{{ t.views }} </td></tr>
|
||||
<tr><td><a href='{{ url_for('forum_topic', f=t.forum, t=t) }}'>{{ t.title }}</a></td>
|
||||
<td><a href='{{ url_for('user', username=t.author.name) }}'>{{ t.author.name }}</a></td>
|
||||
<td>{{ t.date_created | date }}</td>
|
||||
<td>{{ t.thread.comments | length }}</td>
|
||||
<td>{{ t.views }} </td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
{% elif len(f.sub_forums) == 0 %}
|
||||
<p>Il n'y a aucun topic sur ce forum ! Animons-le vite !</p>
|
||||
{% endif %}
|
||||
|
||||
{% if len(f.sub_forums) > 0 %}
|
||||
<h2>Forums</h2>
|
||||
<table class=forumlist>
|
||||
<tr><th>{{ f.name }}</th><th>Nombre de sujets</th></tr>
|
||||
|
||||
{% for sf in f.sub_forums %}
|
||||
<tr><td><a href='/forum{{ sf.url }}'>{{ sf.name }}</td>
|
||||
<td>{{ sf.topics | length }}</td></tr>
|
||||
<tr><td>{{ sf.descr }}</td><td></td></tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if len(f.sub_forums) == 0 or (current_user.is_authenticated and current_user.priv('access-admin-board')) %}
|
||||
<div class=form>
|
||||
|
||||
<h2>Créer un nouveau sujet</h2>
|
||||
|
||||
<form action="" method="post" enctype="multipart/form-data">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
|
@ -40,10 +55,11 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{{ widget_editor.editor(form.message) }}
|
||||
{{ widget_editor.text_editor(form.message) }}
|
||||
|
||||
<div>{{ form.submit(class_='bg-green') }}</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
{% extends "base/base.html" %}
|
||||
{% import "widgets/editor.html" as widget_editor %}
|
||||
|
||||
{% block title %}
|
||||
<a href='/forum'>Forum de Planète Casio</a> » <h1>{{ t.forum.name }}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<h1>{{ t.title }}</h1>
|
||||
|
||||
<div>{{ t.thread.top_comment.text }}</div>
|
||||
|
||||
{% for i, c in enumerate(t.thread.comments) %}
|
||||
{% if c != t.thread.top_comment %}
|
||||
<div>{{ c.text }}</div>
|
||||
{% elif i != 0 %}
|
||||
<div>Ce message est le top comment</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class=form>
|
||||
<h3>Commenter le sujet</h3>
|
||||
<form action="" method="post" enctype="multipart/form-data">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{{ widget_editor.text_editor(form.message, label=False) }}
|
||||
|
||||
<div>{{ form.submit(class_='bg-green') }}</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -1,9 +1,9 @@
|
|||
{% macro editor(form) %}
|
||||
{% macro text_editor(field, label=True) %}
|
||||
<div class=editor>
|
||||
{{ form.hidden_tag() }}
|
||||
{{ form.label }}
|
||||
{{ form.contents() }}
|
||||
{% for error in form.contents.errors %}
|
||||
{{ field.label if label }}
|
||||
<div>Widgets. Lots of widgets :3</div>
|
||||
{{ field() }}
|
||||
{% for error in field.errors %}
|
||||
<span class=msgerror>{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -18,6 +18,7 @@ For more information, see the Werkzeug documentation:
|
|||
|
||||
from werkzeug.routing import BaseConverter, ValidationError
|
||||
from app.models.forum import Forum
|
||||
from app.models.topic import Topic
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
@ -48,10 +49,10 @@ class TopicSlugConverter(BaseConverter):
|
|||
if m is None:
|
||||
raise Exception(f"TopicSlugConverter: conversation failed")
|
||||
|
||||
return int(m[1], 10)
|
||||
return Topic.query.get_or_404(int(m[1], 10))
|
||||
|
||||
def to_url(self, topic_id):
|
||||
return str(topic_id)
|
||||
def to_url(self, topic):
|
||||
return str(topic.id)
|
||||
|
||||
# Export only the converter classes
|
||||
__all__ = "ForumConverter TopicSlugConverter".split()
|
||||
|
|
Loading…
Reference in New Issue