Formatting.

This commit is contained in:
retoor 2025-01-24 23:35:44 +01:00
parent 18b76ebd5e
commit 9b93403a93
28 changed files with 342 additions and 246 deletions

View File

@ -1,9 +1,10 @@
import pathlib
from types import SimpleNamespace
from aiohttp import web
from app.app import Application as BaseApplication
from snek.docs.app import Application as DocsApplication
from app.cache import time_cache_async
from snek.mapper import get_mappers
from snek.service import get_services
from snek.system import http
@ -17,7 +18,6 @@ from snek.view.login_form import LoginFormView
from snek.view.register import RegisterView
from snek.view.register_form import RegisterFormView
from snek.view.web import WebView
from types import SimpleNamespace
class Application(BaseApplication):
@ -51,7 +51,7 @@ class Application(BaseApplication):
self.router.add_view("/about.md", AboutMDView)
self.router.add_view("/docs.html", DocsHTMLView)
self.router.add_view("/docs.md", DocsMDView)
self.router.add_view("/web.html", WebView)
self.router.add_view("/login.html", LoginView)
self.router.add_view("/login.json", LoginFormView)
@ -60,7 +60,10 @@ class Application(BaseApplication):
self.router.add_get("/http-get", self.handle_http_get)
self.router.add_get("/http-photo", self.handle_http_photo)
self.add_subapp("/docs", DocsApplication(path=pathlib.Path(__file__).parent.joinpath("docs")))
self.add_subapp(
"/docs",
DocsApplication(path=pathlib.Path(__file__).parent.joinpath("docs")),
)
async def handle_test(self, request):
@ -79,9 +82,8 @@ class Application(BaseApplication):
return web.Response(
body=path.read_bytes(), headers={"Content-Type": "image/png"}
)
#@time_cache_async(60)
# @time_cache_async(60)
async def render_template(self, template, request, context=None):
return await super().render_template(template, request, context)

View File

@ -1,24 +1,27 @@
from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement
class LoginForm(Form):
title = HTMLElement(tag="h1", text="Login")
username = FormInputElement(
name="username",
name="username",
required=True,
min_length=2,
max_length=20,
regex=r"^[a-zA-Z0-9_]+$",
place_holder="Username",
type="text"
type="text",
)
password = FormInputElement(
name="password",
required=True,
regex=r"^[a-zA-Z0-9_.+-]{6,}",
type="password",
place_holder="Password",
)
password = FormInputElement(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}",type="password",place_holder="Password")
action = FormButtonElement(
name="action",
value="submit",
text="Login",
type="button"
name="action", value="submit", text="Login", type="button"
)

View File

@ -1,4 +1,5 @@
from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement
class UsernameField(FormInputElement):
@ -9,32 +10,35 @@ class UsernameField(FormInputElement):
result.append("Username is not available.")
return result
class RegisterForm(Form):
title = HTMLElement(tag="h1", text="Register")
username = UsernameField(
name="username",
name="username",
required=True,
min_length=2,
max_length=20,
regex=r"^[a-zA-Z0-9_]+$",
place_holder="Username",
type="text"
type="text",
)
email = FormInputElement(
name="email",
required=False,
regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
place_holder="Email address",
type="email"
type="email",
)
password = FormInputElement(
name="password",
required=True,
regex=r"^[a-zA-Z0-9_.+-]{6,}",
type="password",
place_holder="Password",
)
password = FormInputElement(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}",type="password",place_holder="Password")
action = FormButtonElement(
name="action",
value="submit",
text="Register",
type="button"
name="action", value="submit", text="Register", type="button"
)

View File

@ -1,12 +1,12 @@
import functools
import functools
from snek.mapper.user import UserMapper
@functools.cache
def get_mappers(app=None):
return dict(
user=UserMapper(app=app)
)
@functools.cache
def get_mappers(app=None):
return {"user": UserMapper(app=app)}
def get_mapper(name, app=None):
return get_mappers(app=app)[name]
return get_mappers(app=app)[name]

View File

@ -1,6 +1,7 @@
from snek.system.mapper import BaseMapper
from snek.model.user import UserModel
from snek.system.mapper import BaseMapper
class UserMapper(BaseMapper):
table_name = "user"
model_class = UserModel
model_class = UserModel

View File

