Added a bit of style to edit.html

Added buy capability to view with dialog

Added additional properties to Item (url and imageurl); NEEDS TESTING

Made delete items work properly
This commit is contained in:
Jurn Wubben 2025-05-20 18:22:02 +02:00
parent 3ba6099976
commit 05c40d0148
9 changed files with 222 additions and 82 deletions

2
.envrc
View file

@ -1,2 +0,0 @@
use flake
export FLASK_APP=app

View file

@ -1,30 +1,65 @@
from typing import Any
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, IntegerField, HiddenField from wtforms import (
StringField,
SubmitField,
IntegerField,
HiddenField,
FloatField,
URLField,
)
from wtforms.validators import DataRequired from wtforms.validators import DataRequired
class NewWishlist(FlaskForm): class NewWishlist(FlaskForm):
title = StringField("Title:", validators=[DataRequired()]) title = StringField("Title:", validators=[DataRequired()])
description = StringField("Description:", validators=[DataRequired()]) description = StringField("Description:", validators=[DataRequired()])
submit = SubmitField("Submit") submit = SubmitField("Submit")
# Each submit needs a different page fot it to work on the same page. # Each submit needs a different page fot it to work on the same page.
class DeleteWishlist(FlaskForm): class DeleteWishlist(FlaskForm):
wl_del_submit = SubmitField("Delete wishlist") wl_del_submit = SubmitField("Delete wishlist")
class EditWishlistInfo(FlaskForm): class EditWishlistInfo(FlaskForm):
title = StringField("Title:", validators=[DataRequired()]) title = StringField("Title", validators=[DataRequired()])
description = StringField("Description:", validators=[DataRequired()]) description = StringField("Description", validators=[DataRequired()])
wl_edit_submit = SubmitField("Submit") wl_edit_submit = SubmitField("Submit")
class ResetWishlistUrls(FlaskForm): class ResetWishlistUrls(FlaskForm):
wl_reset_submit = SubmitField("Reset urls") wl_reset_submit = SubmitField("Reset urls")
class NewItem(FlaskForm): class NewItem(FlaskForm):
title = StringField("Title:", validators=[DataRequired()]) title = StringField("Title", validators=[DataRequired()])
description = StringField("Description:", validators=[DataRequired()]) description = StringField("Description", validators=[DataRequired()])
price = IntegerField("Price:", validators=[DataRequired()]) price = FloatField("Price", validators=[DataRequired()])
it_new_submit = SubmitField("Submit") url = URLField("Url", validators=[DataRequired()])
image = URLField("Image url", validators=[DataRequired()])
it_new_submit = SubmitField("Submit")
class CheckItem(FlaskForm):
num = HiddenField()
class DeleteItem(FlaskForm): class DeleteItem(FlaskForm):
index = HiddenField() index = HiddenField()
it_del_submit = SubmitField("Delete item") it_del_submit = SubmitField("Delete item")
def parseHiddenIndex(field: HiddenField, array: list[Any]) -> int | None:
try:
if field.data == None or field.data == "":
raise ValueError()
index = int(field.data)
if index > len(array):
raise ValueError()
return index - 1
except ValueError:
return None

View file

