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

Back-end for assign-tags mutations #1890

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 2 additions & 4 deletions back/boxtribute_server/authz.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ def authorize_cross_organisation_access(
MUTATIONS_FOR_BETA_LEVEL[3] = MUTATIONS_FOR_BETA_LEVEL[2] + ("deleteBoxes",)
MUTATIONS_FOR_BETA_LEVEL[4] = MUTATIONS_FOR_BETA_LEVEL[3] + (
"moveBoxesToLocation",
"assignTagToBoxes",
"unassignTagFromBoxes",
"assignTagsToBoxes",
"unassignTagsFromBoxes",
)
MUTATIONS_FOR_BETA_LEVEL[5] = MUTATIONS_FOR_BETA_LEVEL[4] + (
"createCustomProduct",
Expand All @@ -359,8 +359,6 @@ def authorize_cross_organisation_access(
"createBeneficiary",
"updateBeneficiary",
"deactivateBeneficiary",
"assignTag",
"unassignTag",
)
MUTATIONS_FOR_BETA_LEVEL[99] = MUTATIONS_FOR_BETA_LEVEL[98] + (
# + mutations for mobile distribution pages
Expand Down
34 changes: 15 additions & 19 deletions back/boxtribute_server/business_logic/statistics/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from ...models.definitions.tags_relation import TagsRelation
from ...models.definitions.transaction import Transaction
from ...models.definitions.unit import Unit
from ...models.utils import compute_age, convert_ids
from ...models.utils import compute_age, convert_ids, execute_sql
from ...utils import in_ci_environment, in_production_environment
from .sql import MOVED_BOXES_QUERY

Expand Down Expand Up @@ -359,25 +359,21 @@ def compute_moved_boxes(base_id):
if in_production_environment() and not in_ci_environment(): # pragma: no cover
# Earliest row ID in tables in 2023
min_history_id = 1_324_559
database = db.replica or db.database
cursor = database.execute_sql(
MOVED_BOXES_QUERY,
(
base_id,
min_history_id,
TargetType.BoxState.name,
TargetType.BoxState.name,
TargetType.OutgoingLocation.name,
TargetType.OutgoingLocation.name,
TargetType.Shipment.name,
base_id,
TargetType.BoxState.name,
base_id,
),

facts = execute_sql(
base_id,
min_history_id,
TargetType.BoxState.name,
TargetType.BoxState.name,
TargetType.OutgoingLocation.name,
TargetType.OutgoingLocation.name,
TargetType.Shipment.name,
base_id,
TargetType.BoxState.name,
base_id,
database=db.replica or db.database,
query=MOVED_BOXES_QUERY,
)
# Turn cursor result into dict (https://stackoverflow.com/a/56219996/3865876)
column_names = [x[0] for x in cursor.description]
facts = [dict(zip(column_names, row)) for row in cursor.fetchall()]
for fact in facts:
fact["tag_ids"] = convert_ids(fact["tag_ids"])

Expand Down
81 changes: 0 additions & 81 deletions back/boxtribute_server/business_logic/tag/crud.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
from peewee import JOIN

from ...db import db
from ...enums import TaggableObjectType, TagType
from ...exceptions import IncompatibleTagTypeAndResourceType
from ...models.definitions.beneficiary import Beneficiary
from ...models.definitions.box import Box
from ...models.definitions.tag import Tag
from ...models.definitions.tags_relation import TagsRelation
from ...models.utils import (
safely_handle_deletion,
save_creation_to_history,
save_update_to_history,
utcnow,
)


Expand Down Expand Up @@ -101,78 +95,3 @@ def delete_tag(*, user_id, tag, now):
TagsRelation.deleted_on.is_null(),
).execute()
return tag


def assign_tag(*, user_id, id, resource_id, resource_type, tag=None):
"""Create TagsRelation entry as cross reference of the tag given by ID, and the
given resource (a box or a beneficiary). Insert timestamp for modification in
resource model.
Validate that tag type and resource type are compatible.
If the requested tag is already assigned to the resource, return the unmodified
resource immediately.
Return the resource.
"""
tag = Tag.get_by_id(id) if tag is None else tag
if (
(tag.type == TagType.Beneficiary) and (resource_type == TaggableObjectType.Box)
) or (
(tag.type == TagType.Box) and (resource_type == TaggableObjectType.Beneficiary)
):
raise IncompatibleTagTypeAndResourceType(tag=tag, resource_type=resource_type)

model = Box if resource_type == TaggableObjectType.Box else Beneficiary
resource = (
model.select(model, TagsRelation.tag)
.join(
TagsRelation,
JOIN.LEFT_OUTER,
on=(
(TagsRelation.object_id == model.id)
& (TagsRelation.object_type == resource_type)
& (TagsRelation.tag == tag.id)
& TagsRelation.deleted_on.is_null()
),
)
.where(model.id == resource_id)
.objects() # make .tag a direct attribute of resource
.get()
)
if resource.tag is not None:
return resource

now = utcnow()
resource.last_modified_by = user_id
resource.last_modified_on = now

with db.database.atomic():
TagsRelation.create(
object_id=resource_id,
object_type=resource_type,
tag=id,
created_on=now,
created_by=user_id,
)
resource.save()
return resource


def unassign_tag(*, user_id, id, resource_id, resource_type):
"""Soft-delete TagsRelation entry defined by given tag ID, resource ID, and resource
type. Insert timestamp for modification in resource model.
Return the resource that the tag was unassigned from.
"""
now = utcnow()
model = Box if resource_type == TaggableObjectType.Box else Beneficiary
resource = model.get_by_id(resource_id)
resource.last_modified_by = user_id
resource.last_modified_on = now

with db.database.atomic():
TagsRelation.update(deleted_on=now, deleted_by=user_id).where(
TagsRelation.tag == id,
TagsRelation.object_id == resource_id,
TagsRelation.object_type == resource_type,
TagsRelation.deleted_on.is_null(),
).execute()
resource.save()
return resource
16 changes: 1 addition & 15 deletions back/boxtribute_server/business_logic/tag/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from ...authz import authorize
from ...models.definitions.tag import Tag
from .crud import assign_tag, create_tag, delete_tag, unassign_tag, update_tag
from .crud import create_tag, delete_tag, update_tag

mutation = MutationType()

Expand All @@ -21,20 +21,6 @@ def resolve_update_tag(*_, update_input):
return update_tag(user_id=g.user.id, tag=tag, **update_input)


@mutation.field("assignTag")
def resolve_assign_tag(*_, assignment_input):
tag = Tag.get_by_id(assignment_input["id"])
authorize(permission="tag_relation:assign", base_id=tag.base_id)
return assign_tag(user_id=g.user.id, tag=tag, **assignment_input)


@mutation.field("unassignTag")
def resolve_unassign_tag(*_, unassignment_input):
tag = Tag.get_by_id(unassignment_input["id"])
authorize(permission="tag_relation:assign", base_id=tag.base_id)
return unassign_tag(user_id=g.user.id, **unassignment_input)


@mutation.field("deleteTag")
def resolve_delete_tag(*_, id):
tag = Tag.get_by_id(id)
Expand Down
34 changes: 19 additions & 15 deletions back/boxtribute_server/business_logic/warehouse/box/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from ....models.definitions.unit import Unit
from ....models.utils import (
BATCH_SIZE,
convert_ids,
save_creation_to_history,
save_update_to_history,
utcnow,
Expand Down Expand Up @@ -440,9 +441,11 @@ def move_boxes_to_location(*, user_id, boxes, location):
return list(Box.select().where(Box.id << box_ids))


def assign_tag_to_boxes(*, user_id, boxes, tag):
"""Add TagsRelation entries for given boxes and tag. Update last_modified_* fields
of the affected boxes.
def assign_missing_tags_to_boxes(*, user_id, boxes):
"""Add TagsRelation entries according to information in `boxes` (a list of tag IDs
to be added per box). The tags must not be already assigned to the boxes.

Update last_modified_* fields of the affected boxes.
Return the list of updated boxes.
"""
if not boxes:
Expand All @@ -451,46 +454,47 @@ def assign_tag_to_boxes(*, user_id, boxes, tag):
now = utcnow()
tags_relations = [
TagsRelation(
object_id=box.id,
object_id=box["id"],
object_type=TaggableObjectType.Box,
tag=tag.id,
tag=tag_id,
created_on=now,
created_by=user_id,
)
for box in boxes
for tag_id in convert_ids(box["missing_tag_ids"])
]

box_ids = [box.id for box in boxes]
box_ids = [box["id"] for box in boxes]
with db.database.atomic():
Box.update(last_modified_on=now, last_modified_by=user_id).where(
Box.id << box_ids
).execute()
TagsRelation.bulk_create(tags_relations, batch_size=BATCH_SIZE)

# Skip re-fetching box data (last_modified_* fields will be outdated in response)
return boxes
return list(Box.select().where(Box.id << box_ids))


def unassign_tags_from_boxes(*, user_id, boxes, tag_ids):
"""Soft-delete TagsRelation rows containing the given boxes and tag IDs. Already
deleted TagsRelations are ignored.

def unassign_tag_from_boxes(*, user_id, boxes, tag):
"""Soft-delete TagsRelation rows containing the given tag. Update last_modified_*
fields of the affected boxes.
Update last_modified_* fields of the affected boxes.
Return the list of updated boxes.
"""
if not boxes:
return []

box_ids = [box.id for box in boxes]
box_ids = {box.id for box in boxes}
now = utcnow()
with db.database.atomic():
Box.update(last_modified_on=now, last_modified_by=user_id).where(
Box.id << box_ids
).execute()
TagsRelation.update(deleted_on=now, deleted_by=user_id).where(
TagsRelation.tag == tag.id,
TagsRelation.tag << tag_ids,
TagsRelation.object_id << box_ids,
TagsRelation.object_type == TaggableObjectType.Box,
TagsRelation.deleted_on.is_null(),
).execute()

# Skip re-fetching box data (last_modified_* fields will be outdated in response)
return boxes
return list(Box.select().where(Box.id << box_ids))
Loading
Loading