Compare commits

..

No commits in common. "9b93403a93ac0b03a57fb5dc10db5c35349c4d6f" and "2ba55f692dfc1b60ac55d514b182fb8834cb99bb" have entirely different histories.

29 changed files with 239 additions and 366 deletions

View File

@ -1,14 +1,12 @@
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 jinja_markdown2 import MarkdownExtension
from snek.mapper import get_mappers
from snek.service import get_services
from snek.system import http
from snek.system.markdown import MarkdownExtension
from snek.system.middleware import cors_middleware
from snek.view.about import AboutHTMLView, AboutMDView
from snek.view.docs import DocsHTMLView, DocsMDView
@ -18,6 +16,7 @@ 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 +50,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,11 +59,6 @@ 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")),
)
async def handle_test(self, request):
return await self.render_template(
@ -82,8 +76,9 @@ 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,27 +1,24 @@
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement
from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement
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",
)
password = FormInputElement(
name="password",
required=True,
regex=r"^[a-zA-Z0-9_.+-]{6,}",
type="password",
place_holder="Password",
type="text"
)
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,5 +1,4 @@
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement
from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement
class UsernameField(FormInputElement):
@ -10,35 +9,32 @@ 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",
)
password = FormInputElement(
name="password",
required=True,
regex=r"^[a-zA-Z0-9_.+-]{6,}",
type="password",
place_holder="Password",
type="email"
)
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
@functools.cache
def get_mappers(app=None):
return {"user": UserMapper(app=app)}
return dict(
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,7 +1,6 @@
from snek.model.user import UserModel
from snek.system.mapper import BaseMapper
from snek.model.user import UserModel
class UserMapper(BaseMapper):
table_name = "user"
model_class = UserModel
model_class = UserModel

View File

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

View File

@ -1,10 +1,9 @@
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,
@ -13,6 +12,8 @@ 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,13 +1,12 @@
import functools
from snek.service.user import UserService
from snek.service.user import UserService
import functools
@functools.cache
def get_services(app):
return {"user": UserService(app=app)}
return dict(
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,6 +1,5 @@
from snek.system import security
from snek.system.service import BaseService
from snek.system.service import BaseService
from snek.system import security
class UserService(BaseService):
mapper_name = "user"
@ -13,5 +12,6 @@ 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,19 +26,8 @@
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
@ -48,18 +37,16 @@ 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)
@ -72,27 +59,25 @@ 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:
@ -100,12 +85,8 @@ class Form(model.BaseModel):
except KeyError:
pass
is_valid = all(field["is_valid"] for field in html_elements.values())
return {
"fields": html_elements,
"is_valid": is_valid,
"errors": await self.errors,
}
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)
@property
async def errors(self):
@ -117,4 +98,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.
import asyncio
import pathlib
import uuid
import zlib
from urllib.parse import urljoin
from aiohttp import web
import aiohttp
import imgkit
from app.cache import time_cache_async
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import pathlib
import uuid
import imgkit
import asyncio
import zlib
import io
async def crc32(data):
try:
@ -43,7 +43,6 @@ 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")
@ -51,19 +50,17 @@ 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()
@ -74,23 +71,21 @@ 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:
@ -100,7 +95,6 @@ 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:
@ -108,4 +102,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,22 +1,23 @@
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):
@ -26,12 +27,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
self.new()
kwargs['uid'] = uid
model = 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)
@ -39,19 +40,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,81 +1,48 @@
# Original source: https://brandonjay.dev/posts/2021/render-markdown-html-in-python-with-jinja2
from types import SimpleNamespace
from app.cache import time_cache_async
from mistune import HTMLRenderer, Markdown
from mistune import escape
from mistune import Markdown
from mistune import HTMLRenderer
from pygments import highlight
from pygments.formatters import html
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
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)
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"}
def __init__(self, environment):
self.app = SimpleNamespace(jinja2_env=environment)
super(MarkdownExtension, self).__init__(environment)
def parse(self, parser):
line_number = next(parser.stream).lineno
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)
def _to_html(self, md_file, caller):
return render_markdown_sync(self.app, caller())
return render_markdown_sync(app,markdown_string)

View File

@ -8,14 +8,12 @@
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)
@ -24,15 +22,12 @@ 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
@ -40,4 +35,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
from collections import OrderedDict
import json
from datetime import datetime, timezone
from collections import OrderedDict
import copy
TIMESTAMP_REGEX = r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}\+\d{2}:\d{2}$"
@ -44,21 +44,12 @@ 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:
@ -79,21 +70,7 @@ 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
@ -126,13 +103,9 @@ 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):
@ -168,7 +141,7 @@ class Validator:
"help_text": self.help_text,
"errors": errors,
"is_valid": is_valid,
"index": self.index,
"index": self.index
}
@ -183,7 +156,7 @@ class ModelField(Validator):
async def to_json(self):
result = await super().to_json()
result["name"] = self.name
result['name'] = self.name
return result
@ -220,39 +193,30 @@ 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)
@ -269,12 +233,10 @@ 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)
@ -292,13 +254,16 @@ 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)
@ -317,22 +282,20 @@ class BaseModel:
obj = await self.to_json()
record = {}
for key, value in obj.items():
if not isinstance(value, dict) or "value" not in value:
if not isinstance(value, dict) or not 'value' 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":
@ -362,4 +325,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,13 +1,12 @@
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:
@ -17,6 +16,5 @@ 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,22 +1,24 @@
from snek.mapper import get_mapper
from snek.model.user import UserModel
from snek.system.mapper import BaseMapper
from snek.mapper import get_mapper
from snek.system.mapper import BaseMapper
from snek.model.user import UserModel
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)
@ -25,13 +27,14 @@ 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,14 +1,13 @@
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
@ -18,36 +17,31 @@ 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,7 +9,8 @@ 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"
)
```
@ -22,16 +23,15 @@ 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,12 +40,11 @@ class IndexView(BaseView):
```
## How to create a FormView
```python
from snek.form.register import RegisterForm
from snek.system.view import BaseFormView
from snek.form.register import RegisterForm
class RegisterFormView(BaseFormView):
form = RegisterForm
```
## How to register a class view

View File

@ -16,7 +16,7 @@
<fancy-button url="/register.html" text="Register"></fancy-button>
<a href="/about.html">Design choices</a>
<a href="/web.html">App preview</a>
<a href="/docs/docs/">API docs</a>
<a href="/docs.html">API docs</a>
</div>
</body>
</html>

View File

@ -1,3 +1,5 @@
from snek.system.view import BaseView
@ -5,9 +7,8 @@ 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,3 +1,6 @@
from snek.system.view import BaseView
@ -5,9 +8,8 @@ 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,6 +1,5 @@
from snek.system.view import BaseView
class IndexView(BaseView):
async def get(self):

View File

@ -1,18 +1,13 @@
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,6 +1,5 @@
from snek.form.login import LoginForm
from snek.system.view import BaseFormView
from snek.form.login import LoginForm
class LoginFormView(BaseFormView):
form = LoginForm
form = LoginForm

View File

@ -1,7 +1,6 @@
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,12 +1,9 @@
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,7 +1,6 @@
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")