[ADD] Horilla Widgets

This commit is contained in:
Horilla
2023-09-20 12:32:44 +05:30
parent bf5a3ee497
commit 282950509e
19 changed files with 868 additions and 0 deletions

View File

3
horilla_widgets/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
horilla_widgets/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class HorillaWidgetsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'horilla_widgets'

40
horilla_widgets/forms.py Normal file
View File

@@ -0,0 +1,40 @@
"""
forms.py
Horilla forms
"""
from typing import Any
from django import forms
from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField
class HorillaForm(forms.Form):
def clean(self) -> dict[str, Any]:
for field_name, field_instance in self.fields.items():
if isinstance(field_instance, HorillaMultiSelectField):
self.errors.pop(field_name, None)
if len(self.data.getlist(field_name)) < 1:
raise forms.ValidationError({field_name: "Thif field is required"})
cleaned_data = super().clean()
employee_data = self.fields[field_name].queryset.filter(
id__in=self.data.getlist(field_name)
)
cleaned_data[field_name] = employee_data
cleaned_data = super().clean()
return cleaned_data
class HorillaModelForm(forms.ModelForm):
def clean(self) -> dict[str, Any]:
for field_name, field_instance in self.fields.items():
if isinstance(field_instance, HorillaMultiSelectField):
self.errors.pop(field_name, None)
if len(self.data.getlist(field_name)) < 1:
raise forms.ValidationError({field_name: "Thif field is required"})
cleaned_data = super().clean()
employee_data = self.fields[field_name].queryset.filter(
id__in=self.data.getlist(field_name)
)
cleaned_data[field_name] = employee_data
cleaned_data = super().clean()
return cleaned_data

View File

View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,180 @@
{% load i18n %}
<style>
.oh-modal__close-custom {
border: none;
background: none;
font-size: 1.5rem;
opacity: 0.7;
position: absolute;
top: 25px;
right: 15px;
cursor: pointer;
}
.tag-badge {
box-sizing: border-box;
display: inline-block;
/* background-color: orangered; */
color: black;
border-radius: 3rem;
text-align: center;
font-size: 1.6rem;
font-weight: 400;
padding: 0.05rem 0.8rem 0.1rem;
line-height: inherit;
padding: 7px;
padding-right: 10px;
margin-bottom: 5px;
border: solid orangered 2px;
cursor: pointer;
}
.tag-badge--primary {
background-color: orangered;
color: white;
}
.oh-profile__avatar-limit-height {
height: 30px !important;
}
.oh-profile_name_custom {
font-size: 13px;
padding-left: 4px;
}
.oh-profile__image_custm {
width: 26px;
height: 26px;
}
.badge-container {
height: 30vh;
overflow: auto;
}
.tag-badge--outline {
background: white;
border: solid orangered 2px;
color: black;
}
.oh-sticky-table__th--custom {
padding-left: 10px !important;
}
.oh-sticky-table__tr--selected {
background: #ff450017 !important;
}
.avatars {
display: flex;
padding: 10px;
}
.avatars__item {
background-color: #596376;
border: 2px solid white;
border-radius: 100%;
color: #ffffff;
display: block;
font-family: sans-serif;
font-size: 12px;
font-weight: 100;
height: 26px;
width: 26px;
line-height: 17px;
text-align: center;
transition: margin 0.1s ease-in-out;
overflow: hidden;
margin-left: -10px;
}
.avatars__item:first-child {
z-index: 5;
}
.avatars__item:nth-child(2) {
z-index: 4;
}
.avatars__item:nth-child(3) {
z-index: 3;
}
.avatars__item:nth-child(4) {
z-index: 2;
}
.avatars__item:nth-child(5) {
z-index: 1;
}
.avatars__item:last-child {
z-index: 0;
}
.avatars__item img {
width: 100%;
}
.avatars:hover .avatars__item {
margin-right: 10px;
}
.select2{
width: 100% !important;
}
#slectContainer{{self.attrs.id}} .select2-container .select2-selection{
padding: 5px !important;
height: 50px !important;
overflow: hidden;
overflow-y: scroll;
}
</style>
<div id="slectContainer{{self.attrs.id}}">
<select name="{{field_name}}" id="{{self.attrs.id}}" class="w-100" multiple>
{% for instance in queryset %}
<option value="{{instance.id}}">{{instance}}</option>
{% endfor %}
</select>
</div>
<div class="d-flex justify-content-between">
<div class="avatars" id="avatarsContainer">
</div>
<span
style="cursor: pointer;padding: 10px;"
data-toggle="oh-modal-toggle"
data-target="#filterChoose"
>Filter and filterChoose</span
>
</div>
<div
class="oh-modal"
id="filterChoose"
role="dialog"
aria-labelledby="filterChoose"
aria-hidden="true"
>
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<button
type="button"
class="oh-modal__close-custom"
onclick="event.stopPropagation();event.preventDefault;$('#filterChoose').toggleClass('oh-modal--show');"
>
<ion-icon name="close-outline"></ion-icon>
</button>
</div>
<div class="oh-modal__dialog-body" id="filterChooseModalBody{{self.attrs.id}}">
<div class="oh-wrapper">
<!-- Nav -->
{% include "horilla_widgets/multiselect_components/nav.html" %}
<!-- Filter Tags -->
{% include "horilla_widgets/multiselect_components/filter_tags.html" %}
<!-- Sticky Table -->
{% include "horilla_widgets/multiselect_components/table.html" %}
<!-- Pagination -->
{% include "horilla_widgets/multiselect_components/pagination.html" %}
<div class="d-flex flex-row-reverse">
<button class="oh-btn oh-btn--secondary oh-btn--shadow pr-3 pl-3" onclick="event.stopPropagation();event.preventDefault;$('#filterChoose').toggleClass('oh-modal--show');">
Add
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,26 @@
<div
class="oh-modal"
id="viewSelectedModal"
role="dialog"
aria-labelledby="viewSelectedModal"
aria-hidden="true"
>
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<button
class="oh-modal__close-custom"
onclick="event.stopPropagation();$('#viewSelectedModal').toggleClass('oh-modal--show');"
aria-label="Close"
>
<ion-icon name="close-outline"></ion-icon>
</button>
</div>
<div class="oh-wrapper oh-modal__dialog-body" id="viewSelectedModalBody">
<!-- Search and count badge -->
{% include "horilla_widgets/multiselect_components/search.html" %}
<!-- Profile pills -->
{% include "horilla_widgets/multiselect_components/user_tags.html" %}
</div>
</div>
</div>

