2025-01-30 12:17:25 +01:00
|
|
|
import logging
|
|
|
|
from sqlite3 import Row
|
|
|
|
from typing import Any, Self, TYPE_CHECKING
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
from flask import url_for
|
|
|
|
|
|
|
|
from .exceptions import DatabaseException, ErrorException, NotFoundException
|
|
|
|
from .record import BrickRecord
|
|
|
|
from .sql import BrickSQL
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from .set import BrickSet
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
# Lego set metadata (customizable list of entries that can be checked)
|
|
|
|
class BrickMetadata(BrickRecord):
|
|
|
|
kind: str
|
|
|
|
|
|
|
|
# Set state endpoint
|
|
|
|
set_state_endpoint: str
|
|
|
|
|
|
|
|
# Queries
|
|
|
|
delete_query: str
|
|
|
|
insert_query: str
|
|
|
|
select_query: str
|
|
|
|
update_field_query: str
|
|
|
|
update_set_state_query: str
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
/,
|
|
|
|
*,
|
|
|
|
record: Row | dict[str, Any] | None = None,
|
|
|
|
):
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
# Ingest the record if it has one
|
|
|
|
if record is not None:
|
|
|
|
self.ingest(record)
|
|
|
|
|
|
|
|
# SQL column name
|
|
|
|
def as_column(self, /) -> str:
|
2025-01-30 16:35:26 +01:00
|
|
|
return '{kind}_{id}'.format(
|
|
|
|
id=self.fields.id,
|
|
|
|
kind=self.kind.lower()
|
|
|
|
)
|
2025-01-30 12:17:25 +01:00
|
|
|
|
|
|
|
# HTML dataset name
|
|
|
|
def as_dataset(self, /) -> str:
|
|
|
|
return '{id}'.format(
|
|
|
|
id=self.as_column().replace('_', '-')
|
|
|
|
)
|
|
|
|
|
|
|
|
# Delete from database
|
|
|
|
def delete(self, /) -> None:
|
|
|
|
BrickSQL().executescript(
|
|
|
|
self.delete_query,
|
|
|
|
id=self.fields.id,
|
|
|
|
)
|
|
|
|
|
2025-01-30 16:38:11 +01:00
|
|
|
# Grab data from a form
|
|
|
|
def from_form(self, form: dict[str, str], /) -> Self:
|
|
|
|
name = form.get('name', None)
|
|
|
|
|
|
|
|
if name is None or name == '':
|
|
|
|
raise ErrorException('Status name cannot be empty')
|
|
|
|
|
|
|
|
self.fields.name = name
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
2025-01-30 12:17:25 +01:00
|
|
|
# Insert into database
|
|
|
|
def insert(self, /, **context) -> None:
|
|
|
|
self.safe()
|
|
|
|
|
|
|
|
# Generate an ID for the metadata (with underscores to make it
|
|
|
|
# column name friendly)
|
|
|
|
self.fields.id = str(uuid4()).replace('-', '_')
|
|
|
|
|
|
|
|
BrickSQL().executescript(
|
|
|
|
self.insert_query,
|
|
|
|
id=self.fields.id,
|
|
|
|
name=self.fields.safe_name,
|
|
|
|
**context
|
|
|
|
)
|
|
|
|
|
|
|
|
# Rename the entry
|
|
|
|
def rename(self, /) -> None:
|
|
|
|
self.safe()
|
|
|
|
|
|
|
|
self.update_field('name', value=self.fields.name)
|
|
|
|
|
|
|
|
# Make the name "safe"
|
|
|
|
# Security: eh.
|
|
|
|
def safe(self, /) -> None:
|
|
|
|
# Prevent self-ownage with accidental quote escape
|
|
|
|
self.fields.safe_name = self.fields.name.replace("'", "''")
|
|
|
|
|
|
|
|
# URL to change the selected state of this metadata item for a set
|
|
|
|
def url_for_set_state(self, id: str, /) -> str:
|
|
|
|
return url_for(
|
|
|
|
self.set_state_endpoint,
|
|
|
|
id=id,
|
|
|
|
metadata_id=self.fields.id
|
|
|
|
)
|
|
|
|
|
2025-01-30 15:03:16 +01:00
|
|
|
# Select a specific metadata (with an id)
|
2025-01-30 12:17:25 +01:00
|
|
|
def select_specific(self, id: str, /) -> Self:
|
|
|
|
# Save the parameters to the fields
|
|
|
|
self.fields.id = id
|
|
|
|
|
|
|
|
# Load from database
|
|
|
|
if not self.select():
|
|
|
|
raise NotFoundException(
|
|
|
|
'{kind} with ID {id} was not found in the database'.format(
|
|
|
|
kind=self.kind.capitalize(),
|
|
|
|
id=self.fields.id,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
# Update a field
|
|
|
|
def update_field(
|
|
|
|
self,
|
|
|
|
field: str,
|
|
|
|
/,
|
|
|
|
*,
|
|
|
|
json: Any | None = None,
|
|
|
|
value: Any | None = None
|
|
|
|
) -> Any:
|
|
|
|
if value is None:
|
|
|
|
value = json.get('value', None) # type: ignore
|
|
|
|
|
|
|
|
if value is None:
|
|
|
|
raise ErrorException('"{field}" of a {kind} cannot be set to an empty value'.format( # noqa: E501
|
|
|
|
field=field,
|
|
|
|
kind=self.kind
|
|
|
|
))
|
|
|
|
|
|
|
|
if field == 'id' or not hasattr(self.fields, field):
|
|
|
|
raise NotFoundException('"{field}" is not a field of a {kind}'.format( # noqa: E501
|
|
|
|
kind=self.kind,
|
|
|
|
field=field
|
|
|
|
))
|
|
|
|
|
|
|
|
parameters = self.sql_parameters()
|
|
|
|
parameters['value'] = value
|
|
|
|
|
|
|
|
# Update the status
|
|
|
|
rows, _ = BrickSQL().execute_and_commit(
|
|
|
|
self.update_field_query,
|
|
|
|
parameters=parameters,
|
|
|
|
field=field,
|
|
|
|
)
|
|
|
|
|
|
|
|
if rows != 1:
|
|
|
|
raise DatabaseException('Could not update the field "{field}" for {kind} {name} ({id})'.format( # noqa: E501
|
|
|
|
field=field,
|
|
|
|
kind=self.kind,
|
|
|
|
name=self.fields.name,
|
|
|
|
id=self.fields.id,
|
|
|
|
))
|
|
|
|
|
|
|
|
# Info
|
|
|
|
logger.info('{kind} "{name}" ({id}): field "{field}" changed to "{value}"'.format( # noqa: E501
|
|
|
|
kind=self.kind.capitalize(),
|
|
|
|
name=self.fields.name,
|
|
|
|
id=self.fields.id,
|
|
|
|
field=field,
|
|
|
|
value=value,
|
|
|
|
))
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
# Update the selected state of this metadata item for a set
|
2025-01-31 16:34:52 +01:00
|
|
|
def update_set_state(
|
|
|
|
self,
|
|
|
|
brickset: 'BrickSet',
|
|
|
|
/,
|
|
|
|
*,
|
|
|
|
json: Any | None = None,
|
|
|
|
state: bool | None = None,
|
|
|
|
) -> Any:
|
|
|
|
if state is None:
|
|
|
|
state = json.get('value', False) # type: ignore
|
2025-01-30 12:17:25 +01:00
|
|
|
|
|
|
|
parameters = self.sql_parameters()
|
|
|
|
parameters['set_id'] = brickset.fields.id
|
|
|
|
parameters['state'] = state
|
|
|
|
|
|
|
|
# Update the status
|
|
|
|
rows, _ = BrickSQL().execute_and_commit(
|
|
|
|
self.update_set_state_query,
|
|
|
|
parameters=parameters,
|
|
|
|
name=self.as_column(),
|
|
|
|
)
|
|
|
|
|
|
|
|
if rows != 1:
|
|
|
|
raise DatabaseException('Could not update the {kind} "{name}" state for set {set} ({id})'.format( # noqa: E501
|
|
|
|
kind=self.kind,
|
|
|
|
name=self.fields.name,
|
|
|
|
set=brickset.fields.set,
|
|
|
|
id=brickset.fields.id,
|
|
|
|
))
|
|
|
|
|
|
|
|
# Info
|
|
|
|
logger.info('{kind} "{name}" state change to "{state}" for set {set} ({id})'.format( # noqa: E501
|
|
|
|
kind=self.kind,
|
|
|
|
name=self.fields.name,
|
|
|
|
state=state,
|
|
|
|
set=brickset.fields.set,
|
|
|
|
id=brickset.fields.id,
|
|
|
|
))
|
|
|
|
|
|
|
|
return state
|