| 1 | """ |
|---|
| 2 | A plugin for Elixir, which adds and maintains columns for the user that creates, |
|---|
| 3 | and respectivelly updates, a record. |
|---|
| 4 | |
|---|
| 5 | The user may be supplied directly, or a function getting an argument to fetch |
|---|
| 6 | the user may be supplied. the argument (getter_param) is optional, defaulting |
|---|
| 7 | to None. |
|---|
| 8 | |
|---|
| 9 | The field names may be optionally supplied in the column_field_names parameter. |
|---|
| 10 | If column_field_names is None (by default), ['created_by', 'updated_by'], |
|---|
| 11 | will be assumed. The field names may be already defined, in which case they will |
|---|
| 12 | be used as they are. This feature will allow those fields belonging to entity |
|---|
| 13 | relationships. If only one field will be supplied, the same field name will be |
|---|
| 14 | set for inserter and updater, the effect will be to keep the last toucher. In |
|---|
| 15 | combination with the versioned extension, the inserter will be the user who |
|---|
| 16 | touched version 1. |
|---|
| 17 | |
|---|
| 18 | Example: |
|---|
| 19 | |
|---|
| 20 | def get_current_user(param): |
|---|
| 21 | return current_user |
|---|
| 22 | |
|---|
| 23 | class Movie(Entity): |
|---|
| 24 | title = Field(Unicode(30)) |
|---|
| 25 | year = Field(Integer) |
|---|
| 26 | acts_as_owned(Unicode(17), get_current_user, 12, ['toucher']) |
|---|
| 27 | |
|---|
| 28 | |
|---|
| 29 | This extension may well be complemented by the excellent 'versioned' extension. |
|---|
| 30 | """ |
|---|
| 31 | |
|---|
| 32 | from sqlalchemy import Column, and_ |
|---|
| 33 | from sqlalchemy.orm import mapper, MapperExtension, EXT_CONTINUE |
|---|
| 34 | |
|---|
| 35 | from elixir.statements import Statement |
|---|
| 36 | from elixir.properties import EntityBuilder |
|---|
| 37 | |
|---|
| 38 | __all__ = ['acts_as_owned'] |
|---|
| 39 | __doc_all__ = [] |
|---|
| 40 | |
|---|
| 41 | # |
|---|
| 42 | # utility functions |
|---|
| 43 | # |
|---|
| 44 | |
|---|
| 45 | def get_entity_where(instance): |
|---|
| 46 | clauses = [] |
|---|
| 47 | for column in instance.table.primary_key.columns: |
|---|
| 48 | instance_value = getattr(instance, column.name) |
|---|
| 49 | clauses.append(column==instance_value) |
|---|
| 50 | return and_(*clauses) |
|---|
| 51 | |
|---|
| 52 | def get_current_user(instance): |
|---|
| 53 | gcu = instance.__class__.__get_current_user__ |
|---|
| 54 | if callable(gcu): |
|---|
| 55 | return gcu(instance.__getter_param__) |
|---|
| 56 | else: |
|---|
| 57 | return gcu |
|---|
| 58 | |
|---|
| 59 | # |
|---|
| 60 | # a mapper extension to track the user that insert, or update a record |
|---|
| 61 | # |
|---|
| 62 | |
|---|
| 63 | class OwnedMapperExtension(MapperExtension): |
|---|
| 64 | |
|---|
| 65 | def before_insert(self, mapper, connection, instance): |
|---|
| 66 | owned_columns = instance.__class__.__owned_column_field_names__ |
|---|
| 67 | setattr(instance, owned_columns[1], None) |
|---|
| 68 | setattr(instance, owned_columns[0], get_current_user(instance)) |
|---|
| 69 | return EXT_CONTINUE |
|---|
| 70 | |
|---|
| 71 | def before_update(self, mapper, connection, instance): |
|---|
| 72 | owned_columns = instance.__class__.__owned_column_field_names__ |
|---|
| 73 | setattr(instance, owned_columns[1], get_current_user(instance)) |
|---|
| 74 | return EXT_CONTINUE |
|---|
| 75 | |
|---|
| 76 | owned_mapper_extension = OwnedMapperExtension() |
|---|
| 77 | |
|---|
| 78 | |
|---|
| 79 | # |
|---|
| 80 | # the acts_as_owned statement |
|---|
| 81 | # |
|---|
| 82 | |
|---|
| 83 | class OwnedEntityBuilder(EntityBuilder): |
|---|
| 84 | |
|---|
| 85 | def __init__(self, entity, column_type, get_current_user, getter_param=None, |
|---|
| 86 | column_field_names=None): |
|---|
| 87 | entity._descriptor.add_mapper_extension(owned_mapper_extension) |
|---|
| 88 | self.entity = entity |
|---|
| 89 | self.add_mapper_extension(owned_mapper_extension) |
|---|
| 90 | |
|---|
| 91 | entity.__current_user_column_type__ = column_type |
|---|
| 92 | if column_field_names in [None, []]: |
|---|
| 93 | column_field_names = ['created_by', 'updated_by'] |
|---|
| 94 | entity.__owned_column_field_names__ = column_field_names |
|---|
| 95 | if len(column_field_names) < 2: |
|---|
| 96 | entity.__owned_column_field_names__.append(column_field_names[0]) |
|---|
| 97 | if callable(get_current_user): |
|---|
| 98 | entity.__get_current_user__ = staticmethod(get_current_user) |
|---|
| 99 | entity.__getter_param__ = getter_param |
|---|
| 100 | else: entity.__get_current_user__ = get_current_user |
|---|
| 101 | |
|---|
| 102 | def create_non_pk_cols(self): |
|---|
| 103 | # add columns for the user who inserted or updated the record, if fields |
|---|
| 104 | # don't exist yet. this to allow their definition as relationships etc. |
|---|
| 105 | for fieldname in self.entity.__owned_column_field_names__: |
|---|
| 106 | if fieldname not in \ |
|---|
| 107 | [col.name for col in self.entity._descriptor.columns]: |
|---|
| 108 | self.entity._descriptor.add_column(Column(fieldname, |
|---|
| 109 | self.entity.__current_user_column_type__)) |
|---|
| 110 | |
|---|
| 111 | acts_as_owned = Statement(OwnedEntityBuilder) |
|---|