improve ui
This commit is contained in:
parent
7c0ba4c2aa
commit
b18572ee56
9 changed files with 187 additions and 40 deletions
176
src/main.py
176
src/main.py
|
@ -56,6 +56,39 @@ def admin_list_podcasts(session: SessionDep, request: Request):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/admin/new")
|
||||||
|
def admin_create_podcast(request: Request):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request=request,
|
||||||
|
name="admin_new.html.j2",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/admin/new")
|
||||||
|
def admin_create_podcast_post(
|
||||||
|
session: SessionDep,
|
||||||
|
request: Request,
|
||||||
|
name: Annotated[str, Form()],
|
||||||
|
):
|
||||||
|
if name.strip() == "":
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
|
status_code=400,
|
||||||
|
context={
|
||||||
|
"title": "Invalid entry",
|
||||||
|
"description": "Name must not be empty",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
podcast = models.Podcast(name=name, description=name)
|
||||||
|
|
||||||
|
session.add(podcast)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return RedirectResponse(f"/admin/{podcast.id}", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/admin/{podcast_id}")
|
@app.get("/admin/{podcast_id}")
|
||||||
def admin_list_podcast(session: SessionDep, request: Request, podcast_id: str):
|
def admin_list_podcast(session: SessionDep, request: Request, podcast_id: str):
|
||||||
podcast = session.exec(
|
podcast = session.exec(
|
||||||
|
@ -63,7 +96,15 @@ def admin_list_podcast(session: SessionDep, request: Request, podcast_id: str):
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if podcast is None:
|
if podcast is None:
|
||||||
raise HTTPException(status_code=404, detail="Podcast not found")
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
|
status_code=400,
|
||||||
|
context={
|
||||||
|
"title": "Not found",
|
||||||
|
"description": "The podcast was not found.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
episodes = podcast.episodes
|
episodes = podcast.episodes
|
||||||
episodes.sort(key=lambda e: e.publish_date, reverse=True)
|
episodes.sort(key=lambda e: e.publish_date, reverse=True)
|
||||||
|
@ -170,29 +211,11 @@ async def admin_upload_episode(
|
||||||
|
|
||||||
|
|
||||||
@app.get("/admin/{podcast_id}/{episode_id}/delete")
|
@app.get("/admin/{podcast_id}/{episode_id}/delete")
|
||||||
def admin_delete_episode(session: SessionDep, podcast_id: str, episode_id: str):
|
def admin_delete_episode(
|
||||||
episode = session.exec(
|
session: SessionDep,
|
||||||
select(models.PodcastEpisode).where(
|
request: Request,
|
||||||
and_(
|
podcast_id: str,
|
||||||
models.PodcastEpisode.id == episode_id,
|
episode_id: str,
|
||||||
models.PodcastEpisode.podcast_id == podcast_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if episode is None:
|
|
||||||
raise HTTPException(status_code=404, detail="Episode or podcast not found")
|
|
||||||
|
|
||||||
(settings.directory / f"{episode_id}.m4a").unlink()
|
|
||||||
session.delete(episode)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
return RedirectResponse(f"/admin/{podcast_id}", status_code=303)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/admin/{podcast_id}/{episode_id}/edit")
|
|
||||||
def admin_edit_episode(
|
|
||||||
session: SessionDep, request: Request, podcast_id: str, episode_id: str
|
|
||||||
):
|
):
|
||||||
episode = session.exec(
|
episode = session.exec(
|
||||||
select(models.PodcastEpisode).where(
|
select(models.PodcastEpisode).where(
|
||||||
|
@ -204,7 +227,49 @@ def admin_edit_episode(
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if episode is None:
|
if episode is None:
|
||||||
raise HTTPException(status_code=404, detail="Episode or podcast not found")
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
|
status_code=404,
|
||||||
|
context={
|
||||||
|
"title": "Not found",
|
||||||
|
"description": "The episode or podcast was not found.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
(settings.directory / f"{episode_id}.m4a").unlink()
|
||||||
|
session.delete(episode)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return RedirectResponse(f"/admin/{podcast_id}", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/admin/{podcast_id}/{episode_id}/edit")
|
||||||
|
def admin_edit_episode(
|
||||||
|
session: SessionDep,
|
||||||
|
request: Request,
|
||||||
|
podcast_id: str,
|
||||||
|
episode_id: str,
|
||||||
|
):
|
||||||
|
episode = session.exec(
|
||||||
|
select(models.PodcastEpisode).where(
|
||||||
|
and_(
|
||||||
|
models.PodcastEpisode.id == episode_id,
|
||||||
|
models.PodcastEpisode.podcast_id == podcast_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if episode is None:
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
|
status_code=404,
|
||||||
|
context={
|
||||||
|
"title": "Not found",
|
||||||
|
"description": "The episode or podcast was not found.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
|
@ -216,6 +281,7 @@ def admin_edit_episode(
|
||||||
@app.post("/admin/{podcast_id}/{episode_id}/edit")
|
@app.post("/admin/{podcast_id}/{episode_id}/edit")
|
||||||
def admin_edit_episode_post(
|
def admin_edit_episode_post(
|
||||||
session: SessionDep,
|
session: SessionDep,
|
||||||
|
request: Request,
|
||||||
podcast_id: str,
|
podcast_id: str,
|
||||||
episode_id: str,
|
episode_id: str,
|
||||||
name: Annotated[str, Form()],
|
name: Annotated[str, Form()],
|
||||||
|
@ -231,7 +297,15 @@ def admin_edit_episode_post(
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if episode is None:
|
if episode is None:
|
||||||
raise HTTPException(status_code=404, detail="Episode or podcast not found")
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
|
status_code=404,
|
||||||
|
context={
|
||||||
|
"title": "Not found",
|
||||||
|
"description": "The episode or podcast was not found.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if name.strip() != "":
|
if name.strip() != "":
|
||||||
episode.name = name
|
episode.name = name
|
||||||
|
@ -254,7 +328,15 @@ def admin_edit_podcast(session: SessionDep, request: Request, podcast_id: str):
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if podcast is None:
|
if podcast is None:
|
||||||
raise HTTPException(status_code=404, detail="Podcast not found")
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
|
status_code=404,
|
||||||
|
context={
|
||||||
|
"title": "Not found",
|
||||||
|
"description": "The podcast was not found.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
|
@ -266,6 +348,7 @@ def admin_edit_podcast(session: SessionDep, request: Request, podcast_id: str):
|
||||||
@app.post("/admin/{podcast_id}/edit")
|
@app.post("/admin/{podcast_id}/edit")
|
||||||
def admin_edit_podcast_post(
|
def admin_edit_podcast_post(
|
||||||
session: SessionDep,
|
session: SessionDep,
|
||||||
|
request: Request,
|
||||||
podcast_id: str,
|
podcast_id: str,
|
||||||
name: Annotated[str, Form()],
|
name: Annotated[str, Form()],
|
||||||
description: Annotated[str, Form()],
|
description: Annotated[str, Form()],
|
||||||
|
@ -276,7 +359,15 @@ def admin_edit_podcast_post(
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if podcast is None:
|
if podcast is None:
|
||||||
raise HTTPException(status_code=404, detail="Podcast not found")
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
|
status_code=404,
|
||||||
|
context={
|
||||||
|
"title": "Not found",
|
||||||
|
"description": "The podcast was not found.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if name.strip() != "":
|
if name.strip() != "":
|
||||||
podcast.name = name
|
podcast.name = name
|
||||||
|
@ -285,23 +376,38 @@ def admin_edit_podcast_post(
|
||||||
|
|
||||||
if image is not None and image.size > 0:
|
if image is not None and image.size > 0:
|
||||||
if not (image.filename.endswith(".jpg") or image.filename.endswith(".png")):
|
if not (image.filename.endswith(".jpg") or image.filename.endswith(".png")):
|
||||||
raise HTTPException(
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail="The uploaded podcast image must be a jpg or png",
|
context={
|
||||||
|
"title": "Invalid entry",
|
||||||
|
"description": "The uploaded podcast image must be a jpg or png.",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
im = Image.open(image.file)
|
im = Image.open(image.file)
|
||||||
|
|
||||||
if im.size[0] != im.size[1] or im.size[0] < 1400 or im.size[0] > 3000:
|
if im.size[0] != im.size[1] or im.size[0] < 1400 or im.size[0] > 3000:
|
||||||
raise HTTPException(
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail="The uploaded podcast image must be square and between 1400x1400px and 3000x3000px in size",
|
context={
|
||||||
|
"title": "Invalid entry",
|
||||||
|
"description": "The uploaded podcast image must be square and between 1400x1400px and 3000x3000px in size.",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if im.mode != "RGB":
|
if im.mode != "RGB":
|
||||||
raise HTTPException(
|
return templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
name="admin_error.html.j2",
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail="The uploaded podcast image must be in RGB format",
|
context={
|
||||||
|
"title": "Invalid entry",
|
||||||
|
"description": "The uploaded podcast image must be in RGB format.",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if podcast.image_filename is not None:
|
if podcast.image_filename is not None:
|
||||||
|
@ -318,7 +424,7 @@ def admin_edit_podcast_post(
|
||||||
|
|
||||||
|
|
||||||
@app.get("/{podcast_id}.xml")
|
@app.get("/{podcast_id}.xml")
|
||||||
def get_feed(session: SessionDep, request: Request, podcast_id: str):
|
def get_podcast_feed(session: SessionDep, request: Request, podcast_id: str):
|
||||||
podcast = session.exec(
|
podcast = session.exec(
|
||||||
select(models.Podcast).where(models.Podcast.id == podcast_id)
|
select(models.Podcast).where(models.Podcast.id == podcast_id)
|
||||||
).first()
|
).first()
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Podcast(SQLModel, table=True):
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
explicit: bool = Field(default=True)
|
explicit: bool = Field(default=True)
|
||||||
image_filename: Optional[str] = Field(default=False)
|
image_filename: Optional[str] = Field(default=None)
|
||||||
|
|
||||||
episodes: list["PodcastEpisode"] = Relationship(back_populates="podcast")
|
episodes: list["PodcastEpisode"] = Relationship(back_populates="podcast")
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% extends 'layout.html.j2' %}
|
{% extends 'admin_layout.html.j2' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{{ super() }}
|
||||||
<h1>{{ episode.name }}</h1>
|
<h1>{{ episode.name }}</h1>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|
7
src/templates/admin_error.html.j2
Normal file
7
src/templates/admin_error.html.j2
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends 'admin_layout.html.j2' %}
|
||||||
|
{% block content %}
|
||||||
|
{{ super() }}
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<p>{{ description }}</p>
|
||||||
|
<p><a href="#" onclick="history.back()">Go Back</a></p>
|
||||||
|
{% endblock %}
|
|
@ -1,5 +1,6 @@
|
||||||
{% extends 'layout.html.j2' %}
|
{% extends 'admin_layout.html.j2' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{{ super() }}
|
||||||
{% if podcast.image_filename %}
|
{% if podcast.image_filename %}
|
||||||
<img src="/{{ podcast.id }}/{{ podcast.image_filename }}" width="256px" />
|
<img src="/{{ podcast.id }}/{{ podcast.image_filename }}" width="256px" />
|
||||||
<br><br>
|
<br><br>
|
||||||
|
@ -74,6 +75,7 @@
|
||||||
function setFormEnabled(enabled) {
|
function setFormEnabled(enabled) {
|
||||||
submitButton.disabled = !enabled;
|
submitButton.disabled = !enabled;
|
||||||
fileInput.disabled = !enabled;
|
fileInput.disabled = !enabled;
|
||||||
|
resp.setAttribute("aria-busy", JSON.stringify(!enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
window.go = () => {
|
window.go = () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% extends 'layout.html.j2' %}
|
{% extends 'admin_layout.html.j2' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{{ super() }}
|
||||||
<h1>{{ podcast.name }}</h1>
|
<h1>{{ podcast.name }}</h1>
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{% extends 'layout.html.j2' %}
|
{% extends 'admin_layout.html.j2' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{{ super() }}
|
||||||
<h1>Podcasts</h1>
|
<h1>Podcasts</h1>
|
||||||
|
<p>
|
||||||
|
<b>Actions:</b> <a href="/admin/new">Create</a>
|
||||||
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for podcast in podcasts %}
|
{% for podcast in podcasts %}
|
||||||
<li><a href="/admin/{{ podcast.id }}">{{ podcast.name }}</a></li>
|
<li><a href="/admin/{{ podcast.id }}">{{ podcast.name }}</a></li>
|
||||||
|
|
11
src/templates/admin_layout.html.j2
Normal file
11
src/templates/admin_layout.html.j2
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends 'layout.html.j2' %}
|
||||||
|
{% block content %}
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Podcast Server</strong></li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/admin">Podcasts</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endblock %}
|
15
src/templates/admin_new.html.j2
Normal file
15
src/templates/admin_new.html.j2
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'admin_layout.html.j2' %}
|
||||||
|
{% block content %}
|
||||||
|
{{ super() }}
|
||||||
|
<h1>New Podcast</h1>
|
||||||
|
<form method="post">
|
||||||
|
<fieldset>
|
||||||
|
<label>
|
||||||
|
Name
|
||||||
|
<input name="name" required />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<input type="submit" value="Create" />
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue