From 2305ced85888a23f86ecfcdfb64a3b69c4997a4c Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 21 Jul 2019 12:14:09 +0400 Subject: =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=20=D0=B1=D0=B5=D0=BA=D0=B5=D0=BD=D0=B4.=20=D0=94=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B4=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF=D1=80=D0=B8=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=B7?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B2=20=D1=80?= =?UTF-8?q?=D1=8F=D0=B4=D1=83.=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=80=D0=B5=D0=B4=D1=8B=D0=B4=D1=83=D1=89=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=20vue.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- day9/task5_vue/backend/core.py | 82 ---------- day9/task5_vue/backend/core/response_types.py | 82 ++++++++++ day9/task5_vue/backend/core/router.py | 49 ++++++ day9/task5_vue/backend/core/server.py | 65 ++++++++ day9/task5_vue/backend/core/utils.py | 131 +++++++++++++++ day9/task5_vue/backend/database.py | 43 ----- day9/task5_vue/backend/database/database.py | 29 ++++ day9/task5_vue/backend/database/field_types.py | 128 +++++++++++++++ day9/task5_vue/backend/database/scheme.py | 46 ++++++ day9/task5_vue/backend/database/validators.py | 96 +++++++++++ day9/task5_vue/backend/database/wrappers.py | 149 +++++++++++++++++ day9/task5_vue/backend/router.py | 41 ----- day9/task5_vue/backend/routes.py | 15 ++ day9/task5_vue/backend/schemes.py | 23 +++ day9/task5_vue/backend/server.py | 73 --------- day9/task5_vue/backend/settings.py | 18 +++ day9/task5_vue/backend/utils.py | 131 --------------- day9/task5_vue/backend/views.py | 160 ++++++++++++++++++ day9/task5_vue/backend_serve.py | 214 ------------------------- day9/task5_vue/package-lock.json | 2 +- day9/task5_vue/package.json | 2 +- day9/task5_vue/runserver.py | 27 ++++ day9/task5_vue/src/components/EditFormBox.vue | 104 ++++++++++-- 23 files changed, 1107 insertions(+), 603 deletions(-) delete mode 100644 day9/task5_vue/backend/core.py create mode 100644 day9/task5_vue/backend/core/response_types.py create mode 100644 day9/task5_vue/backend/core/router.py create mode 100644 day9/task5_vue/backend/core/server.py create mode 100644 day9/task5_vue/backend/core/utils.py delete mode 100644 day9/task5_vue/backend/database.py create mode 100644 day9/task5_vue/backend/database/database.py create mode 100644 day9/task5_vue/backend/database/field_types.py create mode 100644 day9/task5_vue/backend/database/scheme.py create mode 100644 day9/task5_vue/backend/database/validators.py create mode 100644 day9/task5_vue/backend/database/wrappers.py delete mode 100644 day9/task5_vue/backend/router.py create mode 100644 day9/task5_vue/backend/routes.py create mode 100644 day9/task5_vue/backend/schemes.py delete mode 100644 day9/task5_vue/backend/server.py create mode 100644 day9/task5_vue/backend/settings.py delete mode 100644 day9/task5_vue/backend/utils.py create mode 100644 day9/task5_vue/backend/views.py delete mode 100644 day9/task5_vue/backend_serve.py create mode 100644 day9/task5_vue/runserver.py (limited to 'day9/task5_vue') diff --git a/day9/task5_vue/backend/core.py b/day9/task5_vue/backend/core.py deleted file mode 100644 index 71c6d5a..0000000 --- a/day9/task5_vue/backend/core.py +++ /dev/null @@ -1,82 +0,0 @@ -from abc import ABC, abstractmethod -import json - -from backend.utils import HTTP_STATUS_CODES - - -class Response(ABC): - @property - def status_code(self) -> int: - """ - По дефолту возвращается статус 200 - """ - return 200 - - @property - @abstractmethod - def content_type(self) -> str: - pass - - @property - @abstractmethod - def content(self) -> bytes: - pass - - -class HtmlResponse(Response): - def __init__(self, html, status_code=200): - self._html: str = html - self._code = status_code - - @property - def status_code(self) -> int: - return self._code - - @property - def content_type(self) -> str: - return 'text/html' - - @property - def content(self) -> bytes: - return self._html.encode() - - -class TextFileResponse(Response): - def __init__(self, path, extension): - with open(path, 'rb') as f: - self._content = f.read() - - self._extension = extension - - @property - def content_type(self) -> str: - return f'text/{self._extension}' - - @property - def content(self) -> bytes: - return self._content - - -class ImageResponse(TextFileResponse): - @property - def content_type(self) -> str: - return f'image/{self._extension}' - - -class JsonResponse(Response): - def __init__(self, json_object): - self._json_str = json.dumps(json_object, ensure_ascii=False) - - @property - def content_type(self) -> str: - return 'application/json' - - @property - def content(self) -> bytes: - return self._json_str.encode() - - -class ErrorResponse(HtmlResponse): - def __init__(self, http_code, message=''): - html = f'

ERROR {http_code} {HTTP_STATUS_CODES[http_code].upper()}


{message}' - super().__init__(html, http_code) diff --git a/day9/task5_vue/backend/core/response_types.py b/day9/task5_vue/backend/core/response_types.py new file mode 100644 index 0000000..ca3a9f2 --- /dev/null +++ b/day9/task5_vue/backend/core/response_types.py @@ -0,0 +1,82 @@ +from abc import ABC, abstractmethod +import json + +from backend.core.utils import HTTP_STATUS_CODES + + +class Response(ABC): + @property + def status_code(self) -> int: + """ + По дефолту возвращается статус 200 + """ + return 200 + + @property + @abstractmethod + def content_type(self) -> str: + pass + + @property + @abstractmethod + def content(self) -> bytes: + pass + + +class HtmlResponse(Response): + def __init__(self, html, status_code=200): + self._html: str = html + self._code = status_code + + @property + def status_code(self) -> int: + return self._code + + @property + def content_type(self) -> str: + return 'text/html' + + @property + def content(self) -> bytes: + return self._html.encode() + + +class TextFileResponse(Response): + def __init__(self, path, extension): + with open(path, 'rb') as f: + self._content = f.read() + + self._extension = extension + + @property + def content_type(self) -> str: + return f'text/{self._extension}' + + @property + def content(self) -> bytes: + return self._content + + +class ImageResponse(TextFileResponse): + @property + def content_type(self) -> str: + return f'image/{self._extension}' + + +class JsonResponse(Response): + def __init__(self, json_object): + self._json_str = json.dumps(json_object, ensure_ascii=False) + + @property + def content_type(self) -> str: + return 'application/json' + + @property + def content(self) -> bytes: + return self._json_str.encode() + + +class ErrorResponse(HtmlResponse): + def __init__(self, http_code, message=''): + html = f'

