Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Template_Categories table #2193

Merged
merged 38 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4bab291
Draft migration to add TemplateCategories table
whabanks Jun 11, 2024
da56d60
Fix prop logic for template_process_type
whabanks Jun 11, 2024
e7346fd
Add indexes, unique constraints
whabanks Jun 12, 2024
70334b0
Add CRUD methods for TemplateCategories
whabanks Jun 13, 2024
0430b4b
Insert low, med, high default categories during migration
whabanks Jun 13, 2024
28637f2
Fix prop logic for template_process_type again
whabanks Jun 13, 2024
b76744e
WIP: Add API endpoints for interacting with TemplateCategories
whabanks Jun 13, 2024
e34dc4a
Implement dao and api to update process_type
whabanks Jun 17, 2024
6a291fc
Address PR comments
whabanks Jun 17, 2024
fcb3550
Finish adding needed api endpoints
whabanks Jun 17, 2024
d8799ac
Chore: logic cleanup
whabanks Jun 17, 2024
6da3eaa
First batch of unit tests & bug fixes
whabanks Jun 18, 2024
a0fd757
Implement filtering when fetching all categories
whabanks Jun 19, 2024
e172bb7
Add lazy join on TemplateCategory
whabanks Jun 19, 2024
8a374c2
Clean up dao tests
whabanks Jun 19, 2024
46d0130
Add tests for deleting a template category
whabanks Jun 19, 2024
98c93bf
Add API tests, squash bugs
whabanks Jun 20, 2024
c7cab45
Fix pre-existing tests
whabanks Jun 24, 2024
6055586
Misc. fixes
whabanks Jun 24, 2024
231128f
We definitely didn't want that FK on templatehistory...
whabanks Jun 24, 2024
654d6b3
Merge branch 'main' into feat/add-template-categories-table
whabanks Jun 24, 2024
33006d7
Logic cleanups
whabanks Jun 24, 2024
8e0afa2
Rename migration
whabanks Jun 25, 2024
f927c99
Add tests for models
whabanks Jun 25, 2024
041a647
Add tests that were missed for template rest and dao
whabanks Jun 25, 2024
0be6d19
Rename /template/category to /template-category
whabanks Jun 25, 2024
44ad1f1
various fixes
whabanks Jun 25, 2024
d1c5c4e
Merge branch 'main' into feat/add-template-categories-table
jzbahrai Jun 27, 2024
1c84439
Re-word dao_delete_template_category_by_id
whabanks Jul 2, 2024
b7f33df
Merge branch 'main' into feat/add-template-categories-table
whabanks Jul 2, 2024
119cac4
Merge branch 'main' into feat/add-template-categories-table
whabanks Jul 2, 2024
1c342e1
Merge branch 'main' into feat/add-template-categories-table
jzbahrai Jul 3, 2024
3c37d1d
Add created_at and updated_at fields
whabanks Jul 3, 2024
38bc4e6
Add default value for created at, fix tests
whabanks Jul 3, 2024
5e1a947
Quick fix
whabanks Jul 3, 2024
ce80c82
Fix column defaults
whabanks Jul 3, 2024
ac90f0d
Fix silly typo..
whabanks Jul 3, 2024
d182ffe
Remove unneeded assert
whabanks Jul 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions app/dao/template_categories_dao.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import uuid

from sqlalchemy import asc
Fixed Show fixed Hide fixed

from app import db
from app.dao.dao_utils import transactional
Fixed Show fixed Hide fixed
from app.models import TemplateCategory


@transactional
def dao_create_template_category(template_category: TemplateCategory):
jzbahrai marked this conversation as resolved.
Show resolved Hide resolved
template_category.id = uuid.uuid4()
db.session.add(template_category)


@transactional
def dao_update_template_category(template_category: TemplateCategory):
db.session.add(template_category)


def dao_get_template_category_by_id(template_category_id):
return TemplateCategory.query.filter_by(id=template_category_id).one()


