Rename checkboxes (too generic) to status (and some bug fixes)

This commit is contained in:
Gregoo 2025-01-30 15:03:16 +01:00
parent 26d4860bcc
commit cd70fb28dc
46 changed files with 304 additions and 281 deletions

View File

@ -13,7 +13,7 @@ from bricktracker.sql import close
from bricktracker.version import __version__ from bricktracker.version import __version__
from bricktracker.views.add import add_page from bricktracker.views.add import add_page
from bricktracker.views.admin.admin import admin_page from bricktracker.views.admin.admin import admin_page
from bricktracker.views.admin.checkbox import admin_checkbox_page from bricktracker.views.admin.status import admin_status_page
from bricktracker.views.admin.database import admin_database_page from bricktracker.views.admin.database import admin_database_page
from bricktracker.views.admin.image import admin_image_page from bricktracker.views.admin.image import admin_image_page
from bricktracker.views.admin.instructions import admin_instructions_page from bricktracker.views.admin.instructions import admin_instructions_page
@ -78,7 +78,7 @@ def setup_app(app: Flask) -> None:
# Register admin routes # Register admin routes
app.register_blueprint(admin_page) app.register_blueprint(admin_page)
app.register_blueprint(admin_checkbox_page) app.register_blueprint(admin_status_page)
app.register_blueprint(admin_database_page) app.register_blueprint(admin_database_page)
app.register_blueprint(admin_image_page) app.register_blueprint(admin_image_page)
app.register_blueprint(admin_instructions_page) app.register_blueprint(admin_instructions_page)

View File

@ -93,7 +93,7 @@ class BrickMetadata(BrickRecord):
metadata_id=self.fields.id metadata_id=self.fields.id
) )
# Select a specific checkbox (with an id) # Select a specific metadata (with an id)
def select_specific(self, id: str, /) -> Self: def select_specific(self, id: str, /) -> Self:
# Save the parameters to the fields # Save the parameters to the fields
self.fields.id = id self.fields.id = id

View File

