Skip to content

Commit

Permalink
Log Record Can Have Many Employees (Part 1) (#239)
Browse files Browse the repository at this point in the history
* so many whimsical magical wonderful changes

* run the linter
  • Loading branch information
Connor Bechthold authored Apr 21, 2024
1 parent d1f9f1f commit 4315888
Show file tree
Hide file tree
Showing 14 changed files with 211 additions and 114 deletions.
1 change: 1 addition & 0 deletions backend/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def init_app(app):
from .residents import Residents
from .buildings import Buildings
from .log_record_residents import LogRecordResidents
from .log_record_attn_tos import LogRecordAttnTos

app.app_context().push()
db.init_app(app)
Expand Down
34 changes: 34 additions & 0 deletions backend/app/models/log_record_attn_tos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from sqlalchemy import inspect
from sqlalchemy.orm.properties import ColumnProperty

from . import db


class LogRecordAttnTos(db.Model):
__tablename__ = "log_record_attn_tos"

id = db.Column(db.Integer, primary_key=True, nullable=False)
log_record_id = db.Column(
db.Integer, db.ForeignKey("log_records.log_id"), nullable=False
)
attn_to_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)

def to_dict(self, include_relationships=False):
# define the entities table
cls = type(self)

mapper = inspect(cls)
formatted = {}
for column in mapper.attrs:
field = column.key
attr = getattr(self, field)
# if it's a regular column, extract the value
if isinstance(column, ColumnProperty):
formatted[field] = attr
# otherwise, it's a relationship field
# (currently not applicable, but may be useful for entity groups)
elif include_relationships:
# recursively format the relationship
# don't format the relationship's relationships
formatted[field] = [obj.to_dict() for obj in attr]
return formatted
4 changes: 3 additions & 1 deletion backend/app/models/log_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class LogRecords(db.Model):
datetime = db.Column(db.DateTime(timezone=True), nullable=False)
flagged = db.Column(db.Boolean, nullable=False)
attn_to = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
# TODO: replace open String fields with VarChar(NUM_CHARS)
note = db.Column(db.String, nullable=False)
building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), nullable=False)
tags = db.relationship(
Expand All @@ -19,6 +18,9 @@ class LogRecords(db.Model):
residents = db.relationship(
"Residents", secondary="log_record_residents", back_populates="log_records"
)
attn_tos = db.relationship(
"User", secondary="log_record_attn_tos", back_populates="log_records"
)
building = db.relationship("Buildings", back_populates="log_record")

def to_dict(self, include_relationships=False):
Expand Down
3 changes: 3 additions & 0 deletions backend/app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class User(db.Model):
last_modified = db.Column(
db.DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
)
log_records = db.relationship(
"LogRecords", secondary="log_record_attn_tos", back_populates="attn_tos"
)

__table_args__ = (
db.CheckConstraint(
Expand Down
85 changes: 41 additions & 44 deletions backend/app/services/implementations/log_records_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ def add_record(self, log_record):

residents = new_log_record["residents"]
tags = new_log_record["tags"]
attn_tos = new_log_record["attn_tos"]

del new_log_record["residents"]
del new_log_record["tags"]
del new_log_record["attn_tos"]

new_log_record["datetime"] = datetime.fromisoformat(
new_log_record["datetime"].replace("Z", "+00:00")
Expand All @@ -40,6 +42,7 @@ def add_record(self, log_record):
new_log_record = LogRecords(**new_log_record)
self.construct_residents(new_log_record, residents)
self.construct_tags(new_log_record, tags)
self.construct_attn_tos(new_log_record, attn_tos)

db.session.add(new_log_record)
db.session.commit()
Expand All @@ -65,6 +68,17 @@ def construct_tags(self, log_record, tags):
raise Exception(f"Tag with id {tag_id} does not exist")
log_record.tags.append(tag)

def construct_attn_tos(self, log_record, attn_tos):
attn_tos = list(set(attn_tos))
for attn_to_id in attn_tos:
attn_to = User.query.filter_by(id=attn_to_id).first()

if not attn_to:
raise Exception(
f"Employee (attn_to) with id {attn_to_id} does not exist"
)
log_record.attn_tos.append(attn_to)

def to_json_list(self, logs):
try:
logs_list = []
Expand All @@ -78,18 +92,12 @@ def to_json_list(self, logs):
"last_name": log[3],
},
"residents": log[4] if log[4] else [],
"attn_to": {
"id": log[5],
"first_name": log[6],
"last_name": log[7],
}
if log[5]
else None,
"building": {"id": log[8], "name": log[9]},
"tags": log[10] if log[10] else [],
"note": log[11],
"flagged": log[12],
"datetime": log[13].isoformat(),
"attn_tos": log[5] if log[5] else [],
"building": {"id": log[6], "name": log[7]},
"tags": log[8] if log[8] else [],
"note": log[9],
"flagged": log[10],
"datetime": log[11].isoformat(),
}
)
return logs_list
Expand Down Expand Up @@ -124,11 +132,13 @@ def filter_by_residents(self, residents):

def filter_by_attn_tos(self, attn_tos):
if type(attn_tos) == list:
sql_statement = f"\nattn_to={attn_tos[0]}"
sql_statement = f"\n'{attn_tos[0]}'=ANY (attn_to_ids)"
for i in range(1, len(attn_tos)):
sql_statement = sql_statement + f"\nOR attn_to={attn_tos[i]}"
sql_statement = (
sql_statement + f"\nAND '{attn_tos[i]}'=ANY (attn_to_ids)"
)
return sql_statement
return f"\nattn_to={attn_tos}"
return f"\n'{attn_tos}'=ANY (attn_to_ids)"

def filter_by_date_range(self, date_range):
sql = ""
Expand Down Expand Up @@ -208,6 +218,14 @@ def join_tag_attributes(self):
GROUP BY logs.log_id \n \
) t ON logs.log_id = t.log_id\n"

