[UPDT] HORILLA AUTOMATIONS: Add also sent_to field in mail automation

This commit is contained in:
Horilla
2024-12-09 11:12:17 +05:30
parent dc1311cf70
commit 5d6a7c79c6
10 changed files with 128 additions and 37 deletions

View File

@@ -5,10 +5,15 @@ horilla_automations/forms.py
from typing import Any from typing import Any
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _
from base.forms import ModelForm from base.forms import ModelForm
from employee.filters import EmployeeFilter
from employee.models import Employee
from horilla_automations.methods.methods import generate_choices from horilla_automations.methods.methods import generate_choices
from horilla_automations.models import MODEL_CHOICES, MailAutomation from horilla_automations.models import MODEL_CHOICES, MailAutomation
from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField
from horilla_widgets.widgets.select_widgets import HorillaMultiSelectWidget
class AutomationForm(ModelForm): class AutomationForm(ModelForm):
@@ -23,6 +28,19 @@ class AutomationForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields["also_sent_to"] = HorillaMultiSelectField(
queryset=Employee.objects.all(),
required=False,
widget=HorillaMultiSelectWidget(
filter_route_name="employee-widget-filter",
filter_class=EmployeeFilter,
filter_instance_contex_name="f",
filter_template_path="employee_filters.html",
instance=self.instance,
),
label="Also Sent to",
help_text=_("The employees selected here will receive the email as Cc."),
)
if not self.data: if not self.data:
mail_to = [] mail_to = []
@@ -57,6 +75,18 @@ class AutomationForm(ModelForm):
model = MailAutomation model = MailAutomation
fields = "__all__" fields = "__all__"
def clean(self):
cleaned_data = super().clean()
if isinstance(self.fields["also_sent_to"], HorillaMultiSelectField):
self.errors.pop("also_sent_to", None)
employee_data = self.fields["also_sent_to"].queryset.filter(
id__in=self.data.getlist("also_sent_to")
)
cleaned_data["also_sent_to"] = employee_data
return cleaned_data
def save(self, commit: bool = ...) -> Any: def save(self, commit: bool = ...) -> Any:
self.instance: MailAutomation = self.instance self.instance: MailAutomation = self.instance
condition_querystring = self.cleaned_data["condition_querystring"] condition_querystring = self.cleaned_data["condition_querystring"]

View File

@@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _trans
from base.methods import eval_validate from base.methods import eval_validate
from base.models import HorillaMailTemplate from base.models import HorillaMailTemplate
from employee.models import Employee
from horilla.models import HorillaModel from horilla.models import HorillaModel
from horilla_views.cbv_methods import render_template from horilla_views.cbv_methods import render_template
@@ -36,13 +37,20 @@ class MailAutomation(HorillaModel):
mail_to = models.TextField(verbose_name="Mail to") mail_to = models.TextField(verbose_name="Mail to")
mail_details = models.CharField( mail_details = models.CharField(
max_length=250, max_length=250,
help_text="Fill mail template details(reciever/instance, `self` will be the person who trigger the automation)", help_text=_trans(
"Fill mail template details(reciever/instance, `self` will be the person who trigger the automation)"
),
) )
mail_detail_choice = models.TextField(default="", editable=False) mail_detail_choice = models.TextField(default="", editable=False)
trigger = models.CharField(max_length=10, choices=choices) trigger = models.CharField(max_length=10, choices=choices)
# udpate the on_update logic to if and only if when # udpate the on_update logic to if and only if when
# changes in the previous and current value # changes in the previous and current value
mail_template = models.ForeignKey(HorillaMailTemplate, on_delete=models.CASCADE) mail_template = models.ForeignKey(HorillaMailTemplate, on_delete=models.CASCADE)
also_sent_to = models.ManyToManyField(
Employee,
blank=True,
verbose_name=_trans("Also Send to"),
)
template_attachments = models.ManyToManyField( template_attachments = models.ManyToManyField(
HorillaMailTemplate, HorillaMailTemplate,
related_name="template_attachment", related_name="template_attachment",
@@ -89,6 +97,12 @@ class MailAutomation(HorillaModel):
"horilla_automations/mail_to.html", {"instance": self, "mappings": mappings} "horilla_automations/mail_to.html", {"instance": self, "mappings": mappings}
) )
def get_mail_cc_display(self):
employees = self.also_sent_to.all()
return render_template(
"horilla_automations/mail_cc.html", {"employees": employees}
)
def detailed_url(self): def detailed_url(self):
return reverse("automation-detailed-view", kwargs={"pk": self.pk}) return reverse("automation-detailed-view", kwargs={"pk": self.pk})

View File

