1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-11-24 18:47:23 +00:00

Merge branch 'master' into Develop

# Conflicts:
#	cps/admin.py
#	cps/config_sql.py
#	cps/templates/config_edit.html
#	cps/web.py
This commit is contained in:
Ozzieisaacs 2020-12-03 16:03:37 +01:00
commit 7aabfc573b
33 changed files with 5589 additions and 5029 deletions

View File

@ -164,7 +164,6 @@ def view_configuration():
@login_required
@admin_required
def update_view_configuration():
reboot_required = False
to_save = request.form.to_dict()
_config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
@ -172,7 +171,8 @@ def update_view_configuration():
_config_string("config_calibre_web_title")
_config_string("config_columns_to_ignore")
reboot_required |= _config_string("config_title_regex")
if _config_string("config_title_regex"):
calibre_db.update_title_sort(config)
_config_int("config_read_column")
_config_int("config_theme")
@ -191,10 +191,6 @@ def update_view_configuration():
config.save()
flash(_(u"Calibre-Web configuration updated"), category="success")
before_request()
if reboot_required:
db.dispose()
ub.dispose()
web_server.stop(True)
return view_configuration()
@ -614,12 +610,24 @@ def _configuration_ldap_helper(to_save, gdriveError):
return reboot_required, _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'),
gdriveError)
if to_save["ldap_import_user_filter"] == '0':
config.config_ldap_member_user_object = ""
else:
if config.config_ldap_member_user_object.count("%s") != 1:
return reboot_required, \
_configuration_result(_('LDAP Member User Filter needs to Have One "%s" Format Identifier'),
gdriveError)
if config.config_ldap_member_user_object.count("(") != config.config_ldap_member_user_object.count(")"):
return reboot_required, _configuration_result(_('LDAP Member User Filter Has Unmatched Parenthesis'),
gdriveError)
if config.config_ldap_cacert_path or config.config_ldap_cert_path or config.config_ldap_key_path:
if not (os.path.isfile(config.config_ldap_cacert_path) and
os.path.isfile(config.config_ldap_cert_path) and
os.path.isfile(config.config_ldap_key_path)):
return reboot_required, \
_configuration_result(_('LDAP CACertificate, Certificate or Key Location is not Valid, Please Enter Correct Path'),
_configuration_result(_('LDAP CACertificate, Certificate or Key Location is not Valid, '
'Please Enter Correct Path'),
gdriveError)
return reboot_required, None

View File

@ -113,6 +113,7 @@ class _Settings(_Base):
config_ldap_key_path = Column(String, default="")
config_ldap_dn = Column(String, default='dc=example,dc=org')
config_ldap_user_object = Column(String, default='uid=%s')
config_ldap_member_user_object = Column(String, default='') #
config_ldap_openldap = Column(Boolean, default=True)
config_ldap_group_object_filter = Column(String, default='(&(objectclass=posixGroup)(cn=%s))')
config_ldap_group_members_field = Column(String, default='memberUid')

View File

@ -935,8 +935,6 @@ def convert_bookformat(book_id):
@edit_required
def edit_list_book(param):
vals = request.form.to_dict()
# calibre_db.update_title_sort(config)
#calibre_db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
book = calibre_db.get_book(vals['pk'])
if param =='series_index':
edit_book_series_index(vals['value'], book)

View File

@ -862,6 +862,7 @@ def HandleUserRequest(dummy=None):
@kobo.route("/v1/products/<dummy>/recommendations", methods=["GET", "POST"])
@kobo.route("/v1/products/<dummy>/nextread", methods=["GET", "POST"])
@kobo.route("/v1/products/<dummy>/reviews", methods=["GET", "POST"])
@kobo.route("/v1/products/books/external/<dummy>", methods=["GET", "POST"])
@kobo.route("/v1/products/books/series/<dummy>", methods=["GET", "POST"])
@kobo.route("/v1/products/books/<dummy>", methods=["GET", "POST"])
@kobo.route("/v1/products/dailydeal", methods=["GET", "POST"])

View File

@ -25,7 +25,7 @@ import sys
import datetime
from functools import wraps
from flask import Blueprint, request, render_template, Response, g, make_response
from flask import Blueprint, request, render_template, Response, g, make_response, abort
from flask_login import current_user
from sqlalchemy.sql.expression import func, text, or_, and_
from werkzeug.security import check_password_hash
@ -33,7 +33,7 @@ from werkzeug.security import check_password_hash
from . import constants, logger, config, db, calibre_db, ub, services, get_locale, isoLanguages
from .helper import get_download_link, get_book_cover
from .pagination import Pagination
from .web import render_read_books, download_required
from .web import render_read_books, download_required, load_user_from_request
from flask_babel import gettext as _
from babel import Locale as LC
from babel.core import UnknownLocaleError
@ -383,8 +383,13 @@ def feed_shelf(book_id):
@opds.route("/opds/download/<book_id>/<book_format>/")
@requires_basic_auth_if_no_ano
@download_required
def opds_download_link(book_id, book_format):
# I gave up with this: With enabled ldap login, the user doesn't get logged in, therefore it's always guest
# workaround, loading the user from the request and checking it's download rights here
# in case of anonymous browsing user is None
user = load_user_from_request(request) or current_user
if not user.role_download():
return abort(403)
if "Kobo" in request.headers.get('User-Agent'):
client = "kobo"
else:
@ -418,7 +423,10 @@ def feed_search(term):
def check_auth(username, password):
if sys.version_info.major == 3:
try:
username = username.encode('windows-1252')
except UnicodeEncodeError:
username = username.encode('utf-8')
user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) ==
username.decode('utf-8').lower()).first()
return bool(user and check_password_hash(str(user.password), password))

