Issue: Playhouse GFK issue with IN statement
Author: thibaultmeyer
State: closed
Created: 2014-02-27 19:49:35

Hi,

i have an issue with the Generic FK (playhouse) when I apply the MySQL IN statement. 

``` python
class Tag(Model):
    tag = CharField()
    object_type = CharField(null=True)
    object_id = IntegerField(null=True)
    object = GFKField('object_type', 'object_id')

class Blog(Model):
    tags = ReverseGFK(Tag, 'object_type', 'object_id')

class Photo(Model):
    tags = ReverseGFK(Tag, 'object_type', 'object_id')
    published = BooleanField()
```

``` python
Tag.select().where(Tag.object << Photo.select().where(Photo.published==True))
```

``` python
     80                     instance._obj_cache[self.att_name] = rel_obj
     81             return instance._obj_cache.get(self.att_name)
---> 82         return self.field
     83 
     84     def __set__(self, instance, value):

AttributeError: 'GFKField' object has no attribute 'field'
```


Thanks, looking.


The attribute error shouldn't occur, but I will also add that unfortunately you cannot query GFK's like that.

Your query:

``` python
Tag.select().where(Tag.object << Photo.select().where(Photo.published==True))
```

To accomplish what you're going for, you'll want to:

``` python
    published_photos = Photo.select().where(Photo.published == True)
    Tag.select().where(
        (Tag.object_id << published_photos.select(Photo.id)) &
        (Tag.object_type == Photo._meta.db_table))
```


I have the following model using GFK:

``` python

class Subscription(GFKModel):
    subscriber_type = CharField(null=True)
    subscriber_id = IntegerField(null=True)
    subscriber = GFKField('subscriber_type', 'subscriber_id')

    resource_type = CharField(null=True)
    resource_id = IntegerField(null=True)
    resource = GFKField('resource_type', 'resource_id')

    class Meta:
        indexes = (
            ((
                'subscriber_type',
                'subscriber_id',
                'resource_type',
                'resource_id',
            ), True),
        )
```

And I use this helper for a get_or_create (notice the double workaround, because each simpler alternative doesn't work):

``` python


def subscribe(subscriber, resource):
    # HACK workaround for using get_or_create / create_or_get with GFKFields.
    try:
        # HACK workaround for querying equality of GFKField. I'd love to do the following:
        # Subscription.get(
        #     (Subscription.subscriber == subscriber) &
        #     (Subscription.resource == resource))

        sub = Subscription.get(
            (Subscription.subscriber_id == subscriber._get_pk_value()) &
            (Subscription.subscriber_type == subscriber._meta.db_table) &
            (Subscription.resource_id == resource._get_pk_value()) &
            (Subscription.resource_type == resource._meta.db_table))

        created = False

    except Subscription.DoesNotExist:
        sub = Subscription.create(
            subscriber=subscriber,
            resource=resource,
        )

        created = True

    return sub, created

```

Is this limitation by design? (@coleifer earlier said "you cannot query GFKs like that")

Would peewee accept a PR that promotes the GFKField class to something else than just 'object' to be more friendly to querying? Is this extremely difficult?


It is not difficult, but semantics other than equality and inequality would
be weird. I'll look at implementing this.
On Jan 20, 2016 12:32 AM, "Francisco Demartino" notifications@github.com
wrote:

> I have the following model using GFK:
> 
> class Subscription(GFKModel):
>     subscriber_type = CharField(null=True)
>     subscriber_id = IntegerField(null=True)
>     subscriber = GFKField('subscriber_type', 'subscriber_id')
> 
> ```
> resource_type = CharField(null=True)
> resource_id = IntegerField(null=True)
> resource = GFKField('resource_type', 'resource_id')
> 
> class Meta:
>     indexes = (
>         ((
>             'subscriber_type',
>             'subscriber_id',
>             'resource_type',
>             'resource_id',
>         ), True),
>     )
> ```
> 
> And I use this helper for a get_or_create (notice the double workaround,
> because each simpler alternative doesn't work):
> 
> def subscribe(subscriber, resource):
>     # HACK workaround for using get_or_create / create_or_get with GFKFields.
>     try:
>         # HACK workaround for querying equality of GFKField. I'd love to do the following:
>         # Subscription.get(
>         #     (Subscription.subscriber == subscriber) &
>         #     (Subscription.resource == resource))
> 
> ```
>     sub = Subscription.get(
>         (Subscription.subscriber_id == subscriber._get_pk_value()) &
>         (Subscription.subscriber_type == subscriber._meta.db_table) &
>         (Subscription.resource_id == resource._get_pk_value()) &
>         (Subscription.resource_type == resource._meta.db_table))
> 
>     created = False
> 
> except Subscription.DoesNotExist:
>     sub = Subscription.create(
>         subscriber=subscriber,
>         resource=resource,
>     )
> 
>     created = True
> 
> return sub, created
> ```
> 
> Is this limitation by design? (@coleifer https://github.com/coleifer
> earlier said "you cannot query GFKs like that")
> 
> Would peewee accept a PR that promotes the GFKField class to something
> else than just 'object' to be more friendly to querying? Is this extremely
> difficult?
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/coleifer/peewee/issues/313#issuecomment-173108828.


Thank you, Charles!

For an example of "real-world" code look here:
https://github.com/franciscod/telegram-universal-forwarder-bot/blob/master/models.py
https://github.com/franciscod/telegram-universal-forwarder-bot/blob/master/resources/util.py


nice, thanks!


@coleifer Does the new GFK API impact `create_or_get()`?


It should, but I will add a test-case.


Turns out it did not, but it does with the latest commit referenced.


woohoo! thank you so much!