Merge pull request #70 from th3r00t/admin

V 0.7.0 Update.
This commit is contained in:
th3r00t
2021-08-14 21:58:53 +00:00
committed by GitHub
15 changed files with 135 additions and 126 deletions

18
.gitattributes vendored
View File

@@ -1,14 +1,4 @@
docs/* linguist-documentation * linguist-vendored
src/backend/* *.py linguist-vendored=false
src/interace/static/css/* linguist-vendored *.js linguist-vendored=false
src/interface/admin.py *.html linguist-vendored=false
src/interface/apps.py
src/interface/forms.py
src/interface/models.py
src/interface/tests.py
src/interfaec/views.py
src/interface/templates/*
src/interface/templatetags/*
src/interace/static/js/pyshelf_ux.js
src/frontend/urs.py
src/frontend/settings.py

53
README.md vendored
View File

@@ -1,4 +1,4 @@
# pyShelf 0.6.1 # pyShelf 0.7.0
<p align="center"><b>Terminal based ebook server. Open source & Lightweight.</b></p> <p align="center"><b>Terminal based ebook server. Open source & Lightweight.</b></p>
<p align="center">Having used Calibre for hosting my eBook collection in the past, I found myself frustrated having to install X on my server, or manage my library externally, Thus I have decided to spin up my own.</p> <p align="center">Having used Calibre for hosting my eBook collection in the past, I found myself frustrated having to install X on my server, or manage my library externally, Thus I have decided to spin up my own.</p>
@@ -14,6 +14,12 @@ Follow or influence development @ <p align="center"><b>
<a href="https://discord.gg/H9TbNJS">Discord</a> <a href="https://discord.gg/H9TbNJS">Discord</a>
</b></p> </b></p>
## 0.7.0 Patch Notes.
# New Features
* Administration System
* PDF Support
## Current Features ## Current Features
@@ -43,49 +49,6 @@ Follow or influence development @ <p align="center"><b>
* mobi * mobi
* pdf * pdf
## 0.6.1 Patch Notes.
# New Features
* PDF Format
* Image & Description acquisition needs work.
## 0.6.0 Patch Notes.
# New Features
* Automated Collections
* A work in progress, the collections are based on your folder structure.
* User System
* Per User Favorites
* Expanded book information view
* Websocket server
* currently only responds to ping, and importBooks, more responders are planned.
* Full <b>Docker</b> integration.
* On Demand Importing
* .mobi Support
* Result set ordering
* You can now choose to order your results:
* Title
* Author
* Categories
* & Tags
* Reworked UI/UX
* More intuitive, less intrusive, & stays out of the way. <i>caveat: I need to rework the placement of the next & previous page controls. While they do remain usable, I intend to have them follow the users</i>
position on the page in future releases.
![pyShelf 0.6.0 navbar](https://github.com/th3r00t/pyShelf/raw/master/src/interface/static/img/navbar.png)
* New controls
* Sort
* Ascending / Descending result set
* Display of the result set count, and your current position in the set.
* A pop over layer to hold things like
* [x] User login & Registration
* [x] Control panel
* [x] Book details
## Installation & Support Information ## Installation & Support Information
# Installation # Installation
@@ -133,7 +96,7 @@ The first step is to login, after logging in the button whill show your username
- [x] Automated Collections - [x] Automated Collections
- [ ] Manual Collections - [ ] Manual Collections
- [ ] Books Removal - [x] Books Removal
- [ ] Access Restrictions - [ ] Access Restrictions
- [ ] Metadata Manipulation - [ ] Metadata Manipulation
- [ ] Others? - [ ] Others?

2
config.json vendored
View File

@@ -1 +1 @@
{"TITLE": "pyShelf E-Book Server", "VERSION": "0.6.0", "BOOKPATH": "", "DB_HOST": "localhost", "DB_PORT": "5432", "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", "BOOKSHELF": "data/shelf.json", "ALLOWED_HOSTS": "*", "SECRET": "", "BUILD_MODE": "production"} {"TITLE": "pyShelf E-Book Server", "VERSION": "0.7.0", "BOOKPATH": "", "DB_HOST": "localhost", "DB_PORT": "5432", "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", "BOOKSHELF": "data/shelf.json", "ALLOWED_HOSTS": "*", "SECRET": "", "BUILD_MODE": "production"}

2
doxygen.conf vendored
View File

@@ -38,7 +38,7 @@ PROJECT_NAME = "pyShelf Open Source Ebook Server"
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = 0.6.0 PROJECT_NUMBER = 0.7.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a

View File

@@ -22,9 +22,10 @@ from django.urls import include, path, re_path
from django.conf.urls.static import static from django.conf.urls.static import static
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
from interface import views from interface import views
from interface.admin import admin_site
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls), path("admin/", admin_site.urls),
path("", views.index, name="index"), path("", views.index, name="index"),
path("home", views.home, name="home"), path("home", views.home, name="home"),
re_path("^live$", views.live, name="live"), re_path("^live$", views.live, name="live"),

View File

@@ -1,4 +1,5 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import AdminSite
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from .models import Books, Collections, Favorites, Navigation, User from .models import Books, Collections, Favorites, Navigation, User
@@ -22,8 +23,27 @@ class CustomUserAdmin(UserAdmin):
) )
admin.site.register(Books) class pyShelfAdminSite(AdminSite):
admin.site.register(Collections) site_title = 'pyShelf admin'
admin.site.register(Favorites) site_header = 'pyShelf Administration'
admin.site.register(Navigation) index_title = 'Library'
admin.site.register(User, CustomUserAdmin)
class BookModelSearch(admin.ModelAdmin):
search_fields=('title','author','tags')
class CollectionModelSearch(admin.ModelAdmin):
search_fields=('collection',)
class FavoritesModelSearch(admin.ModelAdmin):
search_fields=('user_id',)
admin_site = pyShelfAdminSite(name='pyadmin')
admin_site.register(Books, BookModelSearch)
admin_site.register(Collections, CollectionModelSearch)
admin_site.register(Favorites, FavoritesModelSearch)
admin_site.register(Navigation)
admin_site.register(User, CustomUserAdmin)

View File

@@ -21,6 +21,7 @@ class Books(models.Model):
class Meta: class Meta:
db_table = "books" db_table = "books"
verbose_name_plural = 'Books'
def __str__(self): def __str__(self):
return self.title return self.title
@@ -59,6 +60,7 @@ class Books(models.Model):
class Collections(models.Model): class Collections(models.Model):
class Meta: class Meta:
db_table = "collections" db_table = "collections"
verbose_name_plural = 'Collections'
def __str__(self): def __str__(self):
return self.collection.__str__() return self.collection.__str__()
@@ -120,6 +122,7 @@ class Navigation(models.Model):
class Meta: class Meta:
db_table = "navigation" db_table = "navigation"
verbose_name_plural = "Navigation"
def __str__(self): def __str__(self):
return self.title return self.title
@@ -162,6 +165,7 @@ class Favorites(models.Model):
class Meta: class Meta:
db_table = "favorites" db_table = "favorites"
verbose_name_plural = "Favorites"
def __str__(self): def __str__(self):
return str(self.book) return str(self.book)

View File

@@ -13,19 +13,19 @@ body {
padding: 0; padding: 0;
font-size: 14px; font-size: 14px;
font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
color: #333; color: #fff;
background: #fff; background: #282828;
} }
/* LINKS */ /* LINKS */
a:link, a:visited { a:link, a:visited {
color: #447e9b; color: #fff;
text-decoration: none; text-decoration: none;
} }
a:focus, a:hover { a:focus, a:hover {
color: #036; color: #909090;
} }
a:focus { a:focus {
@@ -64,7 +64,7 @@ h1 {
margin: 0 0 20px; margin: 0 0 20px;
font-weight: 300; font-weight: 300;
font-size: 20px; font-size: 20px;
color: #666; color: #3f3;
} }
h2 { h2 {
@@ -234,10 +234,10 @@ th {
thead th, thead th,
tfoot td { tfoot td {
color: #666; color: #000;
padding: 5px 10px; padding: 5px 10px;
font-size: 11px; font-size: 11px;
background: #fff; background: #676767;
border: none; border: none;
border-top: 1px solid #eee; border-top: 1px solid #eee;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
@@ -253,18 +253,18 @@ thead th.required {
} }
tr.alt { tr.alt {
background: #f6f6f6; background: #797979;
} }
tr:nth-child(odd), .row-form-errors { tr:nth-child(odd), .row-form-errors {
background: #fff; background: #676767;
} }
tr:nth-child(even), tr:nth-child(even),
tr:nth-child(even) .errorlist, tr:nth-child(even) .errorlist,
tr:nth-child(odd) + .row-form-errors, tr:nth-child(odd) + .row-form-errors,
tr:nth-child(odd) + .row-form-errors .errorlist { tr:nth-child(odd) + .row-form-errors .errorlist {
background: #f9f9f9; background: #797979;
} }
/* SORTABLE TABLES */ /* SORTABLE TABLES */
@@ -273,15 +273,15 @@ thead th {
padding: 5px 10px; padding: 5px 10px;
line-height: normal; line-height: normal;
text-transform: uppercase; text-transform: uppercase;
background: #f6f6f6; background: #797979;
} }
thead th a:link, thead th a:visited { thead th a:link, thead th a:visited {
color: #666; color: #000;
} }
thead th.sorted { thead th.sorted {
background: #eee; background: #797979;
} }
thead th.sorted .text { thead th.sorted .text {
@@ -300,7 +300,7 @@ table thead th .text a {
} }
table thead th .text a:focus, table thead th .text a:hover { table thead th .text a:focus, table thead th .text a:hover {
background: #eee; background: #797979;
} }
thead th.sorted a.sortremove { thead th.sorted a.sortremove {
@@ -327,7 +327,7 @@ table thead th.sorted .sortpriority {
margin-right: 2px; margin-right: 2px;
} }
table thead th.sorted .sortoptions a { table thead th.sort79797e.sortoptions a {
position: relative; position: relative;
width: 14px; width: 14px;
height: 14px; height: 14px;
@@ -424,7 +424,7 @@ select[multiple] {
/* FORM BUTTONS */ /* FORM BUTTONS */
.button, input[type=submit], input[type=button], .submit-row input, a.button { .button, input[type=submit], input[type=button], .submit-row input, a.button {
background: #79aec8; background: #888;
padding: 10px 15px; padding: 10px 15px;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
@@ -471,7 +471,7 @@ input[type=button][disabled].default {
.module { .module {
border: none; border: none;
margin-bottom: 30px; margin-bottom: 30px;
background: #fff; background: #363636;
} }
.module p, .module ul, .module h3, .module h4, .module dl, .module pre { .module p, .module ul, .module h3, .module h4, .module dl, .module pre {
@@ -497,7 +497,7 @@ input[type=button][disabled].default {
font-weight: 400; font-weight: 400;
font-size: 13px; font-size: 13px;
text-align: left; text-align: left;
background: #79aec8; background: #888;
color: #fff; color: #fff;
} }
@@ -608,7 +608,7 @@ td ul.errorlist + input, td ul.errorlist + select, td ul.errorlist + textarea {
/* BREADCRUMBS */ /* BREADCRUMBS */
div.breadcrumbs { div.breadcrumbs {
background: #79aec8; background: #888;
padding: 10px 40px; padding: 10px 40px;
border: none; border: none;
font-size: 14px; font-size: 14px;
@@ -812,10 +812,12 @@ table#change-history tbody th {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 40px; padding: 10px 10px;
background: #417690; background: #363636;
color: #ffc; color: #3f3;
overflow: hidden; overflow: hidden;
border-bottom: #3f3 1px solid;
margin-bottom: 5px;
} }
#header a:link, #header a:visited { #header a:link, #header a:visited {
@@ -835,11 +837,11 @@ table#change-history tbody th {
margin: 0 20px 0 0; margin: 0 20px 0 0;
font-weight: 300; font-weight: 300;
font-size: 24px; font-size: 24px;
color: #f5dd5d; color: #3f3;
} }
#branding h1, #branding h1 a:link, #branding h1 a:visited { #branding h1, #branding h1 a:link, #branding h1 a:visited {
color: #f5dd5d; color: #3f3;
} }
#branding h2 { #branding h2 {
@@ -871,14 +873,14 @@ table#change-history tbody th {
#user-tools a:focus, #user-tools a:hover { #user-tools a:focus, #user-tools a:hover {
text-decoration: none; text-decoration: none;
border-bottom-color: #79aec8; border-bottom-color: #888;
color: #79aec8; color: #888;
} }
/* SIDEBAR */ /* SIDEBAR */
#content-related { #content-related {
background: #f8f8f8; background: #797979;
} }
#content-related .module { #content-related .module {
@@ -887,7 +889,7 @@ table#change-history tbody th {
#content-related h3 { #content-related h3 {
font-size: 14px; font-size: 14px;
color: #666; color: #fff;
padding: 0 16px; padding: 0 16px;
margin: 0 0 16px; margin: 0 0 16px;
} }
@@ -918,7 +920,7 @@ table#change-history tbody th {
margin-bottom: 16px; margin-bottom: 16px;
border-bottom: 1px solid #eaeaea; border-bottom: 1px solid #eaeaea;
font-size: 18px; font-size: 18px;
color: #333; color: #f7f7f7;
} }
.delete-confirmation form input[type="submit"] { .delete-confirmation form input[type="submit"] {

View File

@@ -34,13 +34,13 @@
} }
#changelist .toplinks { #changelist .toplinks {
border-bottom: 1px solid #ddd; border-bottom: 1px solid #363636;
} }
#changelist .paginator { #changelist .paginator {
color: #666; color: #666;
border-bottom: 1px solid #eee; border-bottom: 1px solid #363636;
background: #fff; background: #191919;
overflow: hidden; overflow: hidden;
} }
@@ -69,10 +69,10 @@
#changelist #toolbar { #changelist #toolbar {
padding: 8px 10px; padding: 8px 10px;
margin-bottom: 15px; margin-bottom: 0px;
border-top: 1px solid #eee; border-top: 1px solid #363636;
border-bottom: 1px solid #eee; border-bottom: 1px solid #363636;
background: #f8f8f8; background: #000;
color: #666; color: #666;
} }
@@ -127,7 +127,7 @@
right: 0; right: 0;
z-index: 1000; z-index: 1000;
width: 240px; width: 240px;
background: #f8f8f8; background: #000;
border-left: none; border-left: none;
margin: 0; margin: 0;
} }
@@ -151,7 +151,7 @@
#changelist-filter ul { #changelist-filter ul {
margin: 5px 0; margin: 5px 0;
padding: 0 15px 15px; padding: 0 15px 15px;
border-bottom: 1px solid #eaeaea; border-bottom: 1px solid #282828;
} }
#changelist-filter ul:last-child { #changelist-filter ul:last-child {
@@ -172,9 +172,10 @@
} }
#changelist-filter li.selected { #changelist-filter li.selected {
border-left: 5px solid #eaeaea; border-left: 5px solid #797979;
padding-left: 10px; padding-left: 10px;
margin-left: -15px; margin-left: -15px;
background-color: #282828;
} }
#changelist-filter li.selected a { #changelist-filter li.selected a {
@@ -282,11 +283,11 @@
#changelist .actions { #changelist .actions {
padding: 10px; padding: 10px;
background: #fff; background: #191919;
border-top: none; border-top: none;
border-bottom: none; border-bottom: none;
line-height: 24px; line-height: 24px;
color: #999; color: #fff;
} }
#changelist .actions.selected { #changelist .actions.selected {
@@ -311,8 +312,8 @@
#changelist .actions select { #changelist .actions select {
vertical-align: top; vertical-align: top;
height: 24px; height: 24px;
background: none; background: #363636;
color: #000; color: #fff;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
font-size: 14px; font-size: 14px;
@@ -335,14 +336,14 @@
font-size: 13px; font-size: 13px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
background: #fff; background: #363636;
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset; box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer; cursor: pointer;
height: 24px; height: 24px;
line-height: 1; line-height: 1;
padding: 4px 8px; padding: 4px 8px;
margin: 0; margin: 0;
color: #333; color: #fff;
} }
#changelist .actions .button:focus, #changelist .actions .button:hover { #changelist .actions .button:focus, #changelist .actions .button:hover {