View File

@@ -0,0 +1,35 @@
<style>
.oh-checkpoint-badge--success {
color: yellowgreen;
}
.oh-checkpoint-badge {
cursor: pointer;
}
.m-zero {
margin: 0 !important;
}
</style>
<div class="oh-filter-tag-container mb-2 p-0" id="filterTagContainer{{self.attrs.id}}">
</div>
<div class="ration-container mb-2 d-flex justify-content-between">
<div>
<div
class="oh-checkpoint-badge oh-checkpoint-badge--secondary"
data-toggle="oh-modal-toggle"
data-target="#viewSelectedModal"
>
Selected <span class="selected-count">0</span>/<span class="total-count">{{queryset|length}}</span>
</div>
<div class="oh-checkpoint-badge oh-checkpoint-badge--secondary" id="selectAllUsers">
Select All <span class="total-count visible-count">{{queryset|length}}</span> user
</div>
<div class="oh-checkpoint-badge" id="unselectAllUsers">
Unselect All
</div>
</div>
<div class="oh-checkpoint-badge oh-checkpoint-badge--success m-zero" id="selectAllInstances">
Select All {{queryset|length}} Item
</div>
</div>

View File

@@ -0,0 +1,146 @@
<section
class="oh-main__topbar p-3 mb-1 m-0"
style="padding-left: 0 !important; padding-right: 0 !important"
x-data="{searchShow: false}"
id="widgetFilterFormContainer{{self.attrs.id}}"
>
<div class="oh-main__titlebar oh-main__titlebar--left">
<div class="mb-2 mt-2">
<h1 class="oh-main__titlebar-title fw-bold">{{field.label}}</h1>
</div>
</div>
<div class="oh-main__titlebar oh-main__titlebar--right">
<div
class="oh-input-group oh-input__search-group"
:class="searchShow ? 'oh-input__search-group--show' : ''"
>
<input
type="text"
class="oh-input oh-input__icon"
name="search"
aria-label="Search Input"
placeholder="Search"
/>
</div>
<div class="oh-main__titlebar-button-container">
<div class="oh-dropdown" x-data="{open: false}">
<button class="oh-btn ml-2" @click="open = !open">
<ion-icon
name="filter"
class="mr-1 md hydrated"
role="img"
aria-label="filter"
></ion-icon
>Filter
<div id="filterCount"></div>
</button>
<div
class="oh-dropdown__menu oh-dropdown__menu--left oh-dropdown__filter p-4"
x-show="open"
@click.outside="open = false"
>
{% include filter_template_path %}
<div class="oh-dropdown__filter-footer">
<button
type="submit"
class="oh-btn oh-btn--secondary oh-btn--small w-100"
data-action="{% url filter_route_name %}"
id="widgetFilterButton{{self.attrs.id}}"
>
Filter
</button>
</div>
</div>
</div>
</div>
</div>
</section>
<script>
$(document).ready(function () {
function transformJson(inputJson) {
// Clone the input JSON to avoid modifying the original object
const modifiedJson = JSON.parse(JSON.stringify(inputJson));
// Iterate through each object in the JSON array
modifiedJson.forEach((obj) => {
// Remove "_id" when it appears at the end of a name
// If there are "__" in the name, keep only the word after the last "__" and capitalize it
obj.name = obj.name.replace(/_id$/, "");
obj.name.replace("_", " ");
if (obj.name.includes("__")) {
const parts = obj.name.split("__");
obj.name = parts[parts.length - 1];
}
obj.name = obj.name.charAt(0).toUpperCase() + obj.name.slice(1);
obj.name = obj.name.replace(/_/g, " ");
});
return modifiedJson;
}
$("#widgetFilterButton{{self.attrs.id}}").click(function (e) {
e.preventDefault();
const formFields = $("#widgetFilterFormContainer{{self.attrs.id}}").find(
"[name]"
);
var formData = [];
formFields.each(function () {
const name = $(this).attr("name");
const value = $(this).val();
formData.push({ name, value });
});
var filterUrl = $(this).attr("data-action");
$.ajax({
type: "get",
url: filterUrl,
data: formData,
success: function (response) {
let ids = response.ids;
$("#chooseTableHeaderParent .oh-sticky-table__tbody").hide();
$.each(ids, function (indexInArray, valueOfElement) {
$(
`#chooseTableHeaderParent .oh-sticky-table__tbody[data-instance-id=${valueOfElement}]`
).show();
});
$(".visible-count").html(
$(".oh-sticky-table__tr--custom:visible .oh-sticky-table__sd")
.length
);
var labeledTags = transformJson(formData);
var tagContainer = $("#filterTagContainer{{self.attrs.id}}");
tagContainer.html("");
$.each(labeledTags, function (indexInArray, valueOfElement) {
if (valueOfElement.value.length != 0) {
tagContainer.append(
$(`
<span class="oh-filter-tag" id="tag-{{self.attrs.id}}"
>${valueOfElement.name}<button class="oh-filter-tag__close" onclick="closeFilterTag(event, ${formData[indexInArray].name}, '#tag-{{self.attrs.id}}')">
<ion-icon name="close-outline"></ion-icon></button
>
</span>`)
);
}
});
},
});
});
$("#widgetFilterFormContainer{{self.attrs.id}} [name=search]").keyup(
function (e) {
$("#widgetFilterButton{{self.attrs.id}}").click();
}
);
});
function closeFilterTag(e, fieldName, targetTag) {
e.preventDefault();
$(fieldName).val("");
if ($(fieldName).is("select")) {
$(fieldName)
.next()
.find(`#select2-${$(fieldName).attr("id")}-container`)
.html("---------");
}
$("#widgetFilterButton{{self.attrs.id}}").click();
$(targetTag).remove();
}
</script>