def dao_get_template_category_by_template_id(template_id):
return TemplateCategory.query.join(TemplateCategory.templates).filter_by(id=template_id).one()
whabanks marked this conversation as resolved.
Show resolved Hide resolved


def dao_get_all_template_categories():
whabanks marked this conversation as resolved.
Show resolved Hide resolved
return TemplateCategory.query.order_by(asc(TemplateCategory.name_en)).all()
34 changes: 34 additions & 0 deletions app/dao/templates_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,40 @@ def dao_update_template_reply_to(template_id, reply_to):
return template


@transactional
def dao_update_template_category(template_id, category_id):
jzbahrai marked this conversation as resolved.
Show resolved Hide resolved
Template.query.filter_by(id=template_id).update(
whabanks marked this conversation as resolved.
Show resolved Hide resolved
{
"template_category_id": category_id,
"updated_at": datetime.utcnow(),
"version": Template.version + 1,
}
)

template = Template.query.filter_by(id=template_id).one()

history = TemplateHistory(
**{
"id": template.id,
"name": template.name,
"template_type": template.template_type,
"created_at": template.created_at,
"updated_at": template.updated_at,
"content": template.content,
"service_id": template.service_id,
"subject": template.subject,
"postage": template.postage,
"created_by_id": template.created_by_id,
"version": template.version,
"archived": template.archived,
"process_type": template.process_type,
"service_letter_contact_id": template.service_letter_contact_id,
}
)
db.session.add(history)
return template


@transactional
def dao_redact_template(template, user_id):
template.template_redacted.redact_personalisation = True
Expand Down
43 changes: 43 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,36 @@ def get_users_with_permission(self):
PRECOMPILED_TEMPLATE_NAME = "Pre-compiled PDF"


class TemplateCategory(BaseModel):
__tablename__ = "template_categories"

id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name_en = db.Column(db.String(255), unique=True, nullable=False)
name_fr = db.Column(db.String(255), unique=True, nullable=False)
description_en = db.Column(db.String(200), nullable=True)
description_fr = db.Column(db.String(200), nullable=True)
sms_process_type = db.Column(db.String(200), nullable=False)
email_process_type = db.Column(db.String(200), nullable=False)
hidden = db.Column(db.Boolean, nullable=False, default=False)

def serialize(self):
return {
"id": self.id,
"name_en": self.name_en,
"name_fr": self.name_fr,
"description_en": self.description_en,
"description_fr": self.description_fr,
"sms_process_type": self.sms_process_type,
"email_process_type": self.email_process_type,
"hidden": self.hidden,
}

@classmethod
def from_json(cls, data):
fields = data.copy()
return cls(**fields)


class TemplateBase(BaseModel):
__abstract__ = True

Expand Down Expand Up @@ -1078,6 +1108,10 @@ def service_id(cls):
def created_by_id(cls):
return db.Column(UUID(as_uuid=True), db.ForeignKey("users.id"), index=True, nullable=False)

@declared_attr
def template_category_id(cls):
return db.Column(UUID(as_uuid=True), db.ForeignKey("template_categories.id"), index=True, nullable=True)

@declared_attr
def created_by(cls):
return db.relationship("User")
Expand Down Expand Up @@ -1179,6 +1213,7 @@ class Template(TemplateBase):

service = db.relationship("Service", backref="templates")
version = db.Column(db.Integer, default=0, nullable=False)
category = db.relationship("TemplateCategory", backref="templates")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this to template_category. Ignore my relationship comment above, this should work!


folder = db.relationship(
"TemplateFolder",
Expand All @@ -1198,6 +1233,14 @@ def get_link(self):
_external=True,
)

@property
def template_process_type(self):
if self.template_type == SMS_TYPE:
whabanks marked this conversation as resolved.
Show resolved Hide resolved
return self.process_type if self.process_type else self.template_categories.sms_process_type
elif self.template_type == EMAIL_TYPE:
return self.process_type if self.process_type else self.template_categories.email_process_type
return self.process_type