View File

@@ -30,13 +30,13 @@ form .form-row p {
label { label {
font-weight: normal; font-weight: normal;
color: #666; color: #fff;
font-size: 13px; font-size: 13px;
} }
.required label, label.required { .required label, label.required {
font-weight: bold; font-weight: bold;
color: #333; color: #999;
} }
/* RADIO BUTTONS */ /* RADIO BUTTONS */
@@ -250,8 +250,8 @@ fieldset.monospace textarea {
.submit-row { .submit-row {
padding: 12px 14px; padding: 12px 14px;
margin: 0 0 20px; margin: 0 0 20px;
background: #f8f8f8; background: #888888;
border: 1px solid #eee; border: 1px solid #3f3f3f;
border-radius: 4px; border-radius: 4px;
text-align: right; text-align: right;
overflow: hidden; overflow: hidden;

View File

@@ -13,21 +13,22 @@
flex: 0 0 23px; flex: 0 0 23px;
width: 23px; width: 23px;
border-right: 1px solid #eaeaea; border-right: 1px solid #eaeaea;
background-color: #ffffff; background-color: #262626;
cursor: pointer; cursor: pointer;
font-size: 20px; font-size: 20px;
color: #447e9b; color: #447e9b;
padding: 0; padding: 0;
display: none !important;
} }
[dir="rtl"] .toggle-nav-sidebar { [dir="rtl"] .toggle-nav-sidebar {
border-left: 1px solid #eaeaea; border-left: 1px solid #262626;
border-right: 0; border-right: 0;
} }
.toggle-nav-sidebar:hover, .toggle-nav-sidebar:hover,
.toggle-nav-sidebar:focus { .toggle-nav-sidebar:focus {
background-color: #f6f6f6; background-color: #000;
} }
#nav-sidebar { #nav-sidebar {
@@ -37,7 +38,8 @@
margin-left: -276px; margin-left: -276px;
border-top: 1px solid transparent; border-top: 1px solid transparent;
border-right: 1px solid #eaeaea; border-right: 1px solid #eaeaea;
background-color: #ffffff; border: 0px;
background-color: #282828;
overflow: auto; overflow: auto;
} }
@@ -59,7 +61,7 @@
} }
.main.shifted > #nav-sidebar { .main.shifted > #nav-sidebar {
left: 24px; left: 0px;
margin-left: 0; margin-left: 0;
} }
@@ -96,7 +98,7 @@
} }
#nav-sidebar .current-model { #nav-sidebar .current-model {
background: #ffc; background: #555555;
} }
@media (max-width: 767px) { @media (max-width: 767px) {

View File

@@ -12,6 +12,27 @@
font-size: small; font-size: small;
} }
.shelf_item{ .shelf_item{
width: fit-content;
}
#book_shelf {
justify-content: left;
flex-direction: column;
}
.book_description {
display: none;
}
.book_details_list {
display: grid;
grid-template-areas:
"book_title"
"book_author"
"book_tags"
"book_controls";
grid-area: details;
grid-template-rows: .5fr .5fr .15fr 0.25fr;
} }
} }
@@ -29,7 +50,8 @@
font-size: small; font-size: small;
} }
.shelf_item{ .shelf_item{
max-width: min-content; min-width: 100%;
margin: 0px auto 10px auto;
} }
.nav_search{ .nav_search{
/* display: none !important; */ /* display: none !important; */
@@ -47,7 +69,6 @@
padding: 0px; padding: 0px;
margin: 0px 0px; margin: 0px 0px;
} }
.vert-nav-item {} .vert-nav-item {}
#vert-nav { #vert-nav {

