jpegPhoto profile form

This commit is contained in:
Éloi Rivard 2021-12-08 18:06:50 +01:00 committed by Éloi Rivard
parent 5ba87a2ddc
commit 0053369604
19 changed files with 432 additions and 221 deletions

View file

@ -22,7 +22,7 @@ from flask_babel import Babel, gettext as _
from flask_themer import Themer, render_template, FileSystemThemeLoader from flask_themer import Themer, render_template, FileSystemThemeLoader
from logging.config import dictConfig from logging.config import dictConfig
from .flaskutils import current_user from .flaskutils import current_user, base64picture
from .ldaputils import LDAPObject from .ldaputils import LDAPObject
from .oauth2utils import setup_oauth from .oauth2utils import setup_oauth
from .models import User, Group from .models import User, Group
@ -163,6 +163,10 @@ def teardown_ldap_connection(app):
g.ldap.unbind_s() g.ldap.unbind_s()
def setup_jinja(app):
app.jinja_env.filters["base64picture"] = base64picture
def setup_babel(app): def setup_babel(app):
babel = Babel(app) babel = Babel(app)
@ -231,6 +235,7 @@ def create_app(config=None, validate=True):
setup_ldap_models(app) setup_ldap_models(app)
setup_oauth(app) setup_oauth(app)
setup_blueprints(app) setup_blueprints(app)
setup_jinja(app)
setup_babel(app) setup_babel(app)
setup_themer(app) setup_themer(app)

View file

