From 05dc158719366a1a6e0c62b50ee3aefe45bf57ee Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 18 Nov 2021 12:54:37 +0100 Subject: [PATCH 1/5] [cleanup] PEP8 cleanup --- flaschengeist/app.py | 3 +- flaschengeist/config.py | 9 +++++- flaschengeist/controller/imageController.py | 18 +++++------ flaschengeist/database.py | 1 + flaschengeist/models/__init__.py | 2 +- flaschengeist/models/image.py | 3 +- flaschengeist/models/user.py | 4 ++- flaschengeist/plugins/__init__.py | 2 +- flaschengeist/plugins/auth_ldap/__init__.py | 16 +++++----- flaschengeist/plugins/auth_plain/__init__.py | 8 ++++- .../plugins/balance/balance_controller.py | 9 +++++- flaschengeist/plugins/events/models.py | 18 +++++++++-- flaschengeist/plugins/pricelist/models.py | 6 +++- .../plugins/pricelist/pricelist_controller.py | 31 ++++++++++++++++--- setup.py | 11 ++++++- tests/conftest.py | 8 ++++- 16 files changed, 115 insertions(+), 34 deletions(-) diff --git a/flaschengeist/app.py b/flaschengeist/app.py index 0012c6f..8d15c11 100644 --- a/flaschengeist/app.py +++ b/flaschengeist/app.py @@ -52,7 +52,8 @@ def __load_plugins(app): app.register_blueprint(plugin.blueprint) except: logger.error( - f"Plugin {entry_point.name} was enabled, but could not be loaded due to an error.", exc_info=True + f"Plugin {entry_point.name} was enabled, but could not be loaded due to an error.", + exc_info=True, ) del plugin continue diff --git a/flaschengeist/config.py b/flaschengeist/config.py index 47af03f..48c71b2 100644 --- a/flaschengeist/config.py +++ b/flaschengeist/config.py @@ -69,7 +69,14 @@ def configure_app(app, test_config=None): read_configuration(test_config) # Always enable this builtin plugins! - update_dict(config, {"auth": {"enabled": True}, "roles": {"enabled": True}, "users": {"enabled": True}}) + update_dict( + config, + { + "auth": {"enabled": True}, + "roles": {"enabled": True}, + "users": {"enabled": True}, + }, + ) if "secret_key" not in config["FLASCHENGEIST"]: logger.warning("No secret key was configured, please configure one for production systems!") diff --git a/flaschengeist/controller/imageController.py b/flaschengeist/controller/imageController.py index 5139d56..26bfd2d 100644 --- a/flaschengeist/controller/imageController.py +++ b/flaschengeist/controller/imageController.py @@ -13,7 +13,7 @@ from flaschengeist.config import config def check_mimetype(mime: str): - return mime in config["FILES"].get('allowed_mimetypes', []) + return mime in config["FILES"].get("allowed_mimetypes", []) def send_image(id: int = None, image: Image = None): @@ -32,10 +32,10 @@ def send_thumbnail(id: int = None, image: Image = None): if not image.thumbnail_: with PImage.open(image.open()) as im: im.thumbnail(tuple(config["FILES"].get("thumbnail_size"))) - s = image.path_.split('.') - s.insert(len(s)-1, 'thumbnail') - im.save('.'.join(s)) - image.thumbnail_ = '.'.join(s) + s = image.path_.split(".") + s.insert(len(s) - 1, "thumbnail") + im.save(".".join(s)) + image.thumbnail_ = ".".join(s) db.session.commit() return send_file(image.thumbnail_, mimetype=image.mimetype_, download_name=image.filename_) @@ -45,10 +45,10 @@ def upload_image(file: FileStorage): raise UnprocessableEntity path = Path(config["FILES"].get("data_path")) / str(date.today().year) - path.mkdir(mode=int('0700', 8), parents=True, exist_ok=True) - - if file.filename.count('.') < 1: - name = secure_filename(file.filename + '.' + file.mimetype.split('/')[-1]) + path.mkdir(mode=int("0700", 8), parents=True, exist_ok=True) + + if file.filename.count(".") < 1: + name = secure_filename(file.filename + "." + file.mimetype.split("/")[-1]) else: name = secure_filename(file.filename) img = Image(mimetype_=file.mimetype, filename_=name) diff --git a/flaschengeist/database.py b/flaschengeist/database.py index 85fc3e1..410e0cf 100644 --- a/flaschengeist/database.py +++ b/flaschengeist/database.py @@ -6,5 +6,6 @@ db = SQLAlchemy() def case_sensitive(s): if db.session.bind.dialect.name == "mysql": from sqlalchemy import func + return func.binary(s) return s diff --git a/flaschengeist/models/__init__.py b/flaschengeist/models/__init__.py index 4eca8e3..acffc1a 100644 --- a/flaschengeist/models/__init__.py +++ b/flaschengeist/models/__init__.py @@ -44,7 +44,7 @@ class ModelSerializeMixin: class Serial(TypeDecorator): """Same as MariaDB Serial used for IDs""" - cache_ok=True + cache_ok = True impl = BigInteger().with_variant(mysql.BIGINT(unsigned=True), "mysql").with_variant(sqlite.INTEGER, "sqlite") diff --git a/flaschengeist/models/image.py b/flaschengeist/models/image.py index 1242c2e..7cf9967 100644 --- a/flaschengeist/models/image.py +++ b/flaschengeist/models/image.py @@ -6,6 +6,7 @@ from pathlib import Path from . import ModelSerializeMixin, Serial from ..database import db + class Image(db.Model, ModelSerializeMixin): __tablename__ = "image" id: int = db.Column("id", Serial, primary_key=True) @@ -18,7 +19,7 @@ class Image(db.Model, ModelSerializeMixin): return open(self.path_, "rb") -@event.listens_for(Image, 'before_delete') +@event.listens_for(Image, "before_delete") def clear_file(mapper, connection, target: Image): if target.path_: p = Path(target.path_) diff --git a/flaschengeist/models/user.py b/flaschengeist/models/user.py index 84116a6..16b901c 100644 --- a/flaschengeist/models/user.py +++ b/flaschengeist/models/user.py @@ -67,7 +67,9 @@ class User(db.Model, ModelSerializeMixin): sessions_ = db.relationship("Session", back_populates="user_") _attributes = db.relationship( - "_UserAttribute", collection_class=attribute_mapped_collection("name"), cascade="all, delete" + "_UserAttribute", + collection_class=attribute_mapped_collection("name"), + cascade="all, delete", ) @property diff --git a/flaschengeist/plugins/__init__.py b/flaschengeist/plugins/__init__.py index fabc4e2..da5bc84 100644 --- a/flaschengeist/plugins/__init__.py +++ b/flaschengeist/plugins/__init__.py @@ -33,7 +33,7 @@ class Plugin: blueprint = None # You have to override permissions = [] # You have to override - id = "dev.flaschengeist.plugin" # You have to override + id = "dev.flaschengeist.plugin" # You have to override name = "plugin" # You have to override models = None # You have to override diff --git a/flaschengeist/plugins/auth_ldap/__init__.py b/flaschengeist/plugins/auth_ldap/__init__.py index 3ceb6e0..846dc5d 100644 --- a/flaschengeist/plugins/auth_ldap/__init__.py +++ b/flaschengeist/plugins/auth_ldap/__init__.py @@ -96,12 +96,14 @@ class AuthLDAP(AuthPlugin): display_name=user.display_name, base_dn=self.base_dn, ) - attributes.update({ - "sn": user.lastname, - "givenName": user.firstname, - "uid": user.userid, - "userPassword": self.__hash(password), - }) + attributes.update( + { + "sn": user.lastname, + "givenName": user.firstname, + "uid": user.userid, + "userPassword": self.__hash(password), + } + ) ldap_conn.add(dn, self.object_classes, attributes) self._set_roles(user) except (LDAPPasswordIsMandatoryError, LDAPBindError): @@ -145,7 +147,7 @@ class AuthLDAP(AuthPlugin): if "jpegPhoto" in r and len(r["jpegPhoto"]) > 0: avatar = _Avatar() avatar.mimetype = "image/jpeg" - avatar.binary = bytearray(r['jpegPhoto'][0]) + avatar.binary = bytearray(r["jpegPhoto"][0]) return avatar else: raise NotFound diff --git a/flaschengeist/plugins/auth_plain/__init__.py b/flaschengeist/plugins/auth_plain/__init__.py index c8de367..3b2b8f7 100644 --- a/flaschengeist/plugins/auth_plain/__init__.py +++ b/flaschengeist/plugins/auth_plain/__init__.py @@ -19,7 +19,13 @@ class AuthPlain(AuthPlugin): if User.query.first() is None: logger.info("Installing admin user") role = Role(name="Superuser", permissions=Permission.query.all()) - admin = User(userid="admin", firstname="Admin", lastname="Admin", mail="", roles_=[role]) + admin = User( + userid="admin", + firstname="Admin", + lastname="Admin", + mail="", + roles_=[role], + ) self.modify_user(admin, None, "admin") db.session.add(admin) db.session.commit() diff --git a/flaschengeist/plugins/balance/balance_controller.py b/flaschengeist/plugins/balance/balance_controller.py index 58b1152..1d6174f 100644 --- a/flaschengeist/plugins/balance/balance_controller.py +++ b/flaschengeist/plugins/balance/balance_controller.py @@ -114,7 +114,14 @@ def get_transaction(transaction_id) -> Transaction: def get_transactions( - user, start=None, end=None, limit=None, offset=None, show_reversal=False, show_cancelled=True, descending=False + user, + start=None, + end=None, + limit=None, + offset=None, + show_reversal=False, + show_cancelled=True, + descending=False, ): count = None query = Transaction.query.filter((Transaction.sender_ == user) | (Transaction.receiver_ == user)) diff --git a/flaschengeist/plugins/events/models.py b/flaschengeist/plugins/events/models.py index daf308a..51fd4a7 100644 --- a/flaschengeist/plugins/events/models.py +++ b/flaschengeist/plugins/events/models.py @@ -39,7 +39,13 @@ class Service(db.Model, ModelSerializeMixin): is_backup: bool = db.Column(db.Boolean, default=False) value: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False) - _job_id = db.Column("job_id", Serial, db.ForeignKey(f"{_table_prefix_}job.id"), nullable=False, primary_key=True) + _job_id = db.Column( + "job_id", + Serial, + db.ForeignKey(f"{_table_prefix_}job.id"), + nullable=False, + primary_key=True, + ) _user_id = db.Column("user_id", Serial, db.ForeignKey("user.id"), nullable=False, primary_key=True) user_: User = db.relationship("User") @@ -83,11 +89,17 @@ class Event(db.Model, ModelSerializeMixin): type: Union[EventType, int] = db.relationship("EventType") is_template: bool = db.Column(db.Boolean, default=False) jobs: list[Job] = db.relationship( - "Job", back_populates="event_", cascade="all,delete,delete-orphan", order_by="[Job.start, Job.end]" + "Job", + back_populates="event_", + cascade="all,delete,delete-orphan", + order_by="[Job.start, Job.end]", ) # Protected for internal use _type_id = db.Column( - "type_id", Serial, db.ForeignKey(f"{_table_prefix_}event_type.id", ondelete="CASCADE"), nullable=False + "type_id", + Serial, + db.ForeignKey(f"{_table_prefix_}event_type.id", ondelete="CASCADE"), + nullable=False, ) diff --git a/flaschengeist/plugins/pricelist/models.py b/flaschengeist/plugins/pricelist/models.py index 55b1152..630766d 100644 --- a/flaschengeist/plugins/pricelist/models.py +++ b/flaschengeist/plugins/pricelist/models.py @@ -132,7 +132,11 @@ class DrinkPriceVolume(db.Model, ModelSerializeMixin): _prices: list[DrinkPrice] = db.relationship( DrinkPrice, back_populates="_volume", cascade="all,delete,delete-orphan" ) - ingredients: list[Ingredient] = db.relationship("Ingredient", foreign_keys=Ingredient.volume_id, cascade="all,delete,delete-orphan") + ingredients: list[Ingredient] = db.relationship( + "Ingredient", + foreign_keys=Ingredient.volume_id, + cascade="all,delete,delete-orphan", + ) def __repr__(self): return f"DrinkPriceVolume({self.id},{self.drink_id},{self.volume},{self.prices})" diff --git a/flaschengeist/plugins/pricelist/pricelist_controller.py b/flaschengeist/plugins/pricelist/pricelist_controller.py index d42085a..bd89518 100644 --- a/flaschengeist/plugins/pricelist/pricelist_controller.py +++ b/flaschengeist/plugins/pricelist/pricelist_controller.py @@ -5,11 +5,21 @@ from flaschengeist import logger from flaschengeist.database import db from flaschengeist.utils.decorators import extract_session -from .models import Drink, DrinkPrice, Ingredient, Tag, DrinkType, DrinkPriceVolume, DrinkIngredient, ExtraIngredient +from .models import ( + Drink, + DrinkPrice, + Ingredient, + Tag, + DrinkType, + DrinkPriceVolume, + DrinkIngredient, + ExtraIngredient, +) from .permissions import EDIT_VOLUME, EDIT_PRICE, EDIT_INGREDIENTS_DRINK import flaschengeist.controller.imageController as image_controller + def update(): db.session.commit() @@ -130,7 +140,14 @@ def _create_public_drink(drink): def get_drinks( - name=None, public=False, limit=None, offset=None, search_name=None, search_key=None, ingredient=False, receipt=None + name=None, + public=False, + limit=None, + offset=None, + search_name=None, + search_key=None, + ingredient=False, + receipt=None, ): count = None if name: @@ -176,7 +193,13 @@ def get_drinks( def get_pricelist( - public=False, limit=None, offset=None, search_name=None, search_key=None, sortBy=None, descending=False + public=False, + limit=None, + offset=None, + search_name=None, + search_key=None, + sortBy=None, + descending=False, ): count = None query = DrinkPrice.query @@ -300,7 +323,7 @@ def update_drink(identifier, data): else: drink = get_drink(identifier) for key, value in data.items(): - if hasattr(drink, key) and key != 'has_image': + if hasattr(drink, key) and key != "has_image": setattr(drink, key, value if value != "" else None) if drink_type: diff --git a/setup.py b/setup.py index d8ccc0f..6d80097 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,16 @@ class DocsCommand(Command): def run(self): """Run command.""" - command = ["python", "-m", "pdoc", "--skip-errors", "--html", "--output-dir", self.output, "flaschengeist"] + command = [ + "python", + "-m", + "pdoc", + "--skip-errors", + "--html", + "--output-dir", + self.output, + "flaschengeist", + ] self.announce( "Running command: %s" % str(command), ) diff --git a/tests/conftest.py b/tests/conftest.py index b99d8d6..5290fd8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,7 +22,13 @@ with open(os.path.join(os.path.dirname(__file__), "data.sql"), "r") as f: @pytest.fixture def app(): db_fd, db_path = tempfile.mkstemp() - app = create_app({"TESTING": True, "DATABASE": {"file_path": f"/{db_path}"}, "LOGGING": {"level": "DEBUG"}}) + app = create_app( + { + "TESTING": True, + "DATABASE": {"file_path": f"/{db_path}"}, + "LOGGING": {"level": "DEBUG"}, + } + ) with app.app_context(): install_all() engine = database.db.engine From 7cb31bf60e8dea7f0e3ae1925ed9f181372b3b0e Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 18 Nov 2021 12:57:18 +0100 Subject: [PATCH 2/5] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 886770a..aef08d3 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,8 @@ dmypy.json .vscode/ *.log +data/ + # config flaschengeist/flaschengeist.toml From 48933cdf5f81887aab9187c90ed12b31008ecc0a Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 18 Nov 2021 23:02:03 +0100 Subject: [PATCH 3/5] [core] Minor fixes --- flaschengeist/config.py | 2 ++ flaschengeist/logging.toml | 2 +- flaschengeist/models/__init__.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/flaschengeist/config.py b/flaschengeist/config.py index 48c71b2..524ed6e 100644 --- a/flaschengeist/config.py +++ b/flaschengeist/config.py @@ -68,6 +68,8 @@ def configure_app(app, test_config=None): global config read_configuration(test_config) + configure_logger() + # Always enable this builtin plugins! update_dict( config, diff --git a/flaschengeist/logging.toml b/flaschengeist/logging.toml index 3af9f99..c9a5647 100644 --- a/flaschengeist/logging.toml +++ b/flaschengeist/logging.toml @@ -6,7 +6,7 @@ disable_existing_loggers = false [formatters] [formatters.simple] - format = "%(asctime)s - %(name)s (%(levelname)s) - %(message)s" + format = "%(asctime)s - %(levelname)s - %(message)s" [formatters.extended] format = "%(asctime)s — %(filename)s - %(funcName)s - %(lineno)d - %(threadName)s - %(name)s — %(levelname)s — %(message)s" diff --git a/flaschengeist/models/__init__.py b/flaschengeist/models/__init__.py index acffc1a..339a598 100644 --- a/flaschengeist/models/__init__.py +++ b/flaschengeist/models/__init__.py @@ -61,6 +61,7 @@ class UtcDateTime(TypeDecorator): aware value, even with SQLite or MySQL. """ + cache_ok = True impl = DateTime(timezone=True) @staticmethod From d00c603697896c9aa3f74c9c95f8bc5b86cbbc76 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 18 Nov 2021 23:06:03 +0100 Subject: [PATCH 4/5] [events] Allow server side pageination --- .../plugins/events/event_controller.py | 48 ++++++++++++++----- flaschengeist/plugins/events/routes.py | 34 +++++++++---- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/flaschengeist/plugins/events/event_controller.py b/flaschengeist/plugins/events/event_controller.py index fb50655..b5f7410 100644 --- a/flaschengeist/plugins/events/event_controller.py +++ b/flaschengeist/plugins/events/event_controller.py @@ -124,7 +124,14 @@ def get_templates(): return Event.query.filter(Event.is_template == True).all() -def get_events(start: Optional[datetime] = None, end=None, with_backup=False): +def get_events( + start: Optional[datetime] = None, + end: Optional[datetime] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + descending: Optional[bool] = False, + with_backup=False, +): """Query events which start from begin until end Args: start (datetime): Earliest start @@ -138,6 +145,14 @@ def get_events(start: Optional[datetime] = None, end=None, with_backup=False): query = query.filter(start <= Event.start) if end is not None: query = query.filter(Event.start < end) + if descending: + query = query.order_by(Event.start.desc()) + else: + query = query.order_by(Event.start) + if limit is not None: + query = query.limit(limit) + if offset is not None and offset > 0: + query = query.offset(offset) events = query.all() if not with_backup: for event in events: @@ -188,7 +203,13 @@ def get_job(job_slot_id, event_id): def add_job(event, job_type, required_services, start, end=None, comment=None): - job = Job(required_services=required_services, type=job_type, start=start, end=end, comment=comment) + job = Job( + required_services=required_services, + type=job_type, + start=start, + end=end, + comment=comment, + ) event.jobs.append(job) update() return job @@ -198,7 +219,10 @@ def update(): try: db.session.commit() except IntegrityError: - logger.debug("Error, looks like a Job with that type already exists on an event", exc_info=True) + logger.debug( + "Error, looks like a Job with that type already exists on an event", + exc_info=True, + ) raise BadRequest() @@ -222,21 +246,19 @@ def assign_job(job: Job, user, value): def unassign_job(job: Job = None, user=None, service=None, notify=False): if service is None: - assert(job is not None and user is not None) + assert job is not None and user is not None service = Service.query.get((job.id, user.id_)) else: user = service.user_ if not service: raise BadRequest - + event_id = service.job_.event_id_ - + db.session.delete(service) db.session.commit() if notify: - EventPlugin.plugin.notify( - user, "Your assignmet was cancelled", {"event_id": event_id} - ) + EventPlugin.plugin.notify(user, "Your assignmet was cancelled", {"event_id": event_id}) @scheduled @@ -249,7 +271,9 @@ def assign_backups(): for service in services: if service.job_.start <= now or service.job_.is_full(): EventPlugin.plugin.notify( - service.user_, "Your backup assignment was cancelled.", {"event_id": service.job_.event_id_} + service.user_, + "Your backup assignment was cancelled.", + {"event_id": service.job_.event_id_}, ) logger.debug(f"Service is outdated or full, removing. {service.serialize()}") db.session.delete(service) @@ -257,6 +281,8 @@ def assign_backups(): service.is_backup = False logger.debug(f"Service not full, assigning backup. {service.serialize()}") EventPlugin.plugin.notify( - service.user_, "Your backup assignment was accepted.", {"event_id": service.job_.event_id_} + service.user_, + "Your backup assignment was accepted.", + {"event_id": service.job_.event_id_}, ) db.session.commit() diff --git a/flaschengeist/plugins/events/routes.py b/flaschengeist/plugins/events/routes.py index fce9416..6e0ec96 100644 --- a/flaschengeist/plugins/events/routes.py +++ b/flaschengeist/plugins/events/routes.py @@ -169,7 +169,8 @@ def get_event(event_id, current_session): JSON encoded event object """ event = event_controller.get_event( - event_id, with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP) + event_id, + with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP), ) return jsonify(event) @@ -177,17 +178,21 @@ def get_event(event_id, current_session): @EventPlugin.blueprint.route("/events", methods=["GET"]) @login_required() def get_filtered_events(current_session): - begin = request.args.get("from") - if begin is not None: - begin = from_iso_format(begin) - end = request.args.get("to") - if end is not None: - end = from_iso_format(end) + begin = request.args.get("from", type=from_iso_format) + end = request.args.get("to", type=from_iso_format) + limit = request.args.get("limit", type=int) + offset = request.args.get("offset", type=int) + descending = "descending" in request.args if begin is None and end is None: begin = datetime.now() return jsonify( event_controller.get_events( - begin, end, with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP) + start=begin, + end=end, + limit=limit, + offset=offset, + descending=descending, + with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP), ) ) @@ -222,7 +227,9 @@ def get_events(current_session, year=datetime.now().year, month=datetime.now().m end = datetime(year=year, month=month + 1, day=1, tzinfo=timezone.utc) events = event_controller.get_events( - begin, end, with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP) + begin, + end, + with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP), ) return jsonify(events) except ValueError: @@ -243,7 +250,14 @@ def _add_job(event, data): raise BadRequest("Missing or invalid POST parameter") job_type = event_controller.get_job_type(job_type) - event_controller.add_job(event, job_type, required_services, start, end, comment=data.get("comment", None)) + event_controller.add_job( + event, + job_type, + required_services, + start, + end, + comment=data.get("comment", None), + ) @EventPlugin.blueprint.route("/events", methods=["POST"]) From 795475fe155ca511bcf51d48365a136f4e838c63 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Fri, 19 Nov 2021 13:32:54 +0100 Subject: [PATCH 5/5] [pricelist] Delete old images --- flaschengeist/plugins/pricelist/pricelist_controller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flaschengeist/plugins/pricelist/pricelist_controller.py b/flaschengeist/plugins/pricelist/pricelist_controller.py index bd89518..872b412 100644 --- a/flaschengeist/plugins/pricelist/pricelist_controller.py +++ b/flaschengeist/plugins/pricelist/pricelist_controller.py @@ -525,7 +525,7 @@ def delete_extra_ingredient(identifier): def save_drink_picture(identifier, file): - drink = get_drink(identifier) + drink = delete_drink_picture(identifier) drink.image_ = image_controller.upload_image(file) db.session.commit() return drink @@ -533,6 +533,8 @@ def save_drink_picture(identifier, file): def delete_drink_picture(identifier): drink = get_drink(identifier) - drink.image = None + if drink.image_: + db.session.delete(drink.image_) + drink.image_ = None db.session.commit() return drink