[UPDT] HORILLA VIEWS: CBV updates

This commit is contained in:
Horilla
2024-08-07 12:01:46 +05:30
parent d47d9d7707
commit 16df045cf4
19 changed files with 1166 additions and 176 deletions

View File

@@ -9,7 +9,13 @@ from venv import logger
from django import template
from django.contrib import messages
from django.core.cache import cache as CACHE
from django.core.paginator import Paginator
from django.db.models.fields.related import ForeignKey
from django.db.models.fields.related_descriptors import (
ForwardManyToOneDescriptor,
ReverseOneToOneDescriptor,
)
from django.http import HttpResponse
from django.middleware.csrf import get_token
from django.shortcuts import redirect, render
@@ -194,10 +200,11 @@ def get_short_uuid(length: int, prefix: str = "hlv"):
def update_initial_cache(request: object, cache: dict, view: object):
if cache.get(request.session.session_key):
cache[request.session.session_key].update({view: {}})
if cache.get(request.session.session_key + "cbv"):
cache.get(request.session.session_key + "cbv").update({view: {}})
return
cache.update({request.session.session_key: {view: {}}})
cache.set(request.session.session_key + "cbv", {view: {}})
return
@@ -218,8 +225,28 @@ class Reverse:
reverse: bool = True
page: str = ""
def __str__(self) -> str:
return str(self.reverse)
cache = {}
def getmodelattribute(value, attr: str):
"""
Gets an attribute of a model dynamically from a string name, handling related fields.
"""
result = value
attrs = attr.split("__")
for attr in attrs:
if hasattr(result, attr):
result = getattr(result, attr)
if isinstance(result, ForwardManyToOneDescriptor):
result = result.field.related_model
elif hasattr(result, "field") and isinstance(result.field, ForeignKey):
result = getattr(result.field.remote_field.model, attr, None)
elif hasattr(result, "related") and isinstance(
result, ReverseOneToOneDescriptor
):
result = getattr(result.related.related_model, attr, None)
return result
def sortby(
@@ -230,16 +257,17 @@ def sortby(
"""
request = getattr(_thread_locals, "request", None)
sort_key = query_dict[key]
if not cache.get(request.session.session_key):
cache[request.session.session_key] = Reverse()
cache[request.session.session_key].page = (
if not CACHE.get(request.session.session_key + "cbvsortby"):
CACHE.set(request.session.session_key + "cbvsortby", Reverse())
CACHE.get(request.session.session_key + "cbvsortby").page = (
"1" if not query_dict.get(page) else query_dict.get(page)
)
reverse = cache[request.session.session_key].reverse
reverse_object = CACHE.get(request.session.session_key + "cbvsortby")
reverse = reverse_object.reverse
none_ids = []
none_queryset = []
model = queryset.model
model_attr = getattribute(model, sort_key)
model_attr = getmodelattribute(model, sort_key)
is_method = isinstance(model_attr, types.FunctionType)
if not is_method:
none_queryset = queryset.filter(**{f"{sort_key}__isnull": True})
@@ -256,19 +284,16 @@ def sortby(
current_page = query_dict.get(page)
if current_page or is_first_sort:
order = not order
if (
cache[request.session.session_key].page == current_page
and not is_first_sort
):
if reverse_object.page == current_page and not is_first_sort:
order = not order
cache[request.session.session_key].page = current_page
reverse_object.page = current_page
try:
queryset = sorted(queryset, key=_sortby, reverse=order)
except TypeError:
none_queryset = list(queryset.filter(id__in=none_ids))
queryset = sorted(queryset.exclude(id__in=none_ids), key=_sortby, reverse=order)
cache[request.session.session_key].reverse = order
reverse_object.reverse = order
if order:
order = "asc"
queryset = list(queryset) + list(none_queryset)
@@ -277,6 +302,7 @@ def sortby(
order = "desc"
setattr(request, "sort_order", order)
setattr(request, "sort_key", sort_key)
CACHE.set(request.session.session_key + "cbvsortby", reverse_object)
return queryset
@@ -284,22 +310,21 @@ def update_saved_filter_cache(request, cache):
"""
Method to save filter on cache
"""
if cache.get(request.session.session_key):
cache[request.session.session_key].update(
if cache.get(request.session.session_key + request.path + "cbv"):
cache.get(request.session.session_key + request.path + "cbv").update(
{
"path": request.path,
"query_dict": request.GET,
"request": request,
# "request": request,
}
)
return cache
cache.update(
cache.set(
request.session.session_key + request.path + "cbv",
{
request.session.session_key: {
"path": request.path,
"query_dict": request.GET,
"request": request,
}
}
"path": request.path,
"query_dict": request.GET,
# "request": request,
},
)
return cache

View File

@@ -7,6 +7,7 @@ from django.template.loader import render_to_string
from django.utils.safestring import SafeText
from horilla.horilla_middlewares import _thread_locals
from horilla_views import models
class ToggleColumnForm(forms.Form):
@@ -34,3 +35,43 @@ class ToggleColumnForm(forms.Form):
context = {"form": self, "request": self.request}
table_html = render_to_string("generic/as_list.html", context)
return table_html
class SavedFilterForm(forms.ModelForm):
"""
SavedFilterForm
"""
color = forms.CharField(
widget=forms.TextInput(
attrs={
"class": "oh-input w-100",
"type": "color",
"placeholder": "Choose a color",
}
)
)
class Meta:
model = models.SavedFilter
fields = ["title", "is_default", "color"]
def structured(self):
"""
Render the form fields as HTML table rows with Bootstrap styling.
"""
request = getattr(_thread_locals, "request", None)
context = {
"form": self,
"request": request,
}
table_html = render_to_string("common_form.html", context)
return table_html
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
attrs = self.fields["title"].widget.attrs
attrs["class"] = "oh-input w-100"
attrs["placeholder"] = "Saved filter title"
if self.instance.pk:
self.verbose_name = self.instance.title

View File

@@ -6,12 +6,13 @@ import json
from typing import Any
from urllib.parse import parse_qs
from bs4 import BeautifulSoup
from django import forms
from django.core.cache import cache as CACHE
from django.core.paginator import Page
from django.http import HttpRequest, HttpResponse, QueryDict
from django.shortcuts import render
from django.urls import resolve, reverse
from django.views.generic import DetailView, FormView, ListView, TemplateView
from django.views.generic import DetailView, FormView, ListView, TemplateView, View
from base.methods import closest_numbers, get_key_instances
from horilla.filters import FilterSet
@@ -29,9 +30,6 @@ from horilla_views.cbv_methods import (
from horilla_views.forms import ToggleColumnForm
from horilla_views.templatetags.generic_template_filters import getattribute
cache = {}
saved_filters = {}
class HorillaListView(ListView):
"""
@@ -80,18 +78,23 @@ class HorillaListView(ListView):
sortby_key: str = "sortby"
sortby_mapping: list = []
selected_instances_key_id: str = "selectedInstances"
show_filter_tags: bool = True
filter_keys_to_remove: list = []
records_per_page: int = 50
export_fields: list = []
verbose_name: str = ""
def __init__(self, **kwargs: Any) -> None:
self.view_id = get_short_uuid(4)
if not self.view_id:
self.view_id = get_short_uuid(4)
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
update_initial_cache(request, cache, HorillaListView)
update_initial_cache(request, CACHE, HorillaListView)
# hidden columns configuration
existing_instance = models.ToggleColumn.objects.filter(
@@ -110,27 +113,46 @@ class HorillaListView(ListView):
self.visible_column.remove(column)
def get_queryset(self):
queryset = super().get_queryset()
if not self.queryset:
queryset = super().get_queryset()
if self.filter_class:
query_dict = self.request.GET
if "filter_applied" in query_dict.keys():
update_saved_filter_cache(self.request, saved_filters)
elif saved_filters.get(
str(self.request.session.session_key) + self.request.path
):
query_dict = saved_filters[
str(self.request.session.session_key) + self.request.path
]["query_dict"]
if self.filter_class:
query_dict = self.request.GET
if "filter_applied" in query_dict.keys():
update_saved_filter_cache(self.request, CACHE)
elif CACHE.get(
str(self.request.session.session_key) + self.request.path + "cbv"
):
query_dict = CACHE.get(
str(self.request.session.session_key)
+ self.request.path
+ "cbv"
)["query_dict"]
self._saved_filters = query_dict
queryset = self.filter_class(query_dict, queryset).qs
return queryset
default_filter = models.SavedFilter.objects.filter(
path=self.request.path,
created_by=self.request.user,
is_default=True,
).first()
if not bool(query_dict) and default_filter:
data = eval(default_filter.filter)
query_dict = QueryDict("", mutable=True)
for key, value in data.items():
query_dict[key] = value
query_dict._mutable = False
self._saved_filters = query_dict
self.request.exclude_filter_form = True
self.queryset = self.filter_class(
data=query_dict, queryset=queryset, request=self.request
).qs
return self.queryset
def get_context_data(self, **kwargs: Any):
context = super().get_context_data(**kwargs)
context["view_id"] = self.view_id
context["columns"] = self.visible_column
context["hidden_columns"] = list(set(self.columns) - set(self.visible_column))
context["toggle_form"] = self.toggle_form
context["search_url"] = self.search_url
@@ -146,8 +168,25 @@ class HorillaListView(ListView):
context["row_status_class"] = self.row_status_class
context["sortby_key"] = self.sortby_key
context["sortby_mapping"] = self.sortby_mapping
context["selected_instances_key_id"] = self.selected_instances_key_id
context["row_status_indications"] = self.row_status_indications
context["saved_filters"] = self._saved_filters
if not self.verbose_name:
self.verbose_name = self.model.__class__
context["model_name"] = self.verbose_name
context["export_fields"] = self.export_fields
referrer = self.request.GET.get("referrer", "")
if referrer:
# Remove the protocol and domain part
referrer = "/" + "/".join(referrer.split("/")[3:])
context["stored_filters"] = (
models.SavedFilter.objects.filter(
path=self.request.path, created_by=self.request.user
)
| models.SavedFilter.objects.filter(
referrer=referrer, created_by=self.request.user
)
).distinct()
context["select_all_ids"] = self.select_all
if self._saved_filters.get("field"):
@@ -170,13 +209,13 @@ class HorillaListView(ListView):
if value[0] in ["unknown", "on"] + self.filter_keys_to_remove
]
for key in keys_to_remove:
data_dict.pop(key)
for key in keys_to_remove + ["referrer"]:
if key in data_dict.keys():
data_dict.pop(key)
context["filter_dict"] = data_dict
request = self.request
ordered_ids = list(queryset.values_list("id", flat=True))
model = queryset.model
is_first_sort = False
query_dict = self.request.GET
if (
@@ -193,8 +232,13 @@ class HorillaListView(ListView):
queryset = sortby(
query_dict, queryset, self.sortby_key, is_first_sort=is_first_sort
)
ordered_ids = [instance.id for instance in queryset]
setattr(model, "ordered_ids", ordered_ids)
ordered_ids = []
if not self._saved_filters.get("field"):
for instance in queryset:
instance.ordered_ids = ordered_ids
ordered_ids.append(instance.pk)
context["queryset"] = paginator_qry(
queryset, self._saved_filters.get("page"), self.records_per_page
@@ -213,13 +257,19 @@ class HorillaListView(ListView):
context["groups"] = paginator_qry(
groups, self._saved_filters.get("page"), 10
)
cache[self.request.session.session_key][HorillaListView] = context
for group in context["groups"]:
for instance in group["list"]:
instance.ordered_ids = ordered_ids
ordered_ids.append(instance.pk)
CACHE.get(self.request.session.session_key + "cbv")[HorillaListView] = context
from horilla.urls import path, urlpatterns
self.export_path = f"export-list-view-{get_short_uuid(4)}/"
urlpatterns.append(path(self.export_path, self.export_data))
context["export_path"] = self.export_path
return context
def select_all(self, *args, **kwargs):
@@ -236,30 +286,52 @@ class HorillaListView(ListView):
request = getattr(_thread_locals, "request", None)
ids = eval(request.GET["ids"])
_columns = eval(request.GET["columns"])
queryset = self.model.objects.filter(id__in=ids)
MODEL = self.model
FIELDS = self.visible_column
_model = self.model
class HorillaListViewResorce(resources.ModelResource):
"""
Instant Resource class
"""
id = fields.Field(column_name="ID")
class Meta:
model = MODEL
"""
Meta class for additional option
"""
model = _model
fields = []
def dehydrate_id(self, instance):
"""
Dehydrate method for id field
"""
return instance.pk
def before_export(self, queryset, *args, **kwargs):
return super().before_export(queryset, *args, **kwargs)
for field_tuple in FIELDS:
dynamic_fn_str = f"def dehydrate_{field_tuple[1]}(self, instance):return str(getattribute(instance, '{field_tuple[1]}'))"
for field_tuple in _columns:
dynamic_fn_str = f"def dehydrate_{field_tuple[1]}(self, instance):return self.remove_extra_spaces(getattribute(instance, '{field_tuple[1]}'))"
exec(dynamic_fn_str)
dynamic_fn = locals()[f"dehydrate_{field_tuple[1]}"]
locals()[field_tuple[1]] = fields.Field(column_name=field_tuple[0])
def remove_extra_spaces(self, text):
"""
Remove blank space but keep line breaks and add new lines for <li> tags.
"""
soup = BeautifulSoup(str(text), "html.parser")
for li in soup.find_all("li"):
li.insert_before("\n")
li.unwrap()
text = soup.get_text()
lines = text.splitlines()
non_blank_lines = [line.strip() for line in lines if line.strip()]
cleaned_text = "\n".join(non_blank_lines)
return cleaned_text
book_resource = HorillaListViewResorce()
# Export the data using the resource
@@ -285,7 +357,7 @@ class HorillaSectionView(TemplateView):
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
update_initial_cache(request, cache, HorillaListView)
update_initial_cache(request, CACHE, HorillaListView)
nav_url: str = ""
view_url: str = ""
@@ -327,17 +399,14 @@ class HorillaDetailedView(DetailView):
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
update_initial_cache(request, cache, HorillaDetailedView)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
update_initial_cache(request, CACHE, HorillaDetailedView)
def get_context_data(self, **kwargs: Any):
context = super().get_context_data(**kwargs)
instance_ids = eval(str(self.request.GET.get(self.ids_key)))
pk = context["object"].pk
context["instance"] = context["object"]
url = resolve(self.request.path)
key = list(url.kwargs.keys())[0]
@@ -361,7 +430,9 @@ class HorillaDetailedView(DetailView):
context["actions"] = self.actions
context["action_method"] = self.action_method
cache[self.request.session.session_key][HorillaDetailedView] = context
CACHE.get(self.request.session.session_key + "cbv")[
HorillaDetailedView
] = context
return context
@@ -379,7 +450,7 @@ class HorillaTabView(TemplateView):
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
update_initial_cache(request, cache, HorillaTabView)
update_initial_cache(request, CACHE, HorillaTabView)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -391,7 +462,7 @@ class HorillaTabView(TemplateView):
context["active_target"] = active_tab.tab_target
context["tabs"] = self.tabs
cache[self.request.session.session_key][HorillaTabView] = context
CACHE.get(self.request.session.session_key + "cbv")[HorillaTabView] = context
return context
@@ -443,23 +514,44 @@ class HorillaCardView(ListView):
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
update_initial_cache(request, cache, HorillaCardView)
update_initial_cache(request, CACHE, HorillaCardView)
self._saved_filters = QueryDict()
def get_queryset(self):
queryset = super().get_queryset()
if self.filter_class:
query_dict = self.request.GET
if "filter_applied" in query_dict.keys():
update_saved_filter_cache(self.request, saved_filters)
elif saved_filters.get(self.request.session.session_key):
query_dict = saved_filters[self.request.session.session_key][
"query_dict"
]
if not self.queryset:
queryset = super().get_queryset()
if self.filter_class:
query_dict = self.request.GET
if "filter_applied" in query_dict.keys():
update_saved_filter_cache(self.request, CACHE)
elif CACHE.get(
str(self.request.session.session_key) + self.request.path + "cbv"
):
query_dict = CACHE.get(
str(self.request.session.session_key)
+ self.request.path
+ "cbv"
)["query_dict"]
self._saved_filters = query_dict
queryset = self.filter_class(query_dict, queryset).qs
return queryset
self._saved_filters = query_dict
self.request.exclude_filter_form = True
self.queryset = self.filter_class(
query_dict, queryset, request=self.request
).qs
default_filter = models.SavedFilter.objects.filter(
path=self.request.path,
created_by=self.request.user,
is_default=True,
).first()
if not bool(query_dict) and default_filter:
data = eval(default_filter.filter)
query_dict = QueryDict("", mutable=True)
for key, value in data.items():
query_dict[key] = value
query_dict._mutable = False
return self.queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -488,11 +580,30 @@ class HorillaCardView(ListView):
if value[0] in ["unknown", "on"] + self.filter_keys_to_remove
]
for key in keys_to_remove:
data_dict.pop(key)
for key in keys_to_remove + ["referrer"]:
if key in data_dict.keys():
data_dict.pop(key)
context["filter_dict"] = data_dict
cache[self.request.session.session_key][HorillaCardView] = context
ordered_ids = list(queryset.values_list("id", flat=True))
if not self._saved_filters.get("field"):
for instance in queryset:
instance.ordered_ids = ordered_ids
ordered_ids.append(instance.pk)
CACHE.get(self.request.session.session_key + "cbv")[HorillaCardView] = context
referrer = self.request.GET.get("referrer", "")
if referrer:
# Remove the protocol and domain part
referrer = "/" + "/".join(referrer.split("/")[3:])
context["stored_filters"] = (
models.SavedFilter.objects.filter(
path=self.request.path, created_by=self.request.user
)
| models.SavedFilter.objects.filter(
referrer=referrer, created_by=self.request.user
)
).distinct()
return context
@@ -504,9 +615,6 @@ class ReloadMessages(TemplateView):
return context
dynamic_create_cache = {}
def save(self: forms.ModelForm, commit=True, *args, **kwargs):
"""
This method is used to super save the form using custom logic
@@ -518,28 +626,35 @@ def save(self: forms.ModelForm, commit=True, *args, **kwargs):
if commit:
response = super(type(self), self).save(*args, **kwargs)
new_isntance_pk = self.instance.pk
dynamic_create_cache[request.session.session_key + dynamic_field] = {
"dynamic_field": dynamic_field,
"value": new_isntance_pk,
"form": self,
}
CACHE.set(
request.session.session_key + "cbv" + dynamic_field,
{
"dynamic_field": dynamic_field,
"value": new_isntance_pk,
"model": self._meta.model,
},
)
return response
from django.views.generic import UpdateView
class HorillaFormView(FormView):
"""
HorillaFormView
"""
class HttpResponse:
"""
HttpResponse
"""
def __new__(
self, content: str = "", targets_to_reload: list = [], script: str = ""
) -> HttpResponse:
"""
__new__ method
"""
targets_to_reload = list(set(targets_to_reload))
targets_to_reload.append("#reloadMessagesButton")
# targets_to_reload.append("#reloadMessagesButton")
script_id = get_short_uuid(4)
script = (
f"<script id='scriptTarget{script_id}'>"
@@ -564,6 +679,7 @@ class HorillaFormView(FormView):
view_id: str = get_short_uuid(4)
form_class: forms.ModelForm = None
template_name = "generic/horilla_form.html"
ids_key: str = "instance_ids"
form_disaply_attr: str = ""
new_display_title: str = "Add New"
close_button_attrs: str = """"""
@@ -577,7 +693,7 @@ class HorillaFormView(FormView):
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
update_initial_cache(request, cache, HorillaFormView)
update_initial_cache(request, CACHE, HorillaFormView)
if self.form_class:
setattr(self.form_class, "structured", structured)
@@ -601,12 +717,29 @@ class HorillaFormView(FormView):
context["dynamic_create_fields"] = self.dynamic_create_fields
context["form_class_path"] = self.form_class_path
context["view_id"] = self.view_id
pk = self.form.instance.pk
# next/previous option in the forms
if pk and self.request.GET.get(self.ids_key):
instance_ids = eval(str(self.request.GET.get(self.ids_key)))
url = resolve(self.request.path)
key = list(url.kwargs.keys())[0]
url_name = url.url_name
if instance_ids:
previous_id, next_id = closest_numbers(instance_ids, pk)
next_url = reverse(url_name, kwargs={key: next_id})
previous_url = reverse(url_name, kwargs={key: previous_id})
self.form.instance_ids = str(instance_ids)
self.form.ids_key = self.ids_key
self.form.next_url = next_url
self.form.previous_url = previous_url
return context
def get_form(self, form_class=None):
pk = self.kwargs.get("pk")
instance = self.model.objects.filter(pk=pk).first()
data = None
files = None
if self.request.method == "POST":
@@ -619,18 +752,29 @@ class HorillaFormView(FormView):
self.form_class_path = form.__class__.__module__ + "." + form.__class__.__name__
if self.request.method == "GET":
[
(
"employee_id",
FormView,
)
]
for dynamic_tuple in self.dynamic_create_fields:
view = dynamic_tuple[1]
view.display_title = "Dynamic create"
field = dynamic_tuple[0]
dynamic_create_cache[self.request.session.session_key + field] = {
"dynamic_field": field,
"value": getattr(form.instance, field, ""),
"form": form,
}
key = self.request.session.session_key + "cbv" + field
CACHE.set(
key,
{
"dynamic_field": field,
"value": getattribute(form.instance, field),
"model": form._meta.model,
},
)
from horilla.urls import path, urlpatterns
from django.urls import path
from horilla.urls import urlpatterns
urlpatterns.append(
path(
@@ -660,7 +804,7 @@ class HorillaFormView(FormView):
self.form_class.verbose_name = self.new_display_title
form.close_button_attrs = self.close_button_attrs
form.submit_button_attrs = self.submit_button_attrs
cache[self.request.session.session_key][HorillaFormView] = form
CACHE.get(self.request.session.session_key + "cbv")[HorillaFormView] = form
self.form = form
return form
@@ -692,7 +836,7 @@ class HorillaNavView(TemplateView):
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
update_initial_cache(request, cache, HorillaNavView)
update_initial_cache(request, CACHE, HorillaNavView)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -709,5 +853,143 @@ class HorillaNavView(TemplateView):
context["filter_instance_context_name"] = self.filter_instance
if self.filter_instance:
context[self.filter_form_context_name] = self.filter_instance.form
cache[self.request.session.session_key][HorillaNavView] = context
context["active_view"] = models.ActiveView.objects.filter(
path=self.request.path
).first()
CACHE.get(self.request.session.session_key + "cbv")[HorillaNavView] = context
return context
class HorillaProfileView(DetailView):
"""
GenericHorillaProfileView
"""
template_name = "generic/horilla_profile_view.html"
view_id: str = None
filter_class: FilterSet = None
# add these method under the model
# get_avatar --> image/profile
# get_subtitle --> Job position
actions: list = []
tabs: list = []
def __init__(self, **kwargs: Any) -> None:
self.url_prefix = str(self.__class__.__name__.lower())
if not self.view_id:
self.view_id = get_short_uuid(4)
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
update_initial_cache(request, CACHE, HorillaProfileView)
from horilla.urls import path, urlpatterns
for tab in self.tabs:
if not tab.get("url"):
url = f"{self.url_prefix}-{tab['title']}"
urlpatterns.append(
path(
url + "/<int:pk>/",
tab["view"],
)
)
tab["url"] = "/" + url + "/{pk}/"
# hidden columns configuration
existing_instance = models.ToggleColumn.objects.filter(
user_id=request.user, path=request.path_info
).first()
self.visible_tabs = self.tabs.copy()
self.tabs_list = [(tab["title"], tab["title"]) for tab in self.visible_tabs]
hidden_tabs = (
[] if not existing_instance else existing_instance.excluded_columns
)
self.toggle_form = ToggleColumnForm(
self.tabs_list,
hidden_tabs,
)
for column in self.tabs_list:
if column[1] in hidden_tabs:
for tab in self.visible_tabs:
if tab["title"] == column[1]:
self.visible_tabs.remove(tab)
@classmethod
def add_tab(cls, tab: dict = None, index: int = None, tabs: list = None) -> None:
"""
Tab adding method
example tab arg look like
tab = {
"title":"About",
"view" : views.about_tab,
"accessibility": "path_to_your.accessibility", # path to your accessibility method
}
tabs = [tab,etc...]
add_tab(tabs=tabs) / add_tab(tab=tab)
"""
if tabs:
cls.tabs = cls.tabs + tabs
if tab:
if index is None:
cls.tabs.append(tab)
return
cls.tabs.index(index, tab)
def get_context_data(self, **kwargs: Any) -> dict:
context = super().get_context_data(**kwargs)
context["instance"] = context["object"]
context["tabs"] = self.tabs
context["visible_tabs"] = self.visible_tabs
context["toggle_form"] = self.toggle_form
context["view_id"] = self.view_id
active_tab = models.ActiveTab.objects.filter(
created_by=self.request.user, path=self.request.path
).first()
if active_tab:
context["active_target"] = active_tab.tab_target
instance_ids = eval(self.request.GET.get("instance_ids", "[]"))
instances = self.model.objects.filter(id__in=instance_ids)
context["instances"] = instances
balance_count = instances.count() - 6
if balance_count > 9:
display_count = "9+"
elif 1 < balance_count <= 9:
display_count = f"{balance_count-1}+"
else:
display_count = None
previous_id, next_id = closest_numbers(instance_ids, context["instance"].pk)
url = resolve(self.request.path)
key = list(url.kwargs.keys())[0]
url_name = url.url_name
next_url = reverse(url_name, kwargs={key: next_id})
previous_url = reverse(url_name, kwargs={key: previous_id})
if instance_ids:
context["instance_ids"] = str(instance_ids)
context["next_url"] = next_url
context["previous_url"] = previous_url
context["display_count"] = display_count
context["actions"] = self.actions
context["filter_class"] = self.filter_class
cache = {
"instances": context["instances"],
"instance_ids": context["instance_ids"],
"filter_class": context["filter_class"],
"view_id": context["view_id"],
"object": context["object"],
}
CACHE.set(f"{self.request.session.session_key}search_in_instance_ids", cache)
return context

View File

@@ -50,3 +50,38 @@ class ActiveGroup(HorillaModel):
path = models.CharField(max_length=256)
group_target = models.CharField(max_length=256)
group_by_field = models.CharField(max_length=256)
class SavedFilter(HorillaModel):
"""
SavedFilter
"""
title = models.CharField(max_length=20, null=True)
color = models.CharField(max_length=10, default="")
is_default = models.BooleanField(default=False)
filter = models.TextField()
urlencode = models.TextField(default="")
path = models.CharField(max_length=256)
referrer = models.CharField(max_length=256, default="")
def save(self, *args, **kwargs):
SavedFilter.objects.filter(
is_default=True, path=self.path, created_by=self.created_by
).exclude(id=self.pk).update(is_default=False)
return super().save(*args, **kwargs)
def __str__(self) -> str:
return str(self.title)
class ActiveView(HorillaModel):
"""
This model to store the active view type for HNV
"""
path = models.CharField(max_length=256)
type = models.CharField(max_length=50)
def save(self, *args, **kwargs):
return super().save(*args, **kwargs)

View File

@@ -0,0 +1,10 @@
{% for object in instances|slice:"3" %}
<a
hx-get="{{object.get_profile_url}}"
hx-target="#{{view_id|safe}}"
hx-swap="outerHTML"
><img {% if instance.pk == object.pk %} class="active" {% endif %}
style="border-radius :50px ;overflow :hidden;" src="{{object.get_avatar}}" />
<span>{{object}}</span>
</a>
{% endfor %}

View File

@@ -1,6 +1,16 @@
{% load i18n %} {% load basefilters %}
<!-- filter items showing here -->
<div
class="oh-modal"
id="savedFilterModal"
role="dialog"
aria-labelledby="savedFilterModal"
aria-hidden="true"
>
<div class="oh-modal__dialog" style="max-width: 550px" id="SavedFilterFormTarget"></div>
</div>
<button hx-get='{% url "saved-filter" %}?path={{request.path}}&referrer={{request.META.HTTP_REFERER}}&{{saved_filters.urlencode}}' hx-target="#SavedFilterFormTarget" hidden id="loadsavedfilterform"></button>
<div style="display: none">{{filter_dict}}</div>
<script>
function fieldLabel(value, field) {
@@ -47,23 +57,26 @@
{% endif %}
{%for field,values in filter_dict.items %} {% if values %}
{% with translation_field=field|filter_field %}
{% if field != "path" %}
<span class="oh-titlebar__tag filter-field" >
{% trans translation_field %} :
{% for value in values %}
${fieldLabel("{% trans value %}", "{{field}}")}
{% endfor %}
{% for value in values %}
${fieldLabel("{% trans value %}", "{{field}}")}
{% endfor %}
<button class="oh-titlebar__tag-close" onclick="clearFilterFromTag($(this))" data-x-field="{{field}}">
<ion-icon
name="close-outline"
role="img"
class="md hydrated close-icon"
aria-label="close outline"
>
>
</ion-icon>
</button>
</span>
{% endif %}
{% endwith %} {% endif %} {% endfor %}
{% if filter_dict %}
<button class="oh-titlebar__save" onclick="$('#loadsavedfilterform').click();$('#savedFilterModal').addClass('oh-modal--show')" name="save" aria-label="save" title='{% trans "Save filter" %}''><ion-icon name="bookmark-outline"></ion-icon>Save</button></span>
<span class="oh-titlebar__tag oh-titlebar__tag--custom" title="{% trans 'Clear All' %}" role="button" onclick="clearAllFilter($(this));"
><ion-icon class="close-icon" name="close-outline"></ion-icon></span
>

View File

@@ -1,4 +1,5 @@
{% load widget_tweaks %} {% load i18n %}
{% load generic_template_filters %}
<style>
.condition-highlight {
background-color: #ffa5000f;
@@ -14,15 +15,36 @@
</button>
</div>
{% endif %}
<div class="oh-modal__dialog-body">
<div class="oh-modal__dialog-body oh-modal__dialog-relative">
{% if form.instance_ids %}
<div class="oh-modal__dialog oh-modal__dialog--navigation m-0 p-0">
<button
hx-get="{{form.previous_url}}?{{form.ids_key}}={{form.instance_id}}&{{request.GET.urlencode}}"
hx-swap="innerHTML"
hx-target="#genericModalBody"
class="oh-modal__diaglog-nav oh-modal__nav-prev"
>
<ion-icon name="chevron-back-outline"></ion-icon>
</button>
<button
hx-get="{{form.next_url}}?{{form.ids_key}}={{form.instance_id}}&{{request.GET.urlencode}}"
hx-swap="innerHTML"
hx-target="#genericModalBody"
class="oh-modal__diaglog-nav oh-modal__nav-next"
>
<ion-icon name="chevron-forward-outline"></ion-icon>
</button>
</div>
{% endif %}
<div class="oh-general__tab-target oh-profile-section" id="personal">
<div class="oh-profile-section__card row">
<div class="row">
<div class="col-12">{{ form.non_field_errors }}</div>
<div class="row" style="padding-right: 0;">
<div class="col-12" style="padding-right: 0;">{{ form.non_field_errors }}</div>
{% for field in form.visible_fields %}
<div class="col-12 col-md-6">
<div class="col-12 col-md-{{field|col}}" id="id_{{ field.name }}_parent_div" style="padding-right: 0;">
<div class="oh-label__info" for="id_{{ field.name }}">
<label class="oh-label" for="id_{{ field.name }}"
<label class="oh-label {% if field.field.required %} required-star{% endif %}" for="id_{{ field.name }}"
>{% trans field.label %}</label
>
{% if field.help_text != '' %}
@@ -39,8 +61,10 @@
</div>
{% else %}
<div id="dynamic_field_{{field.name}}">
{{ field|add_class:'form-control' }} {% endif %} {{ field.errors }}
{{ field|add_class:'form-control' }}
{{ field.errors }}
</div>
{% endif %}
</div>
{% endfor %}
</div>

View File

@@ -1,13 +1,18 @@
{% load static i18n generic_template_filters %}
<div id="{{view_id|safe}}">
<script>
if (!$(".HTV").length) {
$("#reloadMessagesButton").click()
}
</script>
{% if bulk_select_option %}
<div class="d-flex justify-content-between">
<div>
<div class="oh-checkpoint-badge text-success"
onclick="
addToSelectedId({{select_all_ids|safe}});
selectSelected('#{{view_id|safe}}');
reloadSelectedCount($('#count_{{view_id|safe}}'));
addToSelectedId({{select_all_ids|safe}},'{{selected_instances_key_id}}');
selectSelected('#{{view_id|safe}}','{{selected_instances_key_id}}');
reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}');
"
style="cursor: pointer;">
{% trans "Select All" %} ({{queryset.paginator.count}})
@@ -17,9 +22,9 @@
class="oh-checkpoint-badge text-secondary d-none"
style="cursor: pointer;"
onclick="
$('#selectedInstances').attr('data-ids',JSON.stringify([]));
selectSelected('#{{view_id|safe}}');
reloadSelectedCount($('#count_{{view_id|safe}}'));
$('#{{selected_instances_key_id}}').attr('data-ids',JSON.stringify([]));
selectSelected('#{{view_id|safe}}','{{selected_instances_key_id}}');
reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}');
$('#{{view_id}} .list-table-row').prop('checked',false);
$('#{{view_id}} .highlight-selected').removeClass('highlight-selected');
$('#{{view_id}} .bulk-list-table-row').prop('checked',false);
@@ -39,12 +44,31 @@
class="oh-checkpoint-badge text-info d-none"
style="cursor: pointer;"
onclick="
selectedIds = $('#selectedInstances').attr('data-ids')
selectedIds = $('#{{selected_instances_key_id}}').attr('data-ids')
window.location.href = '{{export_path}}' + '?ids='+selectedIds
"
>
{% trans "Export" %}
</div>
{% for filter in stored_filters %}
<div class="oh-hover-btn-container"
hx-get="{{request.path}}?{{filter.urlencode}}"
hx-target="#{{view_id|safe}}" hx-swap="outerHTML"
>
<button class="oh-hover-btn" style="
cursor: pointer;
border: solid 2px {{filter.color}};
color: {{filter.color}} !important;
">
{{filter.title}}
</button>
<div class="oh-hover-btn-drawer" onclick="event.stopPropagation()">
<button class="oh-hover-btn__small" onclick="$('#savedFilterModal').addClass('oh-modal--show')" hx-get="{% url "saved-filter-update" filter.id %}" hx-target="#SavedFilterFormTarget" hx-swap="innerHTML"><ion-icon name="create-outline"></ion-icon></button>
<button class="oh-hover-btn__small" onclick="$(this).parent().find('button:hidden').click();$(this).closest('.oh-hover-btn-container').remove()" ><ion-icon name="trash-outline"></ion-icon></button>
<button hidden hx-get="{% url "delete-saved-filter" filter.id %}" hx-swap="none"></button>
</div>
</div>
{% endfor %}
</div>
{% if row_status_indications %}
<div class="d-flex flex-row-reverse">
@@ -73,7 +97,6 @@
onclick="
event.stopPropagation()
$(this).parent().find('.oh-accordion-meta__body').toggleClass('d-none')
$(this).toggleClass('oh-accordion-meta__header--show')
"
>
<span class="oh-accordion-meta__title p-2">
@@ -112,7 +135,7 @@
onchange="
$(this).closest('.oh-sticky-table').find('.list-table-row').prop('checked',$(this).is(':checked')).change();
$(document).ready(function () {
reloadSelectedCount($('#count_{{view_id|safe}}'));
reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}');
});
"
title="Select All"
@@ -163,7 +186,7 @@
if (!element.is(':checked')) {
removeId(element)
}
reloadSelectedCount($('#count_{{view_id|safe}}'));
reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}');
});
"
value = "{{instance.pk}}"
@@ -176,7 +199,7 @@
<div
class="{% if index == 1 %} oh-sticky-table__sd {% else %} oh-sticky-table__td{% endif %}"
>
{{instance|getattribute:attribute|safe}}
{{instance|getattribute:attribute|selected_format:request.user.employee_get.employee_work_info.company_id|safe}}
</div>
{% else %}
<div
@@ -362,7 +385,7 @@
{% if bulk_select_option %}
<script>
ids = JSON.parse(
$("#selectedInstances").attr("data-ids") || "[]"
$("#{{selected_instances_key_id}}").attr("data-ids") || "[]"
);
$.each(ids, function (indexInArray, valueOfElement) {
$(`#{{view_id|safe}} .oh-sticky-table__tbody .list-table-row[value=${valueOfElement}]`).prop("checked",true).change()
@@ -372,7 +395,7 @@
) {
id = $(this).val()
ids = JSON.parse(
$("#selectedInstances").attr("data-ids") || "[]"
$("#{{selected_instances_key_id}}").attr("data-ids") || "[]"
);
ids = Array.from(new Set(ids));
let index = ids.indexOf(id);
@@ -383,7 +406,7 @@
ids.splice(index, 1);
}
}
$("#selectedInstances").attr("data-ids", JSON.stringify(ids));
$("#{{selected_instances_key_id}}").attr("data-ids", JSON.stringify(ids));
}
);
@@ -395,6 +418,11 @@
$(".oh-accordion-meta__header").click(function (e) {
var open = $(this).attr("data-open");
open = JSON.parse(open)
if (!$(this).parent().parent().find(".oh-accordion-meta__body.d-none").length && !$(this).find(".oh-accordion-meta__header--show").length) {
$(this).removeClass("oh-accordion-meta__header--show");
}else{
$(this).addClass("oh-accordion-meta__header--show");
}
$(this).attr("data-open", !open);
var field = $(this).attr("data-field");
var groupIndex = $(this).attr("data-group");

View File

@@ -1,16 +1,42 @@
{% load static i18n generic_template_filters %}
<div id="{{view_id|safe}}">
{% if queryset|length %}
{% if card_status_indications %}
<div class="d-flex flex-row-reverse">
{% for indication in card_status_indications %}
<span class="m-1" style="cursor: pointer;margin-left: 7px;" {{indication.2|safe}}>
<span class="oh-dot oh-dot--small me-1 {{indication.0}}"></span>
{{indication.1}}
</span>
{% endfor %}
<script>
if (!$(".HTV").length) {
$("#reloadMessagesButton").click()
}
</script>
<div class="d-flex justify-content-between mb-2">
<div>
{% for filter in stored_filters %}
<div class="oh-hover-btn-container"
hx-get="{{request.path}}?{{filter.urlencode}}"
hx-target="#{{view_id|safe}}" hx-swap="outerHTML"
>
<button class="oh-hover-btn" style="
cursor: pointer;
border: solid 2px {{filter.color}};
color: {{filter.color}} !important;
">
{{filter.title}}
</button>
<div class="oh-hover-btn-drawer" onclick="event.stopPropagation()">
<button class="oh-hover-btn__small" onclick="$('#savedFilterModal').addClass('oh-modal--show')" hx-get="{% url "saved-filter-update" filter.id %}" hx-target="#SavedFilterFormTarget" hx-swap="innerHTML"><ion-icon name="create-outline"></ion-icon></button>
<button class="oh-hover-btn__small" onclick="$(this).parent().find('button:hidden').click();$(this).closest('.oh-hover-btn-container').remove()" ><ion-icon name="trash-outline"></ion-icon></button>
<button hidden hx-get="{% url "delete-saved-filter" filter.id %}" hx-swap="none"></button>
</div>
</div>
{% endfor %}
</div>
{% if card_status_indications %}
<div class="d-flex flex-row-reverse">
{% for indication in card_status_indications %}
<span class="m-1" style="cursor: pointer;margin-left: 7px;" {{indication.2|safe}}>
<span class="oh-dot oh-dot--small me-1 {{indication.0}}"></span>
{{indication.1}}
</span>
{% endfor %}
</div>
</div>
{% endif %}
<button class="reload-record" hidden hx-get="{{request.path}}?{{request.GET.urlencode}}" hx-target="#{{view_id|safe}}" hx-swap="outerHTML">
</button>
{% if show_filter_tags %}
@@ -29,8 +55,8 @@
</div>
</div>
<div class="oh-kanban-card__details">
<span class="oh-kanban-card__title">{{details.title|format:instance|safe}}</span>
<span class="oh-kanban-card__subtitle">{{details.subtitle|format:instance|safe}}</span>
<span class="oh-kanban-card__title">{{details.title|format:instance|selected_format:request.user.employee_get.employee_work_info.company_id|safe}}</span>
<span class="oh-kanban-card__subtitle">{{details.subtitle|format:instance|selected_format:request.user.employee_get.employee_work_info.company_id|safe}}</span>
</div>
{% if actions %}
<div class="oh-kanban-card__dots" onclick="event.stopPropagation()">

View File

@@ -48,13 +48,13 @@
</div>
<div class="oh-timeoff-modal__profile-info">
<span class="oh-timeoff-modal__user fw-bold">
{{object|getattribute:header.title}}
{{object|getattribute:header.title|selected_format:request.user.employee_get.employee_work_info.company_id}}
</span>
<span
class="oh-timeoff-modal__user m-0"
style="font-size: 18px; color: #4d4a4a"
>
{{object|getattribute:header.subtitle}}</span
{{object|getattribute:header.subtitle|selected_format:request.user.employee_get.employee_work_info.company_id}}</span
>
</div>
</div>
@@ -67,12 +67,16 @@
<div class="row">
{% for col in body %}
<div class="col-6 mt-3">
{% if not col.2 %}
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title">{{col.0}}</span>
<span class="oh-timeoff-modal__stat-count"
>{{object|getattribute:col.1|safe}}</span
>{{object|getattribute:col.1|selected_format:request.user.employee_get.employee_work_info.company_id|safe}}</span
>
</div>
{% else %}
{{object|getattribute:col.1|safe}}
{% endif %}
</div>
{% endfor %}
</div>

View File

@@ -12,12 +12,12 @@
></div>
</div>
{% endfor %}
<form id="{{view_id}}" hx-post="{{request.path}}?{{request.GET.urlencode}}" hx-swap="outerHTML">{{form.structured}}</form>
<form id="{{view_id}}" hx-post="{{request.path}}?{{request.GET.urlencode}}" hx-encoding="multipart/form-data" hx-swap="outerHTML">{{form.structured}}</form>
{% for field_tuple in dynamic_create_fields %}
<button
hidden
id="modalButton{{field_tuple.0}}"
hx-get="dynamic-path-{{field_tuple.0}}-{{request.session.session_key}}?dynamic_field={{field_tuple.0}}"
hx-get="/dynamic-path-{{field_tuple.0}}-{{request.session.session_key}}?dynamic_field={{field_tuple.0}}"
hx-target="#dynamicModal{{field_tuple.0}}Body"
onclick="$('#dynamicModal{{field_tuple.0}}').addClass('oh-modal--show');"
>

View File

@@ -1,5 +1,11 @@
{% load static i18n generic_template_filters %}
<div id="{{view_id|safe}}">
{% include "generic/export_fields_modal.html" %}
<script>
if (!$(".HTV").length) {
$("#reloadMessagesButton").click()
}
</script>
<button class="reload-record" hidden hx-get="{{request.path}}?{{saved_filters.urlencode}}" hx-target="#{{view_id|safe}}" hx-swap="outerHTML">
</button>
{% if show_filter_tags %} {% include "generic/filter_tags.html" %} {% endif %}
@@ -9,9 +15,9 @@
<div>
<div class="oh-checkpoint-badge text-success"
onclick="
addToSelectedId({{select_all_ids|safe}});
selectSelected('#{{view_id|safe}}');
reloadSelectedCount($('#count_{{view_id|safe}}'));
addToSelectedId({{select_all_ids|safe}},'{{selected_instances_key_id}}');
selectSelected('#{{view_id|safe}}','{{selected_instances_key_id}}');
reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}');
"
style="cursor: pointer;">
{% trans "Select All" %} ({{queryset.paginator.count}})
@@ -21,9 +27,9 @@
class="oh-checkpoint-badge text-secondary d-none"
style="cursor: pointer;"
onclick="
$('#selectedInstances').attr('data-ids',JSON.stringify([]));
selectSelected('#{{view_id|safe}}');
reloadSelectedCount($('#count_{{view_id|safe}}'));
$('#{{selected_instances_key_id}}').attr('data-ids',JSON.stringify([]));
selectSelected('#{{view_id|safe}}','{{selected_instances_key_id}}');
reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}');
$('#{{view_id}} .list-table-row').prop('checked',false);
$('#{{view_id}} .highlight-selected').removeClass('highlight-selected');
$('#{{view_id}} .bulk-list-table-row').prop('checked',false);
@@ -42,13 +48,31 @@
id="export_{{view_id}}"
class="oh-checkpoint-badge text-info d-none"
style="cursor: pointer;"
onclick="
selectedIds = $('#selectedInstances').attr('data-ids')
window.location.href = '/{{export_path}}' + '?ids='+selectedIds
"
data-toggle="oh-modal-toggle"
data-target="#exportFields{{view_id|safe}}"
>
{% trans "Export" %}
</div>
{% for filter in stored_filters %}
<div class="oh-hover-btn-container"
hx-get="{{request.path}}?{{filter.urlencode}}"
hx-target="#{{view_id|safe}}" hx-swap="outerHTML"
>
<button class="oh-hover-btn" style="
cursor: pointer;
border: solid 2px {{filter.color}};
color: {{filter.color}} !important;
">
{{filter.title}}
</button>
<div class="oh-hover-btn-drawer" onclick="event.stopPropagation()">
<button class="oh-hover-btn__small" onclick="$('#savedFilterModal').addClass('oh-modal--show')" hx-get="{% url "saved-filter-update" filter.id %}" hx-target="#SavedFilterFormTarget" hx-swap="innerHTML"><ion-icon name="create-outline"></ion-icon></button>
<button class="oh-hover-btn__small" onclick="$(this).parent().find('button:hidden').click();$(this).closest('.oh-hover-btn-container').remove()" ><ion-icon name="trash-outline"></ion-icon></button>
<button hidden hx-get="{% url "delete-saved-filter" filter.id %}" hx-swap="none"></button>
</div>
</div>
{% endfor %}
</div>
{% if row_status_indications %}
<div class="d-flex flex-row-reverse">
@@ -108,7 +132,7 @@
onchange="
$(this).closest('.oh-sticky-table').find('.list-table-row').prop('checked',$(this).is(':checked')).change();
$(document).ready(function () {
reloadSelectedCount($('#count_{{view_id|safe}}'));
reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}');
});
"
title="Select All"
@@ -183,7 +207,7 @@
if (!element.is(':checked')) {
removeId(element)
}
reloadSelectedCount($('#count_{{view_id|safe}}'));
reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}');
});
"
value = "{{instance.pk}}"
@@ -195,7 +219,7 @@
<div
class="{% if index == 1 %} oh-sticky-table__sd {% else %} oh-sticky-table__td{% endif %}"
>
{{instance|getattribute:attribute|safe}}
{{instance|getattribute:attribute|selected_format:request.user.employee_get.employee_work_info.company_id|safe}}
</div>
{% else %}
<div
@@ -329,7 +353,7 @@
</nav>
</div>
<script>
reloadSelectedCount($('#count_{{view_id|safe}}'));
reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}');
var tabId = $("#{{view_id}}").closest(".oh-tabs__content").attr("id");
let badge = $(`#badge-${tabId}`);
let count = "{{queryset.paginator.count}}";
@@ -340,11 +364,10 @@
</script>
{% if bulk_select_option %}
<script>
selectSelected("#{{view_id|safe}}")
selectSelected("#{{view_id|safe}}",'{{selected_instances_key_id}}')
</script>
{% endif %}
{% endif %}
</div>
<script>
$("ul[data-search-url] a").click(function (e) {
e.preventDefault();

View File

@@ -14,7 +14,7 @@
></ion-icon>
</a>
</div>
<form autocomplete="off" id="filterForm" onsubmit="event.preventDefault()" hx-get="{{search_url}}" hx-replace-url="true" hx-target="{{search_swap_target}}" class="oh-main__titlebar oh-main__titlebar--right">
<form autocomplete="off" id="filterForm" onsubmit="event.preventDefault()" hx-get="{{search_url}}?&referrer={{request.META.HTTP_REFERER}}&{{request.GET.urlencode}}" hx-replace-url="true" hx-target="{{search_swap_target}}" class="oh-main__titlebar oh-main__titlebar--right">
<div class="oh-input-group oh-input__search-group" id="searchGroup">
<ion-icon
name="search-outline"
@@ -73,11 +73,20 @@
{% if view_types %}
<ul class="oh-view-types">
{% for type in view_types %}
<li class="oh-view-type" onclick="$(this).closest('form').attr('hx-get','{{type.url}}');$(this).closest('form').find('#applyFilter').click();
<li class="oh-view-type" data-url="{{type.url}}" data-type="{{type.type}}" hx-get="{% url "active-hnv-view-type" %}?view={{type.type}}&path={{request.path}}" hx-swap="none" onclick="$(this).closest('form').attr('hx-get','{{type.url}}?&referrer={{request.META.HTTP_REFERER}}&{{request.GET.urlencode}}');$(this).closest('form').find('#applyFilter').click();
">
<a class="oh-btn oh-btn--view" {{type.attrs|safe}}
><ion-icon name="{{type.icon}}"></ion-icon
></a>
{% if active_view.type == type.type %}
<script>
$("form#filterForm.oh-main__titlebar oh-main__titlebar--right").attr('hx-get','{{type.url}}?&referrer={{request.META.HTTP_REFERER}}&{{request.GET.urlencode}}');
setTimeout(() => {
$(".oh-view-types .oh-view-type[data-type={{type.type}}]").click()
$(".oh-view-types .oh-view-type[data-type={{type.type}}] a").addClass("oh-btn--view-active")
}, 10);
</script>
{% endif %}
</li>
{% endfor %}
</ul>

View File

@@ -0,0 +1,301 @@
{% load static i18n generic_template_filters %}
<div class="oh-card mt-4 mb-5" id="{{view_id|safe}}">
<button
hidden
hx-get="{{request.path}}"
class="reload-profile-view"
hx-target="#{{view_id|safe}}"
hx-swap="outerHTML"
></button>
<div class="row">
<div
class="col-lg-12 d-flex justify-content-between align-items-center mb-4"
>
<div class="members-container d-flex float-left">
<ul class="m-0 p-0 d-flex align-items-center">
{% for object in instances|slice:"6" %}
<li {% if instance.pk == object.pk %} class="active" {% endif %}>
<a
hx-get="{{object.get_profile_url}}"
hx-target="#{{view_id|safe}}"
hx-swap="outerHTML"
class="d-block">
<img
style="border-radius :50px ;overflow :hidden;"
src="{{object.get_avatar}}"
width="28px"
/>
</a>
</li>
{% endfor %}
{% if display_count %}
<li class="li-last">
<a href="#" class="d-block" id="dropdownLink"> {{display_count}} </a>
<div class="dropdown-content" id="dropdownContent">
<div
class="oh-input-group oh-input__search-group my-2"
>
<ion-icon
name="search-outline"
class="oh-input-group__icon oh-input-group__icon--left md hydrated oh-faq_search--icon"
role="img"
aria-label="search outline"
></ion-icon>
<input
type="text"
hx-get="{% url "search-in-instance-ids" %}?instance_ids={{instance_ids}}"
hx-target="#{{view_id|safe}} #profileSearch"
hx-trigger="keyup"
class="oh-input oh-input__icon"
aria-label="Search Input"
name="search"
placeholder="Search"
/>
<div class="oh-autocomplete-suggestions"></div>
</div>
<div id="profileSearch">
{% for object in instances|slice:"6:9" %}
<a
hx-get="{{object.get_profile_url}}"
hx-target="#{{view_id|safe}}"
hx-swap="outerHTML"
><img
{% if instance.pk == object.pk %} class="active" {% endif %}
style="border-radius :30px ;overflow :hidden;"
src="{{object.get_avatar}}" />
<span>{{object}}</span>
</a>
{% endfor %}
</div>
</div>
</li>
{% endif %}
</ul>
</div>
<div class="d-flex align-items-center gap-3">
<div class="container-right-left-arrows float-right">
<a
hx-get="{{previous_url}}?instance_ids={{instance_ids}}"
hx-target="#{{view_id|safe}}"
hx-swap="outerHTML"
title=""
style="color: hsl(8, 77%, 56%);cursor: pointer;"
class="ms-1 fw-bold"
>
<ion-icon
name="arrow-back-circle-outline"
style="font-size: 35px"
role="img"
class="md"
aria-label="arrow back circle outline"
></ion-icon>
</a>
<a
hx-get="{{next_url}}?instance_ids={{instance_ids}}"
hx-target="#{{view_id|safe}}"
hx-swap="outerHTML"
title=""
style="color: hsl(8, 77%, 56%);cursor: pointer;"
class="ms-1 fw-bold"
>
<ion-icon
name="arrow-forward-circle-outline"
style="font-size: 35px"
role="img"
class="md d"
aria-label="arrow forward circle outline"
></ion-icon>
</a>
</div>
{% if actions %}
<div class="oh-dropdown" x-data="{open: false}">
<button
class="oh-btn oh-stop-prop oh-btn--transparent oh-accordion-meta__btn py-0 px-2 mb-2"
@click="open = !open"
@click.outside="open = false"
>
<ion-icon
name="settings-outline"
class="oh-navbar__icon md hydrated"
role="img"
aria-label="settings outline"
></ion-icon>
</button>
<div
class="oh-dropdown__menu oh-dropdown__menu--right"
style="min-width: 250px"
x-show="open"
>
<ul class="oh-dropdown__items">
{% for action in actions %}
<li class="oh-dropdown__item">
<a href="#" class="oh-profile-dropdown-link" {{action.attrs|safe}}>
<img
src="{{action.icon}}"
style="width: 20px; height: auto"
title="{{action.title}}"
/>
<button
style="border: none; background: transparent"
>
{{action.title}}
</button>
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-12 col-md-12 col-lg-4">
<div class="d-flex align-items-center">
<div class="oh-profile oh-profile--lg me-3">
<div class="oh-profile__avatar">
<img src="{{instance.get_avatar}}" class="oh-profile__image" />
</div>
<span
class="oh-profile__active-badge oh-profile__active-badge--secondary"
title="Active"
></span>
</div>
<div class="oh-profile__info-container">
<h1 class="oh-profile__info-name">{{instance}}</h1>
<p class="oh-profile__info-designation">{{instance.get_subtitle}}</p>
</div>
</div>
</div>
<div class="col-12 col-sm-12 col-md-12 col-lg-8 d-flex align-items-center">
<ul class="oh-profile__info-list">
<li class="oh-profile__info-list-item">
<span class="oh-profile__info-label">
<ion-icon name="mail-outline"></ion-icon>
<span>{% trans "E-mail" %}:</span>
</span>
<span class="oh-profile__info-value">{{instance.get_email}}</span>
</li>
<li class="oh-profile__info-list-item">
<span class="oh-profile__info-label">
<ion-icon name="call-outline"></ion-icon>
<span>{% trans "Phone" %}:</span>
</span>
<span class="oh-profile__info-value">{{instance.get_contact}}</span>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col lg-12 oh-table_sticky--wrapper">
<div class="oh-sticky-dropdown--header" style="border: none">
<div class="oh-dropdown" x-data="{open: false}">
<button class="oh-sticky-dropdown_btn" @click="open = !open">
<ion-icon
name="ellipsis-vertical-sharp"
role="img"
class="md hydrated"
aria-label="ellipsis vertical sharp"
></ion-icon>
</button>
<div
class="oh-dropdown__menu oh-sticky-table_dropdown"
x-show="open"
@click.outside="
open = false
$($el).closest('#{{view_id|safe}}').parent().find('.reload-profile-view').click();
"
>
{{toggle_form.as_list}}
</div>
</div>
</div>
<ul
class="oh-general__tabs oh-general__tabs--border oh-general__tabs--profile oh-general__tabs--no-grow oh-profile-section__tab mt-5"
>
{% for tab in visible_tabs %} {% if not tab.accessibility or tab.accessibility|accessibility:instance %}
<li class="oh-general__tab">
<a
href="#"
class="oh-general__tab-link {% if forloop.counter == 1 and not active_target %} oh-general__tab-link--active {% endif %}"
data-action="general-tab"
onclick="switchGeneralTab(event)"
data-target="#profileTab{{forloop.counter}}"
hx-get="{{tab.url|format:instance}}"
hx-trigger="revealed"
hx-target="#profileTab{{forloop.counter}}"
>{{tab.title}}</a
>
</li>
{% endif %} {% endfor %}
</ul>
{% for tab in visible_tabs %}
<div
class="oh-general__tab-target oh-profile__info-tab mb-4 pt-4 {% if forloop.counter == 1 and not active_target %} {% else %} d-none {% endif %}"
id="profileTab{{forloop.counter}}"
>
{{tab.title}}
</div>
{% endfor %}
</div>
</div>
<script>
$(".oh-general__tab-link").click(function (e) {
var target = `[data-target="${$(this).attr('data-target')}"]`
e.preventDefault();
$.ajax({
type: "get",
url: "{% url 'active-tab' %}",
data: {
"path":"{{request.path}}",
"target":target,
},
success: function (response) {
}
});
});
{% if active_target %}
$("#{{view_id|safe}}").find(`{{active_target|safe}}`).click();
{% endif %}
</script>
<script>
document.querySelectorAll("ul li").forEach(function (link) {
link.addEventListener("click", function (event) {
event.preventDefault();
document.querySelectorAll("ul li").forEach(function (link) {
link.classList.remove("active");
});
this.classList.add("active");
});
});
</script>
<script>
document
.getElementById("dropdownLink")
.addEventListener("click", function (event) {
event.preventDefault();
document.getElementById("dropdownContent").classList.toggle("show");
});
window.onclick = function (event) {
if (
!event.target.matches("#dropdownLink") &&
!event.target.closest("#dropdownContent")
) {
var dropdowns = document.getElementsByClassName("dropdown-content");
for (var i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains("show")) {
openDropdown.classList.remove("show");
}
}
}
};
</script>
</div>

View File

@@ -4,7 +4,11 @@
{% comment %} {% include "attendance/attendance/attendance_nav.html" %} {% endcomment %}
{% load i18n generic_template_filters %}
<div class="oh-tabs">
<div class="oh-tabs HTV">
<script>
$("#reloadMessagesButton").click()
</script>
<div class="HTV"></div>
<ul class="oh-tabs__tablist">
{% for tab in tabs %}
<li

View File

@@ -0,0 +1,6 @@
{% extends "index.html" %}
{% block content %}
<div class="oh-wrapper" hx-get="/employee-profile-view/5/?instance_ids=[1,2,3,4,5,6,7,8,9,10,11,12,13]" hx-trigger="load">
</div>
{% endblock content %}

View File

@@ -1,10 +1,11 @@
"""
horillafilters.py
attendancefilters.py
This module is used to write custom template filters.
"""
import datetime
import re
import types
@@ -21,6 +22,38 @@ register = template.Library()
numeric_test = re.compile("^\d+$")
date_format_mapping = {
"DD-MM-YYYY": "%d-%m-%Y",
"DD.MM.YYYY": "%d.%m.%Y",
"DD/MM/YYYY": "%d/%m/%Y",
"MM/DD/YYYY": "%m/%d/%Y",
"YYYY-MM-DD": "%Y-%m-%d",
"YYYY/MM/DD": "%Y/%m/%d",
"MMMM D, YYYY": "%B %d, %Y",
"DD MMMM, YYYY": "%d %B, %Y",
"MMM. D, YYYY": "%b. %d, %Y",
"D MMM. YYYY": "%d %b. %Y",
"dddd, MMMM D, YYYY": "%A, %B %d, %Y",
}
time_format_mapping = {
"hh:mm A": "%I:%M %p",
"HH:mm": "%H:%M",
}
@register.filter(name="selected_format")
def selected_format(date: datetime.date, company: object = None) -> str:
if company and (company.date_format or company.time_format):
if isinstance(date, datetime.date):
format = company.date_format
date_format_mapping.get(format)
return date.strftime(date_format_mapping[format])
elif isinstance(date, datetime.time):
format = company.time_format
return date.strftime(time_format_mapping[format])
return date
@register.filter(name="getattribute")
def getattribute(value, attr: str):
@@ -77,3 +110,13 @@ def accessibility(method: str, instance=None):
PermWrapper(request.user),
)
return True
@register.filter("col")
def col(field: object):
"""
Method to get field col sepration
"""
field_name = field.name
cols = getattr(field.form, "cols", {})
return cols.get(field_name, 6)

View File

@@ -13,4 +13,25 @@ urlpatterns = [
path("active-group", views.ActiveGroup.as_view(), name="cbv-active-group"),
path("reload-field", views.ReloadField.as_view(), name="reload-field"),
path("reload-messages", ReloadMessages.as_view(), name="reload-messages"),
path("saved-filter/", views.SavedFilter.as_view(), name="saved-filter"),
path(
"saved-filter/<int:pk>/",
views.SavedFilter.as_view(),
name="saved-filter-update",
),
path(
"delete-saved-filter/<int:pk>/",
views.DeleteSavedFilter.as_view(),
name="delete-saved-filter",
),
path(
"active-hnv-view-type/",
views.ActiveView.as_view(),
name="active-hnv-view-type",
),
path(
"search-in-instance-ids",
views.SearchInIds.as_view(),
name="search-in-instance-ids",
),
]

View File

@@ -1,17 +1,23 @@
import importlib
from django import forms
from django.contrib import messages
from django.core.cache import cache as CACHE
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_protect
from horilla_views import models
from horilla_views.cbv_methods import get_short_uuid
from horilla_views.generic.cbv.views import dynamic_create_cache
from horilla_views.cbv_methods import get_short_uuid, login_required
from horilla_views.forms import SavedFilterForm
from horilla_views.generic.cbv.views import HorillaFormView
# Create your views here.
@method_decorator(login_required, name="dispatch")
class ToggleColumn(View):
"""
ToggleColumn
@@ -42,6 +48,7 @@ class ToggleColumn(View):
return HttpResponse("success")
@method_decorator(login_required, name="dispatch")
class ReloadField(View):
"""
ReloadField
@@ -58,17 +65,15 @@ class ReloadField(View):
module = importlib.import_module(module_name)
parent_form = getattr(module, class_name)()
dynamic_cache = dynamic_create_cache.get(
request.session.session_key + reload_field
)
form: forms.ModelForm = dynamic_cache["form"]
dynamic_cache = CACHE.get(request.session.session_key + "cbv" + reload_field)
model: models.HorillaModel = dynamic_cache["model"]
cache_field = dynamic_cache["dynamic_field"]
if cache_field != reload_field:
cache_field = reload_field
field = parent_form.fields[cache_field]
queryset = form._meta.model.objects.all()
queryset = model.objects.all()
queryset = field.queryset
choices = [(instance.id, instance) for instance in queryset]
choices.insert(0, ("", "Select option"))
@@ -91,6 +96,7 @@ class ReloadField(View):
)
@method_decorator(login_required, name="dispatch")
class ActiveTab(View):
def get(self, *args, **kwargs):
"""
@@ -112,6 +118,7 @@ class ActiveTab(View):
return JsonResponse({"message": "Success"})
@method_decorator(login_required, name="dispatch")
class ActiveGroup(View):
def get(self, *args, **kwargs):
"""
@@ -135,3 +142,91 @@ class ActiveGroup(View):
instance.group_target = target
instance.save()
return JsonResponse({"message": "Success"})
@method_decorator(login_required, name="dispatch")
class SavedFilter(HorillaFormView):
"""
SavedFilter
"""
model = models.SavedFilter
form_class = SavedFilterForm
new_display_title = "Save Applied Filter"
template_name = "generic/saved_filter_form.html"
form_disaply_attr = "Blah"
def form_valid(self, form: SavedFilterForm) -> HttpResponse:
referrer = self.request.POST.get("referrer", "")
path = self.request.POST.get("path", "/")
result_dict = {key: value[0] for key, value in self.request.GET.lists()}
if form.is_valid():
instance: models.SavedFilter = form.save(commit=False)
if not instance.pk:
instance.path = path
instance.referrer = referrer
instance.filter = result_dict
instance.urlencode = self.request.GET.urlencode()
instance.save()
messages.success(self.request, "Filter Saved")
return self.HttpResponse()
return super().form_valid(form)
def get_context_data(self, **kwargs) -> dict:
context = super().get_context_data(**kwargs)
referrer = self.request.GET.get("referrer", "")
if referrer:
# Remove the protocol and domain part
referrer = "/" + "/".join(referrer.split("/")[3:])
context["path"] = self.request.GET.get("path", "")
context["referrer"] = referrer
return context
@method_decorator(login_required, name="dispatch")
class DeleteSavedFilter(View):
"""
Delete saved filter
"""
def get(self, *args, **kwargs):
pk = kwargs["pk"]
models.SavedFilter.objects.filter(created_by=self.request.user, pk=pk).delete()
return HttpResponse("")
@method_decorator(login_required, name="dispatch")
class ActiveView(View):
"""
ActiveView CBV
"""
def get(self, *args, **kwargs):
path = self.request.GET["path"]
view_type = self.request.GET["view"]
active_view = models.ActiveView.objects.filter(
path=path, created_by=self.request.user
).first()
active_view = active_view if active_view else models.ActiveView()
active_view.path = path
active_view.type = view_type
active_view.save()
return HttpResponse("")
@method_decorator(login_required, name="dispatch")
@method_decorator(csrf_protect, name="dispatch")
class SearchInIds(View):
"""
Search in ids view
"""
def get(self, *args, **kwargs):
"""
Search in instance ids method
"""
cache_key = f"{self.request.session.session_key}search_in_instance_ids"
context: dict = CACHE.get(cache_key)
context["instances"] = context["filter_class"](self.request.GET).qs
return render(self.request, "generic/filter_result.html", context)