very early session and registration support
This commit is contained in:
parent
91fcbb7101
commit
c843f444e7
14 changed files with 317 additions and 67 deletions
|
@ -2,33 +2,26 @@ import flask
|
|||
from flask import request
|
||||
import functools
|
||||
import time
|
||||
import uuid
|
||||
import warnings
|
||||
|
||||
def _generate_session_token():
|
||||
token = str(uuid.uuid4())
|
||||
#print('MAKE SESSION', token)
|
||||
return token
|
||||
import jsonify
|
||||
|
||||
def give_session_token(function):
|
||||
|
||||
def required_fields(fields):
|
||||
'''
|
||||
Declare that the endpoint requires certain POST body fields. Without them,
|
||||
we respond with 400 and a message.
|
||||
'''
|
||||
def with_required_fields(function):
|
||||
@functools.wraps(function)
|
||||
def wrapped(*args, **kwargs):
|
||||
# Inject new token so the function doesn't know the difference
|
||||
token = request.cookies.get('etiquette_session', None)
|
||||
if not token:
|
||||
token = _generate_session_token()
|
||||
request.cookies = dict(request.cookies)
|
||||
request.cookies['etiquette_session'] = token
|
||||
|
||||
ret = function(*args, **kwargs)
|
||||
|
||||
# Send the token back to the client
|
||||
if not isinstance(ret, flask.Response):
|
||||
ret = flask.Response(ret)
|
||||
ret.set_cookie('etiquette_session', value=token, max_age=60)
|
||||
|
||||
return ret
|
||||
if not all(field in request.form for field in fields):
|
||||
response = {'error': 'Required fields: %s' % ', '.join(fields)}
|
||||
response = jsonify.make_json_response(response, status=400)
|
||||
return response
|
||||
return function(*args, **kwargs)
|
||||
return wrapped
|
||||
return with_required_fields
|
||||
|
||||
def not_implemented(function):
|
||||
'''
|
||||
|
|
157
etiquette.py
157
etiquette.py
|
@ -12,6 +12,7 @@ import exceptions
|
|||
import helpers
|
||||
import jsonify
|
||||
import phototagger
|
||||
import sessions
|
||||
|
||||
# pip install
|
||||
# https://raw.githubusercontent.com/voussoir/else/master/_voussoirkit/voussoirkit.zip
|
||||
|
@ -27,6 +28,7 @@ site.debug = True
|
|||
|
||||
P = phototagger.PhotoDB()
|
||||
|
||||
session_manager = sessions.SessionManager()
|
||||
|
||||
####################################################################################################
|
||||
####################################################################################################
|
||||
|
@ -34,6 +36,9 @@ P = phototagger.PhotoDB()
|
|||
####################################################################################################
|
||||
|
||||
|
||||
def back_url():
|
||||
return request.args.get('goto') or request.referrer or '/'
|
||||
|
||||
def create_tag(easybake_string):
|
||||
notes = P.easybake(easybake_string)
|
||||
notes = [{'action': action, 'tagname': tagname} for (action, tagname) in notes]
|
||||
|
@ -60,12 +65,6 @@ def delete_synonym(synonym):
|
|||
master_tag.remove_synonym(synonym)
|
||||
return {'action':'delete_synonym', 'synonym': synonym}
|
||||
|
||||
def make_json_response(j, *args, **kwargs):
|
||||
dumped = json.dumps(j)
|
||||
response = flask.Response(dumped, *args, **kwargs)
|
||||
response.headers['Content-Type'] = 'application/json;charset=utf-8'
|
||||
return response
|
||||
|
||||
def P_album(albumid):
|
||||
try:
|
||||
return P.get_album(albumid)
|
||||
|
@ -159,11 +158,82 @@ def send_file(filepath):
|
|||
####################################################################################################
|
||||
####################################################################################################
|
||||
|
||||
|
||||
@site.route('/')
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def root():
|
||||
motd = random.choice(P.config['motd_strings'])
|
||||
return flask.render_template('root.html', motd=motd)
|
||||
session = session_manager.get(request)
|
||||
return flask.render_template('root.html', motd=motd, session=session)
|
||||
|
||||
@site.route('/login', methods=['GET'])
|
||||
@session_manager.give_token
|
||||
def get_login():
|
||||
session = session_manager.get(request)
|
||||
return flask.render_template('login.html', session=session)
|
||||
|
||||
@site.route('/register', methods=['GET'])
|
||||
def get_register():
|
||||
return flask.redirect('/login')
|
||||
|
||||
@site.route('/login', methods=['POST'])
|
||||
@session_manager.give_token
|
||||
@decorators.required_fields(['username', 'password'])
|
||||
def post_login():
|
||||
if session_manager.get(request):
|
||||
flask.abort(403, 'You\'re already signed in.')
|
||||
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
user = P.get_user(username=username)
|
||||
try:
|
||||
user = P.login(user.id, password)
|
||||
except exceptions.WrongLogin:
|
||||
flask.abort(422, 'Wrong login.')
|
||||
session = sessions.Session(request, user)
|
||||
session_manager.add(session)
|
||||
response = flask.Response('redirect', status=302, headers={'Location': '/'})
|
||||
return response
|
||||
|
||||
@site.route('/register', methods=['POST'])
|
||||
@session_manager.give_token
|
||||
@decorators.required_fields(['username', 'password_1', 'password_2'])
|
||||
def post_register():
|
||||
if session_manager.get(request):
|
||||
flask.abort(403, 'You\'re already signed in.')
|
||||
|
||||
username = request.form['username']
|
||||
password_1 = request.form['password_1']
|
||||
password_2 = request.form['password_2']
|
||||
|
||||
if password_1 != password_2:
|
||||
flask.abort(422, 'Passwords do not match.')
|
||||
|
||||
try:
|
||||
user = P.register_user(username, password_1)
|
||||
except exceptions.UsernameTooShort as e:
|
||||
flask.abort(422, 'Username shorter than minimum of %d' % P.config['min_username_length'])
|
||||
except exceptions.UsernameTooLong as e:
|
||||
flask.abort(422, 'Username longer than maximum of %d' % P.config['max_username_length'])
|
||||
except exceptions.InvalidUsernameChars as e:
|
||||
flask.abort(422, 'Username contains invalid characters %s' % e.args[0])
|
||||
except exceptions.PasswordTooShort as e:
|
||||
flask.abort(422, 'Password is shorter than minimum of %d' % P.config['min_password_length'])
|
||||
except exceptions.UserExists as e:
|
||||
flask.abort(422, 'User %s already exists' % e.args[0])
|
||||
|
||||
session = sessions.Session(request, user)
|
||||
session_manager.add(session)
|
||||
response = flask.Response('redirect', status=302, headers={'Location': '/'})
|
||||
return response
|
||||
|
||||
|
||||
@site.route('/logout', methods=['GET', 'POST'])
|
||||
@session_manager.give_token
|
||||
def logout():
|
||||
session_manager.remove(request)
|
||||
response = flask.Response('redirect', status=302, headers={'Location': back_url()})
|
||||
return response
|
||||
|
||||
|
||||
@site.route('/favicon.ico')
|
||||
|
@ -182,22 +252,24 @@ def get_album_core(albumid):
|
|||
return album
|
||||
|
||||
@site.route('/album/<albumid>')
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_album_html(albumid):
|
||||
album = get_album_core(albumid)
|
||||
session = session_manager.get(request)
|
||||
response = flask.render_template(
|
||||
'album.html',
|
||||
album=album,
|
||||
photos=album['photos'],
|
||||
session=session,
|
||||
view=request.args.get('view', 'grid'),
|
||||
)
|
||||
return response
|
||||
|
||||
@site.route('/album/<albumid>.json')
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_album_json(albumid):
|
||||
album = get_album_core(albumid)
|
||||
return make_json_response(album)
|
||||
return jsonify.make_json_response(album)
|
||||
|
||||
|
||||
@site.route('/album/<albumid>.tar')
|
||||
|
@ -218,21 +290,24 @@ def get_albums_core():
|
|||
return albums
|
||||
|
||||
@site.route('/albums')
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_albums_html():
|
||||
albums = get_albums_core()
|
||||
return flask.render_template('albums.html', albums=albums)
|
||||
session = session_manager.get(request)
|
||||
return flask.render_template('albums.html', albums=albums, session=session)
|
||||
|
||||
@site.route('/albums.json')
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_albums_json():
|
||||
albums = get_albums_core()
|
||||
return make_json_response(albums)
|
||||
return jsonify.make_json_response(albums)
|
||||
|
||||
|
||||
@site.route('/bookmarks')
|
||||
@session_manager.give_token
|
||||
def get_bookmarks():
|
||||
return flask.render_template('bookmarks.html')
|
||||
session = session_manager.get(request)
|
||||
return flask.render_template('bookmarks.html', session=session)
|
||||
|
||||
|
||||
@site.route('/file/<photoid>')
|
||||
|
@ -268,20 +343,20 @@ def get_photo_core(photoid):
|
|||
return photo
|
||||
|
||||
@site.route('/photo/<photoid>', methods=['GET'])
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_photo_html(photoid):
|
||||
photo = get_photo_core(photoid)
|
||||
photo['tags'].sort(key=lambda x: x['qualified_name'])
|
||||
return flask.render_template('photo.html', photo=photo)
|
||||
session = session_manager.get(request)
|
||||
return flask.render_template('photo.html', photo=photo, session=session)
|
||||
|
||||
@site.route('/photo/<photoid>.json', methods=['GET'])
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_photo_json(photoid):
|
||||
photo = get_photo_core(photoid)
|
||||
photo = make_json_response(photo)
|
||||
photo = jsonify.make_json_response(photo)
|
||||
return photo
|
||||
|
||||
|
||||
def get_search_core():
|
||||
#print(request.args)
|
||||
|
||||
|
@ -418,11 +493,12 @@ def get_search_core():
|
|||
return final_results
|
||||
|
||||
@site.route('/search')
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_search_html():
|
||||
search_results = get_search_core()
|
||||
search_kwargs = search_results['search_kwargs']
|
||||
qualname_map = search_results['qualname_map']
|
||||
session = session_manager.get(request)
|
||||
response = flask.render_template(
|
||||
'search.html',
|
||||
next_page_url=search_results['next_page_url'],
|
||||
|
@ -430,13 +506,14 @@ def get_search_html():
|
|||
photos=search_results['photos'],
|
||||
qualname_map=json.dumps(qualname_map),
|
||||
search_kwargs=search_kwargs,
|
||||
session=session,
|
||||
total_tags=search_results['total_tags'],
|
||||
warns=search_results['warns'],
|
||||
)
|
||||
return response
|
||||
|
||||
@site.route('/search.json')
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_search_json():
|
||||
search_results = get_search_core()
|
||||
#search_kwargs = search_results['search_kwargs']
|
||||
|
@ -445,7 +522,7 @@ def get_search_json():
|
|||
include_qualname_map = helpers.truthystring(include_qualname_map)
|
||||
if not include_qualname_map:
|
||||
search_results.pop('qualname_map')
|
||||
return make_json_response(search_results)
|
||||
return jsonify.make_json_response(search_results)
|
||||
|
||||
|
||||
@site.route('/static/<filename>')
|
||||
|
@ -468,18 +545,19 @@ def get_tags_core(specific_tag=None):
|
|||
|
||||
@site.route('/tags')
|
||||
@site.route('/tags/<specific_tag>')
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_tags_html(specific_tag=None):
|
||||
tags = get_tags_core(specific_tag)
|
||||
return flask.render_template('tags.html', tags=tags)
|
||||
session = session_manager.get(request)
|
||||
return flask.render_template('tags.html', tags=tags, session=session)
|
||||
|
||||
@site.route('/tags.json')
|
||||
@site.route('/tags/<specific_tag>.json')
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def get_tags_json(specific_tag=None):
|
||||
tags = get_tags_core(specific_tag)
|
||||
tags = [t[0] for t in tags]
|
||||
return make_json_response(tags)
|
||||
return jsonify.make_json_response(tags)
|
||||
|
||||
|
||||
@site.route('/thumbnail/<photoid>')
|
||||
|
@ -495,7 +573,7 @@ def get_thumbnail(photoid):
|
|||
|
||||
@site.route('/album/<albumid>', methods=['POST'])
|
||||
@site.route('/album/<albumid>.json', methods=['POST'])
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def post_edit_album(albumid):
|
||||
'''
|
||||
Edit the album's title and description.
|
||||
|
@ -512,18 +590,18 @@ def post_edit_album(albumid):
|
|||
tag = P_tag(tag)
|
||||
except exceptions.NoSuchTag:
|
||||
response = {'error': 'That tag doesnt exist', 'tagname': tag}
|
||||
return make_json_response(response, status=404)
|
||||
return jsonify.make_json_response(response, status=404)
|
||||
recursive = request.form.get('recursive', False)
|
||||
recursive = helpers.truthystring(recursive)
|
||||
album.add_tag_to_all(tag, nested_children=recursive)
|
||||
response['action'] = action
|
||||
response['tagname'] = tag.name
|
||||
return make_json_response(response)
|
||||
return jsonify.make_json_response(response)
|
||||
|
||||
|
||||
@site.route('/photo/<photoid>', methods=['POST'])
|
||||
@site.route('/photo/<photoid>.json', methods=['POST'])
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def post_edit_photo(photoid):
|
||||
'''
|
||||
Add and remove tags from photos.
|
||||
|
@ -548,22 +626,22 @@ def post_edit_photo(photoid):
|
|||
tag = P.get_tag(tag)
|
||||
except exceptions.NoSuchTag:
|
||||
response = {'error': 'That tag doesnt exist', 'tagname': tag}
|
||||
return make_json_response(response, status=404)
|
||||
return jsonify.make_json_response(response, status=404)
|
||||
|
||||
method(tag)
|
||||
response['action'] = action
|
||||
#response['tagid'] = tag.id
|
||||
response['tagname'] = tag.name
|
||||
return make_json_response(response)
|
||||
return jsonify.make_json_response(response)
|
||||
|
||||
|
||||
@site.route('/tags', methods=['POST'])
|
||||
@decorators.give_session_token
|
||||
@session_manager.give_token
|
||||
def post_edit_tags():
|
||||
'''
|
||||
Create and delete tags and synonyms.
|
||||
'''
|
||||
print(request.form)
|
||||
#print(request.form)
|
||||
status = 200
|
||||
if 'create_tag' in request.form:
|
||||
action = 'create_tag'
|
||||
|
@ -605,6 +683,13 @@ def post_edit_tags():
|
|||
return response
|
||||
|
||||
|
||||
@site.route('/apitest')
|
||||
@session_manager.give_token
|
||||
def apitest():
|
||||
response = flask.Response('testing')
|
||||
response.set_cookie('etiquette_session', 'don\'t overwrite me')
|
||||
return response
|
||||
|
||||
if __name__ == '__main__':
|
||||
#site.run(threaded=True)
|
||||
pass
|
||||
|
|
|
@ -2,10 +2,10 @@ import datetime
|
|||
import math
|
||||
import mimetypes
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import constants
|
||||
import exceptions
|
||||
import warnings
|
||||
|
||||
from voussoirkit import bytestring
|
||||
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import flask
|
||||
import helpers
|
||||
import json
|
||||
|
||||
def make_json_response(j, *args, **kwargs):
|
||||
dumped = json.dumps(j)
|
||||
response = flask.Response(dumped, *args, **kwargs)
|
||||
response.headers['Content-Type'] = 'application/json;charset=utf-8'
|
||||
return response
|
||||
|
||||
def album(a, minimal=False):
|
||||
j = {
|
||||
|
|
79
sessions.py
Normal file
79
sessions.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
import flask
|
||||
from flask import request
|
||||
import functools
|
||||
import helpers
|
||||
import uuid
|
||||
|
||||
def _generate_token():
|
||||
token = str(uuid.uuid4())
|
||||
#print('MAKE SESSION', token)
|
||||
return token
|
||||
|
||||
def _normalize_token(token):
|
||||
if isinstance(token, flask.Request):
|
||||
token = token.cookies.get('etiquette_session', None)
|
||||
|
||||
class SessionManager:
|
||||
def __init__(self):
|
||||
self.sessions = {}
|
||||
|
||||
def add(self, session):
|
||||
self.sessions[session.token] = session
|
||||
|
||||
def get(self, token):
|
||||
token = _normalize_token(token)
|
||||
return self.sessions.get(token, None)
|
||||
|
||||
def give_token(self, function):
|
||||
'''
|
||||
This decorator ensures that the user has an `etiquette_session` cookie
|
||||
before reaching the request handler.
|
||||
If the user does not have the cookie, they are given one.
|
||||
If they do, its lifespan is reset.
|
||||
'''
|
||||
@functools.wraps(function)
|
||||
def wrapped(*args, **kwargs):
|
||||
# Inject new token so the function doesn't know the difference
|
||||
token = request.cookies.get('etiquette_session', None)
|
||||
if not token:
|
||||
token = _generate_token()
|
||||
request.cookies = dict(request.cookies)
|
||||
request.cookies['etiquette_session'] = token
|
||||
|
||||
response = function(*args, **kwargs)
|
||||
|
||||
if not isinstance(response, flask.Response):
|
||||
response = flask.Response(response)
|
||||
|
||||
# Send the token back to the client
|
||||
# but only if the endpoint didn't manually set the cookie.
|
||||
for (headerkey, value) in response.headers:
|
||||
if headerkey == 'Set-Cookie' and value.startswith('etiquette_session='):
|
||||
break
|
||||
else:
|
||||
response.set_cookie('etiquette_session', value=token, max_age=86400)
|
||||
self.maintain(token)
|
||||
|
||||
return response
|
||||
return wrapped
|
||||
|
||||
def maintain(self, token):
|
||||
session = self.get(token)
|
||||
if session:
|
||||
session.maintain()
|
||||
|
||||
def remove(self, token):
|
||||
token = _normalize_token(token)
|
||||
if token in self.sessions:
|
||||
self.sessions.pop(token)
|
||||
|
||||
class Session:
|
||||
def __init__(self, request, user):
|
||||
self.token = _normalize_token(request)
|
||||
self.user = user
|
||||
self.ip_address = request.remote_addr
|
||||
self.user_agent = request.headers.get('User-Agent', '')
|
||||
self.last_activity = int(helpers.now())
|
||||
|
||||
def maintain(self):
|
||||
self.last_activity = int(helpers.now())
|
|
@ -22,7 +22,7 @@ p
|
|||
|
||||
|
||||
<body>
|
||||
{{header.make_header()}}
|
||||
{{header.make_header(session=session)}}
|
||||
<div id="content_body">
|
||||
<h2>{{album["title"]}}</h2>
|
||||
<p>{{album["description"]}}</p>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
|
||||
<body>
|
||||
{{header.make_header()}}
|
||||
{{header.make_header(session=session)}}
|
||||
<div id="content_body">
|
||||
{% for album in albums %}
|
||||
{% if album["title"] %}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
|
||||
<body>
|
||||
{{header.make_header()}}
|
||||
{{header.make_header(session=session)}}
|
||||
<div id="content_body">
|
||||
<a href="/search?has_tags=no&orderby=random-desc&mimetype=image">Needs tagging</a>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
{% macro make_header() %}
|
||||
{% macro make_header(session) %}
|
||||
<div id="header">
|
||||
<a class="header_element" href="/">Etiquette</a>
|
||||
<a class="header_element" href="/search">Search</a>
|
||||
<a class="header_element" href="/tags">Tags</a>
|
||||
{% if session %}
|
||||
<a class="header_element" href="/user/{{session.user.username}}">{{session.user.username}}</a>
|
||||
<a class="header_element" href="/logout" style="flex:0">Logout</a>
|
||||
{% else %}
|
||||
<a class="header_element" href="/login">Log in</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
74
templates/login.html
Normal file
74
templates/login.html
Normal file
|
@ -0,0 +1,74 @@
|
|||
<!DOCTYPE html5>
|
||||
<html>
|
||||
<head>
|
||||
{% import "header.html" as header %}
|
||||
<title>Login/Register</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="/static/common.css">
|
||||
<script src="/static/common.js"></script>
|
||||
|
||||
<style>
|
||||
#content_body
|
||||
{
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
#login_register_box
|
||||
{
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
form
|
||||
{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 25px;
|
||||
margin: 25px;
|
||||
border: 1px black solid;
|
||||
border-radius: 6px;
|
||||
}
|
||||
form > *
|
||||
{
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
input
|
||||
{
|
||||
width: 300px;
|
||||
}
|
||||
button
|
||||
{
|
||||
width: 80px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
{{header.make_header(session=session)}}
|
||||
<div id="content_body">
|
||||
<div id="login_register_box">
|
||||
<form id="login_form" action="/login" method="post">
|
||||
<span>Log in</span>
|
||||
<input type="text" name="username" placeholder="username">
|
||||
<input type="password" name="password" placeholder="password">
|
||||
<button type="submit">Log in</button>
|
||||
</form>
|
||||
<form id="register_form" action="/register" method="post">
|
||||
<span>Register</span>
|
||||
<input type="text" name="username" placeholder="username">
|
||||
<input type="password" name="password_1" placeholder="password">
|
||||
<input type="password" name="password_2" placeholder="password again">
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
</html>
|
|
@ -87,7 +87,7 @@
|
|||
|
||||
|
||||
<body>
|
||||
{{header.make_header()}}
|
||||
{{header.make_header(session=session)}}
|
||||
<div id="content_body">
|
||||
<div id="left">
|
||||
<div id="editor_area">
|
||||
|
|
|
@ -34,6 +34,11 @@ a:hover
|
|||
<a href="/tags">Browse tags</a>
|
||||
<a href="/albums">Browse albums</a>
|
||||
<a href="/bookmarks">Bookmarks</a>
|
||||
{% if session %}
|
||||
<a href="/user/{{session.user.username}}">{{session.user.username}}</a>
|
||||
{% else %}
|
||||
<a href="/login">Log in</a>
|
||||
{% endif %}
|
||||
</body>
|
||||
|
||||
|
||||
|
|
|
@ -119,7 +119,7 @@ form
|
|||
|
||||
|
||||
<body>
|
||||
{{header.make_header()}}
|
||||
{{header.make_header(session=session)}}
|
||||
<div id="error_message_area">
|
||||
{% for warn in warns %}
|
||||
<span class="search_warning">{{warn}}</span>
|
||||
|
|
|
@ -59,7 +59,7 @@ body
|
|||
|
||||
|
||||
<body>
|
||||
{{header.make_header()}}
|
||||
{{header.make_header(session=session)}}
|
||||
<div id="content_body">
|
||||
<div id="left">
|
||||
<ul>
|
||||
|
|
Loading…
Reference in a new issue