@ -7,7 +7,7 @@ if TYPE_CHECKING:
# Grab the list of checkboxes to create a list of SQL columns # Grab the list of checkboxes to create a list of SQL columns
def migration_0007(sql: 'BrickSQL', /) -> dict[str, Any]: def migration_0007(sql: 'BrickSQL', /) -> dict[str, Any]:
# Don't realy on sql files as they could be removed in the future # Don't realy on sql files as they could be removed in the future
sql.cursor.execute('SELECT "bricktracker_set_checkboxes"."id" FROM "bricktracker_set_checkboxes') # noqa: E501 sql.cursor.execute('SELECT "bricktracker_set_checkboxes"."id" FROM "bricktracker_set_checkboxes"') # noqa: E501
records = sql.cursor.fetchall() records = sql.cursor.fetchall()
return { return {

View File

@ -8,13 +8,13 @@ if TYPE_CHECKING:
from .part import BrickPart from .part import BrickPart
from .rebrickable_set import RebrickableSet from .rebrickable_set import RebrickableSet
from .set import BrickSet from .set import BrickSet
from .set_checkbox import BrickSetCheckbox from .set_status import BrickSetStatus
from .wish import BrickWish from .wish import BrickWish
T = TypeVar( T = TypeVar(
'T', 'T',
'BrickSet', 'BrickSet',
'BrickSetCheckbox', 'BrickSetStatus',
'BrickPart', 'BrickPart',
'BrickMinifigure', 'BrickMinifigure',
'BrickWish', 'BrickWish',

View File

@ -1,6 +1,6 @@
from .instructions_list import BrickInstructionsList from .instructions_list import BrickInstructionsList
from .retired_list import BrickRetiredList from .retired_list import BrickRetiredList
from .set_checkbox_list import BrickSetCheckboxList from .set_status_list import BrickSetStatusList
from .theme_list import BrickThemeList from .theme_list import BrickThemeList
@ -11,8 +11,8 @@ def reload() -> None:
# Reload the instructions # Reload the instructions
BrickInstructionsList(force=True) BrickInstructionsList(force=True)
# Reload the checkboxes # Reload the set statuses
BrickSetCheckboxList(force=True) BrickSetStatusList(force=True)
# Reload retired sets # Reload retired sets
BrickRetiredList(force=True) BrickRetiredList(force=True)

View File

@ -9,7 +9,7 @@ from .exceptions import NotFoundException
from .minifigure_list import BrickMinifigureList from .minifigure_list import BrickMinifigureList
from .part_list import BrickPartList from .part_list import BrickPartList
from .rebrickable_set import RebrickableSet from .rebrickable_set import RebrickableSet
from .set_checkbox_list import BrickSetCheckboxList from .set_status_list import BrickSetStatusList
from .sql import BrickSQL from .sql import BrickSQL
if TYPE_CHECKING: if TYPE_CHECKING:
from .socket import BrickSocket from .socket import BrickSocket
@ -161,7 +161,7 @@ class BrickSet(RebrickableSet):
# Load from database # Load from database
if not self.select( if not self.select(
statuses=BrickSetCheckboxList().as_columns(solo=True) statuses=BrickSetStatusList().as_columns(solo=True)
): ):
raise NotFoundException( raise NotFoundException(
'Set with ID {id} was not found in the database'.format( 'Set with ID {id} was not found in the database'.format(

View File

@ -3,7 +3,7 @@ from typing import Self
from flask import current_app from flask import current_app
from .record_list import BrickRecordList from .record_list import BrickRecordList
from .set_checkbox_list import BrickSetCheckboxList from .set_status_list import BrickSetStatusList
from .set import BrickSet from .set import BrickSet
@ -37,7 +37,7 @@ class BrickSetList(BrickRecordList[BrickSet]):
# Load the sets from the database # Load the sets from the database
for record in self.select( for record in self.select(
order=self.order, order=self.order,
statuses=BrickSetCheckboxList().as_columns() statuses=BrickSetStatusList().as_columns()
): ):
brickset = BrickSet(record=record) brickset = BrickSet(record=record)
@ -73,7 +73,7 @@ class BrickSetList(BrickRecordList[BrickSet]):
for record in self.select( for record in self.select(
order=order, order=order,
limit=limit, limit=limit,
statuses=BrickSetCheckboxList().as_columns() statuses=BrickSetStatusList().as_columns()
): ):
brickset = BrickSet(record=record) brickset = BrickSet(record=record)

View File

@ -4,20 +4,20 @@ from .exceptions import ErrorException
from .metadata import BrickMetadata from .metadata import BrickMetadata
# Lego set checkbox # Lego set status metadata
class BrickSetCheckbox(BrickMetadata): class BrickSetStatus(BrickMetadata):
kind: str = 'checkbox' kind: str = 'status'
prefix: str = 'status' prefix: str = 'status'
# Set state endpoint # Set state endpoint
set_state_endpoint: str = 'set.update_status' set_state_endpoint: str = 'set.update_status'
# Queries # Queries
delete_query: str = 'checkbox/delete' delete_query: str = 'set/metadata/status/delete'
insert_query: str = 'checkbox/insert' insert_query: str = 'set/metadata/status/insert'
select_query: str = 'checkbox/select' select_query: str = 'set/metadata/status/select'
update_field_query: str = 'checkbox/update/field' update_field_query: str = 'set/metadata/status/update/field'
update_set_state_query: str = 'set/update/status' update_set_state_query: str = 'set/metadata/status/update/state'
# Grab data from a form # Grab data from a form
def from_form(self, form: dict[str, str], /) -> Self: def from_form(self, form: dict[str, str], /) -> Self:
@ -25,7 +25,7 @@ class BrickSetCheckbox(BrickMetadata):
grid = form.get('grid', None) grid = form.get('grid', None)
if name is None or name == '': if name is None or name == '':
raise ErrorException('Checkbox name cannot be empty') raise ErrorException('Status name cannot be empty')
self.fields.name = name self.fields.name = name
self.fields.displayed_on_grid = grid == 'on' self.fields.displayed_on_grid = grid == 'on'

View File

@ -3,39 +3,39 @@ import logging
from .exceptions import NotFoundException from .exceptions import NotFoundException
from .fields import BrickRecordFields from .fields import BrickRecordFields
from .record_list import BrickRecordList from .record_list import BrickRecordList
from .set_checkbox import BrickSetCheckbox from .set_status import BrickSetStatus
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Lego sets checkbox list # Lego sets status list
class BrickSetCheckboxList(BrickRecordList[BrickSetCheckbox]): class BrickSetStatusList(BrickRecordList[BrickSetStatus]):
checkboxes: dict[str, BrickSetCheckbox] statuses: dict[str, BrickSetStatus]
# Queries # Queries
select_query = 'checkbox/list' select_query = 'set/metadata/status/list'
def __init__(self, /, *, force: bool = False): def __init__(self, /, *, force: bool = False):
# Load checkboxes only if there is none already loaded # Load statuses only if there is none already loaded
records = getattr(self, 'records', None) records = getattr(self, 'records', None)
if records is None or force: if records is None or force:
# Don't use super()__init__ as it would mask class variables # Don't use super()__init__ as it would mask class variables
self.fields = BrickRecordFields() self.fields = BrickRecordFields()
logger.info('Loading set checkboxes list') logger.info('Loading set statuses list')
BrickSetCheckboxList.records = [] BrickSetStatusList.records = []
BrickSetCheckboxList.checkboxes = {} BrickSetStatusList.statuses = {}
# Load the checkboxes from the database # Load the statuses from the database
for record in self.select(): for record in self.select():
checkbox = BrickSetCheckbox(record=record) status = BrickSetStatus(record=record)
BrickSetCheckboxList.records.append(checkbox) BrickSetStatusList.records.append(status)
BrickSetCheckboxList.checkboxes[checkbox.fields.id] = checkbox BrickSetStatusList.statuses[status.fields.id] = status
# Return the checkboxes as columns for a select # Return the statuses as columns for a select
def as_columns( def as_columns(
self, self,
/, /,
@ -53,19 +53,19 @@ class BrickSetCheckboxList(BrickRecordList[BrickSetCheckbox]):
if solo or record.fields.displayed_on_grid if solo or record.fields.displayed_on_grid
]) ])
# Grab a specific checkbox # Grab a specific status
def get(self, id: str, /) -> BrickSetCheckbox: def get(self, id: str, /) -> BrickSetStatus:
if id not in self.checkboxes: if id not in self.statuses:
raise NotFoundException( raise NotFoundException(
'Checkbox with ID {id} was not found in the database'.format( 'Status with ID {id} was not found in the database'.format(
id=id, id=id,
), ),
) )
return self.checkboxes[id] return self.statuses[id]
# Get the list of checkboxes depending on the context # Get the list of statuses depending on the context
def list(self, /, *, all: bool = False) -> list[BrickSetCheckbox]: def list(self, /, *, all: bool = False) -> list[BrickSetStatus]:
return [ return [
record record
for record for record

View File

@ -318,13 +318,18 @@ class BrickSQL(object):
), ),
package='bricktracker' package='bricktracker'
) )
except Exception:
module = None
# If a module has been loaded, we need to fail if an error
# occured while executing the migration function
if module is not None:
function = getattr(module, 'migration_{name}'.format( function = getattr(module, 'migration_{name}'.format(
name=pending.name name=pending.name
)) ))
context: dict[str, Any] = function(self) context: dict[str, Any] = function(self)
except Exception: else:
context: dict[str, Any] = {} context: dict[str, Any] = {}
self.executescript(pending.get_query(), **context) self.executescript(pending.get_query(), **context)

View File

@ -1,7 +0,0 @@
SELECT
"bricktracker_set_checkboxes"."id",
"bricktracker_set_checkboxes"."name",
"bricktracker_set_checkboxes"."displayed_on_grid"
FROM "bricktracker_set_checkboxes"
{% block where %}{% endblock %}

View File

@ -1,9 +0,0 @@
BEGIN TRANSACTION;
ALTER TABLE "bricktracker_set_statuses"
DROP COLUMN "status_{{ id }}";
DELETE FROM "bricktracker_set_checkboxes"
WHERE "bricktracker_set_checkboxes"."id" IS NOT DISTINCT FROM '{{ id }}';
COMMIT;

View File

@ -1 +0,0 @@
{% extends 'checkbox/base.sql' %}

View File

@ -1,5 +0,0 @@
{% extends 'checkbox/base.sql' %}
{% block where %}
WHERE "bricktracker_set_checkboxes"."id" IS NOT DISTINCT FROM :id
{% endblock %}

View File

@ -1,3 +0,0 @@
UPDATE "bricktracker_set_checkboxes"
SET "{{field}}" = :value
WHERE "bricktracker_set_checkboxes"."id" IS NOT DISTINCT FROM :id

View File

@ -0,0 +1,7 @@
-- description: Rename checkboxes to status metadata
BEGIN TRANSACTION;
ALTER TABLE "bricktracker_set_checkboxes" RENAME TO "bricktracker_metadata_statuses";
COMMIT;

View File

@ -1,5 +1,6 @@
BEGIN transaction; BEGIN transaction;
DROP TABLE IF EXISTS "bricktracker_metadata_statuses";
DROP TABLE IF EXISTS "bricktracker_minifigures"; DROP TABLE IF EXISTS "bricktracker_minifigures";
DROP TABLE IF EXISTS "bricktracker_parts"; DROP TABLE IF EXISTS "bricktracker_parts";
DROP TABLE IF EXISTS "bricktracker_sets"; DROP TABLE IF EXISTS "bricktracker_sets";

View File

@ -0,0 +1,7 @@
SELECT
"bricktracker_metadata_statuses"."id",
"bricktracker_metadata_statuses"."name",
"bricktracker_metadata_statuses"."displayed_on_grid"
FROM "bricktracker_metadata_statuses"
{% block where %}{% endblock %}

View File

@ -0,0 +1,9 @@
BEGIN TRANSACTION;
ALTER TABLE "bricktracker_set_statuses"
DROP COLUMN "status_{{ id }}";
DELETE FROM "bricktracker_metadata_statuses"
WHERE "bricktracker_metadata_statuses"."id" IS NOT DISTINCT FROM '{{ id }}';
COMMIT;

View File

@ -3,7 +3,7 @@ BEGIN TRANSACTION;
ALTER TABLE "bricktracker_set_statuses" ALTER TABLE "bricktracker_set_statuses"
ADD COLUMN "status_{{ id }}" BOOLEAN NOT NULL DEFAULT 0; ADD COLUMN "status_{{ id }}" BOOLEAN NOT NULL DEFAULT 0;
INSERT INTO "bricktracker_set_checkboxes" ( INSERT INTO "bricktracker_metadata_statuses" (
"id", "id",
"name", "name",
"displayed_on_grid" "displayed_on_grid"

View File

@ -0,0 +1 @@
{% extends 'set/metadata/status/base.sql' %}

View File

@ -0,0 +1,5 @@
{% extends 'set/metadata/status/base.sql' %}
{% block where %}
WHERE "bricktracker_metadata_statuses"."id" IS NOT DISTINCT FROM :id
{% endblock %}

View File

@ -0,0 +1,3 @@
UPDATE "bricktracker_metadata_statuses"
SET "{{field}}" = :value
WHERE "bricktracker_metadata_statuses"."id" IS NOT DISTINCT FROM :id

View File

@ -2,11 +2,12 @@ from typing import Tuple
# Some table aliases to make it look cleaner (id: (name, icon)) # Some table aliases to make it look cleaner (id: (name, icon))
ALIASES: dict[str, Tuple[str, str]] = { ALIASES: dict[str, Tuple[str, str]] = {
'bricktracker_metadata_statuses': ('Bricktracker set status metadata', 'checkbox-line'), # noqa: E501
'bricktracker_minifigures': ('Bricktracker minifigures', 'group-line'), 'bricktracker_minifigures': ('Bricktracker minifigures', 'group-line'),
'bricktracker_parts': ('Bricktracker parts', 'shapes-line'), 'bricktracker_parts': ('Bricktracker parts', 'shapes-line'),
'bricktracker_set_checkboxes': ('Bricktracker set checkboxes', 'checkbox-line'), # noqa: E501 'bricktracker_set_checkboxes': ('Bricktracker set checkboxes (legacy)', 'checkbox-line'), # noqa: E501
'bricktracker_set_statuses': ('Bricktracker sets status', 'checkbox-circle-line'), # noqa: E501 'bricktracker_set_statuses': ('Bricktracker set statuses', 'checkbox-line'), # noqa: E501
'bricktracker_set_storages': ('Bricktracker sets storages', 'archive-2-line'), # noqa: E501 'bricktracker_set_storages': ('Bricktracker set storages', 'archive-2-line'), # noqa: E501
'bricktracker_sets': ('Bricktracker sets', 'hashtag'), 'bricktracker_sets': ('Bricktracker sets', 'hashtag'),
'bricktracker_wishes': ('Bricktracker wishes', 'gift-line'), 'bricktracker_wishes': ('Bricktracker wishes', 'gift-line'),
'inventory': ('Parts', 'shapes-line'), 'inventory': ('Parts', 'shapes-line'),

View File

@ -1,4 +1,4 @@
from typing import Final from typing import Final
__version__: Final[str] = '1.2.0' __version__: Final[str] = '1.2.0'
__database_version__: Final[int] = 11 __database_version__: Final[int] = 12

View File

@ -8,8 +8,8 @@ from ..exceptions import exception_handler
from ...instructions_list import BrickInstructionsList from ...instructions_list import BrickInstructionsList
from ...rebrickable_image import RebrickableImage from ...rebrickable_image import RebrickableImage
from ...retired_list import BrickRetiredList from ...retired_list import BrickRetiredList
from ...set_checkbox import BrickSetCheckbox from ...set_status import BrickSetStatus
from ...set_checkbox_list import BrickSetCheckboxList from ...set_status_list import BrickSetStatusList
from ...sql_counter import BrickCounter from ...sql_counter import BrickCounter
from ...sql import BrickSQL from ...sql import BrickSQL
from ...theme_list import BrickThemeList from ...theme_list import BrickThemeList
@ -24,11 +24,11 @@ admin_page = Blueprint('admin', __name__, url_prefix='/admin')
@login_required @login_required
@exception_handler(__file__) @exception_handler(__file__)
def admin() -> str: def admin() -> str:
brickset_checkboxes: list[BrickSetCheckbox] = []
database_counters: list[BrickCounter] = [] database_counters: list[BrickCounter] = []
database_exception: Exception | None = None database_exception: Exception | None = None
database_upgrade_needed: bool = False database_upgrade_needed: bool = False
database_version: int = -1 database_version: int = -1
metadata_statuses: list[BrickSetStatus] = []
nil_minifigure_name: str = '' nil_minifigure_name: str = ''
nil_minifigure_url: str = '' nil_minifigure_url: str = ''
nil_part_name: str = '' nil_part_name: str = ''
@ -41,7 +41,7 @@ def admin() -> str:
database_version = database.version database_version = database.version
database_counters = BrickSQL().count_records() database_counters = BrickSQL().count_records()
brickset_checkboxes = BrickSetCheckboxList().list(all=True) metadata_statuses = BrickSetStatusList().list(all=True)
except Exception as e: except Exception as e:
database_exception = e database_exception = e
@ -62,38 +62,38 @@ def admin() -> str:
'PARTS_FOLDER' 'PARTS_FOLDER'
) )
open_checkbox = request.args.get('open_checkbox', None)
open_image = request.args.get('open_image', None) open_image = request.args.get('open_image', None)
open_instructions = request.args.get('open_instructions', None) open_instructions = request.args.get('open_instructions', None)
open_logout = request.args.get('open_logout', None) open_logout = request.args.get('open_logout', None)
open_retired = request.args.get('open_retired', None) open_retired = request.args.get('open_retired', None)
open_status = request.args.get('open_status', None)
open_theme = request.args.get('open_theme', None) open_theme = request.args.get('open_theme', None)
open_database = ( open_database = (
open_checkbox is None and
open_image is None and open_image is None and
open_instructions is None and open_instructions is None and
open_logout is None and open_logout is None and
open_retired is None and open_retired is None and
open_status is None and
open_theme is None open_theme is None
) )
return render_template( return render_template(
'admin.html', 'admin.html',
configuration=BrickConfigurationList.list(), configuration=BrickConfigurationList.list(),
brickset_checkboxes=brickset_checkboxes, status_error=request.args.get('status_error'),
checkbox_error=request.args.get('checkbox_error'),
database_counters=database_counters, database_counters=database_counters,
database_error=request.args.get('database_error'), database_error=request.args.get('database_error'),
database_exception=database_exception, database_exception=database_exception,
database_upgrade_needed=database_upgrade_needed, database_upgrade_needed=database_upgrade_needed,
database_version=database_version, database_version=database_version,
instructions=BrickInstructionsList(), instructions=BrickInstructionsList(),
metadata_statuses=metadata_statuses,
nil_minifigure_name=nil_minifigure_name, nil_minifigure_name=nil_minifigure_name,
nil_minifigure_url=nil_minifigure_url, nil_minifigure_url=nil_minifigure_url,
nil_part_name=nil_part_name, nil_part_name=nil_part_name,
nil_part_url=nil_part_url, nil_part_url=nil_part_url,
open_checkbox=open_checkbox, open_status=open_status,
open_database=open_database, open_database=open_database,
open_image=open_image, open_image=open_image,
open_instructions=open_instructions, open_instructions=open_instructions,

View File

@ -1,98 +0,0 @@
from flask import (
Blueprint,
jsonify,
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_checkbox import BrickSetCheckbox
admin_checkbox_page = Blueprint(
'admin_checkbox',
__name__,
url_prefix='/admin/checkbox'
)
# Add a checkbox
@admin_checkbox_page.route('/add', methods=['POST'])
@login_required
@exception_handler(
__file__,
post_redirect='admin.admin',
error_name='checkbox_error',
open_checkbox=True
)
def add() -> Response:
BrickSetCheckbox().from_form(request.form).insert()
reload()
return redirect(url_for('admin.admin', open_checkbox=True))
# Delete the checkbox
@admin_checkbox_page.route('<id>/delete', methods=['GET'])
@login_required
@exception_handler(__file__)
def delete(*, id: str) -> str:
return render_template(
'admin.html',
delete_checkbox=True,
checkbox=BrickSetCheckbox().select_specific(id),
error=request.args.get('checkbox_error')
)
# Actually delete the checkbox
@admin_checkbox_page.route('<id>/delete', methods=['POST'])
@login_required
@exception_handler(
__file__,
post_redirect='admin_checkbox.delete',
error_name='checkbox_error'
)
def do_delete(*, id: str) -> Response:
checkbox = BrickSetCheckbox().select_specific(id)
checkbox.delete()
reload()
return redirect(url_for('admin.admin', open_checkbox=True))
# Change the field of a checkbox
@admin_checkbox_page.route('/<id>/field/<name>', methods=['POST'])
@login_required
@exception_handler(__file__, json=True)
def update_field(*, id: str, name: str) -> Response:
checkbox = BrickSetCheckbox().select_specific(id)
value = checkbox.update_field(name, json=request.json)
reload()
return jsonify({'value': value})
# Rename the checkbox
@admin_checkbox_page.route('<id>/rename', methods=['POST'])
@login_required
@exception_handler(
__file__,
post_redirect='admin.admin',
error_name='checkbox_error',
open_checkbox=True
)
def rename(*, id: str) -> Response:
checkbox = BrickSetCheckbox().select_specific(id)
checkbox.from_form(request.form).rename()
reload()
return redirect(url_for('admin.admin', open_checkbox=True))

View File

@ -0,0 +1,98 @@
from flask import (
Blueprint,
jsonify,
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_status import BrickSetStatus
admin_status_page = Blueprint(
'admin_status',
__name__,
url_prefix='/admin/status'
)
# Add a metadata status
@admin_status_page.route('/add', methods=['POST'])
@login_required
@exception_handler(
__file__,
post_redirect='admin.admin',
error_name='status_error',
open_status=True
)
def add() -> Response:
BrickSetStatus().from_form(request.form).insert()
reload()
return redirect(url_for('admin.admin', open_status=True))
# Delete the metadata status
@admin_status_page.route('<id>/delete', methods=['GET'])
@login_required
@exception_handler(__file__)
def delete(*, id: str) -> str:
return render_template(
'admin.html',
delete_status=True,
status=BrickSetStatus().select_specific(id),
error=request.args.get('status_error')
)
# Actually delete the metadata status
@admin_status_page.route('<id>/delete', methods=['POST'])
@login_required
@exception_handler(
__file__,
post_redirect='admin_status.delete',
error_name='status_error'
)
def do_delete(*, id: str) -> Response:
status = BrickSetStatus().select_specific(id)
status.delete()
reload()
return redirect(url_for('admin.admin', open_status=True))
# Change the field of a metadata status
@admin_status_page.route('/<id>/field/<name>', methods=['POST'])
@login_required
@exception_handler(__file__, json=True)
def update_field(*, id: str, name: str) -> Response:
status = BrickSetStatus().select_specific(id)
value = status.update_field(name, json=request.json)
reload()
return jsonify({'value': value})
# Rename the metadata status
@admin_status_page.route('<id>/rename', methods=['POST'])
@login_required
@exception_handler(
__file__,
post_redirect='admin.admin',
error_name='status_error',
open_status=True
)
def rename(*, id: str) -> Response:
status = BrickSetStatus().select_specific(id)
status.from_form(request.form).rename()
reload()
return redirect(url_for('admin.admin', open_status=True))

View File

@ -2,7 +2,7 @@ from flask import Blueprint, render_template
from .exceptions import exception_handler from .exceptions import exception_handler
from ..minifigure_list import BrickMinifigureList from ..minifigure_list import BrickMinifigureList
from ..set_checkbox_list import BrickSetCheckboxList from ..set_status_list import BrickSetStatusList
from ..set_list import BrickSetList from ..set_list import BrickSetList
index_page = Blueprint('index', __name__) index_page = Blueprint('index', __name__)
@ -16,5 +16,5 @@ def index() -> str:
'index.html', 'index.html',
brickset_collection=BrickSetList().last(), brickset_collection=BrickSetList().last(),
minifigure_collection=BrickMinifigureList().last(), minifigure_collection=BrickMinifigureList().last(),
brickset_checkboxes=BrickSetCheckboxList().list(), brickset_statuses=BrickSetStatusList().list(),
) )

View File

@ -16,7 +16,7 @@ from .exceptions import exception_handler
from ..minifigure import BrickMinifigure from ..minifigure import BrickMinifigure
from ..part import BrickPart from ..part import BrickPart
from ..set import BrickSet from ..set import BrickSet
from ..set_checkbox_list import BrickSetCheckboxList from ..set_status_list import BrickSetStatusList
from ..set_list import BrickSetList from ..set_list import BrickSetList
from ..socket import MESSAGES from ..socket import MESSAGES
@ -32,19 +32,19 @@ def list() -> str:
return render_template( return render_template(
'sets.html', 'sets.html',
collection=BrickSetList().all(), collection=BrickSetList().all(),
brickset_checkboxes=BrickSetCheckboxList().list(), brickset_statuses=BrickSetStatusList().list(),
) )
# Change the status of a checkbox # Change the status of a status
@set_page.route('/<id>/status/<metadata_id>', methods=['POST']) @set_page.route('/<id>/status/<metadata_id>', methods=['POST'])
@login_required @login_required
@exception_handler(__file__, json=True) @exception_handler(__file__, json=True)
def update_status(*, id: str, metadata_id: str) -> Response: def update_status(*, id: str, metadata_id: str) -> Response:
brickset = BrickSet().select_light(id) brickset = BrickSet().select_light(id)
checkbox = BrickSetCheckboxList().get(metadata_id) status = BrickSetStatusList().get(metadata_id)
state = checkbox.update_set_state(brickset, request.json) state = status.update_set_state(brickset, request.json)
return jsonify({'value': state}) return jsonify({'value': state})
@ -97,7 +97,7 @@ def details(*, id: str) -> str:
'set.html', 'set.html',
item=BrickSet().select_specific(id), item=BrickSet().select_specific(id),
open_instructions=request.args.get('open_instructions'), open_instructions=request.args.get('open_instructions'),
brickset_checkboxes=BrickSetCheckboxList().list(all=True), brickset_statuses=BrickSetStatusList().list(all=True),
) )

View File

@ -14,7 +14,7 @@ This page helps you navigate the documentation of BrickTracker.
- [First steps](first-steps.md) - [First steps](first-steps.md)
- [Managing your sets](set.md) - [Managing your sets](set.md)
- [Managing your set checkboxes](checkbox.md) - [Managing your set statuses](set-statuses.md)
## Specific procedures ## Specific procedures

View File

@ -1,58 +0,0 @@
# Manage your set chechboxes
> **Note**
> The following page is based on version `1.1.0` of BrickTracker.
They are useful to store "yes/no" info about a set and quickly set it. Once clicked the change is immediatly stored in the database. A visual indicator tells you the change was succesful.
![](images/checkbox-01.png)
## Default checkboxes
The original version of BrickTracker defined the following checkboxes
- Minifigures are collected
- Set is checked
- Set is collected and boxed
## Visibility
The checkboxes are **never visible** on the front page. The display here tries to be as minimalistic as possible.
Prior to version `1.1.0`, the checkboxes were visible both on the Grid view (**Sets**) and the details of a set.
![](images/checkbox-02.png)
![](images/checkbox-03.png)
From version `1.1.0`, it is possible to decide if a checkbox is visible from the Grid or not. It will always be visible in a set details.
### Change the visibility of a checkbox
To change the visibility of a checkbox, head to the **Admin page** and open the **Checkboxes** section.
![](images/checkbox-04.png)
Simply click on the **Displayed on the Set Grid** checkbox to select whether it is displayed or not. The change is immediately saved to the database.
![](images/checkbox-05.png)
In this example, we have decided to have no checkbox visible on the Grid view.
![](images/checkbox-06.png)
## Management
Starting version `1.1.0`, you can manage the checkboxes for the **Checkboxes** section of the **Admin page**.
![](images/checkbox-04.png)
From there you can do the following:
- Add a new checkbox: use the last line of the list and press the **Add** button
- Rename an existing checkbox: use the **Name** field to change the name and press the **Rename** button
- Change the Grid display of an existing checkbox: tick or untick the **Displayed on the Set Grid** checkbox
- Delete an existing checkbox: use the **Delete** button and confirm on the following screen
It is possible to delete all the checkboxes, they are an optional component of a set.
![](images/checkbox-07.png)

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 773 KiB

After

Width:  |  Height:  |  Size: 773 KiB

View File

Before

Width:  |  Height:  |  Size: 389 KiB

After

Width:  |  Height:  |  Size: 389 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 948 KiB

After

Width:  |  Height:  |  Size: 948 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

67
docs/set-statuses.md Normal file
View File

@ -0,0 +1,67 @@
# Manage your set statuses
> **Note**
> The following page is based on version `1.1.0` of BrickTracker.
> **Note**
> On version `1.2.0`, this feature has been renommed from `Checkboxes` to `Set statuses`. It works exactly the same.
They are useful to store "yes/no" info about a set and quickly set it. Once clicked the change is immediatly stored in the database. A visual indicator tells you the change was succesful.
![](images/status-01.png)
## Default statuses
The original version of BrickTracker defined the following statuses
- Minifigures are collected
- Set is checked
- Set is collected and boxed
## Visibility
The statuses are **never visible** on the front page. The display here tries to be as minimalistic as possible.
Prior to version `1.1.0`, the statuses were visible both on the Grid view (**Sets**) and the details of a set.
![](images/status-02.png)
![](images/status-03.png)
From version `1.1.0`, it is possible to decide if a status is visible from the Grid or not. It will always be visible in a set details.
### Change the visibility of a status
> **Note**
> On version `1.2.0`, the Admin page section has been renamed from `Checkboxes` to `Set statuses`. It works exactly the same.
To change the visibility of a status, head to the **Admin page** and open the **Set statuses** section.
![](images/status-04.png)
Simply click on the **Displayed on the Set Grid** status to select whether it is displayed or not. The change is immediately saved to the database.
![](images/status-05.png)
In this example, we have decided to have no status visible on the Grid view.
![](images/status-06.png)
## Management
> **Note**
> On version `1.2.0`, the Admin page section has been renamed from `Checkboxes` to `Set statuses`. It works exactly the same.
Starting version `1.1.0`, you can manage the set statuses for the **Set statuses** section of the **Admin page**.
![](images/status-04.png)
From there you can do the following:
- Add a new set status: use the last line of the list and press the **Add** button
- Rename an existing set status: use the **Name** field to change the name and press the **Rename** button
- Change the Grid display of an existing status: tick or untick the **Displayed on the Set Grid** checkbox
- Delete an existing set status: use the **Delete** button and confirm on the following screen
It is possible to delete all the set statuses, they are an optional component of a set.
![](images/status-07.png)

View File

@ -12,8 +12,8 @@
<h5 class="mb-0"><i class="ri-settings-4-line"></i> Administration</h5> <h5 class="mb-0"><i class="ri-settings-4-line"></i> Administration</h5>
</div> </div>
<div class="accordion accordion-flush" id="admin"> <div class="accordion accordion-flush" id="admin">
{% if delete_checkbox %} {% if delete_status %}
{% include 'admin/checkbox/delete.html' %} {% include 'admin/status/delete.html' %}
{% elif delete_database %} {% elif delete_database %}
{% include 'admin/database/delete.html' %} {% include 'admin/database/delete.html' %}
{% elif drop_database %} {% elif drop_database %}
@ -30,7 +30,7 @@
{% endif %} {% endif %}
{% include 'admin/theme.html' %} {% include 'admin/theme.html' %}
{% include 'admin/retired.html' %} {% include 'admin/retired.html' %}
{% include 'admin/checkbox.html' %} {% include 'admin/status.html' %}
{% include 'admin/database.html' %} {% include 'admin/database.html' %}
{% include 'admin/configuration.html' %} {% include 'admin/configuration.html' %}
{% endif %} {% endif %}

View File

@ -1,42 +1,42 @@
{% import 'macro/accordion.html' as accordion %} {% import 'macro/accordion.html' as accordion %}
{{ accordion.header('Checkboxes', 'checkbox', 'admin', expanded=open_checkbox, icon='checkbox-line', class='p-0') }} {{ accordion.header('Set statuses', 'status', 'admin', expanded=open_status, icon='checkbox-line', class='p-0') }}
{% if checkbox_error %}<div class="alert alert-danger m-2" role="alert"><strong>Error:</strong> {{ checkbox_error }}.</div>{% endif %} {% if status_error %}<div class="alert alert-danger m-2" role="alert"><strong>Error:</strong> {{ status_error }}.</div>{% endif %}
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
{% if brickset_checkboxes | length %} {% if metadata_statuses | length %}
{% for checkbox in brickset_checkboxes %} {% for status in metadata_statuses %}
<li class="list-group-item"> <li class="list-group-item">
<form action="{{ url_for('admin_checkbox.rename', id=checkbox.fields.id) }}" method="post" class="row row-cols-lg-auto g-3 align-items-center"> <form action="{{ url_for('admin_status.rename', id=status.fields.id) }}" method="post" class="row row-cols-lg-auto g-3 align-items-center">
<div class="col-12 flex-grow-1"> <div class="col-12 flex-grow-1">
<label class="visually-hidden" for="name-{{ checkbox.fields.id }}">Name</label> <label class="visually-hidden" for="name-{{ status.fields.id }}">Name</label>
<div class="input-group"> <div class="input-group">
<div class="input-group-text">Name</div> <div class="input-group-text">Name</div>
<input type="text" class="form-control" id="name-{{ checkbox.fields.id }}" name="name" value="{{ checkbox.fields.name }}"> <input type="text" class="form-control" id="name-{{ status.fields.id }}" name="name" value="{{ status.fields.name }}">
<button type="submit" class="btn btn-primary"><i class="ri-edit-line"></i> Rename</button> <button type="submit" class="btn btn-primary"><i class="ri-edit-line"></i> Rename</button>
</div> </div>
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="grid-{{ checkbox.fields.id }}" <input class="form-check-input" type="checkbox" id="grid-{{ status.fields.id }}"
data-changer-id="{{ checkbox.fields.id }}" data-changer-prefix="grid" data-changer-url="{{ url_for('admin_checkbox.update_field', id=checkbox.fields.id, name='displayed_on_grid')}}" data-changer-id="{{ status.fields.id }}" data-changer-prefix="grid" data-changer-url="{{ url_for('admin_status.update_field', id=status.fields.id, name='displayed_on_grid')}}"
{% if checkbox.fields.displayed_on_grid %}checked{% endif %} autocomplete="off"> {% if status.fields.displayed_on_grid %}checked{% endif %} autocomplete="off">
<label class="form-check-label" for="grid-{{ checkbox.fields.id }}"> <label class="form-check-label" for="grid-{{ status.fields.id }}">
<i class="ri-grid-line"></i> Displayed on the Set Grid <i class="ri-grid-line"></i> Displayed on the Set Grid
<i id="status-grid-{{ checkbox.fields.id }}" class="mb-1"></i> <i id="status-grid-{{ status.fields.id }}" class="mb-1"></i>
</label> </label>
</div> </div>
</div> </div>
<div class="col-12"> <div class="col-12">
<a href="{{ url_for('admin_checkbox.delete', id=checkbox.fields.id) }}" class="btn btn-danger" role="button"><i class="ri-delete-bin-2-line"></i> Delete</a> <a href="{{ url_for('admin_status.delete', id=status.fields.id) }}" class="btn btn-danger" role="button"><i class="ri-delete-bin-2-line"></i> Delete</a>
</div> </div>
</form> </form>
</li> </li>
{% endfor %} {% endfor %}
{% else %} {% else %}
<li class="list-group-item"><i class="ri-error-warning-line"></i> No checkbox found.</li> <li class="list-group-item"><i class="ri-error-warning-line"></i> No status found.</li>
{% endif %} {% endif %}
<li class="list-group-item"> <li class="list-group-item">
<form action="{{ url_for('admin_checkbox.add') }}" method="post" class="row row-cols-lg-auto g-3 align-items-center"> <form action="{{ url_for('admin_status.add') }}" method="post" class="row row-cols-lg-auto g-3 align-items-center">
<div class="col-12 flex-grow-1"> <div class="col-12 flex-grow-1">
<label class="visually-hidden" for="name">Name</label> <label class="visually-hidden" for="name">Name</label>
<div class="input-group"> <div class="input-group">

View File

@ -1,25 +1,25 @@
{% import 'macro/accordion.html' as accordion %} {% import 'macro/accordion.html' as accordion %}
{{ accordion.header('Checkbox danger zone', 'checkbox-danger', 'admin', expanded=true, danger=true, class='text-end') }} {{ accordion.header('Set statuses danger zone', 'status-danger', 'admin', expanded=true, danger=true, class='text-end') }}
<form action="{{ url_for('admin_checkbox.do_delete', id=checkbox.fields.id) }}" method="post"> <form action="{{ url_for('admin_status.do_delete', id=status.fields.id) }}" method="post">
{% if error %}<div class="alert alert-danger text-start" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %} {% if error %}<div class="alert alert-danger text-start" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
<div class="alert alert-danger text-center" role="alert">You are about to <strong>delete a checkbox</strong>. This action is irreversible.</div> <div class="alert alert-danger text-center" role="alert">You are about to <strong>delete a set status</strong>. This action is irreversible.</div>
<div class="row row-cols-lg-auto g-3 align-items-center"> <div class="row row-cols-lg-auto g-3 align-items-center">
<div class="col-12 flex-grow-1"> <div class="col-12 flex-grow-1">
<div class="input-group"> <div class="input-group">
<div class="input-group-text">Name</div> <div class="input-group-text">Name</div>
<input type="text" class="form-control" value="{{ checkbox.fields.name }}" disabled> <input type="text" class="form-control" value="{{ status.fields.name }}" disabled>
</div> </div>
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" {% if checkbox.fields.displayed_on_grid %}checked{% endif %} disabled> <input class="form-check-input" type="checkbox" {% if status.fields.displayed_on_grid %}checked{% endif %} disabled>
<span class="form-check-label">Displayed on the Set Grid</span> <span class="form-check-label">Displayed on the Set Grid</span>
</div> </div>
</div> </div>
</div> </div>
<hr class="border-bottom"> <hr class="border-bottom">
<a class="btn btn-danger" href="{{ url_for('admin.admin', open_checkbox=true) }}" role="button"><i class="ri-arrow-left-long-line"></i> Back to the admin</a> <a class="btn btn-danger" href="{{ url_for('admin.admin', open_status=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 checkbox</strong></button> <button type="submit" class="btn btn-danger"><i class="ri-delete-bin-2-line"></i> Delete <strong>the set status</strong></button>
</form> </form>
{{ accordion.footer() }} {{ accordion.footer() }}

View File

@ -8,7 +8,7 @@
data-index="{{ index }}" data-number="{{ item.fields.set }}" data-name="{{ item.fields.name | lower }}" data-parts="{{ item.fields.number_of_parts }}" data-index="{{ index }}" data-number="{{ item.fields.set }}" data-name="{{ item.fields.name | lower }}" data-parts="{{ item.fields.number_of_parts }}"
data-year="{{ item.fields.year }}" data-theme="{{ item.theme.name | lower }}" data-minifigures="{{ item.fields.total_minifigures }}" data-has-minifigures="{{ (item.fields.total_minifigures > 0) | int }}" data-year="{{ item.fields.year }}" data-theme="{{ item.theme.name | lower }}" data-minifigures="{{ item.fields.total_minifigures }}" data-has-minifigures="{{ (item.fields.total_minifigures > 0) | int }}"
data-has-missing="{{ (item.fields.total_missing > 0) | int }}" data-has-missing-instructions="{{ (not (item.instructions | length)) | int }}" data-missing="{{ item.fields.total_missing }}" data-has-missing="{{ (item.fields.total_missing > 0) | int }}" data-has-missing-instructions="{{ (not (item.instructions | length)) | int }}" data-missing="{{ item.fields.total_missing }}"
{% for checkbox in brickset_checkboxes %}data-{{ checkbox.as_dataset() }}="{{ item.fields[checkbox.as_column()] }}" {% endfor %} {% for status in brickset_statuses %}data-{{ status.as_dataset() }}="{{ item.fields[status.as_column()] }}" {% endfor %}
{% endif %} {% endif %}
> >
{{ card.header(item, item.fields.name, solo=solo, identifier=item.fields.set) }} {{ card.header(item, item.fields.name, solo=solo, identifier=item.fields.set) }}
@ -26,11 +26,11 @@
{{ badge.rebrickable(item, solo=solo, last=last) }} {{ badge.rebrickable(item, solo=solo, last=last) }}
{% endif %} {% endif %}
</div> </div>
{% if not tiny and brickset_checkboxes | length %} {% if not tiny and brickset_statuses | length %}
<ul class="list-group list-group-flush card-check border-bottom-0"> <ul class="list-group list-group-flush card-check border-bottom-0">
{% for checkbox in brickset_checkboxes %} {% for status in brickset_statuses %}
<li class="list-group-item {% if not solo %}p-1{% endif %}"> <li class="list-group-item {% if not solo %}p-1{% endif %}">
{{ form.checkbox(checkbox.as_dataset(), item.fields.id, checkbox.fields.name, checkbox.url_for_set_state(item.fields.id), item.fields[checkbox.as_column()], delete=delete) }} {{ form.checkbox(status.as_dataset(), item.fields.id, status.fields.name, status.url_for_set_state(item.fields.id), item.fields[status.as_column()], delete=delete) }}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -22,9 +22,9 @@
<option value="-has-missing">Set is complete</option> <option value="-has-missing">Set is complete</option>
<option value="has-missing">Set has missing pieces</option> <option value="has-missing">Set has missing pieces</option>
<option value="has-missing-instructions">Set has missing instructions</option> <option value="has-missing-instructions">Set has missing instructions</option>
{% for checkbox in brickset_checkboxes %} {% for status in brickset_statuses %}
<option value="{{ checkbox.as_dataset() }}">{{ checkbox.fields.name }}</option> <option value="{{ status.as_dataset() }}">{{ status.fields.name }}</option>
<option value="-{{ checkbox.as_dataset() }}">NOT: {{ checkbox.fields.name }}</option> <option value="-{{ status.as_dataset() }}">NOT: {{ status.fields.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<select id="grid-theme" class="form-select form-select-sm" autocomplete="off"> <select id="grid-theme" class="form-select form-select-sm" autocomplete="off">