Skip to content

Commit

Permalink
delete and better import/export
Browse files Browse the repository at this point in the history
  • Loading branch information
ZanSara committed Aug 5, 2023
1 parent c9ee039 commit 2f25aeb
Show file tree
Hide file tree
Showing 14 changed files with 234 additions and 41 deletions.
19 changes: 13 additions & 6 deletions flashcards_htmx/api/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def cards_component(

card_templates = db["templates"]
for card in deck["cards"].values():
card["preview"] = Template(card_templates[card["type"]]["preview"]).render(**card["data"])
card["rendered_preview"] = Template(card_templates[card["type"]]["preview"]).render(**card["data"])
return render(deck=deck, deck_id=deck_id)


Expand Down Expand Up @@ -104,10 +104,12 @@ async def deck_confirm_delete_component(
raise HTTPException(status_code=404, detail="Deck not found")

return render(
title=f"Deleting {deck['name']}",
content=f"Are you really sure you wanna delete the deck {deck['name']}? It contains {len(deck['cards'])} cards.",
title=f"Deleting deck",
content=f"Are you really sure you wanna delete the deck '{deck['name']}'? It contains {len(deck['cards'])} cards.",
positive=f"Yes, delete {deck['name']}",
negative=f"No, don't delete",
delete_endpoint="delete_deck_endpoint",
endpoint_params={"deck_id": deck_id},
)


Expand All @@ -123,10 +125,15 @@ async def card_confirm_delete_component(
deck = db["decks"].get(deck_id, {})
if not deck:
raise HTTPException(status_code=404, detail="Deck not found")

card = deck["cards"].get(card_id, {})
if not card:
raise HTTPException(status_code=404, detail="Card not found")
card_templates = db["templates"]
return render(
title=f"Deleting card n. {deck['cards'][card_id]['id']}",
content=f"Are you really sure you wanna delete this card? [TODO show card preview]",
title=f"Deleting card",
content=f"<p>Are you really sure you wanna delete this card?</p><br>" + Template(card_templates[card["type"]]["preview"]).render(**card["data"]),
positive=f"Yes, delete it",
negative=f"No, don't delete",
delete_endpoint="delete_card_endpoint",
endpoint_params={"deck_id": deck_id, "card_id": card_id},
)
21 changes: 0 additions & 21 deletions flashcards_htmx/api/json.py

This file was deleted.

85 changes: 81 additions & 4 deletions flashcards_htmx/api/private.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import Optional
from pathlib import Path
import shelve
import json
from copy import deepcopy

from jinja2 import Template
import starlette.status as status
from fastapi import APIRouter, Request, Depends, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi import APIRouter, Request, Depends, HTTPException, UploadFile, File
from fastapi.responses import HTMLResponse, RedirectResponse, FileResponse
from fastapi.templating import Jinja2Templates

from flashcards_htmx.app import template, database
Expand All @@ -17,10 +19,13 @@

@router.get("/home", response_class=HTMLResponse)
async def home_page(request: Request, render=Depends(template("private/home.html"))):
with shelve.open(database) as db:
print([len(deck["cards"]) for deck in db["decks"].values()])
return render(
navbar_title="Home",
searchable=True,
new_item_endpoint=request.url_for("create_deck_page"),
upload_item_endpoint=request.url_for("import_deck_page"),
new_item_text="New Deck...",
)

Expand Down Expand Up @@ -51,6 +56,49 @@ async def create_deck_page(render=Depends(template("private/deck.html"))):
)


@router.get("/decks/import", response_class=HTMLResponse)
async def import_deck_page(render=Depends(template("private/import.html"))):
return render()


@router.post('/decks/import', response_class=RedirectResponse)
async def import_deck_endpoint(file: UploadFile):
try:
contents = await file.read()
deck = json.loads(contents)
with shelve.open(database) as db:
db["decks"][str(len(db["decks"])+1)] = deck
except Exception:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='There was an error importing the deck',
)
finally:
await file.close()
return RedirectResponse(router.url_path_for("home_page"), status_code=status.HTTP_302_FOUND)


@router.get("/decks/{deck_id}/export", response_class=FileResponse)
async def export_deck_endpoint(request: Request):
with shelve.open(database) as db:
deck = deepcopy(db["decks"].get(request.path_params["deck_id"], {}))
if not deck:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)

