From 05c40d0148cdd97b2819e52869e616345ed25234 Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Tue, 20 May 2025 18:22:02 +0200 Subject: [PATCH] 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 --- .envrc | 2 -- app/forms.py | 65 ++++++++++++++++++++++++++++++---------- app/models.py | 61 ++++++++++++++++++++----------------- app/templates/edit.html | 51 ++++++++++++++++++++++++------- app/templates/new.html | 4 +-- app/templates/view.html | 54 ++++++++++++++++++++++++++++++--- app/views.py | 65 +++++++++++++++++++++++++++------------- flake.nix | 2 +- instance/application.db | Bin 12288 -> 12288 bytes 9 files changed, 222 insertions(+), 82 deletions(-) delete mode 100644 .envrc diff --git a/.envrc b/.envrc deleted file mode 100644 index 4d3e59f..0000000 --- a/.envrc +++ /dev/null @@ -1,2 +0,0 @@ -use flake -export FLASK_APP=app \ No newline at end of file diff --git a/app/forms.py b/app/forms.py index 452ad00..5cf3bcd 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,30 +1,65 @@ +from typing import Any 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 + class NewWishlist(FlaskForm): - title = StringField("Title:", validators=[DataRequired()]) - description = StringField("Description:", validators=[DataRequired()]) - submit = SubmitField("Submit") + title = StringField("Title:", validators=[DataRequired()]) + description = StringField("Description:", validators=[DataRequired()]) + submit = SubmitField("Submit") + # Each submit needs a different page fot it to work on the same page. class DeleteWishlist(FlaskForm): - wl_del_submit = SubmitField("Delete wishlist") + wl_del_submit = SubmitField("Delete wishlist") + class EditWishlistInfo(FlaskForm): - title = StringField("Title:", validators=[DataRequired()]) - description = StringField("Description:", validators=[DataRequired()]) - wl_edit_submit = SubmitField("Submit") + title = StringField("Title", validators=[DataRequired()]) + description = StringField("Description", validators=[DataRequired()]) + wl_edit_submit = SubmitField("Submit") + class ResetWishlistUrls(FlaskForm): - wl_reset_submit = SubmitField("Reset urls") + wl_reset_submit = SubmitField("Reset urls") + class NewItem(FlaskForm): - title = StringField("Title:", validators=[DataRequired()]) - description = StringField("Description:", validators=[DataRequired()]) - price = IntegerField("Price:", validators=[DataRequired()]) - it_new_submit = SubmitField("Submit") + title = StringField("Title", validators=[DataRequired()]) + description = StringField("Description", validators=[DataRequired()]) + price = FloatField("Price", validators=[DataRequired()]) + url = URLField("Url", validators=[DataRequired()]) + image = URLField("Image url", validators=[DataRequired()]) + it_new_submit = SubmitField("Submit") + + +class CheckItem(FlaskForm): + num = HiddenField() + class DeleteItem(FlaskForm): - index = HiddenField() - it_del_submit = SubmitField("Delete item") + index = HiddenField() + 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 diff --git a/app/models.py b/app/models.py index 00ecd18..33228ee 100644 --- a/app/models.py +++ b/app/models.py @@ -1,35 +1,42 @@ -from typing import List from app import db -from sqlalchemy.orm import Mapped, mapped_column, RelationshipProperty -from sqlalchemy import Uuid, String, Text -from uuid import uuid4 as uuid +from sqlalchemy.orm import Mapped, mapped_column, RelationshipProperty, relationship +from sqlalchemy import Uuid, String, Text, String, ForeignKey +from uuid import uuid4 as uuid, UUID + class Item(db.Model): - def __init__(self, title: str, description: str, price: float): - self.wishlist_id = None - self.title = title - self.description = description - self.price = price - self.bought = False + def __init__( + self, title: str, description: str, price: float, url: str, image: str + ): + self.bought = False + self.wishlist_id = None + 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): - def __init__(self, title: str, description: str): - self.editId = uuid() - self.viewId = uuid() - self.title = title - self.description = description + def __init__(self, title: str, description: str): + self.editId = uuid() + self.viewId = uuid() + self.title = title + self.description = description - id: Mapped[int] = mapped_column(primary_key=True) - editId: Mapped[Uuid] = mapped_column(Uuid) - viewId: Mapped[Uuid] = mapped_column(Uuid) - title: Mapped[str] = mapped_column(String(250)) - description: Mapped[str] = mapped_column(Text) + id: Mapped[int] = mapped_column(primary_key=True) + editId: Mapped[UUID] = mapped_column(Uuid) + viewId: Mapped[UUID] = mapped_column(Uuid) + title: Mapped[str] = mapped_column(String(250)) + description: Mapped[str] = mapped_column(Text) - items: Mapped[List["Item"]] = db.relationship("Item", backref="post") \ No newline at end of file + items: Mapped[list["Item"]] = relationship("Item", backref="post") diff --git a/app/templates/edit.html b/app/templates/edit.html index e3cd676..1ffc299 100644 --- a/app/templates/edit.html +++ b/app/templates/edit.html @@ -7,19 +7,22 @@ {{ form_wl_editinfo.title.label }} {{ form_wl_editinfo.title() }} + {{ form_wl_editinfo.description.label }} {{ form_wl_editinfo.description() }} + {{ form_wl_editinfo.wl_edit_submit() }} +

Reset urls

@@ -29,41 +32,67 @@ {{ form_wl_reseturls.wl_reset_submit() }} -
-

Delete wishlist

-
- {{ form_wl_delete.hidden_tag() }} - {{ form_wl_delete.wl_del_submit() }} -
-

New item

{{ form_it_new.hidden_tag() }} + {{ form_it_new.title.label }} {{ form_it_new.title() }} + {{ form_it_new.description.label }} {{ form_it_new.description() }} + {{ form_it_new.price.label }} {{ form_it_new.price() }} + + {{ form_it_new.url.label }} + {{ form_it_new.url() }} + + + {{ form_it_new.image.label }} + {{ form_it_new.image() }} + + {{ form_it_new.it_new_submit() }}

Delete items

+ +{% if wishlist.items|length == 0 %} +

No items yet

+{% endif %} \ No newline at end of file + + +
+

Delete wishlist

+
+ {{ form_wl_delete.hidden_tag() }} + {{ form_wl_delete.wl_del_submit() }} +
+ + diff --git a/app/templates/new.html b/app/templates/new.html index 5667c9e..1979102 100644 --- a/app/templates/new.html +++ b/app/templates/new.html @@ -1,4 +1,4 @@ -
+ {{ form.hidden_tag() }} {{ form.title.label }} @@ -8,4 +8,4 @@ {{ form.description() }} {{ form.submit() }} -
\ No newline at end of file + diff --git a/app/templates/view.html b/app/templates/view.html index 448bfb0..5df40ec 100644 --- a/app/templates/view.html +++ b/app/templates/view.html @@ -4,7 +4,53 @@ edit \ No newline at end of file + {% if wishlist.items|length == 0 %} +

No items yet