View File

@@ -0,0 +1,8 @@
<div class="oh-pagination">
<span
class="oh-pagination__page"
data-toggle="modal"
data-target="#addEmployeeModal"
>Page 1 of 1</span
>
</div>

View File

@@ -0,0 +1,60 @@
<section
class="oh-main__topbar p-3 mb-1 m-0"
style="padding-left: 0 !important; padding-right: 0 !important"
x-data="{searchShow: false}"
>
<div
class="oh-input-group w-100 oh-input__search-group"
:class="searchShow ? 'oh-input__search-group--show' : ''"
>
<input
type="text"
class="oh-input oh-input__icon w-100"
aria-label="Search Input"
placeholder="Search"
/>
</div>
</section>
<div class="ration-container mb-2 d-flex justify-content-between">
<div>
<div
class="oh-checkpoint-badge oh-checkpoint-badge--secondary"
>
Selected <span class="selected-count">0</span>/<span class="total-count">138</span>
</div>
<div class="oh-checkpoint-badge oh-checkpoint-badge--secondary" id="selectSelected">
Select All
</div>
<div class="oh-checkpoint-badge" id="unselectSelected">Unselect All</div>
</div>
<!-- <div class="oh-checkpoint-badge oh-checkpoint-badge--success m-zero">
Add Selected
</div> -->
</div>
<script>
$(document).ready(function () {
function updateSelectedCount(){
let selectedCount = $(".tag-badge").not(".tag-badge--outline").length
$(".selected-count").html(selectedCount);
}
$(".tag-badge").click(function (e) {
e.preventDefault();
$(this).toggleClass("tag-badge--outline");
updateSelectedCount()
});
$("#selectSelected").click(function (e) {
e.preventDefault();
$(".tag-badge").removeClass("tag-badge--outline");
updateSelectedCount()
});
$("#unselectSelected").click(function (e) {
e.preventDefault();
$(".tag-badge").addClass("tag-badge--outline");
updateSelectedCount()
});
});
</script>

