-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TO MAIN] DESENG-514 - Adding widget translation model (#2411)
* Adding widget translation model (#2407)
- Loading branch information
1 parent
74f835a
commit 8366029
Showing
13 changed files
with
640 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
met-api/migrations/versions/35124d2e41cb_create_widget_translation_table.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
"""create_widget_translation_table | ||
Revision ID: 35124d2e41cb | ||
Revises: 274a2774607b | ||
Create Date: 2024-03-05 16:43:50.911576 | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
from sqlalchemy.dialects import postgresql | ||
|
||
# revision identifiers, used by Alembic. | ||
revision = '35124d2e41cb' | ||
down_revision = '274a2774607b' | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.create_table('widget_translation', | ||
sa.Column('created_date', sa.DateTime(), nullable=False), | ||
sa.Column('updated_date', sa.DateTime(), nullable=True), | ||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), | ||
sa.Column('widget_id', sa.Integer(), nullable=False), | ||
sa.Column('language_id', sa.Integer(), nullable=False), | ||
sa.Column('title', sa.String(length=100), nullable=True, comment='Custom title for the widget.'), | ||
sa.Column('map_marker_label', sa.String(length=30), nullable=True), | ||
sa.Column('map_file_name', sa.Text(), nullable=True), | ||
sa.Column('poll_title', sa.String(length=255), nullable=True), | ||
sa.Column('poll_description', sa.String(length=2048), nullable=True), | ||
sa.Column('video_url', sa.String(length=255), nullable=True), | ||
sa.Column('video_description', sa.Text(), nullable=True), | ||
sa.Column('created_by', sa.String(length=50), nullable=True), | ||
sa.Column('updated_by', sa.String(length=50), nullable=True), | ||
sa.ForeignKeyConstraint(['language_id'], ['language.id'], ondelete='CASCADE'), | ||
sa.ForeignKeyConstraint(['widget_id'], ['widget.id'], ondelete='CASCADE'), | ||
sa.PrimaryKeyConstraint('id'), | ||
sa.UniqueConstraint('widget_id', 'language_id', name='unique_widget_language') | ||
) | ||
# ### end Alembic commands ### | ||
|
||
|
||
def downgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.drop_table('widget_translation') | ||
# ### end Alembic commands ### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,3 +26,4 @@ class WidgetType(IntEnum): | |
Map = 6 | ||
Video = 7 | ||
Timeline = 9 | ||
Poll = 10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
"""Widget translation model class. | ||
Manages the widget language translation | ||
""" | ||
from __future__ import annotations | ||
from typing import Optional | ||
|
||
from sqlalchemy.sql.schema import ForeignKey | ||
|
||
from .base_model import BaseModel | ||
from .db import db | ||
|
||
|
||
class WidgetTranslation(BaseModel): # pylint: disable=too-few-public-methods | ||
"""Definition of the Widget translation entity.""" | ||
|
||
__tablename__ = 'widget_translation' | ||
__table_args__ = ( | ||
db.UniqueConstraint('widget_id', 'language_id', name='unique_widget_language'), | ||
) | ||
|
||
id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
widget_id = db.Column(db.Integer, ForeignKey('widget.id', ondelete='CASCADE'), nullable=False) | ||
language_id = db.Column(db.Integer, ForeignKey('language.id', ondelete='CASCADE'), nullable=False) | ||
title = db.Column(db.String(100), comment='Custom title for the widget.') | ||
map_marker_label = db.Column(db.String(30)) | ||
map_file_name = db.Column(db.Text()) | ||
poll_title = db.Column(db.String(255)) | ||
poll_description = db.Column(db.String(2048)) | ||
video_url = db.Column(db.String(255)) | ||
video_description = db.Column(db.Text()) | ||
|
||
@classmethod | ||
def get_translation_by_widget_id_and_language_id(cls, widget_id=None, language_id=None): | ||
"""Get translation by widget_id and language_id, or by either one.""" | ||
query = WidgetTranslation.query | ||
if widget_id is not None: | ||
query = query.filter_by(widget_id=widget_id) | ||
if language_id is not None: | ||
query = query.filter_by(language_id=language_id) | ||
|
||
widget_translation_records = query.all() | ||
return widget_translation_records | ||
|
||
@classmethod | ||
def create_widget_translation(cls, translation) -> WidgetTranslation: | ||
"""Create widget translation.""" | ||
new_widget_translation = cls.__create_new_widget_translation_entity(translation) | ||
db.session.add(new_widget_translation) | ||
db.session.commit() | ||
return new_widget_translation | ||
|
||
@staticmethod | ||
def __create_new_widget_translation_entity(translation): | ||
"""Create new widget translation entity.""" | ||
return WidgetTranslation( | ||
widget_id=translation.get('widget_id'), | ||
language_id=translation.get('language_id'), | ||
title=translation.get('title', None), | ||
map_marker_label=translation.get('map_marker_label', None), | ||
map_file_name=translation.get('map_file_name', None), | ||
poll_title=translation.get('poll_title', None), | ||
poll_description=translation.get('poll_description', None), | ||
video_url=translation.get('video_url', None), | ||
video_description=translation.get('video_description', None), | ||
) | ||
|
||
@classmethod | ||
def remove_widget_translation(cls, widget_translation_id) -> WidgetTranslation: | ||
"""Remove widget translation from widget.""" | ||
widget_translation = WidgetTranslation.query.filter_by(id=widget_translation_id).delete() | ||
db.session.commit() | ||
return widget_translation | ||
|
||
@classmethod | ||
def update_widget_translation(cls, widget_translation_id, translation: dict) -> Optional[WidgetTranslation]: | ||
"""Update widget translation.""" | ||
query = WidgetTranslation.query.filter_by(id=widget_translation_id) | ||
widget_translation: WidgetTranslation = query.first() | ||
if not widget_translation: | ||
return None | ||
query.update(translation) | ||
db.session.commit() | ||
return widget_translation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# 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 widget translation resource.""" | ||
|
||
from http import HTTPStatus | ||
|
||
from flask import jsonify, request | ||
from flask_cors import cross_origin | ||
from flask_restx import Namespace, Resource | ||
from marshmallow import ValidationError | ||
|
||
from met_api.auth import jwt as _jwt | ||
from met_api.schemas import utils as schema_utils | ||
from met_api.schemas.widget_translation import WidgetTranslationSchema | ||
from met_api.services.widget_translation_service import WidgetTranslationService | ||
from met_api.utils.util import allowedorigins, cors_preflight | ||
|
||
|
||
API = Namespace('widget_translation', description='Endpoints for Widget translation Management') | ||
|
||
|
||
@cors_preflight('GET, OPTIONS') | ||
@API.route('/language/<language_id>') | ||
class WidgetTranslationResourceByLanguage(Resource): | ||
"""Resource for managing a widget translation.""" | ||
|
||
@staticmethod | ||
@cross_origin(origins=allowedorigins()) | ||
def get(widget_id, language_id): | ||
"""Fetch a list of widgets by widget_id and language_id.""" | ||
try: | ||
widgets = WidgetTranslationService().get_translation_by_widget_id_and_language_id( | ||
widget_id, language_id) | ||
return jsonify(widgets), HTTPStatus.OK | ||
except (KeyError, ValueError) as err: | ||
return str(err), HTTPStatus.INTERNAL_SERVER_ERROR | ||
|
||
|
||
@cors_preflight('POST, OPTIONS') | ||
@API.route('/') | ||
class WidgetTranslations(Resource): | ||
"""Resource for creating a widget translation.""" | ||
|
||
@staticmethod | ||
@cross_origin(origins=allowedorigins()) | ||
@_jwt.requires_auth | ||
def post(widget_id): | ||
"""Add new widget translation.""" | ||
try: | ||
request_json = request.get_json() | ||
request_json['widget_id'] = widget_id | ||
valid_format, errors = schema_utils.validate(request_json, 'widget_translation') | ||
if not valid_format: | ||
return {'message': schema_utils.serialize(errors)}, HTTPStatus.BAD_REQUEST | ||
|
||
pre_populate = request_json.get('pre_populate', True) | ||
|
||
translation = WidgetTranslationSchema().load(request_json) | ||
created_widget_translation = WidgetTranslationService().create_widget_translation(translation, | ||
pre_populate) | ||
return created_widget_translation, HTTPStatus.OK | ||
except (KeyError, ValueError) as err: | ||
return str(err), HTTPStatus.INTERNAL_SERVER_ERROR | ||
except ValidationError as err: | ||
return str(err.messages), HTTPStatus.BAD_REQUEST | ||
|
||
|
||
@cors_preflight('GET, DELETE, PATCH') | ||
@API.route('/<int:widget_translation_id>') | ||
class EditWidgetTranslation(Resource): | ||
"""Resource for updating or deleting a widget translation.""" | ||
|
||
@staticmethod | ||
@cross_origin(origins=allowedorigins()) | ||
@_jwt.requires_auth | ||
def delete(widget_id, widget_translation_id): | ||
"""Remove widget translation for a widget.""" | ||
try: | ||
WidgetTranslationService().delete_widget_translation(widget_id, widget_translation_id) | ||
return 'Widget translation successfully removed', HTTPStatus.OK | ||
except KeyError as err: | ||
return str(err), HTTPStatus.BAD_REQUEST | ||
except ValueError as err: | ||
return str(err), HTTPStatus.NOT_FOUND | ||
|
||
@staticmethod | ||
@cross_origin(origins=allowedorigins()) | ||
@_jwt.requires_auth | ||
def patch(widget_id, widget_translation_id): | ||
"""Update widget translation.""" | ||
try: | ||
translation_data = request.get_json() | ||
updated_widget = WidgetTranslationService().update_widget_translation(widget_id, | ||
widget_translation_id, | ||
translation_data) | ||
return updated_widget, HTTPStatus.OK | ||
except ValueError as err: | ||
return str(err), HTTPStatus.NOT_FOUND | ||
except ValidationError as err: | ||
return str(err.messages), HTTPStatus.BAD_REQUEST |
31 changes: 31 additions & 0 deletions
31
met-api/src/met_api/schemas/schemas/widget_translation.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"$schema": "http://json-schema.org/draft-07/schema", | ||
"$id": "https://met.gov.bc.ca/.well_known/schemas/widget_translation", | ||
"type": "object", | ||
"title": "The root schema", | ||
"description": "The root schema comprises the entire JSON document.", | ||
"default": {}, | ||
"examples": [ | ||
{ | ||
"widget_id": 1, | ||
"language_id": 1 | ||
} | ||
], | ||
"required": ["widget_id", "language_id"], | ||
"properties": { | ||
"widget_id": { | ||
"$id": "#/properties/widget_id", | ||
"type": "number", | ||
"title": "Widget id", | ||
"description": "The widget to which this translation belongs.", | ||
"examples": [1] | ||
}, | ||
"language_id": { | ||
"$id": "#/properties/language_id", | ||
"type": "number", | ||
"title": "Language id", | ||
"description": "The language to which this translation belongs.", | ||
"examples": [1] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
"""Widget translation schema class.""" | ||
|
||
from marshmallow import EXCLUDE, Schema, fields | ||
|
||
|
||
class WidgetTranslationSchema(Schema): | ||
"""Widget translation schema.""" | ||
|
||
class Meta: # pylint: disable=too-few-public-methods | ||
"""Exclude unknown fields in the deserialized output.""" | ||
|
||
unknown = EXCLUDE | ||
|
||
id = fields.Int(data_key='id') | ||
widget_id = fields.Int(data_key='widget_id', required=True) | ||
language_id = fields.Int(data_key='language_id', required=True) | ||
title = fields.Str(data_key='title') | ||
map_marker_label = fields.Str(data_key='map_marker_label') | ||
map_file_name = fields.Str(data_key='map_file_name') | ||
poll_title = fields.Str(data_key='poll_title') | ||
poll_description = fields.Str(data_key='poll_description') | ||
video_url = fields.Str(data_key='video_url') | ||
video_description = fields.Str(data_key='video_description') |
Oops, something went wrong.