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:
parent
3ba6099976
commit
05c40d0148
9 changed files with 222 additions and 82 deletions
2
.envrc
2
.envrc
|
|
@ -1,2 +0,0 @@
|
||||||
use flake
|
|
||||||
export FLASK_APP=app
|
|
||||||
65
app/forms.py
65
app/forms.py
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
65
app/views.py
65
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 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)
|
||||||
|
|
|
||||||
|
|
@ -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.
Loading…
Add table
Add a link
Reference in a new issue