bottleneckcrm/backend/macros/models.py
Ashwin 2be3f51efc Add comprehensive tests for ticket closing, calendar feed, deal board, macros, session caches, and web form models
- Implement tests for closing tickets via menu and status picker, ensuring correct API interactions and error handling.
- Create tests for calendar feed provider, verifying throttling behavior and success responses.
- Add tests for deal board provider, covering reading and moving deals, including edge cases for card movement and filters.
- Enhance macro tests to validate action parsing and application to tickets, including error handling.
- Introduce session cache tests to ensure proper cleanup on sign-out and validate session-scoped providers.
- Extend web form model tests to cover lead rotation functionality, ensuring correct serialization and defaults.
2026-09-27 06:28:09 +05:30

100 lines
3.7 KiB
Python

from django.db import models
from common.base import BaseModel
from common.models import Org, Profile, Tags
from common.utils import PRIORITY_CHOICE, STATUS_CHOICE
# A macro may move a ticket to any status a person may choose, which is every
# one but Duplicate: that status belongs to the merge, which also records where
# the ticket went (`cases.workflow.duplicate_refusal`).
MACRO_STATUS_CHOICES = [c for c in STATUS_CHOICE if c[0] != "Duplicate"]
# The four actions, by the names `POST /macros/<id>/apply/` takes in `only`.
ACTION_KEYS = ("status", "priority", "assignees", "tags")
class Macro(BaseModel):
"""Reusable canned response template applied to a case comment composer.
Per `docs/cases/COORDINATION_DECISIONS.md` D2 we inherit BaseModel and
declare our own org FK rather than using BaseOrgModel. Macros are either
`org`-scoped (visible to everyone in the org, admin-managed) or
`personal` (visible only to the owning Profile).
Optional actions ride along with the text: a status, a priority, a set of
assignees (replacing the ticket's) and tags (added to the ticket's). They
are applied through `cases.updates.update_case`, the ticket PATCH's own
path, by `POST /macros/<id>/apply/`. Blank or empty means "no change".
A macro may be actions only, with an empty body.
"""
SCOPE_ORG = "org"
SCOPE_PERSONAL = "personal"
SCOPE_CHOICES = [
(SCOPE_ORG, "Org"),
(SCOPE_PERSONAL, "Personal"),
]
title = models.CharField(max_length=255)
body = models.TextField(blank=True, default="")
scope = models.CharField(max_length=10, choices=SCOPE_CHOICES, default=SCOPE_ORG)
owner = models.ForeignKey(
Profile,
on_delete=models.CASCADE,
related_name="personal_macros",
null=True,
blank=True,
)
is_active = models.BooleanField(default=True)
usage_count = models.PositiveIntegerField(default=0)
set_status = models.CharField(
max_length=64, choices=MACRO_STATUS_CHOICES, blank=True, default=""
)
set_priority = models.CharField(
max_length=64, choices=PRIORITY_CHOICE, blank=True, default=""
)
set_assignees = models.ManyToManyField(
Profile, blank=True, related_name="macros_assigning"
)
# `add_tags` in the API. Named `tags` here because the tag registry
# (`common.views.tags_views._TAGGABLE`) counts usage and merges tags
# through a `tags` relation, and a macro that adds a tag is a use of it.
tags = models.ManyToManyField(Tags, blank=True, related_name="macros_tagging")
org = models.ForeignKey(Org, on_delete=models.CASCADE, related_name="macros")
class Meta:
verbose_name = "Macro"
verbose_name_plural = "Macros"
db_table = "macro"
ordering = ("-updated_at",)
constraints = [
models.CheckConstraint(
condition=(
models.Q(scope="org", owner__isnull=True)
| models.Q(scope="personal", owner__isnull=False)
),
name="macro_scope_owner_consistent",
),
]
indexes = [
models.Index(fields=["org", "scope", "is_active"]),
models.Index(fields=["owner", "-created_at"]),
]
def __str__(self):
return f"{self.title} ({self.scope})"
def action_keys(self):
"""The actions this macro carries, as `ACTION_KEYS` names, in order."""
keys = []
if self.set_status:
keys.append("status")
if self.set_priority:
keys.append("priority")
if self.set_assignees.exists():
keys.append("assignees")
if self.tags.exists():
keys.append("tags")
return keys