Set tags
This commit is contained in:
parent
5ad94078ed
commit
f34bbe0602
@ -19,6 +19,7 @@ from bricktracker.views.admin.instructions import admin_instructions_page
|
||||
from bricktracker.views.admin.owner import admin_owner_page
|
||||
from bricktracker.views.admin.retired import admin_retired_page
|
||||
from bricktracker.views.admin.status import admin_status_page
|
||||
from bricktracker.views.admin.tag import admin_tag_page
|
||||
from bricktracker.views.admin.theme import admin_theme_page
|
||||
from bricktracker.views.error import error_404
|
||||
from bricktracker.views.index import index_page
|
||||
@ -85,6 +86,7 @@ def setup_app(app: Flask) -> None:
|
||||
app.register_blueprint(admin_retired_page)
|
||||
app.register_blueprint(admin_owner_page)
|
||||
app.register_blueprint(admin_status_page)
|
||||
app.register_blueprint(admin_tag_page)
|
||||
app.register_blueprint(admin_theme_page)
|
||||
|
||||
# An helper to make global variables available to the
|
||||
|
@ -6,10 +6,11 @@ from .fields import BrickRecordFields
|
||||
from .record_list import BrickRecordList
|
||||
from .set_owner import BrickSetOwner
|
||||
from .set_status import BrickSetStatus
|
||||
from .set_tag import BrickSetTag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
T = TypeVar('T', 'BrickSetStatus', 'BrickSetOwner')
|
||||
T = TypeVar('T', BrickSetStatus, BrickSetOwner, BrickSetTag)
|
||||
|
||||
|
||||
# Lego sets metadata list
|
||||
|
@ -10,6 +10,7 @@ if TYPE_CHECKING:
|
||||
from .set import BrickSet
|
||||
from .set_owner import BrickSetOwner
|
||||
from .set_status import BrickSetStatus
|
||||
from .set_tag import BrickSetTag
|
||||
from .wish import BrickWish
|
||||
|
||||
T = TypeVar(
|
||||
@ -17,6 +18,7 @@ T = TypeVar(
|
||||
'BrickSet',
|
||||
'BrickSetOwner',
|
||||
'BrickSetStatus',
|
||||
'BrickSetTag',
|
||||
'BrickPart',
|
||||
'BrickMinifigure',
|
||||
'BrickWish',
|
||||
|
@ -4,6 +4,8 @@ from .set_owner import BrickSetOwner
|
||||
from .set_owner_list import BrickSetOwnerList
|
||||
from .set_status import BrickSetStatus
|
||||
from .set_status_list import BrickSetStatusList
|
||||
from .set_tag import BrickSetTag
|
||||
from .set_tag_list import BrickSetTagList
|
||||
from .theme_list import BrickThemeList
|
||||
|
||||
|
||||
@ -20,6 +22,9 @@ def reload() -> None:
|
||||
# Reload the set statuses
|
||||
BrickSetStatusList(BrickSetStatus, force=True)
|
||||
|
||||
# Reload the set tags
|
||||
BrickSetTagList(BrickSetTag, force=True)
|
||||
|
||||
# Reload retired sets
|
||||
BrickRetiredList(force=True)
|
||||
|
||||
|
@ -13,6 +13,8 @@ from .set_owner import BrickSetOwner
|
||||
from .set_owner_list import BrickSetOwnerList
|
||||
from .set_status import BrickSetStatus
|
||||
from .set_status_list import BrickSetStatusList
|
||||
from .set_tag import BrickSetTag
|
||||
from .set_tag_list import BrickSetTagList
|
||||
from .sql import BrickSQL
|
||||
if TYPE_CHECKING:
|
||||
from .socket import BrickSocket
|
||||
@ -77,6 +79,13 @@ class BrickSet(RebrickableSet):
|
||||
owner = BrickSetOwnerList(BrickSetOwner).get(id)
|
||||
owner.update_set_state(self, state=True)
|
||||
|
||||
# Save the tags
|
||||
tags: list[str] = list(data.get('tags', []))
|
||||
|
||||
for id in tags:
|
||||
tag = BrickSetTagList(BrickSetTag).get(id)
|
||||
tag.update_set_state(self, state=True)
|
||||
|
||||
# Commit the transaction to the database
|
||||
socket.auto_progress(
|
||||
message='Set {set}: writing to the database'.format(
|
||||
@ -172,7 +181,8 @@ class BrickSet(RebrickableSet):
|
||||
# Load from database
|
||||
if not self.select(
|
||||
owners=BrickSetOwnerList(BrickSetOwner).as_columns(),
|
||||
statuses=BrickSetStatusList(BrickSetStatus).as_columns(all=True)
|
||||
statuses=BrickSetStatusList(BrickSetStatus).as_columns(all=True),
|
||||
tags=BrickSetTagList(BrickSetTag).as_columns(),
|
||||
):
|
||||
raise NotFoundException(
|
||||
'Set with ID {id} was not found in the database'.format(
|
||||
|
@ -7,6 +7,8 @@ from .set_owner import BrickSetOwner
|
||||
from .set_owner_list import BrickSetOwnerList
|
||||
from .set_status import BrickSetStatus
|
||||
from .set_status_list import BrickSetStatusList
|
||||
from .set_tag import BrickSetTag
|
||||
from .set_tag_list import BrickSetTagList
|
||||
from .set import BrickSet
|
||||
|
||||
|
||||
@ -41,7 +43,8 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
||||
for record in self.select(
|
||||
order=self.order,
|
||||
owners=BrickSetOwnerList(BrickSetOwner).as_columns(),
|
||||
statuses=BrickSetStatusList(BrickSetStatus).as_columns()
|
||||
statuses=BrickSetStatusList(BrickSetStatus).as_columns(),
|
||||
tags=BrickSetTagList(BrickSetTag).as_columns(),
|
||||
):
|
||||
brickset = BrickSet(record=record)
|
||||
|
||||
@ -78,7 +81,8 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
||||
order=order,
|
||||
limit=limit,
|
||||
owners=BrickSetOwnerList(BrickSetOwner).as_columns(),
|
||||
statuses=BrickSetStatusList(BrickSetStatus).as_columns()
|
||||
statuses=BrickSetStatusList(BrickSetStatus).as_columns(),
|
||||
tags=BrickSetTagList(BrickSetTag).as_columns(),
|
||||
):
|
||||
brickset = BrickSet(record=record)
|
||||
|
||||
|
16
bricktracker/set_tag.py
Normal file
16
bricktracker/set_tag.py
Normal file
@ -0,0 +1,16 @@
|
||||
from .metadata import BrickMetadata
|
||||
|
||||
|
||||
# Lego set tag metadata
|
||||
class BrickSetTag(BrickMetadata):
|
||||
kind: str = 'tag'
|
||||
|
||||
# Set state endpoint
|
||||
set_state_endpoint: str = 'set.update_tag'
|
||||
|
||||
# Queries
|
||||
delete_query: str = 'set/metadata/tag/delete'
|
||||
insert_query: str = 'set/metadata/tag/insert'
|
||||
select_query: str = 'set/metadata/tag/select'
|
||||
update_field_query: str = 'set/metadata/tag/update/field'
|
||||
update_set_state_query: str = 'set/metadata/tag/update/state'
|
17
bricktracker/set_tag_list.py
Normal file
17
bricktracker/set_tag_list.py
Normal file
@ -0,0 +1,17 @@
|
||||
import logging
|
||||
|
||||
from .metadata_list import BrickMetadataList
|
||||
from .set_tag import BrickSetTag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Lego sets tag list
|
||||
class BrickSetTagList(BrickMetadataList[BrickSetTag]):
|
||||
kind: str = 'set tags'
|
||||
|
||||
# Database table
|
||||
table: str = 'bricktracker_set_tags'
|
||||
|
||||
# Queries
|
||||
select_query = 'set/metadata/tag/list'
|
19
bricktracker/sql/migrations/0014.sql
Normal file
19
bricktracker/sql/migrations/0014.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- description: Add set tags
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- Create a table to define each set tags: an id and a name
|
||||
CREATE TABLE "bricktracker_metadata_tags" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
|
||||
-- Create a table for the set tags
|
||||
CREATE TABLE "bricktracker_set_tags" (
|
||||
"id" TEXT NOT NULL,
|
||||
PRIMARY KEY("id"),
|
||||
FOREIGN KEY("id") REFERENCES "bricktracker_sets"("id")
|
||||
);
|
||||
|
||||
COMMIT;
|
@ -1,12 +1,14 @@
|
||||
BEGIN transaction;
|
||||
|
||||
DROP TABLE IF EXISTS "bricktracker_metadata_statuses";
|
||||
DROP TABLE IF EXISTS "bricktracker_metadata_tags";
|
||||
DROP TABLE IF EXISTS "bricktracker_minifigures";
|
||||
DROP TABLE IF EXISTS "bricktracker_parts";
|
||||
DROP TABLE IF EXISTS "bricktracker_sets";
|
||||
DROP TABLE IF EXISTS "bricktracker_set_checkboxes";
|
||||
DROP TABLE IF EXISTS "bricktracker_set_statuses";
|
||||
DROP TABLE IF EXISTS "bricktracker_set_storages";
|
||||
DROP TABLE IF EXISTS "bricktracker_set_tags";
|
||||
DROP TABLE IF EXISTS "bricktracker_wishes";
|
||||
DROP TABLE IF EXISTS "inventory";
|
||||
DROP TABLE IF EXISTS "inventory_old";
|
||||
|
@ -12,6 +12,9 @@ SELECT
|
||||
{% block owners %}
|
||||
{% if owners %}{{ owners }},{% endif %}
|
||||
{% endblock %}
|
||||
{% block tags %}
|
||||
{% if tags %}{{ tags }},{% endif %}
|
||||
{% endblock %}
|
||||
{% block statuses %}
|
||||
{% if statuses %}{{ statuses }},{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -23,6 +23,11 @@ LEFT JOIN "bricktracker_set_statuses"
|
||||
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_statuses"."id"
|
||||
{% endif %}
|
||||
|
||||
{% if tags %}
|
||||
LEFT JOIN "bricktracker_set_tags"
|
||||
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_tags"."id"
|
||||
{% endif %}
|
||||
|
||||
-- LEFT JOIN + SELECT to avoid messing the total
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
|
@ -12,6 +12,9 @@ WHERE "bricktracker_set_owners"."id" IS NOT DISTINCT FROM '{{ id }}';
|
||||
DELETE FROM "bricktracker_set_statuses"
|
||||
WHERE "bricktracker_set_statuses"."id" IS NOT DISTINCT FROM '{{ id }}';
|
||||
|
||||
DELETE FROM "bricktracker_set_tags"
|
||||
WHERE "bricktracker_set_tags"."id" IS NOT DISTINCT FROM '{{ id }}';
|
||||
|
||||
DELETE FROM "bricktracker_minifigures"
|
||||
WHERE "bricktracker_minifigures"."id" IS NOT DISTINCT FROM '{{ id }}';
|
||||
|
||||
|
6
bricktracker/sql/set/metadata/tag/base.sql
Normal file
6
bricktracker/sql/set/metadata/tag/base.sql
Normal file
@ -0,0 +1,6 @@
|
||||
SELECT
|
||||
"bricktracker_metadata_tags"."id",
|
||||
"bricktracker_metadata_tags"."name"
|
||||
FROM "bricktracker_metadata_tags"
|
||||
|
||||
{% block where %}{% endblock %}
|
9
bricktracker/sql/set/metadata/tag/delete.sql
Normal file
9
bricktracker/sql/set/metadata/tag/delete.sql
Normal file
@ -0,0 +1,9 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "bricktracker_set_tags"
|
||||
DROP COLUMN "tag_{{ id }}";
|
||||
|
||||
DELETE FROM "bricktracker_metadata_tags"
|
||||
WHERE "bricktracker_metadata_tags"."id" IS NOT DISTINCT FROM '{{ id }}';
|
||||
|
||||
COMMIT;
|
14
bricktracker/sql/set/metadata/tag/insert.sql
Normal file
14
bricktracker/sql/set/metadata/tag/insert.sql
Normal file
@ -0,0 +1,14 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "bricktracker_set_tags"
|
||||
ADD COLUMN "tag_{{ id }}" BOOLEAN NOT NULL DEFAULT 0;
|
||||
|
||||
INSERT INTO "bricktracker_metadata_tags" (
|
||||
"id",
|
||||
"name"
|
||||
) VALUES (
|
||||
'{{ id }}',
|
||||
'{{ name }}'
|
||||
);
|
||||
|
||||
COMMIT;
|
1
bricktracker/sql/set/metadata/tag/list.sql
Normal file
1
bricktracker/sql/set/metadata/tag/list.sql
Normal file
@ -0,0 +1 @@
|
||||
{% extends 'set/metadata/tag/base.sql' %}
|
5
bricktracker/sql/set/metadata/tag/select.sql
Normal file
5
bricktracker/sql/set/metadata/tag/select.sql
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends 'set/metadata/tag/base.sql' %}
|
||||
|
||||
{% block where %}
|
||||
WHERE "bricktracker_metadata_tags"."id" IS NOT DISTINCT FROM :id
|
||||
{% endblock %}
|
3
bricktracker/sql/set/metadata/tag/update/field.sql
Normal file
3
bricktracker/sql/set/metadata/tag/update/field.sql
Normal file
@ -0,0 +1,3 @@
|
||||
UPDATE "bricktracker_metadata_tags"
|
||||
SET "{{field}}" = :value
|
||||
WHERE "bricktracker_metadata_tags"."id" IS NOT DISTINCT FROM :id
|
10
bricktracker/sql/set/metadata/tag/update/state.sql
Normal file
10
bricktracker/sql/set/metadata/tag/update/state.sql
Normal file
@ -0,0 +1,10 @@
|
||||
INSERT INTO "bricktracker_set_tags" (
|
||||
"id",
|
||||
"{{name}}"
|
||||
) VALUES (
|
||||
:set_id,
|
||||
:state
|
||||
)
|
||||
ON CONFLICT("id")
|
||||
DO UPDATE SET "{{name}}" = :state
|
||||
WHERE "bricktracker_set_tags"."id" IS NOT DISTINCT FROM :set_id
|
@ -9,6 +9,7 @@ ALIASES: dict[str, Tuple[str, str]] = {
|
||||
'bricktracker_set_owners': ('Bricktracker set owners', 'checkbox-line'), # noqa: E501
|
||||
'bricktracker_set_statuses': ('Bricktracker set statuses', 'user-line'), # noqa: E501
|
||||
'bricktracker_set_storages': ('Bricktracker set storages', 'archive-2-line'), # noqa: E501
|
||||
'bricktracker_set_tags': ('Bricktracker set tags', 'price-tag-2-line'), # noqa: E501
|
||||
'bricktracker_sets': ('Bricktracker sets', 'hashtag'),
|
||||
'bricktracker_wishes': ('Bricktracker wishes', 'gift-line'),
|
||||
'inventory': ('Parts', 'shapes-line'),
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import Final
|
||||
|
||||
__version__: Final[str] = '1.2.0'
|
||||
__database_version__: Final[int] = 13
|
||||
__database_version__: Final[int] = 14
|
||||
|
@ -5,6 +5,8 @@ from ..configuration_list import BrickConfigurationList
|
||||
from .exceptions import exception_handler
|
||||
from ..set_owner import BrickSetOwner
|
||||
from ..set_owner_list import BrickSetOwnerList
|
||||
from ..set_tag import BrickSetTag
|
||||
from ..set_tag_list import BrickSetTagList
|
||||
from ..socket import MESSAGES
|
||||
|
||||
add_page = Blueprint('add', __name__, url_prefix='/add')
|
||||
@ -20,6 +22,7 @@ def add() -> str:
|
||||
return render_template(
|
||||
'add.html',
|
||||
brickset_owners=BrickSetOwnerList(BrickSetOwner).list(),
|
||||
brickset_tags=BrickSetTagList(BrickSetTag).list(),
|
||||
path=current_app.config['SOCKET_PATH'],
|
||||
namespace=current_app.config['SOCKET_NAMESPACE'],
|
||||
messages=MESSAGES
|
||||
@ -36,6 +39,7 @@ def bulk() -> str:
|
||||
return render_template(
|
||||
'add.html',
|
||||
brickset_owners=BrickSetOwnerList(BrickSetOwner).list(),
|
||||
brickset_tags=BrickSetTagList(BrickSetTag).list(),
|
||||
path=current_app.config['SOCKET_PATH'],
|
||||
namespace=current_app.config['SOCKET_NAMESPACE'],
|
||||
messages=MESSAGES,
|
||||
|
@ -12,6 +12,8 @@ from ...set_owner import BrickSetOwner
|
||||
from ...set_owner_list import BrickSetOwnerList
|
||||
from ...set_status import BrickSetStatus
|
||||
from ...set_status_list import BrickSetStatusList
|
||||
from ...set_tag import BrickSetTag
|
||||
from ...set_tag_list import BrickSetTagList
|
||||
from ...sql_counter import BrickCounter
|
||||
from ...sql import BrickSQL
|
||||
from ...theme_list import BrickThemeList
|
||||
@ -32,6 +34,7 @@ def admin() -> str:
|
||||
database_version: int = -1
|
||||
metadata_owners: list[BrickSetOwner] = []
|
||||
metadata_statuses: list[BrickSetStatus] = []
|
||||
metadata_tags: list[BrickSetTag] = []
|
||||
nil_minifigure_name: str = ''
|
||||
nil_minifigure_url: str = ''
|
||||
nil_part_name: str = ''
|
||||
@ -46,6 +49,7 @@ def admin() -> str:
|
||||
|
||||
metadata_owners = BrickSetOwnerList(BrickSetOwner).list()
|
||||
metadata_statuses = BrickSetStatusList(BrickSetStatus).list(all=True)
|
||||
metadata_tags = BrickSetTagList(BrickSetTag).list()
|
||||
except Exception as e:
|
||||
database_exception = e
|
||||
|
||||
@ -72,6 +76,7 @@ def admin() -> str:
|
||||
open_owner = request.args.get('open_owner', None)
|
||||
open_retired = request.args.get('open_retired', None)
|
||||
open_status = request.args.get('open_status', None)
|
||||
open_tag = request.args.get('open_tag', None)
|
||||
open_theme = request.args.get('open_theme', None)
|
||||
|
||||
open_database = (
|
||||
@ -81,6 +86,7 @@ def admin() -> str:
|
||||
open_owner is None and
|
||||
open_retired is None and
|
||||
open_status is None and
|
||||
open_tag is None and
|
||||
open_theme is None
|
||||
)
|
||||
|
||||
@ -95,20 +101,23 @@ def admin() -> str:
|
||||
instructions=BrickInstructionsList(),
|
||||
metadata_owners=metadata_owners,
|
||||
metadata_statuses=metadata_statuses,
|
||||
metadata_tags=metadata_tags,
|
||||
nil_minifigure_name=nil_minifigure_name,
|
||||
nil_minifigure_url=nil_minifigure_url,
|
||||
nil_part_name=nil_part_name,
|
||||
nil_part_url=nil_part_url,
|
||||
open_status=open_status,
|
||||
open_database=open_database,
|
||||
open_image=open_image,
|
||||
open_instructions=open_instructions,
|
||||
open_logout=open_logout,
|
||||
open_owner=open_owner,
|
||||
open_retired=open_retired,
|
||||
open_status=open_status,
|
||||
open_tag=open_tag,
|
||||
open_theme=open_theme,
|
||||
owner_error=request.args.get('owner_error'),
|
||||
status_error=request.args.get('status_error'),
|
||||
retired=BrickRetiredList(),
|
||||
status_error=request.args.get('status_error'),
|
||||
tag_error=request.args.get('tag_error'),
|
||||
theme=BrickThemeList(),
|
||||
)
|
||||
|
84
bricktracker/views/admin/tag.py
Normal file
84
bricktracker/views/admin/tag.py
Normal file
@ -0,0 +1,84 @@
|
||||
from flask import (
|
||||
Blueprint,
|
||||
redirect,
|
||||
request,
|
||||
render_template,
|
||||
url_for,
|
||||
)
|
||||
from flask_login import login_required
|
||||
from werkzeug.wrappers.response import Response
|
||||
|
||||
from ..exceptions import exception_handler
|
||||
from ...reload import reload
|
||||
from ...set_tag import BrickSetTag
|
||||
|
||||
admin_tag_page = Blueprint(
|
||||
'admin_tag',
|
||||
__name__,
|
||||
url_prefix='/admin/tag'
|
||||
)
|
||||
|
||||
|
||||
# Add a metadata tag
|
||||
@admin_tag_page.route('/add', methods=['POST'])
|
||||
@login_required
|
||||
@exception_handler(
|
||||
__file__,
|
||||
post_redirect='admin.admin',
|
||||
error_name='tag_error',
|
||||
open_tag=True
|
||||
)
|
||||
def add() -> Response:
|
||||
BrickSetTag().from_form(request.form).insert()
|
||||
|
||||
reload()
|
||||
|
||||
return redirect(url_for('admin.admin', open_tag=True))
|
||||
|
||||
|
||||
# Delete the metadata tag
|
||||
@admin_tag_page.route('<id>/delete', methods=['GET'])
|
||||
@login_required
|
||||
@exception_handler(__file__)
|
||||
def delete(*, id: str) -> str:
|
||||
return render_template(
|
||||
'admin.html',
|
||||
delete_tag=True,
|
||||
tag=BrickSetTag().select_specific(id),
|
||||
error=request.args.get('tag_error')
|
||||
)
|
||||
|
||||
|
||||
# Actually delete the metadata tag
|
||||
@admin_tag_page.route('<id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
@exception_handler(
|
||||
__file__,
|
||||
post_redirect='admin_tag.delete',
|
||||
error_name='tag_error'
|
||||
)
|
||||
def do_delete(*, id: str) -> Response:
|
||||
tag = BrickSetTag().select_specific(id)
|
||||
tag.delete()
|
||||
|
||||
reload()
|
||||
|
||||
return redirect(url_for('admin.admin', open_tag=True))
|
||||
|
||||
|
||||
# Rename the metadata tag
|
||||
@admin_tag_page.route('<id>/rename', methods=['POST'])
|
||||
@login_required
|
||||
@exception_handler(
|
||||
__file__,
|
||||
post_redirect='admin.admin',
|
||||
error_name='tag_error',
|
||||
open_tag=True
|
||||
)
|
||||
def rename(*, id: str) -> Response:
|
||||
tag = BrickSetTag().select_specific(id)
|
||||
tag.from_form(request.form).rename()
|
||||
|
||||
reload()
|
||||
|
||||
return redirect(url_for('admin.admin', open_tag=True))
|
@ -6,6 +6,8 @@ from ..set_owner import BrickSetOwner
|
||||
from ..set_owner_list import BrickSetOwnerList
|
||||
from ..set_status import BrickSetStatus
|
||||
from ..set_status_list import BrickSetStatusList
|
||||
from ..set_tag import BrickSetTag
|
||||
from ..set_tag_list import BrickSetTagList
|
||||
from ..set_list import BrickSetList
|
||||
|
||||
index_page = Blueprint('index', __name__)
|
||||
@ -20,5 +22,6 @@ def index() -> str:
|
||||
brickset_collection=BrickSetList().last(),
|
||||
brickset_owners=BrickSetOwnerList(BrickSetOwner).list(),
|
||||
brickset_statuses=BrickSetStatusList(BrickSetStatus).list(),
|
||||
brickset_tags=BrickSetTagList(BrickSetTag).list(),
|
||||
minifigure_collection=BrickMinifigureList().last(),
|
||||
)
|
||||
|
@ -16,11 +16,13 @@ from .exceptions import exception_handler
|
||||
from ..minifigure import BrickMinifigure
|
||||
from ..part import BrickPart
|
||||
from ..set import BrickSet
|
||||
from ..set_list import BrickSetList
|
||||
from ..set_owner import BrickSetOwner
|
||||
from ..set_owner_list import BrickSetOwnerList
|
||||
from ..set_status import BrickSetStatus
|
||||
from ..set_status_list import BrickSetStatusList
|
||||
from ..set_list import BrickSetList
|
||||
from ..set_tag import BrickSetTag
|
||||
from ..set_tag_list import BrickSetTagList
|
||||
from ..socket import MESSAGES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -37,6 +39,7 @@ def list() -> str:
|
||||
collection=BrickSetList().all(),
|
||||
brickset_owners=BrickSetOwnerList(BrickSetOwner).list(),
|
||||
brickset_statuses=BrickSetStatusList(BrickSetStatus).list(),
|
||||
brickset_tags=BrickSetTagList(BrickSetTag).list(),
|
||||
)
|
||||
|
||||
|
||||
@ -66,6 +69,19 @@ def update_status(*, id: str, metadata_id: str) -> Response:
|
||||
return jsonify({'value': state})
|
||||
|
||||
|
||||
# Change the state of a tag
|
||||
@set_page.route('/<id>/tag/<metadata_id>', methods=['POST'])
|
||||
@login_required
|
||||
@exception_handler(__file__, json=True)
|
||||
def update_tag(*, id: str, metadata_id: str) -> Response:
|
||||
brickset = BrickSet().select_light(id)
|
||||
tag = BrickSetTagList(BrickSetTag).get(metadata_id)
|
||||
|
||||
state = tag.update_set_state(brickset, json=request.json)
|
||||
|
||||
return jsonify({'value': state})
|
||||
|
||||
|
||||
# Ask for deletion of a set
|
||||
@set_page.route('/<id>/delete', methods=['GET'])
|
||||
@login_required
|
||||
@ -116,6 +132,7 @@ def details(*, id: str) -> str:
|
||||
open_instructions=request.args.get('open_instructions'),
|
||||
brickset_owners=BrickSetOwnerList(BrickSetOwner).list(),
|
||||
brickset_statuses=BrickSetStatusList(BrickSetStatus).list(all=True),
|
||||
brickset_tags=BrickSetTagList(BrickSetTag).list(),
|
||||
)
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@ class BrickSetSocket extends BrickSocket {
|
||||
this.html_input = document.getElementById(`${id}-set`);
|
||||
this.html_no_confim = document.getElementById(`${id}-no-confirm`);
|
||||
this.html_owners = document.getElementById(`${id}-owners`);
|
||||
this.html_tags = document.getElementById(`${id}-tags`);
|
||||
|
||||
// Card elements
|
||||
this.html_card = document.getElementById(`${id}-card`);
|
||||
@ -150,11 +151,22 @@ class BrickSetSocket extends BrickSocket {
|
||||
});
|
||||
}
|
||||
|
||||
// Grab the tags
|
||||
const tags = [];
|
||||
if (this.html_tags) {
|
||||
this.html_tags.querySelectorAll('input').forEach(input => {
|
||||
if (input.checked) {
|
||||
tags.push(input.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.spinner(true);
|
||||
|
||||
this.socket.emit(this.messages.IMPORT_SET, {
|
||||
set: (set !== undefined) ? set : this.html_input.value,
|
||||
owners: owners,
|
||||
tags: tags,
|
||||
refresh: this.refresh
|
||||
});
|
||||
} else {
|
||||
@ -267,6 +279,10 @@ class BrickSetSocket extends BrickSocket {
|
||||
this.html_owners.querySelectorAll('input').forEach(input => input.disabled = !enabled);
|
||||
}
|
||||
|
||||
if (this.html_tags) {
|
||||
this.html_tags.querySelectorAll('input').forEach(input => input.disabled = !enabled);
|
||||
}
|
||||
|
||||
if (this.html_card_confirm) {
|
||||
this.html_card_confirm.disabled = !enabled;
|
||||
}
|
||||
|
@ -46,6 +46,19 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if brickset_tags | length %}
|
||||
<h5 class="border-bottom mt-2">Tags</h5>
|
||||
<div id="add-tags">
|
||||
{% for tag in brickset_tags %}
|
||||
{% with id=tag.as_dataset() %}
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="{{ tag.fields.id }}" id="{{ id }}" autocomplete="off">
|
||||
<label class="form-check-label" for="{{ id }}">{{ tag.fields.name }}</label>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<hr>
|
||||
<div class="mb-3">
|
||||
<p>
|
||||
|
@ -18,6 +18,8 @@
|
||||
{% include 'admin/owner/delete.html' %}
|
||||
{% elif delete_status %}
|
||||
{% include 'admin/status/delete.html' %}
|
||||
{% elif delete_tag %}
|
||||
{% include 'admin/tag/delete.html' %}
|
||||
{% elif drop_database %}
|
||||
{% include 'admin/database/drop.html' %}
|
||||
{% elif import_database %}
|
||||
@ -34,6 +36,7 @@
|
||||
{% include 'admin/retired.html' %}
|
||||
{% include 'admin/owner.html' %}
|
||||
{% include 'admin/status.html' %}
|
||||
{% include 'admin/tag.html' %}
|
||||
{% include 'admin/database.html' %}
|
||||
{% include 'admin/configuration.html' %}
|
||||
{% endif %}
|
||||
|
42
templates/admin/tag.html
Normal file
42
templates/admin/tag.html
Normal file
@ -0,0 +1,42 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Set tags', 'tag', 'admin', expanded=open_tag, icon='price-tag-2-line', class='p-0') }}
|
||||
{% if tag_error %}<div class="alert alert-danger m-2" role="alert"><strong>Error:</strong> {{ tag_error }}.</div>{% endif %}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% if metadata_tags | length %}
|
||||
{% for tag in metadata_tags %}
|
||||
<li class="list-group-item">
|
||||
<form action="{{ url_for('admin_tag.rename', id=tag.fields.id) }}" method="post" class="row row-cols-lg-auto g-3 align-items-center">
|
||||
<div class="col-12 flex-grow-1">
|
||||
<label class="visually-hidden" for="name-{{ tag.fields.id }}">Name</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">Name</div>
|
||||
<input type="text" class="form-control" id="name-{{ tag.fields.id }}" name="name" value="{{ tag.fields.name }}">
|
||||
<button type="submit" class="btn btn-primary"><i class="ri-edit-line"></i> Rename</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<a href="{{ url_for('admin_tag.delete', id=tag.fields.id) }}" class="btn btn-danger" role="button"><i class="ri-delete-bin-2-line"></i> Delete</a>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<li class="list-group-item"><i class="ri-error-warning-line"></i> No tag found.</li>
|
||||
{% endif %}
|
||||
<li class="list-group-item">
|
||||
<form action="{{ url_for('admin_tag.add') }}" method="post" class="row row-cols-lg-auto g-3 align-items-center">
|
||||
<div class="col-12 flex-grow-1">
|
||||
<label class="visually-hidden" for="name">Name</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">Name</div>
|
||||
<input type="text" class="form-control" id="name" name="name" value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary"><i class="ri-add-circle-line"></i> Add</button>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
{{ accordion.footer() }}
|
19
templates/admin/tag/delete.html
Normal file
19
templates/admin/tag/delete.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Set tags danger zone', 'tag-danger', 'admin', expanded=true, danger=true, class='text-end') }}
|
||||
<form action="{{ url_for('admin_tag.do_delete', id=tag.fields.id) }}" method="post">
|
||||
{% if tag_error %}<div class="alert alert-danger text-start" role="alert"><strong>Error:</strong> {{ tag_error }}.</div>{% endif %}
|
||||
<div class="alert alert-danger text-center" role="alert">You are about to <strong>delete a set tag</strong>. This action is irreversible.</div>
|
||||
<div class="row row-cols-lg-auto g-3 align-items-center">
|
||||
<div class="col-12 flex-grow-1">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">Name</div>
|
||||
<input type="text" class="form-control" value="{{ tag.fields.name }}" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="border-bottom">
|
||||
<a class="btn btn-danger" href="{{ url_for('admin.admin', open_tag=true) }}" role="button"><i class="ri-arrow-left-long-line"></i> Back to the admin</a>
|
||||
<button type="submit" class="btn btn-danger"><i class="ri-delete-bin-2-line"></i> Delete <strong>the set tag</strong></button>
|
||||
</form>
|
||||
{{ accordion.footer() }}
|
@ -72,6 +72,15 @@
|
||||
{{ badge(check=set, url=url, solo=solo, last=last, color='secondary', icon='hashtag', collapsible='Set:', text=set, alt='Set') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro tag(item, tag, solo=false, last=false) %}
|
||||
{% if last %}
|
||||
{% set tooltip=tag.fields.name %}
|
||||
{% else %}
|
||||
{% set text=tag.fields.name %}
|
||||
{% endif %}
|
||||
{{ badge(check=item.fields[tag.as_column()], solo=solo, last=last, color='light text-primary-emphasis bg-primary-subtle border border-primary-subtle', icon='price-tag-2-line', text=text, alt='Tag', tooltip=tooltip) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro theme(theme, solo=false, last=false) %}
|
||||
{% if last %}
|
||||
{% set tooltip=theme %}
|
||||
|
@ -12,7 +12,13 @@
|
||||
{% for owner in brickset_owners %}
|
||||
{% with checked=item.fields[owner.as_column()] %}
|
||||
data-{{ owner.as_dataset() }}="{{ checked }}"
|
||||
{% if checked %} data-owner-{{ loop.index }}="{{ owner.fields.name | lower }}"{% endif %}
|
||||
{% if checked %} data-search-owner-{{ loop.index }}="{{ owner.fields.name | lower }}"{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% for tag in brickset_tags %}
|
||||
{% with checked=item.fields[tag.as_column()] %}
|
||||
data-{{ tag.as_dataset() }}="{{ checked }}"
|
||||
{% if checked %} data-search-tag-{{ loop.index }}="{{ tag.fields.name | lower }}"{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@ -21,6 +27,9 @@
|
||||
{{ card.image(item, solo=solo, last=last, caption=item.fields.name, alt=item.fields.set) }}
|
||||
<div class="card-body border-bottom-0 {% if not solo %}p-1{% endif %}">
|
||||
{{ badge.theme(item.theme.name, solo=solo, last=last) }}
|
||||
{% for tag in brickset_tags %}
|
||||
{{ badge.tag(item, tag, solo=solo, last=last) }}
|
||||
{% endfor %}
|
||||
{{ badge.year(item.fields.year, solo=solo, last=last) }}
|
||||
{{ badge.parts(item.fields.number_of_parts, solo=solo, last=last) }}
|
||||
{{ badge.total_minifigures(item.fields.total_minifigures, solo=solo, last=last) }}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% if g.login.is_authenticated() %}
|
||||
{{ accordion.header('Owners', 'owner', 'set-details', icon='group-line', class='p-0') }}
|
||||
{{ accordion.header('Owners', 'owner', 'set-details', icon='group-line', class='p-0') }}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% if brickset_owners | length %}
|
||||
{% for owner in brickset_owners %}
|
||||
@ -12,9 +12,23 @@
|
||||
<div class="list-group list-group-flush border-top">
|
||||
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.admin', open_owner=true) }}"><i class="ri-settings-4-line"></i> Manage the set owners</a>
|
||||
</div>
|
||||
{{ accordion.footer() }}
|
||||
{{ accordion.header('Management', 'management', 'set-details', expanded=true, icon='settings-4-line') }}
|
||||
{{ accordion.footer() }}
|
||||
{{ accordion.header('Tags', 'tag', 'set-details', icon='price-tag-2-line', class='p-0') }}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% if brickset_tags | length %}
|
||||
{% for tag in brickset_tags %}
|
||||
<li class="d-flex list-group-item list-group-item-action text-nowrap">{{ form.checkbox(item, tag, delete=delete) }}</li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<li class="list-group-item list-group-item-action"><i class="ri-error-warning-line"></i> No tag found.</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div class="list-group list-group-flush border-top">
|
||||
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.admin', open_tag=true) }}"><i class="ri-settings-4-line"></i> Manage the set tags</a>
|
||||
</div>
|
||||
{{ accordion.footer() }}
|
||||
{{ accordion.header('Management', 'management', 'set-details', expanded=true, icon='settings-4-line') }}
|
||||
<h5 class="border-bottom">Data</h5>
|
||||
<a href="{{ item.url_for_refresh() }}" class="btn btn-primary" role="button"><i class="ri-refresh-line"></i> Refresh the set data</a>
|
||||
{{ accordion.footer() }}
|
||||
{{ accordion.footer() }}
|
||||
{% endif %}
|
||||
|
@ -10,7 +10,7 @@
|
||||
<label class="visually-hidden" for="grid-search">Search</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="ri-search-line"></i><span class="ms-1 d-none d-xl-inline"> Search</span></span>
|
||||
<input id="grid-search" data-search-exact="name,number,parts,theme,year" data-search-list="owner" class="form-control form-control-sm" type="text" placeholder="Set, name, number of parts, theme, year, owner" value="">
|
||||
<input id="grid-search" data-search-exact="name,number,parts,theme,year" data-search-list="searchOwner,searchTag" class="form-control form-control-sm" type="text" placeholder="Set, name, number of parts, theme, year, owner, tag" value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
@ -89,6 +89,20 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 flex-grow-1">
|
||||
<label class="visually-hidden" for="grid-tag">Tag</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="ri-price-tag-2-line"></i><span class="ms-1 d-none d-xl-inline"> Tag</span></span>
|
||||
<select id="grid-tag" class="form-select"
|
||||
data-filter="metadata"
|
||||
autocomplete="off">
|
||||
<option value="" selected>All</option>
|
||||
{% for tag in brickset_tags %}
|
||||
<option value="{{ tag.as_dataset() }}">{{ tag.fields.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" data-grid="true" id="grid">
|
||||
{% for item in collection %}
|
||||
|
Loading…
Reference in New Issue
Block a user