@ -1,12 +1,12 @@
from snek.model.user import UserModel
import functools
import functools
from snek.model.user import UserModel
@functools.cache
def get_models():
return dict(
user=UserModel
return {"user": UserModel}
)
def get_model(name):
return get_models()[name]

View File

@ -1,9 +1,10 @@
from snek.system.model import BaseModel,ModelField
from snek.system.model import BaseModel, ModelField
class UserModel(BaseModel):
username = ModelField(
name="username",
name="username",
required=True,
min_length=2,
max_length=20,
@ -12,8 +13,6 @@ class UserModel(BaseModel):
email = ModelField(
name="email",
required=False,
regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
)
password = ModelField(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]{6,}")
password = ModelField(name="password", required=True, regex=r"^[a-zA-Z0-9_.+-]{6,}")

View File

@ -1,12 +1,13 @@
from snek.service.user import UserService
import functools
import functools
from snek.service.user import UserService
@functools.cache
def get_services(app):
return dict(
user = UserService(app=app)
return {"user": UserService(app=app)}
)
def get_service(name, app=None):
return get_services(app=app)[name]
return get_services(app=app)[name]

View File

@ -1,5 +1,6 @@
from snek.system.service import BaseService
from snek.system import security
from snek.system import security
from snek.system.service import BaseService
class UserService(BaseService):
mapper_name = "user"
@ -12,6 +13,5 @@ class UserService(BaseService):
model.username = username
model.password = await security.hash(password)
if await self.save(model):
return model
return model
raise Exception(f"Failed to create user: {model.errors}.")

View File

@ -1,8 +1,8 @@
import functools
import functools
cache = functools.cache
def async_cache(func):
cache = {}
@ -14,4 +14,4 @@ def async_cache(func):
cache[args] = result
return result
return wrapper
return wrapper

View File

@ -5,17 +5,17 @@
# This code uses the `snek.system.model` library for managing model fields.
# MIT License
#
#
# 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
@ -26,8 +26,19 @@
from snek.system import model
class HTMLElement(model.ModelField):
def __init__(self, id=None, tag="div", name=None, html=None, class_name=None, text=None, *args, **kwargs):
def __init__(
self,
id=None,
tag="div",
name=None,
html=None,
class_name=None,
text=None,
*args,
**kwargs,
):
self.tag = tag
self.text = text
self.id = id
@ -37,16 +48,18 @@ class HTMLElement(model.ModelField):
async def to_json(self):
result = await super().to_json()
result['text'] = self.text
result['id'] = self.id
result['html'] = self.html
result['class_name'] = self.class_name
result['tag'] = self.tag
result["text"] = self.text
result["id"] = self.id
result["html"] = self.html
result["class_name"] = self.class_name
result["tag"] = self.tag
return result
class FormElement(HTMLElement):
pass
class FormInputElement(FormElement):
def __init__(self, type="text", place_holder=None, *args, **kwargs):
super().__init__(tag="input", *args, **kwargs)
@ -59,25 +72,27 @@ class FormInputElement(FormElement):
data["type"] = self.type
return data
class FormButtonElement(FormElement):
def __init__(self, tag="button", *args, **kwargs):
super().__init__(tag=tag, *args, **kwargs)
class Form(model.BaseModel):
@property
def html_elements(self):
return [element for element in self.fields if isinstance(element, HTMLElement)]
def set_user_data(self, data):
return super().set_user_data(data.get('fields'))
return super().set_user_data(data.get("fields"))
async def to_json(self, encode=False):
elements = await super().to_json()
html_elements = {}
for element in elements.keys():
if element == 'is_valid':
if element == "is_valid":
# is_valid is async get property so we can't do getattr on it
continue
continue
field = getattr(self, element)
if isinstance(field, HTMLElement):
try:
@ -85,8 +100,12 @@ class Form(model.BaseModel):
except KeyError:
pass
is_valid = all(field['is_valid'] for field in html_elements.values())
return dict(fields=html_elements, is_valid=is_valid, errors=await self.errors)
is_valid = all(field["is_valid"] for field in html_elements.values())
return {
"fields": html_elements,
"is_valid": is_valid,
"errors": await self.errors,
}
@property
async def errors(self):
@ -98,4 +117,4 @@ class Form(model.BaseModel):
@property
async def is_valid(self):
# This is not good, but timebox to resolve issue exceeded.
return False
return False

View File

@ -11,10 +11,10 @@
# 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
@ -24,17 +24,17 @@
# SOFTWARE.
from aiohttp import web
import aiohttp
from app.cache import time_cache_async
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import asyncio
import pathlib
import uuid
import imgkit
import asyncio
import zlib
import io
from urllib.parse import urljoin
import aiohttp
import imgkit
from app.cache import time_cache_async
from bs4 import BeautifulSoup
async def crc32(data):
try:
@ -43,6 +43,7 @@ async def crc32(data):
pass
return "crc32" + str(zlib.crc32(data))
async def get_file(name, suffix=".cache"):
name = await crc32(name)
path = pathlib.Path(".").joinpath("cache")
@ -50,17 +51,19 @@ async def get_file(name, suffix=".cache"):
path.mkdir(parents=True, exist_ok=True)
return path.joinpath(name + suffix)
async def public_touch(name=None):
path = pathlib.Path(".").joinpath(str(uuid.uuid4()) + name)
path.open("wb").close()
return path
async def create_site_photo(url):
loop = asyncio.get_event_loop()
if not url.startswith("https"):
url = "https://" + url
output_path = await get_file("site-screenshot-" + url, ".png")
if output_path.exists():
return output_path
output_path.touch()
@ -71,21 +74,23 @@ async def create_site_photo(url):
return await loop.run_in_executor(None, make_photo)
async def repair_links(base_url, html_content):
soup = BeautifulSoup(html_content, "html.parser")
for tag in soup.find_all(['a', 'img', 'link']):
if tag.has_attr('href') and not tag['href'].startswith("http"):
tag['href'] = urljoin(base_url, tag['href'])
if tag.has_attr('src') and not tag['src'].startswith("http"):
tag['src'] = urljoin(base_url, tag['src'])
for tag in soup.find_all(["a", "img", "link"]):
if tag.has_attr("href") and not tag["href"].startswith("http"):
tag["href"] = urljoin(base_url, tag["href"])
if tag.has_attr("src") and not tag["src"].startswith("http"):
tag["src"] = urljoin(base_url, tag["src"])
return soup.prettify()
async def is_html_content(content: bytes):
try:
content = content.decode(errors='ignore')
content = content.decode(errors="ignore")
except:
pass
marks = ['<html', '<img', '<p', '<span', '<div']
marks = ["<html", "<img", "<p", "<span", "<div"]
try:
content = content.lower()
for mark in marks:
@ -95,6 +100,7 @@ async def is_html_content(content: bytes):
print(ex)
return False
@time_cache_async(120)
async def get(url):
async with aiohttp.ClientSession() as session:
@ -102,4 +108,4 @@ async def get(url):
content = await response.text()
if await is_html_content(content):
content = (await repair_links(url, content)).encode()
return content
return content

View File

@ -1,23 +1,22 @@
DEFAULT_LIMIT = 30
import typing
from snek.system.model import BaseModel
import types
class BaseMapper:
model_class:BaseModel = None
default_limit:int = DEFAULT_LIMIT
table_name:str = None
model_class: BaseModel = None
default_limit: int = DEFAULT_LIMIT
table_name: str = None
def __init__(self, app):
self.app = app
self.default_limit = self.__class__.default_limit
self.app = app
self.default_limit = self.__class__.default_limit
@property
def db(self):
def db(self):
return self.app.db
async def new(self):
@ -27,12 +26,12 @@ class BaseMapper:
def table(self):
return self.db[self.table_name]
async def get(self, uid:str=None, **kwargs) -> BaseModel:
async def get(self, uid: str = None, **kwargs) -> BaseModel:
if uid:
kwargs['uid'] = uid
model = self.new()
kwargs["uid"] = uid
self.new()
record = self.table.find_one(**kwargs)
return await self.model_class.from_record(mapper=self,record=record)
return await self.model_class.from_record(mapper=self, record=record)
async def exists(self, **kwargs):
return self.table.exists(**kwargs)
@ -40,19 +39,19 @@ class BaseMapper:
async def count(self, **kwargs) -> int:
return self.table.count(**kwargs)
async def save(self, model:BaseModel) -> bool:
async def save(self, model: BaseModel) -> bool:
record = await model.record
if not record.get('uid'):
if not record.get("uid"):
raise Exception(f"Attempt to save without uid: {record}.")
return self.table.upsert(record,['uid'])
return self.table.upsert(record, ["uid"])
async def find(self, **kwargs) -> typing.AsyncGenerator:
if not kwargs.get("_limit"):
kwargs["_limit"] = self.default_limit
for record in self.table.find(**kwargs):
yield await self.model_class.from_record(mapper=self,record=record)
async def delete(self, kwargs=None)-> int:
yield await self.model_class.from_record(mapper=self, record=record)
async def delete(self, kwargs=None) -> int:
if not kwargs or not isinstance(kwargs, dict):
raise Exception("Can't execute delete with no filter.")
return self.table.delete(**kwargs)

View File

@ -1,62 +1,65 @@
# Original source: https://brandonjay.dev/posts/2021/render-markdown-html-in-python-with-jinja2
from types import SimpleNamespace
from mistune import escape
from mistune import Markdown
from mistune import HTMLRenderer
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import html
from pygments.styles import get_style_by_name
import functools
from app.cache import time_cache_async
from mistune import HTMLRenderer, Markdown
from pygments import highlight
from pygments.formatters import html
from pygments.lexers import get_lexer_by_name
class MarkdownRenderer(HTMLRenderer):
_allow_harmful_protocols = True
def __init__(self, app, template):
self.template = template
self.app = app
self.env = self.app.jinja2_env
formatter = html.HtmlFormatter()
self.env.globals['highlight_styles'] = formatter.get_style_defs()
def _escape(self,str):
return str ##escape(str)
def block_code(self, code, lang=None,info=None):
self.template = template
self.app = app
self.env = self.app.jinja2_env
formatter = html.HtmlFormatter()
self.env.globals["highlight_styles"] = formatter.get_style_defs()
def _escape(self, str):
return str ##escape(str)
def block_code(self, code, lang=None, info=None):
if not lang:
lang = info
if not lang:
return f"<div>{code}</div>"
#return '\n<pre><code>%s</code></pre>\n' % escape(code)
# return '\n<pre><code>%s</code></pre>\n' % escape(code)
lexer = get_lexer_by_name(lang, stripall=True)
formatter = html.HtmlFormatter(lineseparator="<br>")
return highlight(code, lexer, formatter)
def render(self):
markdown_string = self.app.template_path.joinpath(self.template).read_text()
renderer = MarkdownRenderer(self.app,self.template)
renderer = MarkdownRenderer(self.app, self.template)
markdown = Markdown(renderer=renderer)
return markdown(markdown_string)
def render_markdown_sync(app, markdown_string):
renderer = MarkdownRenderer(app,None)
renderer = MarkdownRenderer(app, None)
markdown = Markdown(renderer=renderer)
return markdown(markdown_string)
@time_cache_async(120)
async def render_markdown(app, markdown_string):
return render_markdown_sync(app,markdown_string)
return render_markdown_sync(app, markdown_string)
from jinja2 import nodes, TemplateSyntaxError
from jinja2 import TemplateSyntaxError, nodes
from jinja2.ext import Extension
from jinja2.nodes import Const
# Source: https://ron.sh/how-to-write-a-jinja2-extension/
class MarkdownExtension(Extension):
tags = {'markdown'}
tags = {"markdown"}
def __init__(self, environment):
self.app = SimpleNamespace(jinja2_env=environment)
@ -64,13 +67,15 @@ class MarkdownExtension(Extension):
def parse(self, parser):
line_number = next(parser.stream).lineno
md_file = [Const('')]
body = ''
md_file = [Const("")]
body = ""
try:
md_file = [parser.parse_expression()]
except TemplateSyntaxError:
body = parser.parse_statements(['name:endmarkdown'], drop_needle=True)
return nodes.CallBlock(self.call_method('_to_html', md_file), [], [], body).set_lineno(line_number)
body = parser.parse_statements(["name:endmarkdown"], drop_needle=True)
return nodes.CallBlock(
self.call_method("_to_html", md_file), [], [], body
).set_lineno(line_number)
def _to_html(self, md_file, caller):
return render_markdown_sync(self.app,caller())
return render_markdown_sync(self.app, caller())

View File

@ -8,12 +8,14 @@
from aiohttp import web
@web.middleware
async def no_cors_middleware(request, handler):
response = await handler(request)
response.headers.pop("Access-Control-Allow-Origin", None)
return response
@web.middleware
async def cors_allow_middleware(request, handler):
response = await handler(request)
@ -22,12 +24,15 @@ async def cors_allow_middleware(request, handler):
response.headers["Access-Control-Allow-Headers"] = "*"
return response
@web.middleware
async def cors_middleware(request, handler):
if request.method == "OPTIONS":
response = web.Response()
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Methods"] = (
"GET, POST, PUT, DELETE, OPTIONS"
)
response.headers["Access-Control-Allow-Headers"] = "*"
return response
@ -35,4 +40,4 @@ async def cors_middleware(request, handler):
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"
return response
return response

View File

@ -25,12 +25,12 @@
# SOFTWARE.
import copy
import json
import re
import uuid
import json
from datetime import datetime, timezone
from collections import OrderedDict
import copy
from datetime import datetime, timezone
TIMESTAMP_REGEX = r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}\+\d{2}:\d{2}$"
@ -44,12 +44,21 @@ def add_attrs(**kwargs):
for key, value in kwargs.items():
setattr(func, key, value)
return func
return decorator
def validate_attrs(required=False, min_length=None, max_length=None, regex=None, **kwargs):
def validate_attrs(
required=False, min_length=None, max_length=None, regex=None, **kwargs
):
def decorator(func):
return add_attrs(required=required, min_length=min_length, max_length=max_length, regex=regex, **kwargs)(func)
return add_attrs(
required=required,
min_length=min_length,
max_length=max_length,
regex=regex,
**kwargs,
)(func)
class Validator:
@ -70,7 +79,21 @@ class Validator:
def custom_validation(self):
return True
def __init__(self, required=False, min_num=None, max_num=None, min_length=None, max_length=None, regex=None, value=None, kind=None, help_text=None, app=None, model=None, **kwargs):
def __init__(
self,
required=False,
min_num=None,
max_num=None,
min_length=None,
max_length=None,
regex=None,
value=None,
kind=None,
help_text=None,
app=None,
model=None,
**kwargs,
):
self.index = Validator._index
Validator._index += 1
self.app = app
@ -103,9 +126,13 @@ class Validator:
if self.max_num is not None and self.value > self.max_num:
error_list.append(f"Field should be maximal {self.max_num}.")
if self.min_length is not None and len(self.value) < self.min_length:
error_list.append(f"Field should be minimal {self.min_length} characters long.")
error_list.append(
f"Field should be minimal {self.min_length} characters long."
)
if self.max_length is not None and len(self.value) > self.max_length:
error_list.append(f"Field should be maximal {self.max_length} characters long.")
error_list.append(
f"Field should be maximal {self.max_length} characters long."
)
if self.regex and self.value and not re.match(self.regex, self.value):
error_list.append("Invalid value.")
if self.kind and not isinstance(self.value, self.kind):
@ -141,7 +168,7 @@ class Validator:
"help_text": self.help_text,
"errors": errors,
"is_valid": is_valid,
"index": self.index
"index": self.index,
}
@ -156,7 +183,7 @@ class ModelField(Validator):
async def to_json(self):
result = await super().to_json()
result['name'] = self.name
result["name"] = self.name
return result
@ -193,30 +220,39 @@ class UUIDField(ModelField):
class BaseModel:
uid = UUIDField(name="uid", required=True)
created_at = CreatedField(name="created_at", required=True, regex=TIMESTAMP_REGEX, place_holder="Created at")
updated_at = UpdatedField(name="updated_at", regex=TIMESTAMP_REGEX, place_holder="Updated at")
deleted_at = DeletedField(name="deleted_at", regex=TIMESTAMP_REGEX, place_holder="Deleted at")
created_at = CreatedField(
name="created_at",
required=True,
regex=TIMESTAMP_REGEX,
place_holder="Created at",
)
updated_at = UpdatedField(
name="updated_at", regex=TIMESTAMP_REGEX, place_holder="Updated at"
)
deleted_at = DeletedField(
name="deleted_at", regex=TIMESTAMP_REGEX, place_holder="Deleted at"
)
@classmethod
@classmethod
async def from_record(cls, record, mapper):
model = cls.__new__()
model.mapper = mapper
model.mapper = mapper
model.record = record
return model
@property
@property
def mapper(self):
return self._mapper
return self._mapper
@mapper.setter
@mapper.setter
def mapper(self, value):
self._mapper = value
self._mapper = value
@property
@property
def record(self):
return {field.name: field.value for field in self.fields}
@record.setter
@record.setter
def record(self, value):
for key, value in self._record.items():
field = self.fields.get(key)
@ -233,10 +269,12 @@ class BaseModel:
if isinstance(obj, Validator):
self.__dict__[key] = copy.deepcopy(obj)
self.__dict__[key].value = kwargs.pop(key, self.__dict__[key].initial_value)
self.__dict__[key].value = kwargs.pop(
key, self.__dict__[key].initial_value
)
self.fields[key] = self.__dict__[key]
self.fields[key].model = self
self.fields[key].app = kwargs.get('app')
self.fields[key].app = kwargs.get("app")
def __setitem__(self, key, value):
obj = self.__dict__.get(key)
@ -254,16 +292,13 @@ class BaseModel:
field = self.fields.get(key)
if not field:
continue
if value.get('name'):
value = value.get('value')
if value.get("name"):
value = value.get("value")
field.value = value
@property
async def is_valid(self):
return all([await field.is_valid for field in self.fields.values()])
def __getitem__(self, key):
obj = self.__dict__.get(key)
@ -282,20 +317,22 @@ class BaseModel:
obj = await self.to_json()
record = {}
for key, value in obj.items():
if not isinstance(value, dict) or not 'value' in value:
if not isinstance(value, dict) or "value" not in value:
continue
if getattr(self, key).save:
record[key] = value.get('value')
record[key] = value.get("value")
return record
async def to_json(self, encode=False):
model_data = OrderedDict({
"uid": self.uid.value,
"created_at": self.created_at.value,
"updated_at": self.updated_at.value,
"deleted_at": self.deleted_at.value,
"is_valid": await self.is_valid
})
model_data = OrderedDict(
{
"uid": self.uid.value,
"created_at": self.created_at.value,
"updated_at": self.updated_at.value,
"deleted_at": self.deleted_at.value,
"is_valid": await self.is_valid,
}
)
for key, value in self.fields.items():
if key == "record":
@ -325,4 +362,4 @@ class FormElement(ModelField):
data = await super().to_json()
data["name"] = self.name
data["place_holder"] = self.place_holder
return data
return data

