#routes/admin/category.py
from bottle import Bottle,redirect
from controllers.login import checkLogged
app = Bottle()
@app.route('/')
def getCategory():
if checkLogged.call():
from controllers.admin.categories import read
return read.call()
else:
redirect('/login')
@app.route('/',method='post')
def postCategory():
if checkLogged.call():
from controllers.admin.categories import create
return create.call()
else:
redirect('/login')
#controllers/admin/categories/read.py
import config
from copy import deepcopy
from bottle import template
from models.categories import read
def call():
kdict = deepcopy(config.kdict)
kdict['pageTitle'] = 'ទំព័រជំពូក'
kdict['route'] = '/admin/category'
kdict['type'] = 'category'
kdict['items'] = read.call(kdict['maxPosts'])
return template('base',data=kdict)
#models/categories/read.py
import config
from models import setDBconnection as con
def call(amount):
categories_ref = con.db.collection("categories")
query = categories_ref.order_by("date",direction=con.firestore.Query.DESCENDING).limit(amount)
results = query.get()
categories = [category.to_dict() for category in results]
return categories
<!--views/admin/index.tpl-->
<link href="/static/styles/admin/index.css" rel="stylesheet" />
<section class='Index'>
<div class="header-wrapper">
<header class='region'>
<div class='logo'>{{ data['pageTitle'] }}</div>
<form action='/admin/search' method='post'>
<select name="select">
<option>ការផ្សាយ</option>
<option>ជំពូក</option>
<option>សៀវភៅ</option>
<option>អ្នកប្រើប្រាស់</option>
</select>
<input type='text' name="q" placeholder="Search" required />
<input type="submit" value='បញ្ជូន' />
</form>
<div class='logout'><a href='/'>ទំព័រមុខ</a> | <a href='/login/logout'>ចេញក្រៅ</a></div>
</header>
</div>
<div class="main region">
<div class="sidebar">
<div class="menu">
<a href="/admin/post"><img src="/static/images/movie.png" /></a>
<a href="/admin/post">ការផ្សាយ</a>
<a href="/admin/category"><img src="/static/images/category.png" /></a>
<a href="/admin/category">ជំពូក</a>
<a href="/admin/upload"><img src="/static/images/upload.png" /></a>
<a href="/admin/upload">Upload</a>
<a href="/admin/user"><img src="/static/images/users.png" /></a>
<a href="/admin/user">អ្នកប្រើប្រាស់</a>
<a href="/admin/setting"><img src="/static/images/setting.png" /></a>
<a href="/admin/setting">Setting</a>
</div>
</div>
<div class="content">
<%
if '/post' in data['route']:
include('admin/post.tpl')
elif '/category' in data['route']:
include('admin/category.tpl')
end
%>
</div>
</div>
<div class="Listing region">
%if 'items' in data:
<ul class="list">
%for item in data['items']:
<li class="item">
<a class="thumb" href="/{{data['type']}}/{{item['id']}}">
<img src="{{item['thumb']}}" />
</a>
<div>
<a class="title" href="/{{data['type']}}/{{item['id']}}">
{{item['title']}}
</a>
<span id="{{item['id']}}">
<script>
var date = (new Date('{{item["date"]}}')).toLocaleDateString()
$('.Listing .list .item #{{item["id"]}}').append(date)
</script>
</span>
</div>
<div class="edit">
<a href="/admin/{{data['type']}}/edit/{{item['id']}}">
<img src="/static/images/edit.png" />
</a>
<a href="/admin/{{data['type']}}/delete/{{item['id']}}">
<img src="/static/images/delete.png" />
</a>
</div>
</li>
%end
</ul>
%end
<div class="paginate region">
<img onclick="paginate(`{{data['route']}}`)" src="/static/images/load-more.png"/>
</div>
</div>
</section>
/* public/styles/admin/index.css */
.Index .header-wrapper {
background: var(--background-dark);
margin-bottom: 20px;
}
.Index .header-wrapper header{
display: grid;
grid-template-columns: 25% auto 25%;
align-items: center;
padding: 10px 0;
}
.Index .header-wrapper header .logo{
font: 24px/1.5 Limonf3;
}
.Index .header-wrapper header form{
display: grid;
grid-template-columns: 20% auto 20%;
}
.Index .header-wrapper header form input,
.Index .header-wrapper header form select{
font: var(--body-font);
padding: 2px 5px;
}
.Index .header-wrapper header .logout{
text-align: right;
}
.Index .header-wrapper header .logout a{
color: white;
font-size: 16px;
}
.Index .main{
display: grid;
grid-template-columns: 25% auto;
}
.Index .main .sidebar{
background: var(--background-darker);
margin-right: 10px;
}
.Index .main .sidebar .menu{
display: grid;
grid-template-columns: 25% auto;
align-items: center;
padding: 20px;
grid-gap: 15px 10px;
height: 400px;
}
.Index .main .sidebar .menu img{
width: 100%;
float: left;
}
.Index .main .sidebar .menu a{
font: 18px/1.5 Oswald,Koulen;
}
.Index .Listing .list{
display: grid;
grid-template-columns: calc(50% - 5px) calc(50% - 5px);
grid-gap: 10px;
padding-top: 10px;
}
.Index .Listing .info{
background: var(--background-darker);
text-align: center;
margin: 10px auto;
padding: 5px;
}
.Index .Listing .list .item{
display: grid;
grid-template-columns: 22% 56% auto;
background: var(--background-darker);
grid-gap: 10px;
align-items: center;
padding-right: 10px;
}
.Index .Listing .item .thumb{
position: relative;
padding-top: 56.25%;
}
.Index .Listing .item .title{
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.Index .Listing .list .item .thumb img{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
float: left;
}
.Index .Listing .list .item .edit{
text-align: right;
}
.Index .Listing .list .item .edit img{
width: 40px;
visibility: hidden;
}
.Index .Listing .list .item:hover .edit img{
visibility: visible;
}
.Index .paginate{
text-align: center;
padding: 5px 0 0;
margin: 10px auto;
background: var(--background-darker);
}
Vercel: https://khmerweb-vlog.vercel.app