View File

@ -117,7 +117,7 @@ def bind_user(username, password):
return None, error
except LDAPException as ex:
if ex.message == 'Invalid credentials':
error = ("LDAP admin login failed")
error = "LDAP admin login failed"
return None, error
if ex.message == "Can't contact LDAP server":
# log.warning('LDAP Server down: %s', ex)

View File

@ -23,16 +23,12 @@
<h3>{{_("In Library")}}</h3>
{% endif %}
<div class="filterheader hidden-xs hidden-sm">
<a id="new" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<!--div class="btn-group character" role="group">
<a id="no_shelf" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-list"></span><b>?</b></a>
<div id="all" class="btn btn-primary">{{_('All')}}</div>
</div-->
<a id="new" data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data='author', book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
</div>
<div class="row display-flex">
{% if entries[0] %}

View File

@ -60,10 +60,10 @@
<label for="config_google_drive_watch_changes_response">{{_('Metadata Watch Channel ID')}}</label>
<div class="form-group input-group required">
<input type="text" class="form-control" name="config_google_drive_watch_changes_response" id="config_google_drive_watch_changes_response" value="{{ config.config_google_drive_watch_changes_response['id'] }} expires on {{ config.config_google_drive_watch_changes_response['expiration'] | strftime }}" autocomplete="off" disabled="">
<span class="input-group-btn"><a href="{{ url_for('gdrive.revoke_watch_gdrive') }}" class="btn btn-primary">{{_('Revoke')}}</a></span>
<span class="input-group-btn"><a href="{{ url_for('gdrive.revoke_watch_gdrive') }}" id="watch_revoke" class="btn btn-primary">{{_('Revoke')}}</a></span>
</div>
{% else %}
<a href="{{ url_for('gdrive.watch_gdrive') }}" class="btn btn-primary">Enable watch of metadata.db</a>
<a href="{{ url_for('gdrive.watch_gdrive') }}" id="enable_gdrive_watch" class="btn btn-primary">Enable watch of metadata.db</a>
{% endif %}
{% endif %}
{% endif %}
@ -331,6 +331,20 @@
<label for="config_ldap_group_members_field">{{_('LDAP Group Members Field')}}</label>
<input type="text" class="form-control" id="config_ldap_group_members_field" name="config_ldap_group_members_field" value="{% if config.config_ldap_group_members_field != None %}{{ config.config_ldap_group_members_field }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="ldap_import_user_filter">{{_('LDAP Authentication')}}</label>
<select name="ldap_import_user_filter" id="ldap_import_user_filter" class="form-control" data-control="ldap_member_user_object">
<option value="0" {% if config.config_ldap_member_user_object == "" %}selected{% endif %}>{{ _('Autodetect') }}</option>
<option value="1" {% if config.config_ldap_member_user_object %}selected{% endif %}>{{ _('Custom Filter') }}</option>
</select>
</div>
<div data-related="ldap_member_user_object-1">
<div class="form-group">
<label for="config_ldap_member_user_object">{{_('LDAP Member User Filter')}}</label>
<input type="text" class="form-control" id="config_ldap_member_user_object" name="config_ldap_member_user_object" value="{% if config.config_ldap_member_user_object != None %}{{ config.config_ldap_member_user_object }}{% endif %}" autocomplete="off">
</div>
</div>
</div>
{% endif %}
{% if feature_support['oauth'] %}

View File

@ -62,15 +62,18 @@
<div class="discover load-more">
<h2 class="{{title}}">{{_(title)}}</h2>
<div class="filterheader hidden-xs hidden-sm">
<a data-toggle="tooltip" id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<!--div class="btn-group character">
<a id="no_shelf" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-list"></span> <b>{{_('Group by series')}}</b></a>
</div-->
<a data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" id="auth_az" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='authaz')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" id="auth_za" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='authza')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
{% if page == 'series' %}
<a data-toggle="tooltip" title="{{_('Sort ascending according to series index')}}" id="series_asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesasc')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort descending according to series index')}}" id="series_desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesdesc')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
{% endif %}
</div>
<div class="row display-flex">

View File

