275 lines
9.8 KiB
Python
275 lines
9.8 KiB
Python
"""
|
|
horilla_views/forms.py
|
|
"""
|
|
|
|
import os
|
|
|
|
from django import forms
|
|
from django.core.files.base import ContentFile
|
|
from django.core.files.storage import default_storage
|
|
from django.db import transaction
|
|
from django.template.loader import render_to_string
|
|
from django.utils.safestring import SafeText
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from horilla.horilla_middlewares import _thread_locals
|
|
from horilla_views import models
|
|
from horilla_views.cbv_methods import (
|
|
BOOLEAN_CHOICES,
|
|
FIELD_WIDGET_MAP,
|
|
MODEL_FORM_FIELD_MAP,
|
|
get_field_class_map,
|
|
get_original_model_field,
|
|
structured,
|
|
)
|
|
from horilla_views.templatetags.generic_template_filters import getattribute
|
|
|
|
|
|
class ToggleColumnForm(forms.Form):
|
|
"""
|
|
Toggle column form
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
columns,
|
|
default_columns,
|
|
hidden_fields: list,
|
|
*args,
|
|
**kwargs,
|
|
):
|
|
request = getattr(_thread_locals, "request", {})
|
|
self.request = request
|
|
super().__init__(*args, **kwargs)
|
|
for column in columns:
|
|
initial = True
|
|
if column[1] in hidden_fields:
|
|
initial = False
|
|
if not hidden_fields:
|
|
if default_columns and column not in default_columns:
|
|
initial = False
|
|
self.fields[column[1]] = forms.BooleanField(
|
|
label=column[0], initial=initial
|
|
)
|
|
|
|
def as_list(self) -> SafeText:
|
|
"""
|
|
Render the form fields as HTML table rows with.
|
|
"""
|
|
context = {"form": self, "request": self.request}
|
|
table_html = render_to_string("generic/as_list.html", context)
|
|
return table_html
|
|
|
|
|
|
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
|
|
|
|
|
|
class DynamicBulkUpdateForm(forms.Form):
|
|
"""
|
|
DynamicBulkUpdateForm
|
|
"""
|
|
|
|
verbose_name = _("Bulk Update")
|
|
|
|
def __init__(
|
|
self,
|
|
*args,
|
|
root_model: models.models.Model = None,
|
|
bulk_update_fields: list = [],
|
|
ids: list = [],
|
|
**kwargs,
|
|
):
|
|
self.ids = ids
|
|
self.root_model = root_model
|
|
self.bulk_update_fields = sorted(
|
|
bulk_update_fields, key=lambda x: x.count("__")
|
|
)
|
|
self.structured = structured
|
|
mappings = get_field_class_map(root_model, bulk_update_fields)
|
|
self.request = getattribute(_thread_locals, "request")
|
|
|
|
super().__init__(*args, **kwargs)
|
|
for key, val in mappings.items():
|
|
widget = FIELD_WIDGET_MAP.get(type(val))
|
|
field = MODEL_FORM_FIELD_MAP.get(type(val))
|
|
if widget and field:
|
|
if isinstance(val, models.models.BooleanField):
|
|
self.fields[key] = forms.ChoiceField(
|
|
choices=BOOLEAN_CHOICES,
|
|
widget=widget,
|
|
label=val.verbose_name.capitalize(),
|
|
required=False,
|
|
)
|
|
self.fields[key].widget.option_template_name = (
|
|
"horilla_widgets/select_option.html",
|
|
)
|
|
continue
|
|
elif not getattribute(val, "related_model"):
|
|
if isinstance(val, models.models.CharField) and val.choices:
|
|
self.fields[key] = forms.ChoiceField(
|
|
choices=[("", "--------")]
|
|
+ [choice for choice in val.choices if choice[0] != ""],
|
|
widget=forms.Select(
|
|
attrs={"class": "oh-select oh-select-2 w-100"}
|
|
),
|
|
label=val.verbose_name.capitalize(),
|
|
required=False,
|
|
)
|
|
self.fields[key].widget.option_template_name = (
|
|
"horilla_widgets/select_option.html",
|
|
)
|
|
continue
|
|
self.fields[key] = field(
|
|
widget=widget,
|
|
label=val.verbose_name.capitalize(),
|
|
required=False,
|
|
)
|
|
self.fields[key].widget.option_template_name = (
|
|
"horilla_widgets/select_option.html",
|
|
)
|
|
continue
|
|
queryset = val.related_model.objects.all()
|
|
self.fields[key] = field(
|
|
widget=widget,
|
|
queryset=queryset,
|
|
label=val.verbose_name,
|
|
required=False,
|
|
)
|
|
self.fields[key].widget.option_template_name = (
|
|
"horilla_widgets/select_option.html",
|
|
)
|
|
|
|
def is_valid(self):
|
|
valid = True
|
|
try:
|
|
with transaction.atomic():
|
|
# Perform bulk update
|
|
self.save()
|
|
# Simulate error check
|
|
raise Exception("no_errors")
|
|
except Exception as e:
|
|
# Handle errors or validation issues
|
|
if not "no_errors" in str(e):
|
|
valid = False
|
|
self.add_error(None, f"Form not valid: {str(e)}")
|
|
return valid
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""
|
|
Bulk save method
|
|
"""
|
|
mappings = get_field_class_map(self.root_model, self.bulk_update_fields)
|
|
data_mapp = {}
|
|
data_m2m_mapp = {}
|
|
relation_mapp = {}
|
|
map_queryset = {}
|
|
fiels_mapping = {}
|
|
parent_model = self.root_model
|
|
for key, val in mappings.items():
|
|
if val.model.__name__.startswith("Historical"):
|
|
val.model = get_original_model_field(val.model)
|
|
field = MODEL_FORM_FIELD_MAP.get(type(val))
|
|
if field:
|
|
if not fiels_mapping.get(val.model):
|
|
fiels_mapping[val.model] = {}
|
|
if not data_m2m_mapp.get(val.model):
|
|
data_m2m_mapp[val.model] = {}
|
|
if not data_mapp.get(val.model):
|
|
data_mapp[val.model] = {}
|
|
if not relation_mapp.get(val.model):
|
|
if val.model == self.root_model:
|
|
relation_mapp[val.model] = "id__in"
|
|
else:
|
|
related_key = key.split("__")[-2]
|
|
field = getattribute(parent_model, related_key)
|
|
if not hasattr(field, "related"):
|
|
continue
|
|
relation_mapp[val.model] = (
|
|
field.related.field.name
|
|
+ "__"
|
|
+ relation_mapp[parent_model]
|
|
)
|
|
parent_model = val.model
|
|
files = self.files.getlist(key)
|
|
value = self.data.getlist(key)
|
|
if (not value or not value[0]) and not files:
|
|
continue
|
|
key = key.split("__")[-1]
|
|
model_field = getattribute(val.model, key).field
|
|
if isinstance(model_field, models.models.ManyToManyField):
|
|
data_m2m_mapp[val.model][key] = value
|
|
continue
|
|
if files and isinstance(
|
|
model_field,
|
|
(
|
|
models.models.FileField,
|
|
models.models.ImageField,
|
|
),
|
|
):
|
|
file_path = os.path.join(model_field.upload_to, files[0].name)
|
|
|
|
data_mapp[val.model][key] = file_path
|
|
fiels_mapping[val.model][model_field] = files[0]
|
|
continue
|
|
data_mapp[val.model][key] = value[0]
|
|
|
|
for model, data in data_mapp.items():
|
|
if not model in relation_mapp:
|
|
continue
|
|
queryset = model.objects.filter(**{relation_mapp[model]: self.ids})
|
|
# here fields, files, and related fields-
|
|
# get updated but need to save the files manually
|
|
queryset.update(**data)
|
|
map_queryset[model] = queryset
|
|
m2m_data = data_m2m_mapp[model]
|
|
# saving m2m
|
|
if m2m_data:
|
|
for field, ids in m2m_data.items():
|
|
related_objects = getattr(
|
|
model, field
|
|
).field.related_model.objects.filter(id__in=ids)
|
|
for instance in queryset:
|
|
getattr(instance, field).set(related_objects)
|
|
for model, files in fiels_mapping.items():
|
|
if files:
|
|
for field, file in files.items():
|
|
file_path = os.path.join(field.upload_to, file.name)
|
|
default_storage.save(file_path, ContentFile(file.read()))
|