from .basic import SIGsAPIField from django.utils.functional import cached_property from ..models import SIGsAPIRelationModelManager from django.db.models import IntegerField, Field, CharField import importlib from django.forms import ChoiceField, ModelChoiceField from django.forms.widgets import Select def add_to_one ( cls, target, attribute, origin_key, target_key, field, ): # Verifique se já existe um accessor para o atributo direto if not hasattr(cls, origin_key): cls = SIGsAPIField.contribute_to_sigs_api_class( field, cls, origin_key, ) cls._meta.set_attribute(attribute, field) func = cached_property( lambda _self: target.objects.get(**{target_key: getattr(_self, origin_key)})) func.__set_name__(cls, attribute) setattr(cls, attribute, func) def add_to_many (cls, target, attribute, origin_key, target_key, field, ): cls._meta.set_attribute(attribute, field) # Tem certeza que é o mesmo? related_manager_func = cached_property( lambda _self: SIGsAPIRelationModelManager(target,{ target_key: getattr(_self, origin_key), })) related_manager_func.__set_name__(cls, attribute) setattr(cls, attribute, related_manager_func) class SIGsToManyRelation(SIGsAPIField): def __init__(self, target_class, target_class_relation,# = None, origin_class_fk = "pk", target_class_fk = "pk", mapped_by_target = False, null = False, ): self.target_class = target_class self.origin_class_fk = origin_class_fk self.target_class_fk = target_class_fk self.target_class_relation = target_class_relation or None self.mapped_by_target = mapped_by_target super().__init__(property_name= self.origin_class_fk, null = null) def inverted(self): inverted = SIGsToManyRelation( target_class= self.origin_class, target_class_relation = self.origin_class_relation, target_class_fk = self.origin_class_fk, origin_class_fk= self.target_class_fk, mapped_by_target= not self.mapped_by_target, ) inverted.origin_class = self.target_class inverted.origin_class_relation = self.origin_class_relation inverted.opposite_field = self self.opposite_field = inverted return inverted class SIGsToOneRelation(SIGsAPIField): def __init__(self, target_class, target_class_relation,# = None, origin_class_fk = "pk", target_class_fk = "pk", mapped_by_target = False, ): self.target_class = target_class self.origin_class_fk = origin_class_fk self.target_class_fk = target_class_fk self.target_class_relation = target_class_relation or None self.mapped_by_target = mapped_by_target super().__init__(property_name= self.origin_class_fk, null = False) def inverted(self): inverted = SIGsToManyRelation( target_class= self.origin_class, target_class_relation = self.origin_class_relation, target_class_fk = self.origin_class_fk, origin_class_fk= self.target_class_fk, mapped_by_target= not self.mapped_by_target, ) inverted.origin_class = self.target_class inverted.origin_class_relation = self.origin_class_relation inverted.opposite_field = self self.opposite_field = inverted return inverted def contribute_to_sigs_api_class(self, origin_class, origin_class_relation): self.origin_class = origin_class self.origin_class_relation = origin_class_relation self.opposite_field = self.inverted() add_to_one (cls = origin_class, target = self.target_class, attribute = origin_class_relation, origin_key = self.origin_class_fk, target_key = self.target_class_fk, field = self, ) if self.origin_class_fk == "pk": self.attribute_name = origin_class_relation return origin_class return super().contribute_to_sigs_api_class(origin_class, self.origin_class_fk.replace('-','_')) class SIGsAPIManyToOne(SIGsToOneRelation): def __init__(self, target_class, target_class_relation, origin_class_fk = "pk", target_class_fk = "pk", mapped_by_target = False, null = False, ): super().__init__( target_class=target_class, target_class_relation=target_class_relation, origin_class_fk= origin_class_fk, target_class_fk = target_class_fk, mapped_by_target= mapped_by_target, ) def contribute_to_sigs_api_class(self, origin_class, origin_class_relation): origin_class = super().contribute_to_sigs_api_class(origin_class, origin_class_relation) add_to_many(cls = self.target_class, target = origin_class, attribute=self.target_class_relation, origin_key = self.target_class_fk, target_key=self.origin_class_fk, field = self.opposite_field, ) return origin_class class SIGSAPIOneToManyField (SIGsAPIField): def __init__(self, target_class, target_class_relation,# = None, target_class_fk = "pk", ): self.target_class = target_class self.target_class_fk = target_class_fk self.target_class_relation = target_class_relation or None super().__init__() def contribute_to_sigs_api_class(self, origin_class, origin_class_relation): self.attribute_name = origin_class_relation #if not self.related_name: # self.related_name = re.sub('([A-Z]{1})',r'-\1',cls.__name__)[1:].replace('-Model','s').lower() # O nome de propriedade normalmente a gente tira o "id-", então eu o coloco de volta #property_name_on_object = "id-" + property_name.replace('_','-') #if not self.related_name_pk: # self.related_name_pk = "id_" + property_name # Vamos setar a propriedade na OUTRA classe #self.target_class._meta.set_attribute(self.target_class_relation, self) # Adicionando FK func_fk = cached_property( lambda _self: _self._object[self.target_class_fk.replace('_','-')]) func_fk.__set_name__(self.target_class, self.target_class_fk) setattr(self.target_class, self.target_class_fk, func_fk) # Adicionando a relação func = cached_property( lambda _self: origin_class.objects.get(**{self.origin_class_fk: getattr(_self, self.target_class_fk)})) func.__set_name__(self.target_class, self.target_class_relation) setattr(self.target_class, self.target_class_relation, func) # Agora vamos colocar a propriedade na classe origem origin_class._meta.set_attribute(origin_class_relation, self) # Tem certeza que é o mesmo? target_manager_func = cached_property( lambda _self: SIGsAPIRelationModelManager(self.target_class, filters = { self.target_class_fk: _self.pk, })) target_manager_func.__set_name__(origin_class, origin_class_relation, ) setattr(origin_class, origin_class_relation, target_manager_func) return origin_class class SIGsAPIDetailsField (SIGsAPIField): def __init__(self, target_class, origin_fk = "pk", target_fk = "pk", target_class_relation = None,# = None, create_on_target = True, create_on_origin = True, ): self.target_class = target_class self.target_class_fk = target_fk self.origin_class_fk = origin_fk self.target_class_relation = target_class_relation or None self.create_on_target = create_on_target self.create_on_origin = create_on_origin super().__init__() def contribute_to_sigs_api_class(self, origin_class, origin_class_relation): self.attribute_name = origin_class_relation #if not self.related_name: # self.related_name = re.sub('([A-Z]{1})',r'-\1',cls.__name__)[1:].replace('-Model','s').lower() # O nome de propriedade normalmente a gente tira o "id-", então eu o coloco de volta #property_name_on_object = "id-" + property_name.replace('_','-') #if not self.related_name_pk: # self.related_name_pk = "id_" + property_name # Vamos setar a propriedade na própria classe if self.create_on_origin: origin_class._meta.set_attribute(origin_class_relation, self) func = cached_property( lambda _self: self.target_class.objects.get(pk = getattr(_self, self.origin_class_fk))) func.__set_name__(origin_class, origin_class_relation) setattr(origin_class, origin_class_relation, func) if self.origin_class_fk != 'pk': origin_class = super().contribute_to_sigs_api_class(origin_class, self.origin_class_fk.replace('-','_')) if self.create_on_target: # Adicionando FK, mas apenas se não for também PK if self.target_class_fk != 'pk': func_fk = cached_property( lambda _self: _self._object[self.target_class_fk.replace('_','-')]) func_fk.__set_name__(self.target_class, self.target_class_fk) setattr(self.target_class, self.target_class_fk, func_fk) # Adicionando a relação func = cached_property( lambda _self: origin_class.objects.get(**{self.origin_class_fk: getattr(_self, self.target_class_fk)})) func.__set_name__(self.target_class, self.target_class_relation) setattr(self.target_class, self.target_class_relation, func) return origin_class class SIGsAPIOneToOneField (SIGsToOneRelation): def __init__(self, target_class, target_class_relation, origin_class_fk = "pk", target_class_fk = "pk", mapped_by_target = False, null = False, ): super().__init__( target_class=target_class, target_class_relation=target_class_relation, origin_class_fk= origin_class_fk, target_class_fk = target_class_fk, mapped_by_target= mapped_by_target, ) def contribute_to_sigs_api_class(self, origin_class, origin_class_relation): origin_class = super().contribute_to_sigs_api_class(origin_class, origin_class_relation) add_to_one(cls = self.target_class, target = origin_class, attribute=self.target_class_relation, origin_key = self.target_class_fk, target_key=self.origin_class_fk, field = self.opposite_field, ) return origin_class class SingleRelatedAccessor: def __init__(self, field): self.field = field def __get__(self, instance, cls=None): """Recupera o objeto pela API SIGs usando o ID salvo no banco Por exemplo, o campo uf_unidade_federativa Args: intance (_type_): é o objeto! cls (_type_, optional): é a classe do objeto. Defaults to None. """ return self.field.target_class.objects.get(**{ self.field.target_class_fk: getattr(instance, self.field.get_attname()) }) def __set__(self, instance, value): """ Altera a entitdade relacionada. Quando alguem fizer ``child.parent = parent``: - ``self`` is the descriptor managing the ``parent`` attribute - ``instance`` is the ``child`` instance - ``value`` is the ``parent`` instance on the right of the equal sign """ if value is None: if self.field.null: setattr(instance, self.field.get_attname(), None) else: raise ValueError("O valor não pode ser nulo") elif isinstance(value, str): setattr(instance, self.field.get_attname(), value) elif not isinstance(value, self.field.target_class): raise TypeError("Objeto {o}, do tipo {t}, não é da classe {c} dessa relação".format(o = value, t = value.__class__, c = self.field.target_class)) else: setattr(instance, self.field.get_attname(), getattr(value, self.field.target_class_fk)) class SIGsAPIForeignKeyFormField (ChoiceField): def __init__(self, **kwargs): super().__init__(choices = kwargs['choices_form_class'].get_sigsapi_choices, **kwargs) class SIGsAPIForeignKey(CharField, SIGsAPIManyToOne): """Guarda referência para um entidade na API dos SIGs""" def __init__(self, target_class, target_class_relation = None, origin_class_fk = "pk", target_class_fk = "pk", *args, **kwargs): SIGsAPIManyToOne.__init__( self, target_class = target_class, target_class_relation = target_class_relation, origin_class_fk = origin_class_fk, target_class_fk = target_class_fk, ) default = kwargs.pop('default') if(isinstance(default, target_class)): kwargs['default'] = getattr(default, self.target_class_fk) else: kwargs['default'] = default kwargs['max_length'] = 100 #kwargs['choices'] = self.get_sigsapi_choices() CharField.__init__(self, *args, **kwargs) @property def non_db_attrs(self): return super().non_db_attrs + ("target_class","target_class_relation","origin_class_fk","target_class_fk") def deconstruct(self): name, path, args, kwargs = super().deconstruct() kwargs['target_class'] = self.target_class return name, path, args, kwargs def get_attname(self): return "%s_id" % self.name def get_attname_column(self): attname = self.get_attname() column = self.db_column or attname return attname, column def contribute_to_class(self, cls, name, private_only=False, **kwargs): # ADicione o campo simples, integer, que vai salvar o ID IntegerField.contribute_to_class(self, cls, name, private_only=private_only, **kwargs) # Adicione o Accessor, que vai permitir acessar diretamente o objeto setattr(cls, name, SingleRelatedAccessor(self)) if isinstance(self.target_class, str): splitted = self.target_class.split('.') class_name = splitted[-1] module_name = '.'.join(splitted[0:-1]) module = importlib.import_module(module_name) self.target_class = getattr(module, class_name) if not self.target_class_relation: self.target_class_relation = cls.__name__.lower() # Agora vamos colocar a propriedade na classe target self.target_class._meta.set_attribute(self.target_class_relation, self) # Tem certeza que é o mesmo? related_manager_func = cached_property( lambda _self: SIGsAPIRelationModelManager(cls,{ self.origin_class_fk: _self.pk })) related_manager_func.__set_name__(self.target_class, self.target_class_relation) setattr(self.target_class, self.target_class_relation, related_manager_func) #def get_sigsapi_choices(self): # return [ (getattr(object, self.target_class_fk), object) for object in self.target_class.objects.all() ] def formfield(self, *args, **kwargs): """define o FormField a ser usado para esse Field kwargs contém as opções fornecidas pelo usuário para esse FormField Returns: django.forms.Field: o forms.Field apropriado para esse db.Field """ defaults = { 'form_class': ModelChoiceField, #'choices_form_class': ModelChoiceField, 'queryset': self.target_class.objects.all(), 'to_field_name': self.target_class_fk, #'choices': lambda _self: [ (getattr(object, self.target_class_fk), object) for object in _self.target_class.objects.all() ] **kwargs, 'widget': Select, 'blank': False, } return Field.formfield(self, **defaults)