diff --git a/base/admin.py b/base/admin.py index 6fcc91533..7ed3127e6 100644 --- a/base/admin.py +++ b/base/admin.py @@ -9,6 +9,7 @@ from simple_history.admin import SimpleHistoryAdmin from base.models import ( Company, Department, + DynamicEmailConfiguration, JobPosition, JobRole, EmployeeShiftSchedule, @@ -41,3 +42,4 @@ admin.site.register(RotatingShift) admin.site.register(RotatingShiftAssign) admin.site.register(ShiftRequest) admin.site.register(WorkTypeRequest) +admin.site.register(DynamicEmailConfiguration) diff --git a/base/backends.py b/base/backends.py new file mode 100644 index 000000000..b011fcff0 --- /dev/null +++ b/base/backends.py @@ -0,0 +1,93 @@ +""" +email_backend.py + +This module is used to write email backends +""" +from django.core.mail.backends.smtp import EmailBackend +from horilla import settings +from base.thread_local_middleware import _thread_locals + + +class ConfiguredEmailBackend(EmailBackend): + def __init__( + self, + host=None, + port=None, + username=None, + password=None, + use_tls=None, + fail_silently=None, + use_ssl=None, + timeout=None, + ssl_keyfile=None, + ssl_certfile=None, + **kwargs + ): + from base.models import DynamicEmailConfiguration + + request = getattr(_thread_locals, "request", None) + compay = None + if request: + compay = request.user.employee_get.get_company() + configuration = DynamicEmailConfiguration.objects.filter( + company_id=compay + ).first() + # Use default settings if configuration is not available + host = configuration.host if configuration else host or settings.EMAIL_HOST + port = configuration.port if configuration else port or settings.EMAIL_PORT + username = ( + configuration.username + if configuration + else username or settings.EMAIL_HOST_USER + ) + password = ( + configuration.password + if configuration + else password or settings.EMAIL_HOST_PASSWORD + ) + use_tls = ( + configuration.use_tls + if configuration + else use_tls or settings.EMAIL_USE_TLS + ) + fail_silently = ( + configuration.fail_silently + if configuration + else fail_silently or getattr(settings,"EMAIL_FAIL_SILENTLY",True) + ) + use_ssl = ( + configuration.use_ssl + if configuration + else use_ssl or getattr(settings,"EMAIL_USE_SSL",None) + ) + timeout = ( + configuration.timeout + if configuration + else timeout or getattr(settings,"EMAIL_TIMEOUT",None) + ) + ssl_keyfile = ( + getattr(configuration, "ssl_keyfile", None) + if configuration + else ssl_keyfile or getattr(settings, "ssl_keyfile", None) + ) + ssl_certfile = ( + getattr(configuration, "ssl_certfile", None) + if configuration + else ssl_keyfile or getattr(settings, "ssl_certfile", None) + ) + super(ConfiguredEmailBackend, self).__init__( + host=host, + port=port, + username=username, + password=password, + use_tls=use_tls, + fail_silently=fail_silently, + use_ssl=use_ssl, + timeout=timeout, + ssl_keyfile=ssl_keyfile, + ssl_certfile=ssl_certfile, + **kwargs + ) + + +__all__ = ["ConfiguredEmailBackend"] diff --git a/base/forms.py b/base/forms.py index 3e65de9c9..441787781 100644 --- a/base/forms.py +++ b/base/forms.py @@ -21,6 +21,7 @@ from employee.models import Employee from base.models import ( Company, Department, + DynamicEmailConfiguration, JobPosition, JobRole, WorkType, @@ -1472,3 +1473,21 @@ class RotatingWorkTypeAssignExportForm(forms.Form): "based_on", ], ) + + + +class DynamicMailConfForm(ModelForm): + """ + DynamicEmailConfiguration + """ + class Meta: + model = DynamicEmailConfiguration + fields = "__all__" + + def as_p(self): + """ + Render the form fields as HTML table rows with Bootstrap styling. + """ + context = {"form": self} + table_html = render_to_string("attendance_form.html", context) + return table_html \ No newline at end of file diff --git a/base/methods.py b/base/methods.py index c38e35028..144cfa4d1 100644 --- a/base/methods.py +++ b/base/methods.py @@ -502,7 +502,10 @@ def reload_queryset(fields): """ for k, v in fields.items(): if isinstance(v, ModelChoiceField): - v.queryset = v.queryset.model.objects.all() + if v.queryset.model == Employee: + v.queryset = v.queryset.model.objects.filter(is_active=True) + else: + v.queryset = v.queryset.model.objects.all() return diff --git a/base/models.py b/base/models.py index 2f01b0195..a3258e139 100644 --- a/base/models.py +++ b/base/models.py @@ -3,13 +3,14 @@ models.py This module is used to register django models """ +from collections.abc import Iterable +from typing import Any import django from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.db import models from base.horilla_company_manager import HorillaCompanyManager - # Create your models here. @@ -49,7 +50,6 @@ class Company(models.Model): date_format = models.CharField(max_length=30, blank=True, null=True) time_format = models.CharField(max_length=20, blank=True, null=True) - class Meta: """ Meta class to add additional options @@ -62,11 +62,13 @@ class Company(models.Model): def __str__(self) -> str: return str(self.company) - + from base.thread_local_middleware import _thread_locals from django import forms + + class Department(models.Model): """ Department model @@ -80,19 +82,23 @@ class Department(models.Model): class Meta: verbose_name = _("Department") verbose_name_plural = _("Departments") - + def clean(self, *args, **kwargs): super().clean(*args, **kwargs) request = getattr(_thread_locals, "request", None) if request and request.POST: - company = request.POST.getlist('company_id', None) - department = request.POST.get('department', None) - if Department.objects.filter(company_id__id__in=company,department = department).exclude(id= self.id).exists(): - raise ValidationError( - "This department already exists in this company" + company = request.POST.getlist("company_id", None) + department = request.POST.get("department", None) + if ( + Department.objects.filter( + company_id__id__in=company, department=department ) - return - + .exclude(id=self.id) + .exists() + ): + raise ValidationError("This department already exists in this company") + return + def save(self, *args, **kwargs): super().save(*args, **kwargs) self.clean(*args, **kwargs) @@ -174,24 +180,26 @@ class WorkType(models.Model): def __str__(self) -> str: return str(self.work_type) - + def clean(self, *args, **kwargs): super().clean(*args, **kwargs) request = getattr(_thread_locals, "request", None) if request and request.POST: - company = request.POST.getlist('company_id', None) - work_type = request.POST.get('work_type', None) - if WorkType.objects.filter(company_id__id__in=company,work_type = work_type).exclude(id= self.id).exists(): - raise ValidationError( - "This work type already exists in this company" - ) - return - + company = request.POST.getlist("company_id", None) + work_type = request.POST.get("work_type", None) + if ( + WorkType.objects.filter(company_id__id__in=company, work_type=work_type) + .exclude(id=self.id) + .exists() + ): + raise ValidationError("This work type already exists in this company") + return + def save(self, *args, **kwargs): super().save(*args, **kwargs) self.clean(*args, **kwargs) return self - + class RotatingWorkType(models.Model): """ @@ -353,19 +361,25 @@ class EmployeeType(models.Model): def __str__(self) -> str: return str(self.employee_type) - + def clean(self, *args, **kwargs): super().clean(*args, **kwargs) request = getattr(_thread_locals, "request", None) if request and request.POST: - company = request.POST.getlist('company_id', None) - employee_type = request.POST.get('employee_type', None) - if EmployeeType.objects.filter(company_id__id__in=company,employee_type = employee_type).exclude(id= self.id).exists(): + company = request.POST.getlist("company_id", None) + employee_type = request.POST.get("employee_type", None) + if ( + EmployeeType.objects.filter( + company_id__id__in=company, employee_type=employee_type + ) + .exclude(id=self.id) + .exists() + ): raise ValidationError( "This employee type already exists in this company" ) - return - + return + def save(self, *args, **kwargs): super().save(*args, **kwargs) self.clean(*args, **kwargs) @@ -429,19 +443,25 @@ class EmployeeShift(models.Model): def __str__(self) -> str: return str(self.employee_shift) - + def clean(self, *args, **kwargs): super().clean(*args, **kwargs) request = getattr(_thread_locals, "request", None) if request and request.POST: - company = request.POST.getlist('company_id', None) - employee_shift = request.POST.get('employee_shift', None) - if EmployeeShift.objects.filter(company_id__id__in=company,employee_shift = employee_shift).exclude(id= self.id).exists(): + company = request.POST.getlist("company_id", None) + employee_shift = request.POST.get("employee_shift", None) + if ( + EmployeeShift.objects.filter( + company_id__id__in=company, employee_shift=employee_shift + ) + .exclude(id=self.id) + .exists() + ): raise ValidationError( "This employee shift already exists in this company" ) - return - + return + def save(self, *args, **kwargs): super().save(*args, **kwargs) self.clean(*args, **kwargs) @@ -827,3 +847,76 @@ class ShiftRequest(models.Model): def __str__(self): return f"{self.employee_id}" + + +class DynamicEmailConfiguration(models.Model): + """ + SingletoneModel to keep the mail server configurations + """ + + host = models.CharField( + blank=True, null=True, max_length=256, verbose_name=_("Email Host") + ) + + port = models.SmallIntegerField(blank=True, null=True, verbose_name=_("Email Port")) + + from_email = models.CharField( + blank=True, null=True, max_length=256, verbose_name=_("Default From Email") + ) + + username = models.CharField( + blank=True, + null=True, + max_length=256, + verbose_name=_("Email Host Username"), + ) + + password = models.CharField( + blank=True, + null=True, + max_length=256, + verbose_name=_("Email Authentication Password"), + ) + + use_tls = models.BooleanField(default=True, verbose_name=_("Use TLS")) + + use_ssl = models.BooleanField(default=False, verbose_name=_("Use SSL")) + + fail_silently = models.BooleanField(default=False, verbose_name=_("Fail Silently")) + + timeout = models.SmallIntegerField( + blank=True, null=True, verbose_name=_("Email Send Timeout (seconds)") + ) + company_id = models.OneToOneField( + Company, on_delete=models.CASCADE, null=True, blank=True + ) + + def clean(self): + if self.use_ssl and self.use_tls: + raise ValidationError( + _( + '"Use TLS" and "Use SSL" are mutually exclusive, ' + "so only set one of those settings to True." + ) + ) + if not self.company_id: + servers_same_company = DynamicEmailConfiguration.objects.filter( + company_id__isnull=True + ).exclude(id=self.id) + if servers_same_company.exists(): + raise ValidationError({"company_id": _("This field is required")}) + + def __str__(self): + return self.username + + def save(self, *args, **kwargs) -> None: + super().save(*args, **kwargs) + servers_same_company = DynamicEmailConfiguration.objects.filter( + company_id=self.company_id + ).exclude(id=self.id) + if servers_same_company.exists(): + self.delete() + return + + class Meta: + verbose_name = _("Email Configuration") diff --git a/base/templates/base/mail_server/form.html b/base/templates/base/mail_server/form.html new file mode 100644 index 000000000..e33fd1437 --- /dev/null +++ b/base/templates/base/mail_server/form.html @@ -0,0 +1,3 @@ +
\ No newline at end of file diff --git a/base/templates/base/mail_server/mail_server.html b/base/templates/base/mail_server/mail_server.html new file mode 100644 index 000000000..a7f47648e --- /dev/null +++ b/base/templates/base/mail_server/mail_server.html @@ -0,0 +1,43 @@ +{% extends 'settings.html' %} {% load i18n %} {% block settings %} + + + + +{% endblock settings %} diff --git a/base/templates/base/mail_server/mail_server_view.html b/base/templates/base/mail_server/mail_server_view.html new file mode 100644 index 000000000..fefb5141b --- /dev/null +++ b/base/templates/base/mail_server/mail_server_view.html @@ -0,0 +1,34 @@ +{% load i18n %} +