Issue: Query with double outer join returns empty object instead of `None`
Author: ibushong
State: closed
Created: 2019-08-16 17:45:37

Models:
```python
class City(BaseModel):
    name = CharField(null=False)


class Venue(BaseModel):
    name = CharField(null=False)
    city = ForeignKeyField(City, null=False)

    def __str__(self):
        return "%s (%s)" % (self.name, self.city.name)


class Event(BaseModel):
    name = CharField(null=False)
    venue = ForeignKeyField(Venue, null=True)
```
script:
```python
event = Event.create(name="Party", venue=None)

q = (Event.select(Event, Venue, City)
     .join(Venue, join_type=JOIN.LEFT_OUTER)
     .join(City, join_type=JOIN.LEFT_OUTER)
     )

for event in q:
    print event.id, event.name, event.venue
```

```
Traceback (most recent call last):
  File "/Users/ibushong/discotech/discotech-api/peewee_bug.py", line 50, in <module>
    print ep.id, ep.name, ep.venue
  File "/Users/ibushong/discotech/discotech-api/peewee_bug.py", line 29, in __str__
    return "%s (%s)" % (self.name, self.city.name)
  File "/Users/ibushong/.pyenv/versions/discotech/lib/python2.7/site-packages/peewee.py", line 4241, in __get__
    return self.get_rel_instance(instance)
  File "/Users/ibushong/.pyenv/versions/discotech/lib/python2.7/site-packages/peewee.py", line 4236, in get_rel_instance
    raise self.rel_model.DoesNotExist
__main__.CityDoesNotExist
```

I would expect `event.venue` to just return `None` but it seems like it's actually returning an "empty" venue object. So then when `__str__()` tries to access `self.city` (which must not be null), it his the `DoesNotExist` error.

This feels like a bug, but maybe it's just improper usage?

If you don't select anything from City, Peewee will mark the intervening model (Venue) as NULL. There was a similar issue reporting the opposite condition -- namely, not to use empty models. The current logic looks at the models as nodes in a graph. In your example, Venue is in the middle, connecting Event <- Venue <- City. Since you requested stuff on Event and City, Peewee sees that Venue is both a source (i.e., we joining *from* Venue) and a destination (we are joining *to* Venue). Because of this, Peewee will drop an empty model instance in place so that we can go from Event -> City even in the event no data is returned for Venue. Although that isn't the case here, that is the reason for that logic.

The way to avoid this would require looking through the graph each time a result row is constructed and pruning empty parts. I'm not too inclined to make that change, but it's probably the right one.

In the meantime, just add the appropriate conditionals to work around it.

* if event.venue.is is None ?
* fix `__str__` so that it's smarter about resolving foreign keys

Going to mark this as a nice-to-have in the TODOs list.

Thanks for the explanation. I suspected it might be something like that. Pruning it definitely sounds like the right solution.

Also, while I was implementing a workaround, I realized a more dangerous/relevant symptom of this issue. If in the `for events in q:` loop I wanted to condition something on if the event has a venue or not, I would do `if event.venue:` but this is True even for events without a venue (since they have an empty Venue object). As you recommended in your comment, `if event.venue.id:` works, but I could see this being a pesky bug to track down. Actually I think I _just_ realized that this is the cause of a weird bug I saw a while ago in my project...