+ {% endif %} + {% for item in wishlist.items %} +
  • +
    + {{ form.csrf_token }} + {{ form.num(value = loop.index) }} + + + {{ item.title }}: {{ item.description }} +
    +
  • + {% endfor %} + + + +

    Are you sure you bought this product. You won't be able to undo this.

    +
    + + +
    +
    + + diff --git a/app/views.py b/app/views.py index ce6d1b4..c168c6a 100644 --- a/app/views.py +++ b/app/views.py @@ -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.forms import ( NewWishlist, @@ -7,6 +7,8 @@ from app.forms import ( ResetWishlistUrls, NewItem, DeleteItem, + CheckItem, + parseHiddenIndex, ) from app.models import Wishlist, Item from uuid import UUID, uuid4 as uuid @@ -21,7 +23,7 @@ def index(): def new(): form = NewWishlist() 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.commit() @@ -44,7 +46,7 @@ def edit(id: str): form_it_delete = DeleteItem() # 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: db.session.delete(i) db.session.delete(wishlist) @@ -52,12 +54,15 @@ def edit(id: str): return redirect(url_for("index")) elif form_wl_editinfo.validate_on_submit() and form_wl_editinfo.wl_edit_submit.data: - wishlist.title = form_wl_editinfo.title.data - wishlist.description = form_wl_editinfo.description.data + wishlist.title = str(form_wl_editinfo.title.data) + wishlist.description = str(form_wl_editinfo.description.data) db.session.commit() 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.viewId = uuid() db.session.commit() @@ -65,27 +70,36 @@ def edit(id: str): return redirect(url_for("edit", id=wishlist.editId)) elif form_it_new.validate_on_submit() and form_it_new.it_new_submit.data: f = form_it_new + price = f.price.data if f.price.data != None else 0 + item = Item( - f.title.data, - f.description.data, - f.price.data + str( + f.title.data, + ), + str( + f.description.data, + ), + price, + str( + f.url.data, + ), + str( + f.image.data, + ), ) wishlist.items.append(item) db.session.commit() return redirect(url_for("edit", id=id)) elif form_it_delete.validate_on_submit() and form_it_delete.it_del_submit.data: - print(form_it_delete.index.data) - data = form_it_delete.index.data - i = int(data) if data != None else 0 - if i + 1 > len(wishlist.items): - return "Invalid item to delete", 400 - - wishlist.items.remove(wishlist.items[i]) + index = parseHiddenIndex(form_it_delete.index, wishlist.items) + if index == None: + return abort(400) + + wishlist.items.remove(wishlist.items[index]) db.session.commit() return redirect(url_for("edit", id=id)) - return render_template( "edit.html", @@ -98,11 +112,22 @@ def edit(id: str): ) -@app.route("/view/") +@app.route("/view/", methods=["GET", "POST"]) def view(id: str): - wishlist = db.one_or_404( + wishlist: Wishlist = db.one_or_404( db.select(Wishlist).filter_by(viewId=UUID(id)), 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) diff --git a/flake.nix b/flake.nix index a97daa2..6fa056c 100644 --- a/flake.nix +++ b/flake.nix @@ -14,7 +14,7 @@ { nativeBuildInputs = [ (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 ]; }; diff --git a/instance/application.db b/instance/application.db index 8580d3a7c1c18024aed001ed7f2c4bf7ceb293df..556d43aef0b4a6c8a501b6c1b7da084c8ea4a312 100644 GIT binary patch delta 198 zcmZojXh@hK&B#1a##xY=L9fYymw|zSiT5P~zZ&n$jfIuGTuoL??Bb%Lj4k1l<@uJ% zl@{eFgt$h8DERq@DENi?_~OF4U-JbQ`5{6&5RQb%o9_R42=yz3 zQ;ZEQj8c-5OLG8e1f%Sf&{mrx_(%rkJEzrY0H~rzNLZS{Nl;8XFlU8iC9M zVLvX2foA53W{Cz#rfG>umL{g=#z~1L$rc7i#%U?Z21y3yM#)KuDF!B%X2xkIi6)7b zW=UxV1|}BCNyY|d$!Qjr1|YLaQj6Sr{1^C#R)Zm>658CK(!8n42aWn3@}!r=?jU9LNDNFFDO3DJd<* z%)l(o%*5CzB{3z<+$_;7DKRa{+|1B0#lp}i)hMYxCCMbs*vuq3&B!v*GBqX5($w74 z(AXk1%^d2&k^pv)fzrZB#ugSyNtQ;52BxMaX{IJgDFzmXX66=&7Rd&tW+rAS$*Jb1 zW@aXd$ti{=sm4YYCP~RA#>vTvNfzeGiOHtkS*dy1sYN;YIgxB26U8-CEmP`E%*@S9 z4Ga>~Oe`%@l8h|V63q?L43jOAjf@S=lG9QxQ%#f2Oj8mKO$^PGEesNq&CE@clhRU+ z%u)4+$`19C@IAtDJ9j!Fv$R$*(?yVQ%sU9lT*zs zEmKm`OwvpZk_=K(ElktQL4HUEMYwTdl0~XXa*Cxvl1Xy1Sz@Ajl9`FQWnyA#nt`dY zF_O{sFn4<}gUsgVH?TA`uuM%gFt#)^GqE%_O*Az(NHQ`qNHRAxGcrm`GfA{CHZV6# zHnp%YGD%6auuM%cGe|N{N;EQ0OfpYRN=x%#f|z7%kY;RRZfI#@X=q|%X>6EeY-Vh1 zU~FKRY;Kg2l9HTkoSbG-Z*E{>U~Xz*l9*zUXl!n7XpoqgY?x|pn386kmgd0-G0HG8 z)yyn8Ez!a_)zBo(AlWj_z}(Uz)yOQ-C^gk2)yTv&Db2_%HO0_8#ndD*HN_&$IL+L` z#LUphAT7l_IWa8_R9G