use bottle version as main
This commit is contained in:
commit
de1b5918b2
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "templates/plain"]
|
||||
path = templates/plain
|
||||
url = https://gitea.ditaajipratama.net/aji/costapy-template-plain.git
|
129
README.md
129
README.md
@ -1,6 +1,5 @@
|
||||
# CostaPy
|
||||
Python Web Framework. Build with CherryPy and Mako.
|
||||
Forked from [Pytavia](https://github.com/sidonesia/pytavia). But now it have a huge difference in the structure.
|
||||
Python Web Framework. Build with Bottle and Mako.
|
||||
|
||||
## License
|
||||
|
||||
@ -24,12 +23,9 @@ along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
## Requirement & Installation
|
||||
|
||||
You need this libraries to use CostaPy:
|
||||
- cherrypy
|
||||
- cherrypy-cors
|
||||
- bottle
|
||||
- gunicorn
|
||||
- mako
|
||||
- mysql-connector
|
||||
- bcrypt
|
||||
- pyjwt[crypto]
|
||||
|
||||
You can install it with run this command
|
||||
|
||||
@ -39,138 +35,35 @@ Here is the completed command
|
||||
|
||||
sudo apt-get install -y python3-pip
|
||||
pip install --upgrade pip
|
||||
pip install cherrypy
|
||||
pip install cherrypy-cors
|
||||
pip install bottle
|
||||
pip install gunicorn
|
||||
pip install mako
|
||||
pip install mysql-connector
|
||||
pip install bcrypt
|
||||
pip install pyjwt[crypto]
|
||||
|
||||
## Usage
|
||||
|
||||
Use this command to start the web service
|
||||
|
||||
python<ver> costa.py <ip_address> <port> <service_name>
|
||||
|
||||
For an example like this
|
||||
|
||||
python3 costa.py localhost 80 My_Service
|
||||
python3 costa.py
|
||||
|
||||
You can use nohup too and running it in the background like this
|
||||
|
||||
nohup python3 costa.py localhost 80 My_Service &
|
||||
nohup python3 costa.py &
|
||||
|
||||
## Configuration
|
||||
|
||||
### Server (config/server.py)
|
||||
|
||||
tools.sessions.on </br>
|
||||
Default: True </br>
|
||||
Description: Enable sessions </br>
|
||||
|
||||
engine.autoreload.on </br>
|
||||
Default: False </br>
|
||||
Description: Auto Reload when source code change. Don't use it in production. </br>
|
||||
|
||||
request.show_tracebacks </br>
|
||||
Default: False </br>
|
||||
Description: Show traceback for debugging in development purposes. </br>
|
||||
|
||||
### Global Variable (config/globalvar.py)
|
||||
|
||||
`directory.py` is the place for storing your Global Variable.
|
||||
`globalvar.py` is the place for storing your Global Variable.
|
||||
|
||||
GV_base_url </br>
|
||||
`baseurl` </br>
|
||||
Is the variable for your base URL (without `/` in the end).
|
||||
|
||||
GV_title </br>
|
||||
`title` </br>
|
||||
Is the variable for your web title.
|
||||
|
||||
### Directory (config/directory.py)
|
||||
|
||||
`directory.py` is the place for storing your path. It is useful to calling the path more efficiently. there is 2 method that you can store your path. store it in variable for templating configuration, and store it as object for routing the url.
|
||||
|
||||
This is example that use for templating
|
||||
|
||||
html_user = "static/pages-user"
|
||||
template_user = "static/template-user"
|
||||
|
||||
And this is example that use for routing the url
|
||||
|
||||
dirconfig = {
|
||||
'/' :
|
||||
{
|
||||
'tools.sessions.on' : True ,
|
||||
'tools.staticdir.root' : os.path.abspath(os.getcwd()) ,
|
||||
},
|
||||
'/your_dir' :
|
||||
{
|
||||
'tools.staticdir.on' : True ,
|
||||
'tools.staticdir.dir' : './static/your-dir' ,
|
||||
},
|
||||
}
|
||||
|
||||
### Templating (config/template.py)
|
||||
|
||||
Templating is useful when you had more than 1 website template for difference use case. For an example, when you had user and admin in the use case, the website for user have a navbar and footer, and the website for admin have a navbar and sidebar.
|
||||
|
||||
Before you create a template, make sure your `directory` configuration is ready for storing templates and pages. For an example:
|
||||
|
||||
html_user = "static/pages-user"
|
||||
template_user = "static/template-user"
|
||||
|
||||
To create the template, you need to insert this code in `def __init__(self)`
|
||||
|
||||
self.html_pages_user = html.main.get_html(directory.html_user)
|
||||
self.html_template_user = html.main.get_html(directory.template_user)
|
||||
|
||||
if you had admin template, you just need to add the code. for the example like this
|
||||
|
||||
self.html_pages_user = html.main.get_html(directory.html_user)
|
||||
self.html_template_user = html.main.get_html(directory.template_user)
|
||||
|
||||
self.html_pages_admin = html.main.get_html(directory.html_admin)
|
||||
self.html_template_admin = html.main.get_html(directory.template_admin)
|
||||
|
||||
and then you need create function for each of your template in main class like this
|
||||
|
||||
def user(self, page):
|
||||
params_list = {
|
||||
"template" : self.html_template_user ["user.html" ] ,
|
||||
"topnav" : self.html_template_user ["user-topnav.html" ] ,
|
||||
"container" : self.html_pages_user [page+".html" ]
|
||||
}
|
||||
return params_list
|
||||
|
||||
### Database (config/database.py)
|
||||
|
||||
This is the sample template for configure it
|
||||
|
||||
db_default = {
|
||||
'host' : 'localhost',
|
||||
'user' : 'root',
|
||||
'password' : '',
|
||||
'database' : 'your_db',
|
||||
'autocommit' : True,
|
||||
}
|
||||
|
||||
You also can make more than 1 database configuration like this
|
||||
|
||||
db_default = {
|
||||
'host' : 'localhost',
|
||||
'user' : 'root',
|
||||
'password' : '',
|
||||
'database' : 'your_db',
|
||||
'autocommit' : True,
|
||||
}
|
||||
|
||||
db_other = {
|
||||
'host' : 'localhost',
|
||||
'user' : 'root',
|
||||
'password' : '',
|
||||
'database' : 'other_db',
|
||||
'autocommit' : True,
|
||||
}
|
||||
`directory.py` is the place for storing your path. It is useful to calling the path more efficiently.
|
||||
|
||||
## Handling the modules
|
||||
|
||||
|
0
config/database.py
Executable file → Normal file
0
config/database.py
Executable file → Normal file
@ -1,30 +1,13 @@
|
||||
import os
|
||||
from core import templatestaticdir
|
||||
from core import template
|
||||
|
||||
# pages directory
|
||||
page = {
|
||||
'public' :'page/public' ,
|
||||
'error' :'page/error' # Non-template
|
||||
'public' :'pages/public'
|
||||
}
|
||||
|
||||
# public staticdir
|
||||
dirconfig = {
|
||||
'/' :
|
||||
static = [
|
||||
{
|
||||
'tools.sessions.on' : True ,
|
||||
'tools.staticdir.root' : os.path.abspath(os.getcwd()) ,
|
||||
},
|
||||
'/css' :
|
||||
{
|
||||
'tools.staticdir.on' : True ,
|
||||
'tools.staticdir.dir' : './static/css' ,
|
||||
},
|
||||
'/js' :
|
||||
{
|
||||
'tools.staticdir.on' : True ,
|
||||
'tools.staticdir.dir' : './static/js' ,
|
||||
},
|
||||
}
|
||||
|
||||
# template staticdir: dirconfig dirtemplate
|
||||
templatestaticdir.add(dirconfig, "templates")
|
||||
"route" :"/css/<filepath:re:.*\.(css|sass|css.map)>",
|
||||
"root" :"./static/css"
|
||||
}
|
||||
]
|
||||
template.add(static, "templates")
|
||||
|
18
config/globalvar.py
Executable file → Normal file
18
config/globalvar.py
Executable file → Normal file
@ -1,24 +1,28 @@
|
||||
baseurl = "http://localhost:81"
|
||||
title = "CostaPy"
|
||||
title = "CostaPy"
|
||||
|
||||
menu = {
|
||||
menu = {
|
||||
"public": {
|
||||
"topnav": [
|
||||
"navbar": [
|
||||
{
|
||||
"name":"Home",
|
||||
"target":"_self",
|
||||
"href":"/",
|
||||
"roles":["guest"]
|
||||
},
|
||||
{
|
||||
"name":"About",
|
||||
"href":"#",
|
||||
"target":"_self",
|
||||
"href":"/about",
|
||||
"roles":["guest"]
|
||||
},
|
||||
{
|
||||
"name":"CostaPy Website",
|
||||
"href":"https://costapy.ditaajipratama.com",
|
||||
"name":"Docs",
|
||||
"target":"_blank",
|
||||
"href":"https://costapy.ditaajipratama.net",
|
||||
"roles":["guest"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
copyright = "Copyright (C) 2022 Dita Aji Pratama"
|
||||
|
@ -1,20 +1,16 @@
|
||||
from config import directory
|
||||
host = "localhost"
|
||||
port = 15001
|
||||
reloader = False
|
||||
debug = False
|
||||
server = 'gunicorn' # default = 'wsgiref'
|
||||
|
||||
update = {
|
||||
'server.socket_host' : "hostname" ,
|
||||
'server.socket_port' : "port" ,
|
||||
# cors
|
||||
# session
|
||||
|
||||
'cors.expose.on' : True ,
|
||||
'tools.sessions.on' : True ,
|
||||
# error page 403
|
||||
# error page 404
|
||||
# error page 500
|
||||
|
||||
'engine.autoreload.on' : False ,
|
||||
'request.show_tracebacks' : False ,
|
||||
|
||||
'error_page.403' : f'{directory.page["error"]}/403.html' ,
|
||||
'error_page.404' : f'{directory.page["error"]}/404.html' ,
|
||||
'error_page.500' : f'{directory.page["error"]}/500.html' ,
|
||||
|
||||
'server.max_request_body_size' : 800 * 1024 * 1024 , # 800MB; Default 100MB
|
||||
'server.socket_timeout' : 60 , # Default 10s
|
||||
'response.timeout' : 3600 , # Default 300s
|
||||
}
|
||||
# max_request_body_size = 800 * 1024 * 1024 , # Multiply for 800MB result
|
||||
# socket timeout = 60
|
||||
# response timeout = 3600
|
||||
|
9
core/staticdir.py
Normal file
9
core/staticdir.py
Normal file
@ -0,0 +1,9 @@
|
||||
from bottle import Bottle, get, static_file
|
||||
from config import directory
|
||||
|
||||
app = Bottle()
|
||||
|
||||
for items in directory.static:
|
||||
@app.get(items['route'])
|
||||
def static_items(filepath):
|
||||
return static_file(filepath, root=items['root'])
|
@ -1,9 +1,8 @@
|
||||
import os
|
||||
|
||||
def add(dirconfig, template_directory):
|
||||
template_directory = "templates"
|
||||
template_list = [d for d in os.listdir(template_directory) if os.path.isdir(os.path.join(template_directory, d))]
|
||||
template_list = [d for d in os.listdir(template_directory) if os.path.isdir(os.path.join(template_directory, d))]
|
||||
for template_name in template_list:
|
||||
template_module = __import__(f"{template_directory}.{template_name}.main", fromlist=["static"])
|
||||
for static in getattr(template_module, "static", []):
|
||||
dirconfig[ static['name'] ] = static['value']
|
||||
dirconfig.append(static)
|
@ -1,40 +0,0 @@
|
||||
import os
|
||||
import cherrypy
|
||||
|
||||
def main(file, rename, directory):
|
||||
try:
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
except OSError as error:
|
||||
print(error)
|
||||
try:
|
||||
upload_path = directory
|
||||
upload_filename = file.filename
|
||||
upload_rename = rename
|
||||
upload_file = os.path.normpath(os.path.join(upload_path, upload_rename))
|
||||
upload_size = 0
|
||||
|
||||
print("UPLOADING CORE: Directory: "+directory+"/"+rename)
|
||||
if (os.path.isfile(directory+"/"+rename)):
|
||||
print("UPLOADING CORE: Is exists! Removing!")
|
||||
try:
|
||||
os.remove(directory+"/"+rename)
|
||||
except Exception as e:
|
||||
print(f"UPLOADING CORE: removing failed: {e}")
|
||||
else:
|
||||
print("UPLOADING CORE: Is not exists! Removing skipped!")
|
||||
with open(upload_file, 'wb') as upload_result:
|
||||
while True:
|
||||
data = file.file.read(8192)
|
||||
if not data:
|
||||
break
|
||||
upload_result.write(data)
|
||||
upload_size += len(data)
|
||||
|
||||
print("UPLOAD PATH: " + str(upload_path))
|
||||
print("UPLOAD FILENAME: " + str(upload_filename))
|
||||
print("UPLOAD RENAME: " + str(upload_rename))
|
||||
print("UPLOAD FILE: " + str(upload_file))
|
||||
print("UPLOAD SIZE: " + str(upload_size))
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR CORE UPLOADING: {e}")
|
35
costa.py
35
costa.py
@ -5,28 +5,23 @@
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
# You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import sys
|
||||
import cherrypy
|
||||
import cherrypy_cors
|
||||
import handler
|
||||
import sys
|
||||
from bottle import Bottle, run
|
||||
|
||||
from config import server
|
||||
from config import directory
|
||||
import handler
|
||||
|
||||
if __name__ == '__main__':
|
||||
from core import staticdir
|
||||
from config import server
|
||||
|
||||
dirconfig = directory.dirconfig
|
||||
update = server.update
|
||||
app = Bottle()
|
||||
|
||||
if len(sys.argv) >= 3:
|
||||
app.merge(handler.app)
|
||||
app.merge(staticdir.app)
|
||||
|
||||
update["server.socket_host"] = sys.argv[1]
|
||||
update["server.socket_port"] = int(sys.argv[2])
|
||||
|
||||
cherrypy_cors.install()
|
||||
cherrypy.config.update ( update )
|
||||
cherrypy.quickstart ( handler.handler(), config = dirconfig )
|
||||
|
||||
else:
|
||||
print ("Usage : python<ver> costa.py <ip_address> <port> <service_name>")
|
||||
print ("Example : python3 costa.py localhost 81 CostaPySample")
|
||||
run(app,
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
reloader = server.reloader,
|
||||
server = server.server,
|
||||
debug = server.debug
|
||||
)
|
||||
|
25
handler.py
Executable file → Normal file
25
handler.py
Executable file → Normal file
@ -5,20 +5,19 @@
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
# You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import cherrypy
|
||||
import json
|
||||
import config.directory as directory
|
||||
from bottle import Bottle, route
|
||||
from config import directory
|
||||
|
||||
import templates.bare.main as bare
|
||||
import templates.plain.main as template_public
|
||||
import modules.public.home as public_home
|
||||
|
||||
import modules.public.home as public_home
|
||||
app = Bottle()
|
||||
|
||||
@cherrypy.tools.accept(media="application/json")
|
||||
class handler():
|
||||
|
||||
def index(self, **kwargs):
|
||||
kwargs["mako"] = {
|
||||
"website" : bare.main(directory.page["public"], "home")
|
||||
@app.route('/')
|
||||
def index():
|
||||
params = {
|
||||
"mako":{
|
||||
"website" : template_public.main(directory.page["public"], "home")
|
||||
}
|
||||
return public_home.main().html(kwargs)
|
||||
index.exposed = True
|
||||
}
|
||||
return public_home.main().html(params)
|
||||
|
@ -1,8 +1,5 @@
|
||||
sudo apt-get install -y python3-pip
|
||||
pip install --upgrade pip
|
||||
pip install cherrypy
|
||||
pip install cherrypy-cors
|
||||
pip install bottle
|
||||
pip install gunicorn
|
||||
pip install mako
|
||||
pip install mysql-connector
|
||||
pip install bcrypt
|
||||
pip install pyjwt[crypto]
|
||||
|
@ -1,5 +1,5 @@
|
||||
from mako.template import Template
|
||||
import config.globalvar as globalvar
|
||||
from mako.template import Template
|
||||
from config import globalvar
|
||||
|
||||
class main:
|
||||
|
||||
@ -7,21 +7,18 @@ class main:
|
||||
pass
|
||||
|
||||
def html(self, params):
|
||||
return Template(params["mako"]["website"]['template']).render(
|
||||
return Template(params["mako"]["website"]['index']).render(
|
||||
title = globalvar.title,
|
||||
baseurl = globalvar.baseurl,
|
||||
topnav = Template(params["mako"]["website"]['topnav']).render(
|
||||
title = globalvar.title,
|
||||
baseurl = globalvar.baseurl,
|
||||
menu = globalvar.menu['public']['topnav'],
|
||||
header = "Welcome to CostaPy",
|
||||
navbar = Template(params["mako"]["website"]['navbar']).render(
|
||||
menu = globalvar.menu['public']['navbar'],
|
||||
user_roles = ["guest"],
|
||||
active_page = "Home"
|
||||
),
|
||||
footer = Template(params["mako"]["website"]['footer']).render(
|
||||
copyright = "Dita Aji Pratama",
|
||||
copyright = globalvar.copyright,
|
||||
),
|
||||
container = Template(params["mako"]["website"]['container']).render(
|
||||
baseurl = globalvar.baseurl,
|
||||
greeting = f"Hello world, welcome to {globalvar.title}"
|
||||
greeting = f"Welcome to your new web application! This placeholder page is here to let you know that your web framework is successfully set up and ready to go. Now, it's time to start building your project. Dive into the documentation to explore the features and capabilities at your disposal."
|
||||
)
|
||||
)
|
||||
|
@ -1 +0,0 @@
|
||||
403 Forbidden
|
@ -1 +0,0 @@
|
||||
404 Not found
|
@ -1 +0,0 @@
|
||||
500 Internal server error
|
@ -1,9 +0,0 @@
|
||||
<div class="container-fluid my-3">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h1>Welcome</h1>
|
||||
<h3>This is your first pages</h3>
|
||||
<p>${greeting}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
1
pages/public/home.html
Normal file
1
pages/public/home.html
Normal file
@ -0,0 +1 @@
|
||||
<p>${greeting}</p>
|
@ -1,13 +0,0 @@
|
||||
import cherrypy
|
||||
import cherrypy_cors
|
||||
import json
|
||||
|
||||
def body_json():
|
||||
result = None
|
||||
if cherrypy.request.method == 'OPTIONS':
|
||||
cherrypy_cors.preflight(allowed_methods=['GET', 'POST'])
|
||||
if cherrypy.request.method == 'POST':
|
||||
cherrypy.serving.response.headers['Content-Type'] = 'application/json'
|
||||
body_request = cherrypy.request.body.read()
|
||||
result = json.loads(body_request.decode())
|
||||
return result
|
@ -1,13 +0,0 @@
|
||||
import datetime
|
||||
|
||||
def prcss(loc, msg):
|
||||
print(f"[loggorilla][{datetime.datetime.now()}][\033[32mprcss\033[39m][\033[95m{loc}\033[39m] {msg}")
|
||||
|
||||
def accss(loc, msg):
|
||||
print(f"[loggorilla][{datetime.datetime.now()}][\033[36maccss\033[39m][\033[95m{loc}\033[39m] {msg}")
|
||||
|
||||
def fyinf(loc, msg):
|
||||
print(f"[loggorilla][{datetime.datetime.now()}][\033[93mfyinf\033[39m][\033[95m{loc}\033[39m] {msg}")
|
||||
|
||||
def error(loc, msg):
|
||||
print(f"[loggorilla][{datetime.datetime.now()}][\033[31merror\033[39m][\033[95m{loc}\033[39m] {msg}")
|
@ -1,23 +0,0 @@
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
import smtplib
|
||||
|
||||
def smtp(config):
|
||||
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['Subject' ] = config['subject' ]
|
||||
msg['From' ] = config['from' ]
|
||||
msg['To' ] = config['to' ]
|
||||
|
||||
part1 = MIMEText(config['text'], 'plain')
|
||||
part2 = MIMEText(config['html'], 'html' )
|
||||
|
||||
msg.attach(part1)
|
||||
msg.attach(part2)
|
||||
|
||||
smtp_server = smtplib.SMTP(config['server']['host'], config['server']['port'])
|
||||
smtp_server.ehlo()
|
||||
smtp_server.starttls()
|
||||
smtp_server.login( config['login']['email'], config['login']['password'] )
|
||||
smtp_server.sendmail('&&&&&&', config['to'], msg.as_string() )
|
||||
smtp_server.quit()
|
@ -1,23 +0,0 @@
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
import jwt
|
||||
|
||||
def encode(payload, id_rsa, passphrase):
|
||||
private_key = open(id_rsa, 'r').read()
|
||||
key = serialization.load_ssh_private_key(private_key.encode(), password=passphrase)
|
||||
token = jwt.encode(
|
||||
payload = payload,
|
||||
key = key,
|
||||
algorithm = 'RS256'
|
||||
)
|
||||
return token
|
||||
|
||||
def decode(token, id_rsa):
|
||||
public_key = open(id_rsa, 'r').read()
|
||||
key = serialization.load_ssh_public_key(public_key.encode())
|
||||
header = jwt.get_unverified_header(token)
|
||||
payload = jwt.decode(
|
||||
jwt = token,
|
||||
key = key,
|
||||
algorithms = [header['alg'], ]
|
||||
)
|
||||
return payload
|
@ -1,3 +0,0 @@
|
||||
<footer class="mt-auto bg-dark text-light p-3 text-center">
|
||||
© 2022 ${copyright}
|
||||
</footer>
|
@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>${title}</title>
|
||||
<script src="${baseurl}/bare/lib/jquery/jquery-3.7.0.min.js"></script>
|
||||
<link href="${baseurl}/bare/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css">
|
||||
<script src="${baseurl}/bare/lib/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="${baseurl}/bare/css/style.css">
|
||||
</head>
|
||||
<body class="d-flex flex-column" style="min-height:100vh;">
|
||||
${topnav}
|
||||
<div class="mb-5">
|
||||
${container}
|
||||
</div>
|
||||
${footer}
|
||||
</body>
|
||||
</html>
|
@ -1,17 +0,0 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
||||
<a class="navbar-brand" href="${baseurl}">${title}</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
% for item in menu:
|
||||
% if any(role in item['roles'] for role in user_roles):
|
||||
<li class="nav-item ${'active' if item['name'] == active_page else ''}">
|
||||
<a class="nav-link" href="${item['href']}">${item['name']}</a>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
@ -1,30 +0,0 @@
|
||||
from core import html
|
||||
|
||||
static = [
|
||||
{
|
||||
'name':'/bare/lib',
|
||||
'value':{
|
||||
'tools.staticdir.on' : True ,
|
||||
'tools.staticdir.dir' : './templates/bare/static/lib' ,
|
||||
}
|
||||
},
|
||||
{
|
||||
'name':'/bare/css',
|
||||
'value':{
|
||||
'tools.staticdir.on' : True ,
|
||||
'tools.staticdir.dir' : './templates/bare/static/css' ,
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
def main(dir, page):
|
||||
|
||||
html_template = html.main.get_html("templates/bare/html")
|
||||
html_page = html.main.get_html(dir)
|
||||
params_list = {
|
||||
"template" : html_template ["template.html" ] ,
|
||||
"topnav" : html_template ["topnav.html" ] ,
|
||||
"footer" : html_template ["footer.html" ] ,
|
||||
"container" : html_page [ page+".html" ]
|
||||
}
|
||||
return params_list
|
@ -1 +0,0 @@
|
||||
/* your style here */
|
@ -1,22 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011-2020 Twitter, Inc.
|
||||
Copyright (c) 2011-2020 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
templates/plain
Submodule
1
templates/plain
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit adcbeee71da67d013627b67855ce3351b3a847b7
|
Loading…
Reference in New Issue
Block a user