1

I've been reading lately about the generic relations. I know that GenericForeignKey is to define and manaeg the generic relation using ForeignKey and PositiveIntegerField fields. I dove into the source code in search for the __set__ method of the GenericForeignKey to see how does it work.

Here is the snippet for GenericForeignKey.__set__():

def __set__(self, instance, value):
    ct = None
    fk = None
    if value is not None:
        ct = self.get_content_type(obj=value)
        fk = value._get_pk_val()
    setattr(instance, self.ct_field, ct)
    setattr(instance, self.fk_field, fk)
    setattr(instance, self.cache_attr, value)

and model definition from django docs example:

class TaggedItem(models.Model):
    tag = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

Question:

When I assign value of guido to the content_object then what is the value of each of these paremeters: self, instance and value in the GenericForeignKey.__set__()?

Is self=<GenericForeignKey: 1>, instance='content_object', and value=<User: guido>?

>>> guido = User.objects.get(username='Guido')
>>> t = TaggedItem(content_object=guido, tag='bdfl')
>>> t.save()
an0o0nym
  • 1,456
  • 16
  • 33
  • 1
    Read up on [descriptor protocol](https://docs.python.org/3/howto/descriptor.html)! Here's a useful answer: http://stackoverflow.com/a/34554353/674039 – wim Jul 31 '16 at 00:01

1 Answers1

0

The __set__ method is for descriptors.

The following simple example will show what the arguments passed to __set__ are:

class MyDescriptor:
    def __set__(self, instance, value):
        print((self, instance, value))

class MyClass:
    attr = MyDescriptor()

inst = MyClass()
inst.attr = "foo"

You'll get something like:

<__main__.MyDescriptor object at 0x000002017192AD68>,   # self
<__main__.MyClass object at 0x000002017192ACF8>,        # instance
'foo'                                                   # value

Specifically:

  • self is the instance of the MyDescriptor descriptor (MyClass.attr),
  • instance is the instance of the MyClass class (inst), and
  • value is what you're setting the attribute to ("foo").

See a more thorough example here.

So, without similarly diving into the Django code, it would seem that:

  • self is the instance of the GenericForeignKey descriptor (TaggedItem.content_object),
  • instance is the instance of the TaggedItem class, and
  • value is what you're setting the attribute to.

But note that, with this line:

t = TaggedItem(content_object=guido, tag='bdfl')

It looks like you're creating a TaggedItem, which creates a descriptor with this line

content_object = GenericForeignKey('content_type', 'object_id')

So, at least from the code you posted, the __set__ method won't be called. Instead GenericForeignKey's __init__ method would be called.

To call GenericForeignKey's __set__ method, you'd need to do have an instance of a class (call it inst) that had a GenericForeignKey descriptor as an attribute (call it attr), then write something like:

inst.attr = "not guido"

Then, the __set__ method of the GenericForeignKey descriptor would be called.

jedwards
  • 29,432
  • 3
  • 65
  • 92
  • That makes sense! However it does not explicitly answer the question, it gives you the tools to understand what I was asking about. – an0o0nym Jul 31 '16 at 01:26
  • @an0o0nym regarding "When I assign value of guido to the content_object then what is the value of each of these paremeters: self, instance and value in the GenericForeignKey.__set__()?" the answer sort of addresses it. It tells you what the parameters *would be* if it was called, but I explained why it probably wouldn't be called. – jedwards Jul 31 '16 at 01:28