Files
ihrm/horilla_views/generic/cbv/views.py

1310 lines
46 KiB
Python

"""
horilla/generic/views.py
"""
import json
from typing import Any
from urllib.parse import parse_qs
from bs4 import BeautifulSoup
from django import forms
from django.contrib import messages
from django.core.cache import cache as CACHE
from django.core.paginator import Page
from django.http import HttpRequest, HttpResponse, QueryDict
from django.shortcuts import render
from django.urls import resolve, reverse
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, FormView, ListView, TemplateView
from base.methods import closest_numbers, eval_validate, get_key_instances
from horilla.filters import FilterSet
from horilla.group_by import group_by_queryset
from horilla.horilla_middlewares import _thread_locals
from horilla_views import models
from horilla_views.cbv_methods import ( # update_initial_cache,
get_short_uuid,
hx_request_required,
paginator_qry,
sortby,
structured,
update_saved_filter_cache,
)
from horilla_views.forms import DynamicBulkUpdateForm, ToggleColumnForm
from horilla_views.templatetags.generic_template_filters import getattribute
@method_decorator(hx_request_required, name="dispatch")
class HorillaListView(ListView):
"""
HorillaListView
"""
filter_class: FilterSet = None
view_id: str = """"""
export_file_name: str = None
template_name: str = "generic/horilla_list_table.html"
context_object_name = "queryset"
# column = [("Verbose Name","field_name","avatar_mapping")], opt: avatar_mapping
columns: list = []
default_columns: list = []
search_url: str = ""
bulk_select_option: bool = True
filter_selected: bool = True
quick_export: bool = True
bulk_update: bool = True
custom_empty_template: str = ""
action_method: str = """"""
"""
eg:
def accessibility(
request, instance: object = None, user_perms: PermWrapper = [], *args, **kwargs
)->bool:
# True if accessible to the action else False
return True
actions = [
{
"action": "Edit",
"accessibility": "path_to_your.accessibility", # path to your accessibility method
"attrs": '''{instance_attributes_called_like_this}''',
},
etc..
]
"""
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 = []
selected_instances_key_id: str = "selectedInstances"
show_filter_tags: bool = True
show_toggle_form: bool = True
filter_keys_to_remove: list = []
records_per_page: int = 50
export_fields: list = []
verbose_name: str = ""
bulk_update_fields: list = []
bulk_template: str = "generic/bulk_form.html"
header_attrs: dict = {}
def post(self, *args, **kwargs):
"""
POST method to handle post submissions
"""
return self.get(self, *args, **kwargs)
def __init__(self, **kwargs: Any) -> None:
if not self.view_id:
self.view_id = get_short_uuid(4)
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
# # update_initial_cache(request, CACHE, HorillaListView)
# 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()
if not existing_instance:
if not self.default_columns:
self.default_columns = self.columns
self.toggle_form = ToggleColumnForm(
self.columns, self.default_columns, hidden_fields
)
for column in self.columns:
if column not in self.default_columns:
self.visible_column.remove(column)
else:
self.toggle_form = ToggleColumnForm(
self.columns, self.default_columns, hidden_fields
)
for column in self.columns:
if column[1] in hidden_fields:
self.visible_column.remove(column)
def bulk_update_accessibility(self) -> bool:
"""
Accessibility method for bulk update
"""
return self.request.user.has_perm(
f"{self.model._meta.app_label}.change_{self.model.__name__.lower()}"
)
def serve_bulk_form(self, request: HttpRequest) -> HttpResponse:
"""
Bulk form serve method
"""
if not self.bulk_update_accessibility():
return HttpResponse("You dont have permission")
form = self.get_bulk_form()
form.verbose_name = (
form.verbose_name
+ f" ({len((eval_validate(request.GET.get('instance_ids','[]'))))} {_('Records')})"
)
return render(
request,
self.bulk_template,
{"form": form, "post_bulk_path": self.post_bulk_path},
)
def handle_bulk_submission(self, request: HttpRequest) -> HttpRequest:
"""
This method to handle bulk update form submission
"""
if not self.bulk_update_accessibility():
return HttpResponse("You dont have permission")
instance_ids = request.GET.get("instance_ids", "[]")
instance_ids = eval_validate(instance_ids)
form = DynamicBulkUpdateForm(
request.POST,
request.FILES,
root_model=self.model,
bulk_update_fields=self.bulk_update_fields,
ids=instance_ids,
)
if instance_ids and form.is_valid():
form.save()
messages.success(request, _("Selected Records updated"))
script_id = get_short_uuid(length=3, prefix="bulk")
return HttpResponse(
f"""
<script id="{script_id}">
$("#{script_id}").closest(".oh-modal--show").removeClass("oh-modal--show");
$(".reload-record").click()
$("#reloadMessagesButton").click()
</script>
"""
)
if not instance_ids:
messages.info(request, _("No records selected"))
return render(
request,
self.bulk_template,
{"form": form, "post_bulk_path": self.post_bulk_path},
)
def get_bulk_form(self):
"""
Bulk from generating method
"""
# Bulk update feature
return DynamicBulkUpdateForm(
root_model=self.model, bulk_update_fields=self.bulk_update_fields
)
def get_queryset(self, queryset=None, filtered=False, *args, **kwargs):
if not self.queryset:
self.queryset = super().get_queryset() if not queryset else queryset
self._saved_filters = QueryDict("", mutable=True)
if self.filter_class:
query_dict = self.request.GET
selected_ids = eval_validate(
self.request.POST.get("selected_ids", "[]")
)
if (
self.request.session.get("prev_path")
and self.request.session.get("prev_path") != self.request.path
):
selected_ids = []
self.request.session["hlv_selected_ids"] = selected_ids
self.request.session["prev_path"] = self.request.path
if selected_ids and selected_ids != self.request.session.get(
"hlv_selected_ids", []
):
self.request.session["hlv_selected_ids"] = selected_ids
self.request.session["prev_path"] = self.request.path
if "filter_applied" in query_dict.keys():
update_saved_filter_cache(self.request, CACHE)
elif CACHE.get(
str(self.request.session.session_key) + self.request.path + "cbv"
):
query_dict = CACHE.get(
str(self.request.session.session_key)
+ self.request.path
+ "cbv"
)["query_dict"]
default_filter = models.SavedFilter.objects.filter(
path=self.request.path,
created_by=self.request.user,
is_default=True,
).first()
if not bool(query_dict) and default_filter:
data = eval_validate(default_filter.filter)
query_dict = QueryDict("", mutable=True)
for key, value in data.items():
query_dict[key] = value
query_dict._mutable = False
self._saved_filters = query_dict
self.request.exclude_filter_form = True
if not filtered:
self.queryset = self.filter_class(
data=query_dict, queryset=self.queryset, request=self.request
).qs
else:
self.queryset = queryset
if self.request.GET.get(
"show_all"
) == "true" and self.request.session.get("hlv_selected_ids"):
del self.request.session["hlv_selected_ids"]
if self.request.session.get("hlv_selected_ids"):
self.request.actual_ids = list(
self.queryset.values_list("id", flat=True)
)
self.queryset = self.queryset.filter(
id__in=self.request.session["hlv_selected_ids"]
)
return self.queryset
def get_context_data(self, **kwargs: Any):
context = super().get_context_data(**kwargs)
context["view_id"] = self.view_id
context["columns"] = self.visible_column
context["hidden_columns"] = list(set(self.columns) - set(self.visible_column))
context["toggle_form"] = self.toggle_form
context["show_toggle_form"] = self.show_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["header_attrs"] = self.header_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["selected_instances_key_id"] = self.selected_instances_key_id
context["row_status_indications"] = self.row_status_indications
context["saved_filters"] = self._saved_filters
context["quick_export"] = self.quick_export
context["filter_selected"] = self.filter_selected
context["bulk_update"] = self.bulk_update
if not self.verbose_name:
self.verbose_name = self.model.__class__
context["model_name"] = self.verbose_name
context["export_fields"] = self.export_fields
context["custom_empty_template"] = self.custom_empty_template
referrer = self.request.GET.get("referrer", "")
if referrer:
# Remove the protocol and domain part
referrer = "/" + "/".join(referrer.split("/")[3:])
context["stored_filters"] = (
models.SavedFilter.objects.filter(
path=self.request.path, created_by=self.request.user
)
| models.SavedFilter.objects.filter(
referrer=referrer, created_by=self.request.user
)
).distinct()
context["select_all_ids"] = self.select_all
if self._saved_filters.get("field"):
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 key in ["filter_applied", "nav_url"] + self.filter_keys_to_remove
]
for key in (
keys_to_remove + ["referrer", "nav_url"] + self.filter_keys_to_remove
):
if key in data_dict.keys():
data_dict.pop(key)
context["filter_dict"] = data_dict
context["keys_to_remove"] = keys_to_remove
request = self.request
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 = []
if not self._saved_filters.get("field"):
for instance in queryset:
ordered_ids.append(instance.pk)
self.request.session[f"ordered_ids_{self.model.__name__.lower()}"] = 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_table.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
)
# for group in context["groups"]:
# for instance in group["list"]:
# instance.ordered_ids = ordered_ids
# ordered_ids.append(instance.pk)
# CACHE.get(self.request.session.session_key + "cbv")[HorillaListView] = context
from horilla.urls import path, urlpatterns
self.export_path = f"export-list-view-{get_short_uuid(4)}/"
urlpatterns.append(path(self.export_path, self.export_data))
context["export_path"] = self.export_path
if self.bulk_update_fields and self.bulk_update_accessibility():
get_bulk_path = (
f"get-bulk-update-{self.view_id}-{self.request.session.session_key}/"
)
post_bulk_path = (
f"post-bulk-update-{self.view_id}-{self.request.session.session_key}/"
)
self.post_bulk_path = post_bulk_path
urlpatterns.append(
path(
get_bulk_path,
self.serve_bulk_form,
)
)
urlpatterns.append(
path(
post_bulk_path,
self.handle_bulk_submission,
)
)
context["bulk_update_fields"] = self.bulk_update_fields
context["bulk_path"] = get_bulk_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 fields, resources
request = getattr(_thread_locals, "request", None)
ids = eval_validate(request.POST["ids"])
_columns = eval_validate(request.POST["columns"])
queryset = self.model.objects.filter(id__in=ids)
_model = self.model
class HorillaListViewResorce(resources.ModelResource):
"""
Instant Resource class
"""
id = fields.Field(column_name="ID")
class Meta:
"""
Meta class for additional option
"""
model = _model
fields = []
def dehydrate_id(self, instance):
"""
Dehydrate method for id field
"""
return instance.pk
for field_tuple in _columns:
dynamic_fn_str = f"def dehydrate_{field_tuple[1]}(self, instance):return self.remove_extra_spaces(getattribute(instance, '{field_tuple[1]}'))"
exec(dynamic_fn_str)
dynamic_fn = locals()[f"dehydrate_{field_tuple[1]}"]
locals()[field_tuple[1]] = fields.Field(column_name=field_tuple[0])
def remove_extra_spaces(self, text):
"""
Remove blank space but keep line breaks and add new lines for <li> tags.
"""
soup = BeautifulSoup(str(text), "html.parser")
for li in soup.find_all("li"):
li.insert_before("\n")
li.unwrap()
text = soup.get_text()
lines = text.splitlines()
non_blank_lines = [line.strip() for line in lines if line.strip()]
cleaned_text = "\n".join(non_blank_lines)
return cleaned_text
book_resource = HorillaListViewResorce()
# Export the data using the resource
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
@method_decorator(hx_request_required, name="dispatch")
class HorillaDetailedView(DetailView):
"""
HorillDetailedView
"""
title = "Detailed View"
template_name = "generic/horilla_detailed_view.html"
header: dict = {
"title": "Horilla",
"subtitle": "Horilla Detailed View",
"avatar": "",
}
body: list = []
action_method: list = []
actions: list = []
cols: dict = {}
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 get_context_data(self, **kwargs: Any):
context = super().get_context_data(**kwargs)
instance_ids = self.request.session.get(
f"ordered_ids_{self.model.__name__.lower()}", []
)
pk = context["object"].pk
# if instance_ids:
# context["object"].ordered_ids = instance_ids
context["instance"] = context["object"]
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
context["cols"] = self.cols
# CACHE.get(self.request.session.session_key + "cbv")[
# HorillaDetailedView
# ] = context
return context
@method_decorator(hx_request_required, name="dispatch")
class HorillaTabView(TemplateView):
"""
HorillaTabView
"""
view_id: str = get_short_uuid(3, "htv")
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
context["view_id"] = self.view_id
# CACHE.get(self.request.session.session_key + "cbv")[HorillaTabView] = context
return context
@method_decorator(hx_request_required, name="dispatch")
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 = {}
"""
eg:
def accessibility(
request, instance: object = None, user_perms: PermWrapper = [], *args, **kwargs
)->bool:
# True if accessible to the action else False
return True
actions = [
{
"action": "Edit",
"accessibility": "path_to_your.accessibility", # path to your accessibility method
"attrs": '''{instance_attributes_called_like_this}''',
},
etc..
]
"""
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):
if not self.queryset:
queryset = super().get_queryset()
if self.filter_class:
query_dict = self.request.GET
if "filter_applied" in query_dict.keys():
update_saved_filter_cache(self.request, CACHE)
elif CACHE.get(
str(self.request.session.session_key) + self.request.path + "cbv"
):
query_dict = CACHE.get(
str(self.request.session.session_key)
+ self.request.path
+ "cbv"
)["query_dict"]
self._saved_filters = query_dict
self.request.exclude_filter_form = True
self.queryset = self.filter_class(
query_dict, queryset, request=self.request
).qs
default_filter = models.SavedFilter.objects.filter(
path=self.request.path,
created_by=self.request.user,
is_default=True,
).first()
if not bool(query_dict) and default_filter:
data = eval_validate(default_filter.filter)
query_dict = QueryDict("", mutable=True)
for key, value in data.items():
query_dict[key] = value
query_dict._mutable = False
return self.queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
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
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 + ["referrer", "nav_url"] + self.filter_keys_to_remove
):
if key in data_dict.keys():
data_dict.pop(key)
context["filter_dict"] = data_dict
ordered_ids = list(queryset.values_list("id", flat=True))
ordered_ids = []
if not self._saved_filters.get("field"):
for instance in queryset:
ordered_ids.append(instance.pk)
self.request.session[f"ordered_ids_{self.model.__name__.lower()}"] = ordered_ids
# CACHE.get(self.request.session.session_key + "cbv")[HorillaCardView] = context
referrer = self.request.GET.get("referrer", "")
if referrer:
# Remove the protocol and domain part
referrer = "/" + "/".join(referrer.split("/")[3:])
context["stored_filters"] = (
models.SavedFilter.objects.filter(
path=self.request.path, created_by=self.request.user
)
| models.SavedFilter.objects.filter(
referrer=referrer, created_by=self.request.user
)
).distinct()
context["queryset"] = paginator_qry(
queryset, self.request.GET.get("page"), self.records_per_page
)
return context
@method_decorator(hx_request_required, name="dispatch")
class ReloadMessages(TemplateView):
"""
Reload messages
"""
template_name = "generic/messages.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
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
CACHE.set(
request.session.session_key + "cbv" + dynamic_field,
{
"dynamic_field": dynamic_field,
"value": new_isntance_pk,
"model": self._meta.model,
},
)
return response
@method_decorator(hx_request_required, name="dispatch")
class HorillaFormView(FormView):
"""
HorillaFormView
"""
class HttpResponse:
"""
HttpResponse
"""
def __new__(
self, content: str = "", targets_to_reload: list = [], script: str = ""
) -> HttpResponse:
"""
__new__ method
"""
targets_to_reload = list(set(targets_to_reload))
targets_to_reload.append("#reloadMessagesButton")
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)
hx_confirm: str = ""
form_class: forms.ModelForm = None
template_name = "generic/horilla_form.html"
ids_key: str = "instance_ids"
form_disaply_attr: str = ""
new_display_title: str = "Add New"
close_button_attrs: str = """"""
submit_button_attrs: str = """"""
# NOTE: Dynamic create view's forms save method will be overwritten
is_dynamic_create_view: bool = False
# [(field_name,DynamicFormView,[other_field1,...])] # other_fields
# can be mentioned like this to pass the field selected
dynamic_create_fields: list = []
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
if not self.success_url:
self.success_url = self.request.path
# update_initial_cache(request, CACHE, HorillaFormView)
if self.form_class:
setattr(self.form_class, "structured", structured)
def get(
self, request: HttpRequest, *args: str, pk=None, **kwargs: Any
) -> HttpResponse:
_pk = pk
response = super().get(request, *args, **kwargs)
return response
def post(
self, request: HttpRequest, *args: str, pk=None, **kwargs: Any
) -> HttpResponse:
_pk = pk
self.get_form()
response = super().post(request, *args, **kwargs)
return response
def init_form(self, *args, data={}, files={}, instance=None, **kwargs):
"""
method where first the form where initialized
"""
self.form_class: forms.ModelForm
form = self.form_class(
data, files, instance=instance, initial=self.get_initial()
)
return form
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
self.form_class_path = (
self.get_form().__class__.__module__ + "." + self.form.__class__.__name__
)
context["dynamic_create_fields"] = self.dynamic_create_fields
context["form_class_path"] = self.form_class_path
context["view_id"] = self.view_id
context["hx_confirm"] = self.hx_confirm
pk = None
if self.form.instance:
pk = self.form.instance.pk
# next/previous option in the forms
if pk and self.request.GET.get(self.ids_key):
instance_ids = self.request.session.get(
f"ordered_ids_{self.model.__name__.lower()}", []
)
url = resolve(self.request.path)
key = list(url.kwargs.keys())[0]
url_name = url.url_name
if instance_ids:
previous_id, next_id = closest_numbers(instance_ids, pk)
next_url = reverse(url_name, kwargs={key: next_id})
previous_url = reverse(url_name, kwargs={key: previous_id})
self.form.instance_ids = str(instance_ids)
self.form.ids_key = self.ids_key
self.form.next_url = next_url
self.form.previous_url = previous_url
return context
def get_queryset(self):
"""
method to get the instance for the form
"""
pk = self.kwargs.get("pk")
return self.model.objects.filter(pk=pk).first()
def get_form(self, form_class=None):
pk = self.kwargs.get("pk")
if not hasattr(self, "form"):
instance = self.get_queryset()
data = None
files = None
if self.request.method == "POST":
data = self.request.POST
files = self.request.FILES
form = self.init_form(data=data, files=files, instance=instance)
if self.is_dynamic_create_view:
# setattr(type(form), "save", save)
from types import MethodType
form.save = MethodType(save, form)
if self.request.method == "GET":
[
(
"employee_id",
FormView,
)
]
for dynamic_tuple in self.dynamic_create_fields:
view = dynamic_tuple[1]
view.display_title = "Dynamic create"
field = dynamic_tuple[0]
additional_data_fields = []
if len(dynamic_tuple) == 3:
additional_data_fields = dynamic_tuple[2]
key = self.request.session.session_key + "cbv" + field
field_instance = form.instance._meta.get_field(field)
value = form.initial.get(field, [])
form_field = forms.ChoiceField
if isinstance(field_instance, models.models.ManyToManyField):
form_field = forms.MultipleChoiceField
if form.instance.pk is not None:
value = list(
getattr(form.instance, field).values_list(
"id", flat=True
)
)
else:
value = getattr(getattribute(form.instance, field), "pk", value)
CACHE.set(
key,
{
"dynamic_field": field,
"value": value,
"model": form._meta.model,
},
)
from django.urls import path
from horilla.urls import urlpatterns
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"))
attrs = form.fields[field].widget.attrs
for data_field in additional_data_fields:
data_field_attr = form.fields[data_field].widget.attrs
if (
f"$('#modalButton{field}Form [name={data_field}]').val(this.value);"
not in data_field_attr.get("onchange", "")
):
data_field_attr["onchange"] = (
data_field_attr.get("onchange", "")
+ f"""
if(this.value != 'dynamic_create'){{
$('#modalButton{field}Form [name={data_field}]').val(this.value);
}}
"""
)
form.fields[field] = form_field(
choices=choices,
label=form.fields[field].label,
required=form.fields[field].required,
)
form.fields[field].widget.option_template_name = (
"horilla_widgets/select_option.html",
)
form.fields[field].widget.attrs = attrs
form.initial[field] = value
for dynamic_tuple in self.dynamic_create_fields:
field = dynamic_tuple[0]
onchange = form.fields[field].widget.attrs.get("onchange", "")
if onchange:
CACHE.set(
self.request.session.session_key
+ "cbv"
+ field
+ "onchange",
onchange,
)
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.get(self.request.session.session_key + "cbv")[HorillaFormView] = form
self.form = form
return self.form
@method_decorator(hx_request_required, name="dispatch")
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 = ""
empty_inputs: list = []
view_types: list = []
create_attrs: str = """"""
apply_first_filter = True
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["apply_first_filter"] = self.apply_first_filter
context["filter_instance_context_name"] = self.filter_instance
last_filter = CACHE.get(
self.request.session.session_key
+ "last-applied-filter"
+ self.request.path,
{},
)
context["empty_inputs"] = self.empty_inputs + ["nav_url"]
context["last_filter"] = dict(last_filter)
if self.filter_instance:
context[self.filter_form_context_name] = self.filter_instance.form
context["active_view"] = models.ActiveView.objects.filter(
path=self.request.path
).first()
# CACHE.get(self.request.session.session_key + "cbv")[HorillaNavView] = context
return context
@method_decorator(hx_request_required, name="dispatch")
class HorillaProfileView(DetailView):
"""
GenericHorillaProfileView
"""
template_name = "generic/horilla_profile_view.html"
view_id: str = None
filter_class: FilterSet = None
push_url: str = None
key_name: str = "pk"
# add these method under the model
# get_avatar --> image/profile
# get_subtitle --> Job position
actions: list = []
tabs: list = []
def __init__(self, **kwargs: Any) -> None:
self.url_prefix = str(self.__class__.__name__.lower())
if not self.view_id:
self.view_id = get_short_uuid(4)
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
# update_initial_cache(request, CACHE, HorillaProfileView)
from horilla.urls import path, urlpatterns
for tab in self.tabs:
if not tab.get("url"):
url = f"{self.url_prefix}-{tab['title']}"
urlpatterns.append(
path(
url + "/<int:pk>/",
tab["view"],
)
)
tab["url"] = "/" + url + "/{pk}/"
# hidden columns configuration
existing_instance = models.ToggleColumn.objects.filter(
user_id=request.user, path=request.path_info
).first()
self.visible_tabs = self.tabs.copy()
self.tabs_list = [(tab["title"], tab["title"]) for tab in self.visible_tabs]
hidden_tabs = (
[] if not existing_instance else existing_instance.excluded_columns
)
self.toggle_form = ToggleColumnForm(
self.tabs_list,
hidden_tabs,
hidden_fields=[],
)
for column in self.tabs_list:
if column[1] in hidden_tabs:
for tab in self.visible_tabs:
if tab["title"] == column[1]:
self.visible_tabs.remove(tab)
@classmethod
def add_tab(cls, tab: dict = None, index: int = None, tabs: list = None) -> None:
"""
Tab adding method
example tab arg look like
tab = {
"title":"About",
"view" : views.about_tab,
"accessibility": "path_to_your.accessibility", # path to your accessibility method
}
tabs = [tab,etc...]
add_tab(tabs=tabs) / add_tab(tab=tab)
"""
if tabs:
cls.tabs = cls.tabs + tabs
if tab:
if index is None:
cls.tabs.append(tab)
return
cls.tabs.index(index, tab)
def get_context_data(self, **kwargs: Any) -> dict:
context = super().get_context_data(**kwargs)
context["instance"] = context["object"]
context["tabs"] = self.tabs
context["visible_tabs"] = self.visible_tabs
context["toggle_form"] = self.toggle_form
context["view_id"] = self.view_id
active_tab = models.ActiveTab.objects.filter(
created_by=self.request.user, path=self.request.path
).first()
if active_tab:
context["active_target"] = active_tab.tab_target
instance_ids = self.request.session.get(
f"ordered_ids_{self.model.__name__.lower()}", []
)
if instance_ids:
CACHE.set(
f"{self.request.session.session_key}hpv-instance-ids", instance_ids
)
else:
instance_ids = CACHE.get(
f"{self.request.session.session_key}hpv-instance-ids"
)
instance_ids = instance_ids if instance_ids else []
instances = self.model.objects.filter(id__in=instance_ids)
context["instances"] = instances
balance_count = instances.count() - 6
if balance_count > 9:
display_count = "9+"
elif 1 < balance_count <= 9:
display_count = f"{balance_count-1}+"
else:
display_count = None
previous_id, next_id = closest_numbers(instance_ids, context["instance"].pk)
url = resolve(self.request.path)
key = list(url.kwargs.keys())[0]
url_name = url.url_name
next_url = reverse(url_name, kwargs={key: next_id})
previous_url = reverse(url_name, kwargs={key: previous_id})
push_url_next = reverse(self.push_url, kwargs={self.key_name: next_id})
push_url_prev = reverse(self.push_url, kwargs={self.key_name: previous_id})
context["instance_ids"] = str(instance_ids)
if instance_ids:
context["next_url"] = next_url
context["previous_url"] = previous_url
context["push_url_next"] = push_url_next
context["push_url_prev"] = push_url_prev
context["display_count"] = display_count
context["actions"] = self.actions
context["filter_class"] = self.filter_class
cache = {
"instances": context["instances"],
"instance_ids": context["instance_ids"],
"filter_class": context["filter_class"],
"view_id": context["view_id"],
"object": context["object"],
}
CACHE.set(f"{self.request.session.session_key}search_in_instance_ids", cache)
return context