Progress.
This commit is contained in:
parent
2e3b85d7f7
commit
ba83922660
@ -37,4 +37,5 @@ RUN pip install --upgrade pip
|
|||||||
RUN pip install -e .
|
RUN pip install -e .
|
||||||
EXPOSE 8081
|
EXPOSE 8081
|
||||||
|
|
||||||
CMD ["gunicorn", "-w", "10", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"]
|
python -m snek.app
|
||||||
|
#CMD ["gunicorn", "-w", "10", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"]
|
||||||
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 retoor
|
||||||
|
|
||||||
|
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
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
2
Makefile
2
Makefile
@ -10,6 +10,8 @@ install:
|
|||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
$(PIP) install -e .
|
$(PIP) install -e .
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
run:
|
run:
|
||||||
$(GUNICORN) -w $(GUNICORN_WORKERS) -k aiohttp.worker.GunicornWebWorker snek.gunicorn:app --bind 0.0.0.0:$(PORT) --reload
|
$(GUNICORN) -w $(GUNICORN_WORKERS) -k aiohttp.worker.GunicornWebWorker snek.gunicorn:app --bind 0.0.0.0:$(PORT) --reload
|
||||||
|
|
||||||
|
@ -6,3 +6,5 @@ services:
|
|||||||
- "8081:8081"
|
- "8081:8081"
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/code
|
- ./:/code
|
||||||
|
entrypoint: ["python","-m","snek.app"]
|
||||||
|
|
@ -1,3 +1,25 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools", "wheel"]
|
requires = ["setuptools", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "Snek"
|
||||||
|
version = "1.0.0"
|
||||||
|
readme = "README.md"
|
||||||
|
license = { file = "LICENSE", content-type="text/markdown" }
|
||||||
|
description = "Snek Chat Application by Molodetz"
|
||||||
|
authors = [
|
||||||
|
{ name = "retoor", email = "retoor@molodetz.nl" }
|
||||||
|
]
|
||||||
|
keywords = ["chat", "snek", "molodetz"]
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"mkdocs>=1.4.0",
|
||||||
|
"shed",
|
||||||
|
"app @ git+https://retoor.molodetz.nl/retoor/app",
|
||||||
|
"beautifulsoup4",
|
||||||
|
"gunicorn",
|
||||||
|
"imgkit",
|
||||||
|
"wkhtmltopdf"
|
||||||
|
]
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ install_requires =
|
|||||||
gunicorn
|
gunicorn
|
||||||
imgkit
|
imgkit
|
||||||
wkhtmltopdf
|
wkhtmltopdf
|
||||||
|
shed
|
||||||
|
|
||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
where = src
|
where = src
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
from app.app import Application as BaseApplication
|
|
||||||
from snek.forms import RegisterForm
|
|
||||||
from aiohttp import web
|
|
||||||
import aiohttp
|
|
||||||
import pathlib
|
import pathlib
|
||||||
from snek import http
|
|
||||||
from snek.middleware import cors_allow_middleware,cors_middleware
|
from aiohttp import web
|
||||||
|
from app.app import Application as BaseApplication
|
||||||
|
|
||||||
|
from snek.system import http
|
||||||
|
from snek.system.middleware import cors_middleware
|
||||||
|
from snek.view.index import IndexView
|
||||||
|
from snek.view.login import LoginView
|
||||||
|
from snek.view.login_form import LoginFormView
|
||||||
|
from snek.view.register import RegisterView
|
||||||
|
from snek.view.register_form import RegisterFormView
|
||||||
|
from snek.view.view import WebView
|
||||||
|
|
||||||
|
|
||||||
class Application(BaseApplication):
|
class Application(BaseApplication):
|
||||||
@ -12,23 +18,38 @@ class Application(BaseApplication):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
middlewares = [
|
middlewares = [
|
||||||
cors_middleware,
|
cors_middleware,
|
||||||
web.normalize_path_middleware(merge_slashes=True)
|
web.normalize_path_middleware(merge_slashes=True),
|
||||||
]
|
]
|
||||||
self.template_path = pathlib.Path(__file__).parent.joinpath("templates")
|
self.template_path = pathlib.Path(__file__).parent.joinpath("templates")
|
||||||
super().__init__(middlewares=middlewares, template_path=self.template_path,*args, **kwargs)
|
super().__init__(
|
||||||
self.router.add_static("/",pathlib.Path(__file__).parent.joinpath("static"),name="static",show_index=True)
|
middlewares=middlewares, template_path=self.template_path, *args, **kwargs
|
||||||
self.router.add_get("/register", self.handle_register)
|
)
|
||||||
self.router.add_get("/login", self.handle_login)
|
self.setup_router()
|
||||||
self.router.add_get("/test", self.handle_test)
|
|
||||||
self.router.add_post("/register", self.handle_register)
|
|
||||||
self.router.add_get("/http-get",self.handle_http_get)
|
|
||||||
self.router.add_get("/http-photo",self.handle_http_photo)
|
|
||||||
|
|
||||||
async def handle_test(self,request):
|
def setup_router(self):
|
||||||
|
self.router.add_get("/", IndexView)
|
||||||
|
self.router.add_static(
|
||||||
|
"/",
|
||||||
|
pathlib.Path(__file__).parent.joinpath("static"),
|
||||||
|
name="static",
|
||||||
|
show_index=True,
|
||||||
|
)
|
||||||
|
self.router.add_view("/web", WebView)
|
||||||
|
self.router.add_view("/login", LoginView)
|
||||||
|
self.router.add_view("/login-form", LoginFormView)
|
||||||
|
self.router.add_view("/register", RegisterView)
|
||||||
|
|
||||||
|
self.router.add_view("/register-form", RegisterFormView)
|
||||||
|
self.router.add_get("/http-get", self.handle_http_get)
|
||||||
|
self.router.add_get("/http-photo", self.handle_http_photo)
|
||||||
|
|
||||||
return await self.render_template("test.html",request,context={"name":"retoor"})
|
async def handle_test(self, request):
|
||||||
|
|
||||||
async def handle_http_get(self, request:web.Request):
|
return await self.render_template(
|
||||||
|
"test.html", request, context={"name": "retoor"}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def handle_http_get(self, request: web.Request):
|
||||||
url = request.query.get("url")
|
url = request.query.get("url")
|
||||||
content = await http.get(url)
|
content = await http.get(url)
|
||||||
return web.Response(body=content)
|
return web.Response(body=content)
|
||||||
@ -36,25 +57,13 @@ class Application(BaseApplication):
|
|||||||
async def handle_http_photo(self, request):
|
async def handle_http_photo(self, request):
|
||||||
url = request.query.get("url")
|
url = request.query.get("url")
|
||||||
path = await http.create_site_photo(url)
|
path = await http.create_site_photo(url)
|
||||||
return web.Response(body=path.read_bytes(),headers={
|
return web.Response(
|
||||||
"Content-Type": "image/png"
|
body=path.read_bytes(), headers={"Content-Type": "image/png"}
|
||||||
})
|
)
|
||||||
|
|
||||||
async def handle_login(self, request):
|
|
||||||
if request.method == "GET":
|
|
||||||
return await self.render_template("login.html", request) #web.json_response({"form": RegisterForm().to_json()})
|
|
||||||
elif request.method == "POST":
|
|
||||||
return await self.render_template("login.html", request) #web.json_response({"form": RegisterForm().to_json()})
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_register(self, request):
|
|
||||||
if request.method == "GET":
|
|
||||||
return await self.render_template("register.html", request) #web.json_response({"form": RegisterForm().to_json()})
|
|
||||||
elif request.method == "POST":
|
|
||||||
return self.render("register.html")
|
|
||||||
|
|
||||||
app = Application()
|
app = Application()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
|
|
||||||
web.run_app(app,port=8081,host="0.0.0.0")
|
web.run_app(app, port=8081, host="0.0.0.0")
|
||||||
|
0
src/snek/form/__init__.py
Normal file
0
src/snek/form/__init__.py
Normal file
24
src/snek/form/login.py
Normal file
24
src/snek/form/login.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement
|
||||||
|
|
||||||
|
class LoginForm(Form):
|
||||||
|
|
||||||
|
title = HTMLElement(tag="h1", text="Login")
|
||||||
|
|
||||||
|
username = FormInputElement(
|
||||||
|
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")
|
||||||
|
|
||||||
|
action = FormButtonElement(
|
||||||
|
name="action",
|
||||||
|
value="submit",
|
||||||
|
text="Login",
|
||||||
|
type="button"
|
||||||
|
)
|
||||||
|
|
31
src/snek/form/register.py
Normal file
31
src/snek/form/register.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from snek.system.form import Form, HTMLElement,FormInputElement,FormButtonElement
|
||||||
|
|
||||||
|
class RegisterForm(Form):
|
||||||
|
|
||||||
|
title = HTMLElement(tag="h1", text="Register")
|
||||||
|
|
||||||
|
username = FormInputElement(
|
||||||
|
name="username",
|
||||||
|
required=True,
|
||||||
|
min_length=2,
|
||||||
|
max_length=20,
|
||||||
|
regex=r"^[a-zA-Z0-9_]+$",
|
||||||
|
place_holder="Username",
|
||||||
|
type="text"
|
||||||
|
)
|
||||||
|
email = FormInputElement(
|
||||||
|
name="email",
|
||||||
|
required=True,
|
||||||
|
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")
|
||||||
|
|
||||||
|
action = FormButtonElement(
|
||||||
|
name="action",
|
||||||
|
value="submit",
|
||||||
|
text="Register",
|
||||||
|
type="button"
|
||||||
|
)
|
||||||
|
|
@ -1,40 +0,0 @@
|
|||||||
from snek import models
|
|
||||||
|
|
||||||
class FormElement(models.ModelField):
|
|
||||||
|
|
||||||
def __init__(self,html_type, place_holder=None, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.place_holder = place_holder
|
|
||||||
self.html_type = html_type
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
data = super().to_json()
|
|
||||||
data["html_type"] = self.html_type
|
|
||||||
data["place_holder"] = self.place_holder
|
|
||||||
return data
|
|
||||||
|
|
||||||
class Form(models.BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class RegisterForm(Form):
|
|
||||||
|
|
||||||
username = FormElement(
|
|
||||||
name="username",
|
|
||||||
required=True,
|
|
||||||
min_length=2,
|
|
||||||
max_length=20,
|
|
||||||
regex=r"^[a-zA-Z0-9_]+$",
|
|
||||||
place_holder="Username",
|
|
||||||
html_type="text"
|
|
||||||
)
|
|
||||||
email = FormElement(
|
|
||||||
name="email",
|
|
||||||
required=True,
|
|
||||||
regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
|
|
||||||
place_holder="Email address",
|
|
||||||
html_type="email"
|
|
||||||
)
|
|
||||||
password = FormElement(name="password",required=True,regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",html_type="password")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
|||||||
from snek.app import app
|
from snek.app import app
|
||||||
|
|
||||||
application = app
|
application = app
|
||||||
|
0
src/snek/model/__init__.py
Normal file
0
src/snek/model/__init__.py
Normal file
19
src/snek/model/user.py
Normal file
19
src/snek/model/user.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from snek.system.model import BaseModel,ModelField
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
|
||||||
|
username = ModelField(
|
||||||
|
name="username",
|
||||||
|
required=True,
|
||||||
|
min_length=2,
|
||||||
|
max_length=20,
|
||||||
|
regex=r"^[a-zA-Z0-9_]+$",
|
||||||
|
)
|
||||||
|
email = ModelField(
|
||||||
|
name="email",
|
||||||
|
required=True,
|
||||||
|
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,}")
|
||||||
|
|
||||||
|
|
@ -57,13 +57,26 @@ class Room {
|
|||||||
this.name = name
|
this.name = name
|
||||||
}
|
}
|
||||||
setMessages(list){
|
setMessages(list){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class InlineAppElement extends HTMLElement {
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
this.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Page {
|
||||||
|
elements = []
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
rooms = []
|
rooms = []
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
/* General Reset */
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Body Styling */
|
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
@ -16,7 +14,6 @@ body {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header Navigation */
|
|
||||||
header {
|
header {
|
||||||
background-color: #0f0f0f;
|
background-color: #0f0f0f;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
@ -43,14 +40,12 @@ header nav a:hover {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Main Layout */
|
|
||||||
main {
|
main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar */
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
background-color: #121212;
|
background-color: #121212;
|
||||||
@ -84,7 +79,6 @@ main {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat Area */
|
|
||||||
.chat-area {
|
.chat-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -103,7 +97,6 @@ main {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat Messages */
|
|
||||||
.chat-messages {
|
.chat-messages {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@ -155,7 +148,6 @@ main {
|
|||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Input Area */
|
|
||||||
.chat-input {
|
.chat-input {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background-color: #121212;
|
background-color: #121212;
|
||||||
@ -190,7 +182,6 @@ main {
|
|||||||
background-color: #e04924;
|
background-color: #e04924;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive Adjustments */
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
display: none;
|
display: none;
|
54
src/snek/static/fancy-button.js
Normal file
54
src/snek/static/fancy-button.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class FancyButton extends HTMLElement {
|
||||||
|
url = null
|
||||||
|
type="button"
|
||||||
|
value = null
|
||||||
|
constructor(){
|
||||||
|
super()
|
||||||
|
this.attachShadow({mode:'open'})
|
||||||
|
this.container = document.createElement('span')
|
||||||
|
this.styleElement = document.createElement("style")
|
||||||
|
this.styleElement.innerHTML = `
|
||||||
|
:root {
|
||||||
|
width:100%;
|
||||||
|
--width: 100%;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: var(--width);
|
||||||
|
min-width: 33%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f05a28;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
color: #EFEFEF;
|
||||||
|
background-color: #e04924;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
this.container.appendChild(this.styleElement)
|
||||||
|
this.buttonElement = document.createElement('button')
|
||||||
|
this.container.appendChild(this.buttonElement)
|
||||||
|
this.shadowRoot.appendChild(this.container)
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.url = this.getAttribute('url');
|
||||||
|
this.value = this.getAttribute('value')
|
||||||
|
const me = this
|
||||||
|
this.buttonElement.appendChild(document.createTextNode(this.getAttribute("text")))
|
||||||
|
this.buttonElement.addEventListener("click",()=>{
|
||||||
|
if(me.url){
|
||||||
|
window.location = me.url
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("fancy-button",FancyButton)
|
100
src/snek/static/generic-form.css
Normal file
100
src/snek/static/generic-form.css
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #e6e6e6;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
generic-form {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #000000;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.generic-form-container {
|
||||||
|
|
||||||
|
background-color: #0f0f0f;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 30px;
|
||||||
|
width: 400px;
|
||||||
|
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.generic-form-container h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
color: #f05a28;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
|
||||||
|
border: 10px solid #000000;
|
||||||
|
}
|
||||||
|
.generic-form-container generic-field {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #e6e6e6;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generic-form-container button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f05a28;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generic-form-container button:hover {
|
||||||
|
background-color: #e04924;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generic-form-container a {
|
||||||
|
color: #f05a28;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generic-form-container a:hover {
|
||||||
|
color: #e04924;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #d8000c;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.generic-form-container {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
}
|
321
src/snek/static/generic-form.js
Normal file
321
src/snek/static/generic-form.js
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
|
||||||
|
class GenericField extends HTMLElement {
|
||||||
|
form = null
|
||||||
|
field = null
|
||||||
|
inputElement = null
|
||||||
|
footerElement = null
|
||||||
|
action = null
|
||||||
|
container = null
|
||||||
|
styleElement = null
|
||||||
|
name = null
|
||||||
|
get value() {
|
||||||
|
return this.inputElement.value
|
||||||
|
}
|
||||||
|
get type() {
|
||||||
|
|
||||||
|
return this.field.tag
|
||||||
|
}
|
||||||
|
set value(val) {
|
||||||
|
val = val == null ? '' : val
|
||||||
|
this.inputElement.value = val
|
||||||
|
this.inputElement.setAttribute("value", val)
|
||||||
|
}
|
||||||
|
setInvalid(){
|
||||||
|
this.inputElement.classList.add("error")
|
||||||
|
this.inputElement.classList.remove("valid")
|
||||||
|
}
|
||||||
|
setErrors(errors){
|
||||||
|
if(errors.length)
|
||||||
|
this.inputElement.setAttribute("title", errors[0])
|
||||||
|
else
|
||||||
|
this.inputElement.setAttribute("title","")
|
||||||
|
}
|
||||||
|
setValid(){
|
||||||
|
this.inputElement.classList.remove("error")
|
||||||
|
this.inputElement.classList.add("valid")
|
||||||
|
}
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.attachShadow({mode:'open'})
|
||||||
|
this.container = document.createElement('div')
|
||||||
|
this.styleElement = document.createElement('style')
|
||||||
|
this.styleElement.innerHTML = `
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
color: #f05a28;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 90%;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #e6e6e6;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 50%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f05a28;
|
||||||
|
border: none;
|
||||||
|
float: right;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #e04924;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #f05a28;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #e04924;
|
||||||
|
}
|
||||||
|
.valid {
|
||||||
|
border: 1px solid green;
|
||||||
|
color:green;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
border: 3px solid red;
|
||||||
|
color: #d8000c;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
input {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
this.container.appendChild(this.styleElement)
|
||||||
|
|
||||||
|
this.shadowRoot.appendChild(this.container)
|
||||||
|
}
|
||||||
|
connectedCallback(){
|
||||||
|
|
||||||
|
this.updateAttributes()
|
||||||
|
|
||||||
|
}
|
||||||
|
setAttribute(name,value){
|
||||||
|
this[name] = value
|
||||||
|
}
|
||||||
|
updateAttributes(){
|
||||||
|
if(this.inputElement == null && this.field){
|
||||||
|
this.inputElement = document.createElement(this.field.tag)
|
||||||
|
if(this.field.tag == 'button'){
|
||||||
|
if(this.field.value == "submit"){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
this.action = this.field.value
|
||||||
|
}
|
||||||
|
this.inputElement.name = this.field.name
|
||||||
|
this.name = this.inputElement.name
|
||||||
|
const me = this
|
||||||
|
this.inputElement.addEventListener("keyup",(e)=>{
|
||||||
|
if(e.key == 'Enter'){
|
||||||
|
me.dispatchEvent(new Event("submit"))
|
||||||
|
}else if(me.field.value != e.target.value)
|
||||||
|
{
|
||||||
|
const event = new CustomEvent("change", {detail:me,bubbles:true})
|
||||||
|
me.dispatchEvent(event)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.inputElement.addEventListener("click",(e)=>{
|
||||||
|
const event = new CustomEvent("click",{detail:me,bubbles:true})
|
||||||
|
me.dispatchEvent(event)
|
||||||
|
})
|
||||||
|
this.container.appendChild(this.inputElement)
|
||||||
|
|
||||||
|
}
|
||||||
|
if(!this.field){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.inputElement.setAttribute("type",this.field.type == null ? 'input' : this.field.type)
|
||||||
|
this.inputElement.setAttribute("name",this.field.name == null ? '' : this.field.name)
|
||||||
|
|
||||||
|
if(this.field.text != null){
|
||||||
|
this.inputElement.innerText = this.field.text
|
||||||
|
}
|
||||||
|
if(this.field.html != null){
|
||||||
|
this.inputElement.innerHTML = this.field.html
|
||||||
|
}
|
||||||
|
if(this.field.class_name){
|
||||||
|
this.inputElement.classList.add(this.field.class_name)
|
||||||
|
}
|
||||||
|
this.inputElement.setAttribute("tabindex", this.field.index)
|
||||||
|
this.inputElement.classList.add(this.field.name)
|
||||||
|
this.value = this.field.value
|
||||||
|
let place_holder = null
|
||||||
|
if(this.field.place_holder)
|
||||||
|
place_holder = this.field.place_holder
|
||||||
|
if(this.field.required && place_holder){
|
||||||
|
place_holder = place_holder
|
||||||
|
}
|
||||||
|
if(place_holder)
|
||||||
|
this.field.place_holder = "* " + place_holder
|
||||||
|
this.inputElement.setAttribute("placeholder",place_holder)
|
||||||
|
if(this.field.required)
|
||||||
|
this.inputElement.setAttribute("required","required")
|
||||||
|
else
|
||||||
|
this.inputElement.removeAttribute("required")
|
||||||
|
if(!this.footerElement){
|
||||||
|
this.footerElement = document.createElement('div')
|
||||||
|
this.footerElement.style.clear = 'both'
|
||||||
|
this.container.appendChild(this.footerElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('generic-field', GenericField);
|
||||||
|
|
||||||
|
class GenericForm extends HTMLElement {
|
||||||
|
fields = {}
|
||||||
|
form = {}
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
|
||||||
|
super();
|
||||||
|
this.attachShadow({ mode: 'open' });
|
||||||
|
this.styleElement = document.createElement("style")
|
||||||
|
this.styleElement.innerHTML = `
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width:90%
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
|
||||||
|
background-color: #0f0f0f;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 30px;
|
||||||
|
width: 400px;
|
||||||
|
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
form {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
this.container = document.createElement('div');
|
||||||
|
this.container.appendChild(this.styleElement)
|
||||||
|
this.container.classList.add("generic-form-container")
|
||||||
|
this.shadowRoot.appendChild(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
const url = this.getAttribute('url');
|
||||||
|
if (url) {
|
||||||
|
const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get")
|
||||||
|
if(!url.startsWith("/"))
|
||||||
|
fullUrl.searchParams.set('url', url)
|
||||||
|
this.loadForm(fullUrl.toString());
|
||||||
|
} else {
|
||||||
|
this.container.textContent = "No URL provided!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadForm(url) {
|
||||||
|
const me = this
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
me.form = await response.json();
|
||||||
|
|
||||||
|
let fields = Object.values(me.form.fields)
|
||||||
|
|
||||||
|
fields = fields.sort((a,b)=>{
|
||||||
|
console.info(a.index,b.index)
|
||||||
|
return a.index - b.index
|
||||||
|
})
|
||||||
|
fields.forEach(field=>{
|
||||||
|
const fieldElement = document.createElement('generic-field')
|
||||||
|
me.fields[field.name] = fieldElement
|
||||||
|
fieldElement.setAttribute("form", me)
|
||||||
|
fieldElement.setAttribute("field", field)
|
||||||
|
me.container.appendChild(fieldElement)
|
||||||
|
fieldElement.updateAttributes()
|
||||||
|
fieldElement.addEventListener("change",(e)=>{
|
||||||
|
me.form.fields[e.detail.name].value = e.detail.value
|
||||||
|
})
|
||||||
|
fieldElement.addEventListener("click",async (e)=>{
|
||||||
|
if(e.detail.type == "button"){
|
||||||
|
if(e.detail.value == "submit")
|
||||||
|
{
|
||||||
|
await me.validate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.container.textContent = `Error: ${error.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async validate(){
|
||||||
|
const url = this.getAttribute("url")
|
||||||
|
const me = this
|
||||||
|
const response = await fetch(url,{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({"action":"validate", "form":me.form})
|
||||||
|
});
|
||||||
|
const form = await response.json()
|
||||||
|
Object.values(form.fields).forEach(field=>{
|
||||||
|
if(!me.form.fields[field.name])
|
||||||
|
return
|
||||||
|
me.form.fields[field.name].is_valid = field.is_valid
|
||||||
|
if(!field.is_valid){
|
||||||
|
me.fields[field.name].setInvalid()
|
||||||
|
me.fields[field.name].setErrors(field.errors)
|
||||||
|
console.info(field.name,"is invalid")
|
||||||
|
}else{
|
||||||
|
me.fields[field.name].setValid()
|
||||||
|
}
|
||||||
|
me.fields[field.name].setAttribute("field",field)
|
||||||
|
me.fields[field.name].updateAttributes()
|
||||||
|
})
|
||||||
|
Object.values(form.fields).forEach(field=>{
|
||||||
|
console.info(field.errors)
|
||||||
|
me.fields[field.name].setErrors(field.errors)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('generic-form', GenericForm);
|
@ -1,9 +1,6 @@
|
|||||||
.html-frame {
|
.html-frame {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
clip-path: inset(0px 0px 50px 100px); /* Crop content */
|
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
|
|
||||||
}
|
}
|
@ -12,28 +12,25 @@ class HTMLFrame extends HTMLElement {
|
|||||||
if (url) {
|
if (url) {
|
||||||
const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get")
|
const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get")
|
||||||
if(!url.startsWith("/"))
|
if(!url.startsWith("/"))
|
||||||
fullUrl.searchParams.set('url', url)
|
fullUrl.searchParams.set('url', url)
|
||||||
console.info(fullUrl)
|
this.loadAndRender(fullUrl.toString());
|
||||||
this.fetchAndDisplayHtml(fullUrl.toString());
|
|
||||||
} else {
|
} else {
|
||||||
this.container.textContent = "No URL provided!";
|
this.container.textContent = "No source URL!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchAndDisplayHtml(url) {
|
async loadAndRender(url) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
|
throw new Error(`Error: ${response.status} ${response.statusText}`);
|
||||||
}
|
}
|
||||||
const html = await response.text();
|
const html = await response.text();
|
||||||
this.container.innerHTML = html; // Insert the fetched HTML into the container
|
this.container.innerHTML = html;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.container.textContent = `Error: ${error.message}`;
|
this.container.textContent = `Error: ${error.message}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the custom element
|
|
||||||
customElements.define('html-frame', HTMLFrame);
|
customElements.define('html-frame', HTMLFrame);
|
@ -1,24 +1,12 @@
|
|||||||
/* General Reset */
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Body Styling */
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #e6e6e6;
|
|
||||||
line-height: 1.5;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Registration Form Container */
|
|
||||||
.registration-container {
|
.registration-container {
|
||||||
background-color: #0f0f0f;
|
background-color: #0f0f0f;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@ -26,16 +14,15 @@
|
|||||||
width: 400px;
|
width: 400px;
|
||||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
left: calc(50%-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Form Heading */
|
|
||||||
.registration-container h1 {
|
.registration-container h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
color: #f05a28;
|
color: #f05a28;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Input Fields */
|
|
||||||
.registration-container input {
|
.registration-container input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -47,7 +34,6 @@
|
|||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Submit Button */
|
|
||||||
.registration-container button {
|
.registration-container button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -65,7 +51,6 @@
|
|||||||
background-color: #e04924;
|
background-color: #e04924;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Links */
|
|
||||||
.registration-container a {
|
.registration-container a {
|
||||||
color: #f05a28;
|
color: #f05a28;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -79,14 +64,11 @@
|
|||||||
color: #e04924;
|
color: #e04924;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Error Message Styling */
|
|
||||||
.error {
|
.error {
|
||||||
color: #d8000c;
|
color: #d8000c;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive Design */
|
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
.registration-container {
|
.registration-container {
|
||||||
width: 90%;
|
width: 90%;
|
0
src/snek/system/__init__.py
Normal file
0
src/snek/system/__init__.py
Normal file
171
src/snek/system/form.py
Normal file
171
src/snek/system/form.py
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
from snek.system import model
|
||||||
|
|
||||||
|
class HTMLElement(model.ModelField):
|
||||||
|
def __init__(self,id:str=None, tag:str="div", name:str=None,html:str=None, class_name:str=None, text:str=None, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Create a new HTMLElement.
|
||||||
|
|
||||||
|
:param id: The id of the element
|
||||||
|
:param tag: The tag of the element
|
||||||
|
:param name: The name of the element, used to generate a class name if not provided
|
||||||
|
:param html: The inner html of the element
|
||||||
|
:param class_name: The class name of the element
|
||||||
|
:param text: The text of the element
|
||||||
|
"""
|
||||||
|
self.tag = tag
|
||||||
|
self.text = text
|
||||||
|
self.id = id
|
||||||
|
self.class_name = class_name or name
|
||||||
|
self.html = html
|
||||||
|
super().__init__(name=name,*args, **kwargs)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
"""
|
||||||
|
Return a json representation of the element.
|
||||||
|
|
||||||
|
This will return a dict with the following keys:
|
||||||
|
|
||||||
|
- text: The text of the element
|
||||||
|
- id: The id of the element
|
||||||
|
- html: The inner html of the element
|
||||||
|
- class_name: The class name of the element
|
||||||
|
- tag: The tag of the element
|
||||||
|
|
||||||
|
:return: A json representation of the element
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
result = 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
|
||||||
|
return result
|
||||||
|
|
||||||
|
class FormElement(HTMLElement):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class FormInputElement(FormElement):
|
||||||
|
|
||||||
|
def __init__(self,type="text",place_holder=None, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize a FormInputElement with specified attributes.
|
||||||
|
|
||||||
|
:param type: The type of the input element (default is "text").
|
||||||
|
:param place_holder: The placeholder text for the input element.
|
||||||
|
:param args: Additional positional arguments.
|
||||||
|
:param kwargs: Additional keyword arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().__init__(tag="input", *args, **kwargs)
|
||||||
|
self.place_holder = place_holder
|
||||||
|
self.type = type
|
||||||
|
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
"""
|
||||||
|
Return a json representation of the element.
|
||||||
|
|
||||||
|
This will return a dict with the following keys:
|
||||||
|
|
||||||
|
- place_holder: The placeholder text for the input element
|
||||||
|
- type: The type of the input element
|
||||||
|
|
||||||
|
:return: A json representation of the element
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
data = super().to_json()
|
||||||
|
data["place_holder"] = self.place_holder
|
||||||
|
data["type"] = self.type
|
||||||
|
return data
|
||||||
|
|
||||||
|
class FormButtonElement(FormElement):
|
||||||
|
# Just use the label text property to assign a button label.
|
||||||
|
def __init__(self, tag="button", *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize a FormButtonElement with specified attributes.
|
||||||
|
|
||||||
|
:param tag: The tag of the button element (default is "button").
|
||||||
|
:param args: Additional positional arguments.
|
||||||
|
:param kwargs: Additional keyword arguments.
|
||||||
|
"""
|
||||||
|
super().__init__(tag=tag, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Form(model.BaseModel):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def html_elements(self):
|
||||||
|
"""
|
||||||
|
Return a list of all :class:`HTMLElement` objects in the form.
|
||||||
|
|
||||||
|
This is a convenience property that filters the :attr:`fields` list to only
|
||||||
|
include elements that are instances of :class:`HTMLElement`.
|
||||||
|
|
||||||
|
:return: A list of :class:`HTMLElement` objects
|
||||||
|
:rtype: list
|
||||||
|
"""
|
||||||
|
json_elements = super().to_json()
|
||||||
|
return [element for element in self.fields if isinstance(element,HTMLElement)]
|
||||||
|
def set_user_data(self, data):
|
||||||
|
"""
|
||||||
|
Set user data for the form by updating the fields with the provided data.
|
||||||
|
|
||||||
|
This method extracts the 'fields' key from the provided data dictionary
|
||||||
|
and passes it to the parent class's `set_user_data` method to update the
|
||||||
|
form fields accordingly.
|
||||||
|
|
||||||
|
:param data: A dictionary containing the form data, expected to have a
|
||||||
|
'fields' key with the data to update the form fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return super().set_user_data(data.get('fields'))
|
||||||
|
|
||||||
|
def to_json(self, encode=False):
|
||||||
|
"""
|
||||||
|
Return a JSON representation of the form, including field values and metadata.
|
||||||
|
|
||||||
|
This method returns a dictionary with the following keys:
|
||||||
|
|
||||||
|
- ``fields``: A dictionary of field names to their current values.
|
||||||
|
- ``is_valid``: A boolean indicating whether the form is valid.
|
||||||
|
- ``errors``: A dictionary of field names to lists of error strings.
|
||||||
|
|
||||||
|
If the ``encode`` argument is ``True``, the dictionary will be JSON-encoded
|
||||||
|
before being returned. Otherwise, the dictionary is returned directly.
|
||||||
|
|
||||||
|
:param encode: If ``True``, JSON-encode the returned dictionary.
|
||||||
|
:type encode: bool
|
||||||
|
:return: A JSON representation of the form.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
elements = super().to_json()
|
||||||
|
html_elements = {}
|
||||||
|
for element in elements.keys():
|
||||||
|
print("DDD!",element,flush=True)
|
||||||
|
field = getattr(self,element)
|
||||||
|
if isinstance(field,HTMLElement):
|
||||||
|
print("QQQQ!",element,flush=True)
|
||||||
|
try:
|
||||||
|
html_elements[element] = elements[element]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return dict(fields=html_elements,is_valid=self.is_valid,errors=self.errors)
|
||||||
|
@property
|
||||||
|
def errors(self):
|
||||||
|
"""
|
||||||
|
Return a list of all error strings from all fields in the form.
|
||||||
|
|
||||||
|
The list will be empty if all fields are valid.
|
||||||
|
|
||||||
|
:return: A list of error strings.
|
||||||
|
:rtype: list
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for field in self.html_elements:
|
||||||
|
result += field.errors
|
||||||
|
return result
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
return all(element.is_valid for element in self.html_elements)
|
@ -23,7 +23,7 @@ def validate_attrs(required=False,min_length=None,max_length=None,regex=None,**k
|
|||||||
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:
|
class Validator:
|
||||||
|
_index = 0
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
return self._value
|
return self._value
|
||||||
@ -34,12 +34,14 @@ class Validator:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def initial_value(self):
|
def initial_value(self):
|
||||||
return None
|
return self.value
|
||||||
|
|
||||||
def custom_validation(self):
|
def custom_validation(self):
|
||||||
return True
|
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,**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,**kwargs):
|
||||||
|
self.index = Validator._index
|
||||||
|
Validator._index += 1
|
||||||
self.required = required
|
self.required = required
|
||||||
self.min_num = min_num
|
self.min_num = min_num
|
||||||
self.max_num = max_num
|
self.max_num = max_num
|
||||||
@ -47,8 +49,10 @@ class Validator:
|
|||||||
self.max_length = max_length
|
self.max_length = max_length
|
||||||
self.regex = regex
|
self.regex = regex
|
||||||
self._value = None
|
self._value = None
|
||||||
self.value = value
|
self.value = value
|
||||||
self.type = kind
|
print("xxxx", value,flush=True)
|
||||||
|
|
||||||
|
self.kind = kind
|
||||||
self.help_text = help_text
|
self.help_text = help_text
|
||||||
self.__dict__.update(kwargs)
|
self.__dict__.update(kwargs)
|
||||||
@property
|
@property
|
||||||
@ -61,7 +65,7 @@ class Validator:
|
|||||||
if self.value is None:
|
if self.value is None:
|
||||||
return error_list
|
return error_list
|
||||||
|
|
||||||
if self.type == float or self.type == int:
|
if self.kind == float or self.kind == int:
|
||||||
if self.min_num is not None and self.value < self.min_num:
|
if self.min_num is not None and self.value < self.min_num:
|
||||||
error_list.append("Field should be minimal {}.".format(self.min_num))
|
error_list.append("Field should be minimal {}.".format(self.min_num))
|
||||||
if self.max_num is not None and self.value > self.max_num:
|
if self.max_num is not None and self.value > self.max_num:
|
||||||
@ -70,10 +74,11 @@ class Validator:
|
|||||||
error_list.append("Field should be minimal {} characters long.".format(self.min_length))
|
error_list.append("Field should be minimal {} characters long.".format(self.min_length))
|
||||||
if self.max_length is not None and len(self.value) > self.max_length:
|
if self.max_length is not None and len(self.value) > self.max_length:
|
||||||
error_list.append("Field should be maximal {} characters long.".format(self.max_length))
|
error_list.append("Field should be maximal {} characters long.".format(self.max_length))
|
||||||
|
print(self.regex, self.value,flush=True)
|
||||||
if not self.regex is None and not self.value is None and not re.match(self.regex, self.value):
|
if not self.regex is None and not self.value is None and not re.match(self.regex, self.value):
|
||||||
error_list.append("Invalid value.".format(self.regex))
|
error_list.append("Invalid value.".format(self.regex))
|
||||||
if not self.type is None and type(self.value) != self.type:
|
if not self.kind is None and type(self.value) != self.kind:
|
||||||
error_list.append("Invalid type. It is supposed to be {}.".format(self.type))
|
error_list.append("Invalid kind. It is supposed to be {}.".format(self.kind))
|
||||||
return error_list
|
return error_list
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -89,6 +94,8 @@ class Validator:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
return {
|
return {
|
||||||
"required": self.required,
|
"required": self.required,
|
||||||
@ -98,19 +105,26 @@ class Validator:
|
|||||||
"max_length": self.max_length,
|
"max_length": self.max_length,
|
||||||
"regex": self.regex,
|
"regex": self.regex,
|
||||||
"value": self.value,
|
"value": self.value,
|
||||||
"type": self.type,
|
"kind": str(self.kind),
|
||||||
"help_text": self.help_text,
|
"help_text": self.help_text,
|
||||||
"errors": self.errors,
|
"errors": self.errors,
|
||||||
"is_valid": self.is_valid
|
"is_valid": self.is_valid,
|
||||||
|
"index":self.index
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModelField(Validator):
|
class ModelField(Validator):
|
||||||
|
|
||||||
|
index = 1
|
||||||
def __init__(self,name=None,save=True, *args, **kwargs):
|
def __init__(self,name=None,save=True, *args, **kwargs):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
self.save = save
|
self.save = save
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
result = super().to_json()
|
||||||
|
result['name'] = self.name
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class CreatedField(ModelField):
|
class CreatedField(ModelField):
|
||||||
|
|
||||||
@ -146,9 +160,11 @@ class BaseModel:
|
|||||||
updated_at = UpdatedField(name="updated_at",regex=TIMESTAMP_REGEX,place_holder="Updated 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")
|
deleted_at = DeletedField(name="deleted_at",regex=TIMESTAMP_REGEX, place_holder="Deleted at")
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
print(self.__dict__)
|
print(self.__dict__)
|
||||||
print(dir(self.__class__))
|
print(dir(self.__class__))
|
||||||
|
self.fields = {}
|
||||||
for key in dir(self.__class__):
|
for key in dir(self.__class__):
|
||||||
obj = getattr(self.__class__,key)
|
obj = getattr(self.__class__,key)
|
||||||
|
|
||||||
@ -156,6 +172,7 @@ class BaseModel:
|
|||||||
self.__dict__[key] = copy.deepcopy(obj)
|
self.__dict__[key] = copy.deepcopy(obj)
|
||||||
print("JAAA")
|
print("JAAA")
|
||||||
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]
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
obj = self.__dict__.get(key)
|
obj = self.__dict__.get(key)
|
||||||
@ -169,6 +186,22 @@ class BaseModel:
|
|||||||
return obj.value
|
return obj.value
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
def set_user_data(self, data):
|
||||||
|
for key, value in data.items():
|
||||||
|
field = self.fields.get(key)
|
||||||
|
if not field:
|
||||||
|
continue
|
||||||
|
if value.get('name'):
|
||||||
|
value = value.get('value')
|
||||||
|
field.value = value
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
for field in self.fields.values():
|
||||||
|
if not field.is_valid:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
obj = self.__dict__.get(key)
|
obj = self.__dict__.get(key)
|
||||||
@ -180,7 +213,7 @@ class BaseModel:
|
|||||||
if isinstance(obj,Validator):
|
if isinstance(obj,Validator):
|
||||||
obj.value = value
|
obj.value = value
|
||||||
else:
|
else:
|
||||||
setattr(self,key,value)
|
self.__dict__[key] = value #setattr(self,key,value)
|
||||||
#def __getattr__(self, key):
|
#def __getattr__(self, key):
|
||||||
# obj = self.__dict__.get(key)
|
# obj = self.__dict__.get(key)
|
||||||
# if isinstance(obj,Validator):
|
# if isinstance(obj,Validator):
|
||||||
@ -201,6 +234,7 @@ class BaseModel:
|
|||||||
"updated_at": self.updated_at.value,
|
"updated_at": self.updated_at.value,
|
||||||
"deleted_at": self.deleted_at.value
|
"deleted_at": self.deleted_at.value
|
||||||
})
|
})
|
||||||
|
|
||||||
for key,value in self.__dict__.items():
|
for key,value in self.__dict__.items():
|
||||||
if key == "record":
|
if key == "record":
|
||||||
continue
|
continue
|
||||||
@ -225,39 +259,8 @@ class FormElement(ModelField):
|
|||||||
self.place_holder = place_holder
|
self.place_holder = place_holder
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
data = super().to_json()
|
data = super().to_json()
|
||||||
data["name"] = self.name
|
data["name"] = self.name
|
||||||
data["place_holder"] = self.place_holder
|
data["place_holder"] = self.place_holder
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestModel(BaseModel):
|
|
||||||
|
|
||||||
first_name = FormElement(name="first_name",required=True,min_length=3,max_length=20,regex=r"^[a-zA-Z0-9_]+$",place_holder="First name")
|
|
||||||
last_name = FormElement(name="last_name",required=True,min_length=3,max_length=20,regex=r"^[a-zA-Z0-9_]+$",place_holder="Last name")
|
|
||||||
email = FormElement(name="email",required=True,regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",place_holder="Email address")
|
|
||||||
password = FormElement(name="password",required=True,regex=r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$",place_holder="Password")
|
|
||||||
|
|
||||||
class Form:
|
|
||||||
username = FormElement(required=True,min_length=3,max_length=20,regex=r"^[a-zA-Z0-9_]+$",place_holder="Username")
|
|
||||||
email = FormElement(required=True,regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",place_holder="Email address")
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.place_holder = kwargs.pop("place_holder",None)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
model = TestModel(first_name="John",last_name="Doe",email="n9K9p@example.com",password="Password123")
|
|
||||||
model2 = TestModel(first_name="John",last_name="Doe",email="ddd",password="zzz")
|
|
||||||
model.first_name = "AAA"
|
|
||||||
print(model.first_name)
|
|
||||||
print(model.first_name.value)
|
|
||||||
|
|
||||||
print(model.first_name)
|
|
||||||
print(model.first_name.value)
|
|
||||||
print(model.to_json(True))
|
|
||||||
print(model2.to_json(True))
|
|
||||||
print(model2.record)
|
|
31
src/snek/templates/base.html
Normal file
31
src/snek/templates/base.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
<script src="/fancy-button.js"></script>
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<link rel="stylesheet" href="/generic-form.css">
|
||||||
|
<script src="/html-frame.js"></script>
|
||||||
|
<script src="/generic-form.js"></script>
|
||||||
|
<link rel="stylesheet" href="/html-frame.css"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
{% block header %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<aside class="sidebar">
|
||||||
|
{% block sidebar %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
</aside>
|
||||||
|
{% block main %}
|
||||||
|
{% endblock %}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
31
src/snek/templates/base_chat.html
Normal file
31
src/snek/templates/base_chat.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
<script src="/fancy-button.js"></script>
|
||||||
|
<link rel="stylesheet" href="/base.css">
|
||||||
|
<link rel="stylesheet" href="/generic-form.css">
|
||||||
|
<script src="/html-frame.js"></script>
|
||||||
|
<script src="/generic-form.js"></script>
|
||||||
|
<link rel="stylesheet" href="/html-frame.css"></script>
|
||||||
|
<link rel="stylesheet" href="/register__.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
{% block header %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<aside class="sidebar">
|
||||||
|
{% block sidebar %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
</aside>
|
||||||
|
{% block main %}
|
||||||
|
{% endblock %}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
20
src/snek/templates/index.html
Normal file
20
src/snek/templates/index.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Snek chat by Molodetz</title>
|
||||||
|
<link rel="stylesheet" href="generic-form.css">
|
||||||
|
<link rel="stylesheet" href="register__.css">
|
||||||
|
<script src="/fancy-button.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="registration-container">
|
||||||
|
<h1>Snek</h1>
|
||||||
|
<fancy-button url="/login" text="Login"></fancy-button>
|
||||||
|
<span style="padding:10px;">Or</span>
|
||||||
|
<fancy-button url="/register" text="Register"></fancy-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,20 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block main %}
|
||||||
<meta charset="UTF-8">
|
<generic-form url="/login-form"></generic-form>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
{% endblock %}
|
||||||
<title>Register</title>
|
|
||||||
<link rel="stylesheet" href="register.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="registration-container">
|
|
||||||
<h1>Login</h1>
|
|
||||||
<form>
|
|
||||||
<input type="text" name="username" placeholder="Username or password" required>
|
|
||||||
<input type="password" name="password" placeholder="Password" required>
|
|
||||||
<button type="submit">Create Account</button>
|
|
||||||
<a href="/register">Not having an account yet? Register here.</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
@ -1,22 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block main %}
|
||||||
<meta charset="UTF-8">
|
<generic-form url="/register-form"></generic-form>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
{% endblock %}
|
||||||
<title>Register</title>
|
|
||||||
<link rel="stylesheet" href="register.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="registration-container">
|
|
||||||
<h1>Register</h1>
|
|
||||||
<form>
|
|
||||||
<input type="text" name="username" placeholder="Username" required>
|
|
||||||
<input type="email" name="email" placeholder="Email Address" required>
|
|
||||||
<input type="password" name="password" placeholder="Password" required>
|
|
||||||
<input type="password" name="confirm_password" placeholder="Confirm Password" required>
|
|
||||||
<button type="submit">Create Account</button>
|
|
||||||
<a href="#">Already have an account? Login here.</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -4,9 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Dark Themed Chat Application</title>
|
<title>Dark Themed Chat Application</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="base.css">
|
||||||
<script src="/html_frame.js"></script>
|
|
||||||
<script src="/html_frame.css"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
@ -59,5 +57,4 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
31
src/snek/view/base.py
Normal file
31
src/snek/view/base.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
class BaseView(web.View):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def app(self):
|
||||||
|
return self.request.app
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db(self):
|
||||||
|
return self.app.db
|
||||||
|
|
||||||
|
def json_response(self, data):
|
||||||
|
return web.json_response(data)
|
||||||
|
|
||||||
|
def render_template(self, template_name, context=None):
|
||||||
|
return self.request.app.render_template(template_name, self.request,context)
|
||||||
|
|
||||||
|
class BaseFormView(BaseView):
|
||||||
|
|
||||||
|
form = None
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
form = self.form()
|
||||||
|
return self.json_response(form.to_json())
|
||||||
|
|
||||||
|
async def post(self):
|
||||||
|
form = self.form()
|
||||||
|
post = await self.request.json()
|
||||||
|
form.set_user_data(post['form'])
|
||||||
|
return self.json_response(form.to_json())
|
6
src/snek/view/index.py
Normal file
6
src/snek/view/index.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from snek.view.base import BaseView
|
||||||
|
|
||||||
|
class IndexView(BaseView):
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
return await self.render_template("index.html")
|
13
src/snek/view/login.py
Normal file
13
src/snek/view/login.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from snek.form.register import RegisterForm
|
||||||
|
from snek.view.base import BaseView
|
||||||
|
|
||||||
|
class LoginView(BaseView):
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
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()})
|
5
src/snek/view/login_form.py
Normal file
5
src/snek/view/login_form.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from snek.view.base import BaseFormView
|
||||||
|
from snek.form.login import LoginForm
|
||||||
|
|
||||||
|
class LoginFormView(BaseFormView):
|
||||||
|
form = LoginForm
|
6
src/snek/view/register.py
Normal file
6
src/snek/view/register.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from snek.view.base import BaseView
|
||||||
|
|
||||||
|
class RegisterView(BaseView):
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
return await self.render_template("register.html")
|
5
src/snek/view/register_form.py
Normal file
5
src/snek/view/register_form.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from snek.form.register import RegisterForm
|
||||||
|
from snek.view.base import BaseFormView
|
||||||
|
|
||||||
|
class RegisterFormView(BaseFormView):
|
||||||
|
form = RegisterForm
|
6
src/snek/view/view.py
Normal file
6
src/snek/view/view.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from snek.view.base import BaseView
|
||||||
|
|
||||||
|
class WebView(BaseView):
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
return await self.render_template("web.html")
|
Loading…
Reference in New Issue
Block a user