classFieldDefinition(AbstractField):"""This class is the equivalent django.forms.Field class, used to create reusable field types"""field_type=StrategyClassField(registry=field_registry,null=False,blank=False)content_type=models.ForeignKey(ContentType,on_delete=models.CASCADE,null=True,blank=True)system_data=models.JSONField(default=dict,blank=True,editable=False,null=True)attributes_strategy=StrategyField(registry=attributes_registry,default=fqn(DefaultAttributeHandler),help_text="Strategy to use for attributes retrieval",)strategy_config=models.JSONField(default=dict,blank=True,editable=False,null=True)validated=models.BooleanField(default=False,blank=True)objects=FieldDefinitionManager()classMeta:verbose_name=_("Field Definition")verbose_name_plural=_("Field Definitions")constraints=(UniqueConstraint(fields=("name",),name="fielddefinition_unique_name"),UniqueConstraint(fields=("slug",),name="fielddefinition_unique_slug"),)def__str__(self):returnself.name@propertydefattributes(self):base=self.attrstry:returnbase|self.attributes_strategy.get()exceptException:self.validated=Falsereturnbase@attributes.setterdefattributes(self,value):self.attributes_strategy.set(value)defget_attributes(self,instance:"FlexField"):returnself.attributes_strategy.get(instance)defnatural_key(self):return(self.name,)defclean(self):self.name=str(self.name)try:self.get_field()exceptTypeError:raiseValidationError("Field definition cannot be validated")defsave(self,*args,force_insert=False,force_update=False,using=None,update_fields=None,):self.attrs=self.get_default_attributes()|self.attrsifnotupdate_fields:self.validated=Falseelif"validated"inupdate_fields:passsuper().save(*args,force_insert=force_insert,force_update=force_update,using=using,update_fields=update_fields,)defget_default_attributes(self):attrs=get_common_attrs()ifself.field_type:returnattrs|get_kwargs_from_field_class(self.field_type)returnattrsdefset_default_attributes(self):ifself.field_type:attrs=self.get_default_attributes()self.attributes=attrselifnotisinstance(self.attrs,dict)ornotself.attrs:self.attributes=get_common_attrs()@propertydefrequired(self):returnself.attributes.get("required",False)defget_field(self,override_attrs=None):try:ifoverride_attrsisnotNone:kwargs=dict(override_attrs)else:kwargs=dict(self.attributes)validators=[]ifself.validation:validators.append(JsValidator(self.validation))ifself.regex:validators.append(ReValidator(self.regex))kwargs["validators"]=validatorsfield_class=type(f"{self.name}Field",(FlexFormMixin,self.field_type),{})fld=field_class(**kwargs)exceptExceptionase:# pragma: no coverlogger.exception(e)raiseTypeError(f"Error creating field for FieldDefinition {self.name}: {e}")returnfld
hope_flex_fields.models.Fieldset
hope_flex_fields.models.FlexField
Source code in src/hope_flex_fields/models/flexfield.py
classFlexField(AbstractField):fieldset=models.ForeignKey(Fieldset,on_delete=models.CASCADE,related_name="fields")definition=models.ForeignKey(FieldDefinition,on_delete=models.CASCADE,related_name="instances")master=models.ForeignKey("self",blank=True,null=True,on_delete=models.CASCADE,related_name="+")objects=FieldsetFieldManager()classMeta:verbose_name=_("Flex Field")verbose_name_plural=_("flex Fields")constraints=(UniqueConstraint(fields=("name","fieldset"),name="flexfield_unique_name"),UniqueConstraint(fields=("slug","fieldset"),name="flexfield_unique_slug"),)def__str__(self):returnself.name@propertydefattributes(self):returnself.attrs@attributes.setterdefattributes(self,value):self.attrs=value@propertydefdependants(self):returnFlexField.objects.filter(master=self)defbase_type(self):returnself.definition.field_type.__name__defvalidate_attrs(self):try:self.get_field()exceptExceptionase:raiseValidationError(e)defclean(self):self.name=namefy(str(self.name))self.validate_attrs()defnatural_key(self):returnself.name,self.fieldset.namedefget_merged_attrs(self):attrs=dict(**self.definition.get_attributes(self))ifisinstance(self.attributes,dict):attrs.update(self.attrs)returnattrsdefget_field(self,override_attrs=None,**extra)->"FlexFormMixin":try:ifoverride_attrsisnotNone:kwargs=dict(override_attrs)else:kwargs=self.get_merged_attrs()kwargs.update(extra)validators=[]ifself.validation:validators.append(JsValidator(self.validation))elifself.definition.validation:validators.append(JsValidator(self.definition.validation))ifself.regex:validators.append(ReValidator(self.regex))elifself.definition.regex:validators.append(ReValidator(self.definition.regex))kwargs["validators"]=validatorsfield_class=type(f"{self.name}Field",(FlexFormMixin,self.definition.field_type),{"flex_field":self},)fld=field_class(**kwargs)ifself.definition.attributes_strategy.validators:fld.validators.extend([v(fld)forvinself.definition.attributes_strategy.validators])exceptExceptionase:# pragma: no coverraiseFlexFieldCreationError(f"Error creating field for FlexField {self.name}: {e}")returnfld
hope_flex_fields.models.DataChecker
Used for complex validations to combine different fieldsets
Source code in src/hope_flex_fields/models/datachecker.py
classDataChecker(ValidatorMixin,models.Model):"""Used for complex validations to combine different fieldsets"""last_modified=models.DateTimeField(auto_now=True)name=models.CharField(max_length=255,unique=True)description=models.TextField(blank=True)fieldsets=models.ManyToManyField(Fieldset,through=DataCheckerFieldset)objects=DataCheckerManager()classMeta:verbose_name=_("DataChecker")verbose_name_plural=_("DataCheckers")def__str__(self):returnself.namedefnatural_key(self):return(self.name,)@memoized_method()defget_fields(self)->Generator["FlexField",None,None]:fs:DataCheckerFieldsetforfsinself.members.select_related("fieldset").all():# for field in fs.fieldset.fields.select_related("definition").filter():forfieldinfs.fieldset.get_fields():yieldfs,field@memoized_method()defget_field(self,name)->"FlexField":for__,fieldinself.get_fields():iffield.name==name:returnfield# for fs in self.members.all():# for field in fs.fieldset.fields.select_related("definition").filter():# if field.name == name:# return field@deprecated("0.6.3",details="uses get_form_class()")defget_form(self)->"Generic[F]":returnself.get_form_class()defget_form_class(self)->"Generic[F]":from..formsimportFlexFormfields:dict[str,forms.Field]={}field:"FlexField"# for fs in self.members.select_related("fieldset").all():# for field in fs.fieldset.fields.select_related("definition").filter():forfs,fieldinself.get_fields():fld:FlexFormMixin=field.get_field()fld.label=f"{fs.prefix}{field.name}"if"%s"infs.prefix:full_name=fs.prefix%field.nameelse:full_name=f"{fs.prefix}{field.name}"fields[full_name]=fldform_class_attrs={"datachecker":self,"validator":self,**dict(sorted(fields.items()))}returntype(f"{self.name}DataCheckerForm",(FlexForm,),form_class_attrs)