From 16df045cf4d7874482bcca929797c8ae7e8b7800 Mon Sep 17 00:00:00 2001 From: Horilla Date: Wed, 7 Aug 2024 12:01:46 +0530 Subject: [PATCH] [UPDT] HORILLA VIEWS: CBV updates --- horilla_views/cbv_methods.py | 75 ++- horilla_views/forms.py | 41 ++ horilla_views/generic/cbv/views.py | 448 ++++++++++++++---- horilla_views/models.py | 35 ++ .../templates/generic/filter_result.html | 10 + .../templates/generic/filter_tags.html | 21 +- horilla_views/templates/generic/form.html | 36 +- horilla_views/templates/generic/group_by.html | 56 ++- .../templates/generic/horilla_card.html | 50 +- .../generic/horilla_detailed_view.html | 10 +- .../templates/generic/horilla_form.html | 4 +- .../templates/generic/horilla_list.html | 55 ++- .../templates/generic/horilla_nav.html | 13 +- .../generic/horilla_profile_view.html | 301 ++++++++++++ .../templates/generic/horilla_tabs.html | 6 +- .../templates/generic/profile_view.html | 6 + .../templatetags/generic_template_filters.py | 45 +- horilla_views/urls.py | 21 + horilla_views/views.py | 109 ++++- 19 files changed, 1166 insertions(+), 176 deletions(-) create mode 100644 horilla_views/templates/generic/filter_result.html create mode 100644 horilla_views/templates/generic/horilla_profile_view.html create mode 100644 horilla_views/templates/generic/profile_view.html diff --git a/horilla_views/cbv_methods.py b/horilla_views/cbv_methods.py index d16fd771b..93df82dda 100644 --- a/horilla_views/cbv_methods.py +++ b/horilla_views/cbv_methods.py @@ -9,7 +9,13 @@ from venv import logger from django import template from django.contrib import messages +from django.core.cache import cache as CACHE from django.core.paginator import Paginator +from django.db.models.fields.related import ForeignKey +from django.db.models.fields.related_descriptors import ( + ForwardManyToOneDescriptor, + ReverseOneToOneDescriptor, +) from django.http import HttpResponse from django.middleware.csrf import get_token from django.shortcuts import redirect, render @@ -194,10 +200,11 @@ def get_short_uuid(length: int, prefix: str = "hlv"): def update_initial_cache(request: object, cache: dict, view: object): - if cache.get(request.session.session_key): - cache[request.session.session_key].update({view: {}}) + + if cache.get(request.session.session_key + "cbv"): + cache.get(request.session.session_key + "cbv").update({view: {}}) return - cache.update({request.session.session_key: {view: {}}}) + cache.set(request.session.session_key + "cbv", {view: {}}) return @@ -218,8 +225,28 @@ class Reverse: reverse: bool = True page: str = "" + def __str__(self) -> str: + return str(self.reverse) -cache = {} + +def getmodelattribute(value, attr: str): + """ + Gets an attribute of a model dynamically from a string name, handling related fields. + """ + result = value + attrs = attr.split("__") + for attr in attrs: + if hasattr(result, attr): + result = getattr(result, attr) + if isinstance(result, ForwardManyToOneDescriptor): + result = result.field.related_model + elif hasattr(result, "field") and isinstance(result.field, ForeignKey): + result = getattr(result.field.remote_field.model, attr, None) + elif hasattr(result, "related") and isinstance( + result, ReverseOneToOneDescriptor + ): + result = getattr(result.related.related_model, attr, None) + return result def sortby( @@ -230,16 +257,17 @@ def sortby( """ request = getattr(_thread_locals, "request", None) sort_key = query_dict[key] - if not cache.get(request.session.session_key): - cache[request.session.session_key] = Reverse() - cache[request.session.session_key].page = ( + if not CACHE.get(request.session.session_key + "cbvsortby"): + CACHE.set(request.session.session_key + "cbvsortby", Reverse()) + CACHE.get(request.session.session_key + "cbvsortby").page = ( "1" if not query_dict.get(page) else query_dict.get(page) ) - reverse = cache[request.session.session_key].reverse + reverse_object = CACHE.get(request.session.session_key + "cbvsortby") + reverse = reverse_object.reverse none_ids = [] none_queryset = [] model = queryset.model - model_attr = getattribute(model, sort_key) + model_attr = getmodelattribute(model, sort_key) is_method = isinstance(model_attr, types.FunctionType) if not is_method: none_queryset = queryset.filter(**{f"{sort_key}__isnull": True}) @@ -256,19 +284,16 @@ def sortby( current_page = query_dict.get(page) if current_page or is_first_sort: order = not order - if ( - cache[request.session.session_key].page == current_page - and not is_first_sort - ): + if reverse_object.page == current_page and not is_first_sort: order = not order - cache[request.session.session_key].page = current_page + reverse_object.page = current_page try: queryset = sorted(queryset, key=_sortby, reverse=order) except TypeError: none_queryset = list(queryset.filter(id__in=none_ids)) queryset = sorted(queryset.exclude(id__in=none_ids), key=_sortby, reverse=order) - cache[request.session.session_key].reverse = order + reverse_object.reverse = order if order: order = "asc" queryset = list(queryset) + list(none_queryset) @@ -277,6 +302,7 @@ def sortby( order = "desc" setattr(request, "sort_order", order) setattr(request, "sort_key", sort_key) + CACHE.set(request.session.session_key + "cbvsortby", reverse_object) return queryset @@ -284,22 +310,21 @@ def update_saved_filter_cache(request, cache): """ Method to save filter on cache """ - if cache.get(request.session.session_key): - cache[request.session.session_key].update( + if cache.get(request.session.session_key + request.path + "cbv"): + cache.get(request.session.session_key + request.path + "cbv").update( { "path": request.path, "query_dict": request.GET, - "request": request, + # "request": request, } ) return cache - cache.update( + cache.set( + request.session.session_key + request.path + "cbv", { - request.session.session_key: { - "path": request.path, - "query_dict": request.GET, - "request": request, - } - } + "path": request.path, + "query_dict": request.GET, + # "request": request, + }, ) return cache diff --git a/horilla_views/forms.py b/horilla_views/forms.py index a0b44cfdc..291f30235 100644 --- a/horilla_views/forms.py +++ b/horilla_views/forms.py @@ -7,6 +7,7 @@ from django.template.loader import render_to_string from django.utils.safestring import SafeText from horilla.horilla_middlewares import _thread_locals +from horilla_views import models class ToggleColumnForm(forms.Form): @@ -34,3 +35,43 @@ class ToggleColumnForm(forms.Form): context = {"form": self, "request": self.request} table_html = render_to_string("generic/as_list.html", context) return table_html + + +class SavedFilterForm(forms.ModelForm): + """ + SavedFilterForm + """ + + color = forms.CharField( + widget=forms.TextInput( + attrs={ + "class": "oh-input w-100", + "type": "color", + "placeholder": "Choose a color", + } + ) + ) + + class Meta: + model = models.SavedFilter + fields = ["title", "is_default", "color"] + + def structured(self): + """ + Render the form fields as HTML table rows with Bootstrap styling. + """ + request = getattr(_thread_locals, "request", None) + context = { + "form": self, + "request": request, + } + table_html = render_to_string("common_form.html", context) + return table_html + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + attrs = self.fields["title"].widget.attrs + attrs["class"] = "oh-input w-100" + attrs["placeholder"] = "Saved filter title" + if self.instance.pk: + self.verbose_name = self.instance.title diff --git a/horilla_views/generic/cbv/views.py b/horilla_views/generic/cbv/views.py index b1a058262..5805ce213 100644 --- a/horilla_views/generic/cbv/views.py +++ b/horilla_views/generic/cbv/views.py @@ -6,12 +6,13 @@ import json from typing import Any from urllib.parse import parse_qs +from bs4 import BeautifulSoup from django import forms +from django.core.cache import cache as CACHE from django.core.paginator import Page from django.http import HttpRequest, HttpResponse, QueryDict -from django.shortcuts import render from django.urls import resolve, reverse -from django.views.generic import DetailView, FormView, ListView, TemplateView +from django.views.generic import DetailView, FormView, ListView, TemplateView, View from base.methods import closest_numbers, get_key_instances from horilla.filters import FilterSet @@ -29,9 +30,6 @@ from horilla_views.cbv_methods import ( from horilla_views.forms import ToggleColumnForm from horilla_views.templatetags.generic_template_filters import getattribute -cache = {} -saved_filters = {} - class HorillaListView(ListView): """ @@ -80,18 +78,23 @@ class HorillaListView(ListView): sortby_key: str = "sortby" sortby_mapping: list = [] + selected_instances_key_id: str = "selectedInstances" + show_filter_tags: bool = True filter_keys_to_remove: list = [] records_per_page: int = 50 + export_fields: list = [] + verbose_name: str = "" def __init__(self, **kwargs: Any) -> None: - self.view_id = get_short_uuid(4) + if not self.view_id: + self.view_id = get_short_uuid(4) super().__init__(**kwargs) request = getattr(_thread_locals, "request", None) self.request = request - update_initial_cache(request, cache, HorillaListView) + update_initial_cache(request, CACHE, HorillaListView) # hidden columns configuration existing_instance = models.ToggleColumn.objects.filter( @@ -110,27 +113,46 @@ class HorillaListView(ListView): self.visible_column.remove(column) def get_queryset(self): - queryset = super().get_queryset() + if not self.queryset: + queryset = super().get_queryset() - if self.filter_class: - query_dict = self.request.GET - if "filter_applied" in query_dict.keys(): - update_saved_filter_cache(self.request, saved_filters) - elif saved_filters.get( - str(self.request.session.session_key) + self.request.path - ): - query_dict = saved_filters[ - str(self.request.session.session_key) + self.request.path - ]["query_dict"] + if self.filter_class: + query_dict = self.request.GET + if "filter_applied" in query_dict.keys(): + update_saved_filter_cache(self.request, CACHE) + elif CACHE.get( + str(self.request.session.session_key) + self.request.path + "cbv" + ): + query_dict = CACHE.get( + str(self.request.session.session_key) + + self.request.path + + "cbv" + )["query_dict"] - self._saved_filters = query_dict - queryset = self.filter_class(query_dict, queryset).qs - return queryset + default_filter = models.SavedFilter.objects.filter( + path=self.request.path, + created_by=self.request.user, + is_default=True, + ).first() + if not bool(query_dict) and default_filter: + data = eval(default_filter.filter) + query_dict = QueryDict("", mutable=True) + for key, value in data.items(): + query_dict[key] = value + + query_dict._mutable = False + self._saved_filters = query_dict + self.request.exclude_filter_form = True + self.queryset = self.filter_class( + data=query_dict, queryset=queryset, request=self.request + ).qs + return self.queryset def get_context_data(self, **kwargs: Any): context = super().get_context_data(**kwargs) context["view_id"] = self.view_id context["columns"] = self.visible_column + context["hidden_columns"] = list(set(self.columns) - set(self.visible_column)) context["toggle_form"] = self.toggle_form context["search_url"] = self.search_url @@ -146,8 +168,25 @@ class HorillaListView(ListView): context["row_status_class"] = self.row_status_class context["sortby_key"] = self.sortby_key context["sortby_mapping"] = self.sortby_mapping + context["selected_instances_key_id"] = self.selected_instances_key_id context["row_status_indications"] = self.row_status_indications context["saved_filters"] = self._saved_filters + if not self.verbose_name: + self.verbose_name = self.model.__class__ + context["model_name"] = self.verbose_name + context["export_fields"] = self.export_fields + referrer = self.request.GET.get("referrer", "") + if referrer: + # Remove the protocol and domain part + referrer = "/" + "/".join(referrer.split("/")[3:]) + context["stored_filters"] = ( + models.SavedFilter.objects.filter( + path=self.request.path, created_by=self.request.user + ) + | models.SavedFilter.objects.filter( + referrer=referrer, created_by=self.request.user + ) + ).distinct() context["select_all_ids"] = self.select_all if self._saved_filters.get("field"): @@ -170,13 +209,13 @@ class HorillaListView(ListView): if value[0] in ["unknown", "on"] + self.filter_keys_to_remove ] - for key in keys_to_remove: - data_dict.pop(key) + for key in keys_to_remove + ["referrer"]: + if key in data_dict.keys(): + data_dict.pop(key) context["filter_dict"] = data_dict request = self.request ordered_ids = list(queryset.values_list("id", flat=True)) - model = queryset.model is_first_sort = False query_dict = self.request.GET if ( @@ -193,8 +232,13 @@ class HorillaListView(ListView): queryset = sortby( query_dict, queryset, self.sortby_key, is_first_sort=is_first_sort ) + ordered_ids = [instance.id for instance in queryset] - setattr(model, "ordered_ids", ordered_ids) + ordered_ids = [] + if not self._saved_filters.get("field"): + for instance in queryset: + instance.ordered_ids = ordered_ids + ordered_ids.append(instance.pk) context["queryset"] = paginator_qry( queryset, self._saved_filters.get("page"), self.records_per_page @@ -213,13 +257,19 @@ class HorillaListView(ListView): context["groups"] = paginator_qry( groups, self._saved_filters.get("page"), 10 ) - cache[self.request.session.session_key][HorillaListView] = context + + for group in context["groups"]: + for instance in group["list"]: + instance.ordered_ids = ordered_ids + ordered_ids.append(instance.pk) + CACHE.get(self.request.session.session_key + "cbv")[HorillaListView] = context from horilla.urls import path, urlpatterns self.export_path = f"export-list-view-{get_short_uuid(4)}/" urlpatterns.append(path(self.export_path, self.export_data)) context["export_path"] = self.export_path + return context def select_all(self, *args, **kwargs): @@ -236,30 +286,52 @@ class HorillaListView(ListView): request = getattr(_thread_locals, "request", None) ids = eval(request.GET["ids"]) + _columns = eval(request.GET["columns"]) queryset = self.model.objects.filter(id__in=ids) - MODEL = self.model - FIELDS = self.visible_column + _model = self.model class HorillaListViewResorce(resources.ModelResource): + """ + Instant Resource class + """ + id = fields.Field(column_name="ID") class Meta: - model = MODEL + """ + Meta class for additional option + """ + + model = _model fields = [] def dehydrate_id(self, instance): + """ + Dehydrate method for id field + """ return instance.pk - def before_export(self, queryset, *args, **kwargs): - return super().before_export(queryset, *args, **kwargs) - - for field_tuple in FIELDS: - dynamic_fn_str = f"def dehydrate_{field_tuple[1]}(self, instance):return str(getattribute(instance, '{field_tuple[1]}'))" + for field_tuple in _columns: + dynamic_fn_str = f"def dehydrate_{field_tuple[1]}(self, instance):return self.remove_extra_spaces(getattribute(instance, '{field_tuple[1]}'))" exec(dynamic_fn_str) dynamic_fn = locals()[f"dehydrate_{field_tuple[1]}"] locals()[field_tuple[1]] = fields.Field(column_name=field_tuple[0]) + def remove_extra_spaces(self, text): + """ + Remove blank space but keep line breaks and add new lines for
  • tags. + """ + soup = BeautifulSoup(str(text), "html.parser") + for li in soup.find_all("li"): + li.insert_before("\n") + li.unwrap() + text = soup.get_text() + lines = text.splitlines() + non_blank_lines = [line.strip() for line in lines if line.strip()] + cleaned_text = "\n".join(non_blank_lines) + return cleaned_text + book_resource = HorillaListViewResorce() # Export the data using the resource @@ -285,7 +357,7 @@ class HorillaSectionView(TemplateView): super().__init__(**kwargs) request = getattr(_thread_locals, "request", None) self.request = request - update_initial_cache(request, cache, HorillaListView) + update_initial_cache(request, CACHE, HorillaListView) nav_url: str = "" view_url: str = "" @@ -327,17 +399,14 @@ class HorillaDetailedView(DetailView): super().__init__(**kwargs) request = getattr(_thread_locals, "request", None) self.request = request - update_initial_cache(request, cache, HorillaDetailedView) - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) + update_initial_cache(request, CACHE, HorillaDetailedView) def get_context_data(self, **kwargs: Any): context = super().get_context_data(**kwargs) - instance_ids = eval(str(self.request.GET.get(self.ids_key))) pk = context["object"].pk + context["instance"] = context["object"] url = resolve(self.request.path) key = list(url.kwargs.keys())[0] @@ -361,7 +430,9 @@ class HorillaDetailedView(DetailView): context["actions"] = self.actions context["action_method"] = self.action_method - cache[self.request.session.session_key][HorillaDetailedView] = context + CACHE.get(self.request.session.session_key + "cbv")[ + HorillaDetailedView + ] = context return context @@ -379,7 +450,7 @@ class HorillaTabView(TemplateView): super().__init__(**kwargs) request = getattr(_thread_locals, "request", None) self.request = request - update_initial_cache(request, cache, HorillaTabView) + update_initial_cache(request, CACHE, HorillaTabView) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -391,7 +462,7 @@ class HorillaTabView(TemplateView): context["active_target"] = active_tab.tab_target context["tabs"] = self.tabs - cache[self.request.session.session_key][HorillaTabView] = context + CACHE.get(self.request.session.session_key + "cbv")[HorillaTabView] = context return context @@ -443,23 +514,44 @@ class HorillaCardView(ListView): super().__init__(**kwargs) request = getattr(_thread_locals, "request", None) self.request = request - update_initial_cache(request, cache, HorillaCardView) + update_initial_cache(request, CACHE, HorillaCardView) self._saved_filters = QueryDict() def get_queryset(self): - queryset = super().get_queryset() - if self.filter_class: - query_dict = self.request.GET - if "filter_applied" in query_dict.keys(): - update_saved_filter_cache(self.request, saved_filters) - elif saved_filters.get(self.request.session.session_key): - query_dict = saved_filters[self.request.session.session_key][ - "query_dict" - ] + if not self.queryset: + queryset = super().get_queryset() + if self.filter_class: + query_dict = self.request.GET + if "filter_applied" in query_dict.keys(): + update_saved_filter_cache(self.request, CACHE) + elif CACHE.get( + str(self.request.session.session_key) + self.request.path + "cbv" + ): + query_dict = CACHE.get( + str(self.request.session.session_key) + + self.request.path + + "cbv" + )["query_dict"] - self._saved_filters = query_dict - queryset = self.filter_class(query_dict, queryset).qs - return queryset + self._saved_filters = query_dict + self.request.exclude_filter_form = True + self.queryset = self.filter_class( + query_dict, queryset, request=self.request + ).qs + default_filter = models.SavedFilter.objects.filter( + path=self.request.path, + created_by=self.request.user, + is_default=True, + ).first() + if not bool(query_dict) and default_filter: + data = eval(default_filter.filter) + query_dict = QueryDict("", mutable=True) + for key, value in data.items(): + query_dict[key] = value + + query_dict._mutable = False + + return self.queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -488,11 +580,30 @@ class HorillaCardView(ListView): if value[0] in ["unknown", "on"] + self.filter_keys_to_remove ] - for key in keys_to_remove: - data_dict.pop(key) + for key in keys_to_remove + ["referrer"]: + if key in data_dict.keys(): + data_dict.pop(key) context["filter_dict"] = data_dict - cache[self.request.session.session_key][HorillaCardView] = context + ordered_ids = list(queryset.values_list("id", flat=True)) + if not self._saved_filters.get("field"): + for instance in queryset: + instance.ordered_ids = ordered_ids + ordered_ids.append(instance.pk) + + CACHE.get(self.request.session.session_key + "cbv")[HorillaCardView] = context + referrer = self.request.GET.get("referrer", "") + if referrer: + # Remove the protocol and domain part + referrer = "/" + "/".join(referrer.split("/")[3:]) + context["stored_filters"] = ( + models.SavedFilter.objects.filter( + path=self.request.path, created_by=self.request.user + ) + | models.SavedFilter.objects.filter( + referrer=referrer, created_by=self.request.user + ) + ).distinct() return context @@ -504,9 +615,6 @@ class ReloadMessages(TemplateView): return context -dynamic_create_cache = {} - - def save(self: forms.ModelForm, commit=True, *args, **kwargs): """ This method is used to super save the form using custom logic @@ -518,28 +626,35 @@ def save(self: forms.ModelForm, commit=True, *args, **kwargs): if commit: response = super(type(self), self).save(*args, **kwargs) new_isntance_pk = self.instance.pk - dynamic_create_cache[request.session.session_key + dynamic_field] = { - "dynamic_field": dynamic_field, - "value": new_isntance_pk, - "form": self, - } + CACHE.set( + request.session.session_key + "cbv" + dynamic_field, + { + "dynamic_field": dynamic_field, + "value": new_isntance_pk, + "model": self._meta.model, + }, + ) return response -from django.views.generic import UpdateView - - class HorillaFormView(FormView): """ HorillaFormView """ class HttpResponse: + """ + HttpResponse + """ + def __new__( self, content: str = "", targets_to_reload: list = [], script: str = "" ) -> HttpResponse: + """ + __new__ method + """ targets_to_reload = list(set(targets_to_reload)) - targets_to_reload.append("#reloadMessagesButton") + # targets_to_reload.append("#reloadMessagesButton") script_id = get_short_uuid(4) script = ( f" {% if bulk_select_option %}
    {% trans "Select All" %} ({{queryset.paginator.count}}) @@ -17,9 +22,9 @@ class="oh-checkpoint-badge text-secondary d-none" style="cursor: pointer;" onclick=" - $('#selectedInstances').attr('data-ids',JSON.stringify([])); - selectSelected('#{{view_id|safe}}'); - reloadSelectedCount($('#count_{{view_id|safe}}')); + $('#{{selected_instances_key_id}}').attr('data-ids',JSON.stringify([])); + selectSelected('#{{view_id|safe}}','{{selected_instances_key_id}}'); + reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}'); $('#{{view_id}} .list-table-row').prop('checked',false); $('#{{view_id}} .highlight-selected').removeClass('highlight-selected'); $('#{{view_id}} .bulk-list-table-row').prop('checked',false); @@ -39,12 +44,31 @@ class="oh-checkpoint-badge text-info d-none" style="cursor: pointer;" onclick=" - selectedIds = $('#selectedInstances').attr('data-ids') + selectedIds = $('#{{selected_instances_key_id}}').attr('data-ids') window.location.href = '{{export_path}}' + '?ids='+selectedIds " > {% trans "Export" %}
    + {% for filter in stored_filters %} +
    + +
    + + + +
    +
    + {% endfor %}
    {% if row_status_indications %}
    @@ -73,7 +97,6 @@ onclick=" event.stopPropagation() $(this).parent().find('.oh-accordion-meta__body').toggleClass('d-none') - $(this).toggleClass('oh-accordion-meta__header--show') " > @@ -112,7 +135,7 @@ onchange=" $(this).closest('.oh-sticky-table').find('.list-table-row').prop('checked',$(this).is(':checked')).change(); $(document).ready(function () { - reloadSelectedCount($('#count_{{view_id|safe}}')); + reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}'); }); " title="Select All" @@ -163,7 +186,7 @@ if (!element.is(':checked')) { removeId(element) } - reloadSelectedCount($('#count_{{view_id|safe}}')); + reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}'); }); " value = "{{instance.pk}}" @@ -176,7 +199,7 @@
    - {{instance|getattribute:attribute|safe}} + {{instance|getattribute:attribute|selected_format:request.user.employee_get.employee_work_info.company_id|safe}}
    {% else %}
    ids = JSON.parse( - $("#selectedInstances").attr("data-ids") || "[]" + $("#{{selected_instances_key_id}}").attr("data-ids") || "[]" ); $.each(ids, function (indexInArray, valueOfElement) { $(`#{{view_id|safe}} .oh-sticky-table__tbody .list-table-row[value=${valueOfElement}]`).prop("checked",true).change() @@ -372,7 +395,7 @@ ) { id = $(this).val() ids = JSON.parse( - $("#selectedInstances").attr("data-ids") || "[]" + $("#{{selected_instances_key_id}}").attr("data-ids") || "[]" ); ids = Array.from(new Set(ids)); let index = ids.indexOf(id); @@ -383,7 +406,7 @@ ids.splice(index, 1); } } - $("#selectedInstances").attr("data-ids", JSON.stringify(ids)); + $("#{{selected_instances_key_id}}").attr("data-ids", JSON.stringify(ids)); } ); @@ -395,6 +418,11 @@ $(".oh-accordion-meta__header").click(function (e) { var open = $(this).attr("data-open"); open = JSON.parse(open) + if (!$(this).parent().parent().find(".oh-accordion-meta__body.d-none").length && !$(this).find(".oh-accordion-meta__header--show").length) { + $(this).removeClass("oh-accordion-meta__header--show"); + }else{ + $(this).addClass("oh-accordion-meta__header--show"); + } $(this).attr("data-open", !open); var field = $(this).attr("data-field"); var groupIndex = $(this).attr("data-group"); diff --git a/horilla_views/templates/generic/horilla_card.html b/horilla_views/templates/generic/horilla_card.html index 65e934009..9c9b614a7 100644 --- a/horilla_views/templates/generic/horilla_card.html +++ b/horilla_views/templates/generic/horilla_card.html @@ -1,16 +1,42 @@ {% load static i18n generic_template_filters %}
    - {% if queryset|length %} - {% if card_status_indications %} -
    - {% for indication in card_status_indications %} - - - {{indication.1}} - - {% endfor %} + +
    +
    + {% for filter in stored_filters %} +
    + +
    + + + +
    +
    + {% endfor %} +
    + {% if card_status_indications %} +
    + {% for indication in card_status_indications %} + + + {{indication.1}} + + {% endfor %} +
    - {% endif %} {% if show_filter_tags %} @@ -29,8 +55,8 @@
    - {{details.title|format:instance|safe}} - {{details.subtitle|format:instance|safe}} + {{details.title|format:instance|selected_format:request.user.employee_get.employee_work_info.company_id|safe}} + {{details.subtitle|format:instance|selected_format:request.user.employee_get.employee_work_info.company_id|safe}}
    {% if actions %}
    diff --git a/horilla_views/templates/generic/horilla_detailed_view.html b/horilla_views/templates/generic/horilla_detailed_view.html index c56d8b8fb..4b1ebbc6d 100644 --- a/horilla_views/templates/generic/horilla_detailed_view.html +++ b/horilla_views/templates/generic/horilla_detailed_view.html @@ -48,13 +48,13 @@
    - {{object|getattribute:header.title}} + {{object|getattribute:header.title|selected_format:request.user.employee_get.employee_work_info.company_id}} - {{object|getattribute:header.subtitle}}
    @@ -67,12 +67,16 @@
    {% for col in body %}
    + {% if not col.2 %}
    {{col.0}} {{object|getattribute:col.1|safe}}{{object|getattribute:col.1|selected_format:request.user.employee_get.employee_work_info.company_id|safe}}
    + {% else %} + {{object|getattribute:col.1|safe}} + {% endif %}
    {% endfor %}
    diff --git a/horilla_views/templates/generic/horilla_form.html b/horilla_views/templates/generic/horilla_form.html index d105ab397..01a366123 100644 --- a/horilla_views/templates/generic/horilla_form.html +++ b/horilla_views/templates/generic/horilla_form.html @@ -12,12 +12,12 @@ >
    {% endfor %} -
    {{form.structured}}
    +
    {{form.structured}}
    {% for field_tuple in dynamic_create_fields %} {% if show_filter_tags %} {% include "generic/filter_tags.html" %} {% endif %} @@ -9,9 +15,9 @@
    {% trans "Select All" %} ({{queryset.paginator.count}}) @@ -21,9 +27,9 @@ class="oh-checkpoint-badge text-secondary d-none" style="cursor: pointer;" onclick=" - $('#selectedInstances').attr('data-ids',JSON.stringify([])); - selectSelected('#{{view_id|safe}}'); - reloadSelectedCount($('#count_{{view_id|safe}}')); + $('#{{selected_instances_key_id}}').attr('data-ids',JSON.stringify([])); + selectSelected('#{{view_id|safe}}','{{selected_instances_key_id}}'); + reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}'); $('#{{view_id}} .list-table-row').prop('checked',false); $('#{{view_id}} .highlight-selected').removeClass('highlight-selected'); $('#{{view_id}} .bulk-list-table-row').prop('checked',false); @@ -42,13 +48,31 @@ id="export_{{view_id}}" class="oh-checkpoint-badge text-info d-none" style="cursor: pointer;" - onclick=" - selectedIds = $('#selectedInstances').attr('data-ids') - window.location.href = '/{{export_path}}' + '?ids='+selectedIds - " + data-toggle="oh-modal-toggle" + data-target="#exportFields{{view_id|safe}}" > {% trans "Export" %}
    + {% for filter in stored_filters %} +
    + +
    + + + +
    +
    + {% endfor %} +
    {% if row_status_indications %}
    @@ -108,7 +132,7 @@ onchange=" $(this).closest('.oh-sticky-table').find('.list-table-row').prop('checked',$(this).is(':checked')).change(); $(document).ready(function () { - reloadSelectedCount($('#count_{{view_id|safe}}')); + reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}'); }); " title="Select All" @@ -183,7 +207,7 @@ if (!element.is(':checked')) { removeId(element) } - reloadSelectedCount($('#count_{{view_id|safe}}')); + reloadSelectedCount($('#count_{{view_id|safe}}'),'{{selected_instances_key_id}}'); }); " value = "{{instance.pk}}" @@ -195,7 +219,7 @@
    - {{instance|getattribute:attribute|safe}} + {{instance|getattribute:attribute|selected_format:request.user.employee_get.employee_work_info.company_id|safe}}
    {% else %}
    {% if bulk_select_option %} {% endif %} {% endif %} -
    + {% endif %}
  • {% endfor %} diff --git a/horilla_views/templates/generic/horilla_profile_view.html b/horilla_views/templates/generic/horilla_profile_view.html new file mode 100644 index 000000000..05d91de12 --- /dev/null +++ b/horilla_views/templates/generic/horilla_profile_view.html @@ -0,0 +1,301 @@ +{% load static i18n generic_template_filters %} +
    + +
    +
    +
    +
      + {% for object in instances|slice:"6" %} +
    • + + + +
    • + {% endfor %} + {% if display_count %} +
    • + {{display_count}} + +
    • + {% endif %} + +
    +
    + +
    + + {% if actions %} +
    + +
    + +
    +
    + {% endif %} +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +

    {{instance}}

    +

    {{instance.get_subtitle}}

    +
    +
    +
    +
    +
      +
    • + + + {% trans "E-mail" %}: + + {{instance.get_email}} +
    • +
    • + + + {% trans "Phone" %}: + + {{instance.get_contact}} +
    • +
    +
    +
    +
    +
    +
    +
    + +
    + {{toggle_form.as_list}} +
    +
    +
    +
      + {% for tab in visible_tabs %} {% if not tab.accessibility or tab.accessibility|accessibility:instance %} +
    • + {{tab.title}} +
    • + {% endif %} {% endfor %} +
    + {% for tab in visible_tabs %} +
    + {{tab.title}} +
    + {% endfor %} +
    +
    + + + + +
    diff --git a/horilla_views/templates/generic/horilla_tabs.html b/horilla_views/templates/generic/horilla_tabs.html index b62a9d22c..d73c83be0 100644 --- a/horilla_views/templates/generic/horilla_tabs.html +++ b/horilla_views/templates/generic/horilla_tabs.html @@ -4,7 +4,11 @@ {% comment %} {% include "attendance/attendance/attendance_nav.html" %} {% endcomment %} {% load i18n generic_template_filters %} -
    +
    + +
      {% for tab in tabs %}
    • + +
    +{% endblock content %} \ No newline at end of file diff --git a/horilla_views/templatetags/generic_template_filters.py b/horilla_views/templatetags/generic_template_filters.py index 06634e092..b127c8772 100644 --- a/horilla_views/templatetags/generic_template_filters.py +++ b/horilla_views/templatetags/generic_template_filters.py @@ -1,10 +1,11 @@ """ -horillafilters.py +attendancefilters.py This module is used to write custom template filters. """ +import datetime import re import types @@ -21,6 +22,38 @@ register = template.Library() numeric_test = re.compile("^\d+$") +date_format_mapping = { + "DD-MM-YYYY": "%d-%m-%Y", + "DD.MM.YYYY": "%d.%m.%Y", + "DD/MM/YYYY": "%d/%m/%Y", + "MM/DD/YYYY": "%m/%d/%Y", + "YYYY-MM-DD": "%Y-%m-%d", + "YYYY/MM/DD": "%Y/%m/%d", + "MMMM D, YYYY": "%B %d, %Y", + "DD MMMM, YYYY": "%d %B, %Y", + "MMM. D, YYYY": "%b. %d, %Y", + "D MMM. YYYY": "%d %b. %Y", + "dddd, MMMM D, YYYY": "%A, %B %d, %Y", +} + +time_format_mapping = { + "hh:mm A": "%I:%M %p", + "HH:mm": "%H:%M", +} + + +@register.filter(name="selected_format") +def selected_format(date: datetime.date, company: object = None) -> str: + if company and (company.date_format or company.time_format): + if isinstance(date, datetime.date): + format = company.date_format + date_format_mapping.get(format) + return date.strftime(date_format_mapping[format]) + elif isinstance(date, datetime.time): + format = company.time_format + return date.strftime(time_format_mapping[format]) + return date + @register.filter(name="getattribute") def getattribute(value, attr: str): @@ -77,3 +110,13 @@ def accessibility(method: str, instance=None): PermWrapper(request.user), ) return True + + +@register.filter("col") +def col(field: object): + """ + Method to get field col sepration + """ + field_name = field.name + cols = getattr(field.form, "cols", {}) + return cols.get(field_name, 6) diff --git a/horilla_views/urls.py b/horilla_views/urls.py index f429f68e0..fddc6c858 100644 --- a/horilla_views/urls.py +++ b/horilla_views/urls.py @@ -13,4 +13,25 @@ urlpatterns = [ path("active-group", views.ActiveGroup.as_view(), name="cbv-active-group"), path("reload-field", views.ReloadField.as_view(), name="reload-field"), path("reload-messages", ReloadMessages.as_view(), name="reload-messages"), + path("saved-filter/", views.SavedFilter.as_view(), name="saved-filter"), + path( + "saved-filter//", + views.SavedFilter.as_view(), + name="saved-filter-update", + ), + path( + "delete-saved-filter//", + views.DeleteSavedFilter.as_view(), + name="delete-saved-filter", + ), + path( + "active-hnv-view-type/", + views.ActiveView.as_view(), + name="active-hnv-view-type", + ), + path( + "search-in-instance-ids", + views.SearchInIds.as_view(), + name="search-in-instance-ids", + ), ] diff --git a/horilla_views/views.py b/horilla_views/views.py index 9e21fbb86..e0b2c6350 100644 --- a/horilla_views/views.py +++ b/horilla_views/views.py @@ -1,17 +1,23 @@ import importlib from django import forms +from django.contrib import messages +from django.core.cache import cache as CACHE from django.http import HttpResponse, JsonResponse from django.shortcuts import render +from django.utils.decorators import method_decorator from django.views import View +from django.views.decorators.csrf import csrf_protect from horilla_views import models -from horilla_views.cbv_methods import get_short_uuid -from horilla_views.generic.cbv.views import dynamic_create_cache +from horilla_views.cbv_methods import get_short_uuid, login_required +from horilla_views.forms import SavedFilterForm +from horilla_views.generic.cbv.views import HorillaFormView # Create your views here. +@method_decorator(login_required, name="dispatch") class ToggleColumn(View): """ ToggleColumn @@ -42,6 +48,7 @@ class ToggleColumn(View): return HttpResponse("success") +@method_decorator(login_required, name="dispatch") class ReloadField(View): """ ReloadField @@ -58,17 +65,15 @@ class ReloadField(View): module = importlib.import_module(module_name) parent_form = getattr(module, class_name)() - dynamic_cache = dynamic_create_cache.get( - request.session.session_key + reload_field - ) - form: forms.ModelForm = dynamic_cache["form"] + dynamic_cache = CACHE.get(request.session.session_key + "cbv" + reload_field) + model: models.HorillaModel = dynamic_cache["model"] cache_field = dynamic_cache["dynamic_field"] if cache_field != reload_field: cache_field = reload_field field = parent_form.fields[cache_field] - queryset = form._meta.model.objects.all() + queryset = model.objects.all() queryset = field.queryset choices = [(instance.id, instance) for instance in queryset] choices.insert(0, ("", "Select option")) @@ -91,6 +96,7 @@ class ReloadField(View): ) +@method_decorator(login_required, name="dispatch") class ActiveTab(View): def get(self, *args, **kwargs): """ @@ -112,6 +118,7 @@ class ActiveTab(View): return JsonResponse({"message": "Success"}) +@method_decorator(login_required, name="dispatch") class ActiveGroup(View): def get(self, *args, **kwargs): """ @@ -135,3 +142,91 @@ class ActiveGroup(View): instance.group_target = target instance.save() return JsonResponse({"message": "Success"}) + + +@method_decorator(login_required, name="dispatch") +class SavedFilter(HorillaFormView): + """ + SavedFilter + """ + + model = models.SavedFilter + form_class = SavedFilterForm + new_display_title = "Save Applied Filter" + template_name = "generic/saved_filter_form.html" + form_disaply_attr = "Blah" + + def form_valid(self, form: SavedFilterForm) -> HttpResponse: + referrer = self.request.POST.get("referrer", "") + path = self.request.POST.get("path", "/") + result_dict = {key: value[0] for key, value in self.request.GET.lists()} + if form.is_valid(): + instance: models.SavedFilter = form.save(commit=False) + if not instance.pk: + instance.path = path + instance.referrer = referrer + instance.filter = result_dict + instance.urlencode = self.request.GET.urlencode() + instance.save() + messages.success(self.request, "Filter Saved") + return self.HttpResponse() + return super().form_valid(form) + + def get_context_data(self, **kwargs) -> dict: + context = super().get_context_data(**kwargs) + referrer = self.request.GET.get("referrer", "") + if referrer: + # Remove the protocol and domain part + referrer = "/" + "/".join(referrer.split("/")[3:]) + context["path"] = self.request.GET.get("path", "") + context["referrer"] = referrer + return context + + +@method_decorator(login_required, name="dispatch") +class DeleteSavedFilter(View): + """ + Delete saved filter + """ + + def get(self, *args, **kwargs): + pk = kwargs["pk"] + models.SavedFilter.objects.filter(created_by=self.request.user, pk=pk).delete() + return HttpResponse("") + + +@method_decorator(login_required, name="dispatch") +class ActiveView(View): + """ + ActiveView CBV + """ + + def get(self, *args, **kwargs): + path = self.request.GET["path"] + view_type = self.request.GET["view"] + active_view = models.ActiveView.objects.filter( + path=path, created_by=self.request.user + ).first() + + active_view = active_view if active_view else models.ActiveView() + active_view.path = path + active_view.type = view_type + active_view.save() + return HttpResponse("") + + +@method_decorator(login_required, name="dispatch") +@method_decorator(csrf_protect, name="dispatch") +class SearchInIds(View): + """ + Search in ids view + """ + + def get(self, *args, **kwargs): + """ + Search in instance ids method + """ + cache_key = f"{self.request.session.session_key}search_in_instance_ids" + context: dict = CACHE.get(cache_key) + context["instances"] = context["filter_class"](self.request.GET).qs + return render(self.request, "generic/filter_result.html", context)