mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-28 12:30:00 +00:00
UI Improvements
Added additional restrictions to Calibre DB interface
This commit is contained in:
parent
fcefd8031a
commit
0adcd1b3d9
@ -772,8 +772,7 @@ def new_user():
|
||||
@admin_required
|
||||
def edit_mailsettings():
|
||||
content = config.get_mail_settings()
|
||||
# log.debug("edit_mailsettings %r", content)
|
||||
return render_title_template("email_edit.html", content=content, title=_(u"Edit e-mail server settings"),
|
||||
return render_title_template("email_edit.html", content=content, title=_(u"Edit E-mail Server Settings"),
|
||||
page="mailset")
|
||||
|
||||
|
||||
|
@ -44,6 +44,7 @@ class _Settings(_Base):
|
||||
mail_login = Column(String, default='mail@example.com')
|
||||
mail_password = Column(String, default='mypassword')
|
||||
mail_from = Column(String, default='automailer <mail@example.com>')
|
||||
mail_size = Column(Integer, default=25)
|
||||
|
||||
config_calibre_dir = Column(String)
|
||||
config_port = Column(Integer, default=constants.DEFAULT_PORT)
|
||||
|
66
cps/db.py
66
cps/db.py
@ -22,10 +22,11 @@ import sys
|
||||
import os
|
||||
import re
|
||||
import ast
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import Table, Column, ForeignKey
|
||||
from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float, DateTime
|
||||
from sqlalchemy import Table, Column, ForeignKey, CheckConstraint
|
||||
from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float, DateTime, REAL
|
||||
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
@ -72,9 +73,9 @@ class Identifiers(Base):
|
||||
__tablename__ = 'identifiers'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
type = Column(String)
|
||||
val = Column(String)
|
||||
book = Column(Integer, ForeignKey('books.id'))
|
||||
type = Column(String(collation='NOCASE'), nullable=False, default="isbn")
|
||||
val = Column(String(collation='NOCASE'), nullable=False)
|
||||
book = Column(Integer, ForeignKey('books.id'), nullable=False)
|
||||
|
||||
def __init__(self, val, id_type, book):
|
||||
self.val = val
|
||||
@ -126,8 +127,8 @@ class Comments(Base):
|
||||
__tablename__ = 'comments'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
text = Column(String)
|
||||
book = Column(Integer, ForeignKey('books.id'))
|
||||
text = Column(String(collation='NOCASE'), nullable=False)
|
||||
book = Column(Integer, ForeignKey('books.id'), nullable=False)
|
||||
|
||||
def __init__(self, text, book):
|
||||
self.text = text
|
||||
@ -141,7 +142,7 @@ class Tags(Base):
|
||||
__tablename__ = 'tags'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String)
|
||||
name = Column(String(collation='NOCASE'), unique=True, nullable=False)
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
@ -154,9 +155,9 @@ class Authors(Base):
|
||||
__tablename__ = 'authors'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
sort = Column(String)
|
||||
link = Column(String)
|
||||
name = Column(String(collation='NOCASE'), unique=True, nullable=False)
|
||||
sort = Column(String(collation='NOCASE'))
|
||||
link = Column(String, nullable=False, default="")
|
||||
|
||||
def __init__(self, name, sort, link):
|
||||
self.name = name
|
||||
@ -171,8 +172,8 @@ class Series(Base):
|
||||
__tablename__ = 'series'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
sort = Column(String)
|
||||
name = Column(String(collation='NOCASE'), unique=True, nullable=False)
|
||||
sort = Column(String(collation='NOCASE'))
|
||||
|
||||
def __init__(self, name, sort):
|
||||
self.name = name
|
||||
@ -186,7 +187,7 @@ class Ratings(Base):
|
||||
__tablename__ = 'ratings'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
rating = Column(Integer)
|
||||
rating = Column(Integer, CheckConstraint('rating>-1 AND rating<11'), unique=True)
|
||||
|
||||
def __init__(self, rating):
|
||||
self.rating = rating
|
||||
@ -199,7 +200,7 @@ class Languages(Base):
|
||||
__tablename__ = 'languages'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
lang_code = Column(String)
|
||||
lang_code = Column(String(collation='NOCASE'), nullable=False, unique=True)
|
||||
|
||||
def __init__(self, lang_code):
|
||||
self.lang_code = lang_code
|
||||
@ -212,8 +213,8 @@ class Publishers(Base):
|
||||
__tablename__ = 'publishers'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
sort = Column(String)
|
||||
name = Column(String(collation='NOCASE'), nullable=False, unique=True)
|
||||
sort = Column(String(collation='NOCASE'))
|
||||
|
||||
def __init__(self, name, sort):
|
||||
self.name = name
|
||||
@ -227,10 +228,10 @@ class Data(Base):
|
||||
__tablename__ = 'data'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
book = Column(Integer, ForeignKey('books.id'))
|
||||
format = Column(String)
|
||||
uncompressed_size = Column(Integer)
|
||||
name = Column(String)
|
||||
book = Column(Integer, ForeignKey('books.id'), nullable=False)
|
||||
format = Column(String(collation='NOCASE'), nullable=False)
|
||||
uncompressed_size = Column(Integer, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
|
||||
def __init__(self, book, book_format, uncompressed_size, name):
|
||||
self.book = book
|
||||
@ -247,17 +248,20 @@ class Books(Base):
|
||||
|
||||
DEFAULT_PUBDATE = "0101-01-01 00:00:00+00:00"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
title = Column(String)
|
||||
sort = Column(String)
|
||||
author_sort = Column(String)
|
||||
timestamp = Column(TIMESTAMP)
|
||||
pubdate = Column(String)
|
||||
series_index = Column(String)
|
||||
last_modified = Column(TIMESTAMP)
|
||||
path = Column(String)
|
||||
has_cover = Column(Integer)
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(collation='NOCASE'), nullable=False, default='Unknown')
|
||||
sort = Column(String(collation='NOCASE'))
|
||||
author_sort = Column(String(collation='NOCASE'))
|
||||
timestamp = Column(TIMESTAMP, default=datetime.utcnow)
|
||||
pubdate = Column(TIMESTAMP, default=datetime.utcnow)
|
||||
series_index = Column(REAL, nullable=False, default=1.0)
|
||||
last_modified = Column(TIMESTAMP, default=datetime.utcnow)
|
||||
path = Column(String, default="", nullable=False)
|
||||
has_cover = Column(Integer, default=0)
|
||||
uuid = Column(String)
|
||||
isbn = Column(String(collation='NOCASE'), default="")
|
||||
# Iccn = Column(String(collation='NOCASE'), default="")
|
||||
flags = Column(Integer, nullable=False, default=1)
|
||||
|
||||
authors = relationship('Authors', secondary=books_authors_link, backref='books')
|
||||
tags = relationship('Tags', secondary=books_tags_link, backref='books',order_by="Tags.name")
|
||||
|
@ -30,6 +30,7 @@ from uuid import uuid4
|
||||
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
|
||||
from flask_babel import gettext as _
|
||||
from flask_login import current_user, login_required
|
||||
from sqlalchemy import func
|
||||
|
||||
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
|
||||
from . import config, get_locale, db, ub, worker
|
||||
@ -680,10 +681,6 @@ def upload():
|
||||
tags = meta.tags
|
||||
series = meta.series
|
||||
series_index = meta.series_id
|
||||
title_dir = helper.get_valid_filename(title)
|
||||
author_dir = helper.get_valid_filename(authr)
|
||||
filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir)
|
||||
saved_filename = os.path.join(filepath, title_dir + meta.extension.lower())
|
||||
|
||||
if title != _(u'Unknown') and authr != _(u'Unknown'):
|
||||
entry = helper.check_exists_book(authr, title)
|
||||
@ -692,6 +689,11 @@ def upload():
|
||||
flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ")
|
||||
+ Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning")
|
||||
|
||||
title_dir = helper.get_valid_filename(title)
|
||||
author_dir = helper.get_valid_filename(authr)
|
||||
filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir)
|
||||
saved_filename = os.path.join(filepath, title_dir + meta.extension.lower())
|
||||
|
||||
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
|
||||
if not os.path.exists(filepath):
|
||||
try:
|
||||
@ -721,7 +723,7 @@ def upload():
|
||||
has_cover = 1
|
||||
|
||||
# handle authors
|
||||
is_author = db.session.query(db.Authors).filter(db.Authors.name == authr).first()
|
||||
is_author = db.session.query(db.Authors).filter(db.Authors.name == func.binary(authr)).first()
|
||||
if is_author:
|
||||
db_author = is_author
|
||||
else:
|
||||
|
@ -14,10 +14,13 @@
|
||||
<th>{{_('Send to Kindle E-mail Address')}}</th>
|
||||
<th>{{_('Downloads')}}</th>
|
||||
<th class="hidden-xs ">{{_('Admin')}}</th>
|
||||
<th class="hidden-xs">{{_('Download')}}</th>
|
||||
<th class="hidden-xs">{{_('View Books')}}</th>
|
||||
<th class="hidden-xs">{{_('Upload')}}</th>
|
||||
<th class="hidden-xs">{{_('Edit')}}</th>
|
||||
<th class="hidden-xs hidden-sm">{{_('Password')}}</th>
|
||||
<th class="hidden-xs hidden-sm">{{_('Upload')}}</th>
|
||||
<th class="hidden-xs hidden-sm">{{_('Download')}}</th>
|
||||
<th class="hidden-xs hidden-sm hidden-md">{{_('View Books')}}</th>
|
||||
<th class="hidden-xs hidden-sm hidden-md">{{_('Edit')}}</th>
|
||||
<th class="hidden-xs hidden-sm hidden-md">{{_('Delete')}}</th>
|
||||
<th class="hidden-xs hidden-sm hidden-md">{{_('Public Shelf')}}</th>
|
||||
</tr>
|
||||
{% for user in allUser %}
|
||||
{% if not user.role_anonymous() or config.config_anonbrowse %}
|
||||
@ -27,10 +30,13 @@
|
||||
<td>{{user.kindle_mail}}</td>
|
||||
<td>{{user.downloads.count()}}</td>
|
||||
<td class="hidden-xs">{{ display_bool_setting(user.role_admin()) }}</td>
|
||||
<td class="hidden-xs">{{ display_bool_setting(user.role_download()) }}</td>
|
||||
<td class="hidden-xs">{{ display_bool_setting(user.role_viewer()) }}</td>
|
||||
<td class="hidden-xs">{{ display_bool_setting(user.role_upload()) }}</td>
|
||||
<td class="hidden-xs">{{ display_bool_setting(user.role_edit()) }}</td>
|
||||
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_passwd()) }}</td>
|
||||
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_upload()) }}</td>
|
||||
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_download()) }}</td>
|
||||
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_viewer()) }}</td>
|
||||
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_edit()) }}</td>
|
||||
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_delete_books()) }}</td>
|
||||
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_edit_shelfs()) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="discover">
|
||||
<h2>{{title}}</h2>
|
||||
<form role="form" method="POST" autocomplete="off">
|
||||
<div class="panel-group">
|
||||
<div class="panel-group col-md-10 col-lg-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
@ -15,9 +15,12 @@
|
||||
</div>
|
||||
<div id="collapseOne" class="panel-collapse collapse in">
|
||||
<div class="panel-body">
|
||||
<div class="form-group required">
|
||||
<label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label>
|
||||
<input type="text" class="form-control" name="config_calibre_dir" id="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
|
||||
<div class="form-group required input-group">
|
||||
<label for="config_calibre_dir" class="sr-only">{{_('Location of Calibre Database')}}</label>
|
||||
<input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
{% if feature_support['gdrive'] %}
|
||||
<div class="form-group required">
|
||||
@ -87,21 +90,25 @@
|
||||
<label for="config_port">{{_('Server Port')}}</label>
|
||||
<input type="number" min="1" max="65535" class="form-control" name="config_port" id="config_port" value="{% if config.config_port != None %}{{ config.config_port }}{% endif %}" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_certfile">{{_('SSL certfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||
<input type="text" class="form-control" name="config_certfile" id="config_certfile" value="{% if config.config_certfile != None %}{{ config.config_certfile }}{% endif %}" autocomplete="off">
|
||||
<div class="form-group input-group">
|
||||
<label for="config_certfile" class="sr-only">{{_('SSL certfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||
<input type="text" class="form-control" id="config_certfile" name="config_certfile" value="{% if config.config_certfile != None %}{{ config.config_certfile }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="certfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_keyfile">{{_('SSL Keyfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||
<input type="text" class="form-control" name="config_keyfile" id="config_keyfile" value="{% if config.config_keyfile != None %}{{ config.config_keyfile }}{% endif %}" autocomplete="off">
|
||||
<div class="form-group input-group">
|
||||
<label for="config_calibre_dir" class="sr-only">{{_('SSL Keyfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||
<input type="text" class="form-control" id="config_keyfile" name="config_keyfile" value="{% if config.config_keyfile != None %}{{ config.config_keyfile }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="keyfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_updatechannel">{{_('Update Channel')}}</label>
|
||||
<select name="config_updatechannel" id="config_updatechannel" class="form-control">
|
||||
<option value="0" {% if config.config_updatechannel == 0 %}selected{% endif %}>{{_('Stable')}}</option>
|
||||
<!--option value="1" {% if config.config_updatechannel == 1 %}selected{% endif %}>{{_('Stable (Automatic)')}}</option-->
|
||||
<option value="2" {% if config.config_updatechannel == 2 %}selected{% endif %}>{{_('Nightly')}}</option>
|
||||
<!--option-- value="3" {% if config.config_updatechannel == 3 %}selected{% endif %}>{{_('Nightly (Automatic)')}}</option-->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -342,18 +349,27 @@
|
||||
<label for="config_calibre">{{_('Calibre E-Book Converter Settings')}}</label>
|
||||
<input type="text" class="form-control" id="config_calibre" name="config_calibre" value="{% if config.config_calibre != None %}{{ config.config_calibre }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label>
|
||||
<div class="form-group input-group">
|
||||
<label for="config_converterpath" class="sr-only">{{_('Path to Calibre E-Book Converter')}}</label>
|
||||
<input type="text" class="form-control" id="config_converterpath" name="config_converterpath" value="{% if config.config_converterpath != None %}{{ config.config_converterpath }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="converter_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_calibre">{{_('Path to Kepubify E-Book Converter')}}</label>
|
||||
<input type="text" class="form-control" id="config_kepubifypath" name="config_converterpath" value="{% if config.config_kepubifypath != None %}{{ config.config_kepubifypath }}{% endif %}" autocomplete="off">
|
||||
<div class="form-group input-group">
|
||||
<label for="config_kepubifypath" class="sr-only">{{_('Path to Kepubify E-Book Converter')}}</label>
|
||||
<input type="text" class="form-control" id="config_kepubifypath" name="config_kepubifypath" value="{% if config.config_kepubifypath != None %}{{ config.config_kepubifypath }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="kepubify_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
{% if feature_support['rar'] %}
|
||||
<div class="form-group">
|
||||
<label for="config_rarfile_location">{{_('Location of Unrar binary')}}</label>
|
||||
<input type="text" class="form-control" name="config_rarfile_location" id="config_rarfile_location" value="{% if config.config_rarfile_location != None %}{{ config.config_rarfile_location }}{% endif %}" autocomplete="off">
|
||||
<div class="form-group input-group">
|
||||
<label for="config_rarfile_location" class="sr-only">{{_('Location of Unrar binary')}}</label>
|
||||
<input type="text" class="form-control" id="config_rarfile_location" name="config_rarfile_location" value="{% if config.config_rarfile_location != None %}{{ config.config_rarfile_location }}{% endif %}" autocomplete="off">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="unrar_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="discover">
|
||||
<h2>{{title}}</h2>
|
||||
<form role="form" method="POST" autocomplete="off">
|
||||
<div class="panel-group">
|
||||
<div class="panel-group col-md-10 col-lg-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
|
@ -6,14 +6,14 @@
|
||||
{% block body %}
|
||||
<div class="discover">
|
||||
<h1>{{title}}</h1>
|
||||
<form role="form" method="POST">
|
||||
<form role="form" class="col-md-10 col-lg-6" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="mail_server">{{_('SMTP Hostname')}}</label>
|
||||
<input type="text" class="form-control" name="mail_server" id="mail_server" value="{{content.mail_server}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mail_port">{{_('SMTP Port')}}</label>
|
||||
<input type="text" class="form-control" name="mail_port" id="mail_port" value="{{content.mail_port}}">
|
||||
<input type="number" min="1" max="65535" class="form-control" name="mail_port" id="mail_port" value="{% if config.mail_port != None %}{{ config.mail_port }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mail_use_ssl">{{_('Encryption')}}</label>
|
||||
@ -34,6 +34,13 @@
|
||||
<div class="form-group">
|
||||
<label for="mail_from">{{_('From E-mail')}}</label>
|
||||
<input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}">
|
||||
</div>
|
||||
<div class="form-group input-group">
|
||||
<label for="mail_size" class="sr-only">{{_('Attachment Size Limit')}}</label>
|
||||
<input type="number" min="1" max="600" class="form-control" name="attachment_size" id="mail_size" value="{% if config.mail_size != None %}{{ config.mail_size }}{% endif %}">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" id="certfile_path" class="btn btn-default" disabled>MB</button>
|
||||
</span>
|
||||
</div>
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
|
||||
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test E-mail')}}</button>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<div class="col-sm-8">
|
||||
<div class="col-md-10 col-lg-6">
|
||||
<form role="form" id="search" action="{{ url_for('web.advanced_search') }}" method="GET">
|
||||
<div class="form-group">
|
||||
<label for="book_title">{{_('Book Title')}}</label>
|
||||
|
@ -3,6 +3,7 @@
|
||||
<div class="discover">
|
||||
<h1>{{title}}</h1>
|
||||
<form role="form" method="POST" autocomplete="off">
|
||||
<div class="col-md-10 col-lg-8">
|
||||
{% if new_user or ( g.user and content.nickname != "Guest" and g.user.role_admin() ) %}
|
||||
<div class="form-group required">
|
||||
<label for="nickname">{{_('Username')}}</label>
|
||||
@ -65,6 +66,7 @@
|
||||
<div class="btn btn-danger" id="config_delete_kobo_token" data-toggle="modal" data-target="#modalDeleteToken" data-remote="false" {% if not content.remote_auth_token.first() %} style="display: none;" {% endif %}>{{_('Delete')}}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
{% for element in sidebar %}
|
||||
{% if element['config_show'] %}
|
||||
|
Loading…
Reference in New Issue
Block a user