View File

@@ -33,6 +33,7 @@ $(document).ready(function(){
const navlink = $('.nav_link') const navlink = $('.nav_link')
const inputbox = $('input_box') const inputbox = $('input_box')
const loginbtn = $('#btn_login') const loginbtn = $('#btn_login')
const adminbtn = $('.admin-btn')
const server = ('ws://127.0.0.1:1337') const server = ('ws://127.0.0.1:1337')
customlog([cmp_height]); customlog([cmp_height]);
$(".search_submit").click(function(){ $(".search_submit").click(function(){
@@ -170,6 +171,9 @@ $(document).ready(function(){
'<button type="submit" class="btn-sm btn-secondary import-btn"><i class="fas fa-file-import"></i>&nbsp; Import Books</button>' + '<button type="submit" class="btn-sm btn-secondary import-btn"><i class="fas fa-file-import"></i>&nbsp; Import Books</button>' +
'</div>' + '</div>' +
'<div class="col-auto" id="usercp-col2">' + '<div class="col-auto" id="usercp-col2">' +
'<button type="submit" class="btn-sm btn-secondary admin-btn"><i class="fas fa-sign-out-alt"></i>&nbsp; Admin</button>' +
'</div>' +
'<div class="col-auto" id="usercp-col3">' +
'<button type="submit" class="btn-sm btn-secondary logout-btn"><i class="fas fa-sign-out-alt"></i>&nbsp; Logout</button>' + '<button type="submit" class="btn-sm btn-secondary logout-btn"><i class="fas fa-sign-out-alt"></i>&nbsp; Logout</button>' +
'</div>' '</div>'
); );
@@ -181,6 +185,7 @@ $(document).ready(function(){
$('#pop_over_0').dialog("open"); $('#pop_over_0').dialog("open");
}); });
$(document).on('click', '.logout-btn', function(){window.location.href = '/logout'}); $(document).on('click', '.logout-btn', function(){window.location.href = '/logout'});
$(document).on('click', '.admin-btn', function(){window.location.href = '/admin'});
//Web Socket Call //Web Socket Call
$(document).on('click', '.import-btn', async function(){ $(document).on('click', '.import-btn', async function(){

View File

@@ -41,7 +41,7 @@
<li class="nav_menu_tab active_tab" id="btn-home" data-location="/home"><i class="fas fa-home"></i>&nbsp;Home</li> <li class="nav_menu_tab active_tab" id="btn-home" data-location="/home"><i class="fas fa-home"></i>&nbsp;Home</li>
<li class="nav_menu_tab" id="coll_button"><i class="fas fa-layer-group"></i>&nbsp;Collections</li> <li class="nav_menu_tab" id="coll_button"><i class="fas fa-layer-group"></i>&nbsp;Collections</li>
<li class="nav_menu_tab"><i class="fas fa-star"></i><a href="/favorites" class="nav_link">&nbsp;Favorites,</a></li> <li class="nav_menu_tab"><i class="fas fa-star"></i><a href="/favorites" class="nav_link">&nbsp;Favorites,</a></li>
<li class="nav_menu_tab"><i class="fas fa-bug"></i>&nbsp;Bug report</li> <li class="nav_menu_tab"><i class="fas fa-bug"></i>&nbsp;<a href="https://github.com/th3r00t/pyShelf/issues" class="nav_link">Bug report</a></li>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<li class="nav_menu_tab" id="btn_logout"><i class="fa fa-user-circle" aria-hidden="true"></i>&nbsp;{{ request.user }} <li class="nav_menu_tab" id="btn_logout"><i class="fa fa-user-circle" aria-hidden="true"></i>&nbsp;{{ request.user }}