@ -1,35 +1,42 @@
from typing import List
from app import db from app import db
from sqlalchemy.orm import Mapped, mapped_column, RelationshipProperty from sqlalchemy.orm import Mapped, mapped_column, RelationshipProperty, relationship
from sqlalchemy import Uuid, String, Text from sqlalchemy import Uuid, String, Text, String, ForeignKey
from uuid import uuid4 as uuid from uuid import uuid4 as uuid, UUID
class Item(db.Model): class Item(db.Model):
def __init__(self, title: str, description: str, price: float): def __init__(
self.wishlist_id = None self, title: str, description: str, price: float, url: str, image: str
self.title = title ):
self.description = description self.bought = False
self.price = price self.wishlist_id = None
self.bought = False self.title = title
self.description = description
self.price = price
self.url = url
self.image = image
id: Mapped[int] = mapped_column(primary_key=True)
wishlist_id: Mapped[int | None] = mapped_column(ForeignKey("wishlist.id"))
title: Mapped[str] = mapped_column(String(250))
description: Mapped[str] = mapped_column(Text)
price: Mapped[float] = mapped_column()
url: Mapped[str] = mapped_column(Text)
image: Mapped[str] = mapped_column(Text)
bought: Mapped[bool] = mapped_column()
id: Mapped[int] = mapped_column(primary_key=True)
wishlist_id: Mapped[int | None] = mapped_column(db.ForeignKey("wishlist.id"))
title: Mapped[str] = mapped_column(db.String(250))
description: Mapped[str] = mapped_column(db.Text)
price: Mapped[float] = mapped_column()
bought: Mapped[bool] = mapped_column()
class Wishlist(db.Model): class Wishlist(db.Model):
def __init__(self, title: str, description: str): def __init__(self, title: str, description: str):
self.editId = uuid() self.editId = uuid()
self.viewId = uuid() self.viewId = uuid()
self.title = title self.title = title
self.description = description self.description = description
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
editId: Mapped[Uuid] = mapped_column(Uuid) editId: Mapped[UUID] = mapped_column(Uuid)
viewId: Mapped[Uuid] = mapped_column(Uuid) viewId: Mapped[UUID] = mapped_column(Uuid)
title: Mapped[str] = mapped_column(String(250)) title: Mapped[str] = mapped_column(String(250))
description: Mapped[str] = mapped_column(Text) description: Mapped[str] = mapped_column(Text)
items: Mapped[List["Item"]] = db.relationship("Item", backref="post") items: Mapped[list["Item"]] = relationship("Item", backref="post")

View file

@ -7,19 +7,22 @@
{{ form_wl_editinfo.title.label }} {{ form_wl_editinfo.title.label }}
{{ form_wl_editinfo.title() }} {{ form_wl_editinfo.title() }}
<!-- <br> -->
{{ form_wl_editinfo.description.label }} {{ form_wl_editinfo.description.label }}
{{ form_wl_editinfo.description() }} {{ form_wl_editinfo.description() }}
<!-- <br> -->
{{ form_wl_editinfo.wl_edit_submit() }} {{ form_wl_editinfo.wl_edit_submit() }}
</form> </form>
<br>
<h1>Reset urls</h1> <h1>Reset urls</h1>
<ul> <ul>
<li> <li>
View: {{ wishlist.viewId }} View: <a href={{ url_for("view", id=wishlist.viewId) }}>{{ wishlist.viewId }}</a>
</li> </li>
<li> <li>
Edit: {{ wishlist.editId }} Edit: <a href={{ url_for("edit", id=wishlist.editId) }}>{{ wishlist.editId }}</a>
</li> </li>
</ul> </ul>
@ -29,41 +32,67 @@
{{ form_wl_reseturls.wl_reset_submit() }} {{ form_wl_reseturls.wl_reset_submit() }}
</form> </form>
<br>
<h1>Delete wishlist</h1>
<form action="{{ cpath }}" method="POST">
{{ form_wl_delete.hidden_tag() }}
{{ form_wl_delete.wl_del_submit() }}
</form>
<br> <br>
<h1>New item</h1> <h1>New item</h1>
<form action="{{ cpath }}" method="POST"> <form action="{{ cpath }}" method="POST">
{{ form_it_new.hidden_tag() }} {{ form_it_new.hidden_tag() }}
<!-- <br> -->
{{ form_it_new.title.label }} {{ form_it_new.title.label }}
{{ form_it_new.title() }} {{ form_it_new.title() }}
<!-- <br> -->
{{ form_it_new.description.label }} {{ form_it_new.description.label }}
{{ form_it_new.description() }} {{ form_it_new.description() }}
<!-- <br> -->
{{ form_it_new.price.label }} {{ form_it_new.price.label }}
{{ form_it_new.price() }} {{ form_it_new.price() }}
<!-- <br> -->
{{ form_it_new.url.label }}
{{ form_it_new.url() }}
<!-- <br> -->
{{ form_it_new.image.label }}
{{ form_it_new.image() }}
<!-- <br> -->
{{ form_it_new.it_new_submit() }} {{ form_it_new.it_new_submit() }}
</form> </form>
<br> <br>
<h1>Delete items</h1> <h1>Delete items</h1>
{% if wishlist.items|length == 0 %}
<p>No items yet</p>
{% endif %}
<ul> <ul>
{% for value in wishlist.items %} {% for value in wishlist.items %}
<li> <li>
<form action="{{ cpath }}" method="POST"> <form action="{{ cpath }}" method="POST">
{{ form_it_delete.hidden_tag() }} {{ form_it_delete.csrf_token }}
{{ form_it_delete.index(value=loop.index) }} {{ form_it_delete.index(value=loop.index) }}
{{ form_it_delete.it_del_submit() }} {{ form_it_delete.it_del_submit() }}
</form> </form>
{{ value.title }} {{ value.title }}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<br>
<h1>Delete wishlist</h1>
<form action="{{ cpath }}" method="POST">
{{ form_wl_delete.hidden_tag() }}
{{ form_wl_delete.wl_del_submit() }}
</form>
<style>
form {
display:grid;
grid-template-columns: max-content max-content;
grid-gap:5px;
}
form label { text-align:right; }
form label:after { content: ":"; }
</style>

