Flesh out player and deck HTML
This commit is contained in:
parent
5472dbc8b9
commit
fbbaadd098
@ -1,12 +1,14 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from .routers import decks, players
|
||||
from .routers import api_router, html_router
|
||||
from .sql.models import Base
|
||||
from .sql.database import engine
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI()
|
||||
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
||||
|
||||
app.include_router(players.router)
|
||||
app.include_router(decks.router)
|
||||
app.include_router(api_router)
|
||||
app.include_router(html_router)
|
||||
|
@ -0,0 +1,12 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import decks, players
|
||||
|
||||
api_router = APIRouter(prefix="/api")
|
||||
html_router = APIRouter()
|
||||
|
||||
api_router.include_router(decks.api_router)
|
||||
api_router.include_router(players.api_router)
|
||||
|
||||
html_router.include_router(decks.html_router)
|
||||
html_router.include_router(players.html_router)
|
@ -1,13 +1,17 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..templates import jinja_templates, _jsonify
|
||||
from ..sql import crud, schemas
|
||||
from ..sql.database import get_db
|
||||
|
||||
router = APIRouter()
|
||||
from .players import read_player, list_players
|
||||
|
||||
api_router = APIRouter(tags=["deck"])
|
||||
html_router = APIRouter(include_in_schema=False)
|
||||
|
||||
@router.post("/deck", response_model=schemas.Deck, tags=["deck"], status_code=201)
|
||||
@api_router.post("/deck", response_model=schemas.Deck, status_code=201)
|
||||
def create_deck(deck: schemas.DeckCreate, db: Session = Depends(get_db)):
|
||||
db_player = crud.get_player_by_id(db, deck.owner_id)
|
||||
if db_player is None:
|
||||
@ -16,7 +20,7 @@ def create_deck(deck: schemas.DeckCreate, db: Session = Depends(get_db)):
|
||||
return crud.create_deck(db=db, deck=deck)
|
||||
|
||||
|
||||
@router.get("/deck/{deck_id}", response_model=schemas.Deck, tags=["deck"])
|
||||
@api_router.get("/deck/{deck_id}", response_model=schemas.Deck)
|
||||
def read_deck(deck_id: str, db = Depends(get_db)):
|
||||
db_deck = crud.get_deck_by_id(db, deck_id)
|
||||
if db_deck is None:
|
||||
@ -24,11 +28,52 @@ def read_deck(deck_id: str, db = Depends(get_db)):
|
||||
return db_deck
|
||||
|
||||
|
||||
@router.get("/decks", response_model=list[schemas.Deck], tags=["deck"])
|
||||
@api_router.get("/decks", response_model=list[schemas.Deck])
|
||||
def list_decks(skip: int = 0, limit: int = 100, db = Depends(get_db)):
|
||||
return crud.get_decks(db, skip=skip, limit=limit)
|
||||
|
||||
|
||||
@router.delete("/deck/{deck_id}", tags=["deck"], status_code=204)
|
||||
@api_router.delete("/deck/{deck_id}", status_code=204)
|
||||
def delete_deck(deck_id: str, db = Depends(get_db)):
|
||||
crud.delete_deck_by_id(db, int(deck_id))
|
||||
|
||||
|
||||
@html_router.get("/deck/create", response_class=HTMLResponse)
|
||||
def deck_create_html(request: Request, db = Depends(get_db)):
|
||||
players = list_players(db=db)
|
||||
return jinja_templates.TemplateResponse(
|
||||
request,
|
||||
"deck_create.html",
|
||||
{
|
||||
"players": players
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@html_router.get("/deck/{deck_id}", response_class=HTMLResponse)
|
||||
def deck_html(request: Request, deck_id: str, db = Depends(get_db)):
|
||||
deck_info = read_deck(deck_id, db)
|
||||
return jinja_templates.TemplateResponse(
|
||||
request,
|
||||
"deck_detail.html",
|
||||
{
|
||||
"deck": _jsonify(deck_info),
|
||||
"owner": _jsonify(deck_info.owner)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# TODO - pagination
|
||||
@html_router.get("/decks", response_class=HTMLResponse)
|
||||
def decks_html(request: Request, db = Depends(get_db)):
|
||||
decks = list_decks(db=db)
|
||||
print(decks)
|
||||
return jinja_templates.TemplateResponse(
|
||||
request,
|
||||
"deck_list.html",
|
||||
{
|
||||
# TODO - investigate if there are any issues to passing the "live" object into the
|
||||
# template, as opposed to the `_jsonify`'d one.
|
||||
"decks": decks
|
||||
}
|
||||
)
|
@ -1,18 +1,20 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..templates import jinja_templates
|
||||
from ..sql import crud, schemas
|
||||
from ..sql.database import get_db
|
||||
|
||||
router = APIRouter()
|
||||
api_router = APIRouter(tags=["player"])
|
||||
html_router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/player", response_model=schemas.Player, tags=["player"], status_code=201)
|
||||
@api_router.post("/player", response_model=schemas.Player, status_code=201)
|
||||
def create_player(player: schemas.PlayerCreate, db: Session = Depends(get_db)):
|
||||
return crud.create_player(db=db, player=player)
|
||||
|
||||
|
||||
@router.get("/player/{player_id}", response_model=schemas.Player, tags=["player"])
|
||||
@api_router.get("/player/{player_id}", response_model=schemas.Player)
|
||||
def read_player(player_id: str, db = Depends(get_db)):
|
||||
db_player = crud.get_player_by_id(db, player_id)
|
||||
if db_player is None:
|
||||
@ -20,11 +22,31 @@ def read_player(player_id: str, db = Depends(get_db)):
|
||||
return db_player
|
||||
|
||||
|
||||
@router.get("/players", response_model=list[schemas.Player], tags=["player"])
|
||||
@api_router.get("/players", response_model=list[schemas.Player])
|
||||
def list_players(skip: int = 0, limit: int = 100, db = Depends(get_db)):
|
||||
return crud.get_players(db, skip=skip, limit=limit)
|
||||
|
||||
|
||||
@router.delete("/player/{player_id}", tags=["player"], status_code=204)
|
||||
@api_router.delete("/player/{player_id}", status_code=204)
|
||||
def delete_player(player_id: str, db = Depends(get_db)):
|
||||
crud.delete_player_by_id(db, int(player_id))
|
||||
|
||||
|
||||
@html_router.get("/player/create", response_class=HTMLResponse)
|
||||
def player_create_html(request: Request, db = Depends(get_db)):
|
||||
return jinja_templates.TemplateResponse(
|
||||
request,
|
||||
"player_create.html"
|
||||
)
|
||||
|
||||
|
||||
@html_router.get("/player/{player_id}", response_class=HTMLResponse)
|
||||
def player_html(request: Request, player_id: str, db = Depends(get_db)):
|
||||
player_info = read_player(player_id, db)
|
||||
return jinja_templates.TemplateResponse(
|
||||
request,
|
||||
"player_detail.html",
|
||||
{
|
||||
"player": player_info
|
||||
}
|
||||
)
|
||||
|
3
app/static/js/base.js
Normal file
3
app/static/js/base.js
Normal file
@ -0,0 +1,3 @@
|
||||
$.ajaxSetup({
|
||||
contentType: "application/json; charset=utf-8"
|
||||
});
|
16
app/static/js/deck_create.js
Normal file
16
app/static/js/deck_create.js
Normal file
@ -0,0 +1,16 @@
|
||||
$(document).ready(function() {
|
||||
$('#create_button').click(function() {
|
||||
$.post({
|
||||
url: '/api/deck',
|
||||
data: JSON.stringify({
|
||||
'name': $('#name').val(),
|
||||
'description': $('#description').val(),
|
||||
'owner_id': $('#owner_id').val()
|
||||
}),
|
||||
contentType: 'application/json; charset=utf-8'
|
||||
}).done(function (response) {
|
||||
deck_id = response['id'];
|
||||
window.location.href = '/deck/' + deck_id
|
||||
});
|
||||
})
|
||||
})
|
14
app/static/js/player_create.js
Normal file
14
app/static/js/player_create.js
Normal file
@ -0,0 +1,14 @@
|
||||
$(document).ready(function() {
|
||||
$('#create_button').click(function() {
|
||||
$.post({
|
||||
url: '/api/player',
|
||||
data: JSON.stringify({
|
||||
'name': $('#name').val()
|
||||
}),
|
||||
contentType: 'application/json; charset=utf-8'
|
||||
}).done(function (response) {
|
||||
player_id = response['id'];
|
||||
window.location.href = '/player/' + player_id
|
||||
});
|
||||
})
|
||||
})
|
16
app/templates/__init__.py
Normal file
16
app/templates/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
# *NO* idea if this layout is actually good or encouraged -
|
||||
# but I couldn't put this instantiation in `app.__init__.py` or
|
||||
# `app.routers.__init__.py` and then import from there into (say)
|
||||
# `app.routers.decks.py` because:
|
||||
#
|
||||
# ImportError: cannot import name 'jinja_templates' from partially initialized module 'app.routers' (most likely due to a circular import) (/Users/scubbo/Code/edh-elo/app/routers/__init__.py)
|
||||
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
jinja_templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
# TODO - would this be better as a method on a class extending `db.Model` that the classes in `models.py` could then
|
||||
# extend?
|
||||
# (Probably not, as we'd still need to explicitly call it - it wouldn't be implicitly called _by_ Flask)
|
||||
def _jsonify(o):
|
||||
return {k: v for (k, v) in o.__dict__.items() if k != "_sa_instance_state"}
|
19
app/templates/base.html
Normal file
19
app/templates/base.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{% block head %}
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.7.1.min.js"
|
||||
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
|
||||
crossorigin="anonymous"></script>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">{% block content %}{% endblock %}</div>
|
||||
<div id="footer">
|
||||
{% block footer %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
27
app/templates/deck_create.html
Normal file
27
app/templates/deck_create.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Create Deck{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<script src="/static/js/deck_create.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<label for="name">Deck Name</label>
|
||||
<input type="text" name="name" id="name" />
|
||||
|
||||
<label for="description">Description (optional)</label>
|
||||
<input type="text" name="description" id="description" />
|
||||
|
||||
|
||||
<label for="owner_id">Owner</label>
|
||||
<select name="owner_id" id="owner_id">
|
||||
{% for player in players %}
|
||||
<option value="{{ player.id }}">{{ player.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="button" id="create_button" value="Submit"/>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
@ -5,10 +5,9 @@
|
||||
<title>Deck - {{ deck.name }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<h2>This is the page for deck {{ deck.name }} with id {{ deck.id }}, owned by {{ owner.name }}</h2>
|
||||
{% if description %}
|
||||
<p>The description of the deck is: {{ description }}</p>
|
||||
{% if deck.description %}
|
||||
<p>The description of the deck is: {{ deck.description }}</p>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
24
app/templates/deck_list.html
Normal file
24
app/templates/deck_list.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Decks</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Decks</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Deck Name</th>
|
||||
<th>Owner</th>
|
||||
</tr>
|
||||
{% for deck in decks %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/deck/{{ deck.id }}">{{ deck.name }}</a>
|
||||
</td>
|
||||
<td>{{ deck.owner.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
16
app/templates/player_create.html
Normal file
16
app/templates/player_create.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Create Player{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<script src="/static/js/player_create.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<label for="name">Player Name</label>
|
||||
<input type="text" name="name" id="name" />
|
||||
|
||||
<input type="button" id="create_button" value="Submit"/>
|
||||
</form>
|
||||
{% endblock %}
|
@ -2,10 +2,10 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Player - {{ name }}</title>
|
||||
<title>Player - {{ player.name }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<h2>This is the page for player {{ name }} who has id {{ id }}</h2>
|
||||
<h2>This is the page for player {{ player.name }} who has id {{ player.id }}</h2>
|
||||
</body>
|
||||
</html>
|
@ -50,6 +50,9 @@ def test_add_and_retrieve_deck(test_client: TestClient):
|
||||
assert get_deck_response.status_code == 200
|
||||
assert get_deck_response.json()["name"] == "Baby's First Deck"
|
||||
|
||||
# Very basic HTML testing
|
||||
html_response = test_client.get(f"/deck/{deck_id}")
|
||||
assert "owned by jim" in html_response.text
|
||||
|
||||
# Cleanup
|
||||
delete_response = _json_delete(test_client, f"/deck/{deck_id}")
|
||||
@ -57,12 +60,12 @@ def test_add_and_retrieve_deck(test_client: TestClient):
|
||||
|
||||
|
||||
def _json_get(c: TestClient, path: str) -> httpx.Response:
|
||||
return c.get(path, headers={"Content-Type": "application/json"})
|
||||
return c.get(f'/api{path}', headers={"Content-Type": "application/json"})
|
||||
|
||||
|
||||
def _json_post(c: TestClient, path: str, body: Mapping) -> httpx.Response:
|
||||
return c.post(path, headers={"Content-Type": "application/json"}, json=body)
|
||||
return c.post(f'/api{path}', headers={"Content-Type": "application/json"}, json=body)
|
||||
|
||||
|
||||
def _json_delete(c: TestClient, path: str) -> httpx.Response:
|
||||
return c.delete(path, headers={"Content-Type": "application/json"})
|
||||
return c.delete(f'/api{path}', headers={"Content-Type": "application/json"})
|
||||
|
Loading…
x
Reference in New Issue
Block a user