Skip to content

Commit

Permalink
Merge pull request #81 from tom0827/ES-199
Browse files Browse the repository at this point in the history
image url generator
  • Loading branch information
tom0827 authored Aug 14, 2024
2 parents a24a06b + c626231 commit 83279f2
Show file tree
Hide file tree
Showing 24 changed files with 1,309 additions and 476 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""create image info table
Revision ID: c37ea4837943
Revises: a3e6dae331ab
Create Date: 2024-08-01 15:21:53.966495
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = 'c37ea4837943'
down_revision = 'a3e6dae331ab'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('image_info',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('unique_name', sa.String()),
sa.Column('display_name', sa.String()),
sa.Column('date_uploaded', sa.DateTime()),
sa.Column('tenant_id', sa.Integer(), nullable=True),
sa.Column('created_date', sa.DateTime()),
sa.Column('updated_date', sa.DateTime()),
sa.Column('created_by', sa.String(length=50), nullable=True),
sa.Column('updated_by', sa.String(length=50), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.ForeignKeyConstraint(['tenant_id'], ['tenant.id'], ondelete='SET NULL'),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('image_info')
# ### end Alembic commands ###
52 changes: 52 additions & 0 deletions met-api/src/met_api/models/image_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""ImageInfo model class.
Manages the ImageInfo
"""

from sqlalchemy import asc, desc
from sqlalchemy.sql import text

from met_api.models import db
from met_api.models.base_model import BaseModel
from met_api.models.pagination_options import PaginationOptions


class ImageInfo(BaseModel):
"""Definition of the ImageInfo entity."""

__tablename__ = 'image_info'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
unique_name = db.Column(db.String())
display_name = db.Column(db.String())
date_uploaded = db.Column(db.DateTime)
tenant_id = db.Column(db.Integer, db.ForeignKey('tenant.id'), nullable=True)
created_date = db.Column(db.DateTime)
updated_date = db.Column(db.DateTime)

@classmethod
def get_images_paginated(cls, pagination_options: PaginationOptions, search_options=None):
"""Get images paginated."""
query = db.session.query(ImageInfo)

query = cls._add_tenant_filter(query)

if search_options:
query = cls._filter_by_search_text(query, search_options)

sort = cls._get_sort_order(pagination_options)
query = query.order_by(sort)

page = query.paginate(page=pagination_options.page, per_page=pagination_options.size)
return page.items, page.total

@staticmethod
def _filter_by_search_text(query, search_options):
if search_text := search_options.get('search_text'):
query = query.filter(ImageInfo.display_name.ilike('%' + search_text + '%'))
return query

@staticmethod
def _get_sort_order(pagination_options):
sort = asc(text(pagination_options.sort_key)) if pagination_options.sort_order == 'asc' \
else desc(text(pagination_options.sort_key))
return sort
2 changes: 2 additions & 0 deletions met-api/src/met_api/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from .widget_video import API as WIDGET_VIDEO_API
from .engagement_settings import API as ENGAGEMENT_SETTINGS_API
from .cac_form import API as CAC_FORM_API
from .image_info import API as IMAGE_INFO

__all__ = ('API_BLUEPRINT',)

Expand Down Expand Up @@ -79,6 +80,7 @@
API.add_namespace(ENGAGEMENT_METADATA_API)
API.add_namespace(SHAPEFILE_API)
API.add_namespace(TENANT_API)
API.add_namespace(IMAGE_INFO)
API.add_namespace(ENGAGEMENT_MEMBERS_API, path='/engagements/<string:engagement_id>/members')
API.add_namespace(WIDGET_DOCUMENTS_API, path='/widgets/<string:widget_id>/documents')
API.add_namespace(WIDGET_EVENTS_API, path='/widgets/<int:widget_id>/events')
Expand Down
79 changes: 79 additions & 0 deletions met-api/src/met_api/resources/image_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright © 2021 Province of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""API endpoints for managing an image uploads resource."""

from http import HTTPStatus

from flask import request
from flask_cors import cross_origin
from flask_restx import Namespace, Resource
from marshmallow import ValidationError

from met_api.models.pagination_options import PaginationOptions
from met_api.schemas.image_info import ImageInfoParameterSchema, ImageInfoSchema
from met_api.services.image_info_service import ImageInfoService
from met_api.utils.roles import Role
from met_api.utils.tenant_validator import require_role
from met_api.utils.util import allowedorigins, cors_preflight


API = Namespace('image_info', description='Endpoints for Image Info management')
"""Custom exception messages
"""


@cors_preflight('GET, POST, OPTIONS')
@API.route('/')
class ImageInfo(Resource):
"""Resource for managing image info."""

@staticmethod
@cross_origin(origins=allowedorigins())
@require_role([Role.CREATE_IMAGES.value])
def get():
"""Fetch images."""
try:
args = request.args

pagination_options = PaginationOptions(
page=args.get('page', None, int),
size=args.get('size', None, int),
sort_key=args.get('sort_key', 'date_uploaded', str),
sort_order=args.get('sort_order', 'desc', str),
)

search_options = {
'search_text': args.get('search_text', '', type=str),
}

images = ImageInfoService().get_images_paginated(pagination_options, search_options)
return images, HTTPStatus.OK
except ValueError as err:
return str(err), HTTPStatus.INTERNAL_SERVER_ERROR

@staticmethod
@cross_origin(origins=allowedorigins())
@require_role([Role.CREATE_IMAGES.value])
def post():
"""Create a new image upload."""
try:
request_json = ImageInfoParameterSchema().load(API.payload)
image_model = ImageInfoService().create_image_info(request_json)
return ImageInfoSchema().dump(image_model), HTTPStatus.OK
except KeyError as err:
return str(err), HTTPStatus.INTERNAL_SERVER_ERROR
except ValueError as err:
return str(err), HTTPStatus.INTERNAL_SERVER_ERROR
except ValidationError as err:
return str(err.messages), HTTPStatus.INTERNAL_SERVER_ERROR
50 changes: 50 additions & 0 deletions met-api/src/met_api/schemas/image_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Image Info schema class."""
from marshmallow import EXCLUDE, Schema, fields
from met_api.services.object_storage_service import ObjectStorageService


class ImageInfoSchema(Schema):
"""Image Info schema class."""

def __init__(self, *args, **kwargs):
"""Initialize the Image Info schema class."""
super().__init__(*args, **kwargs)
self.object_storage = ObjectStorageService()

class Meta:
"""Exclude unknown fields in the deserialized output."""

unknown = EXCLUDE

id = fields.Int(data_key='id')
unique_name = fields.Str(data_key='unique_name', required=True)
display_name = fields.Str(data_key='display_name', required=True)
date_uploaded = fields.DateTime(data_key='date_uploaded')
tenant_id = fields.Str(data_key='tenant_id')
url = fields.Method('get_object_store_url', dump_only=True)

def get_object_store_url(self, obj):
"""Get the image URL from object storage."""
if obj.unique_name:
return self.object_storage.get_url(obj.unique_name)
else:
return None


class ImageInfoParameterSchema(Schema):
"""Schema for validating fields upon image info creation."""

unique_name = fields.Str(
metadata={'description': 'Unique name of the file'},
required=True,
)

display_name = fields.Str(
metadata={'description': 'Display name of the file'},
required=True,
)

date_uploaded = fields.DateTime(
metadata={'description': 'Date when file was uploaded'},
required=True,
)
40 changes: 40 additions & 0 deletions met-api/src/met_api/services/image_info_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Service for image management."""
from met_api.models.pagination_options import PaginationOptions
from met_api.schemas.image_info import ImageInfoSchema
from met_api.services.object_storage_service import ObjectStorageService
from met_api.models.image_info import ImageInfo as ImageInfoModel


class ImageInfoService:
"""Image Info management service."""

def __init__(self):
"""Initialize."""
self.object_storage = ObjectStorageService()

@staticmethod
def get_images_paginated(pagination_options: PaginationOptions, search_options=None):
"""Get images paginated."""
items, total = ImageInfoModel.get_images_paginated(
pagination_options,
search_options,
)

images = ImageInfoSchema(many=True).dump(items)

return {
'items': images,
'total': total
}

@staticmethod
def create_image_info(request_json: dict):
"""Create an Image Info upload."""
new_image = ImageInfoModel(
unique_name=request_json.get('unique_name', None),
display_name=request_json.get('display_name', None),
date_uploaded=request_json.get('date_uploaded', None),
)
new_image.save()
new_image.commit()
return new_image.find_by_id(new_image.id)
1 change: 1 addition & 0 deletions met-api/src/met_api/utils/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ class Role(Enum):
EXPORT_ALL_TO_CSV = 'export_all_to_csv'
EXPORT_INTERNAL_COMMENT_SHEET = 'export_internal_comment_sheet'
EXPORT_PROPONENT_COMMENT_SHEET = 'export_proponent_comment_sheet'
CREATE_IMAGES = 'create_images'
Loading

0 comments on commit 83279f2

Please sign in to comment.