View File

@ -1,12 +1,13 @@
import hashlib
import hashlib
DEFAULT_SALT = b"snekker-de-snek-"
async def hash(data,salt=DEFAULT_SALT):
async def hash(data, salt=DEFAULT_SALT):
try:
data = data.encode(errors="ignore")
except AttributeError:
pass
pass
try:
salt = salt.encode(errors="ignore")
except AttributeError:
@ -16,5 +17,6 @@ async def hash(data,salt=DEFAULT_SALT):
obj = hashlib.sha256(salted)
return obj.hexdigest()
async def verify(string:str, hashed:str):
return await hash(string) == hashed
async def verify(string: str, hashed: str):
return await hash(string) == hashed

View File

@ -1,24 +1,22 @@
from snek.mapper import get_mapper
from snek.system.mapper import BaseMapper
from snek.model.user import UserModel
from snek.system.mapper import BaseMapper
class BaseService:
mapper_name:BaseMapper = None
mapper_name: BaseMapper = None
def __init__(self, app):
self.app = app
self.app = app
if self.mapper_name:
self.mapper = get_mapper(self.mapper_name, app=self.app)
else:
self.mapper = None
self.mapper = None
async def exists(self, **kwargs):
return await self.count(**kwargs) > 0
async def count(self, **kwargs):
return await self.mapper.count(**kwargs)
@ -27,14 +25,13 @@ class BaseService:
async def get(self, **kwargs):
return await self.mapper.get(**kwargs)
async def save(self, model:UserModel):
async def save(self, model: UserModel):
# if model.is_valid: You Know why not
return await self.mapper.save(model) and True
return await self.mapper.save(model) and True
async def find(self, **kwargs):
return await self.mapper.find(**kwargs)
async def delete(self, **kwargs):
return await self.mapper.delete(**kwargs)
return await self.mapper.delete(**kwargs)