View File

@@ -0,0 +1,205 @@
<div class="oh-sticky-table" style="height: 50vh">
<div
class="oh-sticky-table__table oh-table--sortable"
id="chooseTableHeaderParent"
>
<div class="oh-sticky-table__thead" id="chooseTableHeader">
<div class="oh-sticky-table__tr">
<div class="oh-sticky-table__th oh-sticky-table__th--custom">
<div class="d-flex">
<div class="">
<input
type="checkbox"
title="Select all users"
class="oh-input oh-input__checkbox mt-1 mr-2"
id="choose-all-user"
/>
</div>
Employee
</div>
</div>
</div>
</div>
{% for instance in queryset %}
<div class="oh-sticky-table__tbody" data-instance-id="{{instance.id}}">
<div
class="oh-sticky-table__tr oh-sticky-table__tr--custom"
data-instance-id="{{instance.id}}"
data-label="{{instance}}"
data-avatar="{% if instance.get_image %}{{instance.get_image}}{% else %}https://ui-avatars.com/api/?name={{instance}}&background=random{% endif %}"
draggable="true"
>
<div
class="oh-sticky-table__sd"
id="selectRow{{self.attrs.id}}{{instance.id}}"
>
<div class="d-flex">
<div class="">
<input
type="checkbox"
value="{{instance.id}}"
class="oh-input oh-input__checkbox mt-2 mr-2 all-choose-user-row"
/>
</div>
<div class="oh-profile oh-profile--md">
<div class="oh-profile__avatar mr-1">
<img
src="{% if instance.get_image %}{{instance.get_image}}{% else %}https://ui-avatars.com/api/?name={{instance}}&background=random{% endif %}"
class="oh-profile__image"
/>
</div>
<span class="oh-profile__name oh-text--dark">{{instance}}</span>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<script>
let selectedIds = [];
$(document).ready(function () {
$("#{{self.attrs.id}}").select2({
multiple: true,
});
$("#{{self.attrs.id}}")
.next()
.find(".select2-container")
.css("width", "100%");
$("#chooseTableHeader")
.not("[type=checkbox]")
.click(function (e) {
e.preventDefault();
if ($(this).find("[type=checkbox]:first").is(":checked")) {
$(this).find("[type=checkbox]:first").prop("checked", false);
} else {
$(this).find("[type=checkbox]:first").prop("checked", true);
}
$("#choose-all-user").change();
});
$("#selectAllUsers").click(function (e) {
e.preventDefault();
$("#choose-all-user").prop("checked", true);
$("#choose-all-user").change();
});
$("#unselectAllUsers").click(function (e) {
e.preventDefault();
$("#choose-all-user").prop("checked", false);
$("#choose-all-user").change();
});
$("#choose-all-user").change(function (e) {
e.preventDefault();
if ($(this).is(":checked")) {
$(".all-choose-user-row:visible").prop("checked", true);
$(".oh-sticky-table__tr--custom:visible .oh-sticky-table__sd").addClass(
"oh-sticky-table__tr--selected"
);
} else {
$(".all-choose-user-row:visible").prop("checked", false);
$(
".oh-sticky-table__tr--custom:visible .oh-sticky-table__sd"
).removeClass("oh-sticky-table__tr--selected");
}
});
$(".oh-sticky-table__tr--custom").click(function (e) {
var checkbox = $(this).find("[type=checkbox]");
// Toggle the checkbox's checked state
checkbox.prop("checked", function (i, checked) {
if (!checked) {
console.log($(this));
$(this)
.parent()
.parent()
.parent()
.addClass("oh-sticky-table__tr--selected");
} else {
$(this)
.parent()
.parent()
.parent()
.removeClass("oh-sticky-table__tr--selected");
}
return !checked;
});
});
$("#filterChoose").click(function (e) {
e.preventDefault();
var selectedRows = $("#filterChoose div").find(
".oh-sticky-table__tr--selected"
);
var ids = [];
var instanceData = [];
$.each(selectedRows, function (indexInArray, valueOfElement) {
var id = $(valueOfElement).parent().attr("data-instance-id");
var label = $(valueOfElement).parent().attr("data-label");
ids.push(id);
instanceData.push({ id: id, label: label });
});
var selectedCount = selectedRows.length;
$(".selected-count").html(selectedCount);
console.log(ids);
console.log(instanceData);
$("#{{self.attrs.id}}").val(ids);
$("#{{self.attrs.id}}").change();
$("#avatarsContainer").html("");
$.each(instanceData, function (indexInArray, valueOfElement) {
var imgUrl = $(
`.oh-sticky-table__tr.oh-sticky-table__tr--custom[data-instance-id=${valueOfElement.id}]`
).attr("data-avatar");
$("#avatarsContainer").append(
$(
`<a href="#" class="avatars__item" title="${valueOfElement.label}"><img class="avatar" src="${imgUrl}" alt=""></a>`
)
);
});
});
$("#selectAllInstances").click(function (e) {
e.preventDefault();
$("#choose-all-user").prop("checked", true);
$(".all-choose-user-row").prop("checked", true);
$(".oh-sticky-table__tr--custom .oh-sticky-table__sd").addClass(
"oh-sticky-table__tr--selected"
);
$("#choose-all-user").change();
});
$("#{{ self.attrs.id }}").change(function (e) {
e.preventDefault();
// Get all selected options
$("#avatarsContainer").html("");
$(".oh-sticky-table__tr--selected")
.find("[type=checkbox]")
.prop("checked", false);
$(".oh-sticky-table__tr--selected").removeClass(
"oh-sticky-table__tr--selected"
);
var selectedOptions = $(this).find(`:selected`);
$(".selected-count").html(selectedOptions.length);
// Loop through the selected options
selectedOptions.each(function (indexInArray, valueOfElement) {
// Get the HTML content of each selected option
var optionHtml = $(this).html();
// Append the selected option's HTML to the avatarsContainer
var imgUrl = $(
`.oh-sticky-table__tr.oh-sticky-table__tr--custom[data-instance-id=${$(
valueOfElement
).val()}]`
).attr("data-avatar");
$("#avatarsContainer").append(
$(
`<a href="#" class="avatars__item" title="${optionHtml}"><img class="avatar" src="${imgUrl}" alt=""></a>`
)
);
var optValue = $(valueOfElement).val();
var rowId = "#selectRow{{self.attrs.id}}" + optValue;
$(rowId).addClass("oh-sticky-table__tr--selected");
$(rowId).find("[type=checkbox]").prop("checked", true);
});
});
});
</script>