View file

@ -1,4 +1,4 @@
<form action="/new" method="POST"> <form action="{{ url_for("new") }}" method="POST">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
{{ form.title.label }} {{ form.title.label }}
@ -8,4 +8,4 @@
{{ form.description() }} {{ form.description() }}
{{ form.submit() }} {{ form.submit() }}
</form> </form>

View file

@ -4,7 +4,53 @@
<a href="{{url_for('edit', id=wishlist.editId)}}">edit</a> <a href="{{url_for('edit', id=wishlist.editId)}}">edit</a>
<ul> <ul>
{% for item in wishlist.items %} {% if wishlist.items|length == 0 %}
<li><input type=checkbox {{ "checked" if item.bought }} {{ item.title }}: {{ item.description }}</li> <p>No items yet</p>
{% endfor %} {% endif %}
</ul> {% for item in wishlist.items %}
<li>
<form method="POST" href="{{ url_for("view", id=wishlist.editId) }}">
{{ form.csrf_token }}
{{ form.num(value = loop.index) }}
<input type=checkbox {{ "checked disabled" if item.bought}}>
{{ item.title }}: {{ item.description }}
</form>
</li>
{% endfor %}
</ul>
<dialog>
<p>Are you sure you bought this product. You won't be able to undo this.</p>
<form method="dialog">
<button id="dialog-confirm-ok">OK</button>
<button id="dialog-confirm-no">Close</button>
</form>
</dialog>
<script>
const $ = (...f) => document.querySelector(...f);
const $all = (...f) => document.querySelectorAll(...f);
const items = Array.from($all("input[type=checkbox]"));
const dialog = $("dialog")
const dialogOk = $("#dialog-confirm-ok")
const dialogNo = $("#dialog-confirm-no")
for (let checkbox of items) {
checkbox.addEventListener("click", e => {
e.preventDefault();
const onclick = () => checkbox.form.submit();
dialogOk.addEventListener("click", onclick);
dialogNo.addEventListener(
"click",
() => dialogOk.removeEventListener("click", onclick)
);
console.log(dialog)
dialog.show();
});
}
</script>

View file