ERROR {http_code} {HTTP_STATUS_CODES[http_code].upper()}


{message}' + super().__init__(html, http_code) diff --git a/day9/task5_vue/backend/core/router.py b/day9/task5_vue/backend/core/router.py new file mode 100644 index 0000000..135b241 --- /dev/null +++ b/day9/task5_vue/backend/core/router.py @@ -0,0 +1,49 @@ +import re +from backend.core.response_types import * +from backend.core.utils import NOT_FOUND_CODE + + +class ResolutionError(Exception): + pass + + +class Route: + """ + Представляет из себя класс роута. При инициализации принимает допустимые + методы и регулярное выражение. Содержит методы для работы роутинга. + Чтобы созданный путь стал обрабатываться необходимо поместить объект Route в таблицу роутера. + """ + + def __init__(self, callback, url_format, methods=None): + self.methods = ['GET'] if methods is None else methods + self.url_pattern = re.compile(url_format) + self.callback = callback + + def resolve(self, url, method, query): + match = self.url_pattern.fullmatch(url) + if match is not None: + groups = match.groups() + + matched = len(groups) == self.url_pattern.groups and method in self.methods + if not matched: + raise ResolutionError() + else: + raise ResolutionError() + + return self.callback(query, *match.groups()) + + +def run(request) -> Response: + response = ErrorResponse(NOT_FOUND_CODE) + + method, url = request['method'], request['url'] + for route in router_table: + try: + response = route.resolve(url, method, request['query']) + except ResolutionError: + continue + + return response + + +router_table = [] diff --git a/day9/task5_vue/backend/core/server.py b/day9/task5_vue/backend/core/server.py new file mode 100644 index 0000000..7c284c1 --- /dev/null +++ b/day9/task5_vue/backend/core/server.py @@ -0,0 +1,65 @@ +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import parse_qs + +from backend.core.router import run +from backend.core.utils import parse_multipart_form + +import logging +import json + + +class MyHTTPRequestHandler(BaseHTTPRequestHandler): + def _set_response(self, code, content_type): + self.send_response(code) + self.send_header('Content-type', content_type) + self.end_headers() + + def do_GET(self): + try: + content_length = int(self.headers['Content-Length']) + get_data = parse_qs(self.rfile.read(content_length).decode('utf-8')) + except TypeError: + get_data = {} + + for key in get_data: + get_data[key] = get_data[key][0] + + self.finalize_request(run({ + 'url': self.path, + 'method': 'GET', + 'query': get_data + })) + + def do_POST(self): + content_length = int(self.headers['Content-Length']) + content_type = self.headers['Content-type'] + post_data = self.rfile.read(content_length) + query = {} + + if content_type.split(';')[0] == 'multipart/form-data': + query = {'files': parse_multipart_form(post_data)} + + elif content_type.split(';')[0] in ['text/plain', 'application/x-www-form-urlencoded']: + query = parse_qs(post_data.decode('utf-8')) + for key in query: + query[key] = query[key][0] + + elif content_type.split(';')[0] == 'application/json': + query = json.loads(post_data.decode('utf-8')) + + self.finalize_request(run({ + 'url': self.path, + 'method': 'POST', + 'query': query + })) + + def finalize_request(self, response): + self._set_response(response.status_code, response.content_type) + self.wfile.write(response.content) + + +def start_server(host, port): + server_address = (host, port) + httpd = HTTPServer(server_address, MyHTTPRequestHandler) + logging.getLogger('tableApp').info(f'Server started on {host}:{port}') + httpd.serve_forever() diff --git a/day9/task5_vue/backend/core/utils.py b/day9/task5_vue/backend/core/utils.py new file mode 100644 index 0000000..c1b3a04 --- /dev/null +++ b/day9/task5_vue/backend/core/utils.py @@ -0,0 +1,131 @@ +from io import BytesIO + + +CHUNK_SIZE = 49600 + +HTTP_STATUS_CODES = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi Status", + 226: "IM Used", # see RFC 3229 + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", # unused + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", # see RFC 2324 + 421: "Misdirected Request", # see RFC 7540 + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 426: "Upgrade Required", + 428: "Precondition Required", # see RFC 6585 + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 449: "Retry With", # proprietary MS extension + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 507: "Insufficient Storage", + 510: "Not Extended", +} + +SUCCESS_CODE = 200 +BAD_REQUEST_CODE = 400 +NOT_FOUND_CODE = 404 +METHOD_NOT_ALLOWED_CODE = 405 + + +def render_template(path, **kwargs): + with open(path, encoding='utf-8') as f: + template = f.read() + + for key in kwargs: + template = template.replace(f'%%{key}%%', kwargs[key]) + + return template + + +def parse_multipart_form(data: bytes): + b = BytesIO(data) + separator = b.readline().strip() + + files = [] + + while True: + line = b.readline().strip().strip(b'-') + if not line: + break + + headers_raw = [] + while not line.strip() == b'': + headers_raw.append(line.strip().decode()) + line = b.readline() + + headers = {'Content-Type': headers_raw[1].split(': ')[1]} + for pair in headers_raw[0].split(': ')[1].split('; ')[1:]: + key, value = pair.split('=') + headers[key] = value[1:-1] + + data = b'' + prev_chunk = b.read(CHUNK_SIZE) + chunk = b.read(CHUNK_SIZE) + while separator not in (prev_chunk + chunk): + data += prev_chunk + prev_chunk = chunk + chunk = b.read(CHUNK_SIZE) + + if not chunk: + break + + chunk = (prev_chunk + chunk) + if chunk.startswith(separator) or chunk.endswith(separator): + with_sep = chunk.strip(separator) + else: + if separator in chunk: + with_sep, buffer = chunk.split(separator) + b = BytesIO(buffer + b.read()) + b.readline() + + else: + with_sep = chunk + + data += with_sep + headers['data'] = data + + files.append(headers) + + return files diff --git a/day9/task5_vue/backend/database.py b/day9/task5_vue/backend/database.py deleted file mode 100644 index 5484041..0000000 --- a/day9/task5_vue/backend/database.py +++ /dev/null @@ -1,43 +0,0 @@ -import MySQLdb -from backend.config import * - -import logging - - -def db_column_names(): - cursor = db.cursor() - cursor.execute('DESCRIBE table_task1;') - table_structure = cursor.fetchall() - table_headers = [field[0] for field in table_structure] - return table_headers - - -logger = logging.getLogger('tableApp') -logger.info(f'Trying to connect to database "{DATABASE_NAME}@{HOST}"...') -# В файле config.py создайте соответствующие переменные -db = MySQLdb.connect( - host=HOST, - user=USERNAME, - passwd=PASSWORD, - db=DATABASE_NAME -) -logger.info('Connected') - -logger.info(f'Preparing table "table_task1"...') -db.cursor().execute( - ''' - CREATE TABLE IF NOT EXISTS `table_task1` ( - `service_id` int(11) NOT NULL AUTO_INCREMENT, - `servtype` varchar(20) NOT NULL DEFAULT 'hosting', - `subtype` varchar(32) NOT NULL DEFAULT '', - `user_id` bigint(20) NOT NULL, - `referrer_user_id` bigint(20) NOT NULL, - `state` varchar(1) NOT NULL DEFAULT 'N', - `creation_date` date NOT NULL DEFAULT '0000-01-01', - `creation_time` time NOT NULL DEFAULT '00:00:00', - `creation_request_sent_date` datetime DEFAULT NULL, - `notified_about_expiration` smallint(6) NOT NULL DEFAULT '0', - PRIMARY KEY (`service_id`) - ) ENGINE=InnoDB AUTO_INCREMENT=35109400 DEFAULT CHARSET=utf8; - ''' -) diff --git a/day9/task5_vue/backend/database/database.py b/day9/task5_vue/backend/database/database.py new file mode 100644 index 0000000..87982cf --- /dev/null +++ b/day9/task5_vue/backend/database/database.py @@ -0,0 +1,29 @@ +from backend.database.wrappers import Wrapper, MySQLWrapper + +import logging + + +def initialize_databases(configs, schemes): + for config in configs: + if config['type'] == 'mysql': + logger = logging.getLogger('backendDebug') + + logger.debug('Connected') + logger.debug(f'Trying to connect to database "{config["db_name"]}@{config["host"]}"...') + + wrapper = MySQLWrapper(config['host'], config['username'], config['password'], config['db_name']) + wrappers[config['name']] = wrapper + + cursor = wrapper.connection.cursor() + for scheme in schemes: + logger.debug(f'Preparing table "{scheme.meta["name"]}"...') + cursor.execute('START TRANSACTION; {} COMMIT;'.format(scheme.get_create_line())) + wrapper.schemes[scheme.meta['name']] = scheme + cursor.close() + + +def get_wrapper_for(database_name) -> Wrapper: + return wrappers.get(database_name, None) + + +wrappers = {} diff --git a/day9/task5_vue/backend/database/field_types.py b/day9/task5_vue/backend/database/field_types.py new file mode 100644 index 0000000..d0b2c3b --- /dev/null +++ b/day9/task5_vue/backend/database/field_types.py @@ -0,0 +1,128 @@ +from abc import ABC, abstractmethod + + +class Field(ABC): + def __init__(self, validators=None): + self.validators = [] + if validators is not None: + self.validators.extend(validators) + + def _validate_attributes(self): + pass + + @property + @abstractmethod + def sql_line(self): + pass + + def validate(self, value): + for validator in self.validators: + validator.validate(value, self) + + +class TextField(Field): + def __init__(self, max_length, is_variable_length, nullable, default, validators=None): + super().__init__(validators) + + self.max_length = max_length + self.is_variable_length = is_variable_length + self.nullable = nullable + self.default = default + + self.data_type = str + + @property + def sql_line(self): + request = [ + ('varchar' if self.is_variable_length else 'char') + f'({self.max_length})', + 'NULL' if self.nullable else 'NOT NULL' + ] + + if self.default is not None or self.nullable: + request.append(f'DEFAULT "{self.default}"') + + return ' '.join(request) + + +class IntegerField(Field): + def __init__(self, max_length, nullable, is_auto_increment, default, validators=None): + super().__init__(validators) + + self.max_length = max_length + self.nullable = nullable + self.is_auto_increment = is_auto_increment + self.default = default + + self.data_type = int + + @property + def sql_line(self): + request = [ + 'int' + (f'({self.max_length})' if self.max_length is not None else ''), + 'NULL' if self.nullable else 'NOT NULL' + ] + + if self.default is not None: + request.append(f'DEFAULT "{self.default}"') + + if self.is_auto_increment: + request.append('AUTO_INCREMENT') + + return ' '.join(request) + + +class DateField(Field): + def __init__(self, nullable, default, validators=None): + super().__init__(validators) + + self.nullable = nullable + self.default = default + + self.data_type = str + + @property + def sql_line(self): + request = ['date', 'NULL' if self.nullable else 'NOT NULL'] + + if self.default is not None: + request.append(f'DEFAULT "{self.default}"') + + return ' '.join(request) + + +class TimeField(Field): + def __init__(self, nullable, default, validators=None): + super().__init__(validators) + + self.nullable = nullable + self.default = default + + self.data_type = str + + @property + def sql_line(self): + request = ['time', 'NULL' if self.nullable else 'NOT NULL'] + + if self.default is not None: + request.append(f'DEFAULT "{self.default}"') + + return ' '.join(request) + + +class DatetimeField(Field): + def __init__(self, nullable, default, validators=None): + super().__init__(validators) + + self.nullable = nullable + self.default = default + + self.data_type = str + + @property + def sql_line(self): + request = ['datetime', 'NULL' if self.nullable else 'NOT NULL'] + + if self.default is not None: + request.append(f'DEFAULT "{self.default}"') + + return ' '.join(request) diff --git a/day9/task5_vue/backend/database/scheme.py b/day9/task5_vue/backend/database/scheme.py new file mode 100644 index 0000000..f783f79 --- /dev/null +++ b/day9/task5_vue/backend/database/scheme.py @@ -0,0 +1,46 @@ +from backend.database.validators import ValidationError + + +class DatabaseScheme: + meta = {} + fields = {} + + @classmethod + def get_create_line(cls) -> str: + fields = cls.fields + meta = cls.meta + + lines = [f'`{name}` {field.sql_line}' for name, field in fields.items()] + + if 'primary_key' in meta: + primary_key = ', PRIMARY KEY (`{}`)'.format(meta['primary_key']) + else: + primary_key = '' + + return 'CREATE TABLE IF NOT EXISTS `{name}` (\n{fields}{primary_key}\n);'.format( + name=meta['name'], fields=',\n'.join(lines), primary_key=primary_key + ) + + @classmethod + def validate(cls, data: dict) -> dict: + data_fields = set(data.keys()) + scheme_fields = set(cls.fields.keys()) + + defined_fields = scheme_fields & data_fields + not_defined_fields = scheme_fields ^ defined_fields + + validation_results = {'error': False} + for field in not_defined_fields: + validation_results['error'] = True + validation_results[field] = 'Field is not defined' + + for field in defined_fields: + try: + cls.fields[field].validate(data[field]) + except ValidationError as e: + validation_results['error'] = True + validation_results[field] = str(e) + else: + validation_results[field] = None + + return validation_results diff --git a/day9/task5_vue/backend/database/validators.py b/day9/task5_vue/backend/database/validators.py new file mode 100644 index 0000000..e4b7310 --- /dev/null +++ b/day9/task5_vue/backend/database/validators.py @@ -0,0 +1,96 @@ +from abc import ABC, abstractmethod +import re + + +class ValidationError(Exception): + pass + + +class Validator(ABC): + @staticmethod + @abstractmethod + def validate(value, field_object): + pass + + +class ValidateNull(Validator): + @staticmethod + def validate(value, field_object): + if value is None and not field_object.nullable: + raise ValidationError('Value cannot be NULL') + + +class ValidateType(Validator): + @staticmethod + def validate(value, field_object): + if field_object.nullable and value is None: + return + if not isinstance(value, field_object.data_type): + raise ValidationError('Value is of wrong type') + + +class ValidateLength(Validator): + @staticmethod + def validate(value, field_object): + if len(str(value)) > field_object.max_length: + raise ValidationError('Value has too many digits') + + +class ValidateTime(Validator): + @staticmethod + def validate(value, field_object): + match = re.fullmatch(r'(\d\d):(\d\d):(\d\d)', value) + if not match: + raise ValidationError('Wrong time format') + else: + hour, minute, second = map(int, match.groups()) + if hour not in range(0, 24): + raise ValidationError('Wrong hour value') + + if minute not in range(0, 60): + raise ValidationError('Wrong minute value') + + if second not in range(0, 60): + raise ValidationError('Wrong second value') + + +class ValidateDate(Validator): + @staticmethod + def validate(value, field_object): + match = re.fullmatch(r'(\d\d\d\d)-(\d\d)-(\d\d)', value) + if not match: + raise ValidationError('Wrong date format') + else: + year, month, day = map(int, match.groups()) + if year < 0: + raise ValidationError('Wrong year value') + + if month not in range(1, 12): + raise ValidationError('Wrong month value') + + if month == 2: + if year % 4 == 0 and year % 100 != 0 or year % 400 == 0: + febr_range = range(1, 29) + else: + febr_range = range(1, 28) + if day not in febr_range: + raise ValidationError('Wrong day value') + else: + days_count = { + 1: 31, 3: 31, 4: 30, 5: 31, + 6: 30, 7: 31, 8: 31, 9: 30, + 10: 31, 11: 30, 12: 31 + }.get(month) + if day not in range(1, days_count): + raise ValidationError('Wrong day value') + + +class ValidateDatetime(Validator): + @staticmethod + def validate(value, field_object): + datetime = value.split() + if len(datetime) != 2: + raise ValidationError('Wrong datetime format') + else: + ValidateDate.validate(datetime[0], {}) + ValidateTime.validate(datetime[1], {}) \ No newline at end of file diff --git a/day9/task5_vue/backend/database/wrappers.py b/day9/task5_vue/backend/database/wrappers.py new file mode 100644 index 0000000..824d19e --- /dev/null +++ b/day9/task5_vue/backend/database/wrappers.py @@ -0,0 +1,149 @@ +from abc import ABC, abstractmethod + +import MySQLdb + + +class Wrapper(ABC): + def __init__(self): + self.schemes = {} + + @abstractmethod + def clear_table(self, table_name): + pass + + @abstractmethod + def get_column_names(self): + pass + + @abstractmethod + def insert_one(self, table_name, row, field_names=None): + pass + + @abstractmethod + def update(self, table_name, expressions, conditions): + pass + + @abstractmethod + def delete_from(self, table_name, conditions): + pass + + @abstractmethod + def get_data(self, table_name): + pass + + @abstractmethod + def get_rows(self, table_name, conditions): + pass + + +class MySQLWrapper(Wrapper): + def __init__(self, host, username, password, db_name): + super().__init__() + self.connection = MySQLdb.connect( + host=host, + user=username, + passwd=password, + db=db_name + ) + + def clear_table(self, table_name): + cursor = self.connection.cursor() + cursor.execute(f"START TRANSACTION; DELETE FROM `{table_name}`; COMMIT;") + cursor.close() + + def get_column_names(self): + cursor = self.connection.cursor() + cursor.execute('DESCRIBE table_task1;') + table_structure = cursor.fetchall() + table_headers = [field[0] for field in table_structure] + return table_headers + + def insert_one(self, table_name, row, field_names=None): + cursor = self.connection.cursor() + + if field_names is not None: + field_names_formatted = [] + for name in field_names: + if name != 'NULL' or not name.isnumeric(): + name = f'`{name}`' + field_names_formatted.append(name) + field_names_formatted = f'({",".join(field_names_formatted)})' + else: + field_names_formatted = '' + + row_formatted = [] + for value in row: + if value == 'NULL' or value.isnumeric(): + row_formatted.append(value) + else: + row_formatted.append(f'"{value}"') + + request = "START TRANSACTION; INSERT INTO `{}` {} VALUES ({}); COMMIT;".format( + table_name, field_names_formatted, ",".join(row_formatted) + ) + + print(request) + + cursor.execute(request) + cursor.close() + + def update(self, table_name, expressions, conditions): + cursor = self.connection.cursor() + + expressions_formatted = [] + for field_name, value in expressions.items(): + if value != 'NULL' or not value.isnumeric(): + value = f'"{value}"' + expressions_formatted.append(f'`{field_name}`={value}') + + conditions_formatted = [] + for field_name, value in conditions.items(): + if value != 'NULL' or not value.isnumeric(): + value = f'"{value}"' + conditions_formatted.append(f'`{field_name}`={value}') + + cursor.execute("START TRANSACTION; UPDATE `{}` SET {} WHERE {}; COMMIT;".format( + table_name, ','.join(expressions_formatted), ' AND '.join(conditions_formatted) + )) + + cursor.close() + + def delete_from(self, table_name, conditions): + cursor = self.connection.cursor() + + conditions_formatted = [] + for field_name, value in conditions.items(): + if value != 'NULL' or not value.isnumeric(): + value = f'"{value}"' + conditions_formatted.append(f'`{field_name}`={value}') + + cursor.execute("START TRANSACTION; DELETE FROM `{}` WHERE {}; COMMIT;".format( + table_name, ' AND '.join(conditions_formatted) + )) + + cursor.close() + + def get_data(self, table_name): + cursor = self.connection.cursor() + + cursor.execute(f'SELECT * FROM `{table_name}`;') + content = list(map(list, cursor.fetchall())) + + cursor.close() + return content + + def get_rows(self, table_name, conditions): + cursor = self.connection.cursor() + + conditions_formatted = [] + for field_name, value in conditions.items(): + if value != 'NULL' or not value.isnumeric(): + value = f'"{value}"' + conditions_formatted.append(f'`{field_name}`={value}') + + cursor.execute(f'SELECT * FROM `{table_name}` WHERE {" AND ".join(conditions_formatted)};') + content = list(map(list, cursor.fetchall())) + + cursor.close() + + return content diff --git a/day9/task5_vue/backend/router.py b/day9/task5_vue/backend/router.py deleted file mode 100644 index be8305e..0000000 --- a/day9/task5_vue/backend/router.py +++ /dev/null @@ -1,41 +0,0 @@ -import re -from backend.utils import NOT_FOUND_CODE, BAD_REQUEST_CODE -from backend.core import Response, ErrorResponse - - -def route(url_format, methods=None): - if methods is None: - methods = ['GET'] - - def wrapper(func): - def inner(url, query, *args, **kwargs): - pattern = re.compile(url_format) - match = re.fullmatch(pattern, url) - - if match is None or len(match.groups()) != pattern.groups: - return ErrorResponse(BAD_REQUEST_CODE) - - return func(query, *match.groups(), *args, **kwargs) - - _router_tree[url_format] = _router_tree.get(url_format, {}) - for method in methods: - _router_tree[url_format][method] = inner - - return inner - - return wrapper - - -def run(request) -> Response: - res = ErrorResponse(NOT_FOUND_CODE) - - method, url = request['method'], request['url'] - for url_pattern in _router_tree: - if re.fullmatch(url_pattern, url) and method in _router_tree[url_pattern]: - res = _router_tree[url_pattern][method](url, request['query']) - break - - return res - - -_router_tree = {} diff --git a/day9/task5_vue/backend/routes.py b/day9/task5_vue/backend/routes.py new file mode 100644 index 0000000..65117c2 --- /dev/null +++ b/day9/task5_vue/backend/routes.py @@ -0,0 +1,15 @@ +from backend.core.router import Route, router_table +from backend.views import * + +router_table.extend([ + Route(index_get, '/'), + Route(return_static, r'/static/(.*?)/(.*?)/?'), + + Route(upload_file, '/api/upload/?', ['POST']), + Route(update_post, '/api/update/?', ['POST']), + Route(delete_post, '/api/delete/?', ['POST']), + Route(add_post, '/api/add/?', ['POST']), + Route(db_get, '/api/get/?', ['POST']), + + Route(validate, '/validate/?', ['POST']), +]) diff --git a/day9/task5_vue/backend/schemes.py b/day9/task5_vue/backend/schemes.py new file mode 100644 index 0000000..c0af1dd --- /dev/null +++ b/day9/task5_vue/backend/schemes.py @@ -0,0 +1,23 @@ +from backend.database.field_types import * +from backend.database.validators import * +from backend.database.scheme import DatabaseScheme + + +class Day9Table(DatabaseScheme): + meta = { + 'name': 'table_task1', + 'primary_key': 'service_id' + } + + fields = { + 'service_id': IntegerField(None, False, True, None, validators=[ValidateNull, ValidateType]), + 'servtype': TextField(20, True, False, 'hosting', validators=[ValidateNull, ValidateLength, ValidateType]), + 'subtype': TextField(32, True, False, '', validators=[ValidateNull, ValidateLength, ValidateType]), + 'user_id': IntegerField(None, False, False, None, validators=[ValidateNull, ValidateType]), + 'referrer_user_id': IntegerField(None, False, False, None, validators=[ValidateNull, ValidateType]), + 'state': TextField(1, False, False, 'N', validators=[ValidateNull, ValidateLength, ValidateType]), + 'creation_date': DateField(False, '0000-01-01', validators=[ValidateType, ValidateNull, ValidateDate]), + 'creation_time': TimeField(False, '00:00:00', validators=[ValidateType, ValidateNull, ValidateTime]), + 'creation_request_sent_date': DatetimeField(True, None, validators=[ValidateType, ValidateDatetime]), + 'notified_about_expiration': IntegerField(None, False, False, 0, validators=[ValidateNull, ValidateType]) + } diff --git a/day9/task5_vue/backend/server.py b/day9/task5_vue/backend/server.py deleted file mode 100644 index 48d5946..0000000 --- a/day9/task5_vue/backend/server.py +++ /dev/null @@ -1,73 +0,0 @@ -from http.server import HTTPServer, BaseHTTPRequestHandler -from urllib.parse import parse_qs - -from backend.router import run -from backend.utils import parse_multipart_form - -import logging -import json - - -class MyHTTPRequestHandler(BaseHTTPRequestHandler): - def _set_response(self, code, content_type): - self.send_response(code) - self.send_header('Content-type', content_type) - self.end_headers() - - def do_GET(self): - try: - content_length = int(self.headers['Content-Length']) - get_data = parse_qs(self.rfile.read(content_length).decode('utf-8')) - except TypeError: - get_data = {} - - for key in get_data: - get_data[key] = get_data[key][0] - - self.finalize_request(run({ - 'url': self.path, - 'method': 'GET', - 'query': get_data - })) - - def do_POST(self): - content_length = int(self.headers['Content-Length']) - content_type = self.headers['Content-type'] - post_data = self.rfile.read(content_length) - - if content_type.split(';')[0] == 'multipart/form-data': - files = parse_multipart_form(post_data) - self.finalize_request(run({ - 'url': self.path, - 'method': 'POST', - 'query': {'files': files} - })) - - elif content_type.split(';')[0] in ['text/plain', 'application/x-www-form-urlencoded']: - post_data = parse_qs(post_data.decode('utf-8')) - for key in post_data: - post_data[key] = post_data[key][0] - - self.finalize_request(run({ - 'url': self.path, - 'method': 'POST', - 'query': post_data - })) - - elif content_type.split(';')[0] == 'application/json': - self.finalize_request(run({ - 'url': self.path, - 'method': 'POST', - 'query': json.loads(post_data.decode('utf-8')) - })) - - def finalize_request(self, response): - self._set_response(response.status_code, response.content_type) - self.wfile.write(response.content) - - -def start_server(host, port): - server_address = (host, port) - httpd = HTTPServer(server_address, MyHTTPRequestHandler) - logging.getLogger('tableApp').info(f'Server started on {host}:{port}') - httpd.serve_forever() diff --git a/day9/task5_vue/backend/settings.py b/day9/task5_vue/backend/settings.py new file mode 100644 index 0000000..2271672 --- /dev/null +++ b/day9/task5_vue/backend/settings.py @@ -0,0 +1,18 @@ +STATIC_FILES_PATH = 'static' + +SERVER_HOST = '' +SERVER_PORT = 8000 + +DEBUG_MODE = True + +DATABASES = [ + { + 'type': 'mysql', + + 'name': 'mysql', + 'host': 'pcserv2', + 'username': 'day9', + 'password': 'day9', + 'db_name': 'day9' + } +] diff --git a/day9/task5_vue/backend/utils.py b/day9/task5_vue/backend/utils.py deleted file mode 100644 index c1b3a04..0000000 --- a/day9/task5_vue/backend/utils.py +++ /dev/null @@ -1,131 +0,0 @@ -from io import BytesIO - - -CHUNK_SIZE = 49600 - -HTTP_STATUS_CODES = { - 100: "Continue", - 101: "Switching Protocols", - 102: "Processing", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 207: "Multi Status", - 226: "IM Used", # see RFC 3229 - 300: "Multiple Choices", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 307: "Temporary Redirect", - 308: "Permanent Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", # unused - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Request Entity Too Large", - 414: "Request URI Too Long", - 415: "Unsupported Media Type", - 416: "Requested Range Not Satisfiable", - 417: "Expectation Failed", - 418: "I'm a teapot", # see RFC 2324 - 421: "Misdirected Request", # see RFC 7540 - 422: "Unprocessable Entity", - 423: "Locked", - 424: "Failed Dependency", - 426: "Upgrade Required", - 428: "Precondition Required", # see RFC 6585 - 429: "Too Many Requests", - 431: "Request Header Fields Too Large", - 449: "Retry With", # proprietary MS extension - 451: "Unavailable For Legal Reasons", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported", - 507: "Insufficient Storage", - 510: "Not Extended", -} - -SUCCESS_CODE = 200 -BAD_REQUEST_CODE = 400 -NOT_FOUND_CODE = 404 -METHOD_NOT_ALLOWED_CODE = 405 - - -def render_template(path, **kwargs): - with open(path, encoding='utf-8') as f: - template = f.read() - - for key in kwargs: - template = template.replace(f'%%{key}%%', kwargs[key]) - - return template - - -def parse_multipart_form(data: bytes): - b = BytesIO(data) - separator = b.readline().strip() - - files = [] - - while True: - line = b.readline().strip().strip(b'-') - if not line: - break - - headers_raw = [] - while not line.strip() == b'': - headers_raw.append(line.strip().decode()) - line = b.readline() - - headers = {'Content-Type': headers_raw[1].split(': ')[1]} - for pair in headers_raw[0].split(': ')[1].split('; ')[1:]: - key, value = pair.split('=') - headers[key] = value[1:-1] - - data = b'' - prev_chunk = b.read(CHUNK_SIZE) - chunk = b.read(CHUNK_SIZE) - while separator not in (prev_chunk + chunk): - data += prev_chunk - prev_chunk = chunk - chunk = b.read(CHUNK_SIZE) - - if not chunk: - break - - chunk = (prev_chunk + chunk) - if chunk.startswith(separator) or chunk.endswith(separator): - with_sep = chunk.strip(separator) - else: - if separator in chunk: - with_sep, buffer = chunk.split(separator) - b = BytesIO(buffer + b.read()) - b.readline() - - else: - with_sep = chunk - - data += with_sep - headers['data'] = data - - files.append(headers) - - return files diff --git a/day9/task5_vue/backend/views.py b/day9/task5_vue/backend/views.py new file mode 100644 index 0000000..d6d9351 --- /dev/null +++ b/day9/task5_vue/backend/views.py @@ -0,0 +1,160 @@ +from backend.core.utils import render_template, NOT_FOUND_CODE +from backend.settings import STATIC_FILES_PATH + +from backend.core.response_types import ErrorResponse, TextFileResponse, ImageResponse, HtmlResponse, JsonResponse +from backend.database.database import get_wrapper_for + +import csv + + +def return_static(query, *args): + if args[0] in ['css', 'js']: + try: + return TextFileResponse( + path=STATIC_FILES_PATH + f'/{args[0]}/{args[1]}', + extension={'js': 'javascript', 'css': 'css'}.get(args[0]) + ) + + except (PermissionError, FileNotFoundError): + return ErrorResponse(404) + + elif args[0] == 'images': + try: + return ImageResponse(STATIC_FILES_PATH + f'/{args[0]}/{args[1]}', args[1].split('.')[-1]) + + except (PermissionError, FileNotFoundError): + return ErrorResponse(404) + + +def upload_file(query, *args): + base_html = 'Return to main page
%s' + + try: + data = query['files'][0]['data'].decode('utf-8') + except (UnicodeDecodeError, KeyError, IndexError): + return ErrorResponse(500, base_html % '

Error while reading file

') + + wrapper = get_wrapper_for('mysql') + + permitted_headers = wrapper.get_column_names() + + try: + data = list(csv.reader(data.strip().splitlines(), delimiter=';', quotechar='"')) + if len(data[0]) != len(permitted_headers) or set(data[0]) != set(permitted_headers): + return ErrorResponse(500, base_html % '

File format error

') + except IndexError: + return ErrorResponse(500, base_html % '

File format error

') + + wrapper.clear_table('table_task1') + + scheme = wrapper.schemes['table_task1'] + for row in data[1:]: + validation_results = scheme.validate(dict(zip(data[0], row))) + if not validation_results['error']: + wrapper.insert_one('table_task1', row, data[0]) + else: + return ErrorResponse(500) + + return HtmlResponse(base_html % '

File uploaded

') + + +def validate(query, *args): + wrapper = get_wrapper_for('mysql') + scheme = wrapper.schemes['table_task1'] + validation_results = scheme.validate(query) + return JsonResponse(validation_results) + + +def update_post(query, *args): + wrapper = get_wrapper_for('mysql') + service_id = query['service_id'] + permitted_fields = wrapper.get_column_names() + permitted_fields.remove('service_id') + + query_set = [] + for field_name, value in query.items(): + if field_name in permitted_fields: + query_set.append(f'{field_name}="{value}"') + + wrapper.update('table_task1', query_set, {'service_id': service_id}) + return HtmlResponse('Return to main page

Database Updated

') + + +def delete_post(query, *args): + get_wrapper_for('mysql').delete_from('table_task1', {'service_id': query['service_id']}) + return HtmlResponse('Return to main page

Database Updated

') + + +def add_post(query, *args): + wrapper = get_wrapper_for('mysql') + header_fields = wrapper.get_column_names() + headers = [] + values = [] + for field_name in query: + if field_name in header_fields: + headers.append(field_name) + values.append(f'"{query[field_name]}"') + + if len(headers) == len(header_fields): + wrapper.insert_one('table_task1', values, headers) + + return HtmlResponse('Return to main page

Database Updated

') + + +def db_get(query, *args): + wrapper = get_wrapper_for('mysql') + table_headers = wrapper.get_column_names() + + if query['type'] == 'full': + content = wrapper.get_data('table_task1') + + for row in content: + # creation_time + row[7] = ':'.join(_.rjust(2, '0') for _ in str(row[7]).split(':')) + + # creation_request_sent_date + if row[8] is not None: + creation_date, creation_time = str(row[8]).split() + creation_time = ':'.join(_.rjust(2, '0') for _ in creation_time.split(':')) + row[8] = creation_date + ' ' + creation_time + + json_content = [] + for row in content: + new_row = [] + for col in row: + if not isinstance(col, (float, bool, int, dict, list, tuple, str)): + col = str(col) + new_row.append(col) + json_content.append(new_row) + + return JsonResponse({'headers': table_headers, 'content': json_content}) + + elif query['type'] == 'single_id': + content = wrapper.get_rows('table_task1', {'service_id': query['service_id']})[0] + + # creation_time + content[7] = ':'.join(_.rjust(2, '0') for _ in str(content[7]).split(':')) + + # creation_request_sent_date + if content[8] is not None: + creation_date, creation_time = str(content[8]).split() + creation_time = ':'.join(_.rjust(2, '0') for _ in creation_time.split(':')) + content[8] = creation_date + ' ' + creation_time + + if content is not None: + json_content = [] + for col in content: + if not isinstance(col, (float, bool, int, dict, list, tuple)): + col = str(col) + json_content.append(col) + + return JsonResponse(dict(zip(table_headers, json_content))) + else: + return JsonResponse({}) + + return ErrorResponse(NOT_FOUND_CODE) + + +def index_get(query, *args): + data = render_template('index.html') + return HtmlResponse(data) diff --git a/day9/task5_vue/backend_serve.py b/day9/task5_vue/backend_serve.py deleted file mode 100644 index 7aa89ef..0000000 --- a/day9/task5_vue/backend_serve.py +++ /dev/null @@ -1,214 +0,0 @@ -from backend.router import route -from backend.utils import render_template, NOT_FOUND_CODE -from backend.config import SERVER_HOST, SERVER_PORT, STATIC_FILES_PATH - -from backend.core import ErrorResponse, TextFileResponse, ImageResponse, HtmlResponse, JsonResponse - -import logging -import csv -from sys import stdout - - -@route('/static/(.*?)/(.*?)/?') -def return_static(query, *args): - if args[0] in ['css', 'js']: - try: - return TextFileResponse( - path=STATIC_FILES_PATH + f'/{args[0]}/{args[1]}', - extension={'js': 'javascript', 'css': 'css'}.get(args[0]) - ) - - except (PermissionError, FileNotFoundError): - return ErrorResponse(404) - - elif args[0] == 'images': - try: - return ImageResponse(STATIC_FILES_PATH + f'/{args[0]}/{args[1]}', args[1].split('.')[-1]) - - except (PermissionError, FileNotFoundError): - return ErrorResponse(404) - - -@route('/api/upload/?', ['POST']) -def upload_file(query, *args): - base_html = 'Return to main page
%s' - - try: - data = query['files'][0]['data'].decode() - except (UnicodeDecodeError, KeyError, IndexError): - return ErrorResponse(500, base_html % '

Error while reading file

') - - permitted_headers = db_column_names() - - try: - data = list(csv.reader(data.strip().splitlines(), delimiter=';', quotechar='"')) - if len(data[0]) != len(permitted_headers) or set(data[0]) != set(permitted_headers): - return ErrorResponse(500, base_html % '

File format error

') - except IndexError: - return ErrorResponse(500, base_html % '

File format error

') - - cursor = db.cursor() - cursor.execute("START TRANSACTION; DELETE FROM `table_task1`; COMMIT;") - cursor.close() - - for row in data[1:]: - values = [] - for i in range(len(row)): - if row[i] == 'NULL': - if data[0][i] == 'creation_request_sent_date': - value = 'NULL' - else: - value = '"NULL"' - else: - value = f'"{row[i]}"' - - values.append(value) - - cursor = db.cursor() - cursor.execute( - "START TRANSACTION; " - "INSERT INTO `table_task1` ({}) VALUES ({}); " - "COMMIT;".format( - ','.join(data[0]), ','.join(values) - ) - ) - cursor.close() - - return HtmlResponse(base_html % '

File uploaded

') - - -@route('/api/update/?', ['POST']) -def update_post(query, *args): - print(query) - service_id = query['service_id'] - permitted_fields = db_column_names() - permitted_fields.remove('service_id') - - query_set = [] - for field_name, value in query.items(): - if field_name in permitted_fields: - query_set.append(f'{field_name}="{value}"') - - cursor = db.cursor() - cursor.execute("START TRANSACTION; UPDATE `table_task1` SET {} WHERE {}; COMMIT;".format( - ','.join(query_set), f'service_id="{service_id}"' - )) - cursor.close() - - return HtmlResponse('Return to main page

Database Updated

') - - -@route('/api/delete/?', ['POST']) -def delete_post(query, *args): - service_id = query['service_id'] - cursor = db.cursor() - cursor.execute(f'START TRANSACTION; DELETE FROM `table_task1` WHERE service_id="{service_id}"; COMMIT;') - - return HtmlResponse('Return to main page

Database Updated

') - - -@route('/api/add/?', ['POST']) -def add_post(query, *args): - header_fields = db_column_names() - headers = [] - values = [] - for field_name in query: - if field_name in header_fields: - headers.append(field_name) - values.append(f'"{query[field_name]}"') - - if len(headers) == len(header_fields): - cursor = db.cursor() - cursor.execute("START TRANSACTION; INSERT INTO `table_task1` ({}) VALUES ({}); COMMIT;".format( - ','.join(headers), ','.join(values) - )) - cursor.close() - - return HtmlResponse('Return to main page

Database Updated

') - - -@route('/api/get/?', ['POST']) -def db_get(query, *args): - table_headers = db_column_names() - cursor = db.cursor() - - if query['type'] == 'full': - cursor.execute('SELECT * FROM table_task1;') - content = list(map(list, cursor.fetchall())) - - for row in content: - # creation_time - row[7] = ':'.join(_.rjust(2, '0') for _ in str(row[7]).split(':')) - - # creation_request_sent_date - if row[8] is not None: - creation_date, creation_time = str(row[8]).split() - creation_time = ':'.join(_.rjust(2, '0') for _ in creation_time.split(':')) - row[8] = creation_date + ' ' + creation_time - - cursor.close() - - json_content = [] - for row in content: - new_row = [] - for col in row: - if not isinstance(col, (float, bool, int, dict, list, tuple, str)): - col = str(col) - new_row.append(col) - json_content.append(new_row) - - return JsonResponse({'headers': table_headers, 'content': json_content}) - - elif query['type'] == 'single_id': - cursor.execute(f'SELECT * FROM table_task1 WHERE service_id="{query["service_id"]}";') - content = list(cursor.fetchall()[0]) - - # creation_time - content[7] = ':'.join(_.rjust(2, '0') for _ in str(content[7]).split(':')) - - # creation_request_sent_date - if content[8] is not None: - creation_date, creation_time = str(content[8]).split() - creation_time = ':'.join(_.rjust(2, '0') for _ in creation_time.split(':')) - content[8] = creation_date + ' ' + creation_time - - cursor.close() - - if content is not None: - json_content = [] - for col in content: - if not isinstance(col, (float, bool, int, dict, list, tuple)): - col = str(col) - json_content.append(col) - - return JsonResponse(dict(zip(table_headers, json_content))) - else: - return JsonResponse({}) - - return ErrorResponse(NOT_FOUND_CODE) - - -@route('/') -def index_get(query, *args): - data = render_template('index.html') - return HtmlResponse(data) - - -def prepare_logger(): - logger = logging.getLogger('tableApp') - logger.setLevel(logging.INFO) - - sh = logging.StreamHandler(stdout) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') - sh.setFormatter(formatter) - logger.addHandler(sh) - - -if __name__ == '__main__': - prepare_logger() - - from backend.server import start_server - from backend.database import db, db_column_names - - logging.getLogger('tableApp').info(f'Starting server...') - start_server(SERVER_HOST, SERVER_PORT) diff --git a/day9/task5_vue/package-lock.json b/day9/task5_vue/package-lock.json index 64b647f..7718c3d 100644 --- a/day9/task5_vue/package-lock.json +++ b/day9/task5_vue/package-lock.json @@ -1,6 +1,6 @@ { "name": "task5_vue", - "version": "1.0.0", + "version": "0.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/day9/task5_vue/package.json b/day9/task5_vue/package.json index 5ab0280..650d57a 100644 --- a/day9/task5_vue/package.json +++ b/day9/task5_vue/package.json @@ -4,7 +4,7 @@ "description": "Example app for day9 of reg.ru", "main": "src/index.js", "scripts": { - "start": "python backend_serve.py", + "start": "python runserver.py", "build": "webpack -p" }, "author": "Andrew Guschin", diff --git a/day9/task5_vue/runserver.py b/day9/task5_vue/runserver.py new file mode 100644 index 0000000..8dd4d25 --- /dev/null +++ b/day9/task5_vue/runserver.py @@ -0,0 +1,27 @@ +from backend.database.database import initialize_databases, wrappers +from backend.schemes import * +from backend.settings import DEBUG_MODE, DATABASES, SERVER_HOST, SERVER_PORT + +import logging +from sys import stdout + + +def prepare_logger(): + logger = logging.getLogger('tableApp') + logger.setLevel(logging.DEBUG if DEBUG_MODE else logging.INFO) + + sh = logging.StreamHandler(stdout) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + sh.setFormatter(formatter) + logger.addHandler(sh) + + +if __name__ == '__main__': + prepare_logger() + initialize_databases(DATABASES, [Day9Table]) + + from backend.routes import * + from backend.core.server import start_server + + logging.getLogger('tableApp').info(f'Starting server...') + start_server(SERVER_HOST, SERVER_PORT) diff --git a/day9/task5_vue/src/components/EditFormBox.vue b/day9/task5_vue/src/components/EditFormBox.vue index 72d09fe..46438d4 100644 --- a/day9/task5_vue/src/components/EditFormBox.vue +++ b/day9/task5_vue/src/components/EditFormBox.vue @@ -5,46 +5,50 @@
service_id
-
- -
-
- +
+ +
servtype
- +
subtype
- +
user_id
- +
referrer_user_id
- +
state
- @@ -57,13 +61,13 @@
creation_date
- +
creation_time
- +
@@ -71,28 +75,31 @@
creation_request_sent_date
+
+ {{ validationResponse.creation_request_sent_date }} +
notified_about_expiration
- +
- - + +
@@ -100,16 +107,74 @@ @@ -154,6 +219,11 @@ background-color: #fbfbfb; } + .formRow-error { + font-size: 15px; + color: #ff0000; + } + .formButtons { display: flex; flex-direction: row; -- cgit v1.2.3