View File

@@ -0,0 +1,30 @@
<div class="badge-container">
<span
class="tag-badge tag-badge--primary"
>
<div class="user-info d-flex align-items-center">
<div class="oh-profile__avatar" style="line-height: 0px">
<img
src="https://ui-avatars.com/api/?name=Abigail+Lee&amp;background=random"
class="oh-profile__image oh-profile__image_custm"
alt="Username"
/>
</div>
<span class="oh-profile_name_custom"> Abigail Lee </span>
</div>
</span>
<span
class="tag-badge tag-badge--primary"
>
<div class="user-info d-flex align-items-center">
<div class="oh-profile__avatar" style="line-height: 0px">
<img
src="https://ui-avatars.com/api/?name=Magdalene&background=random"
class="oh-profile__image oh-profile__image_custm"
alt="Username"
/>
</div>
<span class="oh-profile_name_custom">Mary Magdalene</span>
</div>
</span>
</div>

3
horilla_widgets/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

15
horilla_widgets/views.py Normal file
View File

@@ -0,0 +1,15 @@
from django.shortcuts import render
# Create your views here.
# urls.py
# path("employee-widget-filter",views.widget_filter,name="employee-widget-filter")
# views.py
# @login_required
# def widget_filter(request):
# """
# This method is used to return all the ids of the employees
# """
# ids = EmployeeFilter(request.GET).qs.values_list("id", flat=True)
# return JsonResponse({'ids':list(ids)})