@ -1,4 +1,4 @@
from flask import url_for, redirect, render_template from flask import url_for, redirect, render_template, abort
from app import app, db from app import app, db
from app.forms import ( from app.forms import (
NewWishlist, NewWishlist,
@ -7,6 +7,8 @@ from app.forms import (
ResetWishlistUrls, ResetWishlistUrls,
NewItem, NewItem,
DeleteItem, DeleteItem,
CheckItem,
parseHiddenIndex,
) )
from app.models import Wishlist, Item from app.models import Wishlist, Item
from uuid import UUID, uuid4 as uuid from uuid import UUID, uuid4 as uuid
@ -21,7 +23,7 @@ def index():
def new(): def new():
form = NewWishlist() form = NewWishlist()
if form.validate_on_submit(): if form.validate_on_submit():
wishlist = Wishlist(form.title.data, form.description.data) wishlist = Wishlist(str(form.title.data), str(form.description.data))
db.session.add(wishlist) db.session.add(wishlist)
db.session.commit() db.session.commit()
@ -44,7 +46,7 @@ def edit(id: str):
form_it_delete = DeleteItem() form_it_delete = DeleteItem()
# Each submit needs a different page fot it to work on the same page. # Each submit needs a different page fot it to work on the same page.
if form_wl_delete.validate_on_submit() and form_wl_delete.wl_del_submit.data: if form_wl_delete.validate_on_submit() and form_wl_delete.wl_del_submit.data:
for i in wishlist.items: for i in wishlist.items:
db.session.delete(i) db.session.delete(i)
db.session.delete(wishlist) db.session.delete(wishlist)
@ -52,12 +54,15 @@ def edit(id: str):
return redirect(url_for("index")) return redirect(url_for("index"))
elif form_wl_editinfo.validate_on_submit() and form_wl_editinfo.wl_edit_submit.data: elif form_wl_editinfo.validate_on_submit() and form_wl_editinfo.wl_edit_submit.data:
wishlist.title = form_wl_editinfo.title.data wishlist.title = str(form_wl_editinfo.title.data)
wishlist.description = form_wl_editinfo.description.data wishlist.description = str(form_wl_editinfo.description.data)
db.session.commit() db.session.commit()
return redirect(url_for("edit", id=id)) return redirect(url_for("edit", id=id))
elif form_wl_reseturls.validate_on_submit() and form_wl_reseturls.wl_reset_submit.data: elif (
form_wl_reseturls.validate_on_submit()
and form_wl_reseturls.wl_reset_submit.data
):
wishlist.editId = uuid() wishlist.editId = uuid()
wishlist.viewId = uuid() wishlist.viewId = uuid()
db.session.commit() db.session.commit()
@ -65,27 +70,36 @@ def edit(id: str):
return redirect(url_for("edit", id=wishlist.editId)) return redirect(url_for("edit", id=wishlist.editId))
elif form_it_new.validate_on_submit() and form_it_new.it_new_submit.data: elif form_it_new.validate_on_submit() and form_it_new.it_new_submit.data:
f = form_it_new f = form_it_new
price = f.price.data if f.price.data != None else 0
item = Item( item = Item(
f.title.data, str(
f.description.data, f.title.data,
f.price.data ),
str(
f.description.data,
),
price,
str(
f.url.data,
),
str(
f.image.data,
),
) )
wishlist.items.append(item) wishlist.items.append(item)
db.session.commit() db.session.commit()
return redirect(url_for("edit", id=id)) return redirect(url_for("edit", id=id))
elif form_it_delete.validate_on_submit() and form_it_delete.it_del_submit.data: elif form_it_delete.validate_on_submit() and form_it_delete.it_del_submit.data:
print(form_it_delete.index.data) index = parseHiddenIndex(form_it_delete.index, wishlist.items)
data = form_it_delete.index.data if index == None:
i = int(data) if data != None else 0 return abort(400)
if i + 1 > len(wishlist.items):
return "Invalid item to delete", 400 wishlist.items.remove(wishlist.items[index])
wishlist.items.remove(wishlist.items[i])
db.session.commit() db.session.commit()
return redirect(url_for("edit", id=id)) return redirect(url_for("edit", id=id))
return render_template( return render_template(
"edit.html", "edit.html",
@ -98,11 +112,22 @@ def edit(id: str):
) )
@app.route("/view/<id>") @app.route("/view/<id>", methods=["GET", "POST"])
def view(id: str): def view(id: str):
wishlist = db.one_or_404( wishlist: Wishlist = db.one_or_404(
db.select(Wishlist).filter_by(viewId=UUID(id)), db.select(Wishlist).filter_by(viewId=UUID(id)),
description="Failed to get wishlist. Are you sure this is the correct url?", description="Failed to get wishlist. Are you sure this is the correct url?",
) )
checkform = CheckItem()
checkform.num
if checkform.validate_on_submit():
index = parseHiddenIndex(checkform.num, wishlist.items)
if index == None:
return abort(400)
return render_template("view.html", wishlist=wishlist) wishlist.items[index].bought = True
db.session.commit()
return redirect(url_for("view", id=id))
return render_template("view.html", wishlist=wishlist, form=checkform)

View file

@ -14,7 +14,7 @@
{ {
nativeBuildInputs = [ nativeBuildInputs = [
(pkgs.python3.withPackages (pkgs.python3.withPackages
(x: [x.flask x.flask-wtf x.wtforms x.flask-sqlalchemy x.uuid])) (x: [x.flask x.flask-wtf x.wtforms x.flask-sqlalchemy]))
pkgs.entr pkgs.entr
]; ];
}; };

Binary file not shown.