hello friends,

i've done a couple of extensions i found useful, and since their
simplicity i'm sure they might also serve as examples.

all my scarce extensions knowledge comes from analyze of elixir and
existing extensions, and these help with simplifying my code (without
giving up functionality) and avoid patching elixir in order to achieve
some pervert solutions.

please enjoy my work as, we all enjoy the work of others, especially
in the freeware world.

1. inverse_orphans:

in persons.py you have
class Person(Entity):
    id = Field(Integer, pk=True)
    name = Field(Unicode(20))

in books.py you have
from person import *
class Book(Entity):
    id = Field(Integer, pk=True)
    name = Field(Unicode(50))
    author = ManyToOne('Person', inverse='books_authored')

    inverse_orphans()

since a Person is a quite generic entity, you can't be sure he should
author books or cut throats or both, but in a suitable application, i
might ask for the books a person has authored etc. hence, will
silently add Person a books_authored OneToMany fully functional
relationship.

2. record_owned
is an extension made after acts_as_versioned, just that it will add
the facility of auto recording the person that inserted and updated
each record in an Entity.

there is also some advance over the versioned extension, in that you
may also fill manually defined fields, to allow them be part of a
relationship and/or have additional information.

i will also patch the versioned extension to allow this customization,
keeping the present functionality as default.

thanks for your consideration,
alex

--~--~---------~--~----~------------~-------~--~----~
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
-~----------~----~----~----~------~----~------~--~---

"""
inverse_orphans Elixir Statement Generator

===============
inverse_orphans
===============

i am using an identity model module from a third party, 
having, among others, an User class. 
this model file might well be upgraded by it's supplier. 
in another model file, which imports the identity model entities, 
i have a Person class, which has a ManyToOne relationship with the User table. 
However i'd like to also know the Person which references a given User, adding 
OneToMany relationships to User, will need to be maintained everytime the 
supplier upgrades identity model. 
to implement this, i am giving an inverse name on the ManyToOne side, and 
adding an after_mapper action to create the OneToMany relationship.
"""

from elixir.statements import Statement
from elixir import OneToOne, OneToMany, ManyToOne, ManyToMany

__all__ = ['inverse_orphans']
__doc_all__ = __all__


#TODO: inherit from entity builder
class inverse_orphans_entity_builder(object):
    """An inverse_orphans Elixir Statement object"""
    
    def __init__(self, entity):
        self.entity = entity

    def before_table(self):
        '''
        if we name an inverse relationship which is not already defined on the
        target, here we create the inverse relationship on the target.
        should run this for each relationship property.
        '''
        for r in self.entity._descriptor.relationships:
            desc = r.target._descriptor
            if r.inverse_name and desc.find_relationship(r.inverse_name) is None:
                if type(r) == ManyToOne:
                    if 'unique' in r.column_kwargs and r.column_kwargs['unique']:
                        invclass = OneToOne
                    else: invclass = OneToMany
                elif type(r) == ManyToMany:
                    invclass = ManyToMany
                else: invclass = None
            else: invclass = None
            if invclass:
                invprop = invclass(r.entity.__name__, inverse=r.name)
                invprop._target = r.entity
                setattr(r.target, r.inverse_name, invprop)
                invprop.attach(r.target, r.inverse_name)


inverse_orphans = Statement(inverse_orphans_entity_builder)
"""
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), ['record_inserter', 'record_updater'], 
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):
        owner_columns = instance.__class__.__column_field_names__
        setattr(instance, owner_columns[1], None)
        setattr(instance, owner_columns[0], get_current_user(instance))
        return EXT_CONTINUE
        
    def before_update(self, mapper, connection, instance):
        owner_columns = instance.__class__.__column_field_names__
        values = instance.table.select(get_entity_where(instance)).execute().fetchone()
        setattr(instance, owner_columns[0], values[owner_columns[0]])
        setattr(instance, owner_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 = ['record_inserter', 'record_updater']
        entity.__column_field_names__ = column_field_names
        if len(entity.__column_field_names__) < 2:
            entity.__column_field_names__.append(entity.__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.__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)
from elixir import *
from inverse_orphans import inverse_orphans

metadata.bind = "sqlite:///:memory:"
metadata.bind.echo = True

class Movie(Entity):
    title = Field(Unicode(30))
    year = Field(Integer)
    description = Field(Text)
    director = ManyToOne('Director', inverse='movies')
    main_genre = ManyToOne('Genre', inverse='movies_with_main')
    inverse_orphans()

    def __repr__(self):
        return '<Movie "%s" (%d)>' % (self.title, self.year)

class Director(Entity):
    name = Field(Unicode(60))
    #movies = OneToMany('Movie', inverse='director')

    def __repr__(self):
        return '<Director "%s">' % self.name

class Genre(Entity):
    name = Field(Unicode(15), primary_key=True)
    abbrev = Field(Unicode(15), primary_key=True)
    movies = ManyToMany('Movie', inverse='genres')
    inverse_orphans()
    
    def __repr__(self):
        return '<Genre "%s">' % self.name
    
setup_all(True)
#create_all()

scifi = Genre(name="Science-Fiction", abbrev='sf')

rscott = Director(name="Ridley Scott")
glucas = Director(name="George Lucas")
alien = Movie(title="Alien", year=1979, director=rscott, 
              genres=[scifi, Genre(name="Horror", abbrev='hr')],
              main_genre=scifi)
swars = Movie(title="Star Wars", year=1977, director=glucas, genres=[scifi],
              main_genre=scifi)
brunner = Movie(title="Blade Runner", year=1982, director=rscott, genres=[scifi],
                main_genre=scifi)

#rscott.movies.append(brunner)
#rscott.movies.append(alien)
#swars.director = glucas


print rscott.movies
print glucas.movies
print alien.genres

from elixir import *
from elixir.ext.versioned import acts_as_versioned
from record_owner import acts_as_owned

metadata.bind = "sqlite:///:memory:"
#metadata.bind.echo = True

current_user = u'alex'

def get_current_user(param=None):
    return current_user

class Movie(Entity):
    title = Field(Unicode(30))
    year = Field(Integer)
    inserter = Field(Unicode(30)) # the field may be manually defined
#    acts_as_owned(Unicode(17), get_current_user, 12, ['inserter', 'updater'])
    acts_as_owned(Unicode(17), get_current_user, column_field_names=['toucher'])
#    acts_as_owned(Unicode(17), get_current_user, 12, [])
    acts_as_versioned()

    def __repr__(self):
#        return '<Movie "%s" (%d) inserted by "%s", updated by "%s" version %d, since %s>' % \
#               (self.title, self.year, self.inserter, self.updater, self.version, str(self.timestamp))
        return '<Movie "%s" (%d) touched by "%s" version %d, since %s>' % \
               (self.title, self.year, self.toucher, self.version, str(self.timestamp))
#        return '<Movie "%s" (%d) inserted by "%s", updated by "%s" version %d, since %s>' % \
#               (self.title, self.year, self.record_inserter, self.record_updater, self.version, str(self.timestamp))


setup_all(True)
create_all()

alien = Movie(title=u'Alien', year=1979)
#this way, the inserter will not be set
#i = Movie.table.insert()
#i.execute(dict(title=u'Alien', year=1979))
alien = Movie.query.filter_by(title=u'Alien').one()

session.flush()
print alien

current_user = u'alex2'
alien.title = u'alien2'
session.flush()
alien = Movie.query.filter(Movie.title.like(u'%lien%')).one()
print alien

Reply via email to