working version?
This commit is contained in:
commit
44c5fbf284
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.pyc
|
||||
*.swp
|
||||
*.swo
|
||||
9
frontend/index.html
Normal file
9
frontend/index.html
Normal file
@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="/static/css/main.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body hx-trigger="load" hx-get="/app">
|
||||
</body>
|
||||
<script src="/static/js/htmx.min.js"></script>
|
||||
<script>htmx.logAll();</script>
|
||||
</html>
|
||||
22
frontend/static/css/main.css
Normal file
22
frontend/static/css/main.css
Normal file
@ -0,0 +1,22 @@
|
||||
body {
|
||||
}
|
||||
|
||||
.cat {
|
||||
display: grid;
|
||||
grid-template-columns: 20rem 1fr 1fr 1fr;
|
||||
}
|
||||
.cat > div {
|
||||
display: block;
|
||||
padding: 1em;
|
||||
width: 10em;
|
||||
}
|
||||
.cat_header > div {
|
||||
font-weight: bold;
|
||||
}
|
||||
.cat:nth-child(event) {
|
||||
background-color:#f1f1f1;
|
||||
}
|
||||
.cat:nth-child(odd) {
|
||||
background-color:#eaeaea;
|
||||
}
|
||||
|
||||
1
frontend/static/js/htmx.min.js
vendored
Normal file
1
frontend/static/js/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
frontend/templates/app.html.j2
Normal file
2
frontend/templates/app.html.j2
Normal file
@ -0,0 +1,2 @@
|
||||
{% include("templates/cat_form.html.j2") %}
|
||||
{% include("templates/cat_list.html.j2") %}
|
||||
39
frontend/templates/cat.html.j2
Normal file
39
frontend/templates/cat.html.j2
Normal file
@ -0,0 +1,39 @@
|
||||
<div class="cat">
|
||||
<div class="cat_name">{{ cat.name }}</div>
|
||||
<div class="cat_generation">{{ cat.generation_row.day }}</div>
|
||||
<div class="cat_sex">
|
||||
{{ cat.sex }}
|
||||
</div>
|
||||
<div class="cat_state">{{ cat.state }}</div>
|
||||
<div class="cat_parent_a">
|
||||
{% if cat.parent_a != None %}{{ cat_by_ids[cat.parent_a].name }}{% endif %}</div>
|
||||
<div class="cat_parent_b">{% if cat.parent_b != None %}{{ cat_by_ids[cat.parent_b].name }}{% endif %}</div>
|
||||
<div class="cat_stats">
|
||||
<div class="cat_stat_str">STR: {{ cat.stat_str }} ({{ cat.stat_mod_str }})</div>
|
||||
<div class="cat_stat_dex">DEX: {{ cat.stat_dex }} ({{ cat.stat_mod_dex }})</div>
|
||||
<div class="cat_stat_con">CON: {{ cat.stat_con }} ({{ cat.stat_mod_con }})</div>
|
||||
<div class="cat_stat_int">INT: {{ cat.stat_int }} ({{ cat.stat_mod_int }})</div>
|
||||
<div class="cat_stat_spd">SPD: {{ cat.stat_spd }} ({{ cat.stat_mod_spd }})</div>
|
||||
<div class="cat_stat_cha">CHA: {{ cat.stat_cha }} ({{ cat.stat_mod_cha }})</div>
|
||||
<div class="cat_stat_lck">LCK: {{ cat.stat_lck }} ({{ cat.stat_mod_lck }})</div>
|
||||
<div class="cat_stat_total">Total: {{
|
||||
[cat.stat_str,
|
||||
cat.stat_dex,
|
||||
cat.stat_con,
|
||||
cat.stat_int,
|
||||
cat.stat_spd,
|
||||
cat.stat_cha,
|
||||
cat.stat_lck,
|
||||
] | sum
|
||||
}}</div>
|
||||
</div>
|
||||
<div class="cat_actions">
|
||||
<div class="cat_edit_button">
|
||||
<button hx-get="/hx/cat_form/{{ cat.id }}">Edit</button>
|
||||
</div>
|
||||
|
||||
<div class="cat_delete_button">
|
||||
<button class="delete_cat" value="{{ cat.id }}" hx-delete="/cat/{{ cat.id }}">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
55
frontend/templates/cat_form.html.j2
Normal file
55
frontend/templates/cat_form.html.j2
Normal file
@ -0,0 +1,55 @@
|
||||
<div id="cat-form">
|
||||
<form
|
||||
hx-post="/cat"
|
||||
hx-swap="none"
|
||||
>
|
||||
<h2>Generation</h2>
|
||||
<label>Generation:
|
||||
{% include "templates/generation_form.html.j2" with context %}
|
||||
</label>
|
||||
<h2>Cat</h2>
|
||||
<label>Name: <input type="text" name="name" /></label>
|
||||
<label>Sex:
|
||||
<select name="sex">
|
||||
<option value="UNDEF">?</option>
|
||||
<option value="MALE">M</option>
|
||||
<option value="FEMALE">F</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Stat:
|
||||
<select name="state">
|
||||
<option value="BABY">Baby</option>
|
||||
<option value="ADULT">Adult</option>
|
||||
<option value="RETREAT">Retreated</option>
|
||||
<option value="DEAD">Dead</option>
|
||||
</select>
|
||||
</label>
|
||||
<h2>Parents</h2>
|
||||
<label>A:
|
||||
<select name="parent_a">
|
||||
<option selected disabled value>Unset</option>
|
||||
{% for cat in cats_parent %}
|
||||
<option value="{{ cat.id }}">{{ cat.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>B:
|
||||
<select name="parent_b">
|
||||
<option selected disabled value>Unset</option>
|
||||
{% for cat in cats_parent %}
|
||||
<option value="{{ cat.id }}">{{ cat.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<h2>Stats</h2>
|
||||
<label>STR: <input type="number" name="stat_str" value="0"/></label>
|
||||
<label>DEX: <input type="number" name="stat_dex" value="0"/></label>
|
||||
<label>CON: <input type="number" name="stat_con" value="0"/></label>
|
||||
<label>INT: <input type="number" name="stat_int" value="0"/></label>
|
||||
<label>SPD: <input type="number" name="stat_spd" value="0"/></label>
|
||||
<label>CHA: <input type="number" name="stat_cha" value="0"/></label>
|
||||
<label>LCK: <input type="number" name="stat_lck" value="0"/></label>
|
||||
|
||||
<input type="submit" value="Add cat" />
|
||||
</form>
|
||||
</div>
|
||||
66
frontend/templates/cat_form_edit.html.j2
Normal file
66
frontend/templates/cat_form_edit.html.j2
Normal file
@ -0,0 +1,66 @@
|
||||
<div id="cat-form">
|
||||
<form
|
||||
hx-patch="/cat/{{ cat.id }}"
|
||||
hx-swap="none"
|
||||
>
|
||||
<h2>Generation</h2>
|
||||
<label>Generation:
|
||||
{% include "templates/generation_form.html.j2" %}
|
||||
</label>
|
||||
<h2>Cat</h2>
|
||||
<input type="hidden" name="id" value="{{ cat.id }}" />
|
||||
<label>Name: <input type="text" name="name" value="{{ cat.name }}" /></label>
|
||||
<label>Sex:
|
||||
<select name="sex">
|
||||
<option {% if cat.sex.name == 'UNDEF' %}selected{% endif %} value="UNDEF">?</option>
|
||||
<option {% if cat.sex.name == 'MALE' %}selected{% endif %} value="MALE">M</option>
|
||||
<option {% if cat.sex.name == 'FEMALE' %}selected{% endif %} value="FEMALE">F</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Stat:
|
||||
<select name="state" value="{{ cat.state.name }}">
|
||||
<option {% if cat.state.name == 'BABY' %}selected{% endif %} value="BABY">Baby</option>
|
||||
<option {% if cat.state.name == 'ADULT' %}selected{% endif %} value="ADULT">Adult</option>
|
||||
<option {% if cat.state.name == 'RETREAT' %}selected{% endif %} value="RETREAT">Retreated</option>
|
||||
<option {% if cat.state.name == 'DEAD' %}selected{% endif %} value="DEAD">Dead</option>
|
||||
</select>
|
||||
</label>
|
||||
<h2>Parents</h2>
|
||||
<label>A:
|
||||
<select name="parent_a">
|
||||
<option selected disabled value>Unset</option>
|
||||
{% for parent_cat_a in cats_parent %}
|
||||
<option
|
||||
{% if parent_cat_a.id == cat.parent_a %}selected{% endif %}
|
||||
value="{{ parent_cat_a.id }}"
|
||||
>
|
||||
{{ parent_cat_a.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>B:
|
||||
<select name="parent_b">
|
||||
<option selected disabled value>Unset</option>
|
||||
{% for parent_cat_b in cats_parent %}
|
||||
<option
|
||||
{% if parent_cat_b.id == cat.parent_b %}selected{% endif %}
|
||||
value="{{ parent_cat_b.id }}"
|
||||
>
|
||||
{{ parent_cat_b.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<h2>Stats</h2>
|
||||
<label>STR: <input type="number" name="stat_str" value="{{ cat.stat_str }}"/></label>
|
||||
<label>DEX: <input type="number" name="stat_dex" value="{{ cat.stat_dex }}"/></label>
|
||||
<label>CON: <input type="number" name="stat_con" value="{{ cat.stat_con }}"/></label>
|
||||
<label>INT: <input type="number" name="stat_int" value="{{ cat.stat_int }}"/></label>
|
||||
<label>SPD: <input type="number" name="stat_spd" value="{{ cat.stat_spd }}"/></label>
|
||||
<label>CHA: <input type="number" name="stat_cha" value="{{ cat.stat_cha }}"/></label>
|
||||
<label>LCK: <input type="number" name="stat_lck" value="{{ cat.stat_lck }}"/></label>
|
||||
|
||||
<input type="submit" value="Update cat" />
|
||||
</form>
|
||||
</div>
|
||||
53
frontend/templates/cat_list.html.j2
Normal file
53
frontend/templates/cat_list.html.j2
Normal file
@ -0,0 +1,53 @@
|
||||
<div
|
||||
id="catlist"
|
||||
>
|
||||
<h1>Cat list ({{ cat_list | length }})</h1>
|
||||
<div class="cat_header cat"
|
||||
hx-get="/hx/cat_list"
|
||||
hx-include="input[name='sortby'], input[name='state_filters']"
|
||||
hx-target="#catlist_data"
|
||||
hx-trigger="load, reloadCatList from:document, change, htmx:after-request from:(#cat-form form) queue: last"
|
||||
>
|
||||
<div>
|
||||
<label>
|
||||
Name
|
||||
<input type="radio" name="sortby" value="name" />
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
Generation
|
||||
<input type="radio" name="sortby" value="generation" />
|
||||
</div>
|
||||
<div>
|
||||
Sex
|
||||
<input type="radio" name="sortby" value="sex" />
|
||||
</div>
|
||||
<div>
|
||||
State
|
||||
<input type="radio" name="sortby" value="state" />
|
||||
</div>
|
||||
<div>
|
||||
Parent A
|
||||
<input type="radio" name="sortby" value="parent_a" />
|
||||
</div>
|
||||
<div>
|
||||
Parent B
|
||||
<input type="radio" name="sortby" value="parent_b" />
|
||||
</div>
|
||||
<div>
|
||||
Stats
|
||||
<input type="radio" name="sortby" value="stats" />
|
||||
</div>
|
||||
<div>
|
||||
Filters<br/>
|
||||
<label>Babys <input type="checkbox" name="state_filters" value="BABY" />
|
||||
<label>Adults <input type="checkbox" name="state_filters" value="ADULT" />
|
||||
<label>Retreated <input type="checkbox" name="state_filters" value="RETREAT" />
|
||||
<label>Dead <input type="checkbox" name="state_filters" value="DEAD" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="catlist_data"
|
||||
>
|
||||
</div>
|
||||
-</div>
|
||||
4
frontend/templates/cat_list_data.html.j2
Normal file
4
frontend/templates/cat_list_data.html.j2
Normal file
@ -0,0 +1,4 @@
|
||||
{% for cat in cat_list %}
|
||||
{% include "templates/cat.html.j2" %}
|
||||
{% endfor %}
|
||||
|
||||
21
frontend/templates/generation_form.html.j2
Normal file
21
frontend/templates/generation_form.html.j2
Normal file
@ -0,0 +1,21 @@
|
||||
<select
|
||||
name="generation"
|
||||
hx-post="/hx/generation/next"
|
||||
hx-trigger="change[target.selectedOptions[0].value=='new']"
|
||||
hx-on::after-request="document.location.reload()"
|
||||
>
|
||||
<option value="new">
|
||||
New generation
|
||||
</option>
|
||||
{% for generation in generations %}
|
||||
<option
|
||||
{% if selected_generation == generation.id %}
|
||||
selected
|
||||
{% endif %}
|
||||
value="{{ generation.id }}"
|
||||
>
|
||||
{{ generation.day }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
1
frontend/templates/option.html.j2
Normal file
1
frontend/templates/option.html.j2
Normal file
@ -0,0 +1 @@
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
0
mewgenics_heredity/__init__.py
Normal file
0
mewgenics_heredity/__init__.py
Normal file
47
mewgenics_heredity/app.py
Normal file
47
mewgenics_heredity/app.py
Normal file
@ -0,0 +1,47 @@
|
||||
from litestar import Litestar, get
|
||||
from litestar.plugins.sqlalchemy import AsyncSessionConfig, SQLAlchemyAsyncConfig, SQLAlchemyPlugin
|
||||
from litestar.plugins.htmx import HTMXPlugin
|
||||
from litestar.contrib.jinja import JinjaTemplateEngine
|
||||
from litestar.template.config import TemplateConfig
|
||||
from litestar.static_files import create_static_files_router
|
||||
from pathlib import Path
|
||||
from .config import config
|
||||
from .controllers import CatController, FrontendController, GenerationController
|
||||
from .middlewares import UrlEncodedMiddleware
|
||||
|
||||
session_config = AsyncSessionConfig(expire_on_commit=False)
|
||||
sqlalchemy_config = SQLAlchemyAsyncConfig(
|
||||
connection_string=config['db.url'],
|
||||
session_config=session_config,
|
||||
create_all=True
|
||||
)
|
||||
|
||||
async def on_startup(app: Litestar) -> None:
|
||||
async with sqlalchemy_config.get_session() as session:
|
||||
pass
|
||||
|
||||
@get(path='/healthcheck')
|
||||
async def healthcheck() -> str:
|
||||
return 'hello world'
|
||||
|
||||
app = Litestar(
|
||||
route_handlers=[
|
||||
healthcheck,
|
||||
create_static_files_router(path='/', directories=['frontend'], html_mode=True),
|
||||
create_static_files_router(path='/static', directories=['frontend/static']),
|
||||
FrontendController,
|
||||
CatController,
|
||||
GenerationController
|
||||
],
|
||||
on_startup=[on_startup],
|
||||
debug=True,
|
||||
plugins=[
|
||||
SQLAlchemyPlugin(config=sqlalchemy_config),
|
||||
HTMXPlugin()
|
||||
],
|
||||
template_config=TemplateConfig(
|
||||
directory=[Path('frontend')],
|
||||
engine=JinjaTemplateEngine
|
||||
),
|
||||
middleware=[UrlEncodedMiddleware()]
|
||||
)
|
||||
3
mewgenics_heredity/config.py
Normal file
3
mewgenics_heredity/config.py
Normal file
@ -0,0 +1,3 @@
|
||||
config = {
|
||||
'db.url': 'postgresql+psycopg://localhost/mewgenics'
|
||||
}
|
||||
90
mewgenics_heredity/controllers/CatController.py
Normal file
90
mewgenics_heredity/controllers/CatController.py
Normal file
@ -0,0 +1,90 @@
|
||||
from uuid import UUID
|
||||
from typing import Annotated
|
||||
from litestar import Controller, get, post, patch, delete
|
||||
from litestar.di import Provide
|
||||
from litestar.enums import RequestEncodingType
|
||||
from litestar.params import Parameter, Body
|
||||
from litestar.plugins.htmx import TriggerEvent
|
||||
from litestar.status_codes import HTTP_200_OK
|
||||
from ..models import Cat, CatRepository, SexEnum, StateEnum
|
||||
from ..models import Generation, GenerationRepository
|
||||
|
||||
class CatController(Controller):
|
||||
dependencies={
|
||||
'cats_repo': Provide(CatRepository.provider),
|
||||
'generation_repo': Provide(GenerationRepository.provider)
|
||||
}
|
||||
|
||||
@post(path='/cat')
|
||||
async def post_cat(
|
||||
self,
|
||||
cats_repo: CatRepository,
|
||||
generation_repo: GenerationRepository,
|
||||
data: Annotated[Cat.create_type, Body(media_type=RequestEncodingType.URL_ENCODED)]
|
||||
) -> Cat.type:
|
||||
raw_data = data.model_dump(exclude_unset=True, exclude_none=True)
|
||||
if isinstance(raw_data['sex'], str):
|
||||
raw_data['sex'] = getattr(SexEnum, raw_data['sex'])
|
||||
|
||||
if isinstance(raw_data['state'], str):
|
||||
raw_data['state'] = getattr(StateEnum, raw_data['state'])
|
||||
|
||||
new_obj = await cats_repo.add(Cat(**raw_data))
|
||||
await cats_repo.session.commit()
|
||||
|
||||
return Cat.type.model_validate(new_obj)
|
||||
|
||||
@patch(path='/cat/{cat_id:uuid}')
|
||||
async def patch_cat_by_id(
|
||||
self,
|
||||
cat_id: UUID,
|
||||
cats_repo: CatRepository,
|
||||
data: Annotated[Cat.type, Body(media_type=RequestEncodingType.URL_ENCODED)]
|
||||
) -> Cat.type:
|
||||
|
||||
raw_data = data.model_dump(exclude_unset=True, exclude_none=True)
|
||||
if isinstance(raw_data['sex'], str):
|
||||
raw_data['sex'] = getattr(SexEnum, raw_data['sex'])
|
||||
|
||||
if isinstance(raw_data['state'], str):
|
||||
raw_data['state'] = getattr(StateEnum, raw_data['state'])
|
||||
|
||||
raw_data['id'] = cat_id
|
||||
new_obj = await cats_repo.update(Cat(**raw_data))
|
||||
await cats_repo.session.commit()
|
||||
|
||||
return Cat.type.model_validate(new_obj)
|
||||
|
||||
|
||||
@get(path='/cat/{cat_id:uuid}')
|
||||
async def get_cat_by_id(
|
||||
self,
|
||||
cats_repo: CatRepository,
|
||||
cat_id: UUID=Parameter(title='Cat ID')
|
||||
) -> Cat.type:
|
||||
|
||||
return await cats_repo.get(cat_id)
|
||||
|
||||
@get(path='/cats')
|
||||
async def get_cat_list(
|
||||
self,
|
||||
cats_repo: CatRepository,
|
||||
) -> list[Cat]:
|
||||
|
||||
return await cats_repo.list()
|
||||
|
||||
@delete(path='/cat/{cat_id:uuid}', status_code=HTTP_200_OK)
|
||||
async def delete_cat_by_id(
|
||||
self,
|
||||
cat_id: UUID,
|
||||
cats_repo: CatRepository,
|
||||
) -> TriggerEvent:
|
||||
await cats_repo.delete(cat_id)
|
||||
|
||||
await cats_repo.session.commit()
|
||||
|
||||
return TriggerEvent(
|
||||
content='Deleted cat',
|
||||
name='reloadCatList',
|
||||
after='receive'
|
||||
)
|
||||
159
mewgenics_heredity/controllers/FrontendController.py
Normal file
159
mewgenics_heredity/controllers/FrontendController.py
Normal file
@ -0,0 +1,159 @@
|
||||
from uuid import UUID
|
||||
from litestar import Controller, get, post
|
||||
from litestar.di import Provide
|
||||
from litestar.response import Template
|
||||
from litestar.plugins.htmx import HTMXRequest, HTMXTemplate
|
||||
from advanced_alchemy.filters import OrderBy, LimitOffset, CollectionFilter
|
||||
from pprint import pprint
|
||||
|
||||
from ..models import Cat, CatRepository, StateEnum, SexEnum
|
||||
from ..models import Generation, GenerationRepository
|
||||
|
||||
class FrontendController(Controller):
|
||||
dependencies={
|
||||
'cats_repo': Provide(CatRepository.provider),
|
||||
'generation_repo': Provide(GenerationRepository.provider)
|
||||
}
|
||||
|
||||
@get(path='/app')
|
||||
async def app_root(self,
|
||||
request: HTMXRequest,
|
||||
cats_repo: CatRepository,
|
||||
generation_repo: GenerationRepository,
|
||||
) -> Template:
|
||||
cat_list = await cats_repo.list()
|
||||
cats_parent = await cats_repo.list(
|
||||
CollectionFilter(Cat.state, [StateEnum.ADULT, StateEnum.RETREAT])
|
||||
)
|
||||
generations = await generation_repo.list(OrderBy(Generation.day, 'desc'))
|
||||
return HTMXTemplate(
|
||||
template_name='templates/app.html.j2',
|
||||
context={
|
||||
'cat_list': cat_list,
|
||||
'generations': generations,
|
||||
'cats_parent': cats_parent,
|
||||
'selected_generation': generations[0].id
|
||||
}
|
||||
)
|
||||
|
||||
@get(path='/hx/cat_list')
|
||||
async def cat_list(
|
||||
self,
|
||||
cats_repo: CatRepository,
|
||||
state_filters: list[str] | None = None,
|
||||
sortby: str = 'name',
|
||||
sortorder: str = 'asc',
|
||||
|
||||
) -> Template:
|
||||
|
||||
filters = []
|
||||
if state_filters:
|
||||
filters.append(
|
||||
CollectionFilter(
|
||||
Cat.state,
|
||||
[
|
||||
getattr(StateEnum, elt)
|
||||
for elt in state_filters
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
elts = await cats_repo.list(
|
||||
*filters,
|
||||
OrderBy(sortby, sortorder)
|
||||
)
|
||||
|
||||
all_cats = await cats_repo.list()
|
||||
cats_by_ids = dict({
|
||||
elt.id: elt
|
||||
for elt in all_cats
|
||||
})
|
||||
|
||||
return Template(
|
||||
template_name='templates/cat_list_data.html.j2',
|
||||
context={
|
||||
'cat_list': elts,
|
||||
'cat_by_ids': cats_by_ids
|
||||
}
|
||||
)
|
||||
|
||||
"""
|
||||
@get(path='/hx/cat_form')
|
||||
async def get_hx_cat_form(self,
|
||||
cats_repo: CatRepository,
|
||||
generation_repo: GenerationRepository
|
||||
) -> Template:
|
||||
cats = await cats_repo.list(
|
||||
CollectionFilter(Cat.state, [StateEnum.ADULT, StateEnum.RETREAT])
|
||||
)
|
||||
generations = await generation_repo.list(OrderBy(Generation.day, 'desc'))
|
||||
if len(generations) == 0:
|
||||
await generation_repo.add(Generation(day=0))
|
||||
await generation_repo.session.commit()
|
||||
generations = await generation_repo.list(OrderBy(Generation.day, 'desc'))
|
||||
|
||||
return Template(
|
||||
template_name='templates/cat_form.html.j2',
|
||||
context={
|
||||
'cats': cats,
|
||||
'generations': generations,
|
||||
'selected_generation': generations[0].id
|
||||
}
|
||||
)
|
||||
"""
|
||||
|
||||
@get(path='/hx/cat_form/{cat_id:uuid}')
|
||||
async def get_hx_cat_form_edit(self,
|
||||
cat_id: UUID,
|
||||
cats_repo: CatRepository,
|
||||
generation_repo: GenerationRepository
|
||||
) -> HTMXTemplate:
|
||||
cat = await cats_repo.get(cat_id)
|
||||
cats_parent = await cats_repo.list()
|
||||
generations = await generation_repo.list(OrderBy(Generation.day, 'desc'))
|
||||
|
||||
return HTMXTemplate(
|
||||
template_name='templates/cat_form_edit.html.j2',
|
||||
context={
|
||||
'cat': cat,
|
||||
'cats_parent': cats_parent,
|
||||
'generations': generations,
|
||||
'selected_generation': cat.generation
|
||||
},
|
||||
re_target='#cat-form',
|
||||
trigger_event='editCat',
|
||||
after='receive'
|
||||
)
|
||||
|
||||
@post(path='/hx/generation/next')
|
||||
async def post_hx_generation_next(
|
||||
self,
|
||||
cats_repo: CatRepository,
|
||||
generation_repo: GenerationRepository
|
||||
) -> HTMXTemplate:
|
||||
|
||||
generations = await generation_repo.list(OrderBy(Generation.day, 'desc'))
|
||||
next_day = 0 if len(generations) == 0 else generations[0].day + 1
|
||||
|
||||
new_obj = await generation_repo.add(Generation(day=next_day))
|
||||
await generation_repo.session.commit()
|
||||
|
||||
await cats_repo.update_many(list(
|
||||
map(
|
||||
lambda cat: cat.grow(),
|
||||
await cats_repo.list(Cat.state == StateEnum.BABY)
|
||||
)
|
||||
))
|
||||
await cats_repo.session.commit()
|
||||
|
||||
generations = await generation_repo.list(OrderBy(Generation.day, 'desc'))
|
||||
|
||||
return HTMXTemplate(
|
||||
template_name='templates/generation_form.html.j2',
|
||||
context={
|
||||
'generations': generations,
|
||||
'selected_generation': new_obj.id
|
||||
},
|
||||
trigger_event="updateCats",
|
||||
after="receive"
|
||||
)
|
||||
35
mewgenics_heredity/controllers/GenerationController.py
Normal file
35
mewgenics_heredity/controllers/GenerationController.py
Normal file
@ -0,0 +1,35 @@
|
||||
from uuid import UUID
|
||||
from litestar import Controller, get, post
|
||||
from litestar.di import Provide
|
||||
from litestar.params import Parameter
|
||||
from advanced_alchemy.filters import OrderBy, LimitOffset
|
||||
from ..models import Generation, GenerationRepository
|
||||
|
||||
class GenerationController(Controller):
|
||||
dependencies={
|
||||
'generation_repo': Provide(GenerationRepository.provider)
|
||||
}
|
||||
|
||||
@post(path='/generation')
|
||||
async def post_generation(
|
||||
self,
|
||||
generation_repo: GenerationRepository,
|
||||
data: Generation.create_type
|
||||
) -> Generation.type:
|
||||
new_obj = await generation_repo.add(Generation(**data.model_dump()))
|
||||
await generation_repo.session.commit()
|
||||
|
||||
return Generation.type.model_validate(new_obj)
|
||||
|
||||
@post(path='/generation/next')
|
||||
async def post_generation_next(
|
||||
self,
|
||||
generation_repo: GenerationRepository
|
||||
) -> Generation.type:
|
||||
|
||||
last_generation = await generation_repo.list(OrderBy(Generation.day, 'desc'), LimitOffset(limit=1, offset=0))
|
||||
next_day = 0 if len(last_generation) == 0 else last_generation.pop().day + 1
|
||||
new_obj = await generation_repo.add(Generation(day=next_day))
|
||||
|
||||
await generation_repo.session.commit()
|
||||
return Generation.type.model_validate(new_obj)
|
||||
4
mewgenics_heredity/controllers/__init__.py
Normal file
4
mewgenics_heredity/controllers/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .CatController import CatController
|
||||
from .FrontendController import FrontendController
|
||||
from .GenerationController import GenerationController
|
||||
|
||||
23
mewgenics_heredity/middlewares/UrlEncodedMiddleware.py
Normal file
23
mewgenics_heredity/middlewares/UrlEncodedMiddleware.py
Normal file
@ -0,0 +1,23 @@
|
||||
from litestar.types import Scope, Receive, Send, ASGIApp
|
||||
from litestar.middleware import ASGIMiddleware
|
||||
from litestar.enums import ScopeType
|
||||
from litestar.datastructures import Headers
|
||||
from pprint import pprint
|
||||
|
||||
class UrlEncodedMiddleware(ASGIMiddleware):
|
||||
scopes = (ScopeType.HTTP,)
|
||||
|
||||
async def handle(
|
||||
self,
|
||||
scope: Scope,
|
||||
receive: Receive,
|
||||
send: Send,
|
||||
next_app: ASGIApp
|
||||
) -> None:
|
||||
headers = Headers.from_scope(scope)
|
||||
if scope['method'] in ['POST', 'PUT', 'PATCH'] and headers['Content-Type'] == 'application/x-www-form-urlencoded':
|
||||
pass
|
||||
|
||||
|
||||
await next_app(scope, receive, send)
|
||||
|
||||
1
mewgenics_heredity/middlewares/__init__.py
Normal file
1
mewgenics_heredity/middlewares/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .UrlEncodedMiddleware import UrlEncodedMiddleware
|
||||
2
mewgenics_heredity/models/__init__.py
Normal file
2
mewgenics_heredity/models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .cat import Cat, SexEnum, StateEnum, CatRepository
|
||||
from .generation import Generation, GenerationRepository
|
||||
12
mewgenics_heredity/models/__main__.py
Normal file
12
mewgenics_heredity/models/__main__.py
Normal file
@ -0,0 +1,12 @@
|
||||
from sqlalchemy import engine_from_config, create_engine
|
||||
from ..config import config
|
||||
from .base import Base
|
||||
from .cat import Cat
|
||||
|
||||
engine = engine_from_config(configuration=config, prefix='db.', echo=True)
|
||||
|
||||
def initdb():
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
if __name__ == '__main__':
|
||||
initdb()
|
||||
7
mewgenics_heredity/models/base.py
Normal file
7
mewgenics_heredity/models/base.py
Normal file
@ -0,0 +1,7 @@
|
||||
from litestar.plugins.sqlalchemy import base
|
||||
from pydantic import BaseModel as _BaseModel
|
||||
|
||||
Base = base.UUIDBase
|
||||
|
||||
class BaseType(_BaseModel):
|
||||
model_config = {'from_attributes': True}
|
||||
139
mewgenics_heredity/models/cat.py
Normal file
139
mewgenics_heredity/models/cat.py
Normal file
@ -0,0 +1,139 @@
|
||||
import enum
|
||||
from uuid import UUID
|
||||
from typing import Optional
|
||||
from litestar.plugins.sqlalchemy import repository
|
||||
from sqlalchemy import String, Enum, ForeignKey, Integer, select
|
||||
from sqlalchemy.orm import Mapped, mapped_column, selectinload, relationship
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from .base import Base, BaseType
|
||||
from .generation import Generation
|
||||
|
||||
class SexEnum(enum.Enum):
|
||||
UNDEF=0
|
||||
MALE=1
|
||||
FEMALE=2
|
||||
|
||||
def __str__(self):
|
||||
if self == SexEnum.UNDEF:
|
||||
return '?'
|
||||
elif self == SexEnum.MALE:
|
||||
return 'M'
|
||||
elif self == SexEnum.FEMALE:
|
||||
return 'F'
|
||||
|
||||
class StateEnum(enum.Enum):
|
||||
BABY=0
|
||||
ADULT=1
|
||||
RETREAT=2
|
||||
DEAD=3
|
||||
|
||||
def __str__(self):
|
||||
if self == StateEnum.BABY:
|
||||
return 'Baby'
|
||||
elif self == StateEnum.ADULT:
|
||||
return 'Adult'
|
||||
elif self == StateEnum.RETREAT:
|
||||
return 'Retreated'
|
||||
elif self == StateEnum.DEAD:
|
||||
return 'Dead'
|
||||
|
||||
class Cat(Base):
|
||||
__tablename__ = 'cats'
|
||||
|
||||
name: Mapped[str] = mapped_column(String(128))
|
||||
sex: Mapped[SexEnum] = mapped_column(Enum(SexEnum))
|
||||
state: Mapped[StateEnum] = mapped_column(Enum(StateEnum), default=StateEnum.BABY)
|
||||
|
||||
parent_a: Mapped[Optional[UUID]] = mapped_column(ForeignKey('cats.id'))
|
||||
parent_b: Mapped[Optional[UUID]] = mapped_column(ForeignKey('cats.id'))
|
||||
|
||||
stat_str: Mapped[int] = mapped_column(Integer)
|
||||
stat_dex: Mapped[int] = mapped_column(Integer)
|
||||
stat_con: Mapped[int] = mapped_column(Integer)
|
||||
stat_int: Mapped[int] = mapped_column(Integer)
|
||||
stat_spd: Mapped[int] = mapped_column(Integer)
|
||||
stat_cha: Mapped[int] = mapped_column(Integer)
|
||||
stat_lck: Mapped[int] = mapped_column(Integer)
|
||||
|
||||
stat_mod_str: Mapped[int] = mapped_column(Integer, default=0)
|
||||
stat_mod_dex: Mapped[int] = mapped_column(Integer, default=0)
|
||||
stat_mod_con: Mapped[int] = mapped_column(Integer, default=0)
|
||||
stat_mod_int: Mapped[int] = mapped_column(Integer, default=0)
|
||||
stat_mod_spd: Mapped[int] = mapped_column(Integer, default=0)
|
||||
stat_mod_cha: Mapped[int] = mapped_column(Integer, default=0)
|
||||
stat_mod_lck: Mapped[int] = mapped_column(Integer, default=0)
|
||||
|
||||
generation_row: Mapped[Generation] = relationship(lazy="joined", innerjoin=True, viewonly=True)
|
||||
generation: Mapped[UUID] = mapped_column(ForeignKey('generation.id'))
|
||||
|
||||
# level: Mapped[int] = mapped_column(Integer, default=0)
|
||||
|
||||
class type(BaseType):
|
||||
id: UUID | None
|
||||
name: str
|
||||
sex: SexEnum | str
|
||||
state: StateEnum | str
|
||||
parent_a: UUID | None = None
|
||||
parent_b: UUID | None = None
|
||||
|
||||
stat_str: int
|
||||
stat_dex: int
|
||||
stat_con: int
|
||||
stat_int: int
|
||||
stat_spd: int
|
||||
stat_cha: int
|
||||
stat_lck: int
|
||||
|
||||
stat_mod_str: int = 0
|
||||
stat_mod_dex: int = 0
|
||||
stat_mod_con: int = 0
|
||||
stat_mod_int: int = 0
|
||||
stat_mod_spd: int = 0
|
||||
stat_mod_cha: int = 0
|
||||
stat_mod_lck: int = 0
|
||||
|
||||
generation: UUID
|
||||
# level: int = 0
|
||||
|
||||
class create_type(BaseType):
|
||||
name: str
|
||||
sex: SexEnum | str
|
||||
state: StateEnum | str | None = StateEnum.BABY
|
||||
parent_a: UUID | None = None
|
||||
parent_b: UUID | None = None
|
||||
|
||||
stat_str: int
|
||||
stat_dex: int
|
||||
stat_con: int
|
||||
stat_int: int
|
||||
stat_spd: int
|
||||
stat_cha: int
|
||||
stat_lck: int
|
||||
|
||||
stat_mod_str: int | None = None
|
||||
stat_mod_dex: int | None = None
|
||||
stat_mod_con: int | None = None
|
||||
stat_mod_int: int | None = None
|
||||
stat_mod_spd: int | None = None
|
||||
stat_mod_cha: int | None = None
|
||||
stat_mod_lck: int | None = None
|
||||
|
||||
generation: UUID
|
||||
# level: int | None = 0
|
||||
|
||||
def grow(self):
|
||||
if self.state.value < StateEnum.DEAD.value:
|
||||
self.state = StateEnum(self.state.value + 1)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class CatRepository(repository.SQLAlchemyAsyncRepository[Cat]):
|
||||
model_type = Cat
|
||||
|
||||
@classmethod
|
||||
async def provider(cls, db_session: AsyncSession) -> CatRepository:
|
||||
return CatRepository(
|
||||
session=db_session,
|
||||
statement=select(cls.model_type).options(selectinload(cls.model_type.generation_row))
|
||||
)
|
||||
26
mewgenics_heredity/models/generation.py
Normal file
26
mewgenics_heredity/models/generation.py
Normal file
@ -0,0 +1,26 @@
|
||||
from uuid import UUID
|
||||
from litestar.plugins.sqlalchemy import repository
|
||||
from sqlalchemy.orm import Mapped
|
||||
from sqlalchemy.orm import mapped_column
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from .base import Base, BaseType
|
||||
|
||||
class Generation(Base):
|
||||
__tablename__ = 'generation'
|
||||
|
||||
day: Mapped[int] = mapped_column()
|
||||
|
||||
class type(BaseType):
|
||||
id: UUID | None
|
||||
day: int
|
||||
|
||||
class create_type(BaseType):
|
||||
day: int
|
||||
|
||||
class GenerationRepository(repository.SQLAlchemyAsyncRepository[Generation]):
|
||||
model_type = Generation
|
||||
|
||||
@staticmethod
|
||||
async def provider(db_session: AsyncSession) -> GenerationRepository:
|
||||
return GenerationRepository(session=db_session)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user