View File

@ -1,13 +1,14 @@
from aiohttp import web
from snek.system.markdown import render_markdown
from snek.system.markdown import render_markdown
class BaseView(web.View):
@property
@property
def app(self):
return self.request.app
@property
def db(self):
return self.app.db
@ -17,31 +18,36 @@ class BaseView(web.View):
async def render_template(self, template_name, context=None):
if template_name.endswith(".md"):
response = await self.request.app.render_template(template_name,self.request,context)
response = await self.request.app.render_template(
template_name, self.request, context
)
body = await render_markdown(self.app, response.body.decode())
return web.Response(body=body,content_type="text/html")
return await self.request.app.render_template(template_name, self.request,context)
return web.Response(body=body, content_type="text/html")
return await self.request.app.render_template(
template_name, self.request, context
)
class BaseFormView(BaseView):
form = None
form = None
async def get(self):
form = self.form(app=self.app)
return await self.json_response(await form.to_json())
async def post(self):
form = self.form(app=self.app)
post = await self.request.json()
form.set_user_data(post['form'])
form.set_user_data(post["form"])
result = await form.to_json()
if post.get('action') == 'validate':
if post.get("action") == "validate":
# Pass
pass
if post.get('action') == 'submit' and result['is_valid']:
if post.get("action") == "submit" and result["is_valid"]:
await self.submit(form)
return await self.json_response(result)
return await self.json_response(result)
async def submit(self,model=None):
async def submit(self, model=None):
print("Submit sucess")

