Opened 4 years ago

Last modified 3 weeks ago

#16508 assigned New feature

Provide real support for virtual fields

Reported by: vzima Owned by: pirosb3
Component: Database layer (models, ORM) Version: master
Severity: Normal Keywords:
Cc: pirosb3 Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I have created a virtual field based on GenericForeignKey, but I found out that it can not be properly used for model initiation.

Altered example from my test:

class Managing(models.Model):
    shortcut = models.CharField(max_length=20)
    name_cs = TranslationProxyField('name', 'cs', False)


class ManagingTranslation(models.Model):
    name = models.CharField(max_length=20)


class TranslationProxyField(object):
    """
    Descriptor that is a virtual field that provides access to translations.
    """
    def __init__(self, field_name, language_code=None, fallback=False):
        self._field_name = field_name
        self._language_code = language_code
        self._fallback = fallback
        names = [field_name]
        if language_code is not None:
            names.append(language_code.replace('-', '_'))
        if fallback:
            names.append(FALLBACK_FIELD_SUFFIX)
        self.name = '_'.join(names)

    def contribute_to_class(self, cls, name):
        if self.name != name:
            raise ValueError('Field proxy %s is added to class under bad attribute name.' % self)
        self.model = cls
        cls._meta.add_virtual_field(self)

        # Connect myself as the descriptor for this field
        setattr(cls, name, self)

# This results in Type error from Model.__init__, because of extra kwarg
Managing(shortcut='name', name_cs='jméno')

I tried to use pre_init signal, but I found out that I will not get instance argument, so I can not use that. This way is truly usable only for virtual fields which are like GenericForeignKey built on real model fields of object. When it gets more complicated, it can not be used. Confusing on this path is that instance is in providing_args of pre_init signal.

Currently I solve this by quite a hack which is deriving of TranslationProxyField from property instead of object. This works because of setting properties is allowed in Model.__init__.

In my opinion, a stable solution would be either providing an instance argument in pre_init signal or enable setting of virtual field in model initiation. Second option would also enable removal of pre_init connection from GenericForeignKey.

Exact examples can be found at github: https://github.com/vzima/django-multilingual-ds9

Change History (7)

comment:1 Changed 4 years ago by bpeschier

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Triage Stage changed from Unreviewed to Accepted

Virtual fields are also discussed shortly by Michal Petrucha for the Composite fields API [1] and in further responses; it seems like there are more use cases, but I would strongly suggest taking it up on the django-developers mailing list to further discuss it.

[1]: https://groups.google.com/d/msg/django-developers/rF79c8z65cQ/D2ZM1yZPX4cJ

comment:2 Changed 2 years ago by akaariai

  • Type changed from Uncategorized to New feature

comment:3 Changed 10 months ago by pirosb3

  • Cc pirosb3 added
  • Owner changed from nobody to pirosb3
  • Status changed from new to assigned

Hi,

I will be taking this ticked under consideration as we are currently planning a refactor of _meta.

Dan

comment:5 Changed 4 weeks ago by akaariai

I'm planning on tackling this issue. The initial plan is to:

  • Change ForeignObjectRel subclasses to real field instances. (For example, ForeignKey generates a ManyToOneRel in the related model). The Rel instances are already returned from get_field(), but they aren't yet field subclasses.
  • Allow direct usage of ForeignObjectRel subclasses. In certain cases it can be advantageous to be able to define reverse relations directly. For example, see https://github.com/akaariai/django-reverse-unique.
  • Partition ForeignKey to virtual relation field, and concrete data field. The former is the model.author, the latter model.author_id's backing implementation.
  • Consider other cases where true virtual fields are needed.

My initial try will be a class structure like:

    Field
        RelationField (base class for ForeignKey, ManyToOneRel, maybe also ManyToManyField)
        ConcreteField (base class for concrete data fields, for example IntegerField, ...)

I expect this to cause a good amount of breakage in 3rd party apps. I'm going to use the old good "lets first break things, then fix those cases that are complained about".

comment:6 Changed 4 weeks ago by pirosb3

Thanks akaariai,

If you need any help on my side (given the work I did on Meta) please let me know.

Daniel

comment:7 Changed 3 weeks ago by ovangle

Could I propose the following class structure:

Field
    VirtualField
        RelationField
    ConcreteField

The addition of an intermediary VirtualField class would be for future support of a CompositeField class, which isn't logically a RelationField, but is logically virtual (and subfields of the CompositeField would be limited to concrete fields for simplicity of implementation).

Last edited 3 weeks ago by ovangle (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top