"""A saved set of marker positions, so a bulk change can be undone. Positions had no history. A landmark transform rewrites every marker on a level in one statement, and without a way back the honest instruction would be "take a database backup first" - which nobody does before clicking a button in a UI, so in practice the feature would either not be used or be used once, badly. One row holds the whole set as JSON rather than a row per asset. A snapshot is read back whole or not at all, so restore stays a single statement, and there is no orphan-child case to reason about. """ import json from shopdb.extensions import db from .base import BaseModel class MapPositionSnapshot(BaseModel): __tablename__ = 'mappositionsnapshots' snapshotid = db.Column(db.Integer, primary_key=True) # Which level the operation targeted. Nullable because a snapshot may span # levels (moving assets between them), and then no single level owns it. levelid = db.Column(db.Integer, nullable=True) reason = db.Column(db.String(255), nullable=True) assetcount = db.Column(db.Integer, nullable=False, default=0) positionsjson = db.Column(db.Text, nullable=False) # Set when this snapshot has been restored, so the history reads as what # happened rather than as a list of identical-looking saves. restoredat = db.Column(db.DateTime, nullable=True) createdby = db.Column(db.String(100), nullable=True) def __repr__(self): return f"" @property def positions(self): try: return json.loads(self.positionsjson or '[]') except ValueError: return [] def to_dict(self): """Metadata only. The positions themselves are large and nobody browsing a list of snapshots wants them.""" data = super().to_dict() data.pop('positionsjson', None) return data @classmethod def capture(cls, assets, reason, levelid=None, createdby=None): """Record the CURRENT positions of these assets, before they change. Includes levelid and mapverifiedat, not just the coordinates: a restore has to put a marker back on the level it was on and with the review state it had, or undo would silently mark reviewed work as unreviewed. """ rows = [{ 'assetid': asset.assetid, 'mapx': asset.mapx, 'mapy': asset.mapy, 'levelid': asset.levelid, 'mapverifiedat': asset.mapverifiedat.isoformat() if asset.mapverifiedat else None, } for asset in assets] snapshot = cls( levelid=levelid, reason=reason, assetcount=len(rows), positionsjson=json.dumps(rows, separators=(',', ':')), createdby=createdby, ) db.session.add(snapshot) db.session.flush() return snapshot