diff --git a/answer.py b/answer.py new file mode 100644 index 0000000..1e59ff3 --- /dev/null +++ b/answer.py @@ -0,0 +1,71 @@ + + +class Answer: + def __init__(self, request, start_response): + self.request = request + self.urlprefix = self.request.server.config['urlprefix'] + self.urldataprefix = self.request.server.config['urldataprefix'] + self.start_response = start_response + + self.status = "200 OK" + self.headers = [] + self.datalist = None + + self.data = None + self.text = '' + self.html = '' + + self.title = '' + self.content = '' + self.template = None + + def translate(self, text): + return text + + def t(self, text): + return self.translate(text) + + def chain_title(subtitle): + if subtitle: + if self.title: + self.title = subtitle + ' | ' + self.title + else: + self.title = subtitle + + def complete_data(self, data = None): + if not data is None: + self.data = data + if self.data is None: + self.data = bytes() + if not type(self.data) is bytes: + self.data = bytes(str(self.data), "utf8") + if not self.data and self.status == "200 OK": + self.status = "204 No Content" + + size = 65536 + self.datalist = [] + for i in range(0, len(self.data), size): + self.datalist.append(self.data[i:i + size]) + self.start_response(self.status, self.headers) + return self.datalist + + def complete_text(self, text = None): + if not text is None: + self.text = text + if self.text is None: + self.text = "" + return self.complete_data(text) + + def complete_html(self, html = None): + if not html is None: + self.html = html + self.headers += [('Content-Type','text/html')]; + return self.complete_text( self.html ) + + def complete_content(self, content = None, template = None): + if not content is None: + self.content = content + if not template is None: + self.template = template + return self.complete_html( self.template.wrap(self) if self.template else self.content ) + diff --git a/db/connection.py b/db/connection.py index edcf8eb..318a6c0 100644 --- a/db/connection.py +++ b/db/connection.py @@ -44,7 +44,7 @@ class Cursor: data.append(t.from_db(self, v) if t else v) return data - def execute(self, sql = None, *args, **kvargs): + def execute(self, sql, *args, **kvargs): if args or kvargs: sql = parse(self.typebychar, self.connection, sql, *args, **kvargs) self.get_internal().execute(sql) @@ -53,24 +53,26 @@ class Cursor: class Connection: def __init__(self, pool, internal, readonly = True): self.pool = pool + self.request = None self.internal = internal self.readonly = readonly self.finished = True self.begin() - def cursor(self, as_dict = False): + def cursor(self, as_dict = False, sql = None, *args, **kvargs): assert not self.finished cursorclass = MySQLdb.cursors.DictCursor if as_dict else MySQLdb.cursors.Cursor cursor = Cursor(self, self.internal.cursor(cursorclass)) + if sql: + cursor.execute(sql, *args, **kvargs) return cursor - def cursor_list(self): - return self.cursor(False) - def cursor_dict(self): - return self.cursor(True) + def cursor_list(self, sql = None, *args, **kvargs): + return self.cursor(False, sql, *args, **kvargs) + def cursor_dict(self, sql = None, *args, **kvargs): + return self.cursor(True, sql, *args, **kvargs) def query(self, as_dict, sql = None, *args, **kvargs): - with self.cursor(as_dict) as cursor: - cursor.execute(sql, *args, **kvargs) + with self.cursor(as_dict, sql, *args, **kvargs) as cursor: return list(cursor) def query_list(self, sql = None, *args, **kvargs): return self.query(False, sql, *args, **kvargs) @@ -78,12 +80,12 @@ class Connection: return self.query(True, sql, *args, **kvargs) def execute(self, sql = None, *args, **kvargs): - with self.cursor() as cursor: - cursor.execute(sql, *args, **kvargs) + with self.cursor(False, sql, *args, **kvargs): + return - def insert_id(self, *args, **kwargs): + def insert_id(self): assert not self.finished - return self.internal.insert_id(*args, **kwargs) + return self.internal.insert_id() def escape(self, *args, **kwargs): r = self.internal.escape(*args, **kwargs) diff --git a/deps.txt b/deps.txt index 91e7eb3..609ac52 100644 --- a/deps.txt +++ b/deps.txt @@ -1,5 +1,6 @@ datetime +hashlib threading time traceback diff --git a/exception.py b/exception.py new file mode 100644 index 0000000..3ded841 --- /dev/null +++ b/exception.py @@ -0,0 +1,7 @@ + + +class HttpNotFound(Exception): + pass + +class ModelNotFound(Exception): + pass diff --git a/main.py b/main.py index 082ca45..58b36c8 100644 --- a/main.py +++ b/main.py @@ -2,31 +2,20 @@ from config import config from server import Server from request import Request -from db.holder import Holder as ConnectionHolder - -import template.common +import db.holder +import exception server = Server(config) def application(env, start_response): - request = Request(server, env, start_response) - readonly = request.method == 'GET' - with ConnectionHolder(request.server.dbpool, readonly = readonly) as conn: - request.connection = conn - - request.template = template.common.instance - - content = request.t("Hello World!") - - content += '

