Formatting.
This commit is contained in:
parent
18b76ebd5e
commit
9b93403a93
@ -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)
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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,}")
|
||||
|
@ -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]
|
||||
|
@ -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}.")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -1,5 +1,6 @@
|
||||
from snek.system.view import BaseView
|
||||
|
||||
|
||||
class IndexView(BaseView):
|
||||
|
||||
async def get(self):
|
||||
|
@ -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()})
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user