[UPDT] HORILLA AUTOMATIONS: Updated automation method and initial load template
This commit is contained in:
@@ -34,6 +34,10 @@ class HorillaAutomationConfig(AppConfig):
|
|||||||
path = f"{model.__module__}.{model.__name__}"
|
path = f"{model.__module__}.{model.__name__}"
|
||||||
MODEL_CHOICES.append((path, model.__name__))
|
MODEL_CHOICES.append((path, model.__name__))
|
||||||
MODEL_CHOICES.append(("employee.models.Employee", "Employee"))
|
MODEL_CHOICES.append(("employee.models.Employee", "Employee"))
|
||||||
|
MODEL_CHOICES.append(
|
||||||
|
("pms.models.EmployeeKeyResult", "Employee Key Results")
|
||||||
|
)
|
||||||
|
|
||||||
MODEL_CHOICES = list(set(MODEL_CHOICES))
|
MODEL_CHOICES = list(set(MODEL_CHOICES))
|
||||||
try:
|
try:
|
||||||
start_automation()
|
start_automation()
|
||||||
|
|||||||
@@ -29,69 +29,128 @@ def get_related_models(model: HorillaModel) -> list:
|
|||||||
return related_models
|
return related_models
|
||||||
|
|
||||||
|
|
||||||
|
from horilla_automations.methods.recursive_relation import (
|
||||||
|
get_forward_relation_paths_separated,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_choices(model_path):
|
def generate_choices(model_path):
|
||||||
|
"""
|
||||||
|
Generate mail to choice
|
||||||
|
"""
|
||||||
module_name, class_name = model_path.rsplit(".", 1)
|
module_name, class_name = model_path.rsplit(".", 1)
|
||||||
|
|
||||||
module = __import__(module_name, fromlist=[class_name])
|
module = __import__(module_name, fromlist=[class_name])
|
||||||
model_class: Employee = getattr(module, class_name)
|
model_class: Employee = getattr(module, class_name)
|
||||||
|
fk_relation, m2m_relation = get_forward_relation_paths_separated(
|
||||||
|
model_class, Employee
|
||||||
|
)
|
||||||
|
all_fields = fk_relation + m2m_relation
|
||||||
|
|
||||||
to_fields = []
|
all_mail_to_field = []
|
||||||
mail_details_choice = []
|
mail_details_choice = []
|
||||||
|
for field_tuple in all_fields:
|
||||||
for field in list(model_class._meta.fields) + list(model_class._meta.many_to_many):
|
if not getattr(field_tuple[1], "exclude_from_automation", False):
|
||||||
if not getattr(field, "exclude_from_automation", False):
|
all_mail_to_field.append(
|
||||||
related_model = field.related_model
|
(
|
||||||
models = [Employee]
|
f"{field_tuple[0]}__get_email",
|
||||||
if recruitment_installed:
|
f"({field_tuple[1].model.__name__}) {field_tuple[1].verbose_name.capitalize().replace(' id','')}'s mail ",
|
||||||
models.append(Candidate)
|
|
||||||
if related_model in models:
|
|
||||||
email_field = (
|
|
||||||
f"{field.name}__get_email",
|
|
||||||
f"{field.verbose_name.capitalize().replace(' id','')} mail field ",
|
|
||||||
)
|
)
|
||||||
mail_detail = None
|
)
|
||||||
if not isinstance(field, django_models.ManyToManyField):
|
if not field_tuple[1].many_to_many:
|
||||||
mail_detail = (
|
mail_details_choice += [
|
||||||
f"{field.name}__pk",
|
(
|
||||||
field.verbose_name.capitalize().replace(" id", "")
|
f"{field_tuple[0]}__pk",
|
||||||
+ "(Template context)",
|
f"{field_tuple[1].verbose_name.capitalize().replace(' id','')} (Template context)",
|
||||||
)
|
),
|
||||||
if field.related_model == Employee:
|
]
|
||||||
to_fields.append(
|
# Adding reporting manager if the related model is Employee
|
||||||
|
if field_tuple[1].related_model == Employee:
|
||||||
|
# reporting manager mail to
|
||||||
|
all_mail_to_field.append(
|
||||||
(
|
(
|
||||||
f"{field.name}__employee_work_info__reporting_manager_id__get_email",
|
f"{field_tuple[0]}__employee_work_info__reporting_manager_id__get_email",
|
||||||
f"{field.verbose_name.capitalize().replace(' id','')}'s reporting manager",
|
f"{field_tuple[1].verbose_name.capitalize().replace(' id','')} / Reporting Manager's mail ",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if not isinstance(field, django_models.ManyToManyField):
|
# reporting manager template context
|
||||||
mail_details_choice.append(
|
mail_details_choice.append(
|
||||||
(
|
(
|
||||||
f"{field.name}__employee_work_info__reporting_manager_id__pk",
|
f"{field_tuple[0]}__employee_work_info__reporting_manager_id__pk",
|
||||||
f"{field.verbose_name.capitalize().replace(' id','')}'s reporting manager (Template context)",
|
f"{field_tuple[1].verbose_name.capitalize().replace(' id','')} / Reporting Manager (Template context) ",
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
to_fields.append(email_field)
|
if model_class == Employee:
|
||||||
if mail_detail:
|
# reporting manager mail to
|
||||||
mail_details_choice.append(mail_detail)
|
all_mail_to_field.append(
|
||||||
text_area_fields = get_textfield_paths(model_class)
|
|
||||||
mail_details_choice = mail_details_choice + text_area_fields
|
|
||||||
models = [Employee]
|
|
||||||
if recruitment_installed:
|
|
||||||
models.append(Candidate)
|
|
||||||
if model_class in models:
|
|
||||||
to_fields.append(
|
|
||||||
(
|
(
|
||||||
"get_email",
|
f"employee_work_info__reporting_manager_id__get_email",
|
||||||
f"{model_class.__name__}'s mail",
|
f"Reporting Manager's mail ",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mail_to_related_fields = getattr(model_class, "mail_to_related_fields", [])
|
if model_path == "employee.models.Employee":
|
||||||
to_fields = to_fields + mail_to_related_fields
|
all_mail_to_field.append(("get_email", "Employee's mail"))
|
||||||
mail_details_choice.append(("pk", model_class.__name__))
|
elif model_path == "recruitment.models.Candidate":
|
||||||
|
all_mail_to_field.append(("get_email", "Candidate's mail"))
|
||||||
|
|
||||||
|
to_fields = []
|
||||||
|
# mail_details_choice = []
|
||||||
|
|
||||||
|
# for field in list(model_class._meta.fields) + list(model_class._meta.many_to_many):
|
||||||
|
# if not getattr(field, "exclude_from_automation", False):
|
||||||
|
# related_model = field.related_model
|
||||||
|
# models = [Employee]
|
||||||
|
# if recruitment_installed:
|
||||||
|
# models.append(Candidate)
|
||||||
|
# if related_model in models:
|
||||||
|
# email_field = (
|
||||||
|
# f"{field.name}__get_email",
|
||||||
|
# f"{field.verbose_name.capitalize().replace(' id','')} mail field ",
|
||||||
|
# )
|
||||||
|
# mail_detail = None
|
||||||
|
# if not isinstance(field, django_models.ManyToManyField):
|
||||||
|
# mail_detail = (
|
||||||
|
# f"{field.name}__pk",
|
||||||
|
# field.verbose_name.capitalize().replace(" id", "")
|
||||||
|
# + "(Template context)",
|
||||||
|
# )
|
||||||
|
# if field.related_model == Employee:
|
||||||
|
# to_fields.append(
|
||||||
|
# (
|
||||||
|
# f"{field.name}__employee_work_info__reporting_manager_id__get_email",
|
||||||
|
# f"{field.verbose_name.capitalize().replace(' id','')}'s reporting manager",
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
# if not isinstance(field, django_models.ManyToManyField):
|
||||||
|
# mail_details_choice.append(
|
||||||
|
# (
|
||||||
|
# f"{field.name}__employee_work_info__reporting_manager_id__pk",
|
||||||
|
# f"{field.verbose_name.capitalize().replace(' id','')}'s reporting manager (Template context)",
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
|
# to_fields.append(email_field)
|
||||||
|
# if mail_detail:
|
||||||
|
# mail_details_choice.append(mail_detail)
|
||||||
|
text_area_fields = get_textfield_paths(model_class)
|
||||||
|
mail_details_choice = mail_details_choice + text_area_fields
|
||||||
|
# models = [Employee]
|
||||||
|
# if recruitment_installed:
|
||||||
|
# models.append(Candidate)
|
||||||
|
# if model_class in models:
|
||||||
|
# to_fields.append(
|
||||||
|
# (
|
||||||
|
# "get_email",
|
||||||
|
# f"{model_class.__name__}'s mail ({model_class.__name__})",
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
# mail_to_related_fields = getattr(model_class, "mail_to_related_fields", [])
|
||||||
|
# to_fields = to_fields + mail_to_related_fields
|
||||||
|
# mail_details_choice.append(("pk", model_class.__name__))
|
||||||
|
|
||||||
to_fields = list(set(to_fields))
|
to_fields = list(set(to_fields))
|
||||||
return to_fields, mail_details_choice, model_class
|
|
||||||
|
return all_mail_to_field, mail_details_choice, model_class
|
||||||
|
|
||||||
|
|
||||||
def get_model_class(model_path):
|
def get_model_class(model_path):
|
||||||
|
|||||||
180
horilla_automations/methods/recursive_relation.py
Normal file
180
horilla_automations/methods/recursive_relation.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
"""
|
||||||
|
horilla_automation/recursive_relation.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField
|
||||||
|
from django.db.models.fields.reverse_related import (
|
||||||
|
ManyToOneRel,
|
||||||
|
ManyToManyRel,
|
||||||
|
OneToOneRel,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set a recursion depth limit to prevent cycles
|
||||||
|
|
||||||
|
MAX_DEPTH = 4
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_relation_paths(source_model, target_model, max_depth=5):
|
||||||
|
relation_paths = []
|
||||||
|
|
||||||
|
def walk(model, path, visited_models, depth):
|
||||||
|
if depth > max_depth or model in visited_models or is_history_model(model):
|
||||||
|
return
|
||||||
|
|
||||||
|
visited_models.add(model)
|
||||||
|
|
||||||
|
for field in model._meta.get_fields():
|
||||||
|
if not field.is_relation:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Forward relations
|
||||||
|
if not field.auto_created:
|
||||||
|
related_model = field.related_model
|
||||||
|
if not related_model or is_history_model(related_model):
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_path = f"{path}__{field.name}" if path else field.name
|
||||||
|
if related_model == target_model:
|
||||||
|
relation_paths.append(new_path)
|
||||||
|
else:
|
||||||
|
walk(related_model, new_path, visited_models.copy(), depth + 1)
|
||||||
|
|
||||||
|
# Reverse relations (related_name or default accessor)
|
||||||
|
elif isinstance(field, ForeignObjectRel):
|
||||||
|
related_model = field.related_model
|
||||||
|
if not related_model or is_history_model(related_model):
|
||||||
|
continue
|
||||||
|
|
||||||
|
accessor_name = field.get_accessor_name()
|
||||||
|
new_path = f"{path}__{accessor_name}" if path else accessor_name
|
||||||
|
if related_model == target_model:
|
||||||
|
relation_paths.append(new_path)
|
||||||
|
else:
|
||||||
|
walk(related_model, new_path, visited_models.copy(), depth + 1)
|
||||||
|
|
||||||
|
walk(source_model, "", set(), 0)
|
||||||
|
return relation_paths
|
||||||
|
|
||||||
|
|
||||||
|
def is_history_model(model):
|
||||||
|
return (
|
||||||
|
model._meta.model_name.endswith("_history")
|
||||||
|
or model._meta.app_label == "simple_history"
|
||||||
|
or model.__name__.lower().endswith("history")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_simple_relation_paths(source_model, target_model, max_depth=5):
|
||||||
|
results = []
|
||||||
|
all_paths = set()
|
||||||
|
|
||||||
|
def walk(model, path, visited_models, depth):
|
||||||
|
if depth > max_depth or model in visited_models:
|
||||||
|
return
|
||||||
|
|
||||||
|
visited_models = visited_models | {model}
|
||||||
|
|
||||||
|
for field in model._meta.get_fields():
|
||||||
|
if not field.is_relation or isinstance(field, ManyToManyField):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip fields without a valid remote_field or related_model
|
||||||
|
remote = getattr(field, "remote_field", None)
|
||||||
|
related_model = getattr(remote, "model", None)
|
||||||
|
if related_model is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Determine accessor name
|
||||||
|
if field.auto_created and not field.concrete:
|
||||||
|
accessor = field.get_accessor_name()
|
||||||
|
else:
|
||||||
|
accessor = field.name
|
||||||
|
|
||||||
|
print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
|
||||||
|
print(accessor)
|
||||||
|
print(field)
|
||||||
|
|
||||||
|
new_path = f"{path}__{accessor}" if path else accessor
|
||||||
|
|
||||||
|
if related_model == target_model:
|
||||||
|
results.append(new_path)
|
||||||
|
all_paths.add(new_path)
|
||||||
|
elif depth + 1 < max_depth:
|
||||||
|
walk(related_model, new_path, visited_models, depth + 1)
|
||||||
|
|
||||||
|
walk(source_model, "", set(), 0)
|
||||||
|
|
||||||
|
# Post-process to remove paths that are strict supersets of others
|
||||||
|
unique_paths = []
|
||||||
|
for path in sorted(results, key=lambda p: p.count("__")): # shortest first
|
||||||
|
if not any(
|
||||||
|
path != other and path.startswith(other + "__") for other in unique_paths
|
||||||
|
):
|
||||||
|
unique_paths.append(path)
|
||||||
|
|
||||||
|
return unique_paths
|
||||||
|
|
||||||
|
|
||||||
|
def is_history_model(model):
|
||||||
|
return (
|
||||||
|
model._meta.model_name.endswith("_history")
|
||||||
|
or model._meta.app_label == "simple_history"
|
||||||
|
or model.__name__.lower().endswith("history")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_forward_relation_paths_separated(source_model, target_model, max_depth=5):
|
||||||
|
"""
|
||||||
|
Recursively find forward relation paths from source_model to target_model,
|
||||||
|
separating ForeignKey and ManyToManyField paths, excluding history models.
|
||||||
|
"""
|
||||||
|
fk_paths = []
|
||||||
|
m2m_paths = []
|
||||||
|
|
||||||
|
def walk(model, path, visited_models, depth):
|
||||||
|
if depth > max_depth or model in visited_models or is_history_model(model):
|
||||||
|
return
|
||||||
|
|
||||||
|
visited_models.add(model)
|
||||||
|
|
||||||
|
for field in model._meta.get_fields():
|
||||||
|
if not field.is_relation or field.auto_created:
|
||||||
|
continue # Skip non-relational fields and reverse relations
|
||||||
|
|
||||||
|
related_model = field.related_model
|
||||||
|
if not related_model or is_history_model(related_model):
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_path = f"{path}__{field.name}" if path else field.name
|
||||||
|
|
||||||
|
if related_model == target_model:
|
||||||
|
if field.many_to_many:
|
||||||
|
m2m_paths.append((new_path, field))
|
||||||
|
else:
|
||||||
|
fk_paths.append((new_path, field))
|
||||||
|
else:
|
||||||
|
walk(related_model, new_path, visited_models.copy(), depth + 1)
|
||||||
|
|
||||||
|
walk(source_model, "", set(), 0)
|
||||||
|
return fk_paths, m2m_paths
|
||||||
|
|
||||||
|
|
||||||
|
_a = {
|
||||||
|
"pms.models.EmployeeKeyResult": {
|
||||||
|
"mail_to": [
|
||||||
|
("employee_objective_id__employee_id__get_email", "Employee's mail"),
|
||||||
|
(
|
||||||
|
"employee_objective_id__employee_id__employee_work_inf__reporting_manager_id__get_email",
|
||||||
|
"Reporting manager's mail",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"employee_objective_id__objective_id__managers__get_email",
|
||||||
|
"Objective manager's mail",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"mail_instance": [
|
||||||
|
("employee_objective_id__employee_id", "Employee"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ class MailAutomation(HorillaModel):
|
|||||||
title = models.CharField(max_length=256, unique=True)
|
title = models.CharField(max_length=256, unique=True)
|
||||||
method_title = models.CharField(max_length=50, editable=False)
|
method_title = models.CharField(max_length=50, editable=False)
|
||||||
model = models.CharField(max_length=100, choices=MODEL_CHOICES, null=False)
|
model = models.CharField(max_length=100, choices=MODEL_CHOICES, null=False)
|
||||||
mail_to = models.TextField(verbose_name="Mail to")
|
mail_to = models.TextField(verbose_name="Mail to/Notify to")
|
||||||
mail_details = models.CharField(
|
mail_details = models.CharField(
|
||||||
max_length=250,
|
max_length=250,
|
||||||
help_text=_trans(
|
help_text=_trans(
|
||||||
|
|||||||
@@ -449,8 +449,10 @@ def send_mail(request, automation, instance):
|
|||||||
title_template = template.Template(automation.title)
|
title_template = template.Template(automation.title)
|
||||||
title_context = template.Context({"instance": instance, "self": sender})
|
title_context = template.Context({"instance": instance, "self": sender})
|
||||||
render_title = title_template.render(title_context)
|
render_title = title_template.render(title_context)
|
||||||
|
print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
|
||||||
soup = BeautifulSoup(render_bdy, "html.parser")
|
soup = BeautifulSoup(render_bdy, "html.parser")
|
||||||
plain_text = soup.get_text(separator="\n")
|
plain_text = soup.get_text(separator="\n")
|
||||||
|
print(render_title)
|
||||||
|
|
||||||
email = EmailMessage(
|
email = EmailMessage(
|
||||||
subject=render_title,
|
subject=render_title,
|
||||||
@@ -466,11 +468,14 @@ def send_mail(request, automation, instance):
|
|||||||
|
|
||||||
def _send_mail(email):
|
def _send_mail(email):
|
||||||
try:
|
try:
|
||||||
|
print("MAIL SENTTTTTTTTTTTTT")
|
||||||
email.send()
|
email.send()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print("ERRRRRR")
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
def _send_notification(text):
|
def _send_notification(text):
|
||||||
|
print(text)
|
||||||
notify.send(
|
notify.send(
|
||||||
sender,
|
sender,
|
||||||
recipient=user_ids,
|
recipient=user_ids,
|
||||||
@@ -478,6 +483,7 @@ def send_mail(request, automation, instance):
|
|||||||
icon="person-remove",
|
icon="person-remove",
|
||||||
redirect="",
|
redirect="",
|
||||||
)
|
)
|
||||||
|
print("NOTIFICATION SENTTTTTTTTTTTTTT")
|
||||||
|
|
||||||
if automation.delivary_channel != "notification":
|
if automation.delivary_channel != "notification":
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ function getToMail(element) {
|
|||||||
tr = `
|
tr = `
|
||||||
<tr class="dynamic-condition-row">
|
<tr class="dynamic-condition-row">
|
||||||
<td class="sn">${totalRows}</td>
|
<td class="sn">${totalRows}</td>
|
||||||
<td id="conditionalField"></td>
|
<td id="conditionalField">
|
||||||
|
<div hidden>${JSON.stringify(response.serialized_form)}</div>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<select name="condition" onchange="addSelectedAttr(event)" class="w-100">
|
<select name="condition" onchange="addSelectedAttr(event)" class="w-100">
|
||||||
<option value="==">==</option>
|
<option value="==">==</option>
|
||||||
@@ -127,9 +129,7 @@ function getHtml() {
|
|||||||
|
|
||||||
function populateSelect(data, response) {
|
function populateSelect(data, response) {
|
||||||
const selectElement = $(
|
const selectElement = $(
|
||||||
`<select class="w-100" onchange="updateValue($(this));addSelectedAttr(event)" data-response='${JSON.stringify(
|
`<select class="w-100" onchange="updateValue($(this));addSelectedAttr(event)"></select>`
|
||||||
response.serialized_form
|
|
||||||
).toString()}'></select>`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
@@ -139,17 +139,21 @@ function populateSelect(data, response) {
|
|||||||
selectElement.append($option);
|
selectElement.append($option);
|
||||||
});
|
});
|
||||||
return selectElement;
|
return selectElement;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateValue(element) {
|
function updateValue(element) {
|
||||||
field = element.val();
|
console.log(">>>>>>>>>>>>>>>>>>>>>>")
|
||||||
attr = element.attr("data-response");
|
json = element.closest('table').find('#conditionalField div[hidden]').text()
|
||||||
attr = attr
|
console.log(json)
|
||||||
.replace(/[\u0000-\u001F\u007F-\u009F]/g, "")
|
|
||||||
.replace(/\\n/g, "\\\\n")
|
|
||||||
.replace(/\\t/g, "\\\\t");
|
|
||||||
|
|
||||||
response = JSON.parse(attr);
|
field = element.val();
|
||||||
|
// attr = json
|
||||||
|
// .replace(/[\u0000-\u001F\u007F-\u009F]/g, "")
|
||||||
|
// .replace(/\\n/g, "\\\\n")
|
||||||
|
// .replace(/\\t/g, "\\\\t");
|
||||||
|
|
||||||
|
response = JSON.parse(json);
|
||||||
|
|
||||||
valueElement = createElement(field, response);
|
valueElement = createElement(field, response);
|
||||||
element.closest("tr").find(".condition-value-th").html("");
|
element.closest("tr").find(".condition-value-th").html("");
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
{% load static i18n %}
|
||||||
|
<div class="oh-modal__dialog-header">
|
||||||
|
<span class="oh-modal__dialog-title">{% trans "Load Automations" %}</span>
|
||||||
|
<button class="oh-modal__close" aria-label="Close">
|
||||||
|
<ion-icon name="close-outline"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="oh-modal__dialog-body">
|
||||||
|
<div class="oh-card">
|
||||||
|
<form hx-post="{{request.path}}" hx-target="#genericModalBody">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div
|
||||||
|
class="oh-layout--grid-3"
|
||||||
|
style="
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(48%, 1fr));
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{% for automation in automations %}
|
||||||
|
<div class="oh-card rounded">
|
||||||
|
<div class="oh-kanban-card__details">
|
||||||
|
<div class="d-flex-justify-between mb-2">
|
||||||
|
<span class="oh-kanban-card__title"
|
||||||
|
>{{automation.fields.title}}</span
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<input
|
||||||
|
name="{{automation.pk}}"
|
||||||
|
type="checkbox"
|
||||||
|
class="custom-radio-checkmark"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="oh-kanban-card__subtitle truncated-text"
|
||||||
|
style="
|
||||||
|
height: 120px;
|
||||||
|
white-space: wrap;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{automation.template_body|safe}}
|
||||||
|
</div>
|
||||||
|
<ul class="oh-faq__tags m-0">
|
||||||
|
{% if automation.fields.delivary_channel == 'email' %}
|
||||||
|
<li class="oh-faq__tag text-light bg-primary">
|
||||||
|
{% trans "Email" %}
|
||||||
|
</li>
|
||||||
|
{% elif automation.fields.delivary_channel == 'notification' %}
|
||||||
|
<li class="oh-faq__tag text-light bg-danger">
|
||||||
|
{% trans "Notification" %}
|
||||||
|
</li>
|
||||||
|
{% elif automation.fields.delivary_channel == 'both' %}
|
||||||
|
<li class="oh-faq__tag text-light bg-primary">
|
||||||
|
{% trans "Email" %}
|
||||||
|
</li>
|
||||||
|
<li class="oh-faq__tag text-light bg-danger">
|
||||||
|
{% trans "Notification" %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-row-reverse">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="oh-btn oh-btn--secondary mt-2 mr-0 pl-4 pr-5 oh-btn--w-100-resp"
|
||||||
|
>
|
||||||
|
{% trans "Add" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -47,4 +47,9 @@ urlpatterns = [
|
|||||||
views.delete_automation,
|
views.delete_automation,
|
||||||
name="delete-automation",
|
name="delete-automation",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"load-automations",
|
||||||
|
cbvs.LoadAutomationsView.as_view(),
|
||||||
|
name="load-automations",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,11 +2,20 @@
|
|||||||
horilla_automations/views/cbvs.py
|
horilla_automations/views/cbvs.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.urls import reverse_lazy
|
from django.core import serializers
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _trans
|
from django.utils.translation import gettext_lazy as _trans
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
|
from base.models import HorillaMailTemplate
|
||||||
from horilla.decorators import login_required, permission_required
|
from horilla.decorators import login_required, permission_required
|
||||||
from horilla_automations import models
|
from horilla_automations import models
|
||||||
from horilla_automations.filters import AutomationFilter
|
from horilla_automations.filters import AutomationFilter
|
||||||
@@ -45,12 +54,38 @@ class AutomationNavView(views.HorillaNavView):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.create_attrs = f"""
|
self.actions = []
|
||||||
hx-get="{reverse_lazy("create-automation")}"
|
if self.request.user.has_perm("horilla_automation.add_mailautomation"):
|
||||||
hx-target="#genericModalBody"
|
self.create_attrs = f"""
|
||||||
data-target="#genericModal"
|
hx-get="{reverse_lazy("create-automation")}"
|
||||||
data-toggle="oh-modal-toggle"
|
hx-target="#genericModalBody"
|
||||||
"""
|
data-target="#genericModal"
|
||||||
|
data-toggle="oh-modal-toggle"
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.actions.append(
|
||||||
|
{
|
||||||
|
"action": "Load Automations",
|
||||||
|
"attrs": f"""
|
||||||
|
data-toggle="oh-modal-toggle"
|
||||||
|
data-target="#genericModal"
|
||||||
|
hx-target="#genericModalBody"
|
||||||
|
hx-get="{reverse_lazy('load-automations')}"
|
||||||
|
style="cursor: pointer;"
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.actions.append(
|
||||||
|
{
|
||||||
|
"action": "Refresh Automations",
|
||||||
|
"attrs": f"""
|
||||||
|
hx-get="{reverse_lazy('mail-automations-list-view')}"
|
||||||
|
hx-target="#listContainer"
|
||||||
|
class="oh-btn oh-btn--light-bkg"
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
nav_title = _trans("Automations")
|
nav_title = _trans("Automations")
|
||||||
search_url = reverse_lazy("mail-automations-list-view")
|
search_url = reverse_lazy("mail-automations-list-view")
|
||||||
@@ -190,3 +225,102 @@ class AutomationDetailedView(views.HorillaDetailedView):
|
|||||||
""",
|
""",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name="dispatch")
|
||||||
|
@method_decorator(
|
||||||
|
permission_required("horilla_automation.add_mailautomation"), name="dispatch"
|
||||||
|
)
|
||||||
|
class LoadAutomationsView(View):
|
||||||
|
template_name = "horilla_automations/load_automation.html"
|
||||||
|
template_file = os.path.join(settings.BASE_DIR, "load_data", "mail_templates.json")
|
||||||
|
automation_file = os.path.join(
|
||||||
|
settings.BASE_DIR, "load_data", "mail_automations.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
def load_json_files(self):
|
||||||
|
with open(self.template_file, "r") as tf:
|
||||||
|
templates_raw = json.load(tf)
|
||||||
|
with open(self.automation_file, "r") as af:
|
||||||
|
automations_raw = json.load(af)
|
||||||
|
return templates_raw, automations_raw
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
templates_raw, automations_raw = self.load_json_files()
|
||||||
|
|
||||||
|
template_lookup = {item["pk"]: item["fields"]["body"] for item in templates_raw}
|
||||||
|
|
||||||
|
processed_automations = []
|
||||||
|
for automation in automations_raw:
|
||||||
|
processed = automation.copy()
|
||||||
|
template_pk = automation["fields"].get("mail_template")
|
||||||
|
processed["template_body"] = template_lookup.get(template_pk, "")
|
||||||
|
processed_automations.append(processed)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
self.template_name,
|
||||||
|
{"automations": processed_automations},
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
templates_raw, automations_raw = self.load_json_files()
|
||||||
|
|
||||||
|
template_lookup = {item["pk"]: item["fields"]["body"] for item in templates_raw}
|
||||||
|
|
||||||
|
selected_ids = [int(k) for k in request.POST.keys() if k.isdigit()]
|
||||||
|
selected_automations = [a for a in automations_raw if a["pk"] in selected_ids]
|
||||||
|
|
||||||
|
required_template_pks = {
|
||||||
|
a["fields"].get("mail_template")
|
||||||
|
for a in selected_automations
|
||||||
|
if a["fields"].get("mail_template")
|
||||||
|
}
|
||||||
|
|
||||||
|
for template_json in templates_raw:
|
||||||
|
if template_json["pk"] in required_template_pks:
|
||||||
|
template_data = list(
|
||||||
|
serializers.deserialize("json", json.dumps([template_json]))
|
||||||
|
)[0].object
|
||||||
|
existing = HorillaMailTemplate.objects.filter(
|
||||||
|
title=template_data.title
|
||||||
|
).first()
|
||||||
|
if not existing:
|
||||||
|
template_data.pk = None
|
||||||
|
template_data.save()
|
||||||
|
|
||||||
|
for automation_json in selected_automations:
|
||||||
|
deserialized = list(
|
||||||
|
serializers.deserialize("json", json.dumps([automation_json]))
|
||||||
|
)[0]
|
||||||
|
automation_obj = deserialized.object
|
||||||
|
|
||||||
|
template_pk = automation_json["fields"].get("mail_template")
|
||||||
|
template_body = template_lookup.get(template_pk)
|
||||||
|
mail_template = HorillaMailTemplate.objects.filter(
|
||||||
|
body=template_body
|
||||||
|
).first()
|
||||||
|
automation_obj.mail_template = mail_template
|
||||||
|
|
||||||
|
if not models.MailAutomation.objects.filter(
|
||||||
|
title=automation_obj.title
|
||||||
|
).exists():
|
||||||
|
automation_obj.pk = None
|
||||||
|
automation_obj.save()
|
||||||
|
|
||||||
|
messages.success(
|
||||||
|
request, f"Automation '{automation_obj.title}' added successfully."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messages.warning(
|
||||||
|
request, f"Automation '{automation_obj.title}' already exists."
|
||||||
|
)
|
||||||
|
|
||||||
|
script = """
|
||||||
|
<script>
|
||||||
|
$("#reloadMessagesButton").click();
|
||||||
|
$('#applyFilter').click();
|
||||||
|
$('.oh-modal--show').first().removeClass('oh-modal--show');
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
return HttpResponse(script)
|
||||||
|
|||||||
Reference in New Issue
Block a user