@@ -40,12 +40,22 @@ def start_automation():
""" """
Automation signals Automation signals
""" """
from base.models import HorillaMailTemplate
from horilla_automations.methods.methods import get_model_class, split_query_string from horilla_automations.methods.methods import get_model_class, split_query_string
from horilla_automations.models import MailAutomation from horilla_automations.models import MailAutomation
@receiver(post_delete, sender=MailAutomation) @receiver(post_delete, sender=MailAutomation)
@receiver(post_save, sender=MailAutomation) @receiver(post_save, sender=MailAutomation)
def automation_pre_create(sender, instance, **kwargs): def automation_signal(sender, instance, **kwargs):
"""
signal method to handle automation post save
"""
start_connection()
track_previous_instance()
@receiver(post_delete, sender=HorillaMailTemplate)
@receiver(post_save, sender=HorillaMailTemplate)
def template_signal(sender, instance, **kwargs):
""" """
signal method to handle automation post save signal method to handle automation post save
""" """
@@ -81,10 +91,10 @@ def start_automation():
) )
previous_bulk_record = getattr(_thread_locals, "previous_bulk_record", None) previous_bulk_record = getattr(_thread_locals, "previous_bulk_record", None)
previous_queryset = None previous_queryset_copy = []
if previous_bulk_record: if previous_bulk_record:
previous_queryset = previous_bulk_record["queryset"] previous_queryset = previous_bulk_record.get("queryset", None)
previous_queryset_copy = previous_bulk_record["queryset_copy"] previous_queryset_copy = previous_bulk_record.get("queryset_copy", [])
bulk_thread = threading.Thread( bulk_thread = threading.Thread(
target=_bulk_update_thread_handler, target=_bulk_update_thread_handler,
@@ -355,6 +365,19 @@ def send_mail(request, automation, instance):
tos = list(filter(None, tos)) tos = list(filter(None, tos))
to = tos[:1] to = tos[:1]
cc = tos[1:] cc = tos[1:]
try:
also_sent_to = automation.also_sent_to.select_related(
"employee_work_info"
).all()
if also_sent_to.exists():
cc.extend(
str(employee.get_mail())
for employee in also_sent_to
if employee.get_mail()
)
except Exception as e:
logger.error(e)
email_backend = ConfiguredEmailBackend() email_backend = ConfiguredEmailBackend()
display_email_name = email_backend.dynamic_from_email_with_display_name display_email_name = email_backend.dynamic_from_email_with_display_name
if request: if request:
@@ -384,7 +407,9 @@ def send_mail(request, automation, instance):
) )
template_bdy = template.Template(mail_template.body) template_bdy = template.Template(mail_template.body)
context = template.Context({"instance": mail_to_instance, "self": sender}) context = template.Context(
{"instance": mail_to_instance, "self": sender, "model_instance": instance}
)
render_bdy = template_bdy.render(context) render_bdy = template_bdy.render(context)
title_template = template.Template(automation.title) title_template = template.Template(automation.title)

View File

@@ -1,32 +1,39 @@
<div id="formContainer"> <div id="formContainer">
{% include "generic/horilla_form.html" %} {% include "generic/horilla_form.html" %}
</div> </div>
<script> <script>
$("#{{view_id}} form button").click(function (e) {
const form = document.getElementById('multipleConditionForm'); $(document).on('click', '.oh-accordion-header', function (event) {
const elements = form.elements; $(this).closest('.oh-accordion').toggleClass('oh-accordion--show');
const queryString = Array.from(elements) });
.filter(element => element.name && !element.disabled)
.map(element => encodeURIComponent(element.name) + '=' + encodeURIComponent(element.value)) $("#{{view_id}} form button").click(function (e) {
.join('&'); const form = document.getElementById('multipleConditionForm');
$("#{{view_id}} form [name=condition_querystring]").val(queryString); const elements = form.elements;
html = $(".note-editable #multipleConditionForm").html() const queryString = Array.from(elements)
$("#{{view_id}} form [name=condition_html]").val(html); .filter(element => element.name && !element.disabled)
}); .map(element => encodeURIComponent(element.name) + '=' + encodeURIComponent(element.value))
$("#dynamic_field_condition").parent().removeClass("col-md-6"); .join('&');
// summernote $("#{{view_id}} form [name=condition_querystring]").val(queryString);
$("#dynamic_field_condition textarea") html = $(".note-editable #multipleConditionForm").html()
.summernote({ $("#{{view_id}} form [name=condition_html]").val(html);
height: 100, });
toolbar: false,
}) $("#dynamic_field_condition").parent().removeClass("col-md-6");
.summernote("code", getHtml()); // summernote
{% if form.instance.pk %} $("#dynamic_field_condition textarea")
$("#id_mail_to").val({{form.instance.mail_to|safe}}).change() .summernote({
$("#dynamic_field_condition .note-editable").html($("<form id='multipleConditionForm'></form>")); height: 100,
$("#dynamic_field_condition .note-editable #multipleConditionForm").html($(`{{form.instance.condition_html|safe}}`)) toolbar: false,
$(".note-editable select").parent().find("span.select2").remove() })
$(".note-editable select").parent().find(".select2-hidden-accessible").removeClass("select2-hidden-accessible") .summernote("code", getHtml());
$(".note-editable select").parent().find("select").select2(); {% if form.instance.pk %}
{% endif %} $("#id_mail_to").val({{ form.instance.mail_to | safe }}).change()
$("#dynamic_field_condition .note-editable").html($("<form id='multipleConditionForm'></form>"));
$("#dynamic_field_condition .note-editable #multipleConditionForm").html($(`{{form.instance.condition_html|safe}}`))
$(".note-editable select").parent().find("span.select2").remove()
$(".note-editable select").parent().find(".select2-hidden-accessible").removeClass("select2-hidden-accessible")
$(".note-editable select").parent().find("select").select2();
{% endif %}
</script> </script>

