""" forms.py This module is used to register forms for base module """ import calendar import os from typing import Any, Dict import uuid import datetime from datetime import timedelta from django.contrib.auth import authenticate from django import forms from django.contrib.auth.models import Group, Permission from django.forms import DateInput from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy as _trans from django.template.loader import render_to_string from employee.models import Employee from base.models import ( Company, Department, JobPosition, JobRole, WorkType, EmployeeType, EmployeeShift, EmployeeShiftSchedule, RotatingShift, RotatingShiftAssign, RotatingWorkType, RotatingWorkTypeAssign, WorkTypeRequest, ShiftRequest, EmployeeShiftDay, ) # your form here def validate_time_format(value): """ this method is used to validate the format of duration like fields. """ if len(value) > 6: raise ValidationError(_("Invalid format, it should be HH:MM format")) try: hour, minute = value.split(":") hour = int(hour) minute = int(minute) if len(str(hour)) > 3 or minute not in range(60): raise ValidationError(_("Invalid format, it should be HH:MM format")) except ValueError as error: raise ValidationError(_("Invalid format, it should be HH:MM format")) from error BASED_ON = [ ("after", _trans("After")), ("weekly", _trans("Weekend")), ("monthly", _trans("Monthly")), ] def get_next_week_date(target_day, start_date): """ Calculates the date of the next occurrence of the target day within the next week. Parameters: target_day (int): The target day of the week (0-6, where Monday is 0 and Sunday is 6). start_date (datetime.date): The starting date. Returns: datetime.date: The date of the next occurrence of the target day within the next week. """ if start_date.weekday() == target_day: return start_date days_until_target_day = (target_day - start_date.weekday()) % 7 if days_until_target_day == 0: days_until_target_day = 7 return start_date + timedelta(days=days_until_target_day) def get_next_monthly_date(start_date, rotate_every): """ Given a start date and a rotation day (specified as an integer between 1 and 31, or the string 'last'),calculates the next rotation date for a monthly rotation schedule. If the rotation day has not yet occurred in the current month, the next rotation date will be on the rotation day of the current month. If the rotation day has already occurred in the current month, the next rotation date will be on the rotation day of the next month. If 'last' is specified as the rotation day, the next rotation date will be on the last day of the current month. Parameters: - start_date: The start date of the rotation schedule, as a datetime.date object. - rotate_every: The rotation day, specified as an integer between 1 and 31, or the string 'last'. Returns: - A datetime.date object representing the next rotation date. """ if rotate_every == "last": # Set rotate_every to the last day of the current month last_day = calendar.monthrange(start_date.year, start_date.month)[1] rotate_every = str(last_day) rotate_every = int(rotate_every) # Calculate the next change date if start_date.day <= rotate_every or rotate_every == 0: # If the rotation day has not occurred yet this month, or if it's the last- # day of the month, set the next change date to the rotation day of this month try: next_change = datetime.date(start_date.year, start_date.month, rotate_every) except ValueError: next_change = datetime.date( start_date.year, start_date.month + 1, 1 ) # Advance to next month # Set day to rotate_every next_change = datetime.date( next_change.year, next_change.month, rotate_every ) else: # If the rotation day has already occurred this month, set the next change # date to the rotation day of the next month last_day = calendar.monthrange(start_date.year, start_date.month)[1] next_month_start = start_date.replace(day=last_day) + timedelta(days=1) try: next_change = next_month_start.replace(day=rotate_every) except ValueError: next_change = ( next_month_start.replace(month=next_month_start.month + 1) + timedelta(days=1) ).replace(day=rotate_every) return next_change class ModelForm(forms.ModelForm): """ Override django model's form to add initial styling """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): widget = field.widget if isinstance( widget, (forms.NumberInput, forms.EmailInput, forms.TextInput, forms.FileInput), ): if field.label is not None: label = _(field.label.title()) field.widget.attrs.update( {"class": "oh-input w-100", "placeholder": label} ) elif isinstance(widget, (forms.Select,)): label = "" if field.label is not None: label = _(field.label) field.empty_label = _("---Choose {label}---").format(label=label) field.widget.attrs.update( {"class": "oh-select oh-select-2 select2-hidden-accessible"} ) elif isinstance(widget, (forms.Textarea)): field.widget.attrs.update( { "class": "oh-input w-100", "placeholder": _(field.label), "rows": 2, "cols": 40, } ) elif isinstance( widget, ( forms.CheckboxInput, forms.CheckboxSelectMultiple, ), ): field.widget.attrs.update({"class": "oh-switch__checkbox"}) class Form(forms.Form): """ Overrides to add initial styling to the django Form instance """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): widget = field.widget if isinstance( widget, (forms.NumberInput, forms.EmailInput, forms.TextInput) ): if field.label is not None: label = _(field.label) field.widget.attrs.update( {"class": "oh-input w-100", "placeholder": label} ) elif isinstance(widget, (forms.Select,)): label = "" if field.label is not None: label = field.label.replace("id", " ") field.empty_label = _("---Choose {label}---").format(label=label) field.widget.attrs.update( {"class": "oh-select oh-select-2 select2-hidden-accessible"} ) elif isinstance(widget, (forms.Textarea)): label = _(field.label) field.widget.attrs.update( { "class": "oh-input w-100", "placeholder": label, "rows": 2, "cols": 40, } ) elif isinstance( widget, ( forms.CheckboxInput, forms.CheckboxSelectMultiple, ), ): field.widget.attrs.update({"class": "oh-switch__checkbox"}) class UserGroupForm(ModelForm): """ Django user groups form """ class Meta: """ Meta class for additional options """ model = Group fields = "__all__" class AssignUserGroup(Form): """ Form to assign groups """ employee = forms.ModelMultipleChoiceField(queryset=Employee.objects.all()) group = forms.ModelMultipleChoiceField(queryset=Group.objects.all()) def save(self): """ Save method to assign group to employees """ employees = self.cleaned_data["employee"] group = self.cleaned_data["group"] for employee in employees: employee.employee_user_id.groups.add(*group) return group class AssignPermission(Form): """ Forms to assign user permision """ employee = forms.ModelMultipleChoiceField(queryset=Employee.objects.all()) permission = forms.ModelMultipleChoiceField(queryset=Permission.objects.all()) def save(self): """ Save method to assign permission to employee """ employees = self.cleaned_data["employee"] permissions = self.cleaned_data["permission"] for emp in employees: user = emp.employee_user_id user.user_permissions.add(*permissions) return self class CompanyForm(ModelForm): """ Company model's form """ class Meta: """ Meta class for additional options """ model = Company fields = "__all__" def validate_image(self, file): max_size = 5 * 1024 * 1024 if file.size > max_size: raise ValidationError("File size should be less than 5MB.") # Check file extension valid_extensions = [".jpg", ".jpeg", ".png", ".webp", ".svg"] ext = os.path.splitext(file.name)[1].lower() if ext not in valid_extensions: raise ValidationError("Unsupported file extension.") def clean_icon(self): icon = self.cleaned_data.get("icon") if icon: self.validate_image(icon) return icon class DepartmentForm(ModelForm): """ Department model's form """ class Meta: """ Meta class for additional options """ model = Department fields = "__all__" class JobPositionForm(ModelForm): """ JobPosition model's form """ class Meta: """ Meta class for additional options """ model = JobPosition fields = "__all__" class JobRoleForm(ModelForm): """ JobRole model's form """ class Meta: """ Meta class for additional options """ model = JobRole fields = "__all__" class WorkTypeForm(ModelForm): """ WorkType model's form """ class Meta: """ Meta class for additional options """ model = WorkType fields = "__all__" class RotatingWorkTypeForm(ModelForm): """ RotatingWorkType model's form """ class Meta: """ Meta class for additional options """ model = RotatingWorkType fields = "__all__" exclude = ("employee_id",) widgets = { "start_date": DateInput(attrs={"type": "date"}), } class RotatingWorkTypeAssignForm(ModelForm): """ RotatingWorkTypeAssign model's form """ employee_id = forms.ModelMultipleChoiceField( label=_trans("Employee"), queryset=Employee.objects.filter(employee_work_info__isnull=False), ) based_on = forms.ChoiceField( choices=BASED_ON, initial="daily", label=_trans("Based on") ) rotate_after_day = forms.IntegerField(initial=5, label=_trans("Rotate after day")) start_date = forms.DateField( initial=datetime.date.today, widget=forms.DateInput, label=_trans("Start date") ) class Meta: """ Meta class for additional options """ model = RotatingWorkTypeAssign fields = "__all__" exclude = ("next_change_date", "current_work_type", "next_work_type") widgets = { "start_date": DateInput(attrs={"type": "date"}), } labels = { "is_active": _trans("Is Active"), "rotate_every_weekend": _trans("Rotate every weekend"), "rotate_every": _trans("Rotate every"), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["rotate_every_weekend"].widget.attrs.update( { "class": "w-100", "style": "display:none; height:50px; border-radius:0;border:1px \ solid hsl(213deg,22%,84%);", "data-hidden": True, } ) self.fields["rotate_every"].widget.attrs.update( { "class": "w-100", "style": "display:none; height:50px; border-radius:0;border:1px \ solid hsl(213deg,22%,84%);", "data-hidden": True, } ) self.fields["rotate_after_day"].widget.attrs.update( { "class": "w-100 oh-input", "style": " height:50px; border-radius:0;", } ) self.fields["based_on"].widget.attrs.update( { "class": "w-100", "style": " height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);", } ) self.fields["start_date"].widget = forms.DateInput( attrs={ "class": "w-100 oh-input", "type": "date", "style": " height:50px; border-radius:0;", } ) self.fields["rotating_work_type_id"].widget.attrs.update( { "class": "oh-select oh-select-2", } ) self.fields["employee_id"].widget.attrs.update( { "class": "oh-select oh-select-2", } ) def clean_employee_id(self): employee_ids = self.cleaned_data.get("employee_id") if employee_ids: return employee_ids[0] else: return ValidationError(_("This field is required")) def clean(self): cleaned_data = super().clean() if "rotate_after_day" in self.errors: del self.errors["rotate_after_day"] return cleaned_data def save(self, commit=False, manager=None): employee_ids = self.data.getlist("employee_id") rotating_work_type = RotatingWorkType.objects.get( id=self.data["rotating_work_type_id"] ) day_name = self.cleaned_data["rotate_every_weekend"] day_names = [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", ] target_day = day_names.index(day_name.lower()) for employee_id in employee_ids: employee = Employee.objects.filter(id=employee_id).first() rotating_work_type_assign = RotatingWorkTypeAssign() rotating_work_type_assign.rotating_work_type_id = rotating_work_type rotating_work_type_assign.employee_id = employee rotating_work_type_assign.is_active = self.cleaned_data["is_active"] rotating_work_type_assign.based_on = self.cleaned_data["based_on"] rotating_work_type_assign.start_date = self.cleaned_data["start_date"] rotating_work_type_assign.next_change_date = self.cleaned_data["start_date"] rotating_work_type_assign.rotate_after_day = self.data.get( "rotate_after_day" ) rotating_work_type_assign.rotate_every = self.cleaned_data["rotate_every"] rotating_work_type_assign.rotate_every_weekend = self.cleaned_data[ "rotate_every_weekend" ] rotating_work_type_assign.next_change_date = self.cleaned_data["start_date"] rotating_work_type_assign.current_work_type = ( employee.employee_work_info.work_type_id ) rotating_work_type_assign.next_work_type = rotating_work_type.work_type2 based_on = self.cleaned_data["based_on"] start_date = self.cleaned_data["start_date"] if based_on == "weekly": next_date = get_next_week_date(target_day, start_date) rotating_work_type_assign.next_change_date = next_date elif based_on == "monthly": # 0, 1, 2, ..., 31, or "last" rotate_every = self.cleaned_data["rotate_every"] start_date = self.cleaned_data["start_date"] next_date = get_next_monthly_date(start_date, rotate_every) rotating_work_type_assign.next_change_date = next_date elif based_on == "after": rotating_work_type_assign.next_change_date = ( rotating_work_type_assign.start_date + datetime.timedelta(days=int(self.data.get("rotate_after_day"))) ) rotating_work_type_assign.save() class RotatingWorkTypeAssignUpdateForm(forms.ModelForm): """ RotatingWorkTypeAssign model's form """ class Meta: """ Meta class for additional options """ model = RotatingWorkTypeAssign fields = "__all__" exclude = ("next_change_date", "current_work_type", "next_work_type") widgets = { "start_date": DateInput(attrs={"type": "date"}), } labels = { "start_date": _trans("Start date"), "rotate_after_day": _trans("Rotate after day"), "rotate_every_weekend": _trans("Rotate every weekend"), "rotate_every": _trans("Rotate every"), "based_on": _trans("Based on"), "is_active": _trans("Is Active"), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["rotate_every_weekend"].widget.attrs.update( { "class": "w-100", "style": "display:none; height:50px; border-radius:0;border:1px\ solid hsl(213deg,22%,84%);", "data-hidden": True, } ) self.fields["rotate_every"].widget.attrs.update( { "class": "w-100", "style": "display:none; height:50px; border-radius:0;border:1px \ solid hsl(213deg,22%,84%);", "data-hidden": True, } ) self.fields["rotate_after_day"].widget.attrs.update( { "class": "w-100 oh-input", "style": " height:50px; border-radius:0;", } ) self.fields["based_on"].widget.attrs.update( { "class": "w-100", "style": " height:50px; border-radius:0; border:1px solid \ hsl(213deg,22%,84%);", } ) self.fields["start_date"].widget = forms.DateInput( attrs={ "class": "w-100 oh-input", "type": "date", "style": " height:50px; border-radius:0;", } ) self.fields["rotating_work_type_id"].widget.attrs.update( { "class": "oh-select oh-select-2", } ) self.fields["employee_id"].widget.attrs.update( { "class": "oh-select oh-select-2", } ) def save(self, *args, **kwargs): day_name = self.cleaned_data["rotate_every_weekend"] day_names = [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", ] target_day = day_names.index(day_name.lower()) based_on = self.cleaned_data["based_on"] start_date = self.instance.start_date if based_on == "weekly": next_date = get_next_week_date(target_day, start_date) self.instance.next_change_date = next_date elif based_on == "monthly": rotate_every = self.instance.rotate_every # 0, 1, 2, ..., 31, or "last" start_date = self.instance.start_date next_date = get_next_monthly_date(start_date, rotate_every) self.instance.next_change_date = next_date elif based_on == "after": self.instance.next_change_date = ( self.instance.start_date + datetime.timedelta(days=int(self.data.get("rotate_after_day"))) ) return super().save() class EmployeeTypeForm(ModelForm): """ EmployeeType form """ class Meta: """ Meta class for additional options """ model = EmployeeType fields = "__all__" class EmployeeShiftForm(ModelForm): """ EmployeeShift Form """ class Meta: """ Meta class for additional options """ model = EmployeeShift fields = "__all__" exclude = ("days",) def clean(self) -> Dict[str, Any]: full_time = self.data["full_time"] validate_time_format(full_time) full_time = self.data["weekly_full_time"] validate_time_format(full_time) return super().clean() class EmployeeShiftScheduleUpdateForm(ModelForm): """ EmployeeShiftSchedule model's form """ class Meta: """ Meta class for additional options """ fields = "__all__" widgets = { "start_time": DateInput(attrs={"type": "time"}), "end_time": DateInput(attrs={"type": "time"}), } model = EmployeeShiftSchedule def __init__(self, *args, **kwargs): if instance := kwargs.get("instance"): # """ # django forms not showing value inside the date, time html element. # so here overriding default forms instance method to set initial value # """ initial = { "start_time": instance.start_time.strftime("%H:%M"), "end_time": instance.end_time.strftime("%H:%M"), } kwargs["initial"] = initial super().__init__(*args, **kwargs) class EmployeeShiftScheduleForm(ModelForm): """ EmployeeShiftSchedule model's form """ day = forms.ModelMultipleChoiceField( queryset=EmployeeShiftDay.objects.all(), ) class Meta: """ Meta class for additional options """ model = EmployeeShiftSchedule fields = "__all__" exclude = ["is_night_shift"] widgets = { "start_time": DateInput(attrs={"type": "time"}), "end_time": DateInput(attrs={"type": "time"}), } def __init__(self, *args, **kwargs): if instance := kwargs.get("instance"): # """ # django forms not showing value inside the date, time html element. # so here overriding default forms instance method to set initial value # """ initial = { "start_time": instance.start_time.strftime("%H:%M"), "end_time": instance.end_time.strftime("%H:%M"), } kwargs["initial"] = initial super().__init__(*args, **kwargs) self.fields["day"].widget.attrs.update({"id": str(uuid.uuid4())}) self.fields["shift_id"].widget.attrs.update({"id": str(uuid.uuid4())}) def save(self, commit=True): instance = super().save(commit=False) for day in self.data.getlist("day"): if int(day) != int(instance.day.id): data_copy = self.data.copy() data_copy.update({"day": str(day)}) shift_schedule = EmployeeShiftScheduleUpdateForm(data_copy).save( commit=False ) shift_schedule.save() if commit: instance.save() return instance def clean_day(self): """ Validation to day field """ days = self.cleaned_data["day"] for day in days: attendance = EmployeeShiftSchedule.objects.filter( day=day, shift_id=self.data["shift_id"] ).first() if attendance is not None: raise ValidationError( _("Shift schedule is already exist for {day}").format( day=_(day.day) ) ) if days.first() is None: raise ValidationError(_("Employee not chosen")) return days.first() class RotatingShiftForm(ModelForm): """ RotatingShift model's form """ class Meta: """ Meta class for additional options """ model = RotatingShift fields = "__all__" exclude = ("employee_id",) class RotatingShiftAssignForm(forms.ModelForm): """ RotatingShiftAssign model's form """ employee_id = forms.ModelMultipleChoiceField( label=_trans("Employee"), queryset=Employee.objects.filter(employee_work_info__isnull=False), ) based_on = forms.ChoiceField( choices=BASED_ON, initial="daily", label=_trans("Based on") ) rotate_after_day = forms.IntegerField(initial=5, label=_trans("Rotate after day")) start_date = forms.DateField( initial=datetime.date.today, widget=forms.DateInput, label=_trans("Start date") ) class Meta: """ Meta class for additional options """ model = RotatingShiftAssign fields = "__all__" exclude = ("next_change_date", "current_shift", "next_shift") widgets = { "start_date": DateInput(attrs={"type": "date"}), } labels = { "rotating_shift_id": _trans("Rotating Shift"), "start_date": _("Start date"), "is_active": _trans("Is Active"), "rotate_every_weekend": _trans("Rotate every weekend"), "rotate_every": _trans("Rotate every"), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["rotate_every_weekend"].widget.attrs.update( { "class": "w-100 ", "style": "display:none; height:50px; border-radius:0;border:1px \ solid hsl(213deg,22%,84%);", "data-hidden": True, } ) self.fields["rotate_every"].widget.attrs.update( { "class": "w-100 ", "style": "display:none; height:50px; border-radius:0;border:1px \ solid hsl(213deg,22%,84%);", "data-hidden": True, } ) self.fields["rotate_after_day"].widget.attrs.update( { "class": "w-100 oh-input", "style": " height:50px; border-radius:0;", } ) self.fields["based_on"].widget.attrs.update( { "class": "w-100", "style": " height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);", } ) self.fields["start_date"].widget = forms.DateInput( attrs={ "class": "w-100 oh-input", "type": "date", "style": " height:50px; border-radius:0;", } ) self.fields["rotating_shift_id"].widget.attrs.update( { "class": "oh-select oh-select-2", } ) self.fields["employee_id"].widget.attrs.update( { "class": "oh-select oh-select-2", } ) def clean_employee_id(self): """ Validation to employee_id field """ employee_ids = self.cleaned_data.get("employee_id") if employee_ids: return employee_ids[0] else: return ValidationError(_("This field is required")) def clean(self): cleaned_data = super().clean() if "rotate_after_day" in self.errors: del self.errors["rotate_after_day"] return cleaned_data def save( self, commit=False, ): employee_ids = self.data.getlist("employee_id") rotating_shift = RotatingShift.objects.get(id=self.data["rotating_shift_id"]) day_name = self.cleaned_data["rotate_every_weekend"] day_names = [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", ] target_day = day_names.index(day_name.lower()) for employee_id in employee_ids: employee = Employee.objects.filter(id=employee_id).first() rotating_shift_assign = RotatingShiftAssign() rotating_shift_assign.rotating_shift_id = rotating_shift rotating_shift_assign.employee_id = employee rotating_shift_assign.based_on = self.cleaned_data["based_on"] rotating_shift_assign.start_date = self.cleaned_data["start_date"] rotating_shift_assign.next_change_date = self.cleaned_data["start_date"] rotating_shift_assign.is_active = self.cleaned_data["is_active"] rotating_shift_assign.rotate_after_day = self.data.get("rotate_after_day") rotating_shift_assign.rotate_every = self.cleaned_data["rotate_every"] rotating_shift_assign.rotate_every_weekend = self.cleaned_data[ "rotate_every_weekend" ] rotating_shift_assign.next_change_date = self.cleaned_data["start_date"] rotating_shift_assign.current_shift = employee.employee_work_info.shift_id rotating_shift_assign.next_shift = rotating_shift.shift2 based_on = self.cleaned_data["based_on"] start_date = self.cleaned_data["start_date"] if based_on == "weekly": next_date = get_next_week_date(target_day, start_date) rotating_shift_assign.next_change_date = next_date elif based_on == "monthly": # 0, 1, 2, ..., 31, or "last" rotate_every = self.cleaned_data["rotate_every"] start_date = self.cleaned_data["start_date"] next_date = get_next_monthly_date(start_date, rotate_every) rotating_shift_assign.next_change_date = next_date elif based_on == "after": rotating_shift_assign.next_change_date = ( rotating_shift_assign.start_date + datetime.timedelta(days=int(self.data.get("rotate_after_day"))) ) rotating_shift_assign.save() class RotatingShiftAssignUpdateForm(ModelForm): """ RotatingShiftAssign model's form """ class Meta: """ Meta class for additional options """ model = RotatingShiftAssign fields = "__all__" exclude = ("next_change_date", "current_shift", "next_shift") widgets = { "start_date": DateInput(attrs={"type": "date"}), } labels = { "start_date": _trans("Start date"), "rotate_after_day": _trans("Rotate after day"), "rotate_every_weekend": _trans("Rotate every weekend"), "rotate_every": _trans("Rotate every"), "based_on": _trans("Based on"), "is_active": _trans("Is Active"), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["rotate_every_weekend"].widget.attrs.update( { "class": "w-100 ", "style": "display:none; height:50px; border-radius:0; border:1px \ solid hsl(213deg,22%,84%);", "data-hidden": True, } ) self.fields["rotate_every"].widget.attrs.update( { "class": "w-100 ", "style": "display:none; height:50px; border-radius:0; border:1px \ solid hsl(213deg,22%,84%);", "data-hidden": True, } ) self.fields["rotate_after_day"].widget.attrs.update( { "class": "w-100 oh-input", "style": " height:50px; border-radius:0;", } ) self.fields["based_on"].widget.attrs.update( { "class": "w-100", "style": " height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);", } ) self.fields["start_date"].widget = forms.DateInput( attrs={ "class": "w-100 oh-input", "type": "date", "style": " height:50px; border-radius:0;", } ) self.fields["rotating_shift_id"].widget.attrs.update( { "class": "oh-select oh-select-2", } ) self.fields["employee_id"].widget.attrs.update( { "class": "oh-select oh-select-2", } ) def save(self, *args, **kwargs): day_name = self.cleaned_data["rotate_every_weekend"] day_names = [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", ] target_day = day_names.index(day_name.lower()) based_on = self.cleaned_data["based_on"] start_date = self.instance.start_date if based_on == "weekly": next_date = get_next_week_date(target_day, start_date) self.instance.next_change_date = next_date elif based_on == "monthly": rotate_every = self.instance.rotate_every # 0, 1, 2, ..., 31, or "last" start_date = self.instance.start_date next_date = get_next_monthly_date(start_date, rotate_every) self.instance.next_change_date = next_date elif based_on == "after": self.instance.next_change_date = ( self.instance.start_date + datetime.timedelta(days=int(self.data.get("rotate_after_day"))) ) return super().save() class ShiftRequestForm(ModelForm): """ ShiftRequest model's form """ class Meta: """ Meta class for additional options """ model = ShiftRequest fields = "__all__" exclude = ( "approved", "canceled", "previous_shift_id", "is_active", "shift_changed", ) widgets = { "requested_date": DateInput(attrs={"type": "date"}), "requested_till": DateInput(attrs={"type": "date"}), } labels = { "description": _trans("Description"), "requested_date": _trans("Requested Date"), "requested_till": _trans("Requested Till"), } 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 def save(self, commit: bool = ...): if not self.instance.approved: employee = self.instance.employee_id if hasattr(employee, "employee_work_info"): self.instance.previous_shift_id = employee.employee_work_info.shift_id if not self.instance.requested_till: self.instance.requested_till = ( employee.employee_work_info.contract_end_date ) return super().save(commit) # here set default filter for all the employees those have work information filled. class WorkTypeRequestForm(ModelForm): """ WorkTypeRequest model's form """ class Meta: """ Meta class for additional options """ model = WorkTypeRequest fields = "__all__" exclude = ( "approved", "canceled", "previous_work_type_id", "is_active", "work_type_changed", ) widgets = { "requested_date": DateInput(attrs={"type": "date"}), "requested_till": DateInput(attrs={"type": "date"}), } labels = { "requested_date": _trans("Requested Date"), "requested_till": _trans("Requested Till"), "description": _trans("Description"), } 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 def save(self, commit: bool = ...): if not self.instance.approved: employee = self.instance.employee_id if hasattr(employee, "employee_work_info"): self.instance.previous_work_type_id = ( employee.employee_work_info.work_type_id ) if not self.instance.requested_till: self.instance.requested_till = ( employee.employee_work_info.contract_end_date ) return super().save(commit) from django.contrib.auth.models import User class ChangePasswordForm(forms.Form): old_password = forms.CharField( label=_("Old password"), strip=False, widget=forms.PasswordInput( attrs={ "autocomplete": "new-password", "placeholder": _("Enter Old Password"), "class": "oh-input oh-input--password w-100 mb-2", } ), help_text=_("Enter your old password."), ) new_password = forms.CharField( label=_("New password"), strip=False, widget=forms.PasswordInput( attrs={ "autocomplete": "new-password", "placeholder": _("Enter New Password"), "class": "oh-input oh-input--password w-100 mb-2", } ), ) confirm_password = forms.CharField( label=_("New password confirmation"), strip=False, widget=forms.PasswordInput( attrs={ "autocomplete": "new-password", "placeholder": _("Re-Enter Password"), "class": "oh-input oh-input--password w-100 mb-2", } ), ) def __init__(self, user, *args, **kwargs): self.user = user super(ChangePasswordForm, self).__init__(*args, **kwargs) def clean_old_password(self): old_password = self.cleaned_data.get("old_password") if not self.user.check_password(old_password): raise forms.ValidationError("Incorrect old password.") return old_password def clean_new_password(self): new_password = self.cleaned_data.get("new_password") if self.user.check_password(new_password): raise forms.ValidationError( "New password must be different from the old password." ) return new_password def clean(self): cleaned_data = super().clean() new_password = cleaned_data.get("new_password") confirm_password = cleaned_data.get("confirm_password") if new_password and confirm_password and new_password != confirm_password: raise ValidationError( {"new_password": _("New password and confirm password do not match")} ) return cleaned_data class ResetPasswordForm(forms.Form): """ ResetPasswordForm """ password = forms.CharField( label=_("New password"), strip=False, widget=forms.PasswordInput( attrs={ "autocomplete": "new-password", "placeholder": _("Enter Strong Password"), "class": "oh-input oh-input--password w-100 mb-2", } ), help_text=_("Enter your new password."), ) confirm_password = forms.CharField( label=_("New password confirmation"), strip=False, widget=forms.PasswordInput( attrs={ "autocomplete": "new-password", "placeholder": _("Re-Enter Password"), "class": "oh-input oh-input--password w-100 mb-2", } ), help_text=_("Enter the same password as before, for verification."), ) def clean_password(self): """ Validation to password field""" password = self.cleaned_data.get("password") try: if len(password) < 7: raise ValidationError(_("Password must contain at least 8 characters.")) elif not any(char.isupper() for char in password): raise ValidationError( _("Password must contain at least one uppercase letter.") ) elif not any(char.islower() for char in password): raise ValidationError( _("Password must contain at least one lowercase letter.") ) elif not any(char.isdigit() for char in password): raise ValidationError(_("Password must contain at least one digit.")) elif all( char not in "!@#$%^&*()_+-=[]{}|;:,.<>?'\"`~\\/" for char in password ): raise ValidationError( _("Password must contain at least one special character.") ) except ValidationError as error: raise forms.ValidationError(list(error)[0]) return password def clean_confirm_password(self): """ validation method for confirm password field """ password = self.cleaned_data.get("password") confirm_password = self.cleaned_data.get("confirm_password") if password == confirm_password: return confirm_password raise forms.ValidationError(_("Password must be same.")) def save(self, *args, user=None, **kwargs): """ Save method to ResetPasswordForm """ if user is not None: user.set_password(self.data["password"]) user.save() excluded_fields = ["id", "is_active", "shift_changed","work_type_changed"] class ShiftRequestColumnForm(forms.Form): model_fields = ShiftRequest._meta.get_fields() field_choices = [ (field.name, field.verbose_name) for field in model_fields if hasattr(field, "verbose_name") and field.name not in excluded_fields ] selected_fields = forms.MultipleChoiceField( choices=field_choices, widget=forms.CheckboxSelectMultiple, initial=[ "employee_id", "shift_id", "requested_date", "requested_till", "previous_shift_id", "approved", ], ) class WorkTypeRequestColumnForm(forms.Form): model_fields = WorkTypeRequest._meta.get_fields() field_choices = [ (field.name, field.verbose_name) for field in model_fields if hasattr(field, "verbose_name") and field.name not in excluded_fields ] selected_fields = forms.MultipleChoiceField( choices=field_choices, widget=forms.CheckboxSelectMultiple, initial=[ "employee_id", "work_type_id", "requested_date", "requested_till", "previous_shift_id", "approved", ], )