Issue: Through model with not AutoField primary key (e.g., UUIDField) with the default value problem
Author: Ignisor
State: closed
Created: 2020-04-06 11:57:11

Hi.

## Problem description

For example, I have a schema like that:
```python
class _BaseModel(pw.Model):
    id = pw.UUIDField(primary_key=True, default=uuid.uuid4)

    class Meta:
        database = DB


class Cucumber(_BaseModel):
    size = pw.CharField()


CucumbersThroughDeferred = pw.DeferredThroughModel()


class Human(_BaseModel):
    name = pw.CharField()
    cucumbers = pw.ManyToManyField(Cucumber, backref='humans', through_model=CucumbersThroughDeferred)


class CucumberHuman(_BaseModel):
    cucumber = pw.ForeignKeyField(Cucumber)
    human = pw.ForeignKeyField(Human)
    

CucumbersThroughDeferred.set_model(CucumberHuman)
```

And when I add model to M2M field like that, it works okay:

```python
cucumber = Cucumber.create(
    size='big',
)

human = Human.create(name='coleifer')

human.cucumbers.add(cucumber)
```

But if I try to add it using the query:

```python
human = Human.create(name='Ignisor')

human.cucumbers.add(Cucumber.select())
```

I will get an exception:

```
Traceback (most recent call last):
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 3057, in execute_sql
    cursor.execute(sql, params or ())
psycopg2.errors.NotNullViolation: null value in column "id" violates not-null constraint
DETAIL:  Failing row contains (null, d3a22843-58ff-4d32-9c90-352a53744bdd, 27888182-3305-44ff-b175-919ecace48fb).


During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  line 59, in <module>
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 7167, in add
    accessor.through_model.insert_from(
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 1845, in inner
    return method(self, database, *args, **kwargs)
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 1916, in execute
    return self._execute(database)
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 2665, in _execute
    return super(Insert, self)._execute(database)
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 2398, in _execute
    cursor = self.execute_returning(database)
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 2405, in execute_returning
    cursor = database.execute(self)
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 3070, in execute
    return self.execute_sql(sql, params, commit=commit)
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 3064, in execute_sql
    self.commit()
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 2831, in __exit__
    reraise(new_type, new_type(exc_value, *exc_args), traceback)
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 183, in reraise
    raise value.with_traceback(tb)
  File "/Users/ignisor/dev/redbud-compliance-entities/.env/lib/python3.8/site-packages/peewee.py", line 3057, in execute_sql
    cursor.execute(sql, params or ())
peewee.IntegrityError: null value in column "id" violates not-null constraint
DETAIL:  Failing row contains (null, d3a22843-58ff-4d32-9c90-352a53744bdd, 27888182-3305-44ff-b175-919ecace48fb).
```

It works if I use a model with AutoKey as through-model for the M2M field.

## Workaround

Using a through-model with AutoKey as a primary key.

In my case, I came up with Metaclass for that purpose (so I don't need to make different base classes).

```
class ThroughModelMeta(pw.ModelBase):
    def __new__(cls, name, bases, attrs):
        new_cls = super().__new__(cls, name, bases, attrs)
        new_cls._meta.set_primary_key('id', pw.AutoField())

        return new_cls
```

Thanks.

_P.s. And thanks for such a nice ORM_ 😉


This is expected behavior when using a primary-key field whose default-value is calculated in Python.

Bulk-populating a many-to-many using a select query is an optimization. You can see from the traceback that it's doing an `INSERT ... FROM` query, which means we're inserting into the through table directly from the select query (without resolving the rows on the python side).

When you go through the higher-level APIs, peewee will populate your model instances with a new uuid (by calling the `uuid4()` function). But since the `INSERT ... FROM` happens completely in the database, the db has no value for the ids.

This is one of the many reasons it's recommended to just use auto-incrementing integer primary keys. Or if you insist on using UUIDs, set up the column default to call a database function rather than a python one. You've hamstrung your code without realizing it, most likely.

Ok, thanks. I understand that behaviour but now I understand why it cannot be changed. In my case I will simply use AutoField for through models.

Thanks.
On 6 Apr 2020, 17:09 +0300, coleifer/peewee <reply@reply.github.com>, wrote:
>
> You've hamstrung your code without realizing it, most likely