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

1250 lines
43 KiB
Python
Raw Normal View History

"""
horilla/generic/views.py
"""
import json
from typing import Any
from urllib.parse import parse_qs
2024-06-13 11:22:59 +05:30
2024-08-07 12:01:46 +05:30
from bs4 import BeautifulSoup
2024-06-13 11:22:59 +05:30
from django import forms
from django.contrib import messages
2024-08-07 12:01:46 +05:30
from django.core.cache import cache as CACHE
from django.core.paginator import Page
2024-06-13 11:22:59 +05:30
from django.http import HttpRequest, HttpResponse, QueryDict
from django.shortcuts import render
2024-06-13 11:22:59 +05:30
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
2024-06-13 11:22:59 +05:30
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 (
get_short_uuid,
hx_request_required,
paginator_qry,
sortby,
2024-06-13 11:22:59 +05:30
structured,
update_initial_cache,
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 = []
search_url: str = ""
bulk_select_option: bool = True
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 = []
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
export_fields: list = []
verbose_name: str = ""
bulk_update_fields: list = []
bulk_template: str = "generic/bulk_form.html"
header_attrs: dict = {}
def __init__(self, **kwargs: Any) -> None:
2024-08-07 12:01:46 +05:30
if not self.view_id:
self.view_id = get_short_uuid(4)
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
2024-08-07 12:01:46 +05:30
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 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):
2024-08-07 12:01:46 +05:30
if not self.queryset:
self.queryset = super().get_queryset()
self._saved_filters = QueryDict("", mutable=True)
2024-08-07 12:01:46 +05:30
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"]
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)
2024-08-07 12:01:46 +05:30
query_dict = QueryDict("", mutable=True)
for key, value in data.items():
query_dict[key] = value
query_dict._mutable = False
self._saved_filters = query_dict
self.request.exclude_filter_form = True
self.queryset = self.filter_class(
data=query_dict, queryset=self.queryset, request=self.request
2024-08-07 12:01:46 +05:30
).qs
return self.queryset
def get_context_data(self, **kwargs: Any):
context = super().get_context_data(**kwargs)
context["view_id"] = self.view_id
context["columns"] = self.visible_column
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
context["selected_instances_key_id"] = self.selected_instances_key_id
context["row_status_indications"] = self.row_status_indications
context["saved_filters"] = self._saved_filters
2024-08-07 12:01:46 +05:30
if not self.verbose_name:
self.verbose_name = self.model.__class__
context["model_name"] = self.verbose_name
context["export_fields"] = self.export_fields
referrer = self.request.GET.get("referrer", "")
if referrer:
# Remove the protocol and domain part
referrer = "/" + "/".join(referrer.split("/")[3:])
context["stored_filters"] = (
models.SavedFilter.objects.filter(
path=self.request.path, created_by=self.request.user
)
| models.SavedFilter.objects.filter(
referrer=referrer, created_by=self.request.user
)
).distinct()
context["select_all_ids"] = self.select_all
if self._saved_filters.get("field"):
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
):
2024-08-07 12:01:46 +05:30
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
ordered_ids = list(queryset.values_list("id", flat=True))
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
)
2024-08-07 12:01:46 +05:30
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
)
2024-08-07 12:01:46 +05:30
# for group in context["groups"]:
# for instance in group["list"]:
# instance.ordered_ids = ordered_ids
# ordered_ids.append(instance.pk)
2024-08-07 12:01:46 +05:30
CACHE.get(self.request.session.session_key + "cbv")[HorillaListView] = context
2024-06-13 11:22:59 +05:30
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
2024-08-07 12:01:46 +05:30
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
"""
2024-06-13 11:22:59 +05:30
from import_export import fields, resources
request = getattr(_thread_locals, "request", None)
ids = eval_validate(request.GET["ids"])
_columns = eval_validate(request.GET["columns"])
queryset = self.model.objects.filter(id__in=ids)
2024-08-07 12:01:46 +05:30
_model = self.model
class HorillaListViewResorce(resources.ModelResource):
2024-08-07 12:01:46 +05:30
"""
Instant Resource class
"""
id = fields.Field(column_name="ID")
class Meta:
2024-08-07 12:01:46 +05:30
"""
Meta class for additional option
"""
model = _model
fields = []
def dehydrate_id(self, instance):
2024-08-07 12:01:46 +05:30
"""
Dehydrate method for id field
"""
return instance.pk
2024-08-07 12:01:46 +05:30
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])
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
update_initial_cache(request, CACHE, HorillaCardView)
self._saved_filters = QueryDict()
def get_queryset(self):
2024-08-07 12:01:46 +05:30
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)
2024-08-07 12:01:46 +05:30
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
):
2024-08-07 12:01:46 +05:30
if key in data_dict.keys():
data_dict.pop(key)
context["filter_dict"] = data_dict
2024-08-07 12:01:46 +05:30
ordered_ids = list(queryset.values_list("id", flat=True))
ordered_ids = []
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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:
2024-08-07 12:01:46 +05:30
"""
HttpResponse
"""
def __new__(
self, content: str = "", targets_to_reload: list = [], script: str = ""
) -> HttpResponse:
2024-08-07 12:01:46 +05:30
"""
__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"
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
# 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()}", []
)
2024-08-07 12:01:46 +05:30
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,
},
)
2024-08-07 12:01:46 +05:30
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 = """"""
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
2024-08-07 12:01:46 +05:30
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
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
2024-08-07 12:01:46 +05:30
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")
2024-08-07 12:01:46 +05:30
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"
2024-08-07 12:01:46 +05:30
# add these method under the model
# get_avatar --> image/profile
# get_subtitle --> Job position
actions: list = []
tabs: list = []
def __init__(self, **kwargs: Any) -> None:
self.url_prefix = str(self.__class__.__name__.lower())
if not self.view_id:
self.view_id = get_short_uuid(4)
super().__init__(**kwargs)
request = getattr(_thread_locals, "request", None)
self.request = request
update_initial_cache(request, CACHE, HorillaProfileView)
from horilla.urls import path, urlpatterns
for tab in self.tabs:
if not tab.get("url"):
url = f"{self.url_prefix}-{tab['title']}"
urlpatterns.append(
path(
url + "/<int:pk>/",
tab["view"],
)
)
tab["url"] = "/" + url + "/{pk}/"
# hidden columns configuration
existing_instance = models.ToggleColumn.objects.filter(
user_id=request.user, path=request.path_info
).first()
self.visible_tabs = self.tabs.copy()
self.tabs_list = [(tab["title"], tab["title"]) for tab in self.visible_tabs]
hidden_tabs = (
[] if not existing_instance else existing_instance.excluded_columns
)
self.toggle_form = ToggleColumnForm(
self.tabs_list,
hidden_tabs,
)
for column in self.tabs_list:
if column[1] in hidden_tabs:
for tab in self.visible_tabs:
if tab["title"] == column[1]:
self.visible_tabs.remove(tab)
@classmethod
def add_tab(cls, tab: dict = None, index: int = None, tabs: list = None) -> None:
"""
Tab adding method
example tab arg look like
tab = {
"title":"About",
"view" : views.about_tab,
"accessibility": "path_to_your.accessibility", # path to your accessibility method
}
tabs = [tab,etc...]
add_tab(tabs=tabs) / add_tab(tab=tab)
"""
if tabs:
cls.tabs = cls.tabs + tabs
if tab:
if index is None:
cls.tabs.append(tab)
return
cls.tabs.index(index, tab)
def get_context_data(self, **kwargs: Any) -> dict:
context = super().get_context_data(**kwargs)
context["instance"] = context["object"]
context["tabs"] = self.tabs
context["visible_tabs"] = self.visible_tabs
context["toggle_form"] = self.toggle_form
context["view_id"] = self.view_id
active_tab = models.ActiveTab.objects.filter(
created_by=self.request.user, path=self.request.path
).first()
if active_tab:
context["active_target"] = active_tab.tab_target
instance_ids = 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 []
2024-08-07 12:01:46 +05:30
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)
2024-08-07 12:01:46 +05:30
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
2024-08-07 12:01:46 +05:30
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