View File

@@ -0,0 +1,10 @@
{% load i18n %}
{% if employees %}
<ol>
{% for employee in employees %}
<li>{{employee}}</li>
{% endfor %}
</ol>
{% else %}
{% trans "Not Added" %}
{% endif %}

View File

@@ -162,6 +162,7 @@ class AutomationDetailedView(views.HorillaDetailedView):
("Model", "model"), ("Model", "model"),
("Mail Templates", "mail_template"), ("Mail Templates", "mail_template"),
("Mail To", "get_mail_to_display"), ("Mail To", "get_mail_to_display"),
("Mail Cc", "get_mail_cc_display"),
("Trigger", "trigger_display"), ("Trigger", "trigger_display"),
] ]
actions = [ actions = [

View File

@@ -10,6 +10,7 @@ from django import forms
from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField
orginal_template_name = forms.Select.option_template_name
forms.Select.option_template_name = "horilla_widgets/horilla_select_option.html" forms.Select.option_template_name = "horilla_widgets/horilla_select_option.html"

View File

@@ -118,7 +118,7 @@
.select2{ .select2{
width: 100% !important; width: 100% !important;
} }
#slectContainer{{self.attrs.id}} .select2-container .select2-selection{ #selectContainer{{self.attrs.id}} .select2-container .select2-selection{
padding: 5px !important; padding: 5px !important;
max-height: 70px !important; max-height: 70px !important;
overflow: hidden; overflow: hidden;
@@ -138,7 +138,7 @@
border-radius: 6px; border-radius: 6px;
} }
</style> </style>
<div id="slectContainer{{self.attrs.id}}"> <div id="selectContainer{{self.attrs.id}}">
<select name="{{field_name}}" id="{{self.attrs.id}}" {% if required %}required{% endif %} class="w-100 oh-select oh-select2" multiple> <select name="{{field_name}}" id="{{self.attrs.id}}" {% if required %}required{% endif %} class="w-100 oh-select oh-select2" multiple>
{% for instance in queryset %} {% for instance in queryset %}
<option value="{{instance.id}}">{{instance}}</option> <option value="{{instance.id}}">{{instance}}</option>

View File

@@ -200,7 +200,7 @@
}); });
}); });
{% if initial %} {% if initial %}
$('#{{section_id}} #slectContainer{{self.attrs.id}}').find("[name={{field_name}}]").val({{initial|safe}}).change() $('#{{section_id}} #selectContainer{{self.attrs.id}}').find("[name={{field_name}}]").val({{initial|safe}}).change()
{% endif %} {% endif %}
}); });
$("#{{section_id}} #choose-all-user").click(function (e) { $("#{{section_id}} #choose-all-user").click(function (e) {

View File

@@ -36,6 +36,7 @@ class HorillaMultiSelectWidget(forms.Widget):
instance=None, instance=None,
required=False, required=False,
form=None, form=None,
help_text=None,
**kwargs **kwargs
) -> None: ) -> None:
self.filter_route_name = filter_route_name self.filter_route_name = filter_route_name
@@ -45,6 +46,7 @@ class HorillaMultiSelectWidget(forms.Widget):
self.filter_template_path = filter_template_path self.filter_template_path = filter_template_path
self.instance = instance self.instance = instance
self.form = form self.form = form
self.help_text = help_text
super().__init__() super().__init__()
template_name = "horilla_widgets/horilla_multiselect_widget.html" template_name = "horilla_widgets/horilla_multiselect_widget.html"
@@ -73,6 +75,7 @@ class HorillaMultiSelectWidget(forms.Widget):
context["filter_template_path"] = self.filter_template_path context["filter_template_path"] = self.filter_template_path
context["filter_route_name"] = self.filter_route_name context["filter_route_name"] = self.filter_route_name
context["required"] = self.required context["required"] = self.required
context["help_text"] = self.help_text
self.attrs["id"] = ( self.attrs["id"] = (
("id_" + name) if self.attrs.get("id") is None else self.attrs.get("id") ("id_" + name) if self.attrs.get("id") is None else self.attrs.get("id")
) )