[ADD] DYNAMIC FIELDS: Add new app - dynamic fields
This commit is contained in:
180
dynamic_fields/models.py
Normal file
180
dynamic_fields/models.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import logging, re
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.core.management import call_command
|
||||
from dynamic_fields.df_not_allowed_models import DF_NOT_ALLOWED_MODELS
|
||||
from horilla.horilla_middlewares import _thread_locals
|
||||
|
||||
|
||||
from horilla_automations.methods.methods import get_model_class
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Create your models here.
|
||||
FIELD_MAP = {
|
||||
"1": models.CharField,
|
||||
"2": models.IntegerField,
|
||||
"3": models.TextField,
|
||||
"4": models.DateField,
|
||||
"5": models.FileField,
|
||||
}
|
||||
|
||||
# Define the additional arguments for specific fields
|
||||
ARGS = {
|
||||
"1": {"max_length": 30, "default": None},
|
||||
"2": {"default": 0},
|
||||
"3": {"default": None},
|
||||
"4": {"default": timezone.now},
|
||||
"5": {"null": True, "upload_to": "media/dynamic_fields"},
|
||||
}
|
||||
|
||||
|
||||
TYPE = (
|
||||
("1", "Character field"),
|
||||
("2", "Integer field"),
|
||||
("3", "Text field"),
|
||||
("4", "Date field"),
|
||||
("5", "File field"),
|
||||
)
|
||||
|
||||
|
||||
class Choice(models.Model):
|
||||
"""
|
||||
Choice
|
||||
"""
|
||||
|
||||
title = models.CharField(max_length=25)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class DynamicField(models.Model):
|
||||
"""
|
||||
DynamicFields
|
||||
"""
|
||||
|
||||
model = models.CharField(max_length=100)
|
||||
verbose_name = models.CharField(max_length=30)
|
||||
field_name = models.CharField(max_length=30, editable=False)
|
||||
type = models.CharField(max_length=50, choices=TYPE)
|
||||
choices = models.ManyToManyField(Choice, blank=True)
|
||||
is_required = models.BooleanField(default=False)
|
||||
remove_column = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to additional options
|
||||
"""
|
||||
|
||||
unique_together = ("model", "field_name")
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
self.remove_column = True
|
||||
self.save()
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.field_name} | {self.model}"
|
||||
|
||||
def get_field(self):
|
||||
"""
|
||||
Field generate method
|
||||
"""
|
||||
|
||||
def _args(key):
|
||||
args = ARGS.get(key, {})
|
||||
args["blank"] = not self.is_required
|
||||
args["verbose_name"] = self.verbose_name
|
||||
args["null"] = True
|
||||
return args
|
||||
|
||||
field_object: models.CharField = {
|
||||
key: FIELD_MAP[key](**_args(key)) for key in FIELD_MAP
|
||||
}[self.type]
|
||||
if self.choices.exists() and self.type == "1":
|
||||
choices = [(choice.pk, choice.title) for choice in self.choices.all()]
|
||||
field_object.choices = choices
|
||||
field_object.remove_column = self.remove_column
|
||||
return field_object
|
||||
|
||||
def get_model(self):
|
||||
"""
|
||||
method to get the model
|
||||
"""
|
||||
return get_model_class(self.model)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Clean method to write the validations
|
||||
"""
|
||||
if not re.match(r"^[a-zA-Z]+( [a-zA-Z]+)*$", self.verbose_name):
|
||||
raise forms.ValidationError(
|
||||
{
|
||||
"verbose_name": _(
|
||||
"Name can only contain alphabetic characters,\
|
||||
and multiple spaces are not allowed."
|
||||
)
|
||||
}
|
||||
)
|
||||
field_name = "hdf_" + self.verbose_name.lower().replace(" ", "_")
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
model = self.model
|
||||
if not model and request:
|
||||
model = request.GET.get("df_model_path", "")
|
||||
if model:
|
||||
records = DynamicField.objects.filter(model=model).values_list(
|
||||
"field_name", flat=True
|
||||
)
|
||||
if not self.pk and field_name in records:
|
||||
raise forms.ValidationError(
|
||||
{"verbose_name": _("Please enter different name")}
|
||||
)
|
||||
elif field_name in records.exclude(pk=self.id):
|
||||
raise forms.ValidationError(
|
||||
{"verbose_name": _("Please enter different name")}
|
||||
)
|
||||
|
||||
return super().clean()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# instance = self
|
||||
is_create = self.pk is None
|
||||
# hdf -> horilla_dynamic_field
|
||||
field_name = "hdf_" + self.verbose_name.lower().replace(" ", "_")
|
||||
|
||||
if is_create:
|
||||
self.field_name = field_name
|
||||
super().save(*args, **kwargs)
|
||||
call_command("add_field", *(self.pk,))
|
||||
|
||||
else:
|
||||
instance = DynamicField.objects.get(pk=self.pk)
|
||||
model = instance.get_model()
|
||||
field = instance.get_field()
|
||||
if self.remove_column:
|
||||
try:
|
||||
field_to_remove = instance.field_name
|
||||
if hasattr(model, field_to_remove):
|
||||
# Dynamically remove the field
|
||||
model._meta.local_fields = [
|
||||
field
|
||||
for field in model._meta.local_fields
|
||||
if field.name != field_to_remove
|
||||
]
|
||||
setattr(model, instance.field_name, None)
|
||||
|
||||
logger.info(f"Field '{field_to_remove}' removed successfully.")
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
super().save(*args, **kwargs)
|
||||
return self
|
||||
|
||||
|
||||
DF_NOT_ALLOWED_MODELS += [
|
||||
DynamicField,
|
||||
]
|
||||
Reference in New Issue
Block a user