@classmethod
def from_json(cls, data, folder=None):
"""
Expand Down
27 changes: 27 additions & 0 deletions app/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,32 @@ def make_instance(self, data, **kwargs):
return super(BaseSchema, self).make_instance(data)


class TemplateCategorySchema(BaseSchema):
class Meta(BaseSchema.Meta):
model = models.TemplateCategory
exclude = ("id",)

@validates("name_en")
def validate_name_en(self, value):
if not value:
raise ValidationError("Invalid name")

@validates("name_fr")
def validate_name_fr(self, value):
if not value:
raise ValidationError("Invalid name")

@validates("sms_process_type")
def validate_sms_process_type(self, value):
if value not in models.TEMPLATE_PROCESS_TYPE:
raise ValidationError("Invalid SMS process type")

@validates("email_process_type")
def validate_email_process_type(self, value):
if value not in models.TEMPLATE_PROCESS_TYPE:
raise ValidationError("Invalid email process type")


class UserSchema(BaseSchema):
permissions = fields.Method("user_permissions", dump_only=True)
password_changed_at = field_for(models.User, "password_changed_at", format="%Y-%m-%d %H:%M:%S.%f")
Expand Down Expand Up @@ -805,6 +831,7 @@ def validate_archived(self, data, **kwargs):
service_history_schema = ServiceHistorySchema()
api_key_history_schema = ApiKeyHistorySchema()
template_history_schema = TemplateHistorySchema()
template_category_schema = TemplateCategorySchema()
event_schema = EventSchema()
provider_details_schema = ProviderDetailsSchema()
provider_details_history_schema = ProviderDetailsHistorySchema()
Expand Down
20 changes: 20 additions & 0 deletions app/template/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
dao_get_template_versions,
dao_redact_template,
dao_update_template,
dao_update_template_category,
dao_update_template_reply_to,
get_precompiled_letter_template,
)
Expand Down Expand Up @@ -132,6 +133,25 @@
return jsonify(data=template_schema.dump(new_template)), 201


@template_blueprint.route("/<uuid:template_id>/template-category/<uuid:template_category_id>", methods=["POST"])
def update_templates_category(template_id, template_category_id):
updated = dao_update_template_category(template_id, template_category_id)
return jsonify(data=template_schema.dump(updated)), 200


@template_blueprint.route("/<uuid:template_id>/process-type", methods=["POST"])
def update_template_process_type(template_id):
data = request.get_json()
if "process_type" not in data:
message = "Field is required"
errors = {"process_type": [message]}
raise InvalidRequest(errors, status_code=400)

# updated = dao_update_template_process_type(template_id=template_id, process_type=data.get("process_type"))
# return jsonify(data=template_schema.dump(updated)), 200
pass
Fixed Show fixed Hide fixed


@template_blueprint.route("/<uuid:template_id>", methods=["POST"])
def update_template(service_id, template_id):
fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
Expand Down
49 changes: 49 additions & 0 deletions app/template/template_category_rest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from flask import Blueprint, jsonify, request

from app.dao.template_categories_dao import (
dao_create_template_category,
dao_get_all_template_categories,
dao_get_template_category_by_id,
dao_update_template_category,
)
from app.models import TemplateCategory
from app.schemas import template_category_schema

template_category_blueprint = Blueprint(
"template_category",
__name__,
url_prefix="template/category",
)


@template_category_blueprint.route("", methods=["POST"])
def create_template_category():
data = request.get_json()

template_category_schema.load(data)
template_category = TemplateCategory.from_json(data)

dao_create_template_category(template_category)

return jsonify(data=template_category_schema.dump(template_category)), 201


@template_category_blueprint.route("/<template_category_id>", methods=["POST"])
def update_template_category(template_category_id):
request_json = request.get_json()
update_dict = template_category_schema.load(request_json)

category_to_update = dao_get_template_category_by_id(template_category_id)

for key in request_json:
setattr(category_to_update, key, update_dict[key])

dao_update_template_category(category_to_update)

return jsonify(data=category_to_update.serialize()), 200


@template_category_blueprint.route("", methods=["GET"])
def get_template_categories():
template_categories = dao_get_all_template_categories()
return jsonify(data=template_category_schema.dump(template_categories, many=True)), 200
88 changes: 88 additions & 0 deletions migrations/versions/0454_add_template_category.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""

Revision ID: 0454_add_template_categories
Revises: 0453_add_callback_failure_email
Create Date: 2024-06-11 13:32:00
"""