' + "Env:\n" + str(env) + '

' - - tables = list(v[0] for v in request.connection.query_list('SHOW TABLES')) - content += '

DB tables: ' + ', '.join(tables) + '

' - - rows = request.connection.query_dict('SELECT * FROM %T', 'test') - content += '

Rows of test table: ' + str(rows) + '

' - - return request.complete_content(content) - + try: + request = Request(server, env, start_response) + if request.path is None: + raise exception.HttpNotFound() + with db.holder.Holder(request.server.dbpool, readonly = request.readonly) as connection: + request.connection = connection + return request.server.pageroot.process(request, request.path) + except exception.HttpNotFound: + return request.server.pagenotfound.process(request, list()) diff --git a/model/user.py b/model/user.py new file mode 100644 index 0000000..9b1841e --- /dev/null +++ b/model/user.py @@ -0,0 +1,87 @@ + + +import hashlib + +import exception + + +class User: + table = 'users' + + def __init__(self, connection, data): + self.connection = connection + self.id = data['id'] + self.login = data['login'] + self.password = data['password'] + self.name = data['name'] + self.email = data['email'] + + @staticmethod + def query(connection, id): + rows = connection.query_dict('SELECT * FROM %T WHERE `id`=%d', table, id) + assert len(rows) <= 1 + return User(connection, rows[0]) if rows else None; + + @staticmethod + def query_by_login(connection, login): + rows = connection.query_dict('SELECT * FROM %T WHERE `login`=%s', table, login) + assert len(rows) <= 1 + return User(connection, rows[0]) if rows else None; + + @staticmethod + def query_list(connection): + result = list() + with connection.cursor_dict('SELECT * FROM %T ORDER BY `login`', table) as cursor: + for row in cursor: + result.append(User(connection, cursor)) + return result + + + def insert(self, connection): + assert not self.id + connection.execute( + '''INSERT INTO %T SET + `login` = %s, + `name` = %s, + `email` = %s''', + table, + self.login, + self.name, + self.email ) + self.id = self.connection.insert_id() + + def update(self, connection): + assert self.id + connection.execute( + 'UPDATE SET %T `name` = %s, `email` = %s WHERE `id` = %d', + table, self.name, self.email, self.id ) + + @staticmethod + def gen_password_hash(salt, id, plain_password): + return hashlib.sha512(bytes(str(user_id) + '|' + str(salt) + '|' + password, 'utf8')).hexdigest() + + def password_hash(self, plain_password): + assert self.id + return gen_password_hash(self.connection.pool.server.salt, self.id, password) + + @staticmethod + def resetpassword(connection, id, password): + connection.execute( + 'UPDATE %T SET `password` = %s WHERE `id` = %d', + table, password, id ) + if not connection.request \ + or not connection.request.session \ + or connection.request.session.user.id != id: + connection.pool.server.remove_session_for_user(id) + + def update_password(self, connection, password = None): + assert self.id + if not password is None: + self.password = password + resetpassword(self.connection, self.id, self.password) + + def delete(self, connection): + assert self.id + connection.execute('DELETE FROM %T WHERE `id`=%d', table, self.id) + + diff --git a/request.py b/request.py index ea7e913..7ea9126 100644 --- a/request.py +++ b/request.py @@ -1,68 +1,24 @@ + +import answer + class Request: - def __init__( - self, - server, - environ, - start_response - ): + def __init__(self, server, env, start_response): self.server = server - self.environ = environ - self.start_response = start_response - self.method = str(self.environ["REQUEST_METHOD"]) + self.env = env + self.method = str(self.env["REQUEST_METHOD"]) assert self.method == 'GET' or self.method == 'POST' + self.readonly = self.method == 'GET' + + self.path = None + prefix = self.server.config['urlprefix'] + path = self.env["REQUEST_URI"] + if path.startswith(prefix): + path = path[len(prefix):] + self.path = [x for x in path.split('/') if x] self.connection = None self.session = None - self.template = None - self.status = "200 OK" - self.headers = [] - self.title = '' - - def translate(self, text): - return text - - def t(self, text): - return self.translate(text) - - def chain_template(subtemplate): - subtemplate.parent = self.template - self.template = subtemplate - - def chain_title(subtitle): - if subtitle: - if self.title: - self.title = subtitle + ' | ' + self.title - else: - self.title = subtitle + self.answer = answer.Answer(self, start_response) - def complete_data(self, data): - if data is None: - data = bytes() - if not type(data) is bytes: - data = bytes(str(data), "utf8") - if not data and self.status == "200 OK": - self.status = "204 No Content" - - size = 65536 - result_list = [] - for i in range(0, len(data), size): - result_list.append(data[i:i + size]) - data = None - - self.start_response(self.status, self.headers) - return result_list - - def complete_text(self, text): - if text is None: text = "" - return self.complete_data(text) - - def complete_html(self, html): - self.headers += [('Content-Type','text/html')]; - return self.complete_text(html) - - def complete_content(self, content): - if self.template: - content = self.template.wrap(self, content) - return self.complete_html(content) diff --git a/server.py b/server.py index 50a2e0f..33507da 100644 --- a/server.py +++ b/server.py @@ -1,12 +1,17 @@ import datetime -from db.types import bytype, bychar -from db.pool import Pool + +import db.types +import db.pool +import view.root +import view.error class Server: def __init__(self, config): urlprefix = str(config.get('urlprefix', '')) + while urlprefix[-1:] == '/': + urlprefix = urlprefix[:-1] config_db = config.get('db', dict()) config_db_pool = config_db.get('pool', dict()) @@ -32,8 +37,9 @@ class Server: assert self.config['db']['pool']['read'] > 0 assert self.config['db']['pool']['write'] > 0 - self.dbtypebytype = bytype - self.dbtypebychar = bychar - self.dbpool = Pool(self) + self.dbtypebytype = db.types.bytype + self.dbtypebychar = db.types.bychar + self.dbpool = db.pool.Pool(self) - + self.pageroot = view.root.RootPage() + self.pagenotfound = view.error.ErrorPage('404', 'Page Not Found') diff --git a/template/common.py b/template/common.py index c3fa52d..d457ddc 100644 --- a/template/common.py +++ b/template/common.py @@ -1,11 +1,15 @@ from template.template import Template -import template.login as login -import template.usermenu as usermenu +from template.login import LoginTemplate +from template.usermenu import UsermenuTemplate -class Common(Template): - def wrapfunc(self, request, content): +class CommonTemplate(Template): + def __init__(self): + self.login = LoginTemplate() + self.usermenu = UsermenuTemplate() + + def wrap(self, answer): return ''' @@ -26,11 +30,8 @@ class Common(Template): ''' % { - 'title' : request.title, - 'dataprefix' : request.server.config['urldataprefix'], - 'usermenu' : (usermenu.instance if request.session else login.instance).wrap(request, content), - 'content' : content, + 'title' : answer.title, + 'dataprefix' : answer.urldataprefix, + 'usermenu' : (self.usermenu if answer.request.session else self.login).wrap(answer), + 'content' : answer.content, } - - -instance = Common() diff --git a/template/login.py b/template/login.py index 0aab657..9de17d4 100644 --- a/template/login.py +++ b/template/login.py @@ -2,22 +2,20 @@ from template.template import Template class LoginTemplate(Template): - def wrapfunc(self, request, content): + def wrap(self, answer): return '''
- ''' + request.t('Username:') + ''' + ''' + answer.t('Username:') + ''' - ''' + request.t('Password:') + ''' + ''' + answer.t('Password:') + ''' - +
''' - -instance = LoginTemplate() diff --git a/template/template.py b/template/template.py index 220e169..02f3da0 100644 --- a/template/template.py +++ b/template/template.py @@ -1,15 +1,5 @@ class Template: - def __init__(self): - self.parent = None - - def wrap(self, request, content): - content = self.wrapfunc(request, content) - if not self.parent: - return content - return self.parent(request, content) + def wrap(self, answer): + return answer.content - def wrapfunc(self, request, content): - return content - -instance = Template() diff --git a/template/usermenu.py b/template/usermenu.py index 4bcf9aa..886ef1c 100644 --- a/template/usermenu.py +++ b/template/usermenu.py @@ -2,10 +2,8 @@ from template.template import Template class UsermenuTemplate(Template): - def wrapfunc(self, request, content): + def wrap(self, answer): return '''
''' - -instance = UsermenuTemplate() diff --git a/view/error.py b/view/error.py new file mode 100644 index 0000000..a23e834 --- /dev/null +++ b/view/error.py @@ -0,0 +1,20 @@ + + +from view.page import Page +from template.common import CommonTemplate + + +class ErrorPage(Page): + def __init__(self, code, message): + self.code = code + self.message = message + self.template = CommonTemplate() + + def process(self, request, path): + answer = request.answer + answer.status = self.code + ' ' + self.message + answer.template = self.template + answer.content += '

' + self.code + '

' + answer.content += '

' + answer.t(self.message) + '

' + return answer.complete_content() + diff --git a/view/page.py b/view/page.py new file mode 100644 index 0000000..0a395dc --- /dev/null +++ b/view/page.py @@ -0,0 +1,8 @@ + +import exception + + +class Page: + def process(self, request, path): + raise exception.HttpNotFound + diff --git a/view/root.py b/view/root.py new file mode 100644 index 0000000..4d0e792 --- /dev/null +++ b/view/root.py @@ -0,0 +1,40 @@ + + +import exception + +from view.page import Page +from view.user import UserPage +from view.users import UsersPage + +from template.common import CommonTemplate + + +class RootPage(Page): + def __init__(self): + self.template = CommonTemplate() + self.user = UserPage() + self.users = UsersPage() + + def process(self, request, path): + answer = request.answer + answer.template = self.template + + if request.path: + if request.path[0] == 'user': + return self.user.process(request, path[1:]) + if request.path[0] == 'users': + return self.users.process(request, path[1:]) + raise exception.HttpNotFound() + + answer.content += '

' + answer.t("root page") + '

' + answer.content += '

' + answer.t("Welcome!") + '

' + answer.content += '

' + "Env:\n" + str(request.env) + '

' + + tables = list(v[0] for v in request.connection.query_list('SHOW TABLES')) + answer.content += '

DB tables: ' + ', '.join(tables) + '

' + + rows = request.connection.query_dict('SELECT * FROM %T', 'test') + answer.content += '

Rows of test table: ' + str(rows) + '

' + + return answer.complete_content() + diff --git a/view/user.py b/view/user.py new file mode 100644 index 0000000..3468b0c --- /dev/null +++ b/view/user.py @@ -0,0 +1,7 @@ + + +from view.page import Page + + +class UserPage(Page): + pass diff --git a/view/users.py b/view/users.py new file mode 100644 index 0000000..3e9484a --- /dev/null +++ b/view/users.py @@ -0,0 +1,8 @@ + + +from view.page import Page + + +class UsersPage(Page): + pass +