Rename "photo" to "image."
This commit is contained in:
parent
84db8592cb
commit
7d96839af8
11 changed files with 78 additions and 72 deletions
|
@ -26,8 +26,8 @@ def create_app():
|
||||||
# db.create_all()
|
# db.create_all()
|
||||||
|
|
||||||
from .routes import main
|
from .routes import main
|
||||||
from .routes.photos import photo_bp
|
from .routes.images import image_bp
|
||||||
app.register_blueprint(main)
|
app.register_blueprint(main)
|
||||||
app.register_blueprint(photo_bp)
|
app.register_blueprint(image_bp)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -11,8 +11,8 @@ from .users import User
|
||||||
from .work_log import WorkLog
|
from .work_log import WorkLog
|
||||||
from .rooms import Room
|
from .rooms import Room
|
||||||
from .work_note import WorkNote
|
from .work_note import WorkNote
|
||||||
from .photo import Photo
|
from .image import Image
|
||||||
from .photo_links import worklog_photos
|
from .image_links import worklog_images
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"db",
|
"db",
|
||||||
|
@ -25,6 +25,6 @@ __all__ = [
|
||||||
"WorkLog",
|
"WorkLog",
|
||||||
"Room",
|
"Room",
|
||||||
"WorkNote",
|
"WorkNote",
|
||||||
"Photo",
|
"Image",
|
||||||
"worklog_photos"
|
"worklog_images"
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,27 +10,27 @@ from sqlalchemy import Integer, Unicode, DateTime, func
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
from .photo_links import worklog_photos
|
from .image_links import worklog_images
|
||||||
|
|
||||||
class Photo(db.Model):
|
class Image(db.Model):
|
||||||
__tablename__ = 'photos'
|
__tablename__ = 'images'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||||
filename: Mapped[str] = mapped_column(Unicode(512))
|
filename: Mapped[str] = mapped_column(Unicode(512))
|
||||||
caption: Mapped[str] = mapped_column(Unicode(255), default="")
|
caption: Mapped[str] = mapped_column(Unicode(255), default="")
|
||||||
timestamp: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.now(), server_default=func.now())
|
timestamp: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.now(), server_default=func.now())
|
||||||
|
|
||||||
inventory: Mapped[Optional['Inventory']] = relationship('Inventory', back_populates='photo')
|
inventory: Mapped[Optional['Inventory']] = relationship('Inventory', back_populates='image')
|
||||||
user: Mapped[Optional['User']] = relationship('User', back_populates='photo')
|
user: Mapped[Optional['User']] = relationship('User', back_populates='image')
|
||||||
worklogs: Mapped[List['WorkLog']] = relationship('WorkLog', secondary=worklog_photos, back_populates='photos')
|
worklogs: Mapped[List['WorkLog']] = relationship('WorkLog', secondary=worklog_images, back_populates='images')
|
||||||
|
|
||||||
def __init__(self, filename: str, caption: Optional[str] = None):
|
def __init__(self, filename: str, caption: Optional[str] = None):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.caption = caption or ""
|
self.caption = caption or ""
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Photo(id={self.id}, filename={self.filename})>"
|
return f"<Image(id={self.id}, filename={self.filename})>"
|
||||||
|
|
||||||
class PhotoAttachable:
|
class ImageAttachable:
|
||||||
def attach_photo(self, photo: 'Photo') -> None:
|
def attach_image(self, image: 'Image') -> None:
|
||||||
raise NotImplementedError("This model doesn't know how to attach photos.")
|
raise NotImplementedError("This model doesn't know how to attach images.")
|
|
@ -1,7 +1,7 @@
|
||||||
from .. import db
|
from .. import db
|
||||||
|
|
||||||
worklog_photos = db.Table(
|
worklog_images = db.Table(
|
||||||
'worklog_photos',
|
'worklog_images',
|
||||||
db.Column('worklog_id', db.Integer, db.ForeignKey('work_log.id'), primary_key=True),
|
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),
|
||||||
)
|
)
|
|
@ -1,21 +1,21 @@
|
||||||
from typing import Any, List, Optional, TYPE_CHECKING
|
from typing import Any, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from inventory.models.photo import Photo
|
from inventory.models.image import Image
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .brands import Brand
|
from .brands import Brand
|
||||||
from .items import Item
|
from .items import Item
|
||||||
from .work_log import WorkLog
|
from .work_log import WorkLog
|
||||||
from .rooms import Room
|
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 import Boolean, ForeignKey, Identity, Index, Integer, Unicode, DateTime, text
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
from .photo import PhotoAttachable
|
from .image import ImageAttachable
|
||||||
|
|
||||||
class Inventory(db.Model, PhotoAttachable):
|
class Inventory(db.Model, ImageAttachable):
|
||||||
__tablename__ = 'inventory'
|
__tablename__ = 'inventory'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
Index('Inventory$Barcode', 'barcode'),
|
Index('Inventory$Barcode', 'barcode'),
|
||||||
|
@ -34,14 +34,14 @@ class Inventory(db.Model, PhotoAttachable):
|
||||||
location_id: Mapped[Optional[str]] = mapped_column(ForeignKey("rooms.id"))
|
location_id: Mapped[Optional[str]] = mapped_column(ForeignKey("rooms.id"))
|
||||||
barcode: Mapped[Optional[str]] = mapped_column(Unicode(255))
|
barcode: Mapped[Optional[str]] = mapped_column(Unicode(255))
|
||||||
shared: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))'))
|
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')
|
location: Mapped[Optional['Room']] = relationship('Room', back_populates='inventory')
|
||||||
owner = relationship('User', back_populates='inventory')
|
owner = relationship('User', back_populates='inventory')
|
||||||
brand: Mapped[Optional['Brand']] = relationship('Brand', back_populates='inventory')
|
brand: Mapped[Optional['Brand']] = relationship('Brand', back_populates='inventory')
|
||||||
item: Mapped['Item'] = relationship('Item', back_populates='inventory')
|
item: Mapped['Item'] = relationship('Item', back_populates='inventory')
|
||||||
work_logs: Mapped[List['WorkLog']] = relationship('WorkLog', back_populates='work_item')
|
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,
|
def __init__(self, timestamp: datetime.datetime, condition: str, type_id: Optional[int] = None,
|
||||||
name: Optional[str] = None, serial: Optional[str] = None,
|
name: Optional[str] = None, serial: Optional[str] = None,
|
||||||
|
@ -128,5 +128,5 @@ class Inventory(db.Model, PhotoAttachable):
|
||||||
shared=bool(data.get("shared", False))
|
shared=bool(data.get("shared", False))
|
||||||
)
|
)
|
||||||
|
|
||||||
def attach_photo(self, photo: Photo) -> None:
|
def attach_image(self, image: Image) -> None:
|
||||||
self.photo = photo
|
self.image = image
|
||||||
|
|
|
@ -3,15 +3,15 @@ if TYPE_CHECKING:
|
||||||
from .inventory import Inventory
|
from .inventory import Inventory
|
||||||
from .rooms import Room
|
from .rooms import Room
|
||||||
from .work_log import WorkLog
|
from .work_log import WorkLog
|
||||||
from .photo import Photo
|
from .image import Image
|
||||||
|
|
||||||
from sqlalchemy import Boolean, ForeignKey, Identity, Integer, Unicode, text
|
from sqlalchemy import Boolean, ForeignKey, Identity, Integer, Unicode, text
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
from .photo import PhotoAttachable
|
from .image import ImageAttachable
|
||||||
|
|
||||||
class User(db.Model, PhotoAttachable):
|
class User(db.Model, ImageAttachable):
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True)
|
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)
|
first_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True)
|
||||||
location_id: Mapped[Optional[int]] = mapped_column(ForeignKey("rooms.id"), 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"))
|
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')
|
supervisor: Mapped[Optional['User']] = relationship('User', remote_side='User.id', back_populates='subordinates')
|
||||||
subordinates: Mapped[List['User']] = relationship('User', back_populates='supervisor')
|
subordinates: Mapped[List['User']] = relationship('User', back_populates='supervisor')
|
||||||
work_logs: Mapped[List['WorkLog']] = relationship('WorkLog', back_populates='contact')
|
work_logs: Mapped[List['WorkLog']] = relationship('WorkLog', back_populates='contact')
|
||||||
location: Mapped[Optional['Room']] = relationship('Room', back_populates='users')
|
location: Mapped[Optional['Room']] = relationship('Room', back_populates='users')
|
||||||
inventory: Mapped[List['Inventory']] = relationship('Inventory', back_populates='owner')
|
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
|
@property
|
||||||
def full_name(self) -> str:
|
def full_name(self) -> str:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Optional, Any, List, TYPE_CHECKING
|
from typing import Optional, Any, List, TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .inventory import Inventory
|
from .inventory import Inventory
|
||||||
from .photo import Photo
|
from .image import Image
|
||||||
from .users import User
|
from .users import User
|
||||||
from .work_note import WorkNote
|
from .work_note import WorkNote
|
||||||
|
|
||||||
|
@ -10,11 +10,11 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
from .photo import PhotoAttachable
|
from .image import ImageAttachable
|
||||||
from .photo_links import worklog_photos
|
from .image_links import worklog_images
|
||||||
from .work_note import WorkNote
|
from .work_note import WorkNote
|
||||||
|
|
||||||
class WorkLog(db.Model, PhotoAttachable):
|
class WorkLog(db.Model, ImageAttachable):
|
||||||
__tablename__ = 'work_log'
|
__tablename__ = 'work_log'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(Integer, Identity(start=1, increment=1), primary_key=True)
|
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',
|
cascade='all, delete-orphan',
|
||||||
order_by='WorkNote.timestamp'
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -5,7 +5,7 @@ from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from ..models import Inventory
|
from ..models import Inventory
|
||||||
|
|
||||||
from ..models.photo import PhotoAttachable
|
from ..models.image import ImageAttachable
|
||||||
|
|
||||||
inventory_headers = {
|
inventory_headers = {
|
||||||
"Date Entered": lambda i: {"text": i.timestamp.strftime("%Y-%m-%d") if i.timestamp else None},
|
"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)
|
original_name = secure_filename(file_storage.filename)
|
||||||
return f"{model_name}/{sha}_{original_name}"
|
return f"{model_name}/{sha}_{original_name}"
|
||||||
|
|
||||||
def get_photo_attachable_class_by_name(name: str):
|
def get_image_attachable_class_by_name(name: str):
|
||||||
for cls in PhotoAttachable.__subclasses__():
|
for cls in ImageAttachable.__subclasses__():
|
||||||
if getattr(cls, '__tablename__', None) == name:
|
if getattr(cls, '__tablename__', None) == name:
|
||||||
return cls
|
return cls
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -3,18 +3,18 @@ import posixpath
|
||||||
|
|
||||||
from flask import Blueprint, current_app, request, jsonify
|
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 .. 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
|
assert current_app.static_folder
|
||||||
|
|
||||||
hashed_name = generate_hashed_filename(file, model)
|
hashed_name = generate_hashed_filename(file, model)
|
||||||
rel_path = posixpath.join("uploads", "photos", hashed_name)
|
rel_path = posixpath.join("uploads", "images", hashed_name)
|
||||||
abs_path = os.path.join(current_app.static_folder, "uploads", "photos", rel_path)
|
abs_path = os.path.join(current_app.static_folder, "uploads", "images", rel_path)
|
||||||
|
|
||||||
dir_path = os.path.dirname(abs_path)
|
dir_path = os.path.dirname(abs_path)
|
||||||
if not os.path.exists(dir_path):
|
if not os.path.exists(dir_path):
|
||||||
|
@ -29,8 +29,8 @@ def save_photo(file, model: str) -> str:
|
||||||
file.save(abs_path)
|
file.save(abs_path)
|
||||||
return rel_path
|
return rel_path
|
||||||
|
|
||||||
@photo_bp.route("/api/photos", methods=["POST"])
|
@image_bp.route("/api/images", methods=["POST"])
|
||||||
def upload_photo():
|
def upload_image():
|
||||||
file = request.files.get("file")
|
file = request.files.get("file")
|
||||||
model = request.form.get("model")
|
model = request.form.get("model")
|
||||||
model_id = request.form.get("model_id")
|
model_id = request.form.get("model_id")
|
||||||
|
@ -39,9 +39,9 @@ def upload_photo():
|
||||||
if not file or not model or not model_id:
|
if not file or not model or not model_id:
|
||||||
return jsonify({"success": False, "error": "Missing file, model, or model_id"}), 400
|
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:
|
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:
|
try:
|
||||||
model_id = int(model_id)
|
model_id = int(model_id)
|
||||||
|
@ -49,34 +49,40 @@ def upload_photo():
|
||||||
return jsonify({"success": False, "error": "model_id must be an integer"}), 400
|
return jsonify({"success": False, "error": "model_id must be an integer"}), 400
|
||||||
|
|
||||||
# Save file
|
# Save file
|
||||||
rel_path = save_photo(file, model)
|
rel_path = save_image(file, model)
|
||||||
print(rel_path)
|
print(rel_path)
|
||||||
|
|
||||||
# Create Photo row
|
# Create Image row
|
||||||
photo = Photo(filename=rel_path, caption=caption)
|
image = Image(filename=rel_path, caption=caption)
|
||||||
db.session.add(photo)
|
db.session.add(image)
|
||||||
|
|
||||||
# Attach photo to model
|
# Attach image to model
|
||||||
target = db.session.get(ModelClass, model_id)
|
target = db.session.get(ModelClass, model_id)
|
||||||
if not target:
|
if not target:
|
||||||
return jsonify({"success": False, "error": f"No {model} found with ID {model_id}"}), 404
|
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()
|
db.session.commit()
|
||||||
return jsonify({"success": True, "id": photo.id}), 201
|
return jsonify({"success": True, "id": image.id}), 201
|
||||||
|
|
||||||
@photo_bp.route("/api/photos/<int:photo_id>", methods=["GET"])
|
@image_bp.route("/api/images/<int:image_id>", methods=["GET"])
|
||||||
def get_photo(photo_id: int):
|
def get_image(image_id: int):
|
||||||
photo = db.session.get(Photo, photo_id)
|
image = db.session.get(Image, image_id)
|
||||||
if not photo:
|
if not image:
|
||||||
return jsonify({"success": False, "error": f"No photo found with ID {photo_id}"}), 404
|
return jsonify({"success": False, "error": f"No image found with ID {image_id}"}), 404
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"id": photo.id,
|
"id": image.id,
|
||||||
"filename": photo.filename,
|
"filename": image.filename,
|
||||||
"caption": photo.caption,
|
"caption": image.caption,
|
||||||
"timestamp": photo.timestamp.isoformat() if photo.timestamp else None,
|
"timestamp": image.timestamp.isoformat() if image.timestamp else None,
|
||||||
"url": f"/static/{photo.filename}"
|
"url": f"/static/{image.filename}"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@image_bp.route("/api/images/<int:image_id>", 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"})
|
|
@ -59,7 +59,7 @@ const ImageWidget = (() => {
|
||||||
console.log(form);
|
console.log(form);
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
|
|
||||||
fetch("/api/photos", {
|
fetch("/api/images", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData
|
body: formData
|
||||||
}).then(async response => {
|
}).then(async response => {
|
||||||
|
@ -76,7 +76,7 @@ const ImageWidget = (() => {
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
renderToast({ message: `Photo uploaded.`, type: "success" });
|
renderToast({ message: `Image uploaded.`, type: "success" });
|
||||||
location.reload();
|
location.reload();
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
const msg = typeof err === "object" && err.error ? err.error : err.toString();
|
const msg = typeof err === "object" && err.error ? err.error : err.toString();
|
||||||
|
|
|
@ -24,9 +24,6 @@
|
||||||
<label for="identifier" class="form-label">Identifier</label>
|
<label for="identifier" class="form-label">Identifier</label>
|
||||||
<input type="text" class="form-control-plaintext" value="{{ item.identifier }}" readonly>
|
<input type="text" class="form-control-plaintext" value="{{ item.identifier }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-center">
|
|
||||||
{{ images.render_image(item.id, item.photo) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
|
@ -131,6 +128,9 @@
|
||||||
entry_route='worklog_entry', title='Work Log') }}
|
entry_route='worklog_entry', title='Work Log') }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div class="col mt-5">
|
||||||
|
{{ images.render_image(item.id, item.image) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue