[UPDT] HORILLA VIEWS: CBV updates
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
10
horilla_views/templates/generic/filter_result.html
Normal file
10
horilla_views/templates/generic/filter_result.html
Normal 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 %}
|
||||
@@ -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
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');"
|
||||
>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
301
horilla_views/templates/generic/horilla_profile_view.html
Normal file
301
horilla_views/templates/generic/horilla_profile_view.html
Normal 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>
|
||||
@@ -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
|
||||
|
||||
6
horilla_views/templates/generic/profile_view.html
Normal file
6
horilla_views/templates/generic/profile_view.html
Normal 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 %}
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user