from datetime import datetime

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

revision = "0454_add_template_categories"
down_revision = "0452_set_pgaudit_config"

# TODO: Add these to the config instead
DEFAULT_LOW = "0dda24c2-982a-4f44-9749-0e38b2607e89"
DEFAULT_MEDIUM = "f75d6706-21b7-437e-b93a-2c0ab771e28e"
DEFAULT_HIGH = "c4f87d7c-a55b-4c0f-91fe-e56c65bb1871"


def upgrade():
op.create_table(
"template_categories",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, nullable=False),
sa.Column("name_en", sa.String(length=255), nullable=False),
sa.Column("name_fr", sa.String(length=255), nullable=False),
sa.Column("description_en", sa.String(length=255), nullable=True),
sa.Column("description_fr", sa.String(length=255), nullable=True),
sa.Column("sms_process_type", sa.String(length=255), nullable=False),
sa.Column("email_process_type", sa.String(length=255), nullable=False),
sa.Column("hidden", sa.Boolean(), nullable=False),
sa.UniqueConstraint("name_en"),
sa.UniqueConstraint("name_fr"),
)

op.add_column("templates", sa.Column("template_category_id", postgresql.UUID(as_uuid=True), nullable=True))
op.add_column("templates_history", sa.Column("template_category_id", postgresql.UUID(as_uuid=True), nullable=True))
op.create_index(
op.f("ix_template_category_id"),
"templates",
["template_category_id"],
unique=False,
)
op.create_index(
op.f("ix_template_categories_name_en"),
"template_categories",
["name_en"],
unique=False,
)
op.create_index(
op.f("ix_template_categories_name_fr"),
"template_categories",
["name_fr"],
unique=False,
)
op.create_foreign_key("fk_template_template_categories", "templates", "template_categories", ["template_category_id"], ["id"])

# Insert the generic low, medium, and high categories
op.execute(
f"""
INSERT INTO template_category (id, name_en, name_fr, sms_process_type, email_process_type, hidden)
VALUES ({DEFAULT_LOW}, 'Low Category (Bulk)', 'Catégorie Basse (En Vrac)', true
"""
)
op.execute(
f"""
INSERT INTO template_category (id, name_en, name_fr, sms_process_type, email_process_type, hidden)
VALUES ({DEFAULT_MEDIUM}, 'Medium Category (Normal)', 'Catégorie Moyenne (Normale)', true
"""
)
op.execute(
f"""
INSERT INTO template_category (id, name_en, name_fr, sms_process_type, email_process_type, hidden)
VALUES ({DEFAULT_HIGH}, 'High Category (Priority)', 'Catégorie Haute (Priorité)', true
"""
)


def downgrade():
op.drop_constraint("fk_template_template_categories", "templates", type_="foreignkey")
op.drop_index(op.f("ix_template_category_id"), table_name="templates")
op.drop_index(op.f("ix_template_categories_name_en"), table_name="template_categories")
op.drop_index(op.f("ix_template_categories_name_fr"), table_name="template_categories")
op.drop_column("templates", "template_category_id")
op.drop_column("templates_history", "template_category_id")
op.drop_table("template_categories")
Loading
Loading