Browse Source

Merge branch 'master' into new-cli

sh4nks 8 years ago
parent
commit
7726bef567

+ 5 - 0
.gitignore

@@ -54,3 +54,8 @@ flaskbb/static/emoji/*
 bower_components
 bower_components
 node_modules
 node_modules
 .directory
 .directory
+
+# Ignore plugins
+flaskbb/plugins/*
+!flaskbb/plugins/__init__.py
+!flaskbb/plugins/portal/

+ 1 - 1
docs/installation.rst

@@ -3,7 +3,7 @@ Installation
 
 
 -  `Basic Setup <#basic-setup>`_
 -  `Basic Setup <#basic-setup>`_
 -  `Configuration <#configuration>`_
 -  `Configuration <#configuration>`_
--  `Deplyoing <#deploying>`_
+-  `Deploying <#deploying>`_
 
 
 
 
 
 

+ 0 - 2
flaskbb/_compat.py

@@ -16,7 +16,6 @@ if not PY2:     # pragma: no cover
     iterkeys = lambda d: iter(d.keys())
     iterkeys = lambda d: iter(d.keys())
     itervalues = lambda d: iter(d.values())
     itervalues = lambda d: iter(d.values())
     iteritems = lambda d: iter(d.items())
     iteritems = lambda d: iter(d.items())
-    max_integer = sys.maxsize
 else:           # pragma: no cover
 else:           # pragma: no cover
     text_type = unicode
     text_type = unicode
     string_types = (str, unicode)
     string_types = (str, unicode)
@@ -26,7 +25,6 @@ else:           # pragma: no cover
     iterkeys = lambda d: d.iterkeys()
     iterkeys = lambda d: d.iterkeys()
     itervalues = lambda d: d.itervalues()
     itervalues = lambda d: d.itervalues()
     iteritems = lambda d: d.iteritems()
     iteritems = lambda d: d.iteritems()
-    max_integer = sys.maxint
 
 
 
 
 def to_bytes(text):
 def to_bytes(text):

+ 7 - 4
flaskbb/auth/forms.py

@@ -17,7 +17,7 @@ from flask_babelplus import lazy_gettext as _
 
 
 from flaskbb.user.models import User
 from flaskbb.user.models import User
 from flaskbb.utils.helpers import time_utcnow
 from flaskbb.utils.helpers import time_utcnow
-from flaskbb.utils.recaptcha import RecaptchaField
+from flaskbb.utils.fields import RecaptchaField
 
 
 USERNAME_RE = r'^[\w.+-]+$'
 USERNAME_RE = r'^[\w.+-]+$'
 is_username = regexp(USERNAME_RE,
 is_username = regexp(USERNAME_RE,
@@ -32,13 +32,15 @@ class LoginForm(FlaskForm):
     password = PasswordField(_("Password"), validators=[
     password = PasswordField(_("Password"), validators=[
         DataRequired(message=_("Please enter your password."))])
         DataRequired(message=_("Please enter your password."))])
 
 
-    recaptcha = RecaptchaField(_("Captcha"))
-
     remember_me = BooleanField(_("Remember me"), default=False)
     remember_me = BooleanField(_("Remember me"), default=False)
 
 
     submit = SubmitField(_("Login"))
     submit = SubmitField(_("Login"))
 
 
 
 
+class LoginRecaptchaForm(LoginForm):
+    recaptcha = RecaptchaField(_("Captcha"))
+
+
 class RegisterForm(FlaskForm):
 class RegisterForm(FlaskForm):
     username = StringField(_("Username"), validators=[
     username = StringField(_("Username"), validators=[
         DataRequired(message=_("A valid username is required.")),
         DataRequired(message=_("A valid username is required.")),
@@ -58,7 +60,8 @@ class RegisterForm(FlaskForm):
 
 
     language = SelectField(_('Language'))
     language = SelectField(_('Language'))
 
 
-    accept_tos = BooleanField(_("I accept the Terms of Service"), default=True)
+    accept_tos = BooleanField(_("I accept the Terms of Service"), validators=[
+        DataRequired(message=_("Please accept the TOS."))], default=True)
 
 
     submit = SubmitField(_("Register"))
     submit = SubmitField(_("Register"))
 
 

+ 8 - 4
flaskbb/auth/views.py

@@ -21,9 +21,10 @@ from flaskbb.utils.helpers import (render_template, redirect_or_next,
                                    format_timedelta)
                                    format_timedelta)
 from flaskbb.email import send_reset_token, send_activation_token
 from flaskbb.email import send_reset_token, send_activation_token
 from flaskbb.exceptions import AuthenticationError
 from flaskbb.exceptions import AuthenticationError
-from flaskbb.auth.forms import (LoginForm, ReauthForm, ForgotPasswordForm,
-                                ResetPasswordForm, RegisterForm,
-                                AccountActivationForm, RequestActivationForm)
+from flaskbb.auth.forms import (LoginForm, LoginRecaptchaForm, ReauthForm,
+                                ForgotPasswordForm, ResetPasswordForm,
+                                RegisterForm, AccountActivationForm,
+                                RequestActivationForm)
 from flaskbb.user.models import User
 from flaskbb.user.models import User
 from flaskbb.fixtures.settings import available_languages
 from flaskbb.fixtures.settings import available_languages
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
@@ -85,7 +86,10 @@ def login():
         stats_diff = flaskbb_config["AUTH_REQUESTS"] - window_stats[1]
         stats_diff = flaskbb_config["AUTH_REQUESTS"] - window_stats[1]
         login_recaptcha = stats_diff >= flaskbb_config["LOGIN_RECAPTCHA"]
         login_recaptcha = stats_diff >= flaskbb_config["LOGIN_RECAPTCHA"]
 
 
-    form = LoginForm(request.form)
+    form = LoginForm()
+    if login_recaptcha and flaskbb_config["RECAPTCHA_ENABLED"]:
+        form = LoginRecaptchaForm()
+
     if form.validate_on_submit():
     if form.validate_on_submit():
         try:
         try:
             user = User.authenticate(form.login.data, form.password.data)
             user = User.authenticate(form.login.data, form.password.data)

+ 1 - 0
flaskbb/configs/default.py

@@ -172,6 +172,7 @@ class DefaultConfig(object):
     # Celery
     # Celery
     CELERY_BROKER_URL = 'redis://localhost:6379'
     CELERY_BROKER_URL = 'redis://localhost:6379'
     CELERY_RESULT_BACKEND = 'redis://localhost:6379'
     CELERY_RESULT_BACKEND = 'redis://localhost:6379'
+    if not REDIS_ENABLED: CELERY_ALWAYS_EAGER=True
 
 
     # FlaskBB Settings
     # FlaskBB Settings
     # ------------------------------ #
     # ------------------------------ #

+ 28 - 1
flaskbb/forum/models.py

@@ -15,7 +15,7 @@ from sqlalchemy.orm import aliased
 
 
 from flaskbb.extensions import db
 from flaskbb.extensions import db
 from flaskbb.utils.helpers import (slugify, get_categories_and_forums,
 from flaskbb.utils.helpers import (slugify, get_categories_and_forums,
-                                   get_forums, time_utcnow)
+                                   get_forums, time_utcnow, topic_is_unread)
 from flaskbb.utils.database import CRUDMixin, UTCDateTime
 from flaskbb.utils.database import CRUDMixin, UTCDateTime
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
 
 
@@ -322,6 +322,33 @@ class Topic(db.Model, CRUDMixin):
         """Returns the slugified url for the topic."""
         """Returns the slugified url for the topic."""
         return url_for("forum.view_topic", topic_id=self.id, slug=self.slug)
         return url_for("forum.view_topic", topic_id=self.id, slug=self.slug)
 
 
+    def first_unread(self, topicsread, user, forumsread=None):
+        """Returns the url to the first unread post if any else to the topic
+        itself.
+
+        :param topicsread: The topicsread object for the topic
+
+        :param user: The user who should be checked if he has read the last post
+                    in the topic
+
+        :param forumsread: The forumsread object in which the topic is. If you
+                        also want to check if the user has marked the forum as
+                        read, than you will also need to pass an forumsread
+                        object.
+        """
+
+        # If the topic is unread try to get the first unread post
+        if topic_is_unread(self, topicsread, user, forumsread):
+            # 
+            query = Post.query.filter(Post.topic_id == self.id)
+            if topicsread is not None:
+                query = query.filter(Post.date_created > topicsread.last_read)
+            post = query.order_by(Post.id.asc()).first()
+            if post is not None:
+                return post.url
+        
+        return self.url
+
     # Methods
     # Methods
     def __init__(self, title=None, user=None):
     def __init__(self, title=None, user=None):
         """Creates a topic object with some initial values.
         """Creates a topic object with some initial values.

+ 19 - 3
flaskbb/forum/views.py

@@ -438,23 +438,39 @@ def edit_post(post_id):
               "danger")
               "danger")
         return redirect(post.topic.url)
         return redirect(post.topic.url)
 
 
-    form = ReplyForm()
+    if post.first_post:
+        form = NewTopicForm()
+    else:
+        form = ReplyForm()
+
     if form.validate_on_submit():
     if form.validate_on_submit():
         if "preview" in request.form:
         if "preview" in request.form:
             return render_template(
             return render_template(
                 "forum/new_post.html", topic=post.topic,
                 "forum/new_post.html", topic=post.topic,
-                form=form, preview=form.content.data
+                form=form, preview=form.content.data, edit_mode=True
             )
             )
         else:
         else:
             form.populate_obj(post)
             form.populate_obj(post)
             post.date_modified = time_utcnow()
             post.date_modified = time_utcnow()
             post.modified_by = current_user.username
             post.modified_by = current_user.username
             post.save()
             post.save()
+
+            if post.first_post:
+                post.topic.title = form.title.data
+                post.topic.save()
             return redirect(post.topic.url)
             return redirect(post.topic.url)
     else:
     else:
+        if post.first_post:
+            form.title.data = post.topic.title
+
         form.content.data = post.content
         form.content.data = post.content
 
 
-    return render_template("forum/new_post.html", topic=post.topic, form=form)
+    return render_template(
+        "forum/new_post.html",
+        topic=post.topic,
+        form=form,
+        edit_mode=True
+    )
 
 
 
 
 @forum.route("/post/<int:post_id>/delete", methods=["POST"])
 @forum.route("/post/<int:post_id>/delete", methods=["POST"])

+ 2 - 4
flaskbb/management/forms.py

@@ -19,7 +19,6 @@ from sqlalchemy.orm.session import make_transient, make_transient_to_detached
 from flask_babelplus import lazy_gettext as _
 from flask_babelplus import lazy_gettext as _
 
 
 from flaskbb.utils.fields import BirthdayField
 from flaskbb.utils.fields import BirthdayField
-from flaskbb.utils.widgets import SelectBirthdayWidget
 from flaskbb.extensions import db
 from flaskbb.extensions import db
 from flaskbb.forum.models import Forum, Category
 from flaskbb.forum.models import Forum, Category
 from flaskbb.user.models import User, Group
 from flaskbb.user.models import User, Group
@@ -60,9 +59,8 @@ class UserForm(FlaskForm):
     password = PasswordField("Password", validators=[
     password = PasswordField("Password", validators=[
         Optional()])
         Optional()])
 
 
-    birthday = BirthdayField(_("Birthday"), format="%d %m %Y",
-                             widget=SelectBirthdayWidget(),
-                             validators=[Optional()])
+    birthday = BirthdayField(_("Birthday"), format="%d %m %Y", validators=[
+        Optional()])
 
 
     gender = SelectField(_("Gender"), default="None", choices=[
     gender = SelectField(_("Gender"), default="None", choices=[
         ("None", ""),
         ("None", ""),

+ 2 - 2
flaskbb/management/models.py

@@ -12,7 +12,7 @@ from wtforms import (TextField, IntegerField, FloatField, BooleanField,
                      SelectField, SelectMultipleField, validators)
                      SelectField, SelectMultipleField, validators)
 from flask_wtf import FlaskForm
 from flask_wtf import FlaskForm
 
 
-from flaskbb._compat import max_integer, text_type, iteritems
+from flaskbb._compat import text_type, iteritems
 from flaskbb.extensions import db, cache
 from flaskbb.extensions import db, cache
 from flaskbb.utils.database import CRUDMixin
 from flaskbb.utils.database import CRUDMixin
 
 
@@ -204,7 +204,7 @@ class Setting(db.Model, CRUDMixin):
         return settings
         return settings
 
 
     @classmethod
     @classmethod
-    @cache.memoize(timeout=max_integer)
+    @cache.cached(key_prefix="settings")
     def as_dict(cls, from_group=None, upper=True):
     def as_dict(cls, from_group=None, upper=True):
         """Returns all settings as a dict. This method is cached. If you want
         """Returns all settings as a dict. This method is cached. If you want
         to invalidate the cache, simply execute ``self.invalidate_cache()``.
         to invalidate the cache, simply execute ``self.invalidate_cache()``.

+ 1 - 1
flaskbb/templates/forum/forum.html

@@ -76,7 +76,7 @@
                         </div>
                         </div>
                         <div class="col-md-11 col-sm-10 col-xs-10">
                         <div class="col-md-11 col-sm-10 col-xs-10">
                             <div class="topic-name">
                             <div class="topic-name">
-                                <a href="{{ topic.url }}">{{ topic.title }}</a>
+                                <a href="{{ topic.first_unread(topicread, current_user, forumsread) }}">{{ topic.title }}</a>
                                 <!-- Topic Pagination -->
                                 <!-- Topic Pagination -->
                                 <span class="topic-pages">{{ topic_pages(topic, flaskbb_config["POSTS_PER_PAGE"]) }}</span>
                                 <span class="topic-pages">{{ topic_pages(topic, flaskbb_config["POSTS_PER_PAGE"]) }}</span>
                             </div>
                             </div>

+ 17 - 3
flaskbb/templates/forum/new_post.html

@@ -4,26 +4,40 @@
 {% extends theme("layout.html") %}
 {% extends theme("layout.html") %}
 
 
 {% block content %}
 {% block content %}
-{% from theme("macros.html") import render_quickreply, render_submit_field %}
+{% from theme("macros.html") import render_quickreply, render_submit_field, render_field %}
 
 
 <div class="page-view">
 <div class="page-view">
     <ol class="breadcrumb flaskbb-breadcrumb">
     <ol class="breadcrumb flaskbb-breadcrumb">
         <li><a href="{{ url_for('forum.index') }}">{% trans %}Forum{% endtrans %}</a></li>
         <li><a href="{{ url_for('forum.index') }}">{% trans %}Forum{% endtrans %}</a></li>
         <li><a href="{{ topic.forum.url }}">{{ topic.forum.title }}</a></li>
         <li><a href="{{ topic.forum.url }}">{{ topic.forum.title }}</a></li>
         <li><a href="{{ topic.url }}">{{ topic.title }}</a></li>
         <li><a href="{{ topic.url }}">{{ topic.title }}</a></li>
-        <li class="active">{% trans %}New Post{% endtrans %}</li>
+        <li class="active">
+            {% if edit_mode %}
+                {% trans %}Edit Post{% endtrans %}
+            {% else %}
+                {% trans %}New Post{% endtrans %}
+            {% endif %}
+        </li>
     </ol>
     </ol>
 
 
     <form class="form-horizontal" role="form" method="post">
     <form class="form-horizontal" role="form" method="post">
         {{ form.hidden_tag() }}
         {{ form.hidden_tag() }}
         <div class="panel page-panel">
         <div class="panel page-panel">
             <div class="panel-heading page-head">
             <div class="panel-heading page-head">
-                {% trans %}New Post{% endtrans %}
+                {% if edit_mode %}
+                    {% trans %}Edit Post{% endtrans %}
+                {% else %}
+                    {% trans %}New Post{% endtrans %}
+                {% endif %}
             </div>
             </div>
 
 
             <div class="panel-body page-body">
             <div class="panel-body page-body">
                 <div class="col-md-12 col-sm-12 col-xs-12">
                 <div class="col-md-12 col-sm-12 col-xs-12">
 
 
+                    {% if form.title %}
+                        {{ render_field(form.title, div_class="col-md-12 col-sm-12 col-xs-12") }}
+                    {% endif %}
+
                     <div class="form-group">
                     <div class="form-group">
                         <div class="col-md-12 col-sm-12 col-xs-12">
                         <div class="col-md-12 col-sm-12 col-xs-12">
                             <div class="editor-box">
                             <div class="editor-box">

+ 1 - 1
flaskbb/templates/forum/topic.html

@@ -65,7 +65,7 @@
                     <div class="post-meta clearfix">
                     <div class="post-meta clearfix">
                         <div class="pull-left">
                         <div class="pull-left">
                             <!-- Creation date / Date modified -->
                             <!-- Creation date / Date modified -->
-                            <a href="{{ generate_post_url(topic, post, posts.page) }}">
+                            <a href="{{ post.url }}">
                                 {{ post.date_created|format_date('%d %B %Y - %H:%M') }}
                                 {{ post.date_created|format_date('%d %B %Y - %H:%M') }}
                             </a>
                             </a>
                             {% if post.user_id and post.date_modified %}
                             {% if post.user_id and post.date_modified %}

+ 1 - 1
flaskbb/templates/forum/topic_horizontal.html

@@ -66,7 +66,7 @@
                     <div class="post-meta clearfix">
                     <div class="post-meta clearfix">
                         <div class="pull-left">
                         <div class="pull-left">
                             <!-- Creation date / Date modified -->
                             <!-- Creation date / Date modified -->
-                            <a href="{{ generate_post_url(topic, post, posts.page) }}">
+                            <a href="{{ post.url }}">
                                 {{ post.date_created|format_date('%d %B %Y - %H:%M') }}
                                 {{ post.date_created|format_date('%d %B %Y - %H:%M') }}
                             </a>
                             </a>
                             {% if post.user_id and post.date_modified %}
                             {% if post.user_id and post.date_modified %}

+ 1892 - 0
flaskbb/translations/fr/LC_MESSAGES/messages.po

@@ -0,0 +1,1892 @@
+# French translations for PROJECT.
+# Copyright (C) 2016 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# Mickael QUETELARD <mickael.quetelard@gmail.com>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: FlaskBB\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2016-06-09 11:35+0200\n"
+"PO-Revision-Date: 2016-11-07 18:34+0100\n"
+"Last-Translator: Mickael QUETELARD <mickael.quetelard@gmail.com>\n"
+"Language: fr\n"
+"Language-Team: fr <LL@li.org>\n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.3.4\n"
+
+#: flaskbb/email.py:26
+msgid "Password Recovery Confirmation"
+msgstr "Confirmation de récupération de mot de passe"
+
+#: flaskbb/email.py:48 flaskbb/templates/auth/account_activation.html:1
+#: flaskbb/templates/auth/account_activation.html:10
+msgid "Account Activation"
+msgstr "Activation du Compte"
+
+#: flaskbb/auth/forms.py:24 flaskbb/management/forms.py:32
+msgid "You can only use letters, numbers or dashes."
+msgstr "Vous ne pouvez utiliser que des lettres, des chiffres ou des tirets."
+
+#: flaskbb/auth/forms.py:28
+msgid "Username or Email address"
+msgstr "Nom d'utilisateur ou adresse Email"
+
+#: flaskbb/auth/forms.py:29
+msgid "Please enter your username or email address."
+msgstr "Veuillez entrer votre nom d'utilisateur ou votre adresse email."
+
+#: flaskbb/auth/forms.py:32 flaskbb/auth/forms.py:51 flaskbb/auth/forms.py:86
+#: flaskbb/auth/forms.py:109 flaskbb/user/forms.py:64
+msgid "Password"
+msgstr "Mot de passe"
+
+#: flaskbb/auth/forms.py:33 flaskbb/auth/forms.py:87 flaskbb/user/forms.py:65
+msgid "Please enter your password."
+msgstr "Veuillez entrer votre mot de passe."
+
+#: flaskbb/auth/forms.py:35 flaskbb/auth/forms.py:57 flaskbb/auth/forms.py:97
+msgid "Captcha"
+msgstr ""
+
+#: flaskbb/auth/forms.py:37
+msgid "Remember me"
+msgstr "Se souvenir de moi"
+
+#: flaskbb/auth/forms.py:39 flaskbb/templates/auth/login.html:1
+#: flaskbb/templates/auth/login.html:10 flaskbb/templates/layout.html:134
+#: flaskbb/templates/navigation.html:60
+msgid "Login"
+msgstr "Connexion"
+
+#: flaskbb/auth/forms.py:43 flaskbb/auth/forms.py:124
+#: flaskbb/management/forms.py:52 flaskbb/templates/forum/memberlist.html:43
+#: flaskbb/templates/forum/search_result.html:104
+#: flaskbb/templates/management/banned_users.html:62
+#: flaskbb/templates/management/users.html:62
+msgid "Username"
+msgstr "Nom d'utilisateur"
+
+#: flaskbb/auth/forms.py:44 flaskbb/auth/forms.py:125
+#: flaskbb/message/forms.py:23
+msgid "A valid username is required."
+msgstr "Un nom d'utilisateur valide est requis."
+
+#: flaskbb/auth/forms.py:47 flaskbb/auth/forms.py:93 flaskbb/auth/forms.py:105
+#: flaskbb/auth/forms.py:128 flaskbb/management/forms.py:56
+msgid "Email address"
+msgstr "Adresse email"
+
+#: flaskbb/auth/forms.py:48 flaskbb/auth/forms.py:94 flaskbb/auth/forms.py:106
+#: flaskbb/auth/forms.py:129 flaskbb/management/forms.py:57
+#: flaskbb/user/forms.py:37
+msgid "A valid email address is required."
+msgstr "Une adresse email valide est requise."
+
+#: flaskbb/auth/forms.py:49 flaskbb/auth/forms.py:130
+#: flaskbb/management/forms.py:58 flaskbb/user/forms.py:38
+#: flaskbb/user/forms.py:43 flaskbb/user/forms.py:46
+msgid "Invalid email address."
+msgstr "Adresse email invalide."
+
+#: flaskbb/auth/forms.py:53 flaskbb/auth/forms.py:111
+msgid "Passwords must match."
+msgstr "Les mots de passe doivent correspondre."
+
+#: flaskbb/auth/forms.py:55 flaskbb/auth/forms.py:113
+msgid "Confirm password"
+msgstr "Confirmation de mot de passe."
+
+#: flaskbb/auth/forms.py:59 flaskbb/user/forms.py:29
+msgid "Language"
+msgstr "Langue"
+
+#: flaskbb/auth/forms.py:61
+msgid "I accept the Terms of Service"
+msgstr "J'accepte les conditions d'utilisation"
+
+#: flaskbb/auth/forms.py:63 flaskbb/templates/auth/register.html:1
+#: flaskbb/templates/auth/register.html:10 flaskbb/templates/layout.html:139
+#: flaskbb/templates/navigation.html:66
+msgid "Register"
+msgstr "S'inscrire"
+
+#: flaskbb/auth/forms.py:68 flaskbb/management/forms.py:116
+msgid "This username is already taken."
+msgstr "Ce nom d'utilisateur est déjà utilisé."
+
+#: flaskbb/auth/forms.py:73 flaskbb/management/forms.py:130
+#: flaskbb/user/forms.py:60
+msgid "This email address is already taken."
+msgstr "Cette adresse email est déjà utilisé."
+
+#: flaskbb/auth/forms.py:89 flaskbb/templates/auth/reauth.html:1
+#: flaskbb/templates/auth/reauth.html:10
+msgid "Refresh Login"
+msgstr "Rafraichir la connexion"
+
+#: flaskbb/auth/forms.py:99
+msgid "Request Password"
+msgstr "Demande du mot de passe"
+
+#: flaskbb/auth/forms.py:115
+msgid "Reset password"
+msgstr "Réinitialiser le mot de passe"
+
+#: flaskbb/auth/forms.py:120
+msgid "Wrong email address."
+msgstr "Mauvaise adresse email."
+
+#: flaskbb/auth/forms.py:132
+msgid "Send Confirmation Mail"
+msgstr "Envois d'un mail de confirmation"
+
+#: flaskbb/auth/forms.py:138
+msgid "User does not exist."
+msgstr "L'utilisateur n'existe pas."
+
+#: flaskbb/auth/forms.py:141
+msgid "User is already active."
+msgstr "L'utilisateur est déjà actif."
+
+#: flaskbb/auth/forms.py:145
+msgid "Email confirmation token"
+msgstr "Token de confirmation d'email"
+
+#: flaskbb/auth/forms.py:146
+msgid "Please enter the token that we have sent to you."
+msgstr "Veuillez entrer le token que nous vous avons envoyé."
+
+#: flaskbb/auth/forms.py:150
+msgid "Confirm Email"
+msgstr "Confirmation d'email"
+
+#: flaskbb/auth/views.py:95
+msgid "Wrong username or password."
+msgstr "Nom d'utilisateur ou mot de passe erroné."
+
+#: flaskbb/auth/views.py:111
+msgid "Reauthenticated."
+msgstr "Ré-authentification."
+
+#: flaskbb/auth/views.py:114
+msgid "Wrong password."
+msgstr "Mot de passe erroné."
+
+#: flaskbb/auth/views.py:125
+msgid "Logged out"
+msgstr "Déconnecter"
+
+#: flaskbb/auth/views.py:136
+msgid "The registration has been disabled."
+msgstr "L'enregistrement a été désactivé."
+
+#: flaskbb/auth/views.py:150
+#, python-format
+msgid "An account activation email has been sent to %(email)s"
+msgstr "Un email d'activation de compte a été envoyé à %(email)s"
+
+#: flaskbb/auth/views.py:154
+msgid "Thanks for registering."
+msgstr "Merci pour votre inscription."
+
+#: flaskbb/auth/views.py:173
+msgid "Email sent! Please check your inbox."
+msgstr "Email envoyé! Veuillez vérifier votre boite mail."
+
+#: flaskbb/auth/views.py:176
+msgid ""
+"You have entered an username or email address that is not linked with "
+"your account."
+msgstr "Vous avez entré un nom d'utilisateur ou une adresse e-mail qui n'est pas liée "
+"à votre compte."
+
+#: flaskbb/auth/views.py:193
+msgid "Your password token is invalid."
+msgstr "Le token de votre mot de passe est invalide."
+
+#: flaskbb/auth/views.py:197
+msgid "Your password token is expired."
+msgstr "Le token de votre mot de passe est expiré."
+
+#: flaskbb/auth/views.py:203
+msgid "Your password has been updated."
+msgstr "Votre mot de passe a été mis à jour."
+
+#: flaskbb/auth/views.py:214 flaskbb/auth/views.py:232
+msgid "This account is already activated."
+msgstr "Ce compte est déjà activé."
+
+#: flaskbb/auth/views.py:221
+msgid "A new account activation token has been sent to your email address."
+msgstr "Un nouveau token d'activation a été envoyé à votre adresse email."
+
+#: flaskbb/auth/views.py:245
+msgid "Your account activation token is invalid."
+msgstr "Votre token d'activation est invalid."
+
+#: flaskbb/auth/views.py:249
+msgid "Your account activation token is expired."
+msgstr "Votre token d'activation est expiré."
+
+#: flaskbb/auth/views.py:260
+msgid "Your account has been activated."
+msgstr "Votre compte a été activé."
+
+#: flaskbb/forum/forms.py:22
+msgid "Quick reply"
+msgstr "Réponse rapide"
+
+#: flaskbb/forum/forms.py:23 flaskbb/forum/forms.py:34
+#: flaskbb/forum/forms.py:55
+msgid "You cannot post a reply without content."
+msgstr "Vous ne pouvez pas poster une réponse sans contenu."
+
+#: flaskbb/forum/forms.py:25 flaskbb/forum/forms.py:39
+#: flaskbb/templates/forum/topic_controls.html:94
+msgid "Reply"
+msgstr "Répondre"
+
+#: flaskbb/forum/forms.py:33 flaskbb/forum/forms.py:54
+#: flaskbb/forum/forms.py:100
+msgid "Content"
+msgstr "Contenu"
+
+#: flaskbb/forum/forms.py:36 flaskbb/forum/forms.py:57
+msgid "Track this topic"
+msgstr "Suivre ce topic"
+
+#: flaskbb/forum/forms.py:40 flaskbb/forum/forms.py:61
+msgid "Preview"
+msgstr "Prévisualisation"
+
+#: flaskbb/forum/forms.py:51
+msgid "Topic title"
+msgstr "Titre du topic"
+
+#: flaskbb/forum/forms.py:52
+msgid "Please choose a title for your topic."
+msgstr "Veuillez choisir un titre pour votre topic."
+
+#: flaskbb/forum/forms.py:60
+msgid "Post Topic"
+msgstr "Poster un Topic"
+
+#: flaskbb/forum/forms.py:73 flaskbb/templates/management/reports.html:39
+#: flaskbb/templates/management/unread_reports.html:39
+msgid "Reason"
+msgstr "Raison"
+
+#: flaskbb/forum/forms.py:74
+msgid "What is the reason for reporting this post?"
+msgstr "Quelle est la raison de ce post"
+
+#: flaskbb/forum/forms.py:77
+msgid "Report post"
+msgstr "Post de rapport"
+
+#: flaskbb/forum/forms.py:85 flaskbb/forum/forms.py:89
+#: flaskbb/forum/forms.py:104 flaskbb/templates/forum/memberlist.html:26
+#: flaskbb/templates/forum/search_form.html:1
+#: flaskbb/templates/forum/search_form.html:10
+#: flaskbb/templates/forum/search_form.html:15
+#: flaskbb/templates/forum/search_result.html:1
+#: flaskbb/templates/forum/search_result.html:10
+#: flaskbb/templates/layout.html:75
+#: flaskbb/templates/management/banned_users.html:39
+#: flaskbb/templates/management/users.html:39
+#: flaskbb/templates/navigation.html:21
+msgid "Search"
+msgstr "Rechercher"
+
+#: flaskbb/forum/forms.py:97
+msgid "Criteria"
+msgstr "Critère"
+
+#: flaskbb/forum/forms.py:101
+msgid "Post"
+msgstr "Poster"
+
+#: flaskbb/forum/forms.py:101 flaskbb/templates/forum/edit_forum.html:31
+#: flaskbb/templates/forum/forum.html:49
+#: flaskbb/templates/forum/search_result.html:134
+#: flaskbb/templates/forum/topictracker.html:31
+#: flaskbb/templates/management/reports.html:38
+#: flaskbb/templates/management/unread_reports.html:38
+msgid "Topic"
+msgstr ""
+
+#: flaskbb/forum/forms.py:102 flaskbb/templates/forum/category.html:9
+#: flaskbb/templates/forum/category_layout.html:8
+#: flaskbb/templates/forum/edit_forum.html:13
+#: flaskbb/templates/forum/forum.html:10
+#: flaskbb/templates/forum/memberlist.html:9
+#: flaskbb/templates/forum/new_post.html:11
+#: flaskbb/templates/forum/new_topic.html:11
+#: flaskbb/templates/forum/search_form.html:9
+#: flaskbb/templates/forum/search_result.html:9
+#: flaskbb/templates/forum/search_result.html:213
+#: flaskbb/templates/forum/topic.html:10
+#: flaskbb/templates/forum/topic_horizontal.html:14
+#: flaskbb/templates/forum/topictracker.html:14
+#: flaskbb/templates/layout.html:73
+#: flaskbb/templates/management/banned_users.html:8
+#: flaskbb/templates/management/category_form.html:8
+#: flaskbb/templates/management/forum_form.html:8
+#: flaskbb/templates/management/forums.html:7
+#: flaskbb/templates/management/forums.html:62
+#: flaskbb/templates/management/group_form.html:8
+#: flaskbb/templates/management/groups.html:7
+#: flaskbb/templates/management/overview.html:7
+#: flaskbb/templates/management/plugins.html:7
+#: flaskbb/templates/management/reports.html:8
+#: flaskbb/templates/management/settings.html:7
+#: flaskbb/templates/management/unread_reports.html:8
+#: flaskbb/templates/management/user_form.html:8
+#: flaskbb/templates/management/users.html:8
+#: flaskbb/templates/message/message_layout.html:6
+#: flaskbb/templates/navigation.html:19 flaskbb/templates/user/all_posts.html:6
+#: flaskbb/templates/user/all_topics.html:6
+#: flaskbb/templates/user/profile.html:5
+#: flaskbb/templates/user/settings_layout.html:6
+msgid "Forum"
+msgstr ""
+
+#: flaskbb/forum/forms.py:102 flaskbb/templates/forum/search_result.html:99
+#: flaskbb/templates/management/management_layout.html:13
+#: flaskbb/templates/management/users.html:1
+#: flaskbb/templates/management/users.html:34
+msgid "Users"
+msgstr "Utilisateurs"
+
+#: flaskbb/forum/views.py:167
+msgid "You do not have the permissions to create a new topic."
+msgstr "Vous n'avez pas la permission de créer un nouveau topic."
+
+#: flaskbb/forum/views.py:195 flaskbb/utils/helpers.py:109
+msgid "You do not have the permissions to delete this topic."
+msgstr "Vous n'avez pas la permission de supprimer ce topic."
+
+#: flaskbb/forum/views.py:212
+msgid "You do not have the permissions to lock this topic."
+msgstr "Vous n'avez pas la permission de fermer ce topic."
+
+#: flaskbb/forum/views.py:228
+msgid "You do not have the permissions to unlock this topic."
+msgstr "Vous n'avez pas la permission de débloquer ce topic."
+
+#: flaskbb/forum/views.py:244
+msgid "You do not have the permissions to highlight this topic."
+msgstr "Vous n'avez pas la permission de mettre en évidence ce sujet."
+
+#: flaskbb/forum/views.py:261
+msgid "You do not have the permissions to trivialize this topic."
+msgstr "Vous n'avez pas la permission de banaliser ce sujet.
+
+#: flaskbb/forum/views.py:284
+msgid "You do not have the permissions to moderate this forum."
+msgstr "Vous n'avez pas la permission de modérer ce forum."
+
+#: flaskbb/forum/views.py:306
+msgid "In order to perform this action you have to select at least one topic."
+msgstr "Pour exécuter cette action, vous devez sélectionner au moins un sujet."
+
+#: flaskbb/forum/views.py:315
+#, python-format
+msgid "%(count)s topics locked."
+msgstr "%(count)s sujets fermés."
+
+#: flaskbb/forum/views.py:321
+#, python-format
+msgid "%(count)s topics unlocked."
+msgstr "%(count)s sujets débloqués."
+
+#: flaskbb/forum/views.py:328
+#, python-format
+msgid "%(count)s topics highlighted."
+msgstr "%(count)s sujets mis en avant."
+
+#: flaskbb/forum/views.py:334
+#, python-format
+msgid "%(count)s topics trivialized."
+msgstr "%(count)s sujets banalisés."
+
+#: flaskbb/forum/views.py:341
+#, python-format
+msgid "%(count)s topics deleted."
+msgstr "%(count)s sujets supprimés."
+
+#: flaskbb/forum/views.py:349
+msgid "Please choose a new forum for the topics."
+msgstr "Veuillez choisir un nouveau forum pour les sujets."
+
+#: flaskbb/forum/views.py:361
+msgid "You do not have the permissions to move this topic."
+msgstr "Vous n'avez pas la permission de déplacer ce sujet."
+
+#: flaskbb/forum/views.py:381 flaskbb/forum/views.py:408
+msgid "You do not have the permissions to post in this topic."
+msgstr "Vous n'avez pas la permission de poster dans ce sujet."
+
+#: flaskbb/forum/views.py:434
+msgid "You do not have the permissions to edit this post."
+msgstr "Vous n'avez pas la permission d'éditer ce sujet."
+
+#: flaskbb/forum/views.py:465
+msgid "You do not have the permissions to delete this post."
+msgstr "Vous n'avez pas la permissions de supprimer ce message."
+
+#: flaskbb/forum/views.py:489
+msgid "Thanks for reporting."
+msgstr "Merci pour le rapport."
+
+#: flaskbb/forum/views.py:525
+#, python-format
+msgid "Forum %(forum)s marked as read."
+msgstr "Le forum %(forum)s marqué comme lu."
+
+#: flaskbb/forum/views.py:547
+msgid "All forums marked as read."
+msgstr "Tous les forum sont marqué comme lus."
+
+#: flaskbb/management/forms.py:53
+msgid "Please enter a username."
+msgstr "Veuillez entrer un nom d'utilisateur."
+
+#: flaskbb/management/forms.py:63 flaskbb/user/forms.py:82
+msgid "Birthday"
+msgstr "Date d'anniversaire"
+
+#: flaskbb/management/forms.py:67 flaskbb/user/forms.py:86
+msgid "Gender"
+msgstr "Genre"
+
+#: flaskbb/management/forms.py:69 flaskbb/user/forms.py:88
+msgid "Male"
+msgstr "Homme"
+
+#: flaskbb/management/forms.py:70 flaskbb/user/forms.py:89
+msgid "Female"
+msgstr "Femme"
+
+#: flaskbb/management/forms.py:72 flaskbb/user/forms.py:91
+msgid "Location"
+msgstr "Localité"
+
+#: flaskbb/management/forms.py:75 flaskbb/templates/forum/search_result.html:48
+#: flaskbb/templates/forum/topic.html:52
+#: flaskbb/templates/forum/topic_horizontal.html:63
+#: flaskbb/templates/message/conversation.html:47
+#: flaskbb/templates/message/conversation.html:101 flaskbb/user/forms.py:94
+msgid "Website"
+msgstr "Site internet"
+
+#: flaskbb/management/forms.py:78 flaskbb/user/forms.py:97
+msgid "Avatar"
+msgstr ""
+
+#: flaskbb/management/forms.py:81
+msgid "Forum signature"
+msgstr "Signature"
+
+#: flaskbb/management/forms.py:84 flaskbb/user/forms.py:103
+msgid "Notes"
+msgstr ""
+
+#: flaskbb/management/forms.py:87
+msgid "Is active?"
+msgstr "Est actif ?"
+
+#: flaskbb/management/forms.py:91
+msgid "Primary group"
+msgstr "Groupe principale"
+
+#: flaskbb/management/forms.py:96
+msgid "Secondary groups"
+msgstr "Groupe secondaire"
+
+#: flaskbb/management/forms.py:102 flaskbb/management/forms.py:216
+#: flaskbb/management/forms.py:336 flaskbb/management/forms.py:416
+#: flaskbb/templates/management/settings.html:59 flaskbb/user/forms.py:32
+#: flaskbb/user/forms.py:48 flaskbb/user/forms.py:74 flaskbb/user/forms.py:106
+msgid "Save"
+msgstr "Sauvegarder"
+
+#: flaskbb/management/forms.py:151
+msgid "Group name"
+msgstr "Nom du groupe"
+
+#: flaskbb/management/forms.py:152
+msgid "Please enter a name for the group."
+msgstr "Veuillez entrer un nom pour le groupe."
+
+#: flaskbb/management/forms.py:154 flaskbb/management/forms.py:287
+#: flaskbb/management/forms.py:404 flaskbb/templates/management/groups.html:35
+msgid "Description"
+msgstr ""
+
+#: flaskbb/management/forms.py:158
+msgid "Is 'Admin' group?"
+msgstr "Est un groupe Admin ?"
+
+#: flaskbb/management/forms.py:159
+msgid "With this option the group has access to the admin panel."
+msgstr "Avec cette option le groupe a accès au panneau d'administraton."
+
+#: flaskbb/management/forms.py:163
+msgid "Is 'Super Moderator' group?"
+msgstr "Est un groupe de Super Modérateur ?"
+
+#: flaskbb/management/forms.py:164
+msgid ""
+"Check this, if the users in this group are allowed to moderate every "
+"forum."
+msgstr ""
+"Vérifier ceci, si les utilisateurs de ce groupe ont la'autorisation"
+"de modérer chaque forum."
+
+#: flaskbb/management/forms.py:168
+msgid "Is 'Moderator' group?"
+msgstr "Est un groupe Modérateur ?"
+
+#: flaskbb/management/forms.py:169
+msgid ""
+"Check this, if the users in this group are allowed to moderate specified "
+"forums."
+msgstr ""
+"Vérifier ceci, si les utilisateurs de ce groupe ont l'autorisation de modérer des"
+"forums spécifiques."
+
+#: flaskbb/management/forms.py:173
+msgid "Is 'Banned' group?"
+msgstr "Est un groupe bannis ?"
+
+#: flaskbb/management/forms.py:174
+msgid "Only one group of type 'Banned' is allowed."
+msgstr "Un seul groupe de type 'Bannis' est autorisé."
+
+#: flaskbb/management/forms.py:177
+msgid "Is 'Guest' group?"
+msgstr "Est un groupe Visiteur ?"
+
+#: flaskbb/management/forms.py:178
+msgid "Only one group of type 'Guest' is allowed."
+msgstr "Un seul groupe de type Visiteur est autorisé."
+
+#: flaskbb/management/forms.py:181
+msgid "Can edit posts"
+msgstr "Peut modifier les messaes"
+
+#: flaskbb/management/forms.py:182
+msgid "Check this, if the users in this group can edit posts."
+msgstr "Vérifier ceci, si les utilisateurs de ce groupe peuvent modifier les messages."
+
+#: flaskbb/management/forms.py:185
+msgid "Can delete posts"
+msgstr "Peut supprimer les sujets"
+
+#: flaskbb/management/forms.py:186
+msgid "Check this, if the users in this group can delete posts."
+msgstr "Vérifier ceci, si les utilisateurs de ce groupe peuvent supprimer les messages."
+
+#: flaskbb/management/forms.py:190
+msgid "Can delete topics"
+msgstr "Peut supprimer les sujets"
+
+#: flaskbb/management/forms.py:191
+msgid "Check this, if the users in this group can delete topics."
+msgstr "Vérifier ceci, si les utilisateurs de ce groupe peuvent supprimer les sujets."
+
+#: flaskbb/management/forms.py:195
+msgid "Can create topics"
+msgstr "Peut créer des sujets"
+
+#: flaskbb/management/forms.py:196
+msgid "Check this, if the users in this group can create topics."
+msgstr "Vérifier ceci, si les utilisateurs de ce groupe peuvent créer des sujets."
+
+#: flaskbb/management/forms.py:200
+msgid "Can post replies"
+msgstr "Peut envoyer des réponses"
+
+#: flaskbb/management/forms.py:201
+msgid "Check this, if the users in this group can post replies."
+msgstr "Véririfer ceci, si les utilisateurs de ce groupe peuvent envoyer des réponses."
+
+#: flaskbb/management/forms.py:206
+msgid "Moderators can edit user profiles"
+msgstr "Les modérateurs peuvent modifier les profils utilisateur"
+
+#: flaskbb/management/forms.py:207
+msgid ""
+"Allow moderators to edit another user's profile including password and "
+"email changes."
+msgstr "Permettre aux modérateurs de modifier d'autre profil utilisateur incluant "
+"les changement de mot de passe et d'adresse email."
+
+#: flaskbb/management/forms.py:212
+msgid "Moderators can ban users"
+msgstr "Les modérateurs peuvent bannir les utilisateurs"
+
+#: flaskbb/management/forms.py:213
+msgid "Allow moderators to ban other users."
+msgstr "Permettre aux modérateurs de bannir d'autres utilisateurs."
+
+#: flaskbb/management/forms.py:230
+msgid "This group name is already taken."
+msgstr "Ce nom de groupe est déjà pris."
+
+#: flaskbb/management/forms.py:244
+msgid "There is already a group of type 'Banned'."
+msgstr "Il y a déjà un groupe de type 'Bannis'."
+
+#: flaskbb/management/forms.py:259
+msgid "There is already a group of type 'Guest'."
+msgstr "Il y a déjà un groupe de type 'Visiteur'."
+
+#: flaskbb/management/forms.py:282
+msgid "Forum title"
+msgstr "Titre du forum"
+
+#: flaskbb/management/forms.py:283
+msgid "Please enter a forum title."
+msgstr "Veuillez entrer un titre de forum."
+
+#: flaskbb/management/forms.py:289 flaskbb/management/forms.py:406
+msgid "You can format your description with Markdown."
+msgstr "Vous pouvez écrire votre description au format Markdown."
+
+#: flaskbb/management/forms.py:293 flaskbb/management/forms.py:410
+msgid "Position"
+msgstr ""
+
+#: flaskbb/management/forms.py:295
+msgid "Please enter a position for theforum."
+msgstr "Veuillez entrer une position pour le forum."
+
+#: flaskbb/management/forms.py:300
+msgid "Category"
+msgstr "Catégorie"
+
+#: flaskbb/management/forms.py:304
+msgid "The category that contains this forum."
+msgstr "La catégorie qui contient ce forum."
+
+#: flaskbb/management/forms.py:308
+msgid "External link"
+msgstr "Lien externe"
+
+#: flaskbb/management/forms.py:310
+msgid "A link to a website i.e. 'http://flaskbb.org'."
+msgstr "Un lien vers le site i.e. 'http://flaskbb.org'."
+
+#: flaskbb/management/forms.py:314
+#: flaskbb/templates/forum/category_layout.html:80
+#: flaskbb/templates/forum/search_result.html:283
+#: flaskbb/templates/management/forums.html:135
+msgid "Moderators"
+msgstr "Modérateurs"
+
+#: flaskbb/management/forms.py:315
+msgid ""
+"Comma separated usernames. Leave it blank if you do not want to set any "
+"moderators."
+msgstr "Nom d'utilisateur séparé par des virgules. Laissez-le vide si vous ne voulez pas définir de "
+"modérateurs."
+
+#: flaskbb/management/forms.py:320
+msgid "Show moderators"
+msgstr "Voir les modérateurs"
+
+#: flaskbb/management/forms.py:321
+msgid "Do you want to show the moderators on the index page?"
+msgstr "Voulez-vous voir les modérateurs sur la page d'index ?"
+
+#: flaskbb/management/forms.py:325
+msgid "Locked?"
+msgstr "Fermer ?"
+
+#: flaskbb/management/forms.py:326
+msgid "Disable new posts and topics in this forum."
+msgstr "Désactiver les nouveaux sujets dans ce forum."
+
+#: flaskbb/management/forms.py:330
+msgid "Group access"
+msgstr "Accès au groupe"
+
+#: flaskbb/management/forms.py:333
+msgid "Select the groups that can access this forum."
+msgstr "Sélectionner les groupes qui peuvent accéder à ce forum."
+
+#: flaskbb/management/forms.py:341
+msgid "You cannot convert a forum that contains topics into an external link."
+msgstr "Vous ne pouvez pas convertir un forum qui contient des sujets dans un lien externe."
+
+#: flaskbb/management/forms.py:347
+msgid "You also need to specify some moderators."
+msgstr "Vous avez aussi besoin de spécifier des modérateurs."
+
+#: flaskbb/management/forms.py:359
+#, python-format
+msgid "%(user)s is not in a moderators group."
+msgstr "%'(user)s n'est pas dans un groupe de modérateurs."
+
+#: flaskbb/management/forms.py:400
+msgid "Category title"
+msgstr "Titre de catégorie"
+
+#: flaskbb/management/forms.py:401
+msgid "Please enter a category title."
+msgstr "Veuillez entrer un titre de catégorie."
+
+#: flaskbb/management/forms.py:412
+msgid "Please enter a position for the category."
+msgstr "Veuillez entrer une position pour la catégorie."
+
+#: flaskbb/management/views.py:111
+msgid "Settings saved."
+msgstr "Paramètres sauvegardés."
+
+#: flaskbb/management/views.py:150
+msgid "You are not allowed to edit this user."
+msgstr "Vous n'avez pas l'autorisation de modifier cet utilisateur."
+
+#: flaskbb/management/views.py:184
+msgid "User updated."
+msgstr "Utilisateur mis à jour."
+
+#: flaskbb/management/views.py:188
+msgid "Edit User"
+msgstr "Modifier l'utilisateur"
+
+#: flaskbb/management/views.py:223
+msgid "You cannot delete yourself."
+msgstr "Vous ne pouvez pas vous supprimer vous-même."
+
+#: flaskbb/management/views.py:227
+msgid "User deleted."
+msgstr "Utilisateur supprimé."
+
+#: flaskbb/management/views.py:237
+msgid "User added."
+msgstr "Utilisateur ajouté."
+
+#: flaskbb/management/views.py:241
+#: flaskbb/templates/management/banned_users.html:24
+#: flaskbb/templates/management/user_form.html:24
+#: flaskbb/templates/management/users.html:24
+msgid "Add User"
+msgstr "Ajouter un utilisateur"
+
+#: flaskbb/management/views.py:271
+msgid "You do not have the permissions to ban this user."
+msgstr "Vous n'avez pas la permission de bannir cette utilisateur."
+
+#: flaskbb/management/views.py:295
+#: flaskbb/templates/management/banned_users.html:96
+#: flaskbb/templates/management/users.html:122
+msgid "Unban"
+msgstr "Ré-habiliter"
+
+#: flaskbb/management/views.py:313
+msgid "A moderator cannot ban an admin user."
+msgstr "Un modérateur ne peut pas bannir un admin."
+
+#: flaskbb/management/views.py:317
+msgid "User is now banned."
+msgstr "L'utilisateur est maintenant bannis."
+
+#: flaskbb/management/views.py:319
+msgid "Could not ban user."
+msgstr "Vous ne pouvez pas bannir l'utilisateur."
+
+#: flaskbb/management/views.py:329
+msgid "You do not have the permissions to unban this user."
+msgstr "Vous n'avez pas la permission de ré-habiliter cet utilisateur."
+
+#: flaskbb/management/views.py:344 flaskbb/templates/management/users.html:112
+msgid "Ban"
+msgstr "Bannir"
+
+#: flaskbb/management/views.py:359
+msgid "User is now unbanned."
+msgstr "L'utilisateur est maintenant ré-habilité."
+
+#: flaskbb/management/views.py:361
+msgid "Could not unban user."
+msgstr "Vous nz pouvez pas ré-habiliter l'utilisateur."
+
+#: flaskbb/management/views.py:422
+#, python-format
+msgid "Report %(id)s is already marked as read."
+msgstr "Le rapport %(id)s est déjà marqué comme lu."
+
+#: flaskbb/management/views.py:429
+#, python-format
+msgid "Report %(id)s marked as read."
+msgstr "LE rapport %(id)s est marqué comme lu."
+
+#: flaskbb/management/views.py:443
+msgid "All reports were marked as read."
+msgstr "Tous les rapports ont été marqué comme lus."
+
+#: flaskbb/management/views.py:474
+msgid "Group updated."
+msgstr "Groupe mis à jour."
+
+#: flaskbb/management/views.py:478
+msgid "Edit Group"
+msgstr "Modifier le groupe"
+
+#: flaskbb/management/views.py:506
+msgid "You cannot delete one of the standard groups."
+msgstr "Vous ne pouvez pas supprimer un des groupes standard."
+
+#: flaskbb/management/views.py:514
+msgid "You cannot delete the standard groups. Try renaming it instead."
+msgstr "Vous ne pouvez pas supprimer les groupes standard. Essayez de le renommer à la place."
+
+#: flaskbb/management/views.py:520
+msgid "Group deleted."
+msgstr "Groupe supprimé."
+
+#: flaskbb/management/views.py:523
+msgid "No group chosen."
+msgstr "Aucun groupe choisi."
+
+#: flaskbb/management/views.py:533
+msgid "Group added."
+msgstr "Groupe ajouté."
+
+#: flaskbb/management/views.py:537
+#: flaskbb/templates/management/group_form.html:21
+#: flaskbb/templates/management/groups.html:20
+msgid "Add Group"
+msgstr "Ajouter un groupe"
+
+#: flaskbb/management/views.py:556
+msgid "Forum updated."
+msgstr "Forum mis à jour."
+
+#: flaskbb/management/views.py:567 flaskbb/templates/management/forums.html:154
+msgid "Edit Forum"
+msgstr "Modifier le forum"
+
+#: flaskbb/management/views.py:580
+msgid "Forum deleted."
+msgstr "Forum supprimé."
+
+#: flaskbb/management/views.py:592
+msgid "Forum added."
+msgstr "Forum ajouté."
+
+#: flaskbb/management/views.py:601
+#: flaskbb/templates/management/category_form.html:21
+#: flaskbb/templates/management/forum_form.html:21
+#: flaskbb/templates/management/forums.html:20
+#: flaskbb/templates/management/forums.html:44
+msgid "Add Forum"
+msgstr "Ajouter un forum"
+
+#: flaskbb/management/views.py:611
+msgid "Category added."
+msgstr "Catégorie ajoutée."
+
+#: flaskbb/management/views.py:615
+#: flaskbb/templates/management/category_form.html:22
+#: flaskbb/templates/management/forum_form.html:22
+#: flaskbb/templates/management/forums.html:21
+msgid "Add Category"
+msgstr "Ajouter un catégorie"
+
+#: flaskbb/management/views.py:627
+msgid "Category updated."
+msgstr "Catégorie mise à jour."
+
+#: flaskbb/management/views.py:631 flaskbb/templates/management/forums.html:47
+msgid "Edit Category"
+msgstr "Modifier la catégorie"
+
+#: flaskbb/management/views.py:644
+msgid "Category with all associated forums deleted."
+msgstr "Catégorie avec tous les forums associés supprimés."
+
+#: flaskbb/management/views.py:662
+#, python-format
+msgid "Plugin %(plugin)s is already enabled."
+msgstr "Le plugin %(plugin)s est déjà activé."
+
+#: flaskbb/management/views.py:668
+#, python-format
+msgid "Plugin %(plugin)s enabled. Please restart FlaskBB now."
+msgstr "Le plugin %(plugin)s est activé. Veuillez redémarrer FlaskBB maintenant."
+
+#: flaskbb/management/views.py:671
+msgid ""
+"It seems that FlaskBB does not have enough filesystem permissions. Try "
+"removing the 'DISABLED' file by yourself instead."
+msgstr "Il semble que FlaskBB ne dispose pas de permissions suffisantes sur le système de fichiers."
+"Essayez en supprimant le fichier 'DISABLED' par vous-même."
+
+#: flaskbb/management/views.py:684
+#, python-format
+msgid "Plugin %(plugin)s not found."
+msgstr "Le plugin %(plugin)s n'a pas été trouvé."
+
+#: flaskbb/management/views.py:689
+#, python-format
+msgid "Plugin %(plugin)s disabled. Please restart FlaskBB now."
+msgstr "Le plugin %(plugin)s est désactivé. Veuillez redémarrer FlaskBB maintenant."
+
+#: flaskbb/management/views.py:692
+msgid ""
+"It seems that FlaskBB does not have enough filesystem permissions. Try "
+"creating the 'DISABLED' file by yourself instead."
+msgstr "Il semble que FlaskBB ne dispose pas de permissions suffisantes sur le système de fichiers."
+"Essayez en créant le fichier 'DISABLED' par vous-même."
+
+#: flaskbb/management/views.py:707
+msgid "Plugin has been uninstalled."
+msgstr "Le plugin a été désinstallé."
+
+#: flaskbb/management/views.py:709
+msgid "Cannot uninstall plugin."
+msgstr "Le plugin ne peut pas être désinstallé."
+
+#: flaskbb/management/views.py:722
+msgid "Plugin has been installed."
+msgstr "Le plugin a été désinstallé."
+
+#: flaskbb/management/views.py:724
+msgid "Cannot install plugin."
+msgstr "Le plugin ne peut pas être installé."
+
+#: flaskbb/message/forms.py:22
+msgid "Recipient"
+msgstr "Destinataire"
+
+#: flaskbb/message/forms.py:25
+msgid "Subject"
+msgstr "Sujet"
+
+#: flaskbb/message/forms.py:26
+msgid "A Subject is required."
+msgstr "Un sujet est requis."
+
+#: flaskbb/message/forms.py:28 flaskbb/message/forms.py:59
+#: flaskbb/templates/forum/search_result.html:43
+#: flaskbb/templates/forum/topic.html:47
+#: flaskbb/templates/forum/topic_horizontal.html:58
+#: flaskbb/templates/message/conversation.html:97
+#: flaskbb/templates/user/profile_layout.html:47
+msgid "Message"
+msgstr ""
+
+#: flaskbb/message/forms.py:29 flaskbb/message/forms.py:60
+msgid "A message is required."
+msgstr "Un message est requis."
+
+#: flaskbb/message/forms.py:31
+msgid "Start Conversation"
+msgstr "Débuter la conversation"
+
+#: flaskbb/message/forms.py:32
+msgid "Save Conversation"
+msgstr "Sauvegarder la conversation"
+
+#: flaskbb/message/forms.py:37
+msgid "The username you entered does not exist."
+msgstr "Le nom d'utilisateur que vous avez entré n'existe pas.
+
+#: flaskbb/message/forms.py:40
+msgid "You cannot send a PM to yourself."
+msgstr "Vous ne pouvez pas vous envoyer de message."
+
+#: flaskbb/message/forms.py:61
+msgid "Send Message"
+msgstr "Envois du message"
+
+#: flaskbb/message/views.py:71 flaskbb/message/views.py:127
+msgid ""
+"You cannot send any messages anymore because you have reached your "
+"message limit."
+msgstr "Vous ne pouvez plus envoyer de message parce que vous avez atteint "
+"votre limite de message."
+
+#: flaskbb/message/views.py:144 flaskbb/message/views.py:216
+msgid "Message saved."
+msgstr "Message sauvegardé."
+
+#: flaskbb/message/views.py:169 flaskbb/message/views.py:234
+msgid "Message sent."
+msgstr "Message envoyé."
+
+#: flaskbb/message/views.py:175
+msgid "Compose Message"
+msgstr "Rédiger un message"
+
+#: flaskbb/message/views.py:202
+msgid "You cannot edit a sent message."
+msgstr "Vous ne pouvez pas modifier un message envoyé."
+
+#: flaskbb/message/views.py:242
+msgid "Edit Message"
+msgstr "Modifier un message"
+
+#: flaskbb/templates/forum/memberlist.html:1
+#: flaskbb/templates/forum/memberlist.html:10
+#: flaskbb/templates/forum/memberlist.html:36 flaskbb/templates/layout.html:74
+#: flaskbb/templates/navigation.html:20
+msgid "Memberlist"
+msgstr "Liste de membre"
+
+#: flaskbb/templates/layout.html:87 flaskbb/templates/layout.html:103
+#: flaskbb/templates/message/inbox.html:1
+#: flaskbb/templates/message/message_layout.html:18
+msgid "Inbox"
+msgstr "Boîte de réception"
+
+#: flaskbb/templates/layout.html:104
+#: flaskbb/templates/message/message_layout.html:16
+#: flaskbb/templates/navigation.html:54
+msgid "New Message"
+msgstr "Nouveau message"
+
+#: flaskbb/templates/forum/topictracker.html:1
+#: flaskbb/templates/forum/topictracker.html:15
+#: flaskbb/templates/forum/topictracker.html:26
+#: flaskbb/templates/layout.html:116 flaskbb/templates/navigation.html:35
+msgid "Topic Tracker"
+msgstr "Suivre le sujet"
+
+#: flaskbb/templates/layout.html:119
+#: flaskbb/templates/management/management_layout.html:17
+#: flaskbb/templates/navigation.html:38
+#: flaskbb/templates/user/settings_layout.html:8
+msgid "Settings"
+msgstr "Paramètres"
+
+#: flaskbb/templates/layout.html:121
+#: flaskbb/templates/management/banned_users.html:9
+#: flaskbb/templates/management/category_form.html:9
+#: flaskbb/templates/management/forum_form.html:9
+#: flaskbb/templates/management/forums.html:8
+#: flaskbb/templates/management/group_form.html:9
+#: flaskbb/templates/management/groups.html:8
+#: flaskbb/templates/management/overview.html:8
+#: flaskbb/templates/management/plugins.html:8
+#: flaskbb/templates/management/reports.html:9
+#: flaskbb/templates/management/settings.html:8
+#: flaskbb/templates/management/unread_reports.html:9
+#: flaskbb/templates/management/user_form.html:9
+#: flaskbb/templates/management/users.html:9
+#: flaskbb/templates/navigation.html:40
+msgid "Management"
+msgstr "Gestion"
+
+#: flaskbb/templates/layout.html:125 flaskbb/templates/navigation.html:44
+msgid "Logout"
+msgstr "Déconnexion"
+
+#: flaskbb/templates/auth/reset_password.html:1
+#: flaskbb/templates/auth/reset_password.html:10
+#: flaskbb/templates/layout.html:141 flaskbb/templates/navigation.html:67
+msgid "Reset Password"
+msgstr "Réinitialiser le mot de passe"
+
+#: flaskbb/templates/macros.html:325
+msgid "Pages"
+msgstr ""
+
+#: flaskbb/templates/navigation.html:53
+msgid "Private Messages"
+msgstr "Messages privés."
+
+#: flaskbb/templates/auth/forgot_password.html:1
+#: flaskbb/templates/auth/forgot_password.html:10
+msgid "Forgot Password"
+msgstr "Mot de passe oublié"
+
+#: flaskbb/templates/auth/login.html:27
+msgid "Not a member yet?"
+msgstr "Pas encore membre ?"
+
+#: flaskbb/templates/auth/login.html:28
+msgid "Forgot your Password?"
+msgstr "Mot de passe oublié ?"
+
+#: flaskbb/templates/auth/request_account_activation.html:1
+#: flaskbb/templates/auth/request_account_activation.html:10
+msgid "Request Account Activation"
+msgstr "Demander l'activation du compte"
+
+#: flaskbb/templates/email/activate_account.html:3
+#: flaskbb/templates/email/reset_password.html:2
+#, python-format
+msgid "Dear %(user)s,"
+msgstr "Che(è)r(e) %(user)s, "
+
+#: flaskbb/templates/email/activate_account.html:5
+msgid "Click the link below to activate your account:"
+msgstr "Cliquez sur le lien au-dessus pour activer votre compte:"
+
+#: flaskbb/templates/email/activate_account.html:9
+#: flaskbb/templates/email/reset_password.html:8
+msgid "Sincerely,"
+msgstr "Cordialement, "
+
+#: flaskbb/templates/email/activate_account.html:10
+#: flaskbb/templates/email/reset_password.html:9
+msgid "The Administration"
+msgstr "L'administration"
+
+#: flaskbb/templates/email/reset_password.html:4
+msgid "Click the link below to reset your password:"
+msgstr "Cliquez sur le lien au-dessus pour réinitialiser votre mot de passe:"
+
+#: flaskbb/templates/errors/forbidden_page.html:1
+msgid "Forbidden"
+msgstr "Interdit"
+
+#: flaskbb/templates/errors/forbidden_page.html:9
+msgid "403 - Forbidden Page"
+msgstr "403 - Page Interdite"
+
+#: flaskbb/templates/errors/forbidden_page.html:10
+msgid "You do not have the permission to view this page."
+msgstr "Vous n'avez pas la permission de voir cette page."
+
+#: flaskbb/templates/errors/forbidden_page.html:11
+#: flaskbb/templates/errors/page_not_found.html:11
+msgid "Back to the Forums"
+msgstr "Retour aux Forums"
+
+#: flaskbb/templates/errors/page_not_found.html:1
+msgid "Page not found"
+msgstr "Page non trouvée"
+
+#: flaskbb/templates/errors/page_not_found.html:9
+msgid "404 - Page not found!"
+msgstr "404 - Page non trouvée"
+
+#: flaskbb/templates/errors/page_not_found.html:10
+msgid "The page you were looking for does not exist."
+msgstr "La paege que vous recherchez n'existe pas."
+
+#: flaskbb/templates/errors/server_error.html:1
+msgid "Server Error"
+msgstr "Erreur serveur"
+
+#: flaskbb/templates/errors/server_error.html:9
+msgid "500 - Server Error"
+msgstr "500 - Erreur serveur"
+
+#: flaskbb/templates/errors/server_error.html:10
+msgid "Something went wrong!"
+msgstr "Quelque chose à mal tourné !"
+
+#: flaskbb/templates/errors/too_many_logins.html:1
+msgid "Too Many Requests"
+msgstr "Trop de requêtes"
+
+#: flaskbb/templates/errors/too_many_logins.html:9
+msgid "429 - Too Many Requests"
+msgstr "429 - Trop de requêtes"
+
+#: flaskbb/templates/errors/too_many_logins.html:10
+msgid ""
+"In order to prevent brute force attacks on accounts we have limited the "
+"amount of requests on this route."
+msgstr "Afin d'empêcher les attaques de force brute sur les comptes, nous avons limité le "
+"nombre de demandes sur cette url."
+
+#: flaskbb/templates/forum/category_layout.html:9
+#: flaskbb/templates/forum/search_result.html:129
+#: flaskbb/templates/forum/search_result.html:214
+#: flaskbb/templates/management/overview.html:85
+#: flaskbb/templates/user/all_posts.html:28
+#: flaskbb/templates/user/all_topics.html:8
+#: flaskbb/templates/user/all_topics.html:28
+#: flaskbb/templates/user/profile_layout.html:69
+msgid "Topics"
+msgstr "Sujets"
+
+#: flaskbb/templates/forum/category_layout.html:10
+#: flaskbb/templates/forum/edit_forum.html:32
+#: flaskbb/templates/forum/forum.html:50
+#: flaskbb/templates/forum/memberlist.html:50
+#: flaskbb/templates/forum/search_result.html:16
+#: flaskbb/templates/forum/search_result.html:40
+#: flaskbb/templates/forum/search_result.html:105
+#: flaskbb/templates/forum/search_result.html:135
+#: flaskbb/templates/forum/search_result.html:215
+#: flaskbb/templates/forum/topic.html:44
+#: flaskbb/templates/forum/topic_horizontal.html:55
+#: flaskbb/templates/forum/topictracker.html:32
+#: flaskbb/templates/management/banned_users.html:63
+#: flaskbb/templates/management/overview.html:88
+#: flaskbb/templates/management/users.html:63
+#: flaskbb/templates/message/conversation.html:44
+#: flaskbb/templates/message/conversation.html:95
+#: flaskbb/templates/user/all_posts.html:8
+#: flaskbb/templates/user/all_posts.html:34
+#: flaskbb/templates/user/all_topics.html:34
+#: flaskbb/templates/user/profile_layout.html:75
+msgid "Posts"
+msgstr "Messages"
+
+#: flaskbb/templates/forum/category_layout.html:11
+#: flaskbb/templates/forum/edit_forum.html:34
+#: flaskbb/templates/forum/forum.html:52
+#: flaskbb/templates/forum/search_result.html:137
+#: flaskbb/templates/forum/search_result.html:216
+#: flaskbb/templates/forum/topictracker.html:34
+msgid "Last Post"
+msgstr "Dernier message"
+
+#: flaskbb/templates/forum/category_layout.html:27
+#: flaskbb/templates/forum/search_result.html:232
+#: flaskbb/templates/management/forums.html:80
+msgid "Link to"
+msgstr "Liens vers"
+
+#: flaskbb/templates/forum/category_layout.html:114
+#: flaskbb/templates/forum/edit_forum.html:68
+#: flaskbb/templates/forum/edit_forum.html:91
+#: flaskbb/templates/forum/forum.html:85 flaskbb/templates/forum/forum.html:107
+#: flaskbb/templates/forum/search_result.html:162
+#: flaskbb/templates/forum/search_result.html:184
+#: flaskbb/templates/forum/search_result.html:317
+#: flaskbb/templates/forum/topictracker.html:68
+#: flaskbb/templates/forum/topictracker.html:91
+#: flaskbb/templates/management/plugins.html:42
+msgid "by"
+msgstr "par"
+
+#: flaskbb/templates/forum/category_layout.html:123
+#: flaskbb/templates/forum/search_result.html:326
+msgid "No posts."
+msgstr "Pas de messages."
+
+#: flaskbb/templates/forum/edit_forum.html:33
+#: flaskbb/templates/forum/forum.html:51
+#: flaskbb/templates/forum/search_result.html:136
+#: flaskbb/templates/forum/topictracker.html:33
+msgid "Views"
+msgstr "Vues"
+
+#: flaskbb/templates/forum/edit_forum.html:107
+#: flaskbb/templates/forum/forum.html:120
+#: flaskbb/templates/forum/topictracker.html:107
+msgid "No Topics."
+msgstr "Pas de sujets."
+
+#: flaskbb/templates/forum/edit_forum.html:117
+msgid "Back"
+msgstr "Retour"
+
+#: flaskbb/templates/forum/edit_forum.html:128
+msgid "Lock"
+msgstr "Bloquer"
+
+#: flaskbb/templates/forum/edit_forum.html:131
+msgid "Unlock"
+msgstr "Débloquer"
+
+#: flaskbb/templates/forum/edit_forum.html:137
+msgid "Highlight"
+msgstr "Mise en avant"
+
+#: flaskbb/templates/forum/edit_forum.html:140
+msgid "Trivialize"
+msgstr "Banaliser"
+
+#: flaskbb/templates/forum/edit_forum.html:145
+#: flaskbb/templates/management/groups.html:64
+#: flaskbb/templates/management/users.html:132
+msgid "Delete"
+msgstr "Supprimer"
+
+#: flaskbb/templates/forum/edit_forum.html:158
+msgid "Move to..."
+msgstr "Déplacer vers"
+
+#: flaskbb/templates/forum/edit_forum.html:165
+msgid "Move"
+msgstr "Déplacer"
+
+#: flaskbb/templates/forum/forum.html:25
+#: flaskbb/templates/management/unread_reports.html:50
+#: flaskbb/templates/management/unread_reports.html:69
+msgid "Mark as Read"
+msgstr "Marquer comme lu"
+
+#: flaskbb/templates/forum/forum.html:31
+#: flaskbb/templates/forum/topic_controls.html:97
+msgid "Locked"
+msgstr "Bloqué"
+
+#: flaskbb/templates/forum/forum.html:35
+#: flaskbb/templates/forum/new_topic.html:1
+#: flaskbb/templates/forum/new_topic.html:13
+#: flaskbb/templates/forum/new_topic.html:21
+msgid "New Topic"
+msgstr "Nouveau sujet"
+
+#: flaskbb/templates/forum/forum.html:130
+msgid "Moderation Mode"
+msgstr "Mode modération"
+
+#: flaskbb/templates/forum/index.html:11
+msgid "Board Statistics"
+msgstr "Statistiques du forum"
+
+#: flaskbb/templates/forum/index.html:12
+msgid "Who is online?"
+msgstr "Qui est en ligne ?"
+
+#: flaskbb/templates/forum/index.html:17
+msgid "Total number of registered users"
+msgstr "Nombre total d'utilisateur enregistré"
+
+#: flaskbb/templates/forum/index.html:18
+msgid "Total number of topics"
+msgstr "Nombre total de sujets"
+
+#: flaskbb/templates/forum/index.html:19
+msgid "Total number of posts"
+msgstr "Nombre total de messages"
+
+#: flaskbb/templates/forum/index.html:22
+msgid "Newest registered user"
+msgstr "Nouvel utilisateur enregistré"
+
+#: flaskbb/templates/forum/index.html:22
+msgid "No users"
+msgstr "Aucun utilisateurs"
+
+#: flaskbb/templates/forum/index.html:23
+msgid "Registered users online"
+msgstr "Utilisateurs enregistrés en ligne "
+
+#: flaskbb/templates/forum/index.html:25
+msgid "Guests online"
+msgstr "Visiteurs en ligne"
+
+#: flaskbb/templates/forum/memberlist.html:46
+#: flaskbb/templates/forum/search_result.html:106
+#: flaskbb/templates/management/banned_users.html:64
+#: flaskbb/templates/management/users.html:64
+msgid "Date registered"
+msgstr "Date d'enregistrement"
+
+#: flaskbb/templates/forum/memberlist.html:48
+#: flaskbb/templates/forum/search_result.html:107
+#: flaskbb/templates/management/banned_users.html:65
+#: flaskbb/templates/management/users.html:65
+msgid "Group"
+msgstr "Groupe"
+
+#: flaskbb/templates/forum/new_post.html:1
+#: flaskbb/templates/forum/new_post.html:14
+#: flaskbb/templates/forum/new_post.html:21
+msgid "New Post"
+msgstr "Nouveau message"
+
+#: flaskbb/templates/forum/online_users.html:1
+#: flaskbb/templates/forum/online_users.html:15
+msgid "Online Users"
+msgstr "Utilisateurs en ligne"
+
+#: flaskbb/templates/forum/report_post.html:1
+#: flaskbb/templates/forum/report_post.html:16
+msgid "Report Post"
+msgstr "Rapport de message"
+
+#: flaskbb/templates/forum/report_post.html:20
+msgid "Report"
+msgstr "Rapport"
+
+#: flaskbb/templates/forum/report_post.html:21
+msgid "Close"
+msgstr "Fermer"
+
+#: flaskbb/templates/forum/search_result.html:39
+#: flaskbb/templates/forum/topic.html:43
+#: flaskbb/templates/forum/topic_horizontal.html:54
+#: flaskbb/templates/message/conversation.html:43
+#: flaskbb/templates/message/conversation.html:94
+msgid "Joined"
+msgstr "A rejoint"
+
+#: flaskbb/templates/forum/search_result.html:54
+#: flaskbb/templates/forum/topic.html:58
+#: flaskbb/templates/message/conversation.html:106
+msgid "Guest"
+msgstr "Visiteur"
+
+#: flaskbb/templates/forum/search_result.html:89
+msgid "No posts found matching your search criteria."
+msgstr "Aucun messages correspondant à vos critères de recherche."
+
+#: flaskbb/templates/forum/search_result.html:119
+#: flaskbb/templates/management/banned_users.html:104
+#: flaskbb/templates/management/users.html:140
+msgid "No users found matching your search criteria."
+msgstr "Aucun utilisateurs correspondant à vos critères de recherche."
+
+#: flaskbb/templates/forum/search_result.html:197
+msgid "No topics found matching your search criteria."
+msgstr "Aucun sujets correspondant à vos critères de recherche. "
+
+#: flaskbb/templates/forum/search_result.html:335
+msgid "No forums found matching your search criteria."
+msgstr "Aucun forums correspondant à vos critères de recherche"
+
+#: flaskbb/templates/forum/topic.html:2
+#: flaskbb/templates/forum/topic_horizontal.html:2
+#, python-format
+msgid "%(title)s - Topic"
+msgstr "%(title)s - Sujet"
+
+#: flaskbb/templates/forum/topic_controls.html:15
+msgid "Moderate"
+msgstr "Modérer"
+
+#: flaskbb/templates/forum/topic_controls.html:24
+msgid "Delete Topic"
+msgstr "Supprimer le topic"
+
+#: flaskbb/templates/forum/topic_controls.html:36
+msgid "Lock Topic"
+msgstr "Fermer le sujet"
+
+#: flaskbb/templates/forum/topic_controls.html:45
+msgid "Unlock Topic"
+msgstr "Débloquer le sujet"
+
+#: flaskbb/templates/forum/topic_controls.html:56
+msgid "Highlight Topic"
+msgstr "Mise en avant du sujet"
+
+#: flaskbb/templates/forum/topic_controls.html:65
+msgid "Trivialize Topic"
+msgstr "Banaliser le sujet"
+
+#: flaskbb/templates/forum/topic_controls.html:80
+msgid "Untrack Topic"
+msgstr "Ne plus suivre ce sujet"
+
+#: flaskbb/templates/forum/topic_controls.html:87
+msgid "Track Topic"
+msgstr "Suivre ce sujet"
+
+#: flaskbb/templates/forum/topictracker.html:117
+msgid "Untrack Topics"
+msgstr "Ne plus suivre ces sujets"
+
+#: flaskbb/templates/management/banned_users.html:1
+#: flaskbb/templates/management/banned_users.html:21
+#: flaskbb/templates/management/banned_users.html:34
+#: flaskbb/templates/management/user_form.html:21
+#: flaskbb/templates/management/users.html:21
+msgid "Banned Users"
+msgstr "Utilisateurs bannis"
+
+#: flaskbb/templates/management/banned_users.html:10
+#: flaskbb/templates/management/banned_users.html:20
+#: flaskbb/templates/management/user_form.html:10
+#: flaskbb/templates/management/user_form.html:20
+#: flaskbb/templates/management/users.html:10
+#: flaskbb/templates/management/users.html:20
+msgid "Manage Users"
+msgstr "Gérer les utilisateurs"
+
+#: flaskbb/templates/management/banned_users.html:70
+#: flaskbb/templates/management/groups.html:39
+#: flaskbb/templates/management/unread_reports.html:45
+#: flaskbb/templates/management/users.html:69
+msgid "Actions"
+msgstr ""
+
+#: flaskbb/templates/management/banned_users.html:74
+#: flaskbb/templates/management/users.html:79
+msgid "Are you sure you want to unban these Users?"
+msgstr "Etes-vous sûr de vouloir ré-habiliter ces utilisateurs ?"
+
+#: flaskbb/templates/management/banned_users.html:75
+#: flaskbb/templates/management/users.html:80
+msgid "Unban selected Users"
+msgstr "Utilisateurs sélectionnés ré-habilités"
+
+#: flaskbb/templates/management/category_form.html:20
+#: flaskbb/templates/management/forum_form.html:20
+#: flaskbb/templates/management/forums.html:19
+#: flaskbb/templates/management/forums.html:30
+msgid "Manage Forums"
+msgstr "Gérer les forums"
+
+#: flaskbb/templates/management/forums.html:1
+#: flaskbb/templates/management/forums.html:9
+#: flaskbb/templates/management/management_layout.html:19
+msgid "Forums"
+msgstr ""
+
+#: flaskbb/templates/management/forums.html:52
+msgid "Delete Category"
+msgstr "Supprimer la catégorie"
+
+#: flaskbb/templates/management/forums.html:63
+msgid "Topics / Posts"
+msgstr "Sujets / Messages"
+
+#: flaskbb/templates/management/forums.html:100
+msgid "Edit Link"
+msgstr "Modifier le lien"
+
+#: flaskbb/templates/management/forums.html:105
+msgid "Delete Link"
+msgstr "Supprimer le lien"
+
+#: flaskbb/templates/management/forums.html:159
+msgid "Delete Forum"
+msgstr "Supprimer le forum"
+
+#: flaskbb/templates/management/group_form.html:10
+#: flaskbb/templates/management/group_form.html:20
+#: flaskbb/templates/management/groups.html:9
+#: flaskbb/templates/management/groups.html:19
+msgid "Manage Groups"
+msgstr "Gérer les groupes"
+
+#: flaskbb/templates/management/groups.html:1
+#: flaskbb/templates/management/groups.html:28
+#: flaskbb/templates/management/management_layout.html:18
+#: flaskbb/templates/management/overview.html:82
+msgid "Groups"
+msgstr "Groupes"
+
+#: flaskbb/templates/management/groups.html:34
+msgid "Group Name"
+msgstr "Nom de groupe"
+
+#: flaskbb/templates/management/groups.html:43
+msgid "Are you sure you want to delete these Groups?"
+msgstr "Etes-vous sûr de vouloir supprier ces groupes ?"
+
+#: flaskbb/templates/management/groups.html:44
+msgid "Delete selected Groups"
+msgstr "Supprimer les groupes sélectionnés"
+
+#: flaskbb/templates/management/groups.html:59
+#: flaskbb/templates/management/users.html:103
+msgid "Edit"
+msgstr "Modifier"
+
+#: flaskbb/templates/management/groups.html:71
+msgid "No groups found."
+msgstr "Aucun groupes trouvés."
+
+#: flaskbb/templates/management/management_layout.html:12
+#: flaskbb/templates/management/overview.html:1
+#: flaskbb/templates/management/overview.html:16
+#: flaskbb/templates/user/all_posts.html:16
+#: flaskbb/templates/user/all_topics.html:16
+#: flaskbb/templates/user/profile_layout.html:57
+msgid "Overview"
+msgstr "Aperçu"
+
+#: flaskbb/templates/management/management_layout.html:14
+#: flaskbb/templates/management/overview.html:91
+#: flaskbb/templates/management/reports.html:1
+#: flaskbb/templates/management/reports.html:10
+msgid "Reports"
+msgstr "Rapports"
+
+#: flaskbb/templates/management/management_layout.html:20
+#: flaskbb/templates/management/overview.html:115
+#: flaskbb/templates/management/plugins.html:1
+#: flaskbb/templates/management/plugins.html:9
+msgid "Plugins"
+msgstr ""
+
+#: flaskbb/templates/management/overview.html:25
+msgid "Everything seems alright."
+msgstr "Tout semble bien"
+
+#: flaskbb/templates/management/overview.html:26
+msgid "No new notifications."
+msgstr "Aucunes nouvelles notification."
+
+#: flaskbb/templates/management/overview.html:38
+msgid "users"
+msgstr "utilisateurs"
+
+#: flaskbb/templates/management/overview.html:50
+msgid "posts"
+msgstr "messages"
+
+#: flaskbb/templates/management/overview.html:62
+msgid "topics"
+msgstr "sujets"
+
+#: flaskbb/templates/management/overview.html:70
+msgid "Statistics"
+msgstr "Statistiques"
+
+#: flaskbb/templates/management/overview.html:73
+msgid "Registered users"
+msgstr "Utilisateur enregistrés"
+
+#: flaskbb/templates/management/overview.html:76
+msgid "Online users"
+msgstr "Utilisateurs en ligne"
+
+#: flaskbb/templates/management/overview.html:79
+msgid "Banned users"
+msgstr "Utilisateurs bannis"
+
+#: flaskbb/templates/management/overview.html:96
+msgid "Components"
+msgstr "Composants"
+
+#: flaskbb/templates/management/plugins.html:19
+msgid "Manage Plugins"
+msgstr "Gérer les plugins"
+
+#: flaskbb/templates/management/plugins.html:25
+msgid "Plugin"
+msgstr ""
+
+#: flaskbb/templates/management/plugins.html:26
+msgid "Information"
+msgstr ""
+
+#: flaskbb/templates/management/plugins.html:27
+msgid "Manage"
+msgstr "Gérer"
+
+#: flaskbb/templates/management/plugins.html:40
+msgid "Version"
+msgstr ""
+
+#: flaskbb/templates/management/plugins.html:48
+msgid "Enable"
+msgstr "Activer"
+
+#: flaskbb/templates/management/plugins.html:53
+msgid "Disable"
+msgstr "Désactiver"
+
+#: flaskbb/templates/management/plugins.html:60
+msgid "Install"
+msgstr "Installer"
+
+#: flaskbb/templates/management/plugins.html:66
+msgid "Uninstall"
+msgstr "Désinstaller"
+
+#: flaskbb/templates/management/reports.html:21
+#: flaskbb/templates/management/unread_reports.html:21
+msgid "Show unread reports"
+msgstr "Voir les rapports non-lus"
+
+#: flaskbb/templates/management/reports.html:22
+#: flaskbb/templates/management/unread_reports.html:22
+msgid "Show all reports"
+msgstr "Voir tous les rapports"
+
+#: flaskbb/templates/management/reports.html:31
+msgid "All Reports"
+msgstr "Tout les rapports"
+
+#: flaskbb/templates/management/reports.html:37
+#: flaskbb/templates/management/unread_reports.html:37
+msgid "Poster"
+msgstr "Afficher"
+
+#: flaskbb/templates/management/reports.html:40
+#: flaskbb/templates/management/unread_reports.html:40
+msgid "Reporter"
+msgstr "Rapporter"
+
+#: flaskbb/templates/management/reports.html:41
+#: flaskbb/templates/management/unread_reports.html:41
+msgid "Reported"
+msgstr "Rapporté"
+
+#: flaskbb/templates/management/reports.html:54
+#: flaskbb/templates/management/unread_reports.html:76
+msgid "No unread reports."
+msgstr "Aucun rapports non-lus."
+
+#: flaskbb/templates/management/unread_reports.html:1
+#: flaskbb/templates/management/unread_reports.html:10
+#: flaskbb/templates/management/unread_reports.html:31
+msgid "Unread Reports"
+msgstr "Rapports non-lus"
+
+#: flaskbb/templates/management/unread_reports.html:49
+msgid "Are you sure you want to mark these Reports as read?"
+msgstr "Ete-vous sûr de vouloir marquer ces rapports comme lus ?"
+
+#: flaskbb/templates/management/users.html:73
+msgid "Are you sure you want to ban these Users?"
+msgstr "Etes-vous sûr de vouloir bannir ces utilisateurs ?"
+
+#: flaskbb/templates/management/users.html:74
+msgid "Ban selected Users"
+msgstr "Utilisateurs sélectionnés bannis"
+
+#: flaskbb/templates/management/users.html:85
+msgid "Are you sure you want to delete these Users?"
+msgstr "Etes-vous sûr de vouloir supprimer ces utilisateurs ?"
+
+#: flaskbb/templates/management/users.html:86
+msgid "Delete selected Users"
+msgstr "Utilisateurs sélectionnés supprimés"
+
+#: flaskbb/templates/message/conversation.html:105
+msgid "Deleted"
+msgstr "Supprimé"
+
+#: flaskbb/templates/message/conversation_list.html:6
+msgid "Conversations"
+msgstr ""
+
+#: flaskbb/templates/message/conversation_list.html:88
+msgid "No conversations found."
+msgstr "Aucune conversation trouvée"
+
+#: flaskbb/templates/message/drafts.html:1
+#: flaskbb/templates/message/message_layout.html:20
+msgid "Drafts"
+msgstr "Brouillons"
+
+#: flaskbb/templates/message/message_layout.html:8
+msgid "Private Message"
+msgstr "Message privé"
+
+#: flaskbb/templates/message/message_layout.html:19
+msgid "Sent"
+msgstr "Envoyer"
+
+#: flaskbb/templates/message/message_layout.html:21
+#: flaskbb/templates/message/trash.html:1
+msgid "Trash"
+msgstr "Corbeille"
+
+#: flaskbb/templates/message/sent.html:1
+msgid "Sent Messages"
+msgstr "Envoyer les messages"
+
+#: flaskbb/templates/user/all_posts.html:65
+msgid "The user has not written any posts yet."
+msgstr "L'utilisateur n'a pas encore écrit de messages."
+
+#: flaskbb/templates/user/all_topics.html:67
+msgid "The user has not opened any topics yet."
+msgstr "L'utilisateur n'a pas encore ouvert de sujets."
+
+#: flaskbb/templates/user/change_email.html:7
+#: flaskbb/templates/user/settings_layout.html:18
+msgid "Change E-Mail Address"
+msgstr "Changer l'adresse E-Mail"
+
+#: flaskbb/templates/user/change_password.html:7
+#: flaskbb/templates/user/settings_layout.html:19
+msgid "Change Password"
+msgstr "Changer le mot de passe"
+
+#: flaskbb/templates/user/change_user_details.html:8
+#: flaskbb/templates/user/settings_layout.html:17
+msgid "Change User Details"
+msgstr "Changer les détails utilisateur"
+
+#: flaskbb/templates/user/general_settings.html:7
+#: flaskbb/templates/user/settings_layout.html:16
+msgid "General Settings"
+msgstr "Paramètres général"
+
+#: flaskbb/templates/user/profile.html:19
+msgid "Info"
+msgstr ""
+
+#: flaskbb/templates/user/profile.html:25
+msgid "User has not added any notes about him."
+msgstr "L'utilisateur n'a pas encore ajouté de notes à son sujet."
+
+#: flaskbb/templates/user/profile.html:34
+msgid "Signature"
+msgstr ""
+
+#: flaskbb/templates/user/profile_layout.html:27
+msgid "Never seen"
+msgstr "Jamais vu"
+
+#: flaskbb/templates/user/settings_layout.html:15
+msgid "Account Settings"
+msgstr "Paramètres de compte"
+
+#: flaskbb/user/forms.py:30
+msgid "Theme"
+msgstr "Thème"
+
+#: flaskbb/user/forms.py:36
+msgid "Old email address"
+msgstr "Ancienne adresse email"
+
+#: flaskbb/user/forms.py:40
+msgid "New email address"
+msgstr "Nouvelle adresse email"
+
+#: flaskbb/user/forms.py:42
+msgid "Email addresses must match."
+msgstr "Les adresses email doivent correspondre."
+
+#: flaskbb/user/forms.py:45
+msgid "Confirm email address"
+msgstr "Confirmer l'adresse email"
+
+#: flaskbb/user/forms.py:67
+msgid "New password"
+msgstr "Nouveau mot de passe"
+
+#: flaskbb/user/forms.py:69
+msgid "New passwords must match."
+msgstr "Les mots de passe doivent correspondre."
+
+#: flaskbb/user/forms.py:72
+msgid "Confirm new password"
+msgstr "Confirmer le nouveau mot de passe"
+
+#: flaskbb/user/forms.py:78
+msgid "Old password is wrong."
+msgstr "L'ancien mot de passe est incorrect"
+
+#: flaskbb/user/forms.py:100
+msgid "Forum Signature"
+msgstr "Signature du forum"
+
+#: flaskbb/user/views.py:66
+msgid "Settings updated."
+msgstr "Paramètre mis à jour."
+
+#: flaskbb/user/views.py:82
+msgid "Password updated."
+msgstr "Mot de passe mis à jour."
+
+#: flaskbb/user/views.py:94
+msgid "Email address updated."
+msgstr "Adresse email mise à jour."
+
+#: flaskbb/user/views.py:107
+msgid "User details updated."
+msgstr "Détails utilisateurs mis à jour."
+
+#: flaskbb/utils/helpers.py:92
+msgid "You do not have the permissions to execute this action."
+msgstr "Vous n'avez pas la permission d'exécuter cette action."

+ 2 - 4
flaskbb/user/forms.py

@@ -18,7 +18,6 @@ from flask_babelplus import lazy_gettext as _
 
 
 from flaskbb.user.models import User
 from flaskbb.user.models import User
 from flaskbb.extensions import db
 from flaskbb.extensions import db
-from flaskbb.utils.widgets import SelectBirthdayWidget
 from flaskbb.utils.fields import BirthdayField
 from flaskbb.utils.fields import BirthdayField
 from flaskbb.utils.helpers import check_image
 from flaskbb.utils.helpers import check_image
 
 
@@ -79,9 +78,8 @@ class ChangePasswordForm(FlaskForm):
 
 
 
 
 class ChangeUserDetailsForm(FlaskForm):
 class ChangeUserDetailsForm(FlaskForm):
-    birthday = BirthdayField(_("Birthday"), format="%d %m %Y",
-                             validators=[Optional()],
-                             widget=SelectBirthdayWidget())
+    birthday = BirthdayField(_("Birthday"), format="%d %m %Y", validators=[
+        Optional()])
 
 
     gender = SelectField(_("Gender"), default="None", choices=[
     gender = SelectField(_("Gender"), default="None", choices=[
         ("None", ""),
         ("None", ""),

+ 6 - 7
flaskbb/user/models.py

@@ -12,7 +12,6 @@ from werkzeug.security import generate_password_hash, check_password_hash
 from flask import url_for
 from flask import url_for
 from flask_login import UserMixin, AnonymousUserMixin
 from flask_login import UserMixin, AnonymousUserMixin
 
 
-from flaskbb._compat import max_integer
 from flaskbb.extensions import db, cache
 from flaskbb.extensions import db, cache
 from flaskbb.exceptions import AuthenticationError
 from flaskbb.exceptions import AuthenticationError
 from flaskbb.utils.helpers import time_utcnow
 from flaskbb.utils.helpers import time_utcnow
@@ -89,7 +88,7 @@ class User(db.Model, UserMixin, CRUDMixin):
     _password = db.Column('password', db.String(120), nullable=False)
     _password = db.Column('password', db.String(120), nullable=False)
     date_joined = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
     date_joined = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
     lastseen = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
     lastseen = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
-    birthday = db.Column(UTCDateTime(timezone=True))
+    birthday = db.Column(db.DateTime)
     gender = db.Column(db.String(10))
     gender = db.Column(db.String(10))
     website = db.Column(db.String(200))
     website = db.Column(db.String(200))
     location = db.Column(db.String(100))
     location = db.Column(db.String(100))
@@ -338,12 +337,12 @@ class User(db.Model, UserMixin, CRUDMixin):
         return self.secondary_groups.filter(
         return self.secondary_groups.filter(
             groups_users.c.group_id == group.id).count() > 0
             groups_users.c.group_id == group.id).count() > 0
 
 
-    @cache.memoize(timeout=max_integer)
+    @cache.memoize()
     def get_groups(self):
     def get_groups(self):
         """Returns all the groups the user is in."""
         """Returns all the groups the user is in."""
         return [self.primary_group] + list(self.secondary_groups)
         return [self.primary_group] + list(self.secondary_groups)
 
 
-    @cache.memoize(timeout=max_integer)
+    @cache.memoize()
     def get_permissions(self, exclude=None):
     def get_permissions(self, exclude=None):
         """Returns a dictionary with all permissions the user has"""
         """Returns a dictionary with all permissions the user has"""
         if exclude:
         if exclude:
@@ -360,7 +359,7 @@ class User(db.Model, UserMixin, CRUDMixin):
                 perms[c] = getattr(group, c) or perms.get(c, False)
                 perms[c] = getattr(group, c) or perms.get(c, False)
         return perms
         return perms
 
 
-    @cache.memoize(timeout=max_integer)
+    @cache.memoize()
     def get_unread_messages(self):
     def get_unread_messages(self):
         """Returns all unread messages for the user."""
         """Returns all unread messages for the user."""
         unread_messages = Conversation.query.\
         unread_messages = Conversation.query.\
@@ -471,11 +470,11 @@ class Guest(AnonymousUserMixin):
     def groups(self):
     def groups(self):
         return self.get_groups()
         return self.get_groups()
 
 
-    @cache.memoize(timeout=max_integer)
+    @cache.memoize()
     def get_groups(self):
     def get_groups(self):
         return Group.query.filter(Group.guest == True).all()
         return Group.query.filter(Group.guest == True).all()
 
 
-    @cache.memoize(timeout=max_integer)
+    @cache.memoize()
     def get_permissions(self, exclude=None):
     def get_permissions(self, exclude=None):
         """Returns a dictionary with all permissions the user has"""
         """Returns a dictionary with all permissions the user has"""
         if exclude:
         if exclude:

+ 215 - 2
flaskbb/utils/fields.py

@@ -3,19 +3,232 @@
     flaskbb.utils.fields
     flaskbb.utils.fields
     ~~~~~~~~~~~~~~~~~~~~
     ~~~~~~~~~~~~~~~~~~~~
 
 
-    Additional fields for wtforms
+    Additional fields and widgets for wtforms.
+    The reCAPTCHA Field was taken from Flask-WTF and modified
+    to use our own settings system.
 
 
     :copyright: (c) 2014 by the FlaskBB Team.
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
 from datetime import datetime
 from datetime import datetime
-from wtforms.fields import DateField
+try:
+    import urllib2 as http
+except ImportError:
+    # Python 3
+    from urllib import request as http
+
+from flask import request, current_app, Markup, json
+from werkzeug import url_encode
+from wtforms import ValidationError
+from wtforms.fields import DateField, Field
+from wtforms.widgets.core import Select, HTMLString, html_params
+
+from flaskbb._compat import to_bytes, to_unicode
+from flaskbb.utils.settings import flaskbb_config
+
+JSONEncoder = json.JSONEncoder
+
+RECAPTCHA_SCRIPT = u'https://www.google.com/recaptcha/api.js'
+RECAPTCHA_TEMPLATE = u'''
+<script src='%s' async defer></script>
+<div class="g-recaptcha" %s></div>
+'''
+
+RECAPTCHA_VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify'
+RECAPTCHA_ERROR_CODES = {
+    'missing-input-secret': 'The secret parameter is missing.',
+    'invalid-input-secret': 'The secret parameter is invalid or malformed.',
+    'missing-input-response': 'The response parameter is missing.',
+    'invalid-input-response': 'The response parameter is invalid or malformed.'
+}
+
+
+class RecaptchaValidator(object):
+    """Validates a ReCaptcha."""
+
+    def __init__(self, message=None):
+        if message is None:
+            message = RECAPTCHA_ERROR_CODES['missing-input-response']
+        self.message = message
+
+    def __call__(self, form, field):
+        if current_app.testing or not flaskbb_config["RECAPTCHA_ENABLED"]:
+            return True
+
+        if request.json:
+            response = request.json.get('g-recaptcha-response', '')
+        else:
+            response = request.form.get('g-recaptcha-response', '')
+        remote_ip = request.remote_addr
+
+        if not response:
+            raise ValidationError(field.gettext(self.message))
+
+        if not self._validate_recaptcha(response, remote_ip):
+            field.recaptcha_error = 'incorrect-captcha-sol'
+            raise ValidationError(field.gettext(self.message))
+
+    def _validate_recaptcha(self, response, remote_addr):
+        """Performs the actual validation."""
+        try:
+            private_key = flaskbb_config['RECAPTCHA_PRIVATE_KEY']
+        except KeyError:
+            raise RuntimeError("No RECAPTCHA_PRIVATE_KEY config set")
+
+        data = url_encode({
+            'secret': private_key,
+            'remoteip': remote_addr,
+            'response': response
+        })
+
+        http_response = http.urlopen(RECAPTCHA_VERIFY_SERVER, to_bytes(data))
+
+        if http_response.code != 200:
+            return False
+
+        json_resp = json.loads(to_unicode(http_response.read()))
+
+        if json_resp["success"]:
+            return True
+
+        for error in json_resp.get("error-codes", []):
+            if error in RECAPTCHA_ERROR_CODES:
+                raise ValidationError(RECAPTCHA_ERROR_CODES[error])
+
+        return False
+
+
+class RecaptchaWidget(object):
+
+    def recaptcha_html(self, public_key):
+        html = current_app.config.get('RECAPTCHA_HTML')
+        if html:
+            return Markup(html)
+        params = current_app.config.get('RECAPTCHA_PARAMETERS')
+        script = RECAPTCHA_SCRIPT
+        if params:
+            script += u'?' + url_encode(params)
+
+        attrs = current_app.config.get('RECAPTCHA_DATA_ATTRS', {})
+        attrs['sitekey'] = public_key
+        snippet = u' '.join([u'data-%s="%s"' % (k, attrs[k]) for k in attrs])
+        return Markup(RECAPTCHA_TEMPLATE % (script, snippet))
+
+    def __call__(self, field, error=None, **kwargs):
+        """Returns the recaptcha input HTML."""
+
+        if not flaskbb_config["RECAPTCHA_ENABLED"]:
+            return
+
+        try:
+            public_key = flaskbb_config['RECAPTCHA_PUBLIC_KEY']
+        except KeyError:
+            raise RuntimeError("RECAPTCHA_PUBLIC_KEY config not set")
+
+        return self.recaptcha_html(public_key)
+
+
+class RecaptchaField(Field):
+    widget = RecaptchaWidget()
+
+    # error message if recaptcha validation fails
+    recaptcha_error = None
+
+    def __init__(self, label='', validators=None, **kwargs):
+        validators = validators or [RecaptchaValidator()]
+        super(RecaptchaField, self).__init__(label, validators, **kwargs)
+
+
+class SelectBirthdayWidget(object):
+    """Renders a DateTime field with 3 selects.
+    For more information see: http://stackoverflow.com/a/14664504
+    """
+    FORMAT_CHOICES = {
+        '%d': [(x, str(x)) for x in range(1, 32)],
+        '%m': [(x, str(x)) for x in range(1, 13)]
+    }
+
+    FORMAT_CLASSES = {
+        '%d': 'select_date_day',
+        '%m': 'select_date_month',
+        '%Y': 'select_date_year'
+    }
+
+    def __init__(self, years=range(1930, datetime.utcnow().year + 1)):
+        """Initialzes the widget.
+
+        :param years: The min year which should be chooseable.
+                      Defatuls to ``1930``.
+        """
+        super(SelectBirthdayWidget, self).__init__()
+        self.FORMAT_CHOICES['%Y'] = [(x, str(x)) for x in years]
+
+    def __call__(self, field, **kwargs):
+        field_id = kwargs.pop('id', field.id)
+        html = []
+        allowed_format = ['%d', '%m', '%Y']
+        surrounded_div = kwargs.pop('surrounded_div', None)
+        css_class = kwargs.get('class', None)
+
+        for date_format in field.format.split():
+            if date_format in allowed_format:
+                choices = self.FORMAT_CHOICES[date_format]
+                id_suffix = date_format.replace('%', '-')
+                id_current = field_id + id_suffix
+
+                if css_class is not None:  # pragma: no cover
+                    select_class = "{} {}".format(
+                        css_class, self.FORMAT_CLASSES[date_format]
+                    )
+                else:
+                    select_class = self.FORMAT_CLASSES[date_format]
+
+                kwargs['class'] = select_class
+
+                try:
+                    del kwargs['placeholder']
+                except KeyError:
+                    pass
+
+                if surrounded_div is not None:
+                    html.append('<div class="%s">' % surrounded_div)
+
+                html.append('<select %s>' % html_params(name=field.name,
+                                                        id=id_current,
+                                                        **kwargs))
+
+                if field.data:
+                    current_value = int(field.data.strftime(date_format))
+                else:
+                    current_value = None
+
+                for value, label in choices:
+                    selected = (value == current_value)
+
+                    # Defaults to blank
+                    if value == 1 or value == 1930:
+                        html.append(
+                            Select.render_option("None", " ", selected)
+                        )
+
+                    html.append(Select.render_option(value, label, selected))
+
+                html.append('</select>')
+
+                if surrounded_div is not None:
+                    html.append("</div>")
+
+            html.append(' ')
+
+        return HTMLString(''.join(html))
 
 
 
 
 class BirthdayField(DateField):
 class BirthdayField(DateField):
     """Same as DateField, except it allows ``None`` values in case a user
     """Same as DateField, except it allows ``None`` values in case a user
     wants to delete his birthday.
     wants to delete his birthday.
     """
     """
+    widget = SelectBirthdayWidget()
+
     def __init__(self, label=None, validators=None, format='%Y-%m-%d',
     def __init__(self, label=None, validators=None, format='%Y-%m-%d',
                  **kwargs):
                  **kwargs):
         DateField.__init__(self, label, validators, format, **kwargs)
         DateField.__init__(self, label, validators, format, **kwargs)

+ 1 - 0
flaskbb/utils/populate.py

@@ -8,6 +8,7 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
+from __future__ import unicode_literals
 from flaskbb.management.models import Setting, SettingsGroup
 from flaskbb.management.models import Setting, SettingsGroup
 from flaskbb.user.models import User, Group
 from flaskbb.user.models import User, Group
 from flaskbb.forum.models import Post, Topic, Forum, Category
 from flaskbb.forum.models import Post, Topic, Forum, Category

+ 0 - 137
flaskbb/utils/recaptcha.py

@@ -1,137 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    flaskbb.utils.recaptcha
-    ~~~~~~~~~~~~~~~~~~~~~~~
-
-    The reCAPTCHA Field. Taken from Flask-WTF and modified
-    to use our own settings system.
-
-    :copyright: (c) 2014 by the FlaskBB Team.
-    :license: BSD, see LICENSE for more details.
-"""
-from wtforms.fields import Field
-
-try:
-    import urllib2 as http
-except ImportError:
-    # Python 3
-    from urllib import request as http
-
-from flask import request, current_app, Markup, json
-from werkzeug import url_encode
-from wtforms import ValidationError
-
-from flaskbb._compat import to_bytes, to_unicode
-from flaskbb.utils.settings import flaskbb_config
-
-JSONEncoder = json.JSONEncoder
-
-RECAPTCHA_SCRIPT = u'https://www.google.com/recaptcha/api.js'
-RECAPTCHA_TEMPLATE = u'''
-<script src='%s' async defer></script>
-<div class="g-recaptcha" %s></div>
-'''
-
-RECAPTCHA_VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify'
-RECAPTCHA_ERROR_CODES = {
-    'missing-input-secret': 'The secret parameter is missing.',
-    'invalid-input-secret': 'The secret parameter is invalid or malformed.',
-    'missing-input-response': 'The response parameter is missing.',
-    'invalid-input-response': 'The response parameter is invalid or malformed.'
-}
-
-
-class RecaptchaValidator(object):
-    """Validates a ReCaptcha."""
-
-    def __init__(self, message=None):
-        if message is None:
-            message = RECAPTCHA_ERROR_CODES['missing-input-response']
-        self.message = message
-
-    def __call__(self, form, field):
-        if current_app.testing or not flaskbb_config["RECAPTCHA_ENABLED"]:
-            return True
-
-        if request.json:
-            response = request.json.get('g-recaptcha-response', '')
-        else:
-            response = request.form.get('g-recaptcha-response', '')
-        remote_ip = request.remote_addr
-
-        if not response:
-            raise ValidationError(field.gettext(self.message))
-
-        if not self._validate_recaptcha(response, remote_ip):
-            field.recaptcha_error = 'incorrect-captcha-sol'
-            raise ValidationError(field.gettext(self.message))
-
-    def _validate_recaptcha(self, response, remote_addr):
-        """Performs the actual validation."""
-        try:
-            private_key = flaskbb_config['RECAPTCHA_PRIVATE_KEY']
-        except KeyError:
-            raise RuntimeError("No RECAPTCHA_PRIVATE_KEY config set")
-
-        data = url_encode({
-            'secret': private_key,
-            'remoteip': remote_addr,
-            'response': response
-        })
-
-        http_response = http.urlopen(RECAPTCHA_VERIFY_SERVER, to_bytes(data))
-
-        if http_response.code != 200:
-            return False
-
-        json_resp = json.loads(to_unicode(http_response.read()))
-
-        if json_resp["success"]:
-            return True
-
-        for error in json_resp.get("error-codes", []):
-            if error in RECAPTCHA_ERROR_CODES:
-                raise ValidationError(RECAPTCHA_ERROR_CODES[error])
-
-        return False
-
-
-class RecaptchaWidget(object):
-
-    def recaptcha_html(self, public_key):
-        html = current_app.config.get('RECAPTCHA_HTML')
-        if html:
-            return Markup(html)
-        params = current_app.config.get('RECAPTCHA_PARAMETERS')
-        script = RECAPTCHA_SCRIPT
-        if params:
-            script += u'?' + url_encode(params)
-
-        attrs = current_app.config.get('RECAPTCHA_DATA_ATTRS', {})
-        attrs['sitekey'] = public_key
-        snippet = u' '.join([u'data-%s="%s"' % (k, attrs[k]) for k in attrs])
-        return Markup(RECAPTCHA_TEMPLATE % (script, snippet))
-
-    def __call__(self, field, error=None, **kwargs):
-        """Returns the recaptcha input HTML."""
-
-        if not flaskbb_config["RECAPTCHA_ENABLED"]:
-            return
-
-        try:
-            public_key = flaskbb_config['RECAPTCHA_PUBLIC_KEY']
-        except KeyError:
-            raise RuntimeError("RECAPTCHA_PUBLIC_KEY config not set")
-
-        return self.recaptcha_html(public_key)
-
-
-class RecaptchaField(Field):
-    widget = RecaptchaWidget()
-
-    # error message if recaptcha validation fails
-    recaptcha_error = None
-
-    def __init__(self, label='', validators=None, **kwargs):
-        validators = validators or [RecaptchaValidator()]
-        super(RecaptchaField, self).__init__(label, validators, **kwargs)

+ 2 - 2
flaskbb/utils/search.py

@@ -65,7 +65,7 @@ class TopicWhoosheer(AbstractWhoosheer):
             topic_id=topic.id,
             topic_id=topic.id,
             title=topic.title,
             title=topic.title,
             username=topic.username,
             username=topic.username,
-            content=topic.first_post.content
+            content=getattr(topic.first_post,'content',None)
         )
         )
 
 
     @classmethod
     @classmethod
@@ -74,7 +74,7 @@ class TopicWhoosheer(AbstractWhoosheer):
             topic_id=topic.id,
             topic_id=topic.id,
             title=topic.title,
             title=topic.title,
             username=topic.username,
             username=topic.username,
-            content=topic.first_post.content
+            content=getattr(topic.first_post,'content',None)
         )
         )
 
 
     @classmethod
     @classmethod

+ 0 - 96
flaskbb/utils/widgets.py

@@ -1,96 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    flaskbb.utils.widgets
-    ~~~~~~~~~~~~~~~~~~~~~
-
-    Additional widgets for wtforms.
-
-    :copyright: (c) 2014 by the FlaskBB Team.
-    :license: BSD, see LICENSE for more details.
-"""
-from datetime import datetime
-from wtforms.widgets.core import Select, HTMLString, html_params
-
-
-class SelectBirthdayWidget(object):
-    """Renders a DateTime field with 3 selects.
-    For more information see: http://stackoverflow.com/a/14664504
-    """
-    FORMAT_CHOICES = {
-        '%d': [(x, str(x)) for x in range(1, 32)],
-        '%m': [(x, str(x)) for x in range(1, 13)]
-    }
-
-    FORMAT_CLASSES = {
-        '%d': 'select_date_day',
-        '%m': 'select_date_month',
-        '%Y': 'select_date_year'
-    }
-
-    def __init__(self, years=range(1930, datetime.utcnow().year + 1)):
-        """Initialzes the widget.
-
-        :param years: The min year which should be chooseable.
-                      Defatuls to ``1930``.
-        """
-        super(SelectBirthdayWidget, self).__init__()
-        self.FORMAT_CHOICES['%Y'] = [(x, str(x)) for x in years]
-
-    def __call__(self, field, **kwargs):
-        field_id = kwargs.pop('id', field.id)
-        html = []
-        allowed_format = ['%d', '%m', '%Y']
-        surrounded_div = kwargs.pop('surrounded_div', None)
-        css_class = kwargs.get('class', None)
-
-        for date_format in field.format.split():
-            if date_format in allowed_format:
-                choices = self.FORMAT_CHOICES[date_format]
-                id_suffix = date_format.replace('%', '-')
-                id_current = field_id + id_suffix
-
-                if css_class is not None:  # pragma: no cover
-                    select_class = "{} {}".format(
-                        css_class, self.FORMAT_CLASSES[date_format]
-                    )
-                else:
-                    select_class = self.FORMAT_CLASSES[date_format]
-
-                kwargs['class'] = select_class
-
-                try:
-                    del kwargs['placeholder']
-                except KeyError:
-                    pass
-
-                if surrounded_div is not None:
-                    html.append('<div class="%s">' % surrounded_div)
-
-                html.append('<select %s>' % html_params(name=field.name,
-                                                        id=id_current,
-                                                        **kwargs))
-
-                if field.data:
-                    current_value = int(field.data.strftime(date_format))
-                else:
-                    current_value = None
-
-                for value, label in choices:
-                    selected = (value == current_value)
-
-                    # Defaults to blank
-                    if value == 1 or value == 1930:
-                        html.append(
-                            Select.render_option("None", " ", selected)
-                        )
-
-                    html.append(Select.render_option(value, label, selected))
-
-                html.append('</select>')
-
-                if surrounded_div is not None:
-                    html.append("</div>")
-
-            html.append(' ')
-
-        return HTMLString(''.join(html))

+ 31 - 0
migrations/versions/d87cea4e995d_remove_timezone_info_from_birthday_field.py

@@ -0,0 +1,31 @@
+"""remove timezone info from birthday field
+
+Revision ID: d87cea4e995d
+Revises: d9530a529b3f
+Create Date: 2016-11-19 09:19:28.000276
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'd87cea4e995d'
+down_revision = 'd9530a529b3f'
+
+from alembic import op
+import sqlalchemy as sa
+import flaskbb
+
+
+def upgrade():
+    connection = op.get_bind()
+
+    if connection.engine.dialect.name != "sqlite":
+        # user/models.py
+        op.alter_column('users', 'birthday', type_=sa.DateTime(), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+
+
+def downgrade():
+    connection = op.get_bind()
+
+    if connection.engine.dialect.name != "sqlite":
+        # user/models.py
+        op.alter_column('users', 'birthday', existing_type=sa.DateTime(), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)

+ 36 - 1
tests/unit/utils/test_fields.py

@@ -1,7 +1,7 @@
 """Tests for the utils/fields.py file."""
 """Tests for the utils/fields.py file."""
 import pytest
 import pytest
 from wtforms.form import Form
 from wtforms.form import Form
-from flaskbb.utils.fields import BirthdayField
+from flaskbb.utils.fields import SelectBirthdayWidget, BirthdayField
 
 
 
 
 def test_birthday_field():
 def test_birthday_field():
@@ -19,3 +19,38 @@ def test_birthday_field():
 
 
     with pytest.raises(ValueError):
     with pytest.raises(ValueError):
         form.birthday.process_formdata(b)
         form.birthday.process_formdata(b)
+
+
+def test_select_birthday_widget():
+    """Test the SelectDateWidget."""
+
+    assert SelectBirthdayWidget.FORMAT_CHOICES['%d'] == [
+        (x, str(x)) for x in range(1, 32)
+    ]
+    assert SelectBirthdayWidget.FORMAT_CHOICES['%m'] == [
+        (x, str(x)) for x in range(1, 13)
+    ]
+
+    assert SelectBirthdayWidget.FORMAT_CLASSES == {
+        '%d': 'select_date_day',
+        '%m': 'select_date_month',
+        '%Y': 'select_date_year'
+    }
+
+    select_birthday_widget = SelectBirthdayWidget(years=[0, 1])
+
+    assert select_birthday_widget.FORMAT_CHOICES['%Y'] == [(0, '0'), (1, '1')]
+
+    class Field(object):
+        id = 'world'
+        name = 'helloWorld'
+        format = '%d %m %Y'
+        data = None
+
+    html = select_birthday_widget(field=Field(), surrounded_div="test-div")
+    assert 'world' in html
+    assert 'helloWorld' in html
+    assert 'class="select_date_day"' in html
+    assert 'class="select_date_month"' in html
+    assert 'class="select_date_year"' in html
+    assert '<div class="test-div">' in html

+ 0 - 37
tests/unit/utils/test_widgets.py

@@ -1,37 +0,0 @@
-"""Tests for the utils/widgets.py file."""
-from flaskbb.utils.widgets import SelectBirthdayWidget
-
-
-def test_select_birthday_widget():
-    """Test the SelectDateWidget."""
-
-    assert SelectBirthdayWidget.FORMAT_CHOICES['%d'] == [
-        (x, str(x)) for x in range(1, 32)
-    ]
-    assert SelectBirthdayWidget.FORMAT_CHOICES['%m'] == [
-        (x, str(x)) for x in range(1, 13)
-    ]
-
-    assert SelectBirthdayWidget.FORMAT_CLASSES == {
-        '%d': 'select_date_day',
-        '%m': 'select_date_month',
-        '%Y': 'select_date_year'
-    }
-
-    select_birthday_widget = SelectBirthdayWidget(years=[0, 1])
-
-    assert select_birthday_widget.FORMAT_CHOICES['%Y'] == [(0, '0'), (1, '1')]
-
-    class Field(object):
-        id = 'world'
-        name = 'helloWorld'
-        format = '%d %m %Y'
-        data = None
-
-    html = select_birthday_widget(field=Field(), surrounded_div="test-div")
-    assert 'world' in html
-    assert 'helloWorld' in html
-    assert 'class="select_date_day"' in html
-    assert 'class="select_date_month"' in html
-    assert 'class="select_date_year"' in html
-    assert '<div class="test-div">' in html