Skip to content

Commit

Permalink
feat(webui): edit character inventory
Browse files Browse the repository at this point in the history
  • Loading branch information
natelandau committed Oct 26, 2024
1 parent 000728c commit bb7b68a
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 27 deletions.
7 changes: 6 additions & 1 deletion src/valentina/webui/blueprints/character_edit/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -44,3 +44,8 @@
view_func=EditCharacterNote.as_view("note"),
methods=["GET", "POST", "DELETE"],
)
blueprint.add_url_rule(
"/character/<string:character_id>/edit/inventory",
view_func=EditCharacterInventory.as_view("inventory"),
methods=["GET", "POST", "DELETE"],
)
136 changes: 133 additions & 3 deletions src/valentina/webui/blueprints/character_edit/route_info.py
Original file line number Diff line number Diff line change
@@ -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."""

Expand All @@ -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 = {}
Expand Down Expand Up @@ -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 = {}
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 = {}

Check warning on line 289 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L289

Added line #L289 was not covered by tests

if request.args.get("item_id"):
existing_item = await InventoryItem.get(request.args.get("item_id"))

Check warning on line 292 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L292

Added line #L292 was not covered by tests
if existing_item:
data["name"] = existing_item.name
data["description"] = existing_item.description
data["type"] = existing_item.type

Check warning on line 296 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L294-L296

Added lines #L294 - L296 were not covered by tests

return await InventoryItemForm().create_form(data=data)

Check warning on line 298 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L298

Added line #L298 was not covered by tests

async def get(self, character_id: str) -> str:
"""Render the form."""
character = await fetch_active_character(character_id, fetch_links=False)
return catalog.render(

Check warning on line 303 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L302-L303

Added lines #L302 - L303 were not covered by tests
"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()

Check warning on line 315 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L314-L315

Added lines #L314 - L315 were not covered by tests

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."

Check warning on line 324 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L319-L324

Added lines #L319 - L324 were not covered by tests
else:
new_item = InventoryItem(

Check warning on line 326 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L326

Added line #L326 was not covered by tests
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"

Check warning on line 335 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L332-L335

Added lines #L332 - L335 were not covered by tests

return Response(

Check warning on line 337 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L337

Added line #L337 was not covered by tests
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(

Check warning on line 348 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L348

Added line #L348 was not covered by tests
"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)

Check warning on line 359 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L359

Added line #L359 was not covered by tests

item_id = request.args.get("item_id", None)

Check warning on line 361 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L361

Added line #L361 was not covered by tests
if not item_id:
abort(400)

Check warning on line 363 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L363

Added line #L363 was not covered by tests

existing_item = await InventoryItem.get(item_id)

Check warning on line 365 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L365

Added line #L365 was not covered by tests
for item in character.notes:
if item == existing_item:
character.inventory.remove(item)
break
await character.save()

Check warning on line 370 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L368-L370

Added lines #L368 - L370 were not covered by tests

await post_to_audit_log(

Check warning on line 372 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L372

Added line #L372 was not covered by tests
msg=f"Character {character.name} item `{existing_item.name}` deleted",
view=self.__class__.__name__,
)

await existing_item.delete()

Check warning on line 377 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L377

Added line #L377 was not covered by tests

return Response(

Check warning on line 379 in src/valentina/webui/blueprints/character_edit/route_info.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/webui/blueprints/character_edit/route_info.py#L379

Added line #L379 was not covered by tests
headers={
"HX-Redirect": url_for(
"character_view.view",
character_id=character_id,
success_msg="Item deleted",
)
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@
hx-swap="innerHTML"
encoding="application/x-www-form-urlencoded">
<global.WTFormElements form={{ form }} join_label={{ join_label }} floating_label={{ floating_label }} />
<a href="{{ url_for('character_view.view', character_id=character.id, info_msg="Cancelled") }}"
class="btn btn-outline-secondary ms-2">Cancel</a>
<button class="btn btn-outline-secondary ms-2"
hx-target="#main-content"
hx-swap="innerHTML"
hx-trigger="click"
hx-get="{{ url_for('character_view.view', character_id=character.id, tab='info', info_msg='Cancelled') }}">
Cancel
</button>
</form>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{# def
form:QuartForm,
join_label:bool = False,
floating_label:bool = False,
post_url:str = "",
character:Character,
#}

<div class="d-flex justify-content-center align-items-center my-3">
<div class="py-4 px-5 mt-5 rounded-3 border shadow-lg w-50"
id="form-container">
<form method="post"
id="profile-form"
hx-post="{{ post_url }}"
novalidate
hx-indicator="#spinner"
hx-target="#inventory"
hx-swap="innerHTML"
encoding="application/x-www-form-urlencoded">
<global.WTFormElements form={{ form }} join_label={{ join_label }} floating_label={{ floating_label }} />

<button class="btn btn-outline-secondary ms-2"
hx-target="#main-content"
hx-swap="innerHTML"
hx-trigger="click"
hx-get="{{ url_for('character_view.view', character_id=character.id, tab='inventory', info_msg='Cancelled') }}">
Cancel
</button>
</form>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@
hx-swap="innerHTML"
encoding="application/x-www-form-urlencoded">
<global.WTFormElements form={{ form }} join_label={{ join_label }} floating_label={{ floating_label }} />
<a href="{{ url_for('character_view.view', character_id=character.id, info_msg="Cancelled") }}"
class="btn btn-outline-secondary ms-2">Cancel</a>
<button class="btn btn-outline-secondary ms-2"
hx-target="#main-content"
hx-swap="innerHTML"
hx-trigger="click"
hx-get="{{ url_for('character_view.view', character_id=character.id, tab='info', info_msg='Cancelled') }}">
Cancel
</button>
</form>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,53 @@
inventory:list[CharacterInventoryItem]=[]
#}
{# TODO: Add and edit items #}
<button class="btn btn-outline-primary btn-sm ms-3 float-end"
hx-get="{{ url_for('character_edit.inventory', character_id=character.id) }}"
hx-trigger="click"
hx-target="#inventory"
hx-swap="innerHTML">Add New Item</button>
<h2 class="mb-4">Inventory</h2>
<div class="row row-cols-1 row-cols-sm-2">
{% if not inventory %}
<div class="col">
<p>No items in inventory</p>
</div>
{% else %}
{% for key, values in inventory.items() %}
<div id="inventory">
<div class="row row-cols-1 row-cols-sm-2">
{% if not inventory %}
<div class="col">
<h4>{{ key | capitalize }}</h4>
<hr>
<dl class="row">
{% for item in values %}
<dt class="col-sm-3">{{ item.name | from_markdown | safe }}</dt>
<dd class="col-sm-9">
{{ item.description | from_markdown | safe }}
</dd>
{% endfor %}
</dl>
<p>No items in inventory</p>
</div>
{% endfor %}
{% endif %}
{% else %}
{% for key, values in inventory.items() %}
<div class="col">
<h4>{{ key | capitalize }}</h4>
<hr>
<dl class="row">
{% for item in values %}

<dt class="col-sm-3">{{ item.name | from_markdown | safe }}</dt>
<dd class="col-sm-9">
<div class="btn-group float-end">
<button class="btn btn-sm btn-outline-primary"
style="--bs-btn-padding-y: .25rem;
--bs-btn-padding-x: .5rem;
--bs-btn-font-size: .75rem"
hx-get="{{ url_for('character_edit.inventory', character_id=character.id, item_id=item.id) }}"
hx-trigger="click"
hx-target="#inventory"
hx-swap="innerHTML">Edit</button>
<button class="btn btn-sm btn-outline-danger"
style="--bs-btn-padding-y: .25rem;
--bs-btn-padding-x: .5rem;
--bs-btn-font-size: .75rem"
hx-delete="{{ url_for('character_edit.inventory', character_id=character.id, item_id=item.id) }}"
hx-trigger="click"
hx-target="#inventory"
hx-swap="innerHTML">Delete</button>
</div>
{{ item.description | from_markdown | safe }}

</dd>
{% endfor %}
</dl>
</div>
{% endfor %}
{% endif %}
</div>
</div>

0 comments on commit bb7b68a

Please sign in to comment.