@ -14,7 +14,13 @@ from flask import (
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_themer import render_template from flask_themer import render_template
from werkzeug.datastructures import CombinedMultiDict, FileStorage from werkzeug.datastructures import CombinedMultiDict, FileStorage
from .apputils import default_fields, b64_to_obj, login_placeholder, profile_hash, obj_to_b64 from .apputils import (
default_fields,
b64_to_obj,
login_placeholder,
profile_hash,
obj_to_b64,
)
from .forms import ( from .forms import (
InvitationForm, InvitationForm,
LoginForm, LoginForm,
@ -54,7 +60,9 @@ def about():
@bp.route("/login", methods=("GET", "POST")) @bp.route("/login", methods=("GET", "POST"))
def login(): def login():
if current_user(): if current_user():
return redirect(url_for("account.profile_edition", username=current_user().uid[0])) return redirect(
url_for("account.profile_edition", username=current_user().uid[0])
)
form = LoginForm(request.form or None) form = LoginForm(request.form or None)
form["login"].render_kw["placeholder"] = login_placeholder() form["login"].render_kw["placeholder"] = login_placeholder()
@ -295,6 +303,9 @@ def profile_create(current_app, form):
else: else:
user[attribute.name] = [data] user[attribute.name] = [data]
if "jpegPhoto" in form and form["jpegPhoto_delete"].data:
user["jpegPhoto"] = None
user.cn = [f"{user.givenName[0]} {user.sn[0]}"] user.cn = [f"{user.givenName[0]} {user.sn[0]}"]
user.save() user.save()
@ -391,6 +402,9 @@ def profile_edit(editor, username):
elif attribute.name == "groups" and "groups" in editor.write: elif attribute.name == "groups" and "groups" in editor.write:
user.set_groups(attribute.data) user.set_groups(attribute.data)
if "jpegPhoto" in form and form["jpegPhoto_delete"].data:
user["jpegPhoto"] = None
if ( if (
"password1" not in request.form "password1" not in request.form
or not form["password1"].data or not form["password1"].data

View file

@ -111,9 +111,9 @@ GROUP_USER_FILTER = "member={user.dn}"
# The 'READ' and 'WRITE' attributes are the LDAP attributes of the user # The 'READ' and 'WRITE' attributes are the LDAP attributes of the user
# object that users will be able to read and/or write. # object that users will be able to read and/or write.
[ACL.DEFAULT] [ACL.DEFAULT]
READ = ["uid", "groups"]
PERMISSIONS = ["use_oidc"] PERMISSIONS = ["use_oidc"]
WRITE = ["givenName", "sn", "userPassword", "telephoneNumber"] READ = ["uid", "groups"]
WRITE = ["givenName", "sn", "userPassword", "telephoneNumber", "jpegPhoto", "mail"]
[ACL.ADMIN] [ACL.ADMIN]
FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld" FILTER = "memberof=cn=moderators,ou=groups,dc=mydomain,dc=tld"

View file

@ -1,3 +1,4 @@
import base64
import ldap import ldap
import logging import logging
from functools import wraps from functools import wraps
@ -82,3 +83,8 @@ def smtp_needed():
return decorator return decorator
return wrapper return wrapper
def base64picture(data):
data = base64.b64encode(data).decode("utf-8")
return f"data:image/jpeg;base64,{data}"

View file

@ -3,7 +3,7 @@ import wtforms.form
from flask import current_app from flask import current_app
from flask_babel import lazy_gettext as _ from flask_babel import lazy_gettext as _
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired from flask_wtf.file import FileField, FileAllowed
from .models import User, Group from .models import User, Group
@ -122,7 +122,12 @@ PROFILE_FORM_FIELDS = dict(
telephoneNumber=wtforms.TelField( telephoneNumber=wtforms.TelField(
_("Phone number"), render_kw={"placeholder": _("555-000-555")} _("Phone number"), render_kw={"placeholder": _("555-000-555")}
), ),
jpegPhoto=FileField(_("Photo"), validators=[FileRequired()]), jpegPhoto=FileField(
_("Photo"),
validators=[FileAllowed(["jpg", "jpeg"])],
render_kw={"accept": "image/jpg, image/jpeg"},
),
jpegPhoto_delete=wtforms.BooleanField(_("Delete the photo")),
password1=wtforms.PasswordField( password1=wtforms.PasswordField(
_("Password"), _("Password"),
validators=[wtforms.validators.Optional(), wtforms.validators.Length(min=8)], validators=[wtforms.validators.Optional(), wtforms.validators.Length(min=8)],
@ -148,6 +153,9 @@ def profile_form(write_field_names, readonly_field_names):
if "userPassword" in write_field_names: if "userPassword" in write_field_names:
write_field_names |= {"password1", "password2"} write_field_names |= {"password1", "password2"}
if "jpegPhoto" in write_field_names:
write_field_names |= {"jpegPhoto_delete"}
fields = { fields = {
name: PROFILE_FORM_FIELDS.get(name) name: PROFILE_FORM_FIELDS.get(name)
for name in write_field_names | readonly_field_names for name in write_field_names | readonly_field_names

View file

@ -166,6 +166,9 @@ class LDAPObject:
if syntax == "1.3.6.1.4.1.1466.115.121.1.27": # Integer if syntax == "1.3.6.1.4.1.1466.115.121.1.27": # Integer
return int(value.decode("utf-8")) return int(value.decode("utf-8"))
if syntax == "1.3.6.1.4.1.1466.115.121.1.28": # JPEG
return value
if syntax == "1.3.6.1.4.1.1466.115.121.1.7": # Boolean if syntax == "1.3.6.1.4.1.1466.115.121.1.7": # Boolean
return value.decode("utf-8").upper() == "TRUE" return value.decode("utf-8").upper() == "TRUE"
@ -181,6 +184,9 @@ class LDAPObject:
if syntax == "1.3.6.1.4.1.1466.115.121.1.27": # Integer if syntax == "1.3.6.1.4.1.1466.115.121.1.27": # Integer
return str(value).encode("utf-8") return str(value).encode("utf-8")
if syntax == "1.3.6.1.4.1.1466.115.121.1.28": # JPEG
return value
if syntax == "1.3.6.1.4.1.1466.115.121.1.7": # Boolean if syntax == "1.3.6.1.4.1.1466.115.121.1.7": # Boolean
return ("TRUE" if value else "FALSE").encode("utf-8") return ("TRUE" if value else "FALSE").encode("utf-8")
@ -215,13 +221,18 @@ class LDAPObject:
# Object already exists in the LDAP database # Object already exists in the LDAP database
if match: if match:
deletions = [
name
for name, value in self.changes.items()
if value is None and name in self.attrs
]
changes = { changes = {
name: value name: value
for name, value in self.changes.items() for name, value in self.changes.items()
if value and value[0] and self.attrs.get(name) != value if value and value[0] and self.attrs.get(name) != value
} }
formatted_changes = self.python_attrs_to_ldap(changes) formatted_changes = self.python_attrs_to_ldap(changes)
modlist = [ modlist = [(ldap.MOD_DELETE, name, None) for name in deletions] + [
(ldap.MOD_REPLACE, name, values) (ldap.MOD_REPLACE, name, values)
for name, values in formatted_changes.items() for name, values in formatted_changes.items()
] ]

View file

@ -148,6 +148,12 @@ class User(LDAPObject):
self.read |= set(details.get("READ", [])) self.read |= set(details.get("READ", []))
self.write |= set(details.get("WRITE", [])) self.write |= set(details.get("WRITE", []))
def can_read(self, field):
return field in self.read | self.write
def can_writec(self, field):
return field in self.write
@property @property
def can_use_oidc(self): def can_use_oidc(self):
return "use_oidc" in self.permissions return "use_oidc" in self.permissions

View file

@ -42,3 +42,11 @@ footer a {
.ui.corner.labeled.input .ui.dropdown .dropdown.icon { .ui.corner.labeled.input .ui.dropdown .dropdown.icon {
margin-right: 1.5em; margin-right: 1.5em;
} }
.profile-form label img{
cursor: pointer;
}
.profile-form label i{
cursor: pointer;
}

View file

@ -0,0 +1,17 @@
$(function(){
$(".photo-delete-icon").click(function(event){
event.preventDefault();
$(".photo-content").hide();
$(".photo-placeholder").show();
$(".photo-field").val("");
$(".photo-delete-button").prop("checked", true);
});
$(".photo-field").change(function(event){
var upload = URL.createObjectURL(event.target.files[0]);
$(".photo-content").show();
$(".photo-placeholder").hide();
$(".photo-content img").attr("src", upload);
$(".photo-delete-button").prop("checked", false);
});
});

View file

@ -5,13 +5,16 @@
container=true, container=true,
noindicator=false, noindicator=false,
indicator_icon=none, indicator_icon=none,
indicator_text=none indicator_text=none,
display=true
) -%} ) -%}
{% if container %} {% if container %}
<div class="field {{ kwargs.pop('class_', '') }} <div class="field {{ kwargs.pop('class_', '') }}
{%- if field.errors %} error{% endif -%} {%- if field.errors %} error{% endif -%}
{%- if field.render_kw and "disabled" in field.render_kw %} disabled{% endif -%}"> {%- if field.render_kw and "disabled" in field.render_kw %} disabled{% endif -%}"
{% if not display %}style="display: none"{% endif %}
>
{% endif %} {% endif %}
{% if (field.type != 'HiddenField' and field.type !='CSRFTokenField') and label_visible %} {% if (field.type != 'HiddenField' and field.type !='CSRFTokenField') and label_visible %}
@ -31,7 +34,6 @@
{% if field.type not in ("SelectField", "SelectMultipleField") %} {% if field.type not in ("SelectField", "SelectMultipleField") %}
{{ field(**kwargs) }} {{ field(**kwargs) }}
{% elif field.render_kw and "readonly" in field.render_kw %} {% elif field.render_kw and "readonly" in field.render_kw %}
{# This will be un-needed when https://github.com/fomantic/Fomantic-UI/issues/2173 is solved #}
{{ field(class_="ui fluid dropdown multiple read-only", **kwargs) }} {{ field(class_="ui fluid dropdown multiple read-only", **kwargs) }}
{% else %} {% else %}
{{ field(class_="ui fluid dropdown multiple", **kwargs) }} {{ field(class_="ui fluid dropdown multiple", **kwargs) }}

View file

@ -1,5 +1,6 @@
{% extends theme('base.html') %} {% extends theme('base.html') %}
{% import 'fomanticui.html' as sui %} {% import 'fomanticui.html' as sui %}
{% import 'userlist.html' as user_list %}
{% block style %} {% block style %}
<link href="/static/datatables/jquery.dataTables.min.css" rel="stylesheet"> <link href="/static/datatables/jquery.dataTables.min.css" rel="stylesheet">
@ -85,27 +86,7 @@
</div> </div>
{% if edited_group %} {% if edited_group %}
<table class="ui table"> {{ user_list.user_list(user, members) }}
<thead>
<th>{% trans %}Login{% endtrans %}</th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Email{% endtrans %}</th>
<th>{% trans %}Groups{% endtrans %}</th>
</thead>
{% for user_account in members %}
<tr>
<td><a href="{{ url_for('account.profile_edition', username=user_account.uid[0]) }}">{{ user_account.uid[0] }}</a></td>
<td>{{ user_account.name }}</td>
<td><a href="mailto:{{ user_account.mail[0] }}">{{ user_account.mail[0] }}</a></td>
<td>
{% for group in user_account.groups %}
<a class="ui label" href="{{ url_for('groups.group', groupname=group.name) }}">{{ group.name }}</a>
{% endfor %}
</td>
</tr>
{% endfor %}
</table>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -3,20 +3,21 @@
{% block script %} {% block script %}
<script src="/static/js/confirm.js"></script> <script src="/static/js/confirm.js"></script>
<script src="/static/js/profile.js"></script>
{% endblock %} {% endblock %}
{% macro render_field(field, noindicator=false) %} {% macro render_field(field, noindicator=false) %}
{% set lock_indicator = field.render_kw and ("readonly" in field.render_kw or "disabled" in field.render_kw) %} {% set lock_indicator = field.render_kw and ("readonly" in field.render_kw or "disabled" in field.render_kw) %}
{% if not edited_user %} {% if not edited_user %}
{{ sui.render_field(field) }} {{ sui.render_field(field, **kwargs) }}
{% elif edited_user.uid == user.uid or lock_indicator or noindicator %} {% elif edited_user.uid == user.uid or lock_indicator or noindicator %}
{{ sui.render_field(field) }} {{ sui.render_field(field, **kwargs) }}
{% elif field.name in edited_user.write %} {% elif field.name in edited_user.write %}
{{ sui.render_field(field, noindicator=true) }} {{ sui.render_field(field, noindicator=true, **kwargs) }}
{% elif field.name in edited_user.read %} {% elif field.name in edited_user.read %}
{{ sui.render_field(field, indicator_icon="eye", indicator_text=_("This user cannot edit this field")) }} {{ sui.render_field(field, indicator_icon="eye", indicator_text=_("This user cannot edit this field"), **kwargs) }}
{% else %} {% else %}
{{ sui.render_field(field, indicator_icon="eye slash", indicator_text=_("This user cannot see this field")) }} {{ sui.render_field(field, indicator_icon="eye slash", indicator_text=_("This user cannot see this field"), **kwargs) }}
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
@ -71,12 +72,42 @@
action="{{ request.url }}" action="{{ request.url }}"
role="form" role="form"
enctype="multipart/form-data" enctype="multipart/form-data"
class="ui form info{% if user.can_manage_users and edited_user and not edited_user.has_password() %} warning{% endif %}" class="ui form info{% if user.can_manage_users and edited_user and not edited_user.has_password() %} warning{% endif %} profile-form"
> >
{#{ render_field(form.csrf_token) }#} {#{ render_field(form.csrf_token) }#}
<h4 class="ui dividing header">{% trans %}Personal information{% endtrans %}</h4> <h4 class="ui dividing header">{% trans %}Personal information{% endtrans %}</h4>
{% if "jpegPhoto" in form %}
<div class="ui grid">
<div class="three wide column">
{{ render_field(form.jpegPhoto, display=false, class="photo-field") }}
{{ render_field(form.jpegPhoto_delete, display=false, class="photo-delete-button") }}
{% set photo = edited_user.jpegPhoto and edited_user.jpegPhoto[0] %}
<label
class="ui small bordered image photo-content"
for="{{ form.jpegPhoto.id }}"
title="{{ _("Click to upload a photo") }}"
{% if not photo %}style="display: none;"{% endif %}>
<a class="ui right corner label photo-delete-icon" title="{{ _("Delete the photo") }}">
<i class="times icon"></i>
</a>
<img src="{% if photo %}{{ edited_user.jpegPhoto[0]|base64picture }}{% endif %}" alt="User photo">
</label>
<label
class="ui centered photo-placeholder"
for="{{ form.jpegPhoto.id }}"
title="{{ _("Click to upload a photo") }}"
{% if photo %}style="display: none;"{% endif %}>
<i class="massive centered portrait icon"></i>
</label>
</div>
<div class="thirteen wide column">
{% endif %}
<div class="two fields"> <div class="two fields">
{% if "givenName" in form %} {% if "givenName" in form %}
{{ render_field(form.givenName) }} {{ render_field(form.givenName) }}
@ -85,9 +116,13 @@
{{ render_field(form.sn) }} {{ render_field(form.sn) }}
{% endif %} {% endif %}
</div> </div>
{% if "mail" in form %} {% if "mail" in form %}
{{ render_field(form.mail) }} {{ render_field(form.mail) }}
{% endif %} {% endif %}
{% if "jpegPhoto" in form %}</div></div>{% endif %}
{% if "telephoneNumber" in form %} {% if "telephoneNumber" in form %}
{{ render_field(form.telephoneNumber) }} {{ render_field(form.telephoneNumber) }}
{% endif %} {% endif %}

View file

@ -0,0 +1,52 @@
{% macro user_list(watcher, users) %}
<table class="ui table">
<thead>
{% if watcher.can_read("jpegPhoto") %}
<th></th>
{% endif %}
{% if watcher.can_read("uid") %}
<th>{% trans %}Login{% endtrans %}</th>
{% endif %}
{% if watcher.can_read("sn") or watcher.can_read("givenName") %}
<th>{% trans %}Name{% endtrans %}</th>
{% endif %}
{% if watcher.can_read("mail") %}
<th>{% trans %}Email{% endtrans %}</th>
{% endif %}
{% if watcher.can_manage_groups %}
<th>{% trans %}Groups{% endtrans %}</th>
{% endif %}
</thead>
{% for user in users %}
<tr>
{% if watcher.can_read("jpegPhoto") %}
<td>
<a href="{{ url_for('account.profile_edition', username=user.uid[0]) }}">
{% if user.jpegPhoto and user.jpegPhoto[0] %}
<img class="ui avatar image" src="{{ user.jpegPhoto[0]|base64picture }}" alt="User photo">
{% else %}
<i class="user circle big black icon"></i>
{% endif %}
</a>
</td>
{% endif %}
{% if watcher.can_read("uid") %}
<td><a href="{{ url_for('account.profile_edition', username=user.uid[0]) }}">{{ user.uid[0] }}</a></td>
{% endif %}
{% if watcher.can_read("sn") or watcher.can_read("givenName") %}
<td>{{ user.name }}</td>
{% endif %}
{% if watcher.can_read("mail") %}
<td><a href="mailto:{{ user.mail[0] }}">{{ user.mail[0] }}</a></td>
{% endif %}
{% if watcher.can_manage_groups %}
<td>
{% for group in user.groups %}
<a class="ui label" href="{{ url_for('groups.group', groupname=group.name) }}">{{ group.name }}</a>
{% endfor %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% endmacro %}

View file

@ -1,4 +1,5 @@
{% extends theme('base.html') %} {% extends theme('base.html') %}
{% import 'userlist.html' as user_list %}
{% block style %} {% block style %}
<link href="/static/datatables/jquery.dataTables.min.css" rel="stylesheet"> <link href="/static/datatables/jquery.dataTables.min.css" rel="stylesheet">
@ -21,27 +22,5 @@
</div> </div>
</div> </div>
<table class="ui table"> {{ user_list.user_list(user, users) }}
<thead>
<th>{% trans %}Login{% endtrans %}</th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Email{% endtrans %}</th>
{% if user.can_manage_groups %}<th>{% trans %}Groups{% endtrans %}</th>{% endif %}
</thead>
{% for user_account in users %}
<tr>
<td><a href="{{ url_for('account.profile_edition', username=user_account.uid[0]) }}">{{ user_account.uid[0] }}</a></td>
<td>{{ user_account.name }}</td>
<td><a href="mailto:{{ user_account.mail[0] }}">{{ user_account.mail[0] }}</a></td>
{% if user.can_manage_groups %}
<td>
{% for group in user_account.groups %}
<a class="ui label" href="{{ url_for('groups.group', groupname=group.name) }}">{{ group.name }}</a>
{% endfor %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% endblock %} {% endblock %}

View file

@ -9,46 +9,46 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: contact@yaal.fr\n" "Report-Msgid-Bugs-To: contact@yaal.fr\n"
"POT-Creation-Date: 2021-12-07 19:58+0100\n" "POT-Creation-Date: 2021-12-08 18:56+0100\n"
"PO-Revision-Date: 2021-12-07 20:03+0100\n" "PO-Revision-Date: 2021-12-08 18:56+0100\n"
"Last-Translator: Éloi Rivard <eloi.rivard@aquilenet.fr>\n" "Last-Translator: Éloi Rivard <eloi.rivard@aquilenet.fr>\n"
"Language: fr\n" "Language: fr\n"
"Language-Team: French <traduc@traduc.org>\n" "Language-Team: French <traduc@traduc.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.1\n" "Generated-By: Babel 2.9.1\n"
"X-Generator: Gtranslator 40.0\n" "X-Generator: Gtranslator 40.0\n"
#: canaille/__init__.py:123 #: canaille/__init__.py:129
msgid "Could not connect to the LDAP server '{uri}'" msgid "Could not connect to the LDAP server '{uri}'"
msgstr "Impossible de se connecter au serveur LDAP '{uri}'" msgstr "Impossible de se connecter au serveur LDAP '{uri}'"
#: canaille/__init__.py:139 #: canaille/__init__.py:145
msgid "LDAP authentication failed with user '{user}'" msgid "LDAP authentication failed with user '{user}'"
msgstr "" msgstr ""
"L'authentification au serveur LDAP a échoué pour l'utilisateur '{user}'" "L'authentification au serveur LDAP a échoué pour l'utilisateur '{user}'"
#: canaille/account.py:69 canaille/account.py:94 canaille/oauth.py:74 #: canaille/account.py:77 canaille/account.py:102 canaille/oauth.py:74
msgid "Login failed, please check your information" msgid "Login failed, please check your information"
msgstr "La connexion a échoué, veuillez vérifier vos informations." msgstr "La connexion a échoué, veuillez vérifier vos informations."
#: canaille/account.py:100 #: canaille/account.py:108
#, python-format #, python-format
msgid "Connection successful. Welcome %(user)s" msgid "Connection successful. Welcome %(user)s"
msgstr "Connexion réussie. Bienvenue %(user)s" msgstr "Connexion réussie. Bienvenue %(user)s"
#: canaille/account.py:113 #: canaille/account.py:121
#, python-format #, python-format
msgid "You have been disconnected. See you next time %(user)s" msgid "You have been disconnected. See you next time %(user)s"
msgstr "Vous avez été déconnectés. À bientôt %(user)s" msgstr "Vous avez été déconnectés. À bientôt %(user)s"
#: canaille/account.py:130 #: canaille/account.py:138
msgid "Could not send the password initialization link." msgid "Could not send the password initialization link."
msgstr "Impossible d'envoyer le courriel d'initialisation de mot de passe." msgstr "Impossible d'envoyer le courriel d'initialisation de mot de passe."
#: canaille/account.py:135 #: canaille/account.py:143
msgid "" msgid ""
"A password initialization link has been sent at your email address. You " "A password initialization link has been sent at your email address. You "
"should receive it within 10 minutes." "should receive it within 10 minutes."
@ -56,35 +56,35 @@ msgstr ""
"Un lien d'initialisation de votre mot de passe vous a été envoyé par mail. " "Un lien d'initialisation de votre mot de passe vous a été envoyé par mail. "
"Vous devriez le recevoir d'ici une dizaine de minutes." "Vous devriez le recevoir d'ici une dizaine de minutes."
#: canaille/account.py:141 canaille/account.py:326 #: canaille/account.py:149 canaille/account.py:340
msgid "Could not send the password initialization email" msgid "Could not send the password initialization email"
msgstr "Impossible d'envoyer le courriel d'initialisation de mot de passe." msgstr "Impossible d'envoyer le courriel d'initialisation de mot de passe."
#: canaille/account.py:192 canaille/account.py:264 #: canaille/account.py:204 canaille/account.py:275
msgid "User account creation failed." msgid "User account creation failed."
msgstr "La création du compte utilisateur a échoué." msgstr "La création du compte utilisateur a échoué."
#: canaille/account.py:213 canaille/account.py:234 #: canaille/account.py:225 canaille/account.py:246
msgid "The invitation link that brought you here was invalid." msgid "The invitation link that brought you here was invalid."
msgstr "Le lien d'invitation qui vous a amené ici est invalide." msgstr "Le lien d'invitation qui vous a amené ici est invalide."
#: canaille/account.py:220 #: canaille/account.py:232
msgid "Your account has already been created." msgid "Your account has already been created."
msgstr "Votre compte a déjà été créé." msgstr "Votre compte a déjà été créé."
#: canaille/account.py:227 #: canaille/account.py:239
msgid "You are already logged in, you cannot create an account." msgid "You are already logged in, you cannot create an account."
msgstr "Vous êtes déjà connectés, vous ne pouvez pas créer de compte." msgstr "Vous êtes déjà connectés, vous ne pouvez pas créer de compte."
#: canaille/account.py:269 #: canaille/account.py:280
msgid "You account has been created successfuly." msgid "You account has been created successfuly."
msgstr "Votre compte utilisateur a été créé avec succès." msgstr "Votre compte utilisateur a été créé avec succès."
#: canaille/account.py:299 #: canaille/account.py:313
msgid "User account creation succeed." msgid "User account creation succeed."
msgstr "La création du compte utilisateur a réussi." msgstr "La création du compte utilisateur a réussi."
#: canaille/account.py:320 #: canaille/account.py:334
msgid "" msgid ""
"A password initialization link has been sent at the user email address. It " "A password initialization link has been sent at the user email address. It "
"should be received within 10 minutes." "should be received within 10 minutes."
@ -92,7 +92,7 @@ msgstr ""
"Un lien d'initialisation de mot de passe a été envoyé à l'utilisateur par " "Un lien d'initialisation de mot de passe a été envoyé à l'utilisateur par "
"mail. Il devrait arriver d'ici une dizaine de minutes." "mail. Il devrait arriver d'ici une dizaine de minutes."
#: canaille/account.py:334 #: canaille/account.py:348
msgid "" msgid ""
"A password reset link has been sent at the user email address. It should be " "A password reset link has been sent at the user email address. It should be "
"received within 10 minutes." "received within 10 minutes."
@ -100,28 +100,28 @@ msgstr ""
"Un lien de réinitialisation de mot de passe a été envoyé à l'utilisateur par " "Un lien de réinitialisation de mot de passe a été envoyé à l'utilisateur par "
"mail. Il devrait arriver d'ici une dizaine de minutes." "mail. Il devrait arriver d'ici une dizaine de minutes."
#: canaille/account.py:340 #: canaille/account.py:354
msgid "Could not send the password reset email" msgid "Could not send the password reset email"
msgstr "Impossible d'envoyer le lien de réinitialisation." msgstr "Impossible d'envoyer le lien de réinitialisation."
#: canaille/account.py:371 #: canaille/account.py:385
msgid "Profile edition failed." msgid "Profile edition failed."
msgstr "L'édition du profil a échoué." msgstr "L'édition du profil a échoué."
#: canaille/account.py:396 #: canaille/account.py:413
msgid "Profile updated successfuly." msgid "Profile updated successfuly."
msgstr "Le profil a été mis à jour avec succès." msgstr "Le profil a été mis à jour avec succès."
#: canaille/account.py:416 #: canaille/account.py:433
#, python-format #, python-format
msgid "The user %(user)s has been sucessfuly deleted" msgid "The user %(user)s has been sucessfuly deleted"
msgstr "L'utilisateur %(user)s a bien été supprimé" msgstr "L'utilisateur %(user)s a bien été supprimé"
#: canaille/account.py:440 #: canaille/account.py:457
msgid "Could not send the password reset link." msgid "Could not send the password reset link."
msgstr "Impossible d'envoyer le lien de réinitialisation." msgstr "Impossible d'envoyer le lien de réinitialisation."
#: canaille/account.py:447 canaille/account.py:458 #: canaille/account.py:464 canaille/account.py:475
msgid "" msgid ""
"A password reset link has been sent at your email address. You should " "A password reset link has been sent at your email address. You should "
"receive it within 10 minutes." "receive it within 10 minutes."
@ -129,15 +129,15 @@ msgstr ""
"Un lien de ré-initialisation de votre mot de passe vous a été envoyé par " "Un lien de ré-initialisation de votre mot de passe vous a été envoyé par "
"mail. Vous devriez le recevoir d'ici une dizaine de minutes." "mail. Vous devriez le recevoir d'ici une dizaine de minutes."
#: canaille/account.py:464 #: canaille/account.py:481
msgid "Could not reset your password" msgid "Could not reset your password"
msgstr "Impossible de réinitialiser votre mot de passe" msgstr "Impossible de réinitialiser votre mot de passe"
#: canaille/account.py:478 #: canaille/account.py:495
msgid "The password reset link that brought you here was invalid." msgid "The password reset link that brought you here was invalid."
msgstr "Le lien de réinitialisation qui vous a amené ici est invalide." msgstr "Le lien de réinitialisation qui vous a amené ici est invalide."
#: canaille/account.py:487 #: canaille/account.py:504
msgid "Your password has been updated successfuly" msgid "Your password has been updated successfuly"
msgstr "Votre mot de passe a correctement été mis à jour." msgstr "Votre mot de passe a correctement été mis à jour."
@ -145,7 +145,7 @@ msgstr "Votre mot de passe a correctement été mis à jour."
msgid "John Doe" msgid "John Doe"
msgstr "Camille Dupont" msgstr "Camille Dupont"
#: canaille/apputils.py:37 canaille/forms.py:93 canaille/forms.py:188 #: canaille/apputils.py:37 canaille/forms.py:93 canaille/forms.py:196
msgid "jdoe" msgid "jdoe"
msgstr "cdupont" msgstr "cdupont"
@ -165,7 +165,7 @@ msgstr "Impossible de supprimer cet accès."
msgid "The access has been revoked" msgid "The access has been revoked"
msgstr "L'accès a été révoqué." msgstr "L'accès a été révoqué."
#: canaille/flaskutils.py:69 #: canaille/flaskutils.py:70
msgid "No SMTP server has been configured" msgid "No SMTP server has been configured"
msgstr "Aucun serveur SMTP n'a été configuré" msgstr "Aucun serveur SMTP n'a été configuré"
@ -191,27 +191,27 @@ msgid "Login"
msgstr "Identifiant" msgstr "Identifiant"
#: canaille/forms.py:45 canaille/forms.py:69 canaille/forms.py:117 #: canaille/forms.py:45 canaille/forms.py:69 canaille/forms.py:117
#: canaille/forms.py:199 #: canaille/forms.py:207
msgid "jane@doe.com" msgid "jane@doe.com"
msgstr "camille@dupont.fr" msgstr "camille@dupont.fr"
#: canaille/forms.py:55 canaille/forms.py:78 canaille/forms.py:127 #: canaille/forms.py:55 canaille/forms.py:78 canaille/forms.py:132
msgid "Password" msgid "Password"
msgstr "Mot de passe" msgstr "Mot de passe"
#: canaille/forms.py:81 canaille/forms.py:131 #: canaille/forms.py:81 canaille/forms.py:136
msgid "Password confirmation" msgid "Password confirmation"
msgstr "Confirmation du mot de passe" msgstr "Confirmation du mot de passe"
#: canaille/forms.py:84 canaille/forms.py:134 #: canaille/forms.py:84 canaille/forms.py:139
msgid "Password and confirmation do not match." msgid "Password and confirmation do not match."
msgstr "Le mot de passe et sa confirmation ne correspondent pas." msgstr "Le mot de passe et sa confirmation ne correspondent pas."
#: canaille/forms.py:92 canaille/forms.py:187 #: canaille/forms.py:92 canaille/forms.py:195
msgid "Username" msgid "Username"
msgstr "Identifiant" msgstr "Identifiant"
#: canaille/admin/clients.py:28 canaille/forms.py:96 canaille/forms.py:177 #: canaille/admin/clients.py:28 canaille/forms.py:96 canaille/forms.py:185
#: canaille/templates/admin/client_list.html:22 #: canaille/templates/admin/client_list.html:22
#: canaille/templates/group.html:91 canaille/templates/users.html:27 #: canaille/templates/group.html:91 canaille/templates/users.html:27
msgid "Name" msgid "Name"
@ -233,7 +233,7 @@ msgstr "Nom de famille"
msgid "Doe" msgid "Doe"
msgstr "Dupont" msgstr "Dupont"
#: canaille/forms.py:114 canaille/forms.py:192 #: canaille/forms.py:114 canaille/forms.py:200
msgid "Email address" msgid "Email address"
msgstr "Courriel" msgstr "Courriel"
@ -245,25 +245,29 @@ msgstr "Numéro de téléphone"
msgid "555-000-555" msgid "555-000-555"
msgstr "06 01 02 03 04" msgstr "06 01 02 03 04"
#: canaille/forms.py:125 #: canaille/forms.py:126
msgid "Photo" msgid "Photo"
msgstr "Photo" msgstr "Photo"
#: canaille/forms.py:139 #: canaille/forms.py:130 canaille/templates/profile.html:91
msgid "Delete the photo"
msgstr "Supprimer la photo"
#: canaille/forms.py:144
msgid "Number" msgid "Number"
msgstr "Numéro" msgstr "Numéro"
#: canaille/forms.py:141 #: canaille/forms.py:146
msgid "1234" msgid "1234"
msgstr "1234" msgstr "1234"
#: canaille/forms.py:162 canaille/forms.py:205 canaille/templates/group.html:93 #: canaille/forms.py:170 canaille/forms.py:213 canaille/templates/group.html:93
#: canaille/templates/groups.html:9 canaille/templates/users.html:29 #: canaille/templates/groups.html:9 canaille/templates/users.html:29
#: canaille/themes/default/base.html:59 #: canaille/themes/default/base.html:59
msgid "Groups" msgid "Groups"
msgstr "Groupes" msgstr "Groupes"
#: canaille/forms.py:180 #: canaille/forms.py:188
msgid "group" msgid "group"
msgstr "groupe" msgstr "groupe"
@ -553,11 +557,11 @@ msgstr "Envoyer le courriel d'initialisation"
#: canaille/templates/admin/client_edit.html:35 #: canaille/templates/admin/client_edit.html:35
#: canaille/templates/admin/client_edit.html:44 #: canaille/templates/admin/client_edit.html:44
#: canaille/templates/admin/client_edit.html:53 #: canaille/templates/admin/client_edit.html:53
#: canaille/templates/fomanticui.html:46 #: canaille/templates/fomanticui.html:48
msgid "This field is not editable" msgid "This field is not editable"
msgstr "Ce champ n'est pas modifiable" msgstr "Ce champ n'est pas modifiable"
#: canaille/templates/fomanticui.html:50 #: canaille/templates/fomanticui.html:52
msgid "This field is required" msgid "This field is required"
msgstr "Ce champ est requis" msgstr "Ce champ est requis"
@ -584,7 +588,7 @@ msgstr ""
" " " "
#: canaille/templates/forgotten-password.html:38 #: canaille/templates/forgotten-password.html:38
#: canaille/templates/profile.html:137 canaille/templates/profile.html:165 #: canaille/templates/profile.html:164 canaille/templates/profile.html:192
msgid "Send again" msgid "Send again"
msgstr "Envoyer à nouveau" msgstr "Envoyer à nouveau"
@ -605,12 +609,12 @@ msgstr ""
"irrévocable, et toutes les données de cet utilisateur seront supprimées." "irrévocable, et toutes les données de cet utilisateur seront supprimées."
#: canaille/templates/admin/client_edit.html:18 #: canaille/templates/admin/client_edit.html:18
#: canaille/templates/group.html:29 canaille/templates/profile.html:38 #: canaille/templates/group.html:29 canaille/templates/profile.html:41
msgid "Cancel" msgid "Cancel"
msgstr "Annuler" msgstr "Annuler"
#: canaille/templates/admin/client_edit.html:19 #: canaille/templates/admin/client_edit.html:19
#: canaille/templates/group.html:30 canaille/templates/profile.html:39 #: canaille/templates/group.html:30 canaille/templates/profile.html:42
msgid "Delete" msgid "Delete"
msgstr "Supprimer" msgstr "Supprimer"
@ -648,7 +652,7 @@ msgstr "Supprimer le groupe"
msgid "Create group" msgid "Create group"
msgstr "Créer le groupe" msgstr "Créer le groupe"
#: canaille/templates/group.html:79 canaille/templates/profile.html:205 #: canaille/templates/group.html:79 canaille/templates/profile.html:232
msgid "Submit" msgid "Submit"
msgstr "Valider" msgstr "Valider"
@ -775,19 +779,19 @@ msgstr "Je ne suis pas %(username)s"
msgid "Sign in" msgid "Sign in"
msgstr "Se connecter" msgstr "Se connecter"
#: canaille/templates/profile.html:15 #: canaille/templates/profile.html:18
msgid "This user cannot edit this field" msgid "This user cannot edit this field"
msgstr "Cet utilisateur ne peut pas éditer ce champ" msgstr "Cet utilisateur ne peut pas éditer ce champ"
#: canaille/templates/profile.html:17 #: canaille/templates/profile.html:20
msgid "This user cannot see this field" msgid "This user cannot see this field"
msgstr "Cet utilisateur ne peut pas voir ce champ" msgstr "Cet utilisateur ne peut pas voir ce champ"
#: canaille/templates/profile.html:26 #: canaille/templates/profile.html:29
msgid "Account deletion" msgid "Account deletion"
msgstr "Suppression d'un compte" msgstr "Suppression d'un compte"
#: canaille/templates/profile.html:31 #: canaille/templates/profile.html:34
msgid "" msgid ""
"Are you sure you want to delete this user? This action is unrevokable and " "Are you sure you want to delete this user? This action is unrevokable and "
"all the data about this user will be removed." "all the data about this user will be removed."
@ -795,7 +799,7 @@ msgstr ""
"Êtes-vous sûrs de vouloir supprimer cet utilisateur ? Cette action est " "Êtes-vous sûrs de vouloir supprimer cet utilisateur ? Cette action est "
"irrévocable, et toutes les données de cet utilisateur seront supprimées." "irrévocable, et toutes les données de cet utilisateur seront supprimées."
#: canaille/templates/profile.html:33 #: canaille/templates/profile.html:36
msgid "" msgid ""
"Are you sure you want to delete your account? This action is unrevokable and " "Are you sure you want to delete your account? This action is unrevokable and "
"all your data will be removed forever." "all your data will be removed forever."
@ -803,51 +807,55 @@ msgstr ""
"Êtes-vous sûrs de vouloir supprimer votre compte ? Cette action est " "Êtes-vous sûrs de vouloir supprimer votre compte ? Cette action est "
"irrévocable et toutes vos données seront supprimées pour toujours." "irrévocable et toutes vos données seront supprimées pour toujours."
#: canaille/templates/profile.html:48 #: canaille/templates/profile.html:51
msgid "User creation" msgid "User creation"
msgstr "Nouvel utilisateur" msgstr "Nouvel utilisateur"
#: canaille/templates/profile.html:50 canaille/themes/default/base.html:39 #: canaille/templates/profile.html:53 canaille/themes/default/base.html:39
msgid "My profile" msgid "My profile"
msgstr "Mon profil" msgstr "Mon profil"
#: canaille/templates/profile.html:52 #: canaille/templates/profile.html:55
msgid "User profile edition" msgid "User profile edition"
msgstr "Édition d'un profil utilisateur" msgstr "Édition d'un profil utilisateur"
#: canaille/templates/profile.html:58 #: canaille/templates/profile.html:61
msgid "Create a new user account" msgid "Create a new user account"
msgstr "Création d'un nouveau compte utilisateur" msgstr "Création d'un nouveau compte utilisateur"
#: canaille/templates/profile.html:60 #: canaille/templates/profile.html:63
msgid "Edit your personal informations" msgid "Edit your personal informations"
msgstr "Éditez vos informations personnelles" msgstr "Éditez vos informations personnelles"
#: canaille/templates/profile.html:62 #: canaille/templates/profile.html:65
msgid "Edit informations about an user" msgid "Edit informations about an user"
msgstr "Éditez les informations d'un utilisateur" msgstr "Éditez les informations d'un utilisateur"
#: canaille/templates/profile.html:77 #: canaille/templates/profile.html:80
msgid "Personal information" msgid "Personal information"
msgstr "Informations personnelles" msgstr "Informations personnelles"
#: canaille/templates/profile.html:99 #: canaille/templates/profile.html:88 canaille/templates/profile.html:99
msgid "Click to upload a photo"
msgstr "Cliquez pour mettre en ligne une photo"
#: canaille/templates/profile.html:126
msgid "Account information" msgid "Account information"
msgstr "Informations sur le compte" msgstr "Informations sur le compte"
#: canaille/templates/profile.html:117 #: canaille/templates/profile.html:144
msgid "User password is not mandatory" msgid "User password is not mandatory"
msgstr "Le mot de passe utilisateur n'est pas requis à la création" msgstr "Le mot de passe utilisateur n'est pas requis à la création"
#: canaille/templates/profile.html:120 #: canaille/templates/profile.html:147
msgid "The user password can be set:" msgid "The user password can be set:"
msgstr "Il pourra être renseigné :" msgstr "Il pourra être renseigné :"
#: canaille/templates/profile.html:122 #: canaille/templates/profile.html:149
msgid "by filling this form;" msgid "by filling this form;"
msgstr "en remplissant ce formulaire ;" msgstr "en remplissant ce formulaire ;"
#: canaille/templates/profile.html:123 #: canaille/templates/profile.html:150
msgid "" msgid ""
"by sending the user a password initialization mail, after the account " "by sending the user a password initialization mail, after the account "
"creation;" "creation;"
@ -855,7 +863,7 @@ msgstr ""
"en envoyant un lien d'initialisation de mot de passe, par mail à " "en envoyant un lien d'initialisation de mot de passe, par mail à "
"l'utilisateur, après la création de son compte;" "l'utilisateur, après la création de son compte;"
#: canaille/templates/profile.html:124 canaille/templates/profile.html:153 #: canaille/templates/profile.html:151 canaille/templates/profile.html:180
msgid "" msgid ""
"or simply waiting for the user to sign-in a first time, and then receive a " "or simply waiting for the user to sign-in a first time, and then receive a "
"password initialization mail." "password initialization mail."
@ -863,46 +871,46 @@ msgstr ""
"ou simplement en attendant la première connexion de l'utilisateur, afin " "ou simplement en attendant la première connexion de l'utilisateur, afin "
"qu'il reçoive un lien d'initialisation de mot de passe par email." "qu'il reçoive un lien d'initialisation de mot de passe par email."
#: canaille/templates/profile.html:127 canaille/templates/profile.html:156 #: canaille/templates/profile.html:154 canaille/templates/profile.html:183
msgid "The user will not be able to authenticate unless the password is set" msgid "The user will not be able to authenticate unless the password is set"
msgstr "" msgstr ""
"L'utilisateur ne pourra pas se connecter tant que son mot de passe n'est pas " "L'utilisateur ne pourra pas se connecter tant que son mot de passe n'est pas "
"défini" "défini"
#: canaille/templates/profile.html:141 #: canaille/templates/profile.html:168
msgid "Send email" msgid "Send email"
msgstr "Envoyer l'email" msgstr "Envoyer l'email"
#: canaille/templates/profile.html:146 #: canaille/templates/profile.html:173
msgid "This user does not have a password yet" msgid "This user does not have a password yet"
msgstr "L'utilisateur n'a pas encore de mot de passe" msgstr "L'utilisateur n'a pas encore de mot de passe"
#: canaille/templates/profile.html:149 #: canaille/templates/profile.html:176
msgid "You can solve this by:" msgid "You can solve this by:"
msgstr "Vous pouvez régler ceci en :" msgstr "Vous pouvez régler ceci en :"
#: canaille/templates/profile.html:151 #: canaille/templates/profile.html:178
msgid "setting a password using this form;" msgid "setting a password using this form;"
msgstr "renseignant un mot de passe via ce formulaire ;" msgstr "renseignant un mot de passe via ce formulaire ;"
#: canaille/templates/profile.html:152 #: canaille/templates/profile.html:179
msgid "" msgid ""
"sending the user a password initialization mail, by clicking this button;" "sending the user a password initialization mail, by clicking this button;"
msgstr "" msgstr ""
"envoyant un lien d'initialisation de mot de passe, par mail à l'utilisateur, " "envoyant un lien d'initialisation de mot de passe, par mail à l'utilisateur, "
"en cliquant sur ce bouton;" "en cliquant sur ce bouton;"
#: canaille/templates/profile.html:167 #: canaille/templates/profile.html:194
msgid "Send mail" msgid "Send mail"
msgstr "Envoyer l'email" msgstr "Envoyer l'email"
#: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:171 #: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:198
#: canaille/templates/reset-password.html:11 #: canaille/templates/reset-password.html:11
#: canaille/templates/reset-password.html:16 #: canaille/templates/reset-password.html:16
msgid "Password reset" msgid "Password reset"
msgstr "Réinitialisation du mot de passe" msgstr "Réinitialisation du mot de passe"
#: canaille/templates/profile.html:173 #: canaille/templates/profile.html:200
msgid "" msgid ""
"If the user has forgotten his password, you can send him a password reset " "If the user has forgotten his password, you can send him a password reset "
"email by clicking this button." "email by clicking this button."
@ -910,19 +918,19 @@ msgstr ""
"Si l'utilisateur a oublié son mot de passe, vous pouvez lui envoyer un email " "Si l'utilisateur a oublié son mot de passe, vous pouvez lui envoyer un email "
"contenant un lien de réinitilisation en cliquant sur ce bouton." "contenant un lien de réinitilisation en cliquant sur ce bouton."
#: canaille/templates/profile.html:185 #: canaille/templates/profile.html:212
msgid "Delete the user" msgid "Delete the user"
msgstr "Supprimer l'utilisateur" msgstr "Supprimer l'utilisateur"
#: canaille/templates/profile.html:187 #: canaille/templates/profile.html:214
msgid "Delete my account" msgid "Delete my account"
msgstr "Supprimer mon compte" msgstr "Supprimer mon compte"
#: canaille/templates/profile.html:194 #: canaille/templates/profile.html:221
msgid "Impersonate" msgid "Impersonate"
msgstr "Prendre l'identité" msgstr "Prendre l'identité"
#: canaille/templates/profile.html:200 canaille/templates/users.html:19 #: canaille/templates/profile.html:227 canaille/templates/users.html:19
msgid "Invite a user" msgid "Invite a user"
msgstr "Inviter un utilisateur" msgstr "Inviter un utilisateur"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-12-07 19:58+0100\n" "POT-Creation-Date: 2021-12-08 18:56+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,114 +17,114 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.1\n" "Generated-By: Babel 2.9.1\n"
#: canaille/__init__.py:123 #: canaille/__init__.py:129
msgid "Could not connect to the LDAP server '{uri}'" msgid "Could not connect to the LDAP server '{uri}'"
msgstr "" msgstr ""
#: canaille/__init__.py:139 #: canaille/__init__.py:145
msgid "LDAP authentication failed with user '{user}'" msgid "LDAP authentication failed with user '{user}'"
msgstr "" msgstr ""
#: canaille/account.py:69 canaille/account.py:94 canaille/oauth.py:74 #: canaille/account.py:77 canaille/account.py:102 canaille/oauth.py:74
msgid "Login failed, please check your information" msgid "Login failed, please check your information"
msgstr "" msgstr ""
#: canaille/account.py:100 #: canaille/account.py:108
#, python-format #, python-format
msgid "Connection successful. Welcome %(user)s" msgid "Connection successful. Welcome %(user)s"
msgstr "" msgstr ""
#: canaille/account.py:113 #: canaille/account.py:121
#, python-format #, python-format
msgid "You have been disconnected. See you next time %(user)s" msgid "You have been disconnected. See you next time %(user)s"
msgstr "" msgstr ""
#: canaille/account.py:130 #: canaille/account.py:138
msgid "Could not send the password initialization link." msgid "Could not send the password initialization link."
msgstr "" msgstr ""
#: canaille/account.py:135 #: canaille/account.py:143
msgid "" msgid ""
"A password initialization link has been sent at your email address. You " "A password initialization link has been sent at your email address. You "
"should receive it within 10 minutes." "should receive it within 10 minutes."
msgstr "" msgstr ""
#: canaille/account.py:141 canaille/account.py:326 #: canaille/account.py:149 canaille/account.py:340
msgid "Could not send the password initialization email" msgid "Could not send the password initialization email"
msgstr "" msgstr ""
#: canaille/account.py:192 canaille/account.py:264 #: canaille/account.py:204 canaille/account.py:275
msgid "User account creation failed." msgid "User account creation failed."
msgstr "" msgstr ""
#: canaille/account.py:213 canaille/account.py:234 #: canaille/account.py:225 canaille/account.py:246
msgid "The invitation link that brought you here was invalid." msgid "The invitation link that brought you here was invalid."
msgstr "" msgstr ""
#: canaille/account.py:220 #: canaille/account.py:232
msgid "Your account has already been created." msgid "Your account has already been created."
msgstr "" msgstr ""
#: canaille/account.py:227 #: canaille/account.py:239
msgid "You are already logged in, you cannot create an account." msgid "You are already logged in, you cannot create an account."
msgstr "" msgstr ""
#: canaille/account.py:269 #: canaille/account.py:280
msgid "You account has been created successfuly." msgid "You account has been created successfuly."
msgstr "" msgstr ""
#: canaille/account.py:299 #: canaille/account.py:313
msgid "User account creation succeed." msgid "User account creation succeed."
msgstr "" msgstr ""
#: canaille/account.py:320 #: canaille/account.py:334
msgid "" msgid ""
"A password initialization link has been sent at the user email address. " "A password initialization link has been sent at the user email address. "
"It should be received within 10 minutes." "It should be received within 10 minutes."
msgstr "" msgstr ""
#: canaille/account.py:334 #: canaille/account.py:348
msgid "" msgid ""
"A password reset link has been sent at the user email address. It should " "A password reset link has been sent at the user email address. It should "
"be received within 10 minutes." "be received within 10 minutes."
msgstr "" msgstr ""
#: canaille/account.py:340 #: canaille/account.py:354
msgid "Could not send the password reset email" msgid "Could not send the password reset email"
msgstr "" msgstr ""
#: canaille/account.py:371 #: canaille/account.py:385
msgid "Profile edition failed." msgid "Profile edition failed."
msgstr "" msgstr ""
#: canaille/account.py:396 #: canaille/account.py:413
msgid "Profile updated successfuly." msgid "Profile updated successfuly."
msgstr "" msgstr ""
#: canaille/account.py:416 #: canaille/account.py:433
#, python-format #, python-format
msgid "The user %(user)s has been sucessfuly deleted" msgid "The user %(user)s has been sucessfuly deleted"
msgstr "" msgstr ""
#: canaille/account.py:440 #: canaille/account.py:457
msgid "Could not send the password reset link." msgid "Could not send the password reset link."
msgstr "" msgstr ""
#: canaille/account.py:447 canaille/account.py:458 #: canaille/account.py:464 canaille/account.py:475
msgid "" msgid ""
"A password reset link has been sent at your email address. You should " "A password reset link has been sent at your email address. You should "
"receive it within 10 minutes." "receive it within 10 minutes."
msgstr "" msgstr ""
#: canaille/account.py:464 #: canaille/account.py:481
msgid "Could not reset your password" msgid "Could not reset your password"
msgstr "" msgstr ""
#: canaille/account.py:478 #: canaille/account.py:495
msgid "The password reset link that brought you here was invalid." msgid "The password reset link that brought you here was invalid."
msgstr "" msgstr ""
#: canaille/account.py:487 #: canaille/account.py:504
msgid "Your password has been updated successfuly" msgid "Your password has been updated successfuly"
msgstr "" msgstr ""
@ -132,7 +132,7 @@ msgstr ""
msgid "John Doe" msgid "John Doe"
msgstr "" msgstr ""
#: canaille/apputils.py:37 canaille/forms.py:93 canaille/forms.py:188 #: canaille/apputils.py:37 canaille/forms.py:93 canaille/forms.py:196
msgid "jdoe" msgid "jdoe"
msgstr "" msgstr ""
@ -152,7 +152,7 @@ msgstr ""
msgid "The access has been revoked" msgid "The access has been revoked"
msgstr "" msgstr ""
#: canaille/flaskutils.py:69 #: canaille/flaskutils.py:70
msgid "No SMTP server has been configured" msgid "No SMTP server has been configured"
msgstr "" msgstr ""
@ -178,27 +178,27 @@ msgid "Login"
msgstr "" msgstr ""
#: canaille/forms.py:45 canaille/forms.py:69 canaille/forms.py:117 #: canaille/forms.py:45 canaille/forms.py:69 canaille/forms.py:117
#: canaille/forms.py:199 #: canaille/forms.py:207
msgid "jane@doe.com" msgid "jane@doe.com"
msgstr "" msgstr ""
#: canaille/forms.py:55 canaille/forms.py:78 canaille/forms.py:127 #: canaille/forms.py:55 canaille/forms.py:78 canaille/forms.py:132
msgid "Password" msgid "Password"
msgstr "" msgstr ""
#: canaille/forms.py:81 canaille/forms.py:131 #: canaille/forms.py:81 canaille/forms.py:136
msgid "Password confirmation" msgid "Password confirmation"
msgstr "" msgstr ""
#: canaille/forms.py:84 canaille/forms.py:134 #: canaille/forms.py:84 canaille/forms.py:139
msgid "Password and confirmation do not match." msgid "Password and confirmation do not match."
msgstr "" msgstr ""
#: canaille/forms.py:92 canaille/forms.py:187 #: canaille/forms.py:92 canaille/forms.py:195
msgid "Username" msgid "Username"
msgstr "" msgstr ""
#: canaille/admin/clients.py:28 canaille/forms.py:96 canaille/forms.py:177 #: canaille/admin/clients.py:28 canaille/forms.py:96 canaille/forms.py:185
#: canaille/templates/admin/client_list.html:22 #: canaille/templates/admin/client_list.html:22
#: canaille/templates/group.html:91 canaille/templates/users.html:27 #: canaille/templates/group.html:91 canaille/templates/users.html:27
msgid "Name" msgid "Name"
@ -220,7 +220,7 @@ msgstr ""
msgid "Doe" msgid "Doe"
msgstr "" msgstr ""
#: canaille/forms.py:114 canaille/forms.py:192 #: canaille/forms.py:114 canaille/forms.py:200
msgid "Email address" msgid "Email address"
msgstr "" msgstr ""
@ -232,25 +232,29 @@ msgstr ""
msgid "555-000-555" msgid "555-000-555"
msgstr "" msgstr ""
#: canaille/forms.py:125 #: canaille/forms.py:126
msgid "Photo" msgid "Photo"
msgstr "" msgstr ""
#: canaille/forms.py:139 #: canaille/forms.py:130 canaille/templates/profile.html:91
msgid "Delete the photo"
msgstr ""
#: canaille/forms.py:144
msgid "Number" msgid "Number"
msgstr "" msgstr ""
#: canaille/forms.py:141 #: canaille/forms.py:146
msgid "1234" msgid "1234"
msgstr "" msgstr ""
#: canaille/forms.py:162 canaille/forms.py:205 canaille/templates/group.html:93 #: canaille/forms.py:170 canaille/forms.py:213 canaille/templates/group.html:93
#: canaille/templates/groups.html:9 canaille/templates/users.html:29 #: canaille/templates/groups.html:9 canaille/templates/users.html:29
#: canaille/themes/default/base.html:59 #: canaille/themes/default/base.html:59
msgid "Groups" msgid "Groups"
msgstr "" msgstr ""
#: canaille/forms.py:180 #: canaille/forms.py:188
msgid "group" msgid "group"
msgstr "" msgstr ""
@ -533,11 +537,11 @@ msgstr ""
#: canaille/templates/admin/client_edit.html:35 #: canaille/templates/admin/client_edit.html:35
#: canaille/templates/admin/client_edit.html:44 #: canaille/templates/admin/client_edit.html:44
#: canaille/templates/admin/client_edit.html:53 #: canaille/templates/admin/client_edit.html:53
#: canaille/templates/fomanticui.html:46 #: canaille/templates/fomanticui.html:48
msgid "This field is not editable" msgid "This field is not editable"
msgstr "" msgstr ""
#: canaille/templates/fomanticui.html:50 #: canaille/templates/fomanticui.html:52
msgid "This field is required" msgid "This field is required"
msgstr "" msgstr ""
@ -558,7 +562,7 @@ msgid ""
msgstr "" msgstr ""
#: canaille/templates/forgotten-password.html:38 #: canaille/templates/forgotten-password.html:38
#: canaille/templates/profile.html:137 canaille/templates/profile.html:165 #: canaille/templates/profile.html:164 canaille/templates/profile.html:192
msgid "Send again" msgid "Send again"
msgstr "" msgstr ""
@ -577,12 +581,12 @@ msgid ""
msgstr "" msgstr ""
#: canaille/templates/admin/client_edit.html:18 #: canaille/templates/admin/client_edit.html:18
#: canaille/templates/group.html:29 canaille/templates/profile.html:38 #: canaille/templates/group.html:29 canaille/templates/profile.html:41
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: canaille/templates/admin/client_edit.html:19 #: canaille/templates/admin/client_edit.html:19
#: canaille/templates/group.html:30 canaille/templates/profile.html:39 #: canaille/templates/group.html:30 canaille/templates/profile.html:42
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
@ -617,7 +621,7 @@ msgstr ""
msgid "Create group" msgid "Create group"
msgstr "" msgstr ""
#: canaille/templates/group.html:79 canaille/templates/profile.html:205 #: canaille/templates/group.html:79 canaille/templates/profile.html:232
msgid "Submit" msgid "Submit"
msgstr "" msgstr ""
@ -735,139 +739,143 @@ msgstr ""
msgid "Sign in" msgid "Sign in"
msgstr "" msgstr ""
#: canaille/templates/profile.html:15 #: canaille/templates/profile.html:18
msgid "This user cannot edit this field" msgid "This user cannot edit this field"
msgstr "" msgstr ""
#: canaille/templates/profile.html:17 #: canaille/templates/profile.html:20
msgid "This user cannot see this field" msgid "This user cannot see this field"
msgstr "" msgstr ""
#: canaille/templates/profile.html:26 #: canaille/templates/profile.html:29
msgid "Account deletion" msgid "Account deletion"
msgstr "" msgstr ""
#: canaille/templates/profile.html:31 #: canaille/templates/profile.html:34
msgid "" msgid ""
"Are you sure you want to delete this user? This action is unrevokable and" "Are you sure you want to delete this user? This action is unrevokable and"
" all the data about this user will be removed." " all the data about this user will be removed."
msgstr "" msgstr ""
#: canaille/templates/profile.html:33 #: canaille/templates/profile.html:36
msgid "" msgid ""
"Are you sure you want to delete your account? This action is unrevokable " "Are you sure you want to delete your account? This action is unrevokable "
"and all your data will be removed forever." "and all your data will be removed forever."
msgstr "" msgstr ""
#: canaille/templates/profile.html:48 #: canaille/templates/profile.html:51
msgid "User creation" msgid "User creation"
msgstr "" msgstr ""
#: canaille/templates/profile.html:50 canaille/themes/default/base.html:39 #: canaille/templates/profile.html:53 canaille/themes/default/base.html:39
msgid "My profile" msgid "My profile"
msgstr "" msgstr ""
#: canaille/templates/profile.html:52 #: canaille/templates/profile.html:55
msgid "User profile edition" msgid "User profile edition"
msgstr "" msgstr ""
#: canaille/templates/profile.html:58 #: canaille/templates/profile.html:61
msgid "Create a new user account" msgid "Create a new user account"
msgstr "" msgstr ""
#: canaille/templates/profile.html:60 #: canaille/templates/profile.html:63
msgid "Edit your personal informations" msgid "Edit your personal informations"
msgstr "" msgstr ""
#: canaille/templates/profile.html:62 #: canaille/templates/profile.html:65
msgid "Edit informations about an user" msgid "Edit informations about an user"
msgstr "" msgstr ""
#: canaille/templates/profile.html:77 #: canaille/templates/profile.html:80
msgid "Personal information" msgid "Personal information"
msgstr "" msgstr ""
#: canaille/templates/profile.html:99 #: canaille/templates/profile.html:88 canaille/templates/profile.html:99
msgid "Click to upload a photo"
msgstr ""
#: canaille/templates/profile.html:126
msgid "Account information" msgid "Account information"
msgstr "" msgstr ""
#: canaille/templates/profile.html:117 #: canaille/templates/profile.html:144
msgid "User password is not mandatory" msgid "User password is not mandatory"
msgstr "" msgstr ""
#: canaille/templates/profile.html:120 #: canaille/templates/profile.html:147
msgid "The user password can be set:" msgid "The user password can be set:"
msgstr "" msgstr ""
#: canaille/templates/profile.html:122 #: canaille/templates/profile.html:149
msgid "by filling this form;" msgid "by filling this form;"
msgstr "" msgstr ""
#: canaille/templates/profile.html:123 #: canaille/templates/profile.html:150
msgid "" msgid ""
"by sending the user a password initialization mail, after the account " "by sending the user a password initialization mail, after the account "
"creation;" "creation;"
msgstr "" msgstr ""
#: canaille/templates/profile.html:124 canaille/templates/profile.html:153 #: canaille/templates/profile.html:151 canaille/templates/profile.html:180
msgid "" msgid ""
"or simply waiting for the user to sign-in a first time, and then receive " "or simply waiting for the user to sign-in a first time, and then receive "
"a password initialization mail." "a password initialization mail."
msgstr "" msgstr ""
#: canaille/templates/profile.html:127 canaille/templates/profile.html:156 #: canaille/templates/profile.html:154 canaille/templates/profile.html:183
msgid "The user will not be able to authenticate unless the password is set" msgid "The user will not be able to authenticate unless the password is set"
msgstr "" msgstr ""
#: canaille/templates/profile.html:141 #: canaille/templates/profile.html:168
msgid "Send email" msgid "Send email"
msgstr "" msgstr ""
#: canaille/templates/profile.html:146 #: canaille/templates/profile.html:173
msgid "This user does not have a password yet" msgid "This user does not have a password yet"
msgstr "" msgstr ""
#: canaille/templates/profile.html:149 #: canaille/templates/profile.html:176
msgid "You can solve this by:" msgid "You can solve this by:"
msgstr "" msgstr ""
#: canaille/templates/profile.html:151 #: canaille/templates/profile.html:178
msgid "setting a password using this form;" msgid "setting a password using this form;"
msgstr "" msgstr ""
#: canaille/templates/profile.html:152 #: canaille/templates/profile.html:179
msgid "sending the user a password initialization mail, by clicking this button;" msgid "sending the user a password initialization mail, by clicking this button;"
msgstr "" msgstr ""
#: canaille/templates/profile.html:167 #: canaille/templates/profile.html:194
msgid "Send mail" msgid "Send mail"
msgstr "" msgstr ""
#: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:171 #: canaille/templates/admin/mails.html:34 canaille/templates/profile.html:198
#: canaille/templates/reset-password.html:11 #: canaille/templates/reset-password.html:11
#: canaille/templates/reset-password.html:16 #: canaille/templates/reset-password.html:16
msgid "Password reset" msgid "Password reset"
msgstr "" msgstr ""
#: canaille/templates/profile.html:173 #: canaille/templates/profile.html:200
msgid "" msgid ""
"If the user has forgotten his password, you can send him a password reset" "If the user has forgotten his password, you can send him a password reset"
" email by clicking this button." " email by clicking this button."
msgstr "" msgstr ""
#: canaille/templates/profile.html:185 #: canaille/templates/profile.html:212
msgid "Delete the user" msgid "Delete the user"
msgstr "" msgstr ""
#: canaille/templates/profile.html:187 #: canaille/templates/profile.html:214
msgid "Delete my account" msgid "Delete my account"
msgstr "" msgstr ""
#: canaille/templates/profile.html:194 #: canaille/templates/profile.html:221
msgid "Impersonate" msgid "Impersonate"
msgstr "" msgstr ""
#: canaille/templates/profile.html:200 canaille/templates/users.html:19 #: canaille/templates/profile.html:227 canaille/templates/users.html:19
msgid "Invite a user" msgid "Invite a user"
msgstr "" msgstr ""

View file

@ -113,9 +113,9 @@ GROUP_USER_FILTER = "member={user.dn}"
# The 'READ' and 'WRITE' attributes are the LDAP attributes of the user # The 'READ' and 'WRITE' attributes are the LDAP attributes of the user
# object that users will be able to read and/or write. # object that users will be able to read and/or write.
[ACL.DEFAULT] [ACL.DEFAULT]
READ = ["uid", "groups"]
PERMISSIONS = ["use_oidc"] PERMISSIONS = ["use_oidc"]
WRITE = ["givenName", "sn", "userPassword", "telephoneNumber"] READ = ["uid", "groups"]
WRITE = ["jpegPhoto", "givenName", "sn", "userPassword", "telephoneNumber", "mail"]
[ACL.ADMIN] [ACL.ADMIN]
FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld" FILTER = "memberof=cn=admins,ou=groups,dc=mydomain,dc=tld"
@ -154,7 +154,7 @@ GIVEN_NAME = "givenName"
FAMILY_NAME = "sn" FAMILY_NAME = "sn"
PREFERRED_USERNAME = "displayName" PREFERRED_USERNAME = "displayName"
LOCALE = "preferredLanguage" LOCALE = "preferredLanguage"
PICTURE = "photo" PICTURE = "jpegPhoto"
ADDRESS = "postalAddress" ADDRESS = "postalAddress"
# The SMTP server options. If not set, mail related features such as # The SMTP server options. If not set, mail related features such as

View file

@ -151,6 +151,7 @@ def configuration(slapd_server, smtpd, keypair_path):
"WRITE": [ "WRITE": [
"mail", "mail",
"givenName", "givenName",
"jpegPhoto",
"sn", "sn",
"userPassword", "userPassword",
"telephoneNumber", "telephoneNumber",
@ -446,3 +447,8 @@ def bar_group(app, admin, slapd_connection):
yield g yield g
admin._groups = [] admin._groups = []
g.delete(conn=slapd_connection) g.delete(conn=slapd_connection)
@pytest.fixture
def jpeg_photo():
return b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x01,\x01,\x00\x00\xff\xfe\x00\x13Created with GIMP\xff\xe2\x02\xb0ICC_PROFILE\x00\x01\x01\x00\x00\x02\xa0lcms\x040\x00\x00mntrRGB XYZ \x07\xe5\x00\x0c\x00\x08\x00\x0f\x00\x16\x00(acspAPPL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-lcms\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\rdesc\x00\x00\x01 \x00\x00\x00@cprt\x00\x00\x01`\x00\x00\x006wtpt\x00\x00\x01\x98\x00\x00\x00\x14chad\x00\x00\x01\xac\x00\x00\x00,rXYZ\x00\x00\x01\xd8\x00\x00\x00\x14bXYZ\x00\x00\x01\xec\x00\x00\x00\x14gXYZ\x00\x00\x02\x00\x00\x00\x00\x14rTRC\x00\x00\x02\x14\x00\x00\x00 gTRC\x00\x00\x02\x14\x00\x00\x00 bTRC\x00\x00\x02\x14\x00\x00\x00 chrm\x00\x00\x024\x00\x00\x00$dmnd\x00\x00\x02X\x00\x00\x00$dmdd\x00\x00\x02|\x00\x00\x00$mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00$\x00\x00\x00\x1c\x00G\x00I\x00M\x00P\x00 \x00b\x00u\x00i\x00l\x00t\x00-\x00i\x00n\x00 \x00s\x00R\x00G\x00Bmluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x1a\x00\x00\x00\x1c\x00P\x00u\x00b\x00l\x00i\x00c\x00 \x00D\x00o\x00m\x00a\x00i\x00n\x00\x00XYZ \x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-sf32\x00\x00\x00\x00\x00\x01\x0cB\x00\x00\x05\xde\xff\xff\xf3%\x00\x00\x07\x93\x00\x00\xfd\x90\xff\xff\xfb\xa1\xff\xff\xfd\xa2\x00\x00\x03\xdc\x00\x00\xc0nXYZ \x00\x00\x00\x00\x00\x00o\xa0\x00\x008\xf5\x00\x00\x03\x90XYZ \x00\x00\x00\x00\x00\x00$\x9f\x00\x00\x0f\x84\x00\x00\xb6\xc4XYZ \x00\x00\x00\x00\x00\x00b\x97\x00\x00\xb7\x87\x00\x00\x18\xd9para\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02ff\x00\x00\xf2\xa7\x00\x00\rY\x00\x00\x13\xd0\x00\x00\n[chrm\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\xa3\xd7\x00\x00T|\x00\x00L\xcd\x00\x00\x99\x9a\x00\x00&g\x00\x00\x0f\\mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x08\x00\x00\x00\x1c\x00G\x00I\x00M\x00Pmluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x08\x00\x00\x00\x1c\x00s\x00R\x00G\x00B\xff\xdb\x00C\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xdb\x00C\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xc2\x00\x11\x08\x00\x01\x00\x01\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x10\x03\x10\x00\x00\x01\x7f\x0f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02\x7f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x03\x01\x01?\x01\x7f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x02\x01\x01?\x01\x7f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x06?\x02\x7f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01?!\x7f\xff\xda\x00\x0c\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x03\x01\x01?\x10\x7f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x02\x01\x01?\x10\x7f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01?\x10\x7f\xff\xd9"

View file

@ -1,8 +1,16 @@
from canaille.models import User from canaille.models import User
from webtest import Upload
def test_edition( def test_edition(
testclient, slapd_server, slapd_connection, logged_user, admin, foo_group, bar_group testclient,
slapd_server,
slapd_connection,
logged_user,
admin,
foo_group,
bar_group,
jpeg_photo,
): ):
res = testclient.get("/profile/user", status=200) res = testclient.get("/profile/user", status=200)
assert set(res.form["groups"].options) == set( assert set(res.form["groups"].options) == set(
@ -27,6 +35,7 @@ def test_edition(
"cn=foo,ou=groups,dc=slapd-test,dc=python-ldap,dc=org", "cn=foo,ou=groups,dc=slapd-test,dc=python-ldap,dc=org",
"cn=bar,ou=groups,dc=slapd-test,dc=python-ldap,dc=org", "cn=bar,ou=groups,dc=slapd-test,dc=python-ldap,dc=org",
] ]
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res = res.form.submit(name="action", value="edit", status=200) res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res) assert "Profile updated successfuly." in res, str(res)
@ -40,6 +49,7 @@ def test_edition(
assert ["email@mydomain.tld"] == logged_user.mail assert ["email@mydomain.tld"] == logged_user.mail
assert ["555-666-777"] == logged_user.telephoneNumber assert ["555-666-777"] == logged_user.telephoneNumber
assert "666" == logged_user.employeeNumber assert "666" == logged_user.employeeNumber
assert [jpeg_photo] == logged_user.jpegPhoto
foo_group.reload(slapd_connection) foo_group.reload(slapd_connection)
bar_group.reload(slapd_connection) bar_group.reload(slapd_connection)
@ -312,3 +322,58 @@ def test_email_reset_button(smtpd, testclient, slapd_connection, logged_admin):
) )
assert "Send again" in res assert "Send again" in res
assert len(smtpd.messages) == 1 assert len(smtpd.messages) == 1
def test_photo_edition(
testclient,
slapd_server,
slapd_connection,
logged_user,
jpeg_photo,
):
# Add a photo
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res.form["jpegPhoto_delete"] = False
res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res)
with testclient.app.app_context():
logged_user = User.get(dn=logged_user.dn, conn=slapd_connection)
assert [jpeg_photo] == logged_user.jpegPhoto
# No change
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto_delete"] = False
res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res)
with testclient.app.app_context():
logged_user = User.get(dn=logged_user.dn, conn=slapd_connection)
assert [jpeg_photo] == logged_user.jpegPhoto
# Photo deletion
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto_delete"] = True
res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res)
with testclient.app.app_context():
logged_user = User.get(dn=logged_user.dn, conn=slapd_connection)
assert [] == logged_user.jpegPhoto
# Photo deletion AND upload, this should never happen
res = testclient.get("/profile/user", status=200)
res.form["jpegPhoto"] = Upload("logo.jpg", jpeg_photo)
res.form["jpegPhoto_delete"] = True
res = res.form.submit(name="action", value="edit", status=200)
assert "Profile updated successfuly." in res, str(res)
with testclient.app.app_context():
logged_user = User.get(dn=logged_user.dn, conn=slapd_connection)
assert [] == logged_user.jpegPhoto