View File

View File

@@ -0,0 +1,11 @@
"""
horilla_multi_select_field.py
This module is used to write cutom multiple select field
"""
from django import forms
class HorillaMultiSelectField(forms.ModelMultipleChoiceField):
"""
HorillaMultiSelectField
"""

View File

@@ -0,0 +1,97 @@
"""
select_widgets.py
This module is used to write horilla form select widgets
"""
from django import forms
class HorillaMultiSelectWidget(forms.Widget):
"""
HorillaMultiSelectWidget
"""
def __init__(
self,
*args,
filter_route_name,
filter_class=None,
filter_instance_contex_name=None,
filter_template_path=None,
instance=None,
**kwargs
) -> None:
self.filter_route_name = filter_route_name
self.filter_class = filter_class
self.filter_instance_contex_name = filter_instance_contex_name
self.filter_template_path = filter_template_path
self.instance = instance
super().__init__()
template_name = "horilla_widgets/horilla_multiselect_widget.html"
def get_context(self, name, value, attrs):
# Get the default context from the parent class
context = super().get_context(name, value, attrs)
# Add your custom data to the context
queryset = self.choices.queryset
field = self.choices.field
context["queryset"] = queryset
context["field_name"] = name
context["field"] = field
context["self"] = self
context["filter_template_path"] = self.filter_template_path
context["filter_route_name"] = self.filter_route_name
self.attrs["id"] = ("id_" + name ) if self.attrs.get('id') is None else self.attrs.get("id")
context[self.filter_instance_contex_name] = self.filter_class
if self.instance is not None:
data = getattr(self.instance,field)
print(data)
return context
def value_from_datadict(self, data, files, name):
print(data)
print('---------------------')
pass
"""
Attendance form field updations
"""
# def clean(self) -> Dict[str, Any]:
# self.instance.employee_id = Employee.objects.filter(
# id=self.data.get("employee_id")
# ).first()
# self.errors.pop("employee_id", None)
# if self.instance.employee_id is None:
# raise ValidationError({"employee_id": "This field is required"})
# super().clean()
# employee_ids = self.data.getlist("employee_id")
# existing_attendance = Attendance.objects.filter(
# attendance_date=self.data["attendance_date"]
# ).filter(employee_id__id__in=employee_ids)
# if existing_attendance.exists():
# raise ValidationError(
# {
# "employee_id": f"""Already attendance exists for {list(existing_attendance.values_list("employee_id__employee_first_name",flat=True))} employees"""
# }
# )
# class AttendanceForm(ModelForm):
# """
# Model form for Attendance model
# """
# employee_id = HorillaMultiSelectField(
# queryset=Employee.objects.filter(employee_work_info__isnull=False),
# widget=HorillaMultiSelectWidget(
# filter_route_name="employee-widget-filter",
# filter_class=EmployeeFilter,
# filter_instance_contex_name="f",
# filter_template_path="employee_filters.html",
# ),
# label=_("Employees"),
# )