diff --git a/inventory/__init__.py b/inventory/__init__.py index dd5fa9d..c83b6cd 100644 --- a/inventory/__init__.py +++ b/inventory/__init__.py @@ -26,8 +26,8 @@ def create_app(): # db.create_all() from .routes import main - from .routes.photos import photo_bp + from .routes.images import image_bp app.register_blueprint(main) - app.register_blueprint(photo_bp) + app.register_blueprint(image_bp) return app diff --git a/inventory/models/__init__.py b/inventory/models/__init__.py index d56d500..c1ae94b 100644 --- a/inventory/models/__init__.py +++ b/inventory/models/__init__.py @@ -11,8 +11,8 @@ from .users import User from .work_log import WorkLog from .rooms import Room from .work_note import WorkNote -from .photo import Photo -from .photo_links import worklog_photos +from .image import Image +from .image_links import worklog_images __all__ = [ "db", @@ -25,6 +25,6 @@ __all__ = [ "WorkLog", "Room", "WorkNote", - "Photo", - "worklog_photos" + "Image", + "worklog_images" ] diff --git a/inventory/models/photo.py b/inventory/models/image.py similarity index 75% rename from inventory/models/photo.py rename to inventory/models/image.py index 2f6a17b..ee61d1b 100644 --- a/inventory/models/photo.py +++ b/inventory/models/image.py @@ -10,27 +10,27 @@ from sqlalchemy import Integer, Unicode, DateTime, func from sqlalchemy.orm import Mapped, mapped_column, relationship from . import db -from .photo_links import worklog_photos +from .image_links import worklog_images -class Photo(db.Model): - __tablename__ = 'photos' +class Image(db.Model): + __tablename__ = 'images' id: Mapped[int] = mapped_column(Integer, primary_key=True) filename: Mapped[str] = mapped_column(Unicode(512)) caption: Mapped[str] = mapped_column(Unicode(255), default="") timestamp: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.now(), server_default=func.now()) - inventory: Mapped[Optional['Inventory']] = relationship('Inventory', back_populates='photo') - user: Mapped[Optional['User']] = relationship('User', back_populates='photo') - worklogs: Mapped[List['WorkLog']] = relationship('WorkLog', secondary=worklog_photos, back_populates='photos') + inventory: Mapped[Optional['Inventory']] = relationship('Inventory', back_populates='image') + user: Mapped[Optional['User']] = relationship('User', back_populates='image') + worklogs: Mapped[List['WorkLog']] = relationship('WorkLog', secondary=worklog_images, back_populates='images') def __init__(self, filename: str, caption: Optional[str] = None): self.filename = filename self.caption = caption or "" def __repr__(self): - return f"" + return f"" -class PhotoAttachable: - def attach_photo(self, photo: 'Photo') -> None: - raise NotImplementedError("This model doesn't know how to attach photos.") +class ImageAttachable: + def attach_image(self, image: 'Image') -> None: + raise NotImplementedError("This model doesn't know how to attach images.") diff --git a/inventory/models/photo_links.py b/inventory/models/image_links.py similarity index 53% rename from inventory/models/photo_links.py rename to inventory/models/image_links.py index 0888071..a949979 100644 --- a/inventory/models/photo_links.py +++ b/inventory/models/image_links.py @@ -1,7 +1,7 @@ from .. import db -worklog_photos = db.Table( - 'worklog_photos', +worklog_images = db.Table( + 'worklog_images', db.Column('worklog_id', db.Integer, db.ForeignKey('work_log.id'), primary_key=True), - db.Column('photo_id', db.Integer, db.ForeignKey('photos.id'), primary_key=True), + db.Column('image_id', db.Integer, db.ForeignKey('images.id'), primary_key=True), ) \ No newline at end of file diff --git a/inventory/models/inventory.py b/inventory/models/inventory.py index c8c4b22..6eb0b54 100644 --- a/inventory/models/inventory.py +++ b/inventory/models/inventory.py @@ -1,21 +1,21 @@ from typing import Any, List, Optional, TYPE_CHECKING -from inventory.models.photo import Photo +from inventory.models.image import Image if TYPE_CHECKING: from .brands import Brand from .items import Item from .work_log import WorkLog from .rooms import Room - from .photo import Photo + from .image import Image from sqlalchemy import Boolean, ForeignKey, Identity, Index, Integer, Unicode, DateTime, text from sqlalchemy.orm import Mapped, mapped_column, relationship import datetime from . import db -from .photo import PhotoAttachable +from .image import ImageAttachable -class Inventory(db.Model, PhotoAttachable): +class Inventory(db.Model, ImageAttachable): __tablename__ = 'inventory' __table_args__ = ( Index('Inventory$Barcode', 'barcode'), @@ -34,14 +34,14 @@ class Inventory(db.Model, PhotoAttachable): location_id: Mapped[Optional[str]] = mapped_column(ForeignKey("rooms.id")) barcode: Mapped[Optional[str]] = mapped_column(Unicode(255)) shared: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))')) - photo_id: Mapped[Optional[int]] = mapped_column(ForeignKey('photos.id'), nullable=True) + image_id: Mapped[Optional[int]] = mapped_column(ForeignKey('images.id'), nullable=True) location: Mapped[Optional['Room']] = relationship('Room', back_populates='inventory') owner = relationship('User', back_populates='inventory') brand: Mapped[Optional['Brand']] = relationship('Brand', back_populates='inventory') item: Mapped['Item'] = relationship('Item', back_populates='inventory') work_logs: Mapped[List['WorkLog']] = relationship('WorkLog', back_populates='work_item') - photo: Mapped[Optional['Photo']] = relationship('Photo', back_populates='inventory') + image: Mapped[Optional['Image']] = relationship('Image', back_populates='inventory') def __init__(self, timestamp: datetime.datetime, condition: str, type_id: Optional[int] = None, name: Optional[str] = None, serial: Optional[str] = None, @@ -128,5 +128,5 @@ class Inventory(db.Model, PhotoAttachable): shared=bool(data.get("shared", False)) ) - def attach_photo(self, photo: Photo) -> None: - self.photo = photo + def attach_image(self, image: Image) -> None: + self.image = image diff --git a/inventory/models/users.py b/inventory/models/users.py index 64f95fc..e850139 100644 --- a/inventory/models/users.py +++ b/inventory/models/users.py @@ -3,15 +3,15 @@ if TYPE_CHECKING: from .inventory import Inventory from .rooms import Room from .work_log import WorkLog - from .photo import Photo + from .image import Image from sqlalchemy import Boolean, ForeignKey, Identity, Integer, Unicode, text from sqlalchemy.orm import Mapped, mapped_column, relationship from . import db -from .photo import PhotoAttachable +from .image import ImageAttachable -class User(db.Model, PhotoAttachable): +class User(db.Model, ImageAttachable): __tablename__ = 'users' id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True) @@ -21,14 +21,14 @@ class User(db.Model, PhotoAttachable): first_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) location_id: Mapped[Optional[int]] = mapped_column(ForeignKey("rooms.id"), nullable=True) supervisor_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id")) - photo_id: Mapped[Optional[int]] = mapped_column(ForeignKey('photos.id'), nullable=True) + image_id: Mapped[Optional[int]] = mapped_column(ForeignKey('images.id'), nullable=True) supervisor: Mapped[Optional['User']] = relationship('User', remote_side='User.id', back_populates='subordinates') subordinates: Mapped[List['User']] = relationship('User', back_populates='supervisor') work_logs: Mapped[List['WorkLog']] = relationship('WorkLog', back_populates='contact') location: Mapped[Optional['Room']] = relationship('Room', back_populates='users') inventory: Mapped[List['Inventory']] = relationship('Inventory', back_populates='owner') - photo: Mapped[Optional['Photo']] = relationship('Photo', back_populates='user') + image: Mapped[Optional['Image']] = relationship('Image', back_populates='user') @property def full_name(self) -> str: diff --git a/inventory/models/work_log.py b/inventory/models/work_log.py index 8df655c..ee13d28 100644 --- a/inventory/models/work_log.py +++ b/inventory/models/work_log.py @@ -1,7 +1,7 @@ from typing import Optional, Any, List, TYPE_CHECKING if TYPE_CHECKING: from .inventory import Inventory - from .photo import Photo + from .image import Image from .users import User from .work_note import WorkNote @@ -10,11 +10,11 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship import datetime from . import db -from .photo import PhotoAttachable -from .photo_links import worklog_photos +from .image import ImageAttachable +from .image_links import worklog_images from .work_note import WorkNote -class WorkLog(db.Model, PhotoAttachable): +class WorkLog(db.Model, ImageAttachable): __tablename__ = 'work_log' id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True) @@ -35,7 +35,7 @@ class WorkLog(db.Model, PhotoAttachable): cascade='all, delete-orphan', order_by='WorkNote.timestamp' ) - photos: Mapped[List['Photo']] = relationship('Photo', secondary=worklog_photos, back_populates='worklogs') + images: Mapped[List['Image']] = relationship('Image', secondary=worklog_images, back_populates='worklogs') def __init__( self, diff --git a/inventory/routes/helpers.py b/inventory/routes/helpers.py index 9183d23..1fe5384 100644 --- a/inventory/routes/helpers.py +++ b/inventory/routes/helpers.py @@ -5,7 +5,7 @@ from werkzeug.utils import secure_filename from ..models import Inventory -from ..models.photo import PhotoAttachable +from ..models.image import ImageAttachable inventory_headers = { "Date Entered": lambda i: {"text": i.timestamp.strftime("%Y-%m-%d") if i.timestamp else None}, @@ -80,8 +80,8 @@ def generate_hashed_filename(file_storage, model_name: str) -> str: original_name = secure_filename(file_storage.filename) return f"{model_name}/{sha}_{original_name}" -def get_photo_attachable_class_by_name(name: str): - for cls in PhotoAttachable.__subclasses__(): +def get_image_attachable_class_by_name(name: str): + for cls in ImageAttachable.__subclasses__(): if getattr(cls, '__tablename__', None) == name: return cls return None diff --git a/inventory/routes/photos.py b/inventory/routes/images.py similarity index 52% rename from inventory/routes/photos.py rename to inventory/routes/images.py index bb85559..33ec711 100644 --- a/inventory/routes/photos.py +++ b/inventory/routes/images.py @@ -3,18 +3,18 @@ import posixpath from flask import Blueprint, current_app, request, jsonify -from .helpers import generate_hashed_filename, get_photo_attachable_class_by_name +from .helpers import generate_hashed_filename, get_image_attachable_class_by_name from .. import db -from ..models import Photo +from ..models import Image -photo_bp = Blueprint("photo_api", __name__) +image_bp = Blueprint("image_api", __name__) -def save_photo(file, model: str) -> str: +def save_image(file, model: str) -> str: assert current_app.static_folder hashed_name = generate_hashed_filename(file, model) - rel_path = posixpath.join("uploads", "photos", hashed_name) - abs_path = os.path.join(current_app.static_folder, "uploads", "photos", rel_path) + rel_path = posixpath.join("uploads", "images", hashed_name) + abs_path = os.path.join(current_app.static_folder, "uploads", "images", rel_path) dir_path = os.path.dirname(abs_path) if not os.path.exists(dir_path): @@ -29,8 +29,8 @@ def save_photo(file, model: str) -> str: file.save(abs_path) return rel_path -@photo_bp.route("/api/photos", methods=["POST"]) -def upload_photo(): +@image_bp.route("/api/images", methods=["POST"]) +def upload_image(): file = request.files.get("file") model = request.form.get("model") model_id = request.form.get("model_id") @@ -39,9 +39,9 @@ def upload_photo(): if not file or not model or not model_id: return jsonify({"success": False, "error": "Missing file, model, or model_id"}), 400 - ModelClass = get_photo_attachable_class_by_name(model) + ModelClass = get_image_attachable_class_by_name(model) if not ModelClass: - return jsonify({"success": False, "error": f"Model '{model}' does not support photo attachments."}), 400 + return jsonify({"success": False, "error": f"Model '{model}' does not support image attachments."}), 400 try: model_id = int(model_id) @@ -49,34 +49,40 @@ def upload_photo(): return jsonify({"success": False, "error": "model_id must be an integer"}), 400 # Save file - rel_path = save_photo(file, model) + rel_path = save_image(file, model) print(rel_path) - # Create Photo row - photo = Photo(filename=rel_path, caption=caption) - db.session.add(photo) + # Create Image row + image = Image(filename=rel_path, caption=caption) + db.session.add(image) - # Attach photo to model + # Attach image to model target = db.session.get(ModelClass, model_id) if not target: return jsonify({"success": False, "error": f"No {model} found with ID {model_id}"}), 404 - target.attach_photo(photo) + target.attach_image(image) db.session.commit() - return jsonify({"success": True, "id": photo.id}), 201 + return jsonify({"success": True, "id": image.id}), 201 -@photo_bp.route("/api/photos/", methods=["GET"]) -def get_photo(photo_id: int): - photo = db.session.get(Photo, photo_id) - if not photo: - return jsonify({"success": False, "error": f"No photo found with ID {photo_id}"}), 404 +@image_bp.route("/api/images/", methods=["GET"]) +def get_image(image_id: int): + image = db.session.get(Image, image_id) + if not image: + return jsonify({"success": False, "error": f"No image found with ID {image_id}"}), 404 return jsonify({ "success": True, - "id": photo.id, - "filename": photo.filename, - "caption": photo.caption, - "timestamp": photo.timestamp.isoformat() if photo.timestamp else None, - "url": f"/static/{photo.filename}" + "id": image.id, + "filename": image.filename, + "caption": image.caption, + "timestamp": image.timestamp.isoformat() if image.timestamp else None, + "url": f"/static/{image.filename}" }) + +@image_bp.route("/api/images/", methods=["DELETE"]) +def delete_image(image_id): + image = db.session.get(Image, image_id) + if not image: + return jsonify({"success": False, "error": "Image not found"}) diff --git a/inventory/static/js/widget.js b/inventory/static/js/widget.js index e0dfb3e..be8eea6 100644 --- a/inventory/static/js/widget.js +++ b/inventory/static/js/widget.js @@ -59,7 +59,7 @@ const ImageWidget = (() => { console.log(form); const formData = new FormData(form); - fetch("/api/photos", { + fetch("/api/images", { method: "POST", body: formData }).then(async response => { @@ -76,7 +76,7 @@ const ImageWidget = (() => { } return response.json(); }).then(data => { - renderToast({ message: `Photo uploaded.`, type: "success" }); + renderToast({ message: `Image uploaded.`, type: "success" }); location.reload(); }).catch(err => { const msg = typeof err === "object" && err.error ? err.error : err.toString(); diff --git a/inventory/templates/inventory.html b/inventory/templates/inventory.html index 518e2f8..699700f 100644 --- a/inventory/templates/inventory.html +++ b/inventory/templates/inventory.html @@ -24,9 +24,6 @@ -
- {{ images.render_image(item.id, item.photo) }} -
@@ -131,6 +128,9 @@ entry_route='worklog_entry', title='Work Log') }}
{% endif %} +
+ {{ images.render_image(item.id, item.image) }} +
{% endblock %}