def join_attn_to_attributes(self):
return "\nLEFT JOIN\n \
(SELECT logs.log_id, ARRAY_AGG(users.id) AS attn_to_ids, ARRAY_AGG(CONCAT(users.first_name, ' ', users.last_name)) AS attn_to_names FROM log_records logs\n \
JOIN log_record_attn_tos lrat ON logs.log_id = lrat.log_record_id\n \
JOIN users ON lrat.attn_to_id = users.id\n \
GROUP BY logs.log_id \n \
) at ON logs.log_id = at.log_id\n"

def get_log_records(
self,
page_number,
Expand All @@ -223,22 +241,20 @@ def get_log_records(
employees.first_name AS employee_first_name,\n \
employees.last_name AS employee_last_name,\n \
r.residents,\n \
logs.attn_to,\n \
attn_tos.first_name AS attn_to_first_name,\n \
attn_tos.last_name AS attn_to_last_name,\n \
at.attn_to_names, \n \
buildings.id AS building_id,\n \
buildings.name AS building_name,\n \
t.tag_names, \n \
logs.note,\n \
logs.flagged,\n \
logs.datetime\n \
FROM log_records logs\n \
LEFT JOIN users attn_tos ON logs.attn_to = attn_tos.id\n \
JOIN users employees ON logs.employee_id = employees.id\n \
JOIN buildings on logs.building_id = buildings.id"

sql += self.join_resident_attributes()
sql += self.join_tag_attributes()
sql += self.join_attn_to_attributes()
sql += self.filter_log_records(filters)

sql += f"\nORDER BY datetime {sort_direction}"
Expand All @@ -262,12 +278,12 @@ def count_log_records(self, filters=None):
sql = "SELECT\n \
COUNT(*)\n \
FROM log_records logs\n \
LEFT JOIN users attn_tos ON logs.attn_to = attn_tos.id\n \
JOIN users employees ON logs.employee_id = employees.id\n \
JOIN buildings on logs.building_id = buildings.id"

sql += self.join_resident_attributes()
sql += self.join_tag_attributes()
sql += self.join_attn_to_attributes()
sql += self.filter_log_records(filters)

num_results = db.session.execute(text(sql))
Expand All @@ -287,38 +303,19 @@ def delete_log_record(self, log_id):
)
log_record_to_delete.residents = []
log_record_to_delete.tags = []
log_record_to_delete.attn_tos = []
db.session.delete(log_record_to_delete)
db.session.commit()

def update_log_record(self, log_id, updated_log_record):
if "attn_to" in updated_log_record:
LogRecords.query.filter_by(log_id=log_id).update(
{
LogRecords.attn_to: updated_log_record["attn_to"],
}
)
else:
LogRecords.query.filter_by(log_id=log_id).update(
{
LogRecords.attn_to: None,
}
)
if "tags" in updated_log_record:
log_record = LogRecords.query.filter_by(log_id=log_id).first()
if log_record:
log_record.tags = []
self.construct_tags(log_record, updated_log_record["tags"])
else:
LogRecords.query.filter_by(log_id=log_id).update(
{
LogRecords.tags: None,
}
)

log_record = LogRecords.query.filter_by(log_id=log_id).first()
if log_record:
log_record.residents = []
log_record.tags = []
log_record.attn_tos = []
self.construct_residents(log_record, updated_log_record["residents"])
self.construct_tags(log_record, updated_log_record["tags"])
self.construct_attn_tos(log_record, updated_log_record["attn_tos"])

updated_log_record = LogRecords.query.filter_by(log_id=log_id).update(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""create log records attn to junction table
Revision ID: 51ad56d133e9
Revises: eff8a5a7fda3
Create Date: 2024-04-21 00:02:19.646800
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "51ad56d133e9"
down_revision = "eff8a5a7fda3"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"log_record_attn_tos",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("log_record_id", sa.Integer(), nullable=False),
sa.Column("attn_to_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["attn_to_id"],
["users.id"],
),
sa.ForeignKeyConstraint(
["log_record_id"],
["log_records.log_id"],
),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("log_record_attn_tos")
# ### end Alembic commands ###
8 changes: 4 additions & 4 deletions frontend/src/APIClients/LogRecordAPIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const createLog = async ({
note,
tags,
buildingId,
attnTo,
attnTos,
}: CreateLogRecordParams): Promise<PostLogRecordsResponse> => {
try {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
Expand All @@ -114,7 +114,7 @@ const createLog = async ({
note,
tags,
buildingId,
attnTo,
attnTos,
},
{ headers: { Authorization: bearerToken } },
);
Expand Down Expand Up @@ -148,7 +148,7 @@ const editLogRecord = async ({
note,
tags,
buildingId,
attnTo,
attnTos,
}: EditLogRecordParams): Promise<boolean> => {
try {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
Expand All @@ -163,7 +163,7 @@ const editLogRecord = async ({
datetime,
flagged,
note,
attnTo,
attnTos,
tags,
buildingId,
},
Expand Down
Loading

0 comments on commit 4315888

Please sign in to comment.