View File

@ -9,8 +9,7 @@ Currently only some details about the internal API are available.
# of the snek.system.security module.
new_user_object = await app.service.user.register(
username="retoor",
password="retoorded"
username="retoor", password="retoorded"
)
```
@ -23,15 +22,16 @@ var1 = security.encrypt("data")
var2 = security.encrypt(b"data")
# Is correct:
assert(var1 == var2)
assert var1 == var2
```
## How to create a basic HTML / Markdown view
```python
from snek.system.view import BaseView
from snek.system.view import BaseView
class IndexView(BaseView):
async def get(self):
# The render function supports markdown.
# It will render with syntax highlighting.
@ -40,11 +40,12 @@ class IndexView(BaseView):
```
## How to create a FormView
```python
from snek.system.view import BaseFormView
from snek.form.register import RegisterForm
from snek.system.view import BaseFormView
class RegisterFormView(BaseFormView):
form = RegisterForm
```
## How to register a class view

View File

@ -1,5 +1,3 @@
from snek.system.view import BaseView
@ -7,8 +5,9 @@ class AboutHTMLView(BaseView):
async def get(self):
return await self.render_template("about.html")
class AboutMDView(BaseView):
async def get(self):
return await self.render_template("about.md")
return await self.render_template("about.md")

View File

