[IMP] AUTOMATIONS: Add horilla automations
This commit is contained in:
0
horilla_views/__init__.py
Normal file
0
horilla_views/__init__.py
Normal file
10
horilla_views/admin.py
Normal file
10
horilla_views/admin.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.contrib import admin
|
||||
from horilla_views.models import (
|
||||
ToggleColumn,
|
||||
ParentModel,
|
||||
childModel,
|
||||
ActiveTab,
|
||||
ActiveGroup,
|
||||
)
|
||||
|
||||
admin.site.register([ToggleColumn, ParentModel, childModel, ActiveTab, ActiveGroup])
|
||||
6
horilla_views/apps.py
Normal file
6
horilla_views/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HorillaViewsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'horilla_views'
|
||||
292
horilla_views/cbv_methods.py
Normal file
292
horilla_views/cbv_methods.py
Normal file
@@ -0,0 +1,292 @@
|
||||
"""
|
||||
horilla/cbv_methods.py
|
||||
"""
|
||||
|
||||
from urllib.parse import urlencode
|
||||
import uuid
|
||||
from venv import logger
|
||||
from django import template
|
||||
from django.shortcuts import redirect, render
|
||||
from django.template import loader
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.defaultfilters import register
|
||||
from django.urls import reverse
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponse
|
||||
from django.core.paginator import Paginator
|
||||
from django.middleware.csrf import get_token
|
||||
from django.utils.html import format_html
|
||||
from django.utils.functional import lazy
|
||||
from django.utils.safestring import SafeString
|
||||
|
||||
from horilla import settings
|
||||
from horilla_views.templatetags.generic_template_filters import getattribute
|
||||
from base.thread_local_middleware import _thread_locals
|
||||
|
||||
|
||||
def decorator_with_arguments(decorator):
|
||||
"""
|
||||
Decorator that allows decorators to accept arguments and keyword arguments.
|
||||
|
||||
Args:
|
||||
decorator (function): The decorator function to be wrapped.
|
||||
|
||||
Returns:
|
||||
function: The wrapper function.
|
||||
|
||||
"""
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
"""
|
||||
Wrapper function that captures the arguments and keyword arguments.
|
||||
|
||||
Args:
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
function: The inner wrapper function.
|
||||
|
||||
"""
|
||||
|
||||
def inner_wrapper(func):
|
||||
"""
|
||||
Inner wrapper function that applies the decorator to the function.
|
||||
|
||||
Args:
|
||||
func (function): The function to be decorated.
|
||||
|
||||
Returns:
|
||||
function: The decorated function.
|
||||
|
||||
"""
|
||||
return decorator(func, *args, **kwargs)
|
||||
|
||||
return inner_wrapper
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def login_required(view_func):
|
||||
def wrapped_view(self, *args, **kwargs):
|
||||
request = getattr(_thread_locals, "request")
|
||||
if not getattr(self, "request", None):
|
||||
self.request = request
|
||||
path = request.path
|
||||
res = path.split("/", 2)[1].capitalize().replace("-", " ").upper()
|
||||
if res == "PMS":
|
||||
res = "Performance"
|
||||
request.session["title"] = res
|
||||
if path == "" or path == "/":
|
||||
request.session["title"] = "Dashboard".upper()
|
||||
if not request.user.is_authenticated:
|
||||
login_url = reverse("login")
|
||||
params = urlencode(request.GET)
|
||||
url = f"{login_url}?next={request.path}"
|
||||
if params:
|
||||
url += f"&{params}"
|
||||
return redirect(url)
|
||||
try:
|
||||
func = view_func(self, request, *args, **kwargs)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
if not settings.DEBUG:
|
||||
return render(request, "went_wrong.html")
|
||||
return view_func(self, *args, **kwargs)
|
||||
return func
|
||||
|
||||
return wrapped_view
|
||||
|
||||
|
||||
@decorator_with_arguments
|
||||
def permission_required(function, perm):
|
||||
def _function(self, *args, **kwargs):
|
||||
request = getattr(_thread_locals, "request")
|
||||
if not getattr(self, "request", None):
|
||||
self.request = request
|
||||
|
||||
if request.user.has_perm(perm):
|
||||
return function(self, *args, **kwargs)
|
||||
else:
|
||||
messages.info(request, "You dont have permission.")
|
||||
previous_url = request.META.get("HTTP_REFERER", "/")
|
||||
key = "HTTP_HX_REQUEST"
|
||||
if key in request.META.keys():
|
||||
return render(request, "decorator_404.html")
|
||||
script = f'<script>window.location.href = "{previous_url}"</script>'
|
||||
return HttpResponse(script)
|
||||
|
||||
return _function
|
||||
|
||||
|
||||
|
||||
def csrf_input(request):
|
||||
return format_html(
|
||||
'<input type="hidden" name="csrfmiddlewaretoken" value="{}">',
|
||||
get_token(request),
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def csrf_token(context):
|
||||
"""
|
||||
to access csrf token inside the render_template method
|
||||
"""
|
||||
request = context["request"]
|
||||
csrf_input_lazy = lazy(csrf_input, SafeString, str)
|
||||
return csrf_input_lazy(request)
|
||||
|
||||
|
||||
def get_all_context_variables(request) -> dict:
|
||||
"""
|
||||
This method will return dictionary format of context processors
|
||||
"""
|
||||
if getattr(request, "all_context_variables", None) is None:
|
||||
all_context_variables = {}
|
||||
for processor_path in settings.TEMPLATES[0]["OPTIONS"]["context_processors"]:
|
||||
module_path, func_name = processor_path.rsplit(".", 1)
|
||||
module = __import__(module_path, fromlist=[func_name])
|
||||
func = getattr(module, func_name)
|
||||
context = func(request)
|
||||
all_context_variables.update(context)
|
||||
all_context_variables["csrf_token"] = csrf_token(all_context_variables)
|
||||
request.all_context_variables = all_context_variables
|
||||
return request.all_context_variables
|
||||
|
||||
|
||||
def render_template(
|
||||
path: str,
|
||||
context: dict,
|
||||
decoding: str = "utf-8",
|
||||
status: int = None,
|
||||
_using=None,
|
||||
) -> str:
|
||||
"""
|
||||
This method is used to render HTML text with context.
|
||||
"""
|
||||
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
context.update(get_all_context_variables(request))
|
||||
template_loader = loader.get_template(path)
|
||||
template_body = template_loader.template.source
|
||||
template_bdy = template.Template(template_body)
|
||||
context_instance = template.Context(context)
|
||||
rendered_content = template_bdy.render(context_instance)
|
||||
return HttpResponse(rendered_content, status=status).content.decode(decoding)
|
||||
|
||||
|
||||
def paginator_qry(qryset, page_number, records_per_page=50):
|
||||
"""
|
||||
This method is used to paginate queryset
|
||||
"""
|
||||
paginator = Paginator(qryset, records_per_page)
|
||||
qryset = paginator.get_page(page_number)
|
||||
return qryset
|
||||
|
||||
|
||||
def get_short_uuid(length: int, prefix: str = "hlv"):
|
||||
"""
|
||||
Short uuid generating method
|
||||
"""
|
||||
uuid_str = str(uuid.uuid4().hex)
|
||||
return prefix + str(uuid_str[:length]).replace("-", "")
|
||||
|
||||
|
||||
def update_initial_cache(request: object, cache: dict, view: object):
|
||||
if cache.get(request.session.session_key):
|
||||
cache[request.session.session_key].update({view: {}})
|
||||
return
|
||||
cache.update({request.session.session_key: {view: {}}})
|
||||
return
|
||||
|
||||
|
||||
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("generic/form.html", context)
|
||||
return table_html
|
||||
|
||||
|
||||
class Reverse:
|
||||
reverse: bool = True
|
||||
page: str = ""
|
||||
|
||||
|
||||
cache = {}
|
||||
|
||||
|
||||
def sortby(
|
||||
query_dict, queryset, key: str, page: str = "page", is_first_sort: bool = False
|
||||
):
|
||||
"""
|
||||
New simplified method to sort the queryset/lists
|
||||
"""
|
||||
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 = (
|
||||
"1" if not query_dict.get(page) else query_dict.get(page)
|
||||
)
|
||||
reverse = cache[request.session.session_key].reverse
|
||||
none_ids = []
|
||||
|
||||
def _sortby(object):
|
||||
result = getattribute(object, attr=sort_key)
|
||||
if result is None:
|
||||
none_ids.append(object.pk)
|
||||
return result
|
||||
|
||||
order = not reverse
|
||||
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
|
||||
):
|
||||
order = not order
|
||||
cache[request.session.session_key].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)
|
||||
queryset = queryset + none_queryset
|
||||
|
||||
cache[request.session.session_key].reverse = order
|
||||
order = "asc" if not order else "desc"
|
||||
setattr(request, "sort_order", order)
|
||||
setattr(request, "sort_key", sort_key)
|
||||
return queryset
|
||||
|
||||
|
||||
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(
|
||||
{
|
||||
"path": request.path,
|
||||
"query_dict": request.GET,
|
||||
"request": request,
|
||||
}
|
||||
)
|
||||
return cache
|
||||
cache.update(
|
||||
{
|
||||
request.session.session_key: {
|
||||
"path": request.path,
|
||||
"query_dict": request.GET,
|
||||
"request": request,
|
||||
}
|
||||
}
|
||||
)
|
||||
return cache
|
||||
35
horilla_views/forms.py
Normal file
35
horilla_views/forms.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
horilla_views/forms.py
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django.utils.safestring import SafeText
|
||||
from django.template.loader import render_to_string
|
||||
from base.thread_local_middleware import _thread_locals
|
||||
|
||||
|
||||
class ToggleColumnForm(forms.Form):
|
||||
"""
|
||||
Toggle column form
|
||||
"""
|
||||
|
||||
def __init__(self, columns, hidden_fields: list, *args, **kwargs):
|
||||
request = getattr(_thread_locals, "request", {})
|
||||
self.request = request
|
||||
super().__init__(*args, **kwargs)
|
||||
for column in columns:
|
||||
initial = True
|
||||
if column[1] in hidden_fields:
|
||||
initial = False
|
||||
|
||||
self.fields[column[1]] = forms.BooleanField(
|
||||
label=column[0], initial=initial
|
||||
)
|
||||
|
||||
def as_list(self) -> SafeText:
|
||||
"""
|
||||
Render the form fields as HTML table rows with.
|
||||
"""
|
||||
context = {"form": self, "request": self.request}
|
||||
table_html = render_to_string("generic/as_list.html", context)
|
||||
return table_html
|
||||
0
horilla_views/generic/__init__.py
Normal file
0
horilla_views/generic/__init__.py
Normal file
0
horilla_views/generic/cbv/__init__.py
Normal file
0
horilla_views/generic/cbv/__init__.py
Normal file
681
horilla_views/generic/cbv/views.py
Normal file
681
horilla_views/generic/cbv/views.py
Normal file
@@ -0,0 +1,681 @@
|
||||
"""
|
||||
horilla/generic/views.py
|
||||
"""
|
||||
|
||||
import json
|
||||
from django import forms
|
||||
from django.http import HttpRequest, HttpResponse, QueryDict
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
from typing import Any
|
||||
from django.urls import resolve
|
||||
from urllib.parse import parse_qs
|
||||
from django.core.paginator import Page
|
||||
from django.views.generic import ListView, DetailView, TemplateView, FormView
|
||||
from attendance.methods.group_by import group_by_queryset
|
||||
from base.methods import (
|
||||
closest_numbers,
|
||||
get_key_instances,
|
||||
)
|
||||
from horilla.filters import FilterSet
|
||||
from horilla_views import models
|
||||
from horilla_views.cbv_methods import (
|
||||
get_short_uuid,
|
||||
paginator_qry,
|
||||
update_initial_cache,
|
||||
sortby,
|
||||
update_saved_filter_cache,
|
||||
)
|
||||
from base.thread_local_middleware import _thread_locals
|
||||
from horilla_views.cbv_methods import structured
|
||||
from horilla_views.forms import ToggleColumnForm
|
||||
from horilla_views.templatetags.generic_template_filters import getattribute
|
||||
|
||||
|
||||
cache = {}
|
||||
saved_filters = {}
|
||||
|
||||
|
||||
class HorillaListView(ListView):
|
||||
"""
|
||||
HorillaListView
|
||||
"""
|
||||
|
||||
filter_class: FilterSet = None
|
||||
|
||||
view_id: str = """"""
|
||||
|
||||
export_file_name: str = None
|
||||
|
||||
template_name: str = "generic/horilla_list.html"
|
||||
context_object_name = "queryset"
|
||||
# column = [("Verbose Name","field_name","avatar_mapping")], opt: avatar_mapping
|
||||
columns: list = []
|
||||
search_url: str = ""
|
||||
bulk_select_option: bool = True
|
||||
|
||||
action_method: str = """"""
|
||||
actions: list = []
|
||||
|
||||
option_method: str = ""
|
||||
options: list = []
|
||||
row_attrs: str = """"""
|
||||
row_status_class: str = """"""
|
||||
row_status_indications: list = []
|
||||
|
||||
sortby_key: str = "sortby"
|
||||
sortby_mapping: list = []
|
||||
|
||||
show_filter_tags: bool = True
|
||||
filter_keys_to_remove: list = []
|
||||
|
||||
records_per_page: int = 50
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
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)
|
||||
|
||||
# hidden columns configuration
|
||||
existing_instance = models.ToggleColumn.objects.filter(
|
||||
user_id=request.user, path=request.path_info
|
||||
).first()
|
||||
|
||||
hidden_fields = (
|
||||
[] if not existing_instance else existing_instance.excluded_columns
|
||||
)
|
||||
|
||||
self.visible_column = self.columns.copy()
|
||||
|
||||
self.toggle_form = ToggleColumnForm(self.columns, hidden_fields)
|
||||
for column in self.columns:
|
||||
if column[1] in hidden_fields:
|
||||
self.visible_column.remove(column)
|
||||
|
||||
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"
|
||||
]
|
||||
|
||||
self._saved_filters = query_dict
|
||||
queryset = self.filter_class(query_dict, queryset).qs
|
||||
return 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["toggle_form"] = self.toggle_form
|
||||
context["search_url"] = self.search_url
|
||||
|
||||
context["action_method"] = self.action_method
|
||||
context["actions"] = self.actions
|
||||
|
||||
context["option_method"] = self.option_method
|
||||
context["options"] = self.options
|
||||
context["row_attrs"] = self.row_attrs
|
||||
|
||||
context["show_filter_tags"] = self.show_filter_tags
|
||||
context["bulk_select_option"] = self.bulk_select_option
|
||||
context["row_status_class"] = self.row_status_class
|
||||
context["sortby_key"] = self.sortby_key
|
||||
context["sortby_mapping"] = self.sortby_mapping
|
||||
context["row_status_indications"] = self.row_status_indications
|
||||
context["saved_filters"] = self._saved_filters
|
||||
|
||||
context["select_all_ids"] = self.select_all
|
||||
if self._saved_filters.get("field"):
|
||||
active_group = models.ActiveGroup.objects.filter(
|
||||
created_by=self.request.user,
|
||||
path=self.request.path,
|
||||
group_by_field=self._saved_filters["field"],
|
||||
).first()
|
||||
if active_group:
|
||||
context["active_target"] = active_group.group_target
|
||||
|
||||
queryset = self.get_queryset()
|
||||
|
||||
if self.show_filter_tags:
|
||||
data_dict = parse_qs(self._saved_filters.urlencode())
|
||||
data_dict = get_key_instances(self.model, data_dict)
|
||||
keys_to_remove = [
|
||||
key
|
||||
for key, value in data_dict.items()
|
||||
if value[0] in ["unknown", "on"] + self.filter_keys_to_remove
|
||||
]
|
||||
|
||||
for key in keys_to_remove:
|
||||
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 (
|
||||
not request.GET.get(self.sortby_key)
|
||||
and not self._saved_filters.get(self.sortby_key)
|
||||
) or (
|
||||
not request.GET.get(self.sortby_key)
|
||||
and self._saved_filters.get(self.sortby_key)
|
||||
):
|
||||
is_first_sort = True
|
||||
query_dict = self._saved_filters
|
||||
|
||||
if query_dict.get(self.sortby_key):
|
||||
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)
|
||||
|
||||
context["queryset"] = paginator_qry(
|
||||
queryset, self._saved_filters.get("page"), self.records_per_page
|
||||
)
|
||||
|
||||
if request and self._saved_filters.get("field"):
|
||||
field = self._saved_filters.get("field")
|
||||
self.template_name = "generic/group_by.html"
|
||||
if isinstance(queryset, Page):
|
||||
queryset = self.filter_class(
|
||||
request.GET, queryset=queryset.object_list.model.objects.all()
|
||||
).qs
|
||||
groups = group_by_queryset(
|
||||
queryset, field, self._saved_filters.get("page"), "page"
|
||||
)
|
||||
context["groups"] = paginator_qry(
|
||||
groups, self._saved_filters.get("page"), 10
|
||||
)
|
||||
cache[self.request.session.session_key][HorillaListView] = context
|
||||
from horilla.urls import urlpatterns, path
|
||||
|
||||
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):
|
||||
"""
|
||||
Select all method
|
||||
"""
|
||||
return json.dumps(list(self.get_queryset().values_list("id", flat=True)))
|
||||
|
||||
def export_data(self, *args, **kwargs):
|
||||
"""
|
||||
Export list view visible columns
|
||||
"""
|
||||
from import_export import resources, fields
|
||||
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
ids = eval(request.GET["ids"])
|
||||
queryset = self.model.objects.filter(id__in=ids)
|
||||
|
||||
MODEL = self.model
|
||||
FIELDS = self.visible_column
|
||||
|
||||
class HorillaListViewResorce(resources.ModelResource):
|
||||
id = fields.Field(column_name="ID")
|
||||
|
||||
class Meta:
|
||||
model = MODEL
|
||||
fields = []
|
||||
|
||||
def dehydrate_id(self, instance):
|
||||
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]}'))"
|
||||
exec(dynamic_fn_str)
|
||||
dynamic_fn = locals()[f"dehydrate_{field_tuple[1]}"]
|
||||
locals()[field_tuple[1]] = fields.Field(column_name=field_tuple[0])
|
||||
|
||||
book_resource = HorillaListViewResorce()
|
||||
|
||||
# Export the data using the resource
|
||||
dataset = book_resource.export(queryset)
|
||||
|
||||
excel_data = dataset.export("xls")
|
||||
|
||||
# Set the response headers
|
||||
file_name = self.export_file_name
|
||||
if not file_name:
|
||||
file_name = "quick_export"
|
||||
response = HttpResponse(excel_data, content_type="application/vnd.ms-excel")
|
||||
response["Content-Disposition"] = f'attachment; filename="{file_name}.xls"'
|
||||
return response
|
||||
|
||||
|
||||
class HorillaSectionView(TemplateView):
|
||||
"""
|
||||
Horilla Template View
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
self.request = request
|
||||
update_initial_cache(request, cache, HorillaListView)
|
||||
|
||||
nav_url: str = ""
|
||||
view_url: str = ""
|
||||
|
||||
# view container id is used to wrap the component view with th id
|
||||
view_container_id: str = ""
|
||||
|
||||
script_static_paths: list = []
|
||||
style_static_paths: list = []
|
||||
|
||||
template_name = "generic/horilla_section.html"
|
||||
|
||||
def get_context_data(self, **kwargs) -> dict:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["nav_url"] = self.nav_url
|
||||
context["view_url"] = self.view_url
|
||||
context["view_container_id"] = self.view_container_id
|
||||
context["script_static_paths"] = self.script_static_paths
|
||||
context["style_static_paths"] = self.style_static_paths
|
||||
return context
|
||||
|
||||
|
||||
class HorillaDetailedView(DetailView):
|
||||
"""
|
||||
HorillDetailedView
|
||||
"""
|
||||
|
||||
title = "Detailed View"
|
||||
template_name = "generic/horilla_detailed_view.html"
|
||||
header: dict = {"title": "Horilla", "subtitle": "Horilla Detailed View"}
|
||||
body: list = []
|
||||
|
||||
action_method: list = []
|
||||
actions: list = []
|
||||
|
||||
ids_key: str = "instance_ids"
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
url = resolve(self.request.path)
|
||||
key = list(url.kwargs.keys())[0]
|
||||
|
||||
url_name = url.url_name
|
||||
|
||||
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})
|
||||
if instance_ids:
|
||||
context["instance_ids"] = str(instance_ids)
|
||||
context["ids_key"] = self.ids_key
|
||||
|
||||
context["next_url"] = next_url
|
||||
context["previous_url"] = previous_url
|
||||
|
||||
context["title"] = self.title
|
||||
context["header"] = self.header
|
||||
context["body"] = self.body
|
||||
context["actions"] = self.actions
|
||||
context["action_method"] = self.action_method
|
||||
|
||||
cache[self.request.session.session_key][HorillaDetailedView] = context
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class HorillaTabView(TemplateView):
|
||||
"""
|
||||
HorillaTabView
|
||||
"""
|
||||
|
||||
template_name = "generic/horilla_tabs.html"
|
||||
|
||||
tabs: list = []
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
self.request = request
|
||||
update_initial_cache(request, cache, HorillaTabView)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.request.user:
|
||||
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
|
||||
context["tabs"] = self.tabs
|
||||
|
||||
cache[self.request.session.session_key][HorillaTabView] = context
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class HorillaCardView(ListView):
|
||||
"""
|
||||
HorillaCardView
|
||||
"""
|
||||
|
||||
filter_class: FilterSet = None
|
||||
|
||||
view_id: str = get_short_uuid(4, prefix="hcv")
|
||||
|
||||
template_name = "generic/horilla_card.html"
|
||||
context_object_name = "queryset"
|
||||
|
||||
search_url: str = ""
|
||||
|
||||
details: dict = {}
|
||||
|
||||
actions: list = []
|
||||
|
||||
card_attrs: str = """"""
|
||||
|
||||
show_filter_tags: bool = True
|
||||
filter_keys_to_remove: list = []
|
||||
|
||||
records_per_page: int = 50
|
||||
card_status_class: str = """"""
|
||||
card_status_indications: list = []
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
self.request = request
|
||||
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"
|
||||
]
|
||||
|
||||
self._saved_filters = query_dict
|
||||
queryset = self.filter_class(query_dict, queryset).qs
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
queryset = self.get_queryset()
|
||||
|
||||
context["view_id"] = self.view_id
|
||||
context["search_url"] = self.search_url
|
||||
context["card_attrs"] = self.card_attrs
|
||||
context["actions"] = self.actions
|
||||
context["details"] = self.details
|
||||
context["show_filter_tags"] = self.show_filter_tags
|
||||
context["card_status_class"] = self.card_status_class
|
||||
context["card_status_indications"] = self.card_status_indications
|
||||
|
||||
context["queryset"] = paginator_qry(
|
||||
queryset, self.request.GET.get("page"), self.records_per_page
|
||||
)
|
||||
|
||||
if self.show_filter_tags:
|
||||
data_dict = parse_qs(self._saved_filters.urlencode())
|
||||
data_dict = get_key_instances(self.model, data_dict)
|
||||
keys_to_remove = [
|
||||
key
|
||||
for key, value in data_dict.items()
|
||||
if value[0] in ["unknown", "on"] + self.filter_keys_to_remove
|
||||
]
|
||||
|
||||
for key in keys_to_remove:
|
||||
data_dict.pop(key)
|
||||
context["filter_dict"] = data_dict
|
||||
|
||||
cache[self.request.session.session_key][HorillaCardView] = context
|
||||
return context
|
||||
|
||||
|
||||
class ReloadMessages(TemplateView):
|
||||
template_name = "generic/messages.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
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
|
||||
"""
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
|
||||
dynamic_field = request.GET["dynamic_field"]
|
||||
response = None
|
||||
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,
|
||||
}
|
||||
return response
|
||||
|
||||
|
||||
from django.views.generic import UpdateView
|
||||
|
||||
|
||||
class HorillaFormView(FormView):
|
||||
"""
|
||||
HorillaFormView
|
||||
"""
|
||||
|
||||
class HttpResponse:
|
||||
def __new__(
|
||||
self, content: str = "", targets_to_reload: list = [], script: str = ""
|
||||
) -> HttpResponse:
|
||||
targets_to_reload = list(set(targets_to_reload))
|
||||
targets_to_reload.append("#reloadMessagesButton")
|
||||
script_id = get_short_uuid(4)
|
||||
script = (
|
||||
f"<script id='scriptTarget{script_id}'>"
|
||||
+ "{}".format(
|
||||
"".join([f"$(`{target}`).click();" for target in targets_to_reload])
|
||||
)
|
||||
+ f"$('#scriptTarget{script_id}').closest('.oh-modal--show').first().removeClass('oh-modal--show');"
|
||||
+ "$('.reload-record').click();"
|
||||
+ "$('.reload-field').click();"
|
||||
+ script
|
||||
+ "</script>"
|
||||
)
|
||||
|
||||
reload_response = HttpResponse(script).content
|
||||
|
||||
user_response = HttpResponse(content).content
|
||||
response = HttpResponse(reload_response + user_response)
|
||||
self = response
|
||||
return response
|
||||
|
||||
model: object = None
|
||||
view_id: str = get_short_uuid(4)
|
||||
form_class: forms.ModelForm = None
|
||||
template_name = "generic/horilla_form.html"
|
||||
form_disaply_attr: str = ""
|
||||
new_display_title: str = "Add New"
|
||||
close_button_attrs: str = """"""
|
||||
submit_button_attrs: str = """"""
|
||||
|
||||
# NOTE: Dynamic create view's forms save method will be overwritten
|
||||
is_dynamic_create_view: bool = False
|
||||
dynamic_create_fields: list = []
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
self.request = request
|
||||
update_initial_cache(request, cache, HorillaFormView)
|
||||
|
||||
if self.form_class:
|
||||
setattr(self.form_class, "structured", structured)
|
||||
|
||||
def get(
|
||||
self, request: HttpRequest, pk=None, *args: str, **kwargs: Any
|
||||
) -> HttpResponse:
|
||||
response = super().get(request, *args, **kwargs)
|
||||
return response
|
||||
|
||||
def post(
|
||||
self, request: HttpRequest, pk=None, *args: str, **kwargs: Any
|
||||
) -> HttpResponse:
|
||||
self.get_form()
|
||||
response = super().post(request, *args, **kwargs)
|
||||
return response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context["dynamic_create_fields"] = self.dynamic_create_fields
|
||||
context["form_class_path"] = self.form_class_path
|
||||
context["view_id"] = self.view_id
|
||||
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":
|
||||
data = self.request.POST
|
||||
files = self.request.FILES
|
||||
form = self.form_class(data, files, instance=instance)
|
||||
|
||||
if self.is_dynamic_create_view:
|
||||
setattr(type(form), "save", save)
|
||||
|
||||
self.form_class_path = form.__class__.__module__ + "." + form.__class__.__name__
|
||||
if self.request.method == "GET":
|
||||
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,
|
||||
}
|
||||
|
||||
from horilla.urls import urlpatterns, path
|
||||
|
||||
urlpatterns.append(
|
||||
path(
|
||||
f"dynamic-path-{field}-{self.request.session.session_key}",
|
||||
view.as_view(),
|
||||
name=f"dynamic-path-{field}-{self.request.session.session_key}",
|
||||
)
|
||||
)
|
||||
queryset = form.fields[field].queryset
|
||||
choices = [(instance.id, instance) for instance in queryset]
|
||||
choices.insert(0, ("", "Select option"))
|
||||
choices.append(("dynamic_create", "Dynamic create"))
|
||||
form.fields[field] = forms.ChoiceField(
|
||||
choices=choices,
|
||||
label=form.fields[field].label,
|
||||
required=form.fields[field].required,
|
||||
widget=forms.Select(attrs=form.fields[field].widget.attrs),
|
||||
)
|
||||
if pk:
|
||||
form.instance = instance
|
||||
title = str(instance)
|
||||
if self.form_disaply_attr:
|
||||
title = getattribute(instance, self.form_disaply_attr)
|
||||
if instance:
|
||||
self.form_class.verbose_name = title
|
||||
else:
|
||||
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
|
||||
self.form = form
|
||||
return form
|
||||
|
||||
|
||||
class HorillaNavView(TemplateView):
|
||||
"""
|
||||
HorillaNavView
|
||||
|
||||
filter form submit button id: applyFilter
|
||||
"""
|
||||
|
||||
template_name = "generic/horilla_nav.html"
|
||||
|
||||
nav_title: str = ""
|
||||
search_url: str = ""
|
||||
search_swap_target: str = ""
|
||||
search_input_attrs: str = ""
|
||||
search_in: list = []
|
||||
actions: list = []
|
||||
group_by_fields: list = []
|
||||
filter_form_context_name: str = ""
|
||||
filter_instance: FilterSet = None
|
||||
filter_instance_context_name: str = ""
|
||||
filter_body_template: str = ""
|
||||
view_types: list = []
|
||||
create_attrs: str = """"""
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
self.request = request
|
||||
update_initial_cache(request, cache, HorillaNavView)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["nav_title"] = self.nav_title
|
||||
context["search_url"] = self.search_url
|
||||
context["search_swap_target"] = self.search_swap_target
|
||||
context["search_input_attrs"] = self.search_input_attrs
|
||||
context["group_by_fields"] = self.group_by_fields
|
||||
context["actions"] = self.actions
|
||||
context["filter_body_template"] = self.filter_body_template
|
||||
context["view_types"] = self.view_types
|
||||
context["create_attrs"] = self.create_attrs
|
||||
context["search_in"] = self.search_in
|
||||
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
|
||||
return context
|
||||
0
horilla_views/migrations/__init__.py
Normal file
0
horilla_views/migrations/__init__.py
Normal file
95
horilla_views/models.py
Normal file
95
horilla_views/models.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import json
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from horilla.models import HorillaModel
|
||||
from base.thread_local_middleware import _thread_locals
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class ToggleColumn(HorillaModel):
|
||||
"""
|
||||
ToggleColumn
|
||||
"""
|
||||
|
||||
user_id = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="user_excluded_column",
|
||||
editable=False,
|
||||
)
|
||||
path = models.CharField(max_length=256)
|
||||
excluded_columns = models.JSONField(default=list)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
request = getattr(_thread_locals, "request", {})
|
||||
user = request.user
|
||||
self.user_id = user
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.user_id.employee_get)
|
||||
|
||||
|
||||
class ActiveTab(HorillaModel):
|
||||
"""
|
||||
ActiveTab
|
||||
"""
|
||||
|
||||
path = models.CharField(max_length=256)
|
||||
tab_target = models.CharField(max_length=256)
|
||||
|
||||
|
||||
class ActiveGroup(HorillaModel):
|
||||
"""
|
||||
ActiveGroup
|
||||
"""
|
||||
path = models.CharField(max_length=256)
|
||||
group_target = models.CharField(max_length=256)
|
||||
group_by_field = models.CharField(max_length=256)
|
||||
|
||||
|
||||
class ParentModel(models.Model):
|
||||
""" """
|
||||
|
||||
title = models.CharField(max_length=50)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.title
|
||||
|
||||
|
||||
class DemoCompany(models.Model):
|
||||
title = models.CharField(max_length=20)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.title
|
||||
|
||||
|
||||
class DemoDepartment(models.Model):
|
||||
"""
|
||||
DemoDepartment
|
||||
"""
|
||||
|
||||
title = models.CharField(max_length=20)
|
||||
company_id = models.ForeignKey(
|
||||
DemoCompany,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Company",
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.title
|
||||
|
||||
|
||||
class childModel(models.Model):
|
||||
""" """
|
||||
|
||||
title_id = models.ForeignKey(
|
||||
ParentModel, on_delete=models.CASCADE, verbose_name="Title"
|
||||
)
|
||||
department_id = models.ForeignKey(
|
||||
DemoDepartment, on_delete=models.CASCADE, null=True, verbose_name="Department"
|
||||
)
|
||||
description = models.TextField()
|
||||
0
horilla_views/templates/cbv/filter_form.html
Normal file
0
horilla_views/templates/cbv/filter_form.html
Normal file
32
horilla_views/templates/generic/as_list.html
Normal file
32
horilla_views/templates/generic/as_list.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% load i18n %}
|
||||
<form hx-get="{% url "toggle-columns" %}" hx-swap="none">
|
||||
<input type="hidden" name="path" value="{{request.path_info}}">
|
||||
<ul class="oh-dropdown__items">
|
||||
<div class="oh-dropdown_btn-header">
|
||||
<button class="oh-btn oh-btn--success-outline">
|
||||
{% trans "Select All Records" %}
|
||||
</button>
|
||||
<button class="oh-btn oh-btn--primary-outline">
|
||||
{% trans "Unselect All Records" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% for field in form.visible_fields %}
|
||||
<li class="oh-dropdown__item oh-sticy-dropdown-item">
|
||||
<span>{{field.label}}</span>
|
||||
<span class="oh-table__checkbox">
|
||||
<input type="hidden" name="{{field.name}}" onchange="$(this).closest('form').find('[type=submit]').click();" {% if not field.initial %} value ="false" {% endif %}>
|
||||
<input type="checkbox" id="toggle_{{field.name}}" {% if field.initial %} checked {% endif %} onclick="
|
||||
value='';
|
||||
if (!$(this).is(':checked')) {
|
||||
value = 'off'
|
||||
}
|
||||
$(this).siblings('input[type=hidden]').val(value).change();
|
||||
"
|
||||
>
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<input type="submit" hidden>
|
||||
</form>
|
||||
54
horilla_views/templates/generic/components.html
Normal file
54
horilla_views/templates/generic/components.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="genericModal"
|
||||
role="dialog"
|
||||
aria-labelledby="genericModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog" id="genericModalBody"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).on("htmx:afterOnLoad", function (event) {
|
||||
$("[data-toggle='oh-modal-toggle']").click(function (e) {
|
||||
e.preventDefault();
|
||||
let clickedEl = $(e.target).closest('[data-toggle = "oh-modal-toggle"]');
|
||||
if (clickedEl != null) {
|
||||
const targetEl = clickedEl.data("target");
|
||||
$(targetEl).addClass("oh-modal--show");
|
||||
}
|
||||
});
|
||||
});
|
||||
function switchTab(e) {
|
||||
let parentContainerEl = e.target.closest(".oh-tabs");
|
||||
let tabElement = e.target.closest(".oh-tabs__tab");
|
||||
|
||||
let targetSelector = e.target.dataset.target;
|
||||
let targetEl = parentContainerEl
|
||||
? parentContainerEl.querySelector(targetSelector)
|
||||
: null;
|
||||
|
||||
// Highlight active tabs
|
||||
if (tabElement && !tabElement.classList.contains("oh-tabs__tab--active")) {
|
||||
parentContainerEl
|
||||
.querySelectorAll(".oh-tabs__tab--active")
|
||||
.forEach(function (item) {
|
||||
item.classList.remove("oh-tabs__tab--active");
|
||||
});
|
||||
|
||||
if (!tabElement.classList.contains("oh-tabs__new-tab")) {
|
||||
tabElement.classList.add("oh-tabs__tab--active");
|
||||
}
|
||||
}
|
||||
|
||||
// Switch tabs
|
||||
if (targetEl && !targetEl.classList.contains("oh-tabs__content--active")) {
|
||||
parentContainerEl
|
||||
.querySelectorAll(".oh-tabs__content--active")
|
||||
.forEach(function (item) {
|
||||
item.classList.remove("oh-tabs__content--active");
|
||||
});
|
||||
targetEl.classList.add("oh-tabs__content--active");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
9
horilla_views/templates/generic/custom_list.html
Normal file
9
horilla_views/templates/generic/custom_list.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="oh-wrapper" id="{{view_id|safe}}">
|
||||
<h2>
|
||||
Header
|
||||
</h2>
|
||||
{% include "generic/horilla_list.html" %}
|
||||
<h2>
|
||||
Footer
|
||||
</h2>
|
||||
</div>
|
||||
80
horilla_views/templates/generic/filter_tags.html
Normal file
80
horilla_views/templates/generic/filter_tags.html
Normal file
@@ -0,0 +1,80 @@
|
||||
{% load i18n %} {% load basefilters %}
|
||||
|
||||
<!-- filter items showing here -->
|
||||
<div style="display: none">{{filter_dict}}</div>
|
||||
<script>
|
||||
function fieldLabel(value, field) {
|
||||
fiedlElem = $(`#applyFilter`).closest(`form`).find(`[name=${field}]`);
|
||||
if (fiedlElem.is("select")) {
|
||||
// my conditions
|
||||
if (fiedlElem.is("select[multiple]")) {
|
||||
values = fiedlElem.val();
|
||||
values.push(field);
|
||||
$.each(values, function (index, value) {
|
||||
fiedlElem.append(
|
||||
$("<option>", {
|
||||
value: value,
|
||||
text: fiedlElem.find(`[value=${field}]`).html(),
|
||||
})
|
||||
);
|
||||
});
|
||||
} else {
|
||||
fiedlElem.val(value)
|
||||
if (!fiedlElem == "field") {
|
||||
fiedlElem.change();
|
||||
}else{
|
||||
fiedlElem.select2('destroy');
|
||||
fiedlElem.select2()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!fiedlElem.is(":focus")) {
|
||||
fiedlElem.val(value);
|
||||
}
|
||||
}
|
||||
if (field == "field") {
|
||||
return $(`option[value="${value}"]`).html();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
$(document).ready(function () {
|
||||
var nav = $("#filterTagContainerSectionNav");
|
||||
tags = $(`
|
||||
{% if filter_dict %}
|
||||
<span class="oh-titlebar__tag oh-titlebar__tag--custom"
|
||||
>{% trans 'Filters' %}:</span
|
||||
>
|
||||
{% endif %}
|
||||
{%for field,values in filter_dict.items %} {% if values %}
|
||||
{% with translation_field=field|filter_field %}
|
||||
<span class="oh-titlebar__tag filter-field" >
|
||||
{% trans translation_field %} :
|
||||
{% 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>
|
||||
{% endwith %} {% endif %} {% endfor %}
|
||||
{% if filter_dict %}
|
||||
<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
|
||||
>
|
||||
{% endif %}
|
||||
`);
|
||||
|
||||
nav.html(tags);
|
||||
$("oh-tabs__tab oh-tabs__tab--active:first").click();
|
||||
});
|
||||
</script>
|
||||
<div
|
||||
id="filterTagContainerSectionNav"
|
||||
class="oh-titlebar-container__filters mb-2 mt-0 oh-wrapper"
|
||||
></div>
|
||||
61
horilla_views/templates/generic/form.html
Normal file
61
horilla_views/templates/generic/form.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{% load widget_tweaks %} {% load i18n %}
|
||||
<style>
|
||||
.condition-highlight {
|
||||
background-color: #ffa5000f;
|
||||
}
|
||||
</style>
|
||||
{% if form.verbose_name %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="createTitle">
|
||||
{{form.verbose_name}}
|
||||
</h2>
|
||||
<button type="button" class="oh-modal__close--custom" onclick="$(this).closest('.oh-modal--show').removeClass('oh-modal--show')" aria-label="Close" {{form.close_button_attrs|safe}}>
|
||||
<ion-icon name="close-outline" role="img" class="md hydrated" aria-label="close outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="oh-modal__dialog-body">
|
||||
<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>
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="oh-label__info" for="id_{{ field.name }}">
|
||||
<label class="oh-label" for="id_{{ field.name }}"
|
||||
>{% trans field.label %}</label
|
||||
>
|
||||
{% if field.help_text != '' %}
|
||||
<span
|
||||
class="oh-info mr-2"
|
||||
title="{{ field.help_text|safe }}"
|
||||
></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if field.field.widget.input_type == 'checkbox' %}
|
||||
<div class="oh-switch" style="width: 30px">
|
||||
{{ field|add_class:'oh-switch__checkbox' }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="dynamic_field_{{field.name}}">
|
||||
{{ field|add_class:'form-control' }} {% endif %} {{ field.errors }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% for field in form.hidden_fields %} {{ field }} {% endfor %}
|
||||
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<button
|
||||
type="submit"
|
||||
class="oh-btn oh-btn--secondary mt-2 mr-0 pl-4 pr-5 oh-btn--w-100-resp"
|
||||
{{form.submit_button_attrs|safe}}
|
||||
>
|
||||
{% trans 'Save' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
415
horilla_views/templates/generic/group_by.html
Normal file
415
horilla_views/templates/generic/group_by.html
Normal file
@@ -0,0 +1,415 @@
|
||||
{% load static i18n generic_template_filters %}
|
||||
<div id="{{view_id|safe}}">
|
||||
{% 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}}'));
|
||||
"
|
||||
style="cursor: pointer;">
|
||||
{% trans "Select All" %}{{select_all_path}}
|
||||
</div>
|
||||
<div
|
||||
id="unselect_{{view_id}}"
|
||||
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}}'));
|
||||
$('#{{view_id}} .list-table-row').prop('checked',false);
|
||||
$('#{{view_id}} .highlight-selected').removeClass('highlight-selected');
|
||||
$('#{{view_id}} .bulk-list-table-row').prop('checked',false);
|
||||
"
|
||||
>
|
||||
{% trans "Unselect All" %}
|
||||
</div>
|
||||
<div class="oh-checkpoint-badge text-danger d-none"
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
<span id="count_{{view_id}}">
|
||||
0
|
||||
</span> {% trans "Selected" %}
|
||||
</div>
|
||||
<div
|
||||
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
|
||||
"
|
||||
>
|
||||
{% trans "Export" %}
|
||||
</div>
|
||||
</div>
|
||||
{% if row_status_indications %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
{% for indication in row_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>
|
||||
{% endif %}
|
||||
</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 %} {% include "generic/filter_tags.html" %} {% endif %}
|
||||
<div class="oh-card">
|
||||
{% for group in groups %}
|
||||
<div class="oh-accordion-meta">
|
||||
<div class="oh-accordion-meta__item">
|
||||
<div class="oh-accordion-meta__header"
|
||||
data-field="{{saved_filters.field}}"
|
||||
data-path="{{request.path}}"
|
||||
data-group="{{forloop.counter}}"
|
||||
data-open=false
|
||||
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">
|
||||
<span class="oh-accordion-meta__title pt-3 pb-3">
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span
|
||||
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1"
|
||||
title="5 Candidates"
|
||||
>
|
||||
{{group.list.paginator.count}}
|
||||
</span>
|
||||
{{group.grouper|capfirst}}
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-accordion-meta__body d-none">
|
||||
<div class="oh-sticky-table oh-sticky-table--no-overflow mb-5" style="
|
||||
width: 100%;
|
||||
width: -moz-available; /* WebKit-based browsers will ignore this. */
|
||||
width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
|
||||
width: fill-available;
|
||||
">
|
||||
<div class="oh-sticky-table__table">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
{% if bulk_select_option %}
|
||||
<div
|
||||
class="oh-sticky-table__th"
|
||||
style="width: 10px; z-index: 12 !important"
|
||||
>
|
||||
<div class="centered-div" align="center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="oh-input oh-input__checkbox bulk-list-table-row"
|
||||
onchange="
|
||||
$(this).closest('.oh-sticky-table').find('.list-table-row').prop('checked',$(this).is(':checked')).change();
|
||||
$(document).ready(function () {
|
||||
reloadSelectedCount($('#count_{{view_id|safe}}'));
|
||||
});
|
||||
"
|
||||
title="Select All"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for cell in columns %}
|
||||
<div class="oh-sticky-table__th"
|
||||
|
||||
>{{cell.0}}</div>
|
||||
{% endfor %}{% if options or option_method%}
|
||||
<div class="oh-sticky-table__th" >
|
||||
<div style="width: 200px;">
|
||||
{% trans "Options" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %} {% if actions or action_method %}
|
||||
<div class="oh-sticky-table__th oh-sticky-table__right" style="z-index:12 !important;">
|
||||
<div style="width: 200px;">
|
||||
{% trans "Actions" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
{% for instance in group.list %}
|
||||
<div
|
||||
class="oh-sticky-table__tr"
|
||||
draggable="true"
|
||||
data-instance-id="{{instance.id}}"
|
||||
{{row_attrs|format:instance|safe}}
|
||||
>
|
||||
{% if bulk_select_option %}
|
||||
<div
|
||||
class="oh-sticky-table__sd {{row_status_class|format:instance|safe}}"
|
||||
onclick="event.stopPropagation()"
|
||||
style="width: 10px; z-index: 11 !important"
|
||||
>
|
||||
<div class="centered-div" align="center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="oh-input oh-input__checkbox list-table-row"
|
||||
onchange="
|
||||
highlightRow($(this))
|
||||
$(document).ready(function () {
|
||||
if (!element.is(':checked')) {
|
||||
removeId(element)
|
||||
}
|
||||
reloadSelectedCount($('#count_{{view_id|safe}}'));
|
||||
});
|
||||
"
|
||||
value = "{{instance.pk}}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for cell in columns %}
|
||||
{% with attribute=cell.1 index=forloop.counter %} {% if not cell.2 %}
|
||||
<div
|
||||
class="{% if index == 1 %} oh-sticky-table__sd {% else %} oh-sticky-table__td{% endif %}"
|
||||
>
|
||||
{{instance|getattribute:attribute|safe}}
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="{% if index == 1 %} oh-sticky-table__sd {% else %} oh-sticky-table__td{% endif %}"
|
||||
>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="{{instance|getattribute:cell.2}}"
|
||||
class="oh-profile__image"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark">
|
||||
{{instance|getattribute:attribute}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %} {% endwith %} {% endfor %} {% if options or option_method %}
|
||||
<div class="oh-sticky-table__td oh-permission-table--toggle">
|
||||
{% if not option_method %}
|
||||
<div class="oh-btn-group">
|
||||
{% for option in options %}
|
||||
<a
|
||||
href="#"
|
||||
title="{{option.option|safe}}"
|
||||
{{option.attrs|format:instance|safe}}
|
||||
>
|
||||
<ion-icon name="{{option.icon}}"></ion-icon>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %} {{instance|getattribute:option_method|safe}} {% endif %}
|
||||
</div>
|
||||
{% endif %} {% if actions or action_method %}
|
||||
<div class="oh-sticky-table__td oh-sticky-table__right">
|
||||
{% if not action_method %}
|
||||
<div class="oh-btn-group">
|
||||
{% for action in actions %}
|
||||
<a
|
||||
href="#"
|
||||
title="{{action.action|safe}}"
|
||||
{{action.attrs|format:instance|safe}}
|
||||
>
|
||||
<ion-icon name="{{action.icon}}"></ion-icon>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %} {{instance|getattribute:action_method|safe}} {% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page">
|
||||
{% trans "Page" %} {{ group.list.number }}
|
||||
{% trans "of" %} {{ group.list.paginator.num_pages }}.
|
||||
</span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1"
|
||||
>{% trans "Page" %}</span
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
name="{{group.dynamic_name}}"
|
||||
class="oh-pagination__input"
|
||||
value="{{group.list.number}}"
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}"
|
||||
hx-target="#{{view_id}}"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label"
|
||||
>{% trans "of" %}
|
||||
{{group.list.paginator.num_pages}}</span
|
||||
>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if group.list.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#{{view_id}}"
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}&{{group.dynamic_name}}=1"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "First" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#{{view_id}}"
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}&{{group.dynamic_name}}={{ group.list.previous_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Previous" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if group.list.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#{{view_id}}"
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}&{{group.dynamic_name}}={{ group.list.next_page_number }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Next" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-target="#{{view_id}}"
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}&{{group.dynamic_name}}={{ group.list.paginator.num_pages }}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Last" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if groups.paginator.count %}
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
data-toggle="modal"
|
||||
data-target="#addEmployeeModal"
|
||||
>{% trans "Page" %} {{groups.number}} {% trans "of" %} {{groups.paginator.num_pages}}</span
|
||||
>
|
||||
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
class="oh-pagination__input"
|
||||
value="{{groups.number}}"
|
||||
min="1"
|
||||
name="page"
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{groups.paginator.num_pages}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="oh-pagination__items">
|
||||
{% if groups.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{{search_url}}?{{request.GET.urlencode}}&page=1" hx-swap="outerHTML" hx-target="#{{view_id|safe}}" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{{search_url}}?{{request.GET.urlencode}}&page={{ groups.previous_page_number }}" hx-swap="outerHTML" hx-target="#{{view_id|safe}}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if groups.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{{search_url}}?{{request.GET.urlencode}}&page={{ groups.next_page_number }}" hx-swap="outerHTML" hx-target="#{{view_id|safe}}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{{search_url}}?{{request.GET.urlencode}}&page={{ groups.paginator.num_pages }}" hx-swap="outerHTML" hx-target="#{{view_id|safe}}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<script>
|
||||
var tabId = $("#{{view_id}}").closest(".oh-tabs__content").attr("id")
|
||||
let badge = $(`#badge-${tabId}`)
|
||||
let count = "{{queryset.paginator.count}}"
|
||||
let label = badge.attr("data-badge-label") || ""
|
||||
let title = count + " " + label
|
||||
badge.html(count)
|
||||
badge.attr("title",title)
|
||||
</script>
|
||||
{% if bulk_select_option %}
|
||||
<script>
|
||||
ids = JSON.parse(
|
||||
$("#selectedInstances").attr("data-ids") || "[]"
|
||||
);
|
||||
$.each(ids, function (indexInArray, valueOfElement) {
|
||||
$(`#{{view_id|safe}} .oh-sticky-table__tbody .list-table-row[value=${valueOfElement}]`).prop("checked",true).change()
|
||||
});
|
||||
$("#{{view_id|safe}} .oh-sticky-table__tbody .list-table-row").change(function (
|
||||
e
|
||||
) {
|
||||
id = $(this).val()
|
||||
ids = JSON.parse(
|
||||
$("#selectedInstances").attr("data-ids") || "[]"
|
||||
);
|
||||
ids = Array.from(new Set(ids));
|
||||
let index = ids.indexOf(id);
|
||||
if (!ids.includes(id)) {
|
||||
ids.push(id);
|
||||
} else {
|
||||
if (!$(this).is(":checked")) {
|
||||
ids.splice(index, 1);
|
||||
}
|
||||
}
|
||||
$("#selectedInstances").attr("data-ids", JSON.stringify(ids));
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
$(".oh-accordion-meta__header").click(function (e) {
|
||||
var open = $(this).attr("data-open");
|
||||
open = JSON.parse(open)
|
||||
$(this).attr("data-open", !open);
|
||||
var field = $(this).attr("data-field");
|
||||
var groupIndex = $(this).attr("data-group");
|
||||
var target = `[data-group="${groupIndex}"][data-field="${field}"][data-path="{{request.path}}"][data-open="${open}"]`;
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "get",
|
||||
url: "{% url 'cbv-active-group' %}",
|
||||
data: {
|
||||
"path":"{{request.path}}",
|
||||
"target":target,
|
||||
"field":field,
|
||||
},
|
||||
success: function (response) {
|
||||
}
|
||||
});
|
||||
});
|
||||
{% if active_target %}
|
||||
$("#{{view_id|safe}}").find(`{{active_target|safe}}`).click();
|
||||
{% endif %}
|
||||
</script>
|
||||
</div>
|
||||
3
horilla_views/templates/generic/hello.html
Normal file
3
horilla_views/templates/generic/hello.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<button>
|
||||
Hello
|
||||
</button>
|
||||
156
horilla_views/templates/generic/horilla_card.html
Normal file
156
horilla_views/templates/generic/horilla_card.html
Normal file
@@ -0,0 +1,156 @@
|
||||
{% 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 %}
|
||||
</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 %}
|
||||
{% include "generic/filter_tags.html" %}
|
||||
{% endif %}
|
||||
<div class="oh-layout--grid-3">
|
||||
{% for instance in queryset %}
|
||||
<div class="oh-kanban-card {{card_status_class|format:instance|safe}}" {{card_attrs|format:instance|safe}}>
|
||||
<div class="oh-kanban-card__avatar">
|
||||
<div class="oh-kanban-card__profile-container">
|
||||
<img
|
||||
src="{{instance|getattribute:details.image_src}}"
|
||||
class="oh-kanban-card__profile-image"
|
||||
alt="Username"
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
{% if actions %}
|
||||
<div class="oh-kanban-card__dots" onclick="event.stopPropagation()">
|
||||
<div class="oh-dropdown" x-data="{show: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--transparent text-muted p-3"
|
||||
@click="show = !show"
|
||||
>
|
||||
<ion-icon name="ellipsis-vertical-sharp"></ion-icon>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--dark-border oh-dropdown__menu--right"
|
||||
x-show="show"
|
||||
@click.outside="show = false"
|
||||
>
|
||||
<ul class="oh-dropdown__items">
|
||||
{% for action in actions %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a {{action.attrs|format:instance|safe}}>{{action.action}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if queryset.paginator.count %}
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
data-toggle="modal"
|
||||
data-target="#addEmployeeModal"
|
||||
>{% trans "Page" %} {{queryset.number}} {% trans "of" %}
|
||||
{{queryset.paginator.num_pages}}</span
|
||||
>
|
||||
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
class="oh-pagination__input"
|
||||
value="{{queryset.number}}"
|
||||
min="1"
|
||||
name="page"
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
/>
|
||||
<span class="oh-pagination__label"
|
||||
>{% trans "of" %} {{queryset.paginator.num_pages}}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<ul class="oh-pagination__items">
|
||||
{% if queryset.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}&page=1&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "First" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}&page={{ queryset.previous_page_number }}&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Previous" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if queryset.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}&page={{ queryset.next_page_number }}&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Next" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-get="{{search_url}}?{{request.GET.urlencode}}&page={{ queryset.paginator.num_pages }}&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Last" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<script>
|
||||
var tabId = $("#{{view_id}}").closest(".oh-tabs__content").attr("id");
|
||||
let badge = $(`#badge-${tabId}`);
|
||||
let count = "{{queryset.paginator.count}}";
|
||||
let label = badge.attr("data-badge-label") || "";
|
||||
let title = count + " " + label;
|
||||
badge.html(count);
|
||||
badge.attr("title", title);
|
||||
</script>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="oh-wrapper" align="center" style="margin-top: 7vh; margin-bottom:7vh;">
|
||||
<div align="center">
|
||||
<img src="{% static "images/ui/search.svg" %}" class="oh-404__image" alt="Page not found. 404.">
|
||||
<h1 class="oh-404__title">{% trans "No Records found" %}</h1>
|
||||
<p class="oh-404__subtitle">
|
||||
{% trans "No records found." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
97
horilla_views/templates/generic/horilla_detailed_view.html
Normal file
97
horilla_views/templates/generic/horilla_detailed_view.html
Normal file
@@ -0,0 +1,97 @@
|
||||
{% load generic_template_filters %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="genericModalLabel">
|
||||
{{title}}
|
||||
</span>
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal__dialog-body oh-modal__dialog-relative"
|
||||
style="padding-bottom: 0px"
|
||||
>
|
||||
{% if instance_ids %}
|
||||
<div class="oh-modal__dialog oh-modal__dialog--navigation m-0 p-0">
|
||||
<button
|
||||
hx-get="{{previous_url}}?{{ids_key}}={{instance_id}}&{{request.GET.urlencode}}"
|
||||
hx-target="#genericModalBody"
|
||||
class="oh-modal__diaglog-nav oh-modal__nav-prev"
|
||||
>
|
||||
<ion-icon name="chevron-back-outline"></ion-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
hx-get="{{next_url}}?{{ids_key}}={{instance_id}}&{{request.GET.urlencode}}"
|
||||
hx-target="#genericModalBody"
|
||||
class="oh-modal__diaglog-nav oh-modal__nav-next"
|
||||
>
|
||||
<ion-icon name="chevron-forward-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="keyResultsContainer">
|
||||
<div class="my-3" id="keyResultCard">
|
||||
<div class="oh-card oh-card--no-shadow oh-card__body">
|
||||
<a
|
||||
class="oh-timeoff-modal__profile-content"
|
||||
style="text-decoration: none"
|
||||
{{header.attrs|format:object|safe}}
|
||||
>
|
||||
<div class="oh-profile mb-3">
|
||||
<div class="oh-profile__avatar">
|
||||
<img
|
||||
src="{{object|getattribute:header.avatar}}"
|
||||
class="oh-profile__image me-2"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__profile-info">
|
||||
<span class="oh-timeoff-modal__user fw-bold">
|
||||
{{object|getattribute:header.title}}
|
||||
</span>
|
||||
<span
|
||||
class="oh-timeoff-modal__user m-0"
|
||||
style="font-size: 18px; color: #4d4a4a"
|
||||
>
|
||||
{{object|getattribute:header.subtitle}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div
|
||||
class="oh-modal__dialog-header {% if header %} oh-card__footer--border-top{% endif %}"
|
||||
style="padding-top: 5px; padding-rigth: 0px; padding-left: 0px"
|
||||
>
|
||||
<div class="row">
|
||||
{% for col in body %}
|
||||
<div class="col-6 mt-3">
|
||||
<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
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
{% if actions or action_method %}
|
||||
{% if actions and not action_method %}
|
||||
<div class="oh-btn-group" style="width: 100%">
|
||||
{% for action in actions %}
|
||||
<button {{action.attrs|format:object|safe}}>
|
||||
<ion-icon name="{{action.icon}}"></ion-icon>
|
||||
{{action.action}}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %} {{object|getattribute:action_method|safe}} {% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
37
horilla_views/templates/generic/horilla_form.html
Normal file
37
horilla_views/templates/generic/horilla_form.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% for field_tuple in dynamic_create_fields %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="dynamicModal{{field_tuple.0}}"
|
||||
role="dialog"
|
||||
aria-labelledby="dynamicModal{{field_tuple.0}}"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="oh-modal__dialog"
|
||||
id="dynamicModal{{field_tuple.0}}Body"
|
||||
></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<form id="{{view_id}}" hx-post="{{request.path}}?{{request.GET.urlencode}}" 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-target="#dynamicModal{{field_tuple.0}}Body"
|
||||
onclick="$('#dynamicModal{{field_tuple.0}}').addClass('oh-modal--show');"
|
||||
>
|
||||
{{field_tuple.0}}</button>
|
||||
<button hidden class="reload-field" hx-get="{% url "reload-field" %}?form_class_path={{form_class_path}}&dynamic_field={{field_tuple.0}}" hx-target="#dynamic_field_{{field_tuple.0}}" data-target="{{field_tuple.0}}">
|
||||
Reload Field
|
||||
</button>
|
||||
<script>
|
||||
$("#{{view_id}} [name={{field_tuple.0}}]").change(function (e) {
|
||||
if (this.value=="dynamic_create") {
|
||||
$("#modalButton{{field_tuple.0}}").click()
|
||||
$(this).val("").change();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endfor %}
|
||||
|
||||
379
horilla_views/templates/generic/horilla_list.html
Normal file
379
horilla_views/templates/generic/horilla_list.html
Normal file
@@ -0,0 +1,379 @@
|
||||
{% load static i18n generic_template_filters %}
|
||||
<div id="{{view_id|safe}}">
|
||||
<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 %}
|
||||
{% if queryset|length %}
|
||||
{% if bulk_select_option %}
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<div>
|
||||
<div class="oh-checkpoint-badge text-success"
|
||||
onclick="
|
||||
addToSelectedId({{select_all_ids|safe}});
|
||||
selectSelected('#{{view_id|safe}}');
|
||||
reloadSelectedCount($('#count_{{view_id|safe}}'));
|
||||
"
|
||||
style="cursor: pointer;">
|
||||
{% trans "Select All" %}{{select_all_path}}
|
||||
</div>
|
||||
<div
|
||||
id="unselect_{{view_id}}"
|
||||
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}}'));
|
||||
$('#{{view_id}} .list-table-row').prop('checked',false);
|
||||
$('#{{view_id}} .highlight-selected').removeClass('highlight-selected');
|
||||
$('#{{view_id}} .bulk-list-table-row').prop('checked',false);
|
||||
"
|
||||
>
|
||||
{% trans "Unselect All" %}
|
||||
</div>
|
||||
<div class="oh-checkpoint-badge text-danger d-none"
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
<span id="count_{{view_id}}">
|
||||
0
|
||||
</span> {% trans "Selected" %}
|
||||
</div>
|
||||
<div
|
||||
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
|
||||
"
|
||||
>
|
||||
{% trans "Export" %}
|
||||
</div>
|
||||
</div>
|
||||
{% if row_status_indications %}
|
||||
<div class="d-flex flex-row-reverse">
|
||||
{% for indication in row_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>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="oh-table_sticky--wrapper">
|
||||
<div class="oh-sticky-dropdown--header" style="z-index:13;">
|
||||
<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('.oh-table_sticky--wrapper').parent().find('.reload-record').click();
|
||||
"
|
||||
>
|
||||
{{toggle_form.as_list}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable" style="
|
||||
width: 100%;
|
||||
width: -moz-available; /* WebKit-based browsers will ignore this. */
|
||||
width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
|
||||
width: fill-available;
|
||||
">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
{% if bulk_select_option %}
|
||||
<div
|
||||
class="oh-sticky-table__th"
|
||||
style="width: 10px; z-index: 12 !important"
|
||||
>
|
||||
<div class="centered-div" align="center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="oh-input oh-input__checkbox bulk-list-table-row"
|
||||
onchange="
|
||||
$(this).closest('.oh-sticky-table').find('.list-table-row').prop('checked',$(this).is(':checked')).change();
|
||||
$(document).ready(function () {
|
||||
reloadSelectedCount($('#count_{{view_id|safe}}'));
|
||||
});
|
||||
"
|
||||
title="Select All"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %} {% for cell in columns %}
|
||||
<div
|
||||
|
||||
class="oh-sticky-table__th"
|
||||
style="z-index: 11;width:100px;">
|
||||
<div
|
||||
{% for sort_map in sortby_mapping %}
|
||||
{% if sort_map.0 == cell.0 %}
|
||||
hx-get="{{search_url}}?{{saved_filters.urlencode}}&{{sortby_key}}={{sort_map.1}}&filter_applied=on"
|
||||
hx-target="#{{view_id}}"
|
||||
class="
|
||||
{% if request.sort_order == "asc" and request.sort_key == sort_map.1 %}
|
||||
arrow-up
|
||||
{% elif request.sort_order == "desc" and request.sort_key == sort_map.1 %}
|
||||
arrow-down
|
||||
{% else %}
|
||||
arrow-up-down
|
||||
{% endif %}
|
||||
"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
style="width: 200px;"
|
||||
>
|
||||
{{cell.0}}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %} {% if options or option_method%}
|
||||
<div class="oh-sticky-table__th" >
|
||||
<div style="width: 200px;">
|
||||
{% trans "Options" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %} {% if actions or action_method %}
|
||||
<div class="oh-sticky-table__th oh-sticky-table__right" style="z-index:12 !important;">
|
||||
<div style="width: 200px;">
|
||||
{% trans "Actions" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
{% for instance in queryset %}
|
||||
<div
|
||||
class="oh-sticky-table__tr oh-permission-table__tr oh-permission-table--collapsed"
|
||||
draggable="true"
|
||||
data-instance-id="{{instance.id}}"
|
||||
{{row_attrs|format:instance|safe}}
|
||||
>
|
||||
{% if bulk_select_option %}
|
||||
<div
|
||||
class="oh-sticky-table__sd {{row_status_class|format:instance|safe}}"
|
||||
onclick="event.stopPropagation()"
|
||||
style="width: 10px; z-index: 11 !important"
|
||||
>
|
||||
<div class="centered-div" align="center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="oh-input oh-input__checkbox list-table-row"
|
||||
data-view-id="{{view_id|safe}}"
|
||||
onchange="
|
||||
element = $(this)
|
||||
highlightRow(element);
|
||||
$(document).ready(function () {
|
||||
if (!element.is(':checked')) {
|
||||
removeId(element)
|
||||
}
|
||||
reloadSelectedCount($('#count_{{view_id|safe}}'));
|
||||
});
|
||||
"
|
||||
value = "{{instance.pk}}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %} {% for cell in columns %}
|
||||
{% with attribute=cell.1 index=forloop.counter %} {% if not cell.2 %}
|
||||
<div
|
||||
class="{% if index == 1 %} oh-sticky-table__sd {% else %} oh-sticky-table__td{% endif %}"
|
||||
>
|
||||
{{instance|getattribute:attribute|safe}}
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="{% if index == 1 %} oh-sticky-table__sd {% else %} oh-sticky-table__td{% endif %}"
|
||||
>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="{{instance|getattribute:cell.2}}"
|
||||
class="oh-profile__image"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark">
|
||||
{{instance|getattribute:attribute}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %} {% endwith %} {% endfor %} {% if options or option_method %}
|
||||
<div class="oh-sticky-table__td oh-permission-table--toggle">
|
||||
{% if not option_method %}
|
||||
<div class="oh-btn-group">
|
||||
{% for option in options %}
|
||||
<a
|
||||
href="#"
|
||||
title="{{option.option|safe}}"
|
||||
{{option.attrs|format:instance|safe}}
|
||||
>
|
||||
<ion-icon name="{{option.icon}}"></ion-icon>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %} {{instance|getattribute:option_method|safe}} {% endif %}
|
||||
</div>
|
||||
{% endif %} {% if actions or action_method %}
|
||||
<div class="oh-sticky-table__td oh-sticky-table__right">
|
||||
{% if not action_method %}
|
||||
<div class="oh-btn-group">
|
||||
{% for action in actions %}
|
||||
<a
|
||||
href="#"
|
||||
title="{{action.action|safe}}"
|
||||
{{action.attrs|format:instance|safe}}
|
||||
>
|
||||
<ion-icon name="{{action.icon}}"></ion-icon>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %} {{instance|getattribute:action_method|safe}} {% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if queryset.paginator.count %}
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
data-toggle="modal"
|
||||
data-target="#addEmployeeModal"
|
||||
>{% trans "Page" %} {{queryset.number}} {% trans "of" %}
|
||||
{{queryset.paginator.num_pages}}</span
|
||||
>
|
||||
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
class="oh-pagination__input"
|
||||
value="{{queryset.number}}"
|
||||
min="1"
|
||||
name="page"
|
||||
hx-get="{{search_url}}?{{saved_filters.urlencode}}&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
/>
|
||||
<span class="oh-pagination__label"
|
||||
>{% trans "of" %} {{queryset.paginator.num_pages}}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<ul class="oh-pagination__items" data-search-url="{{search_url}}">
|
||||
{% if queryset.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-get="{{search_url}}?{{saved_filters.urlencode}}&page=1&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "First" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-get="{{search_url}}?{{saved_filters.urlencode}}&page={{ queryset.previous_page_number }}&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Previous" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if queryset.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-get="{{search_url}}?{{saved_filters.urlencode}}&page={{ queryset.next_page_number }}&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Next" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a
|
||||
hx-get="{{search_url}}?{{saved_filters.urlencode}}&page={{ queryset.paginator.num_pages }}&filter_applied=on"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#{{view_id|safe}}"
|
||||
class="oh-pagination__link"
|
||||
>{% trans "Last" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<script>
|
||||
reloadSelectedCount($('#count_{{view_id|safe}}'));
|
||||
var tabId = $("#{{view_id}}").closest(".oh-tabs__content").attr("id");
|
||||
let badge = $(`#badge-${tabId}`);
|
||||
let count = "{{queryset.paginator.count}}";
|
||||
let label = badge.attr("data-badge-label") || "";
|
||||
let title = count + " " + label;
|
||||
badge.html(count);
|
||||
badge.attr("title", title);
|
||||
</script>
|
||||
{% if bulk_select_option %}
|
||||
<script>
|
||||
selectSelected("#{{view_id|safe}}")
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<script>
|
||||
$("ul[data-search-url] a").click(function (e) {
|
||||
e.preventDefault();
|
||||
const url = $(this).attr("hx-get")
|
||||
const $urlObj = $('<a>', { href: url });
|
||||
const searchParams = new URLSearchParams($urlObj[0].search);
|
||||
|
||||
let lastPageParam = null;
|
||||
let lastPageValue = 1;
|
||||
|
||||
searchParams.forEach((value, param) => {
|
||||
if (param === "page") {
|
||||
lastPageParam = param;
|
||||
lastPageValue = value.split(",").pop();
|
||||
}
|
||||
});
|
||||
|
||||
form = $(`form[hx-get="{{search_url}}"]`)
|
||||
pageInput = form.find("#pageInput")
|
||||
pageInput.attr("name",lastPageParam)
|
||||
pageInput.attr("value",lastPageValue)
|
||||
|
||||
});
|
||||
</script>
|
||||
{% else %}
|
||||
<div class="oh-wrapper" align="center" style="margin-top: 7vh; margin-bottom:7vh;">
|
||||
<div align="center">
|
||||
<img src="{% static "images/ui/search.svg" %}" class="oh-404__image" alt="Page not found. 404.">
|
||||
<h1 class="oh-404__title">{% trans "No Records found" %}</h1>
|
||||
<p class="oh-404__subtitle">
|
||||
{% trans "No records found." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
223
horilla_views/templates/generic/horilla_nav.html
Normal file
223
horilla_views/templates/generic/horilla_nav.html
Normal file
@@ -0,0 +1,223 @@
|
||||
{% load i18n %}
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold mb-0">{{nav_title}}</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon"
|
||||
></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">
|
||||
<div class="oh-input-group oh-input__search-group" id="searchGroup">
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
name="search"
|
||||
aria-label="Search Input"
|
||||
placeholder="Search"
|
||||
autocomplete="false"
|
||||
autofocus ="true"
|
||||
onkeyup="
|
||||
$(this).closest('form').find('#applyFilter').click();
|
||||
{% if search_in %}
|
||||
$('#applyFilter')[0].click();if(this.value) {
|
||||
$('.search_text').html(this.value)
|
||||
$(this).parent().find('#dropdown').show()
|
||||
}else{
|
||||
$(this).parent().find('#dropdown').hide()
|
||||
}
|
||||
{% endif %}
|
||||
"
|
||||
{% if search_in %}
|
||||
onfocus="
|
||||
if (this.value) {
|
||||
$(this).parent().find('#dropdown').show()
|
||||
}"
|
||||
onfocusout="
|
||||
setTimeout(function() {
|
||||
$('#dropdown').hide()
|
||||
}, 300);
|
||||
"
|
||||
{% endif %}
|
||||
{{search_input_attrs|safe}}
|
||||
/>
|
||||
{% if search_in %}
|
||||
<input type="text" hidden name="search_field">
|
||||
<div class="custom-dropdown" id="dropdown">
|
||||
<ul class="search_content">
|
||||
{% for option in search_in %}
|
||||
<li>
|
||||
<a href="#" onclick="$('[name=search_field]').val('{{option.0}}'); $(this).closest('form').find('#applyFilter').click()">
|
||||
{% trans "Search" %} <b>{{option.1}}</b> {% trans "for:" %}
|
||||
<b class="search_text"></b>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
{% 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();
|
||||
">
|
||||
<a class="oh-btn oh-btn--view" {{type.attrs|safe}}
|
||||
><ion-icon name="{{type.icon}}"></ion-icon
|
||||
></a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if filter_body_template %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" onclick="event.preventDefault()" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
<div id="filterCount"></div>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% include filter_body_template %}
|
||||
<input type="radio" name="filter_applied" checked hidden>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button
|
||||
type="submit"
|
||||
id="applyFilter"
|
||||
onclick="filterFormSubmit('filterForm')"
|
||||
class="oh-btn oh-btn--secondary oh-btn--small w-100">
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<button
|
||||
hidden
|
||||
type="submit"
|
||||
id="applyFilter"
|
||||
class="oh-btn oh-btn--secondary oh-btn--small w-100">
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if group_by_fields %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open" onclick="event.preventDefault()">
|
||||
<ion-icon name="library-outline" class="mr-1 md hydrated" role="img" aria-label="library outline"></ion-icon>
|
||||
{% trans "Group By" %}
|
||||
<div id="filterCount"></div>
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4" x-show="open" @click.outside="open = false" style="display: none">
|
||||
<div class="oh-accordion">
|
||||
<label for="id_field">{% trans "Group By" %}</label>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label" for="id_field">{% trans "Field" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<select onchange="$(this).closest('form').find('#applyFilter').click()" class="oh-select mt-1 w-100" id="id_field" name="field" style="width:100%;">
|
||||
<option value="">{% trans "Select" %}</option>
|
||||
{% for field in group_by_fields %}
|
||||
<option value="{{field.0}}">{{field.1}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if actions %}
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
onclick="event.preventDefault()"
|
||||
class="oh-btn oh-btn--dropdown"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% for action in actions %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a class="oh-dropdown__link" {{action.attrs|safe}}>{{action.action}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<input type="hidden" id="pageInput">
|
||||
<input type="hidden" id="sortInput">
|
||||
</form>
|
||||
{% if create_attrs %}
|
||||
<a
|
||||
onclick="event.preventDefault();event.stopPropagation()"
|
||||
class="oh-btn oh-btn--secondary ml-2"
|
||||
{{create_attrs|safe}}
|
||||
>
|
||||
<ion-icon
|
||||
name="add-sharp"
|
||||
class="mr-1 md hydrated"
|
||||
role="img"
|
||||
aria-label="add sharp"
|
||||
></ion-icon
|
||||
>{% trans "Create" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<script>
|
||||
$(".oh-btn--view").click(function (e) {
|
||||
e.preventDefault();
|
||||
$(".oh-btn--view-active").removeClass("oh-btn--view-active");
|
||||
$(this).addClass("oh-btn--view-active");
|
||||
});
|
||||
if (!$(".oh-btn--view-active").length) {
|
||||
// $("a.oh-btn--view:first").trigger("click")
|
||||
}
|
||||
$(".oh-accordion-header").click(function (e) {
|
||||
e.preventDefault();
|
||||
$(this).parent().toggleClass("oh-accordion--show");
|
||||
});
|
||||
$(document).ready(function() {
|
||||
$("#filterForm").on("htmx:configRequest", function(event) {
|
||||
if (event.detail.verb == "get" && event.target.tagName == "FORM") {
|
||||
event.detail.path = $(this).attr("hx-get");
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</section>
|
||||
<div id="filterTagContainerSectionNav" class="oh-titlebar-container__filters mb-2 mt-0 oh-wrapper"></div>
|
||||
<script>
|
||||
filterFormSubmit("filterForm")
|
||||
$(document).ready(function () {
|
||||
$("#filterForm select").select2("destroy")
|
||||
$("#filterForm select").parent().find("span").remove()
|
||||
$("#filterForm select").select2()
|
||||
});
|
||||
</script>
|
||||
49
horilla_views/templates/generic/horilla_section.html
Normal file
49
horilla_views/templates/generic/horilla_section.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends "index.html" %}
|
||||
{% block content %}
|
||||
{% load static %}
|
||||
|
||||
{% for path in style_path %}
|
||||
<link rel="stylesheet" href="{{path}}"/>
|
||||
{% endfor %}
|
||||
{% for path in script_static_paths %}
|
||||
<script src="{{path}}"></script>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% include "generic/components.html" %}
|
||||
|
||||
<div
|
||||
class="oh-checkpoint-badge mb-2"
|
||||
id="selectedInstances"
|
||||
data-ids="[]"
|
||||
data-clicked=""
|
||||
style="display: none"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
hx-get="{{nav_url}}?{{request.GET.urlencode}}"
|
||||
hx-trigger="load"
|
||||
>
|
||||
<div
|
||||
class="mt-5 oh-wrapper animated-background"
|
||||
style="height:80px;"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-wrapper"
|
||||
hx-get="{{view_url}}?{{request.GET.urlencode}}"
|
||||
hx-trigger="load"
|
||||
id="{{view_container_id}}"
|
||||
>
|
||||
<div
|
||||
class="mt-4 animated-background"
|
||||
style="height:600px;"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
100
horilla_views/templates/generic/horilla_tabs.html
Normal file
100
horilla_views/templates/generic/horilla_tabs.html
Normal file
@@ -0,0 +1,100 @@
|
||||
{% comment %} {% extends "index.html" %}
|
||||
{% block content %}
|
||||
{% comment %} {% include "generic/components.html" %} {% endcomment %}
|
||||
{% comment %} {% include "attendance/attendance/attendance_nav.html" %} {% endcomment %}
|
||||
{% load i18n generic_template_filters %}
|
||||
|
||||
<div class="oh-tabs">
|
||||
<ul class="oh-tabs__tablist">
|
||||
{% for tab in tabs %}
|
||||
<li
|
||||
class="oh-tabs__tab d-flex {% if forloop.counter == 1 and not active_target %} oh-tabs__tab--active {% endif %}"
|
||||
data-target="#htv{{forloop.counter}}"
|
||||
hx-get="{{tab.url}}?{{request.GET.urlencode}}"
|
||||
hx-target="#htv{{forloop.counter}}"
|
||||
hx-trigger="load"
|
||||
onclick="switchTab(event)"
|
||||
>
|
||||
{{tab.title}}
|
||||
<div class="d-flex">
|
||||
<div class="oh-tabs__input-badge-container" onclick="event.stopPropagation()">
|
||||
<span
|
||||
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2"
|
||||
id="badge-htv{{forloop.counter}}"
|
||||
{% if tab.badge_label %}
|
||||
data-badge-label="{{tab.badge_label}}"
|
||||
title="0 {{tab.badge_label}}"
|
||||
{% else %}
|
||||
title="0 {% trans "Records" %}"
|
||||
{% endif %}
|
||||
onclick="event.stopPropagation()"
|
||||
>
|
||||
0
|
||||
</span>
|
||||
</div>
|
||||
{% if tab.actions %}
|
||||
<div onclick="event.stopPropagation()" class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-stop-prop oh-btn--transparent oh-accordion-meta__btn"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
title="Actions"
|
||||
>
|
||||
<ion-icon
|
||||
name="ellipsis-vertical"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="ellipsis vertical"
|
||||
></ion-icon>
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right"
|
||||
x-show="open"
|
||||
style="display: none"
|
||||
>
|
||||
<ul class="oh-dropdown__items">
|
||||
{% for action in tab.actions %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a {{action.attrs|safe}}>{{action.action}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="oh-tabs__contents">
|
||||
{% for tab in tabs %}
|
||||
<div
|
||||
class="oh-tabs__content {% if forloop.counter == 1 and not active_target %} oh-tabs__content--active {% endif %}"
|
||||
id="htv{{forloop.counter}}"
|
||||
>
|
||||
<div class="animated-background"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("li.oh-tabs__tab").click(function (e) {
|
||||
var target = `li[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 %}
|
||||
$("div.oh-tabs").find(`{{active_target|safe}}`).click();
|
||||
{% endif %}
|
||||
</script>
|
||||
38
horilla_views/templates/generic/index.html
Normal file
38
horilla_views/templates/generic/index.html
Normal file
@@ -0,0 +1,38 @@
|
||||
{% extends "index.html" %}
|
||||
{% block content %}
|
||||
<style>
|
||||
.male--custom{
|
||||
border-left: 3.4px solid rgb(32, 128, 218) !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
.female--custom{
|
||||
border-left: 3.4px solid pink !important;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
.male--dot{
|
||||
background-color:blue;
|
||||
}
|
||||
.female--dot{
|
||||
background-color:pink;
|
||||
}
|
||||
|
||||
.oh-tabs__tab--active{
|
||||
border-right: 1px solid hsl(213,22%,84%) !important;
|
||||
}
|
||||
</style>
|
||||
<div hx-get="{% url "horilla-navbar" %}" hx-trigger="load">
|
||||
</div>
|
||||
{% include "generic/components.html" %}
|
||||
<div
|
||||
class="oh-checkpoint-badge mb-2"
|
||||
id="selectedInstances"
|
||||
data-ids="[]"
|
||||
data-clicked=""
|
||||
style="display: none"
|
||||
>
|
||||
</div>
|
||||
{% comment %} path to the horilla tab view {% endcomment %}
|
||||
<div class="oh-wrapper" hx-get="{% url "htv" %}?{{request.GET.urlencode}}" hx-trigger="load" id="listContainer">
|
||||
<div class="animated-background"></div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
7
horilla_views/templates/generic/messages.html
Normal file
7
horilla_views/templates/generic/messages.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% if messages %}
|
||||
<div class="oh-alert-container">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
13
horilla_views/templates/generic/reload_select_field.html
Normal file
13
horilla_views/templates/generic/reload_select_field.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<div id="{{dynamic_id}}">
|
||||
{{field}}
|
||||
</dic>
|
||||
|
||||
<script>
|
||||
$("#{{dynamic_id}} [name={{field.name}}]").change(function (e) {
|
||||
if (this.value=="dynamic_create") {
|
||||
$("#modalButton{{field.name}}").click()
|
||||
$(this).val("").change();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
0
horilla_views/templatetags/__init__.py
Normal file
0
horilla_views/templatetags/__init__.py
Normal file
61
horilla_views/templatetags/generic_template_filters.py
Normal file
61
horilla_views/templatetags/generic_template_filters.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
attendancefilters.py
|
||||
|
||||
This module is used to write custom template filters.
|
||||
|
||||
"""
|
||||
|
||||
import re, types
|
||||
from django import template
|
||||
from django.template.defaultfilters import register
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
numeric_test = re.compile("^\d+$")
|
||||
|
||||
|
||||
@register.filter(name="getattribute")
|
||||
def getattribute(value, attr: str):
|
||||
"""
|
||||
Gets an attribute of an object dynamically from a string name
|
||||
"""
|
||||
result = ""
|
||||
attrs = attr.split("__")
|
||||
for attr in attrs:
|
||||
if hasattr(value, str(attr)):
|
||||
result = getattr(value, attr)
|
||||
if isinstance(result, types.MethodType):
|
||||
result = result()
|
||||
value = result
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@register.filter(name="format")
|
||||
def format(string: str, instance: object):
|
||||
"""
|
||||
format
|
||||
"""
|
||||
attr_placeholder_regex = r"{([^}]*)}"
|
||||
attr_placeholders = re.findall(attr_placeholder_regex, string)
|
||||
|
||||
if not attr_placeholders:
|
||||
return string
|
||||
flag = instance
|
||||
format_context = {}
|
||||
for attr_placeholder in attr_placeholders:
|
||||
attr_name: str = attr_placeholder
|
||||
attrs = attr_name.split("__")
|
||||
for attr in attrs:
|
||||
value = getattr(instance, attr, "")
|
||||
if isinstance(value, types.MethodType):
|
||||
value = value()
|
||||
instance = value
|
||||
format_context[attr_name] = value
|
||||
instance = flag
|
||||
formatted_string = string.format(**format_context)
|
||||
|
||||
return formatted_string
|
||||
0
horilla_views/templatetags/migrations/__init__.py
Normal file
0
horilla_views/templatetags/migrations/__init__.py
Normal file
3
horilla_views/tests.py
Normal file
3
horilla_views/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
16
horilla_views/urls.py
Normal file
16
horilla_views/urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
horilla_views/urls.py
|
||||
"""
|
||||
|
||||
from django.urls import path
|
||||
from horilla_views import views
|
||||
from horilla_views.generic.cbv.views import ReloadMessages
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("toggle-columns", views.ToggleColumn.as_view(), name="toggle-columns"),
|
||||
path("active-tab", views.ActiveTab.as_view(), name="active-tab"),
|
||||
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"),
|
||||
]
|
||||
133
horilla_views/views.py
Normal file
133
horilla_views/views.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import importlib
|
||||
from django import forms
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.views import View
|
||||
from horilla_views import models
|
||||
from horilla_views.cbv_methods import get_short_uuid
|
||||
from horilla_views.generic.cbv.views import dynamic_create_cache
|
||||
|
||||
# Create your views here.
|
||||
|
||||
|
||||
class ToggleColumn(View):
|
||||
"""
|
||||
ToggleColumn
|
||||
"""
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
method to toggle columns
|
||||
"""
|
||||
|
||||
query_dict = self.request.GET
|
||||
path = query_dict["path"]
|
||||
query_dict = dict(query_dict)
|
||||
del query_dict["path"]
|
||||
|
||||
hidden_fields = [key for key, value in query_dict.items() if value[0]]
|
||||
|
||||
existing_instance = models.ToggleColumn.objects.filter(
|
||||
user_id=self.request.user, path=path
|
||||
).first()
|
||||
|
||||
instance = models.ToggleColumn() if not existing_instance else existing_instance
|
||||
instance.path = path
|
||||
instance.excluded_columns = hidden_fields
|
||||
|
||||
instance.save()
|
||||
|
||||
return HttpResponse("success")
|
||||
|
||||
|
||||
class ReloadField(View):
|
||||
"""
|
||||
ReloadField
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Http method to reload dynamic create fields
|
||||
"""
|
||||
class_path = request.GET["form_class_path"]
|
||||
reload_field = request.GET["dynamic_field"]
|
||||
|
||||
module_name, class_name = class_path.rsplit(".", 1)
|
||||
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"]
|
||||
|
||||
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 = field.queryset
|
||||
choices = [(instance.id, instance) for instance in queryset]
|
||||
choices.insert(0, ("", "Select option"))
|
||||
choices.append(("dynamic_create", "Dynamic create"))
|
||||
|
||||
parent_form.fields[cache_field] = forms.ChoiceField(
|
||||
choices=choices,
|
||||
label=field.label,
|
||||
required=field.required,
|
||||
widget=forms.Select(attrs=field.widget.attrs),
|
||||
)
|
||||
parent_form.fields[cache_field].initial = dynamic_cache["value"]
|
||||
|
||||
field = parent_form[cache_field]
|
||||
dynamic_id: str = get_short_uuid(4)
|
||||
return render(
|
||||
request,
|
||||
"generic/reload_select_field.html",
|
||||
{"field": field, "dynamic_id": dynamic_id},
|
||||
)
|
||||
|
||||
|
||||
class ActiveTab(View):
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
CBV method to handle active tab
|
||||
"""
|
||||
path = self.request.GET.get("path")
|
||||
target = self.request.GET.get("target")
|
||||
if path and target and self.request.user:
|
||||
existing_instance = models.ActiveTab.objects.filter(
|
||||
created_by=self.request.user, path=path
|
||||
).first()
|
||||
|
||||
instance = (
|
||||
models.ActiveTab() if not existing_instance else existing_instance
|
||||
)
|
||||
instance.path = path
|
||||
instance.tab_target = target
|
||||
instance.save()
|
||||
return JsonResponse({"message": "Success"})
|
||||
|
||||
|
||||
class ActiveGroup(View):
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
ActiveGroup
|
||||
"""
|
||||
path = self.request.GET.get("path")
|
||||
target = self.request.GET.get("target")
|
||||
group_field = self.request.GET.get("field")
|
||||
if path and target and group_field and self.request.user:
|
||||
existing_instance = models.ActiveGroup.objects.filter(
|
||||
created_by=self.request.user,
|
||||
path=path,
|
||||
group_by_field=group_field,
|
||||
).first()
|
||||
|
||||
instance = (
|
||||
models.ActiveGroup() if not existing_instance else existing_instance
|
||||
)
|
||||
instance.path = path
|
||||
instance.group_by_field = group_field
|
||||
instance.group_target = target
|
||||
instance.save()
|
||||
return JsonResponse({"message": "Success"})
|
||||
Reference in New Issue
Block a user