Issue: VersionedModel
Author: kramer65
State: closed
Created: 2016-03-19 15:40:31

Hi @coleifer 

I needed a way to keep the full history of all records so that after an update I know what changed or after a delete I know what was deleted. So I had a brilliant programmer (not me) create some code, which I [just committed here](https://github.com/kramer65/peewee/blob/feature/versioned/playhouse/versioned.py) (with [tests here](https://github.com/kramer65/peewee/blob/feature/versioned/playhouse/tests/test_versioned.py))

Here is an example of how this can be used:

```
from peewee import CharField, DateField, BooleanField, SqliteDatabase
from playhouse.versioned import VersionedModel

# Setup a database
sqlite_database = SqliteDatabase(':memory:')

# Base class inherits ``VersionedModel`` instead of peewee.Model
class People(VersionedModel):
    class Meta:
        database = sqlite_database

# The actual table we are interested in.
class Person(People):
    name = CharField()
    is_relative = BooleanField()

Person.create_table()

# Let's create a record and change it around a couple times
person = Person()
person.name = 'MIKE'
person.is_relative=False
person.save()  # first save
person.name = 'mike'
person.save()  # second save
person.is_relative = True
person.save()  # third save
# We now stored 1 record in the `person` table, and 3 records in the `personversion` table.

# We can loop through all the versions of this record like this:
for version in person._versions.order_by(person._VersionModel._id):
    print version._original_record_id, version._version_id, version.valid_from, version.valid_until, version.name, version.is_relative
# This prints out
#1 1 2016-03-19 16:34:34.453254 2016-03-19 16:34:34.458547 MIKE False
#1 2 2016-03-19 16:34:34.459667 2016-03-19 16:34:34.460844 mike False
#1 3 2016-03-19 16:34:34.461830 None                       mike True

# We can also revert back to a previous version, which simply adds a new line to the `personversion` table.
# So reverting back to version 2, sets the new version to 4
print person.version_id, person.name, person.is_relative  # 3 mike True
person.revert(2)
print person.version_id, person.name, person.is_relative  # 4 mike False


# Let's see all the versions of this record again:
for version in person._versions.order_by(person._VersionModel._id):
    print version._original_record_id, version._version_id, version.valid_from, version.valid_until, version.name, version.is_relative
# This prints out
#1 1 2016-03-19 16:34:34.453254 2016-03-19 16:34:34.458547 MIKE False
#1 2 2016-03-19 16:34:34.459667 2016-03-19 16:34:34.460844 mike False
#1 3 2016-03-19 16:34:34.461830 2016-03-19 16:34:34.465116 mike True
#1 4 2016-03-19 16:34:34.466063 None                       mike False 
```

The following attributes can not be used in classes that inherit from VersionedModel
- valid_from
- valid_until
- deleted
- _original_record_id
- _original_record
- _id

If you bypass the normal save(), create(), and delete_instance() methods, signals will not be sent, and the model history will not be saved. Some dangerous commands noticed from testing include: (there may be more)
- .insert()
- .insert_many()
- .delete()  # class level

Any comments on this code are very welcome. 

Would be open to merging this into master?


This is neat, but I think it would be more appropriate as a separate library. There are quite a few opinions in this code, and I prefer to keep the playhouse stuff a bit more generic than this module.

Be happy to add a link in the docs if you end up releasing it as a library.


@coleifer 

Thanks for your response. releasing it as a separate lib would be fine as well. 

Regarding the "opinions" in this code, I would really appreciate _your_ opinions and remarks for improvement on this code. Are there any areas that can be changed which could make the usage of this code more intuitive/pythonic for programmers? 

All tips are welcome!