@ -26,12 +26,14 @@
{% endif %}
{% endif %}
<div class="filterheader hidden-xs hidden-sm"><!-- ToDo: Implement filter for search results -->
<a id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='new', query=query)}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='old', query=query)}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='abc', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='zyx', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='pubnew', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='pubold', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="new" data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='new', query=query)}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='old', query=query)}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='abc', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='zyx', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="auth_az" data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='authaz', query=query)}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="auth_za" data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='authza', query=query)}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='pubnew', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort_param='pubold', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
</div>
{% endif %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Calibre-Web\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-11-14 13:15+0100\n"
"POT-Creation-Date: 2020-12-01 14:10+0100\n"
"PO-Revision-Date: 2020-06-07 06:47+0200\n"
"Last-Translator: Dekani <dekani1500@gmail.com>\n"
"Language: fr\n"
@ -40,268 +40,268 @@ msgstr "installé"
msgid "not installed"
msgstr "non installé"
#: cps/about.py:96
#: cps/about.py:99
msgid "Statistics"
msgstr "Statistiques"
#: cps/admin.py:93
#: cps/admin.py:94
msgid "Server restarted, please reload page"
msgstr "Serveur redémarré, merci de rafraîchir la page"
#: cps/admin.py:95
#: cps/admin.py:96
msgid "Performing shutdown of server, please close window"
msgstr "Arrêt du serveur en cours, merci de fermer la fenêtre"
#: cps/admin.py:103
#: cps/admin.py:104
msgid "Reconnect successful"
msgstr "Reconnecté avec succès"
#: cps/admin.py:106
#: cps/admin.py:107
msgid "Unknown command"
msgstr "Commande inconnue"
#: cps/admin.py:116 cps/editbooks.py:611 cps/editbooks.py:623
#: cps/editbooks.py:723 cps/editbooks.py:725 cps/editbooks.py:786
#: cps/editbooks.py:802 cps/updater.py:510 cps/uploader.py:98
#: cps/admin.py:117 cps/editbooks.py:611 cps/editbooks.py:623
#: cps/editbooks.py:726 cps/editbooks.py:728 cps/editbooks.py:789
#: cps/editbooks.py:805 cps/updater.py:510 cps/uploader.py:98
#: cps/uploader.py:108
msgid "Unknown"
msgstr "Inconnu"
#: cps/admin.py:137
#: cps/admin.py:138
msgid "Admin page"
msgstr "Page admin"
#: cps/admin.py:159
#: cps/admin.py:160
msgid "UI Configuration"
msgstr "Configuration de linterface utilisateur"
#: cps/admin.py:191 cps/admin.py:718
#: cps/admin.py:192 cps/admin.py:729
msgid "Calibre-Web configuration updated"
msgstr "Configuration de Calibre-Web mise à jour"
#: cps/admin.py:436 cps/admin.py:442 cps/admin.py:453 cps/admin.py:464
#: cps/admin.py:438 cps/admin.py:444 cps/admin.py:455 cps/admin.py:466
#: cps/templates/modal_dialogs.html:29
msgid "Deny"
msgstr "Refuser"
#: cps/admin.py:438 cps/admin.py:444 cps/admin.py:455 cps/admin.py:466
#: cps/admin.py:440 cps/admin.py:446 cps/admin.py:457 cps/admin.py:468
#: cps/templates/modal_dialogs.html:28
msgid "Allow"
msgstr "Autoriser"
#: cps/admin.py:512
#: cps/admin.py:514
msgid "client_secrets.json Is Not Configured For Web Application"
msgstr "client_secrets.json n'est pas configuré pour l'application Web"
#: cps/admin.py:551
#: cps/admin.py:554
msgid "Logfile Location is not Valid, Please Enter Correct Path"
msgstr "L'emplacement du fichier logfile est incorrect, veuillez saisir un chemin valide"
#: cps/admin.py:556
#: cps/admin.py:560
msgid "Access Logfile Location is not Valid, Please Enter Correct Path"
msgstr "L'emplacement du fichier Access Logfile est incorrect, veuillez saisir un chemin valide"
#: cps/admin.py:582
#: cps/admin.py:586
msgid "Please Enter a LDAP Provider, Port, DN and User Object Identifier"
msgstr "Veuillez saisir un fournisseur LDAP, Port, DN et l'identifiant objet de l'utilisateur"
#: cps/admin.py:595
#: cps/admin.py:601
#, python-format
msgid "LDAP Group Object Filter Needs to Have One \"%s\" Format Identifier"
msgstr "Le filtre objet du groupe LDAP a besoin d'un identifiant de format \"%s\""
#: cps/admin.py:598
#: cps/admin.py:604
msgid "LDAP Group Object Filter Has Unmatched Parenthesis"
msgstr "Le filtre objet du groupe LDAP a une parenthèse non gérée"
#: cps/admin.py:602
#: cps/admin.py:609
#, python-format
msgid "LDAP User Object Filter needs to Have One \"%s\" Format Identifier"
msgstr "Le filtre objet de l'utilisateur LDAP a besoin d'un identifiant de format \"%s\""
#: cps/admin.py:605
#: cps/admin.py:612
msgid "LDAP User Object Filter Has Unmatched Parenthesis"
msgstr "Le filtre objet de l'utilisateur LDAP a une parenthèse non gérée"
#: cps/admin.py:609
#: cps/admin.py:617
msgid "LDAP Certificate Location is not Valid, Please Enter Correct Path"
msgstr "L'emplacement du certificat LDAP est incorrect, veuillez saisir un chemin valide"
#: cps/admin.py:631
#: cps/admin.py:642
msgid "Keyfile Location is not Valid, Please Enter Correct Path"
msgstr "L'emplacement du fichier Keyfile est incorrect, veuillez saisir un chemin valide"
#: cps/admin.py:635
#: cps/admin.py:646
msgid "Certfile Location is not Valid, Please Enter Correct Path"
msgstr "L'emplacement du fichier Certfile est incorrect, veuillez saisir un chemin valide"
#: cps/admin.py:701 cps/admin.py:800 cps/admin.py:890 cps/admin.py:939
#: cps/admin.py:712 cps/admin.py:811 cps/admin.py:901 cps/admin.py:950
#: cps/shelf.py:100 cps/shelf.py:161 cps/shelf.py:202 cps/shelf.py:260
#: cps/shelf.py:309 cps/shelf.py:338 cps/shelf.py:368 cps/shelf.py:392
msgid "Settings DB is not Writeable"
msgstr ""
#: cps/admin.py:713
#: cps/admin.py:724
msgid "DB Location is not Valid, Please Enter Correct Path"
msgstr "L'emplacement DB est incorrect, veuillez saisir un chemin valide"
#: cps/admin.py:715
#: cps/admin.py:726
msgid "DB is not Writeable"
msgstr "La DB n'est pas accessible en écriture"
#: cps/admin.py:748
#: cps/admin.py:759
msgid "Basic Configuration"
msgstr "Configuration principale"
#: cps/admin.py:763 cps/web.py:1508
#: cps/admin.py:774 cps/web.py:1508
msgid "Please fill out all fields!"
msgstr "Veuillez compléter tous les champs !"
#: cps/admin.py:766 cps/admin.py:778 cps/admin.py:784 cps/admin.py:908
#: cps/admin.py:777 cps/admin.py:789 cps/admin.py:795 cps/admin.py:919
msgid "Add new user"
msgstr "Ajouter un nouvel utilisateur"
#: cps/admin.py:775 cps/web.py:1754
#: cps/admin.py:786 cps/web.py:1754
msgid "E-mail is not from valid domain"
msgstr "Cette adresse de courriel nappartient pas à un domaine valide"
#: cps/admin.py:782 cps/admin.py:797
#: cps/admin.py:793 cps/admin.py:808
msgid "Found an existing account for this e-mail address or nickname."
msgstr "Un compte existant a été trouvé pour cette adresse de courriel ou pour ce surnom."
#: cps/admin.py:793
#: cps/admin.py:804
#, python-format
msgid "User '%(user)s' created"
msgstr "Utilisateur '%(user)s' créé"
#: cps/admin.py:809
#: cps/admin.py:820
#, python-format
msgid "User '%(nick)s' deleted"
msgstr "Utilisateur '%(nick)s' supprimé"
#: cps/admin.py:812
#: cps/admin.py:823
msgid "No admin user remaining, can't delete user"
msgstr "Aucun utilisateur admin restant, impossible de supprimer lutilisateur"
#: cps/admin.py:818
#: cps/admin.py:829
msgid "No admin user remaining, can't remove admin role"
msgstr "Aucun utilisateur admin restant, impossible de supprimer le rôle admin"
#: cps/admin.py:854 cps/web.py:1796
#: cps/admin.py:865 cps/web.py:1796
msgid "Found an existing account for this e-mail address."
msgstr "Un compte existant a été trouvé pour cette adresse de courriel."
#: cps/admin.py:863 cps/admin.py:877 cps/admin.py:980 cps/web.py:1772
#: cps/admin.py:874 cps/admin.py:888 cps/admin.py:991 cps/web.py:1772
#, python-format
msgid "Edit User %(nick)s"
msgstr "Éditer l'utilisateur %(nick)s"
#: cps/admin.py:869 cps/web.py:1765
#: cps/admin.py:880 cps/web.py:1765
msgid "This username is already taken"
msgstr "Cet utilisateur est déjà pris"
#: cps/admin.py:884
#: cps/admin.py:895
#, python-format
msgid "User '%(nick)s' updated"
msgstr "Utilisateur '%(nick)s' mis à jour"
#: cps/admin.py:887
#: cps/admin.py:898
msgid "An unknown error occured."
msgstr "Oups ! Une erreur inconnue a eu lieu."
#: cps/admin.py:917 cps/templates/admin.html:71
#: cps/admin.py:928 cps/templates/admin.html:71
msgid "Edit E-mail Server Settings"
msgstr "Modifier les paramètres du serveur de courriels"
#: cps/admin.py:946
#: cps/admin.py:957
#, python-format
msgid "Test e-mail successfully send to %(kindlemail)s"
msgstr "Courriel de test envoyé avec succès sur %(kindlemail)s"
#: cps/admin.py:949
#: cps/admin.py:960
#, python-format
msgid "There was an error sending the Test e-mail: %(res)s"
msgstr "Il y a eu une erreur pendant lenvoi du courriel de test : %(res)s"
#: cps/admin.py:951
#: cps/admin.py:962
msgid "Please configure your e-mail address first..."
msgstr "Veuillez d'abord configurer votre adresse de courriel..."
#: cps/admin.py:953
#: cps/admin.py:964
msgid "E-mail server settings updated"
msgstr "Les paramètres du serveur de courriels ont été mis à jour"
#: cps/admin.py:964
#: cps/admin.py:975
msgid "User not found"
msgstr "L'utilisateur n'a pas été trouvé"
#: cps/admin.py:991
#: cps/admin.py:1002
#, python-format
msgid "Password for user %(user)s reset"
msgstr "Le mot de passe de lutilisateur %(user)s a été réinitialisé"
#: cps/admin.py:994 cps/web.py:1532 cps/web.py:1596
#: cps/admin.py:1005 cps/web.py:1532 cps/web.py:1596
msgid "An unknown error occurred. Please try again later."
msgstr "Une erreur inconnue est survenue. Veuillez réessayer plus tard."
#: cps/admin.py:997 cps/web.py:1470
#: cps/admin.py:1008 cps/web.py:1470
msgid "Please configure the SMTP mail settings first..."
msgstr "Veuillez configurer les paramètres SMTP au préalable..."
#: cps/admin.py:1009
#: cps/admin.py:1020
msgid "Logfile viewer"
msgstr "Visualiseur de fichier journal"
#: cps/admin.py:1049
#: cps/admin.py:1081
msgid "Requesting update package"
msgstr "Demande de mise à jour"
#: cps/admin.py:1050
#: cps/admin.py:1082
msgid "Downloading update package"
msgstr "Téléchargement de la mise à jour"
#: cps/admin.py:1051
#: cps/admin.py:1083
msgid "Unzipping update package"
msgstr "Décompression de la mise à jour"
#: cps/admin.py:1052
#: cps/admin.py:1084
msgid "Replacing files"
msgstr "Remplacement des fichiers"
#: cps/admin.py:1053
#: cps/admin.py:1085
msgid "Database connections are closed"
msgstr "Les connexions à la base de données ont été fermées"
#: cps/admin.py:1054
#: cps/admin.py:1086
msgid "Stopping server"
msgstr "Arrêt du serveur"
#: cps/admin.py:1055
#: cps/admin.py:1087
msgid "Update finished, please press okay and reload page"
msgstr "Mise à jour terminée, merci dappuyer sur okay et de rafraîchir la page"
#: cps/admin.py:1056 cps/admin.py:1057 cps/admin.py:1058 cps/admin.py:1059
#: cps/admin.py:1060
#: cps/admin.py:1088 cps/admin.py:1089 cps/admin.py:1090 cps/admin.py:1091
#: cps/admin.py:1092
msgid "Update failed:"
msgstr "La mise à jour a échoué :"
#: cps/admin.py:1056 cps/updater.py:320 cps/updater.py:521 cps/updater.py:523
#: cps/admin.py:1088 cps/updater.py:320 cps/updater.py:521 cps/updater.py:523
msgid "HTTP Error"
msgstr "Erreur HTTP"
#: cps/admin.py:1057 cps/updater.py:322 cps/updater.py:525
#: cps/admin.py:1089 cps/updater.py:322 cps/updater.py:525
msgid "Connection error"
msgstr "Erreur de connexion"
#: cps/admin.py:1058 cps/updater.py:324 cps/updater.py:527
#: cps/admin.py:1090 cps/updater.py:324 cps/updater.py:527
msgid "Timeout while establishing connection"
msgstr "Délai d'attente dépassé lors de l'établissement de connexion"
#: cps/admin.py:1059 cps/updater.py:326 cps/updater.py:529
#: cps/admin.py:1091 cps/updater.py:326 cps/updater.py:529
msgid "General error"
msgstr "Erreur générale"
#: cps/admin.py:1060
#: cps/admin.py:1092
msgid "Update File Could Not be Saved in Temp Dir"
msgstr "Le fichier de mise à jour ne peut pas être sauvegardé dans le répertoire temporaire"
@ -335,12 +335,12 @@ msgstr "modifier les métadonnées"
msgid "%(langname)s is not a valid language"
msgstr "%(langname)s n'est pas une langue valide"
#: cps/editbooks.py:512 cps/editbooks.py:768
#: cps/editbooks.py:512 cps/editbooks.py:771
#, python-format
msgid "File extension '%(ext)s' is not allowed to be uploaded to this server"
msgstr "Lextension de fichier '%(ext)s' nest pas autorisée pour être déposée sur ce serveur"
#: cps/editbooks.py:516 cps/editbooks.py:772
#: cps/editbooks.py:516 cps/editbooks.py:775
msgid "File to be uploaded must have an extension"
msgstr "Pour être déposé le fichier doit avoir une extension"
@ -354,7 +354,7 @@ msgstr "Impossible de créer le chemin %(path)s (Permission refusée)."
msgid "Failed to store file %(file)s."
msgstr "Échec de la sauvegarde du fichier %(file)s."
#: cps/editbooks.py:551 cps/editbooks.py:903
#: cps/editbooks.py:551 cps/editbooks.py:906
#, python-format
msgid "Database error: %(error)s."
msgstr "Erreur de la base de données: %(error)s."
@ -364,47 +364,47 @@ msgstr "Erreur de la base de données: %(error)s."
msgid "File format %(ext)s added to %(book)s"
msgstr "Le format de fichier %(ext)s a été ajouté à %(book)s"
#: cps/editbooks.py:672
#: cps/editbooks.py:675
msgid "Identifiers are not Case Sensitive, Overwriting Old Identifier"
msgstr ""
#: cps/editbooks.py:709
#: cps/editbooks.py:712
msgid "Metadata successfully updated"
msgstr "Les métadonnées ont bien été mises à jour"
#: cps/editbooks.py:718
#: cps/editbooks.py:721
msgid "Error editing book, please check logfile for details"
msgstr "Erreur dédition du livre, veuillez consulter le journal (log) pour plus de détails"
#: cps/editbooks.py:780
#: cps/editbooks.py:783
#, python-format
msgid "File %(filename)s could not saved to temp dir"
msgstr "Le fichier %(filename)s ne peut pas être sauvegardé dans le répertoire temporaire"
#: cps/editbooks.py:790
#: cps/editbooks.py:793
msgid "Uploaded book probably exists in the library, consider to change before upload new: "
msgstr "Le fichier téléchargé existe probablement dans la librairie, veuillez le modifier avant de le télécharger de nouveau: "
#: cps/editbooks.py:878
#: cps/editbooks.py:881
#, python-format
msgid "Failed to Move Cover File %(file)s: %(error)s"
msgstr "Impossible de déplacer le fichier de couverture %(file)s: %(error)s"
#: cps/editbooks.py:889
#: cps/editbooks.py:892
#, python-format
msgid "File %(file)s uploaded"
msgstr "Le fichier %(file)s a été téléchargé"
#: cps/editbooks.py:915
#: cps/editbooks.py:918
msgid "Source or destination format for conversion missing"
msgstr "Le format de conversion de la source ou de la destination est manquant"
#: cps/editbooks.py:923
#: cps/editbooks.py:926
#, python-format
msgid "Book successfully queued for converting to %(book_format)s"
msgstr "Le livre a été mis avec succès en file de traitement pour conversion vers %(book_format)s"
#: cps/editbooks.py:927
#: cps/editbooks.py:930
#, python-format
msgid "There was an error converting this book: %(res)s"
msgstr "Une erreur est survenue au cours de la conversion du livre : %(res)s"
@ -417,151 +417,151 @@ msgstr "La configuration de Google Drive nest pas terminée, essayez de désa
msgid "Callback domain is not verified, please follow steps to verify domain in google developer console"
msgstr "Le domaine de retour dappel (Callback domain) est non vérifié, veuillez suivre les étapes nécessaires pour vérifier le domaine dans la console de développement de Google"
#: cps/helper.py:79
#: cps/helper.py:82
#, python-format
msgid "%(format)s format not found for book id: %(book)d"
msgstr "le format %(format)s est introuvable pour le livre : %(book)d"
#: cps/helper.py:85 cps/tasks/convert.py:50
#: cps/helper.py:88 cps/tasks/convert.py:50
#, python-format
msgid "%(format)s not found on Google Drive: %(fn)s"
msgstr "le %(format)s est introuvable sur Google Drive : %(fn)s"
#: cps/helper.py:90
#: cps/helper.py:93
#, python-format
msgid "%(format)s not found: %(fn)s"
msgstr "%(format)s introuvable : %(fn)s"
#: cps/helper.py:95 cps/helper.py:228 cps/templates/detail.html:41
#: cps/helper.py:98 cps/helper.py:231 cps/templates/detail.html:41
#: cps/templates/detail.html:45
msgid "Send to Kindle"
msgstr "Envoyer vers Kindle"
#: cps/helper.py:96 cps/helper.py:112 cps/helper.py:230
#: cps/helper.py:99 cps/helper.py:115 cps/helper.py:233
msgid "This e-mail has been sent via Calibre-Web."
msgstr "Ce courriel a été envoyé depuis Calibre-Web."
#: cps/helper.py:110
#: cps/helper.py:113
msgid "Calibre-Web test e-mail"
msgstr "Courriel de test de Calibre-Web"
#: cps/helper.py:111
#: cps/helper.py:114
msgid "Test e-mail"
msgstr "Courriel de test"
#: cps/helper.py:128
#: cps/helper.py:131
msgid "Get Started with Calibre-Web"
msgstr "Bien démarrer avec Calibre-Web"
#: cps/helper.py:133
#: cps/helper.py:136
#, python-format
msgid "Registration e-mail for user: %(name)s"
msgstr "Courriel dinscription pour lutilisateur : %(name)s"
#: cps/helper.py:153 cps/helper.py:157 cps/helper.py:161 cps/helper.py:170
#: cps/helper.py:174 cps/helper.py:178
#: cps/helper.py:156 cps/helper.py:160 cps/helper.py:164 cps/helper.py:173
#: cps/helper.py:177 cps/helper.py:181
#, python-format
msgid "Send %(format)s to Kindle"
msgstr "Envoyer %(format)s vers le Kindle"
#: cps/helper.py:183 cps/helper.py:189
#: cps/helper.py:186 cps/helper.py:192
#, python-format
msgid "Convert %(orig)s to %(format)s and send to Kindle"
msgstr "Convertir de %(orig)s vers %(format)s et envoyer au Kindle"
#: cps/helper.py:230
#: cps/helper.py:233
#, python-format
msgid "E-mail: %(book)s"
msgstr "Courriel : %(book)s"
#: cps/helper.py:232
#: cps/helper.py:235
msgid "The requested file could not be read. Maybe wrong permissions?"
msgstr "Le fichier demandé na pu être lu. Problème de permission daccès ?"
#: cps/helper.py:329
#: cps/helper.py:332
#, python-format
msgid "Deleting bookfolder for book %(id)s failed, path has subfolders: %(path)s"
msgstr ""
#: cps/helper.py:335
#: cps/helper.py:338
#, python-format
msgid "Deleting book %(id)s failed: %(message)s"
msgstr "La suppression du livre %(id)s a échoué: %(message)s"
#: cps/helper.py:345
#: cps/helper.py:348
#, python-format
msgid "Deleting book %(id)s, book path not valid: %(path)s"
msgstr "Suppression du livre %(id)s, le chemin du livre est invalide : %(path)s"
#: cps/helper.py:400
#: cps/helper.py:403
#, python-format
msgid "Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s"
msgstr "Renommer le titre de : '%(src)s' à '%(dest)s' a échoué avec lerreur : %(error)s"
#: cps/helper.py:415
#: cps/helper.py:418
#, python-format
msgid "Rename file in path '%(src)s' to '%(dest)s' failed with error: %(error)s"
msgstr "La modification du nom de fichier du chemin : '%(src)s' vers '%(dest)s' a échoué avec lerreur : %(error)s"
#: cps/helper.py:440 cps/helper.py:450 cps/helper.py:458
#: cps/helper.py:443 cps/helper.py:453 cps/helper.py:461
#, python-format
msgid "File %(file)s not found on Google Drive"
msgstr "Le fichier %(file)s n'a pas été trouvé dans Google Drive"
#: cps/helper.py:479
#: cps/helper.py:482
#, python-format
msgid "Book path %(path)s not found on Google Drive"
msgstr "Le chemin du livre %(path)s n'a pas été trouvé dans Google Drive"
#: cps/helper.py:588
#: cps/helper.py:591
msgid "Error Downloading Cover"
msgstr "Erreur lors du téléchargement de la couverture"
#: cps/helper.py:591
#: cps/helper.py:594
msgid "Cover Format Error"
msgstr "Erreur de format de couverture"
#: cps/helper.py:606
#: cps/helper.py:609
msgid "Failed to create path for cover"
msgstr "Impossible de créer le chemin pour la couverture"
#: cps/helper.py:611
#: cps/helper.py:614
msgid "Cover-file is not a valid image file, or could not be stored"
msgstr "Le fichier couverture n'est pas un fichier image valide, ou ne peut pas être stocké"
#: cps/helper.py:622
#: cps/helper.py:625
msgid "Only jpg/jpeg/png/webp files are supported as coverfile"
msgstr "Seuls les fichiers jpg/jpeg/png/webp sont supportés comme fichier de couverture"
#: cps/helper.py:636
#: cps/helper.py:639
msgid "Only jpg/jpeg files are supported as coverfile"
msgstr "Seuls les fichiers jpg/jpeg sont supportés comme fichier de couverture"
#: cps/helper.py:684
#: cps/helper.py:687
msgid "Unrar binary file not found"
msgstr "Fichier binaire Unrar non trouvé"
#: cps/helper.py:698
#: cps/helper.py:701
msgid "Error excecuting UnRar"
msgstr "Une erreur est survenue lors de l'exécution d'UnRar"
#: cps/helper.py:747
#: cps/helper.py:750
msgid "Waiting"
msgstr "En attente"
#: cps/helper.py:749
#: cps/helper.py:752
msgid "Failed"
msgstr "Echoué"
#: cps/helper.py:751
#: cps/helper.py:754
msgid "Started"
msgstr "Débuté"
#: cps/helper.py:753
#: cps/helper.py:756
msgid "Finished"
msgstr "Terminé"
#: cps/helper.py:755
#: cps/helper.py:758
msgid "Unknown Status"
msgstr "Statut inconnu"
@ -857,7 +857,7 @@ msgstr "Livres archivés"
msgid "Show archived books"
msgstr "Afficher les livres archivés"
#: cps/ub.py:120
#: cps/ub.py:120 cps/web.py:1001
msgid "Books List"
msgstr ""
@ -986,10 +986,6 @@ msgstr "Recherche avancée"
msgid "Search"
msgstr "Chercher"
#: cps/web.py:1001
msgid "Books list"
msgstr ""
#: cps/web.py:1139
msgid "Ratings list"
msgstr "Liste des évaluations"
@ -1299,60 +1295,64 @@ msgstr "Éditer la configuration principale"
msgid "Edit UI Configuration"
msgstr "Configuration de linterface utilisateur"
#: cps/templates/admin.html:137
#: cps/templates/admin.html:136
msgid "Administration"
msgstr "Administration"
#: cps/templates/admin.html:137
msgid "Download Debug Package"
msgstr ""
#: cps/templates/admin.html:138
msgid "View Logs"
msgstr "Afficher les fichiers journaux"
#: cps/templates/admin.html:139
#: cps/templates/admin.html:141
msgid "Reconnect Calibre Database"
msgstr "Reconnecter la base de données Calibre"
#: cps/templates/admin.html:140
#: cps/templates/admin.html:142
msgid "Restart"
msgstr "Redémarrer Calibre-Web"
#: cps/templates/admin.html:141
#: cps/templates/admin.html:143
msgid "Shutdown"
msgstr "Arrêter Calibre-Web"
#: cps/templates/admin.html:147
#: cps/templates/admin.html:149
msgid "Update"
msgstr "Mise à jour de Calibre-Web"
#: cps/templates/admin.html:151
#: cps/templates/admin.html:153
msgid "Version"
msgstr "Version"
#: cps/templates/admin.html:152
#: cps/templates/admin.html:154
msgid "Details"
msgstr "Détails"
#: cps/templates/admin.html:158
#: cps/templates/admin.html:160
msgid "Current version"
msgstr "Version actuelle"
#: cps/templates/admin.html:164
#: cps/templates/admin.html:166
msgid "Check for Update"
msgstr "Rechercher les mises à jour"
#: cps/templates/admin.html:165
#: cps/templates/admin.html:167
msgid "Perform Update"
msgstr "Effectuer la mise à jour"
#: cps/templates/admin.html:177
#: cps/templates/admin.html:179
msgid "Are you sure you want to restart?"
msgstr "Voulez-vous vraiment redémarrer Calibre-Web?"
#: cps/templates/admin.html:182 cps/templates/admin.html:196
#: cps/templates/admin.html:216 cps/templates/shelf.html:80
#: cps/templates/admin.html:184 cps/templates/admin.html:198
#: cps/templates/admin.html:218 cps/templates/shelf.html:80
msgid "OK"
msgstr "OK"
#: cps/templates/admin.html:183 cps/templates/admin.html:197
#: cps/templates/admin.html:185 cps/templates/admin.html:199
#: cps/templates/book_edit.html:192 cps/templates/book_table.html:84
#: cps/templates/config_edit.html:391 cps/templates/config_view_edit.html:151
#: cps/templates/email_edit.html:47 cps/templates/email_edit.html:101
@ -1361,11 +1361,11 @@ msgstr "OK"
msgid "Cancel"
msgstr "Annuler"
#: cps/templates/admin.html:195
#: cps/templates/admin.html:197
msgid "Are you sure you want to shutdown?"
msgstr "Voulez-vous vraiment arrêter Calibre-Web?"
#: cps/templates/admin.html:207
#: cps/templates/admin.html:209
msgid "Updating, please do not reload this page"
msgstr "Mise à jour en cours, ne pas rafraîchir la page"
@ -2304,6 +2304,14 @@ msgstr "Le flux de sortie ne peut pas être affiché"
msgid "Show Access Log: "
msgstr "Afficher le journal d'accès : "
#: cps/templates/logviewer.html:18
msgid "Download Calibre-Web Log"
msgstr ""
#: cps/templates/logviewer.html:21
msgid "Download Access Log"
msgstr ""
#: cps/templates/modal_dialogs.html:6
msgid "Select Allowed/Denied Tags"
msgstr "Sélectionner les étiquettes autorisées/refusées"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -323,31 +323,34 @@ def import_ldap_users():
showtext['text'] = _(u'Error: No user returned in response of LDAP server')
return json.dumps(showtext)
imported = 0
for username in new_users:
user = username.decode('utf-8')
if '=' in user:
match = re.search("([a-zA-Z0-9-]+)=%s", config.config_ldap_user_object, re.IGNORECASE | re.UNICODE)
if match:
match_filter = match.group(1)
match = re.search(match_filter + "=([\d\s\w-]+)", user, re.IGNORECASE | re.UNICODE)
if match:
user = match.group(1)
# if member object field is empty take user object as filter
try:
if config.config_ldap_member_user_object:
user_identifier = extract_user_identifier(user, config.config_ldap_member_user_object)
else:
log.warning("Could Not Parse LDAP User: %s", user)
user_identifier = extract_user_identifier(user, config.config_ldap_user_object)
except Exception as e:
log.warning(e)
continue
else:
log.warning("Could Not Parse LDAP User: %s", user)
user_identifier = user
if ub.session.query(ub.User).filter(ub.User.nickname == user_identifier.lower()).first():
log.warning("LDAP User: %s Already in Database", user_identifier)
continue
if ub.session.query(ub.User).filter(ub.User.nickname == user.lower()).first():
log.warning("LDAP User: %s Already in Database", user)
continue
user_data = services.ldap.get_object_details(user=user,
user_data = services.ldap.get_object_details(user=user_identifier,
group=None,
query_filter=None,
dn_only=False)
if user_data:
content = ub.User()
content.nickname = user
# user_login_field = extract_dynamic_field_from_filter(user, config.config_ldap_user_object)
content.nickname = user_identifier # user_data[user_login_field][0].decode('utf-8')
content.password = '' # dummy password which will be replaced by ldap one
if 'mail' in user_data:
content.email = user_data['mail'][0].decode('utf-8')
@ -365,6 +368,7 @@ def import_ldap_users():
ub.session.add(content)
try:
ub.session.commit()
imported +=1
except Exception as e:
log.warning("Failed to create LDAP user: %s - %s", user, e)
ub.session.rollback()
@ -373,10 +377,28 @@ def import_ldap_users():
log.warning("LDAP User: %s Not Found", user)
showtext['text'] = _(u'At Least One LDAP User Not Found in Database')
if not showtext:
showtext['text'] = _(u'User Successfully Imported')
showtext['text'] = _(u'{} User Successfully Imported'.format(imported))
return json.dumps(showtext)
def extract_user_data_from_field(user, field):
match = re.search(field + "=([\d\s\w-]+)", user, re.IGNORECASE | re.UNICODE)
if match:
return match.group(1)
else:
raise Exception("Could Not Parse LDAP User: %s", user)
# CN=Firstname LastName,OU=Laba,OU=...,DC=...,DC=...
# CN=user displayname,OU=ouname1,OU=ouname2,OU=ouname3,DC=domain,DC=domain
def extract_user_identifier(user, filter):
match = re.search("([a-zA-Z0-9-]+)=%s", filter, re.IGNORECASE | re.UNICODE)
if match:
dynamic_field = match.group(1)
else:
raise Exception("Could Not Parse LDAP User: %s", user)
return extract_user_data_from_field(user, dynamic_field)
# ################################### data provider functions #########################################################
@ -631,6 +653,10 @@ def render_books_list(data, sort, book_id, page):
order = [db.Books.author_sort.asc()]
if sort == 'authza':
order = [db.Books.author_sort.desc()]
if sort == 'seriesasc':
order = [db.Books.series_index.asc()]
if sort == 'seriesdesc':
order = [db.Books.series_index.desc()]
if data == "rated":
if current_user.check_visibility(constants.SIDEBAR_BEST_RATED):
@ -813,7 +839,7 @@ def render_ratings_books(page, book_id, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.ratings.any(db.Ratings.id == book_id),
[db.Books.timestamp.desc(), order[0]])
[order[0]])
if name and name.rating <= 10:
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
title=_(u"Rating: %(rating)s stars", rating=int(name.rating / 2)), page="ratings")
@ -827,7 +853,7 @@ def render_formats_books(page, book_id, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.data.any(db.Data.format == book_id.upper()),
[db.Books.timestamp.desc(), order[0]])
[order[0]])
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
title=_(u"File format: %(format)s", format=name.format), page="formats")
else:
@ -860,7 +886,7 @@ def render_language_books(page, name, order):
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.languages.any(db.Languages.lang_code == name),
[db.Books.timestamp.desc(), order[0]])
[order[0]])
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name,
title=_(u"Language: %(name)s", name=lang_name), page="language")
@ -998,7 +1024,7 @@ def books_list(data, sort_param, book_id, page):
@login_required
def books_table():
visibility = current_user.view_settings.get('table', {})
return render_title_template('book_table.html', title=_(u"Books list"), page="book_table",
return render_title_template('book_table.html', title=_(u"Books List"), page="book_table",
visiblility=visibility)
@web.route("/ajax/listbooks")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff