Issue: Join on expression raises AttributeError: 'Expression' object has no attribute 'name'
Author: mikedrawback
State: closed
Created: 2019-04-27 00:57:03

When joining two models on an expression, I started getting an error on 3.9.4 and 3.9.5.

My models:
```
import peewee

db = peewee.SqliteDatabase(':memory:')

class Product(peewee.Model):
    model_number = peewee.CharField()
    color = peewee.CharField()

    class Meta:
        database = db
        primary_key = peewee.CompositeKey('model_number', 'color')

class Sku(peewee.Model):
    upc_code = peewee.CharField(primary_key=True)
    model_number = peewee.ForeignKeyField(Product, field=Product.model_number)
    color = peewee.ForeignKeyField(Product, field=Product.color)

    class Meta:
        database = db


join_expr = ((Product.model_number==Sku.model_number)&(Product.color==Sku.color))
```
On version 3.9.3, this works:
```Product.select().join(Sku, on=join_expr)```

On verison 3.9.4 +, I get this error:
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "peewee.py", line 698, in inner
    method(clone, *args, **kwargs)
  File "peewee.py", line 6603, in join
    on, attr, constructor = self._normalize_join(src, dest, on, attr)
  File "peewee.py", line 6530, in _normalize_join
    attr = fk_field.name
AttributeError: 'Expression' object has no attribute 'name'
```

I can't quite figure out what changed that is causing this.

Well, part of the issue is that you are creating foreign-key constraints to a composite primary key -- but the fk constraints are to the individual columns that comprise the primary key rather than the (model_number, color) tuple. So, your schema is actually pretty broken.

You would instead do something like this (which is described in [the documentation](http://docs.peewee-orm.com/en/latest/peewee/models.html#table-constraints)):

```python
class Sku(Model):
    upc_code = peewee.CharField(primary_key=True)
    model_number = peewee.CharField()
    color = peewee.CharField()

    class Meta:
        constraints = [SQL('FOREIGN KEY(model_number, color) '
                           'REFERENCES product(model_number, color)')]
        database = db

    @property
    def product(self):
        return Product.get(
            (Product.model_number == self.model_number) &
            (Product.color == self.color))

    @product.setter
    def product(self, obj):
        self.model_number = obj.model_number
        self.color = obj.color
```

I've fixed the underlying issue, however, so your example is running correctly now.

Thank you for fixing and for the advice on composite foreign key constraints, much appreciated.

Whoops, I still had left the `Sku.model_number` and `Sku.color` as foreign key fields -- they should actually be `CharField`. Fixed the code snippet above so that it now reads:

```python
class Sku(Model):
    upc_code = peewee.CharField(primary_key=True)
    model_number = peewee.CharField()
    color = peewee.CharField()

    class Meta:
        constraints = [SQL('FOREIGN KEY(model_number, color) '
                           'REFERENCES product(model_number, color)')]
        database = db
```