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