@ -1,6 +1,3 @@
from snek.system.view import BaseView
@ -8,8 +5,9 @@ class DocsHTMLView(BaseView):
async def get(self):
return await self.render_template("docs.html")
class DocsMDView(BaseView):
async def get(self):
return await self.render_template("docs.md")
return await self.render_template("docs.md")

View File

@ -1,5 +1,6 @@
from snek.system.view import BaseView
class IndexView(BaseView):
async def get(self):

View File

@ -1,13 +1,18 @@
from snek.form.register import RegisterForm
from snek.system.view import BaseView
class LoginView(BaseView):
async def get(self):
return await self.render_template("login.html") #web.json_response({"form": RegisterForm().to_json()})
return await self.render_template(
"login.html"
) # web.json_response({"form": RegisterForm().to_json()})
async def post(self):
form = RegisterForm()
form.set_user_data(await self.request.post())
print(form.is_valid())
return await self.render_template("login.html", self.request) #web.json_response({"form": RegisterForm().to_json()})
return await self.render_template(
"login.html", self.request
) # web.json_response({"form": RegisterForm().to_json()})

View File

@ -1,5 +1,6 @@
from snek.system.view import BaseFormView
from snek.form.login import LoginForm
from snek.system.view import BaseFormView
class LoginFormView(BaseFormView):
form = LoginForm
form = LoginForm

View File

@ -1,6 +1,7 @@
from snek.system.view import BaseView
class RegisterView(BaseView):
async def get(self):
return await self.render_template("register.html")
return await self.render_template("register.html")

View File

@ -1,9 +1,12 @@
from snek.form.register import RegisterForm
from snek.system.view import BaseFormView
class RegisterFormView(BaseFormView):
form = RegisterForm
async def submit(self, form):
result = await self.app.services.user.register(form.email.value,form.username.value,form.password.value)
print("SUBMITTED:",result)
result = await self.app.services.user.register(
form.email.value, form.username.value, form.password.value
)
print("SUBMITTED:", result)

View File

@ -1,6 +1,7 @@
from snek.system.view import BaseView
class WebView(BaseView):
async def get(self):
return await self.render_template("web.html")
return await self.render_template("web.html")