# Remove reviews and renderings
deck["cards"] = {
card_id: {key: val for key, val in card.items() if key != "reviews" and not key.startswith("rendered_")}
for card_id, card in deck["cards"].items()
}


path = Path(__file__).parent.parent / f"tmp/{deck['name']}.json"

with open(path, 'w') as file:
json.dump(deck, file, indent=4)
return FileResponse(path, media_type='application/octet-stream', filename=f"{deck['name']}.json")


@router.get("/decks/{deck_id}", response_class=HTMLResponse)
async def edit_deck_page(deck_id: str, render=Depends(template("private/deck.html"))):
with shelve.open(database) as db:
Expand All @@ -67,16 +115,29 @@ async def save_deck_endpoint(deck_id: str, request: Request):
if not "decks" in db:
db["decks"] = {}
db["decks"][deck_id] = {
**db["decks"].get(deck_id, {"cards": {}}),
"name": form["name"],
"description": form["description"],
"algorithm": form["algorithm"],
"cards": {},
"algorithm": form["algorithm"]
}
return RedirectResponse(
request.url_for("home_page"), status_code=status.HTTP_302_FOUND
)


@router.get("/decks/{deck_id}/delete", response_class=RedirectResponse)
async def delete_deck_endpoint(
request: Request,
deck_id: str
):
with shelve.open(database) as db:
if deck_id not in db["decks"]:
raise HTTPException(status_code=404, detail="Deck not found")
del db["decks"][deck_id]

return RedirectResponse(request.url_for("home_page"), status_code=status.HTTP_302_FOUND)


