From bb7b68a1aa7dedb12f0b2be12e43223aaa65890d Mon Sep 17 00:00:00 2001 From: Nathaniel Landau Date: Fri, 25 Oct 2024 20:18:32 -0400 Subject: [PATCH] feat(webui): edit character inventory --- .../blueprints/character_edit/blueprint.py | 7 +- .../blueprints/character_edit/route_info.py | 136 +++++++++++++++++- .../character_edit/CustomSectionForm.jinja | 9 +- .../character_edit/InventoryItemForm.jinja | 31 ++++ .../templates/character_edit/NoteForm.jinja | 9 +- .../templates/character_view/Inventory.jinja | 65 ++++++--- 6 files changed, 230 insertions(+), 27 deletions(-) create mode 100644 src/valentina/webui/blueprints/character_edit/templates/character_edit/InventoryItemForm.jinja diff --git a/src/valentina/webui/blueprints/character_edit/blueprint.py b/src/valentina/webui/blueprints/character_edit/blueprint.py index 2163b332..5bf6be0e 100644 --- a/src/valentina/webui/blueprints/character_edit/blueprint.py +++ b/src/valentina/webui/blueprints/character_edit/blueprint.py @@ -2,7 +2,7 @@ from quart import Blueprint -from .route_info import EditCharacterCustomSection, EditCharacterNote +from .route_info import EditCharacterCustomSection, EditCharacterInventory, EditCharacterNote from .route_profile import EditProfile from .route_spend_points import SpendPoints, SpendPointsType @@ -44,3 +44,8 @@ view_func=EditCharacterNote.as_view("note"), methods=["GET", "POST", "DELETE"], ) +blueprint.add_url_rule( + "/character//edit/inventory", + view_func=EditCharacterInventory.as_view("inventory"), + methods=["GET", "POST", "DELETE"], +) diff --git a/src/valentina/webui/blueprints/character_edit/route_info.py b/src/valentina/webui/blueprints/character_edit/route_info.py index 3c2d034d..7543da59 100644 --- a/src/valentina/webui/blueprints/character_edit/route_info.py +++ b/src/valentina/webui/blueprints/character_edit/route_info.py @@ -1,24 +1,42 @@ """Route for editing character info such as notes and custom sheet sections.""" +from typing import ClassVar from uuid import UUID +from flask_discord import requires_authorization from quart import Response, abort, request, session, url_for from quart.views import MethodView from quart_wtf import QuartForm from wtforms import ( HiddenField, + SelectField, StringField, SubmitField, TextAreaField, ) from wtforms.validators import DataRequired, Length -from valentina.models import Character, CharacterSheetSection, Note +from valentina.constants import InventoryItemType +from valentina.models import Character, CharacterSheetSection, InventoryItem, Note from valentina.webui import catalog from valentina.webui.utils import fetch_active_character from valentina.webui.utils.discord import post_to_audit_log +class InventoryItemForm(QuartForm): + """Form for an inventory item.""" + + name = StringField("Name", validators=[DataRequired()]) + description = TextAreaField("Description") + type = SelectField( + "Type", + choices=[("", "-- Select --")] + [(x.name, x.value) for x in InventoryItemType], + validators=[DataRequired()], + ) + item_id = HiddenField() + submit = SubmitField("Submit") + + class CustomSectionForm(QuartForm): """Form for a custom section.""" @@ -45,6 +63,8 @@ class CharacterNoteForm(QuartForm): class EditCharacterCustomSection(MethodView): """Edit the character's info.""" + decorators: ClassVar = [requires_authorization] + async def _build_form(self, character: Character) -> QuartForm: """Build the form and populate with existing data if available.""" data = {} @@ -161,6 +181,8 @@ async def delete(self, character_id: str) -> Response: class EditCharacterNote(MethodView): """Edit the character's note.""" + decorators: ClassVar = [requires_authorization] + async def _build_form(self) -> QuartForm: """Build the form and populate with existing data if available.""" data = {} @@ -177,7 +199,7 @@ async def get(self, character_id: str) -> str: """Render the form.""" character = await fetch_active_character(character_id, fetch_links=False) return catalog.render( - "character_edit.CustomSectionForm", + "character_edit.NoteForm", character=character, form=await self._build_form(), join_label=False, @@ -219,7 +241,7 @@ async def post(self, character_id: str) -> Response | str: # If POST request does not validate, return errors return catalog.render( - "character_edit.CustomSectionForm", + "character_edit.NoteForm", character=character, form=form, join_label=False, @@ -255,3 +277,111 @@ async def delete(self, character_id: str) -> Response: ) } ) + + +class EditCharacterInventory(MethodView): + """Edit the character's Inventory.""" + + decorators: ClassVar = [requires_authorization] + + async def _build_form(self) -> QuartForm: + """Build the form and populate with existing data if available.""" + data = {} + + if request.args.get("item_id"): + existing_item = await InventoryItem.get(request.args.get("item_id")) + if existing_item: + data["name"] = existing_item.name + data["description"] = existing_item.description + data["type"] = existing_item.type + + return await InventoryItemForm().create_form(data=data) + + async def get(self, character_id: str) -> str: + """Render the form.""" + character = await fetch_active_character(character_id, fetch_links=False) + return catalog.render( + "character_edit.InventoryItemForm", + character=character, + form=await self._build_form(), + join_label=False, + floating_label=True, + post_url=url_for("character_edit.inventory", character_id=character_id), + ) + + async def post(self, character_id: str) -> Response | str: + """Process the form.""" + character = await fetch_active_character(character_id, fetch_links=True) + form = await self._build_form() + + if await form.validate_on_submit(): + if form.data.get("item_id"): + existing_item = await InventoryItem.get(form.data["item_id"]) + existing_item.name = form.data["name"] + existing_item.description = form.data["description"] + existing_item.type = form.data["type"] + await existing_item.save() + msg = f"{existing_item.name} updated." + else: + new_item = InventoryItem( + character=str(character.id), + name=form.data["name"].strip(), + description=form.data["description"].strip(), + type=form.data["type"], + ) + await new_item.save() + character.inventory.append(new_item) + await character.save() + msg = f"{new_item.name} added to inventory" + + return Response( + headers={ + "HX-Redirect": url_for( + "character_view.view", + character_id=character_id, + success_msg=msg, + ), + } + ) + + # If POST request does not validate, return errors + return catalog.render( + "character_edit.InventoryItemForm", + character=character, + form=form, + join_label=False, + floating_label=True, + post_url=url_for("character_edit.inventory", character_id=character_id), + ) + + async def delete(self, character_id: str) -> Response: + """Delete the note.""" + character = await fetch_active_character(character_id, fetch_links=True) + + item_id = request.args.get("item_id", None) + if not item_id: + abort(400) + + existing_item = await InventoryItem.get(item_id) + for item in character.notes: + if item == existing_item: + character.inventory.remove(item) + break + await character.save() + + await post_to_audit_log( + msg=f"Character {character.name} item `{existing_item.name}` deleted", + view=self.__class__.__name__, + ) + + await existing_item.delete() + + return Response( + headers={ + "HX-Redirect": url_for( + "character_view.view", + character_id=character_id, + success_msg="Item deleted", + ) + } + ) diff --git a/src/valentina/webui/blueprints/character_edit/templates/character_edit/CustomSectionForm.jinja b/src/valentina/webui/blueprints/character_edit/templates/character_edit/CustomSectionForm.jinja index 7705a349..5db86b6b 100644 --- a/src/valentina/webui/blueprints/character_edit/templates/character_edit/CustomSectionForm.jinja +++ b/src/valentina/webui/blueprints/character_edit/templates/character_edit/CustomSectionForm.jinja @@ -18,8 +18,13 @@ hx-swap="innerHTML" encoding="application/x-www-form-urlencoded"> - Cancel + diff --git a/src/valentina/webui/blueprints/character_edit/templates/character_edit/InventoryItemForm.jinja b/src/valentina/webui/blueprints/character_edit/templates/character_edit/InventoryItemForm.jinja new file mode 100644 index 00000000..88fb24d0 --- /dev/null +++ b/src/valentina/webui/blueprints/character_edit/templates/character_edit/InventoryItemForm.jinja @@ -0,0 +1,31 @@ +{# def + form:QuartForm, + join_label:bool = False, + floating_label:bool = False, + post_url:str = "", + character:Character, +#} + +
+
+
+ + + + +
+
diff --git a/src/valentina/webui/blueprints/character_edit/templates/character_edit/NoteForm.jinja b/src/valentina/webui/blueprints/character_edit/templates/character_edit/NoteForm.jinja index 2749ac90..78193de3 100644 --- a/src/valentina/webui/blueprints/character_edit/templates/character_edit/NoteForm.jinja +++ b/src/valentina/webui/blueprints/character_edit/templates/character_edit/NoteForm.jinja @@ -18,8 +18,13 @@ hx-swap="innerHTML" encoding="application/x-www-form-urlencoded"> - Cancel + diff --git a/src/valentina/webui/blueprints/character_view/templates/character_view/Inventory.jinja b/src/valentina/webui/blueprints/character_view/templates/character_view/Inventory.jinja index c6fe4f67..8e25b665 100644 --- a/src/valentina/webui/blueprints/character_view/templates/character_view/Inventory.jinja +++ b/src/valentina/webui/blueprints/character_view/templates/character_view/Inventory.jinja @@ -3,26 +3,53 @@ inventory:list[CharacterInventoryItem]=[] #} {# TODO: Add and edit items #} +

Inventory

-
- {% if not inventory %} -
-

No items in inventory

-
- {% else %} - {% for key, values in inventory.items() %} +
+
+ {% if not inventory %}
-

{{ key | capitalize }}

-
-
- {% for item in values %} -
{{ item.name | from_markdown | safe }}
-
- {{ item.description | from_markdown | safe }} -
- {% endfor %} -
+

No items in inventory

- {% endfor %} - {% endif %} + {% else %} + {% for key, values in inventory.items() %} +
+

{{ key | capitalize }}

+
+
+ {% for item in values %} + +
{{ item.name | from_markdown | safe }}
+
+
+ + +
+ {{ item.description | from_markdown | safe }} + +
+ {% endfor %} +
+
+ {% endfor %} + {% endif %} +