hello gaetan,
thanks for your reviewing my ideas/code.
please follow my (positive) replies:
On Fri, Sep 5, 2008 at 1:25 PM, Gaetan de Menten <[EMAIL PROTECTED]> wrote:
>
> On Mon, Sep 1, 2008 at 5:32 PM, alex bodnaru <[EMAIL PROTECTED]> wrote:
>
>> sorry to bothering.
>
> You are definitely not bothering... Providing patches is not a bother
> ;-) Thanks a lot for your contributions (and sorry for the slow
> reply)...
>
>> i found the owned extension clashed with the newer versioned one, so
>> here are the patched versions for both.
>
> Could you explain briefly how they collided?
they just had the same fields to hold the column names. nothing released.
>
> Here are a couple comments (please don't take them badly, I'm just
> trying to be constructive here):
> - Could you provide patches (diffs) instead of the complete files? It
> makes it easier for me to see what changed exactly without having to
> "apply" your file in the correct place.
no probkem. attaching here.
> - I don't really get how a non-callable "get_current_user" can be of
> any use. If it's a constant for the whole table, why even add the
> column?
right, but that may also be an environment variable etc.
> - I don't think you should special-case [ ] for column_field_names. If
> people want the default names, they should pass None, that's all.
the empty case should never be xplicitly called, so no problem for me
to change that anyway.
> - I don't like the default names for the columns. What do you think
> of "inserted_by" (or "created_by") and "updated_by"?
agree with you: your names seem much nicer.
> - Why do you re-write the record_inserter on each update? It has
> already been set on insert, so you don't need to write it again and
> again. Besides, having a select() query in a before_update extension
> should be avoided if possible as it will slow things down.
you'right. i mistakenly thought these columns as out of the record.
> - I try to wrap code at 80 characters.
np.
>
> --
> Gaƫtan de Menten
> http://openhex.org
>
> >
>
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"SQLElixir" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/sqlelixir?hl=en
-~----------~----~----~----~------~----~------~--~---
--- versioned0.py 2008-08-11 10:27:00.000000000 +0300
+++ versioned.py 2008-09-01 18:23:08.000000000 +0300
@@ -87,8 +87,8 @@
class VersionedMapperExtension(MapperExtension):
def before_insert(self, mapper, connection, instance):
- instance.version = 1
- instance.timestamp = datetime.now()
+ setattr(instance, instance.__class__.__versioned_column_field_names__[0], 1)
+ setattr(instance, instance.__class__.__versioned_column_field_names__[1], datetime.now())
return EXT_CONTINUE
def before_update(self, mapper, connection, instance):
@@ -101,6 +101,7 @@
# to ensure we really should save this version and update the version
# data.
ignored = instance.__class__.__ignored_fields__
+ field_names = instance.__class__.__versioned_column_field_names__
for key in instance.table.c.keys():
if key in ignored:
continue
@@ -109,8 +110,8 @@
dict_values = dict(old_values.items())
connection.execute(
instance.__class__.__history_table__.insert(), dict_values)
- instance.version = instance.version + 1
- instance.timestamp = datetime.now()
+ setattr(instance, field_names[0], getattr(instance, field_names[0]) + 1)
+ setattr(instance, field_names[1], datetime.now())
break
return EXT_CONTINUE
@@ -131,7 +132,7 @@
class VersionedEntityBuilder(EntityBuilder):
- def __init__(self, entity, ignore=[], check_concurrent=False):
+ def __init__(self, entity, ignore=[], check_concurrent=False, column_field_names=None):
self.entity = entity
self.add_mapper_extension(versioned_mapper_extension)
#TODO: we should rather check that the version_id_col isn't set
@@ -139,13 +140,19 @@
self.check_concurrent = check_concurrent
# Changes in these fields will be ignored
- ignore.extend(['version', 'timestamp'])
+ if column_field_names in [None, []]:
+ column_field_names = ['version', 'timestamp']
+ entity.__versioned_column_field_names__ = column_field_names
+ ignore.extend(column_field_names)
entity.__ignored_fields__ = ignore
def create_non_pk_cols(self):
# add a version column to the entity, along with a timestamp
- self.add_table_column(Column('version', Integer))
- self.add_table_column(Column('timestamp', DateTime))
+ field_names = self.entity.__versioned_column_field_names__
+ if field_names[0] not in [col.name for col in self.entity._descriptor.columns]:
+ self.add_table_column(Column(field_names[0], Integer))
+ if field_names[1] not in [col.name for col in self.entity._descriptor.columns]:
+ self.add_table_column(Column(field_names[1], DateTime))
# add a concurrent_version column to the entity, if required
if self.check_concurrent:
@@ -165,9 +172,9 @@
#TODO: fail more noticeably in case there is a version col
columns = [
column.copy() for column in entity.table.c
- if column.name not in ('version', 'concurrent_version')
+ if column.name not in entity.__versioned_column_field_names__
]
- columns.append(Column('version', Integer, primary_key=True))
+ columns.append(Column(entity.__versioned_column_field_names__[0], Integer, primary_key=True))
table = Table(entity.table.name + '_history', entity.table.metadata,
*columns
)
@@ -186,7 +193,7 @@
def get_versions(self):
v = object_session(self).query(Version) \
.filter(get_history_where(self)) \
- .order_by(Version.version) \
+ .order_by(getattr(Version, entity.__versioned_column_field_names__[0])) \
.all()
# history contains all the previous records.
# Add the current one to the list to get all the versions
@@ -196,7 +203,7 @@
def get_as_of(self, dt):
# if the passed in timestamp is older than our current version's
# time stamp, then the most recent version is our current version
- if self.timestamp < dt:
+ if getattr(self, entity.__versioned_column_field_names__[1]) < dt:
return self
# otherwise, we need to look to the history table to get our
@@ -204,18 +211,18 @@
sess = object_session(self)
query = sess.query(Version) \
.filter(and_(get_history_where(self),
- Version.timestamp <= dt)) \
- .order_by(desc(Version.timestamp)).limit(1)
+ getattr(Version, entity.__versioned_column_field_names__[1]) <= dt)) \
+ .order_by(desc(getattr(Version, entity.__versioned_column_field_names__[1]))).limit(1)
return query.first()
def revert_to(self, to_version):
if isinstance(to_version, Version):
- to_version = to_version.version
+ to_version = getattr(to_version, entity.__versioned_column_field_names__[0])
hist = entity.__history_table__
old_version = hist.select(and_(
get_history_where(self),
- hist.c.version == to_version
+ getattr(hist.c, entity.__versioned_column_field_names__[0]) == to_version
)).execute().fetchone()
entity.table.update(get_entity_where(self)).execute(
@@ -223,19 +230,19 @@
)
hist.delete(and_(get_history_where(self),
- hist.c.version >= to_version)).execute()
+ getattr(hist.c, entity.__versioned_column_field_names__[0]) >= to_version)).execute()
self.expire()
for event in after_revert_events:
event(self)
def revert(self):
- assert self.version > 1
- self.revert_to(self.version - 1)
+ assert getattr(self, entity.__versioned_column_field_names__[0]) > 1
+ self.revert_to(getattr(self, entity.__versioned_column_field_names__[0]) - 1)
def compare_with(self, version):
differences = {}
for column in self.table.c:
- if column.name in ('version', 'concurrent_version'):
+ if column.name in (entity.__versioned_column_field_names__[0], 'concurrent_version'):
continue
this = getattr(self, column.name)
that = getattr(version, column.name)
--- test_versioning0.py 2008-09-06 04:02:25.000000000 +0300
+++ test_versioning.py 2008-09-06 04:04:35.000000000 +0300
@@ -4,6 +4,8 @@
import time
+column_field_names=['version_no', 'timestamp_value']
+
nextOneValue = 0
def nextOne():
global nextOneValue
@@ -26,11 +28,13 @@
releasedate = Field(DateTime)
ignoreme = Field(Integer, default=0)
autoupd = Field(Integer, default=nextOne, onupdate=nextOne)
+# version_no = Field(Integer)
+ timestamp_value = Field(DateTime)
director = ManyToOne('Director', inverse='movies')
actors = ManyToMany('Actor', inverse='movies',
tablename='movie_casting')
using_options(tablename='movies')
- acts_as_versioned(ignore=['ignoreme', 'autoupd'])
+ acts_as_versioned(ignore=['ignoreme', 'autoupd'], column_field_names=column_field_names)
class Actor(Entity):
@@ -67,7 +71,7 @@
time.sleep(1)
movie = Movie.get_by(title='12 Monkeys')
- assert movie.version == 1
+ assert getattr(movie, column_field_names[0]) == 1
assert movie.title == '12 Monkeys'
assert movie.director.name == 'Terry Gilliam'
assert movie.autoupd == 2, movie.autoupd
@@ -97,20 +101,20 @@
middle_version = movie.get_as_of(after_update_one)
latest_version = movie.get_as_of(after_update_two)
- initial_timestamp = oldest_version.timestamp
+ initial_timestamp = getattr(oldest_version, column_field_names[1])
- assert oldest_version.version == 1
+ assert getattr(oldest_version, column_field_names[0]) == 1
assert oldest_version.description == 'draft description'
assert oldest_version.ignoreme == 0
assert oldest_version.autoupd is not None
assert oldest_version.autoupd > 0
- assert middle_version.version == 2
+ assert getattr(middle_version, column_field_names[0]) == 2
assert middle_version.description == 'description two'
assert middle_version.autoupd > oldest_version.autoupd
- assert latest_version.version == 3, \
- 'version=%i' % latest_version.version
+ assert getattr(latest_version, column_field_names[0]) == 3, \
+ 'version=%i' % getattr(latest_version, column_field_names[0])
assert latest_version.description == 'description three'
assert latest_version.ignoreme == 1
assert latest_version.autoupd > middle_version.autoupd
@@ -122,7 +126,7 @@
assert len(movie.versions) == 3
assert movie.versions[0] == oldest_version
assert movie.versions[1] == middle_version
- assert [v.version for v in movie.versions] == [1, 2, 3]
+ assert [getattr(v, column_field_names[0]) for v in movie.versions] == [1, 2, 3]
movie.description = 'description four'
@@ -130,7 +134,7 @@
session.commit(); session.clear()
movie = Movie.get_by(title='12 Monkeys')
- assert movie.version == 2, "version=%i, should be 2" % movie.version
+ assert getattr(movie, column_field_names[0]) == 2, "version=%i, should be 2" % getattr(movie, column_field_names[0])
assert movie.description == 'description two', movie.description
movie.description = "description 3"
@@ -141,12 +145,12 @@
session.commit(); session.clear()
movie = Movie.get_by(title='12 Monkeys')
- assert movie.version == 4
+ assert getattr(movie, column_field_names[0]) == 4
movie.revert_to(movie.versions[-2])
movie.description = "description 5"
session.commit(); session.clear()
movie = Movie.get_by(title='12 Monkeys')
- assert movie.version == 4
+ assert getattr(movie, column_field_names[0]) == 4
assert movie.versions[-2].description == "description 3"
"""
A plugin for Elixir, which adds and maintains columns for the user that creates,
and respectivelly updates, a record.
The user may be supplied directly, or a function getting an argument to fetch
the user may be supplied. the argument (getter_param) is optional, defaulting
to None.
The field names may be optionally supplied in the column_field_names parameter.
If column_field_names is None (by default), ['created_by', 'updated_by'],
will be assumed. The field names may be already defined, in which case they will
be used as they are. This feature will allow those fields belonging to entity
relationships. If only one field will be supplied, the same field name will be
set for inserter and updater, the effect will be to keep the last toucher. In
combination with the versioned extension, the inserter will be the user who
touched version 1.
Example:
def get_current_user(param):
return current_user
class Movie(Entity):
title = Field(Unicode(30))
year = Field(Integer)
acts_as_owned(Unicode(17), get_current_user, 12, ['toucher'])
This extension may well be complemented by the excellent 'versioned' extension.
"""
from sqlalchemy import Column, and_
from sqlalchemy.orm import mapper, MapperExtension, EXT_CONTINUE
from elixir.statements import Statement
from elixir.properties import EntityBuilder
__all__ = ['acts_as_owned']
__doc_all__ = []
#
# utility functions
#
def get_entity_where(instance):
clauses = []
for column in instance.table.primary_key.columns:
instance_value = getattr(instance, column.name)
clauses.append(column==instance_value)
return and_(*clauses)
def get_current_user(instance):
gcu = instance.__class__.__get_current_user__
if callable(gcu):
return gcu(instance.__getter_param__)
else:
return gcu
#
# a mapper extension to track the user that insert, or update a record
#
class OwnedMapperExtension(MapperExtension):
def before_insert(self, mapper, connection, instance):
owned_columns = instance.__class__.__owned_column_field_names__
setattr(instance, owned_columns[1], None)
setattr(instance, owned_columns[0], get_current_user(instance))
return EXT_CONTINUE
def before_update(self, mapper, connection, instance):
owned_columns = instance.__class__.__owned_column_field_names__
setattr(instance, owned_columns[1], get_current_user(instance))
return EXT_CONTINUE
owned_mapper_extension = OwnedMapperExtension()
#
# the acts_as_owned statement
#
class OwnedEntityBuilder(EntityBuilder):
def __init__(self, entity, column_type, get_current_user, getter_param=None,
column_field_names=None):
entity._descriptor.add_mapper_extension(owned_mapper_extension)
self.entity = entity
self.add_mapper_extension(owned_mapper_extension)
entity.__current_user_column_type__ = column_type
if column_field_names in [None, []]:
column_field_names = ['created_by', 'updated_by']
entity.__owned_column_field_names__ = column_field_names
if len(column_field_names) < 2:
entity.__owned_column_field_names__.append(column_field_names[0])
if callable(get_current_user):
entity.__get_current_user__ = staticmethod(get_current_user)
entity.__getter_param__ = getter_param
else: entity.__get_current_user__ = get_current_user
def create_non_pk_cols(self):
# add columns for the user who inserted or updated the record, if fields
# don't exist yet. this to allow their definition as relationships etc.
for fieldname in self.entity.__owned_column_field_names__:
if fieldname not in \
[col.name for col in self.entity._descriptor.columns]:
self.entity._descriptor.add_column(Column(fieldname,
self.entity.__current_user_column_type__))
acts_as_owned = Statement(OwnedEntityBuilder)