@router.get("/decks/{deck_id}/cards", response_class=HTMLResponse)
async def cards_page(
deck_id: str, request: Request, render=Depends(template("private/cards.html"))
Expand Down Expand Up @@ -180,6 +241,22 @@ async def save_card_endpoint(deck_id: str, card_id: Optional[str], request: Requ
)


@router.get("/decks/{deck_id}/cards/{card_id}/delete", response_class=RedirectResponse)
async def delete_card_endpoint(
request: Request,
deck_id: str,
card_id: str,
):
with shelve.open(database) as db:
if deck_id not in db["decks"]:
raise HTTPException(status_code=404, detail="Deck not found")
if card_id not in db["decks"][deck_id]["cards"]:
raise HTTPException(status_code=404, detail="Card not found")
del db["decks"][deck_id]["cards"][card_id]

return RedirectResponse(request.url_for("cards_page", deck_id=deck_id), status_code=status.HTTP_302_FOUND)


@router.post("/logout", response_class=RedirectResponse)
async def logout_page(request: Request):
return RedirectResponse(
Expand Down
4 changes: 1 addition & 3 deletions flashcards_htmx/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,15 @@ def render(*args, **kwargs):
async def http_exception_handler(request: Request, exc: HTTPException):
template = get_jinja2().get_template("public/http_error.html")
response = template.render(request=request, code=exc.status_code, message=exc.detail)
return HTMLResponse(response)
return HTMLResponse(response, status_code=exc.status_code)


from flashcards_htmx.api.public import router as public_router # noqa: F401, E402
from flashcards_htmx.api.private import router as private_router # noqa: F401, E402
from flashcards_htmx.api.components import (
router as private_components,
) # noqa: F401, E402
from flashcards_htmx.api.json import router as json_router # noqa: F401, E402

app.include_router(public_router)
app.include_router(private_router)
app.include_router(private_components)
app.include_router(json_router)
2 changes: 1 addition & 1 deletion flashcards_htmx/static/css/private-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ section.cards {
margin: 0;
padding: 0;
display: grid;
grid-template-columns: 1fr auto auto;
grid-template-columns: 1fr auto auto auto;
align-items: center;
gap: 1rem;
}
Expand Down
2 changes: 1 addition & 1 deletion flashcards_htmx/static/css/public-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
--page-corner-radius: 1rem;
}

p, a, h1, h2, h3, h4, h5, h6 {
* {
font-family: 'Lato', sans-serif;
}

Expand Down
2 changes: 1 addition & 1 deletion flashcards_htmx/templates/components/card.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div>
<div class="box">
<a class="content card" href="{{ url_for('edit_card_page', deck_id=deck_id, card_id=id) }}">
{{ card.preview|safe }}
{{ card.rendered_preview|safe }}
</a>
</div>
<div class="tags">
Expand Down
3 changes: 3 additions & 0 deletions flashcards_htmx/templates/components/deck.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ <h1>{{ deck.name }}</h1>
<a class="dotted" href="{{ url_for('cards_page', deck_id=id) }}">
<i class="fas fa-search"></i>
</a>
<a class="dotted" href="{{ url_for('export_deck_endpoint', deck_id=id) }}">
<i class="fas fa-download"></i>
</a>
</div>
<p>{{ deck.description }}</p>
<p class="deck-type">{{ deck.algorithm }}</p>
Expand Down
6 changes: 3 additions & 3 deletions flashcards_htmx/templates/components/message-modal.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div id="modal" _="on closeModal add .closing then wait for animationend then remove me">
<div class="modal-underlay" _="on click trigger closeModal"></div>
<div class="modal-content box">
<div class="modal-content box" style="padding: 2rem; text-align:center;">
<h1>{{ title }}</h1>
<p>{{ content }}</p>
{{ content|safe }}
<div class="buttons">
<button class="positive" _="on click trigger closeModal">{{ positive }}</button>
<a href="{{ url_for(delete_endpoint, **endpoint_params) }}" class="positive">{{ positive }}</a>
<button class="negative" _="on click trigger closeModal">{{ negative }}</button>
</div>
</div>
Expand Down
5 changes: 5 additions & 0 deletions flashcards_htmx/templates/private/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ <h1>{{ navbar_title }}</h1>
<a href="{{ new_item_endpoint }}">
<i class="fas fa-plus"></i>
</a>
{% if upload_item_endpoint %}
<a href="{{ upload_item_endpoint }}">
<i class="fas fa-upload"></i>
</a>
{% endif %}
<div class="paginator">
<a class="dotted"><i class="fa fa-chevron-left"></i></a>
<p>X of XX</p>
Expand Down
5 changes: 4 additions & 1 deletion flashcards_htmx/templates/private/deck.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
{% endif %}
</div>
</form>


<hr />

</div>

</section>
{% endblock %}
25 changes: 25 additions & 0 deletions flashcards_htmx/templates/private/import.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% extends "private/base.html" %}


{% block css%}
<link href="/static/css/deck-details.css" rel="stylesheet">
<link href="/static/css/modal.css" rel="stylesheet">
{% endblock %}


{% block page %}
<section id="single-card">
<div class="box main-card">
<form action="{{ url_for('import_deck_endpoint') }}" method="post" enctype="multipart/form-data">
<div>
<input type="file" name="file">
</div>
<div class="feedback feedback-negative hidden"><p></p></div>
<div class="buttons">
<button type="submit" class="positive">Import</button>
<a href="{{ url_for('home_page') }}" class="neutral">Cancel</a>
</div>
</form>
</div>
</section>
{% endblock %}
61 changes: 61 additions & 0 deletions flashcards_htmx/tmp/1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"name": "1",
"description": "111",
"algorithm": "Random",
"cards": {
"1": {
"data": {
"question": {
"word": "1"
},
"answer": {
"word": "2"
},
"preview": {}
},
"tags": [
"1"
],
"type": "Q/A"
},
"2": {
"data": {
"question": {
"word": "2"
},
"answer": {
"word": "3"
},
"preview": {}
},
"tags": [],
"type": "Q/A"
},
"3": {
"data": {
"question": {
"word": "3"
},
"answer": {
"word": "4"
},
"preview": {}
},
"tags": [],
"type": "Q/A"
},
"4": {
"data": {
"question": {
"word": "4"
},
"answer": {
"word": "5"
},
"preview": {}
},
"tags": [],
"type": "Q/A"
}
}
}
Loading

0 comments on commit 2f25aeb

Please sign in to comment.