From e9f4756e89234b36b7a02f5259d79fffb299d44a Mon Sep 17 00:00:00 2001 From: Horilla Date: Fri, 4 Jul 2025 14:52:39 +0530 Subject: [PATCH] [UPDT] HORILLA: Bring up changes/fix from the master branch --- attendance/models.py | 2 +- .../attendance/attendance/tab_content.html | 11 +- .../work_record/work_record_list.html | 6 +- .../work_record/work_record_view.html | 3 +- base/announcement.py | 88 +++++++++++--- biometric/views.py | 4 +- employee/models.py | 26 +++- horilla_automations/apps.py | 5 +- horilla_automations/models.py | 2 +- horilla_automations/signals.py | 31 +++-- .../horilla_automations/load_automation.html | 6 +- horilla_automations/views/cbvs.py | 2 +- horilla_backup/forms.py | 36 +++--- .../templates/base/auth/group_accordion.html | 9 +- .../templates/base/auth/group_lines.html | 38 +++--- .../templates/base/auth/permission_table.html | 26 ++-- horilla_views/cbv_methods.py | 7 ++ horilla_views/generic/cbv/kanban.py | 3 + horilla_views/generic/cbv/views.py | 12 ++ .../templatetags/generic_template_filters.py | 51 ++++---- leave/views.py | 31 ++++- project/cbv/dashboard.py | 25 ++-- project/cbv/projects.py | 56 ++++----- project/cbv/tasks.py | 93 +++++++------- project/cbv/timesheet.py | 115 ++++++++++-------- project/filters.py | 12 ++ project/models.py | 9 +- project/templates/cbv/tasks/task_filter.html | 15 ++- project/templates/cbv/timesheet/filter.html | 14 +-- project/templates/task/new/filter_task.html | 10 +- .../templates/task/new/task_kanban_view.html | 4 +- recruitment/views/linkedin.py | 41 +++---- templates/index.html | 3 + 33 files changed, 481 insertions(+), 315 deletions(-) create mode 100644 horilla_views/generic/cbv/kanban.py diff --git a/attendance/models.py b/attendance/models.py index 7946cfcca..afc1d997e 100644 --- a/attendance/models.py +++ b/attendance/models.py @@ -223,7 +223,7 @@ class Attendance(HorillaModel): WorkType, null=True, blank=True, - on_delete=models.DO_NOTHING, + on_delete=models.SET_NULL, # 796 verbose_name=_("Work Type"), ) attendance_day = models.ForeignKey( diff --git a/attendance/templates/attendance/attendance/tab_content.html b/attendance/templates/attendance/attendance/tab_content.html index 9896059a3..602989127 100644 --- a/attendance/templates/attendance/attendance/tab_content.html +++ b/attendance/templates/attendance/attendance/tab_content.html @@ -49,7 +49,7 @@
-
@@ -58,7 +58,7 @@ hx-target="#tab_contents"> {% trans "Employee" %}
-
@@ -80,16 +80,15 @@ {% trans "In Date" %}
- {% trans - "Check-Out" %}
+ {% trans "Check-Out" %}
{% trans "Out Date" %}
-
{% trans "Shift" - %}
+
+ {% trans "Shift" %}
{% trans "Work Type" %}
diff --git a/attendance/templates/attendance/work_record/work_record_list.html b/attendance/templates/attendance/work_record/work_record_list.html index eb2522c58..a46c4225e 100644 --- a/attendance/templates/attendance/work_record/work_record_list.html +++ b/attendance/templates/attendance/work_record/work_record_list.html @@ -5,12 +5,8 @@
diff --git a/base/announcement.py b/base/announcement.py index 1140b5233..f6388fba9 100644 --- a/base/announcement.py +++ b/base/announcement.py @@ -110,26 +110,37 @@ def create_announcement(request): employees_from_job = Employee.objects.filter( employee_work_info__job_position_id__in=job_ids ) - employees = employees | Employee.objects.filter( - employee_work_info__department_id__in=departments - ) - employees = employees | Employee.objects.filter( - employee_work_info__job_position_id__in=job_positions - ) - anou.employees.add(*employees) - anou.save() - notify.send( - request.user.employee_get, - recipient=emp_dep, - verb="Your department was mentioned in a post.", - verb_ar="تم ذكر قسمك في منشور.", - verb_de="Ihr Abteilung wurde in einem Beitrag erwähnt.", - verb_es="Tu departamento fue mencionado en una publicación.", - verb_fr="Votre département a été mentionné dans un post.", - redirect="/", - icon="chatbox-ellipses", - ) + all_employees = ( + employees | employees_from_dept | employees_from_job + ).distinct() + announcement.employees.add(*all_employees) + + all_emps = employees_from_dept | employees_from_job | employees + user_map = User.objects.filter(employee_get__in=all_emps).distinct() + + dept_emp_ids = set(employees_from_dept.values_list("id", flat=True)) + job_emp_ids = set(employees_from_job.values_list("id", flat=True)) + direct_emp_ids = set(employees.values_list("id", flat=True)) + + notified_ids = dept_emp_ids.union(job_emp_ids) + direct_only_ids = direct_emp_ids - notified_ids + + sender = request.user.employee_get + + def send_notification(users, verb): + if users.exists(): + notify.send( + sender, + recipient=users, + verb=verb, + verb_ar="لقد تم ذكرك في إعلان.", + verb_de="Sie wurden in einer Ankündigung erwähnt.", + verb_es="Has sido mencionado en un anuncio.", + verb_fr="Vous avez été mentionné dans une annonce.", + redirect="/", + icon="chatbox-ellipses", + ) send_notification( user_map.filter(employee_get__id__in=dept_emp_ids), @@ -147,6 +158,45 @@ def create_announcement(request): messages.success(request, _("Announcement created successfully.")) form = AnnouncementForm() # Reset the form + emp_dep = User.objects.filter( + employee_get__employee_work_info__department_id__in=departments + ) + emp_jobs = User.objects.filter( + employee_get__employee_work_info__job_position_id__in=job_positions + ) + employees = employees | Employee.objects.filter( + employee_work_info__department_id__in=departments + ) + employees = employees | Employee.objects.filter( + employee_work_info__job_position_id__in=job_positions + ) + announcement.employees.add(*employees) + announcement.save() + + notify.send( + request.user.employee_get, + recipient=emp_dep, + verb="Your department was mentioned in a post.", + verb_ar="تم ذكر قسمك في منشور.", + verb_de="Ihr Abteilung wurde in einem Beitrag erwähnt.", + verb_es="Tu departamento fue mencionado en una publicación.", + verb_fr="Votre département a été mentionné dans un post.", + redirect="/", + icon="chatbox-ellipses", + ) + + notify.send( + request.user.employee_get, + recipient=emp_jobs, + verb="Your job position was mentioned in a post.", + verb_ar="تم ذكر وظيفتك في منشور.", + verb_de="Ihre Arbeitsposition wurde in einem Beitrag erwähnt.", + verb_es="Tu puesto de trabajo fue mencionado en una publicación.", + verb_fr="Votre poste de travail a été mentionné dans un post.", + redirect="/", + icon="chatbox-ellipses", + ) + form = AnnouncementForm() return render(request, "announcement/announcement_form.html", {"form": form}) diff --git a/biometric/views.py b/biometric/views.py index abdb085ac..90a69e0cc 100644 --- a/biometric/views.py +++ b/biometric/views.py @@ -2325,9 +2325,9 @@ def zk_biometric_attendance_bulk_logs(devices): datetime=date_time, ) try: - if punch_code in {0, 3, 4, 5}: + if punch_code in {0, 3, 4}: clock_in(request_data) - elif punch_code in {1, 2}: + elif punch_code in {1, 2, 5}: clock_out(request_data) except Exception as error: logger.error( diff --git a/employee/models.py b/employee/models.py index 0a8996d6e..152a2f301 100644 --- a/employee/models.py +++ b/employee/models.py @@ -37,7 +37,7 @@ from employee.methods.duration_methods import format_time, strtime_seconds from horilla import horilla_middlewares from horilla.horilla_middlewares import _thread_locals from horilla.methods import get_horilla_model_class -from horilla.models import HorillaModel +from horilla.models import HorillaModel, has_xss from horilla_audit.methods import get_diff from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog from horilla_views.cbv_methods import render_template @@ -125,6 +125,26 @@ class Employee(models.Model): """ return self.phone + def clean_fields(self, exclude=None): + errors = {} + + # Get the list of fields to exclude from validation + total_exclude = set(exclude or []).union(getattr(self, "xss_exempt_fields", [])) + + for field in self._meta.get_fields(): + if ( + isinstance(field, (models.CharField, models.TextField)) + and field.name not in total_exclude + ): + value = getattr(self, field.name, None) + if value and has_xss(value): + errors[field.name] = ValidationError( + "Potential XSS content detected." + ) + + if errors: + raise ValidationError(errors) + def get_image(self): """ This method is used to return the profile image path of the employee @@ -905,8 +925,8 @@ class EmployeeBankDetails(HorillaModel): branch = models.CharField(max_length=50, null=True) address = models.TextField(max_length=255, null=True) country = models.CharField(max_length=50, blank=True, null=True) - state = models.CharField(max_length=50, blank=True, null=True) - city = models.CharField(max_length=50, blank=True, null=True) + state = models.CharField(max_length=50, blank=True) + city = models.CharField(max_length=50, blank=True) any_other_code1 = models.CharField( max_length=50, verbose_name="Bank Code #1", null=True ) diff --git a/horilla_automations/apps.py b/horilla_automations/apps.py index 86c627862..ab9130e81 100644 --- a/horilla_automations/apps.py +++ b/horilla_automations/apps.py @@ -35,7 +35,10 @@ class HorillaAutomationConfig(AppConfig): MODEL_CHOICES.append((path, model.__name__)) MODEL_CHOICES.append(("employee.models.Employee", "Employee")) MODEL_CHOICES.append( - ("pms.models.EmployeeKeyResult", "Employee Key Results") + ("pms.models.EmployeeKeyResult", "Employee Key Results"), + ) + MODEL_CHOICES.append( + ("pms.models.Comment", "Key Result Comment"), ) MODEL_CHOICES = list(set(MODEL_CHOICES)) diff --git a/horilla_automations/models.py b/horilla_automations/models.py index c78884f2b..f77e2d73f 100644 --- a/horilla_automations/models.py +++ b/horilla_automations/models.py @@ -59,7 +59,7 @@ class MailAutomation(HorillaModel): blank=True, verbose_name=_("Also Send to"), ) - delivary_channel = models.CharField( + delivery_channel = models.CharField( default="email", max_length=50, choices=SEND_OPTIONS, diff --git a/horilla_automations/signals.py b/horilla_automations/signals.py index cf885eb07..2b71486d8 100644 --- a/horilla_automations/signals.py +++ b/horilla_automations/signals.py @@ -399,12 +399,6 @@ def send_mail(request, automation, instance): except Exception as e: logger.error(e) - emails = [str(emp.get_mail()) for emp in employees if emp and emp.get_mail()] - user_ids = [emp.employee_user_id for emp in employees] - - to = emails[:1] - cc = emails[1:] - email_backend = ConfiguredEmailBackend() display_email_name = email_backend.dynamic_from_email_with_display_name if request: @@ -418,10 +412,26 @@ def send_mail(request, automation, instance): if pk_or_text and request and raw_emails: attachments = [] try: - sender = request.user.employee_get + sender: Employee = request.user.employee_get except: sender = None if context_instance: + + # Prevent same person notification on automation + user_ids = [ + emp.employee_user_id + for emp in employees + if sender.employee_user_id.pk != emp.employee_user_id.pk + ] + emails = [ + str(emp.get_mail()) + for emp in employees + if emp and emp.get_mail() + if emp.get_mail() != sender.get_mail() + ] + + to = emails[:1] + cc = emails[1:] if template_attachments := automation.template_attachments.all(): for template_attachment in template_attachments: template_bdy = template.Template(template_attachment.body) @@ -487,9 +497,14 @@ def send_mail(request, automation, instance): logger.error(e) def _send_notification(text): + notify.send( sender, - recipient=user_ids, + recipient=[ + user + for user in user_ids + if sender.employee_user_id.pk != user.pk + ], verb=f"{text}", icon="person-remove", redirect="", diff --git a/horilla_automations/templates/horilla_automations/load_automation.html b/horilla_automations/templates/horilla_automations/load_automation.html index 4fe62859b..f21f4a973 100644 --- a/horilla_automations/templates/horilla_automations/load_automation.html +++ b/horilla_automations/templates/horilla_automations/load_automation.html @@ -40,15 +40,15 @@ {{automation.template_body|safe}}