Package Camelot :: Package camelot :: Package model :: Module authentication |
[frames] | no frames]

# Source Code for Module Camelot.camelot.model.authentication

      1  #  ============================================================================

      2  #

      3  #  Copyright (C) 2007-2008 Conceptive Engineering bvba. All rights reserved.

      4  #  www.conceptive.be / project-camelot@conceptive.be

      5  #

      6  #  This file is part of the Camelot Library.

      7  #

      8  #  This file may be used under the terms of the GNU General Public

      9  #  License version 2.0 as published by the Free Software Foundation

     10  #  and appearing in the file LICENSE.GPL included in the packaging of

     11  #  this file.  Please review the following information to ensure GNU

     12  #  General Public Licensing requirements will be met:

     13  #  http://www.trolltech.com/products/qt/opensource.html

     14  #

     15  #  If you are unsure which license is appropriate for your use, please

     16  #  review the following information:

     17  #  http://www.trolltech.com/products/qt/licensing.html or contact

     18  #  project-camelot@conceptive.be.

     19  #

     20  #  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE

     21  #  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

     22  #

     23  #  For use of this library in commercial applications, please contact

     24  #  project-camelot@conceptive.be

     25  #

     26  #  ============================================================================

     27

     28  from camelot.model import metadata
     29  from camelot.model import entities
     30  from camelot.view.controls import delegates
     31

     32  from elixir.entity import Entity
     33  from elixir.options import using_options
     34  from elixir.fields import Field
     35  from sqlalchemy.types import Date, Unicode, Integer, DateTime, Boolean
     36  from elixir.relationships import ManyToOne, OneToMany
     37  from elixir.properties import ColumnProperty
     38  from sqlalchemy.sql.expression import and_
     39  """Set of classes to store persons, organizations, relationships and

     40  permissions

     41

     42  These structures are modeled like described in 'The Data Model Resource Book'

     43  by Len Silverston, Chapter 2

     44  """
     45  from sqlalchemy import sql
     46

     47  import camelot.types
     48

     49  #from camelot.model import *

     50

     51  __metadata__ = metadata
     52

     53  from camelot.model.synchronization import is_synchronized
     54  from camelot.core.document import documented_entity
     55  from camelot.core.utils import ugettext_lazy as _
     56

     57  from camelot.view.elixir_admin import EntityAdmin
     58  from camelot.view.forms import Form, TabForm, HBoxForm, WidgetOnlyForm
     59  from camelot.admin.form_action import FormActionFromModelFunction
     60  import datetime
     61  import threading
     62

     63

     64  _current_authentication_ = threading.local()
     65

     66 -def end_of_times():
     67      return datetime.date( year = 2400, month = 12, day = 31 )
     68

     69  from camelot.model.type_and_status import type_3_status
     70

     71 -def getCurrentAuthentication():
     72      """Get the currently logged in person"""
     73      global _current_authentication_
     74      if not hasattr( _current_authentication_, 'mechanism' ):
     75          import getpass
     76          _current_authentication_.mechanism = UsernameAuthenticationMechanism.getOrCreateAuthentication( unicode( getpass.getuser() ) )
     77      return _current_authentication_.mechanism
     78

     79 -def updateLastLogin():
     80      """Update the last login of the current person to now"""
     81      from elixir import session
     82      authentication = getCurrentAuthentication()
     83      authentication.last_login = datetime.datetime.now()
     84      session.flush( [authentication] )
     85

     86 -class PartyRelationship( Entity ):
     87      using_options( tablename = 'party_relationship' )
     88      from_date = Field( Date(), default = datetime.date.today, required = True, index = True )
     89      thru_date = Field( Date(), default = end_of_times, required = True, index = True )
     90      comment = Field( camelot.types.RichText() )
     91      is_synchronized( 'synchronized', lazy = True )
     92

     93 -    class Admin( EntityAdmin ):
     94          verbose_name = _('Relationship')
     95          verbose_name_plural = _('Relationships')
     96          list_display = ['established_from', 'established_to', 'from_date', 'thru_date']
     97

     98 -class EmployerEmployee( PartyRelationship ):
     99      """Relation from employer to employee"""
    100      using_options( tablename = 'party_relationship_empl', inheritance = 'multi' )
    101      established_from = ManyToOne( 'Organization', required = True, ondelete = 'cascade', onupdate = 'cascade' )    # the employer
    102      established_to = ManyToOne( 'Person', required = True, ondelete = 'cascade', onupdate = 'cascade' )            # the employee
    103

    104      @ColumnProperty
    105 -    def first_name( self ):
    106          return sql.select( [Person.first_name], Person.party_id == self.established_to_party_id )
    107

    108      @ColumnProperty
    109 -    def last_name( self ):
    110          return sql.select( [Person.last_name], Person.party_id == self.established_to_party_id )
    111

    112      @ColumnProperty
    113 -    def social_security_number( self ):
    114          return sql.select( [Person.social_security_number], Person.party_id == self.established_to_party_id )
    115

    116 -    def __unicode__( self ):
    117          return u'%s %s %s' % ( unicode( self.established_to ), _('Employed by'),unicode( self.established_from ) )
    118

    119 -    class Admin( PartyRelationship.Admin ):
    120          verbose_name = _('Employment relation')
    121          verbose_name_plural = _('Employment relations')
    122          list_filter = ['established_from.name']
    123          list_search = ['established_from.name', 'established_to.first_name', 'established_to.last_name']
    124

    125 -    class EmployeeAdmin( EntityAdmin ):
    126          verbose_name = _('Employee')
    127          list_display = ['established_to', 'from_date', 'thru_date']
    128          form_display = ['established_to', 'comment', 'from_date', 'thru_date']
    129          field_attributes = {'established_to':{'name':_( 'Name' )}}
    130

    131 -    class EmployerAdmin( EntityAdmin ):
    132          verbose_name = _('Employer')
    133          list_display = ['established_from', 'from_date', 'thru_date']
    134          form_display = ['established_from', 'comment', 'from_date', 'thru_date']
    135          field_attributes = {'established_from':{'name':_( 'Name' )}}
    136

    137 -class DirectedDirector( PartyRelationship ):
    138      """Relation from a directed organization to a director"""
    139      using_options( tablename = 'party_relationship_dir', inheritance = 'multi' )
    140      established_from = ManyToOne( 'Organization', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    141      established_to = ManyToOne( 'Party', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    142      title = Field( Unicode( 256 ) )
    143      represented_by = OneToMany( 'RepresentedRepresentor', inverse = 'established_to' )
    144

    145 -    class Admin( PartyRelationship.Admin ):
    146          verbose_name = _('Direction structure')
    147          verbose_name_plural = _('Direction structures')
    148          list_display = ['established_from', 'established_to', 'title', 'represented_by']
    149          list_search = ['established_from.full_name', 'established_to.full_name']
    150          field_attributes = {'established_from':{'name':_('Organization')},

    151                              'established_to':{'name':_('Director')}}
    152

    153 -    class DirectorAdmin( Admin ):
    154          verbose_name = _('Director')
    155          list_display = ['established_to', 'title', 'from_date', 'thru_date']
    156          form_display = ['established_to', 'title', 'from_date', 'thru_date', 'represented_by', 'comment']
    157

    158 -    class DirectedAdmin( Admin ):
    159          verbose_name = _('Directed organization')
    160          list_display = ['established_from', 'title', 'from_date', 'thru_date']
    161          form_display = ['established_from', 'title', 'from_date', 'thru_date', 'represented_by', 'comment']
    162

    163 -class RepresentedRepresentor( Entity ):
    164      """Relation from a representing party to the person representing the party"""
    165      using_options( tablename = 'party_representor' )
    166      from_date = Field( Date(), default = datetime.date.today, required = True, index = True )
    167      thru_date = Field( Date(), default = end_of_times, required = True, index = True )
    168      comment = Field( camelot.types.RichText() )
    169      established_from = ManyToOne( 'Person', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    170      established_to = ManyToOne( 'DirectedDirector', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    171

    172 -    class Admin( EntityAdmin ):
    173          verbose_name = _('Represented by')
    174          list_display = ['established_from', 'from_date', 'thru_date']
    175          form_display = ['established_from', 'from_date', 'thru_date', 'comment']
    176          field_attributes = {'established_from':{'name':_( 'Name' )}}
    177

    178 -class SupplierCustomer( PartyRelationship ):
    179      """Relation from supplier to customer"""
    180      using_options( tablename = 'party_relationship_suppl', inheritance = 'multi' )
    181      established_from = ManyToOne( 'Party', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    182      established_to = ManyToOne( 'Party', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    183

    184 -    class Admin( PartyRelationship.Admin ):
    185          verbose_name = _('Supplier - Customer')
    186          list_display = ['established_from', 'established_to', 'from_date', 'thru_date']
    187

    188 -    class CustomerAdmin( EntityAdmin ):
    189          verbose_name = _('Customer')
    190          list_display = ['established_to', ]
    191          form_display = ['established_to', 'comment', 'from_date', 'thru_date']
    192          field_attributes = {'established_to':{'name':_( 'Name' )}}
    193

    194 -    class SupplierAdmin( EntityAdmin ):
    195          verbose_name = _('Supplier')
    196          list_display = ['established_from', ]
    197          form_display = ['established_from', 'comment', 'from_date', 'thru_date']
    198          field_attributes = {'established_from':{'name':_( 'Name' )}}
    199

    200 -class SharedShareholder( PartyRelationship ):
    201      """Relation from a shared organization to a shareholder"""
    202      using_options( tablename = 'party_relationship_shares', inheritance = 'multi' )
    203      established_from = ManyToOne( 'Organization', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    204      established_to = ManyToOne( 'Party', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    205      shares = Field( Integer() )
    206

    207 -    class Admin( PartyRelationship.Admin ):
    208          verbose_name = _('Shareholder structure')
    209          verbose_name_plural = _('Shareholder structures')
    210          list_display = ['established_from', 'established_to', 'shares',]
    211          list_search = ['established_from.full_name', 'established_to.full_name']
    212          field_attributes = {'established_from':{'name':_('Organization')},

    213                              'established_to':{'name':_('Shareholder')}}
    214

    215 -    class ShareholderAdmin( Admin ):
    216          verbose_name = _('Shareholder')
    217          list_display = ['established_to', 'shares', 'from_date', 'thru_date']
    218          form_display = ['established_to', 'shares', 'from_date', 'thru_date', 'comment']
    219          form_size = (500, 300)
    220

    221 -    class SharedAdmin( Admin ):
    222          verbose_name = _('Shares')
    223          verbose_name_plural = _('Shares')
    224          list_display = ['established_from', 'shares', 'from_date', 'thru_date']
    225          form_display = ['established_from', 'shares', 'from_date', 'thru_date', 'comment']
    226          form_size = (500, 300)
    227

    228 -class AddressAdmin( EntityAdmin ):
    229      """Admin with only the Address information and not the Party information"""
    230      list_display = ['address', 'comment']
    231      form_display = ['address', 'comment', 'from_date', 'thru_date']
    232

    233 -class PartyContactMechanismAdmin( EntityAdmin ):
    234      form_size = ( 700, 200 )
    235      verbose_name = _('Contact mechanism')
    236      verbose_name_plural = _('Contact mechanisms')
    237      list_search = ['party_name', 'mechanism']
    238      list_display = ['party_name', 'mechanism', 'comment', 'from_date', ]
    239      form_display = Form( ['contact_mechanism', 'comment', 'from_date', 'thru_date', ] )
    240      field_attributes = {'party_name':{'minimal_column_width':25, 'editable':False},

    241                          'mechanism':{'minimal_column_width':25,'editable':False}}
    242

    243 -class PartyPartyContactMechanismAdmin( PartyContactMechanismAdmin ):
    244      list_search = ['party_name', 'mechanism']
    245      list_display = ['contact_mechanism', 'comment', 'from_date', ]
    246

    247 -class Party( Entity ):
    248      """Base class for persons and organizations.  Use this base class to refer to either persons or

    249      organisations in building authentication systems, contact management or CRM"""
    250      using_options( tablename = 'party' )
    251      is_synchronized( 'synchronized', lazy = True )
    252      addresses = OneToMany( 'PartyAddress', lazy = True, cascade="all, delete, delete-orphan" )
    253      contact_mechanisms = OneToMany( 'PartyContactMechanism', lazy = True, cascade='all, delete, delete-orphan' )
    254      shares = OneToMany( 'SharedShareholder', inverse = 'established_to', cascade='all, delete, delete-orphan' )
    255      directed_organizations = OneToMany( 'DirectedDirector', inverse = 'established_to', cascade='all, delete, delete-orphan' )
    256      status = OneToMany( type_3_status( 'Party', metadata, entities ), cascade='all, delete, delete-orphan' )
    257

    258      @property
    259 -    def name( self ):
    260          return ''
    261

    262      @ColumnProperty
    263 -    def email( self ):
    264

    265          cm = ContactMechanism
    266          pcm = PartyContactMechanism
    267

    268          return sql.select( [cm.mechanism],

    269                            whereclause = and_( pcm.table.c.party_id == self.id,

    270                                             cm.table.c.mechanism.like( ( u'email', u'%' ) ) ),

    271                            from_obj = [cm.table.join( pcm.table )] ).limit(1)
    272

    273      @ColumnProperty
    274 -    def phone( self ):
    275

    276          cm = ContactMechanism
    277          pcm = PartyContactMechanism
    278

    279          return sql.select( [cm.mechanism],

    280                            whereclause = and_( pcm.table.c.party_id == self.id,

    281                                             cm.table.c.mechanism.like( ( u'phone', u'%' ) ) ),

    282                            from_obj = [cm.table.join( pcm.table )] ).limit(1)
    283

    284      @ColumnProperty
    285 -    def full_name( self ):
    286          aliased_organisation = Organization.table.alias( 'organisation_alias' )
    287          aliased_person = Person.table.alias( 'person_alias' )
    288          aliased_party = Party.table.alias( 'party_alias' )
    289          return sql.functions.coalesce( sql.select( [sql.functions.coalesce(aliased_person.c.first_name,'') + ' ' + sql.functions.coalesce(aliased_person.c.last_name, '')],

    290                                                    whereclause = and_( aliased_party.c.id == self.id ),

    291                                                    from_obj = [aliased_party.join( aliased_person, aliased_person.c.party_id == aliased_party.c.id )] ).limit( 1 ).as_scalar(),

    292                                        sql.select( [aliased_organisation.c.name],

    293                                                   whereclause = and_( aliased_party.c.id == self.id ),

    294                                                   from_obj = [aliased_party.join( aliased_organisation, aliased_organisation.c.party_id == aliased_party.c.id )] ).limit( 1 ).as_scalar() )
    295

    296 -    class Admin( EntityAdmin ):
    297          verbose_name = _('Party')
    298          verbose_name_plural = _('Parties')
    299          list_display = ['name', 'email', 'phone'] # don't use full name, since it might be None for new objects
    300          list_search = ['full_name']
    301          form_display = ['addresses', 'contact_mechanisms', 'shares', 'directed_organizations']
    302          field_attributes = dict(addresses = {'admin':AddressAdmin},

    303                                  contact_mechanisms = {'admin':PartyPartyContactMechanismAdmin},

    304                                  suppliers = {'admin':SupplierCustomer.SupplierAdmin},

    305                                  customers = {'admin':SupplierCustomer.CustomerAdmin},

    306                                  employers = {'admin':EmployerEmployee.EmployerAdmin},

    307                                  employees = {'admin':EmployerEmployee.EmployeeAdmin},

    308                                  directed_organizations = {'admin':DirectedDirector.DirectedAdmin},

    309                                  directors = {'admin':DirectedDirector.DirectorAdmin},

    310                                  shares = {'admin':SharedShareholder.SharedAdmin},

    311                                  shareholders = {'admin':SharedShareholder.ShareholderAdmin},

    312                                  sex = dict( choices = lambda obj:[( u'M', _('male') ), ( u'F', _('female') )], ),

    313                                  name = dict( minimal_column_width = 50 ),

    314                                  email = dict( editable = False, minimal_column_width = 20 ),

    315                                  phone = dict( editable = False, minimal_column_width = 20 )

    316                                  )
    317

    318 -class Organization( Party ):
    319      """An organization represents any internal or external organization.  Organizations can include

    320      businesses and groups of individuals"""
    321      using_options( tablename = 'organization', inheritance = 'multi' )
    322      name = Field( Unicode( 50 ), required = True, index = True )
    323      logo = Field( camelot.types.Image( upload_to = 'organization-logo' ), deferred = True )
    324      tax_id = Field( Unicode( 20 ) )
    325      directors = OneToMany( 'DirectedDirector', inverse = 'established_from', cascade='all, delete, delete-orphan' )
    326      employees = OneToMany( 'EmployerEmployee', inverse = 'established_from', cascade='all, delete, delete-orphan' )
    327      suppliers = OneToMany( 'SupplierCustomer', inverse = 'established_to', cascade='all, delete, delete-orphan' )
    328      customers = OneToMany( 'SupplierCustomer', inverse = 'established_from', cascade='all, delete, delete-orphan' )
    329      shareholders = OneToMany( 'SharedShareholder', inverse = 'established_from', cascade='all, delete, delete-orphan' )
    330

    331 -    def __unicode__( self ):
    332          return self.name
    333

    334      @property
    335 -    def number_of_shares_issued( self ):
    336          return sum( ( shareholder.shares for shareholder in self.shareholders ), 0 )
    337

    338 -    class Admin( Party.Admin ):
    339          verbose_name = _( 'Organization' )
    340          verbose_name_plural = _( 'Organizations' )
    341          list_display = ['name', 'tax_id', 'email', 'phone']
    342          form_display = TabForm( [( _('Basic'), Form( ['name', 'tax_id', 'addresses', 'contact_mechanisms'] ) ),

    343                                  ( _('Employment'), Form( ['employees'] ) ),

    344                                  ( _('Customers'), Form( ['customers'] ) ),

    345                                  ( _('Suppliers'), Form( ['suppliers'] ) ),

    346                                  ( _('Corporate'), Form( ['directors', 'shareholders', 'shares'] ) ),

    347                                  ( _('Branding'), Form( ['logo'] ) ),

    348                                  ( _('Status'), Form( ['status'] ) ),

    349                                  ] )
    350

    351  Organization = documented_entity()( Organization )
    352

    353 -class AuthenticationMechanism( Entity ):
    354      using_options( tablename = 'authentication_mechanism' )
    355      last_login = Field( DateTime() )
    356      is_active = Field( Boolean, default = True, index = True )
    357

    358 -    class Admin( EntityAdmin ):
    359          verbose_name = _('Authentication mechanism')
    360          list_display = ['last_login', 'is_active']
    361

    362 -class UsernameAuthenticationMechanism( AuthenticationMechanism ):
    363      using_options( tablename = 'authentication_mechanism_username', inheritance = 'multi' )
    364      username = Field( Unicode( 40 ), required = True, index = True, unique = True )
    365      password = Field( Unicode( 200 ), required = False, index = False, default = None )
    366

    367      @classmethod
    368 -    def getOrCreateAuthentication( cls, username ):
    369          authentication = cls.query.filter_by( username = username ).first()
    370          if not authentication:
    371              authentication = cls( username = username )
    372              from elixir import session
    373              session.flush( [authentication] )
    374          return authentication
    375

    376 -    def __unicode__( self ):
    377          return self.username
    378

    379 -    class Admin( EntityAdmin ):
    380          verbose_name = _('Authentication mechanism')
    381          list_display = ['username', 'last_login', 'is_active']
    382

    383 -class Person( Party ):
    384      """Person represents natural persons

    385      """
    386      using_options( tablename = 'person', inheritance = 'multi' )
    387      first_name = Field( Unicode( 40 ), required = True )
    388      last_name = Field( Unicode( 40 ), required = True )
    389      middle_name = Field( Unicode( 40 ) )
    390      personal_title = Field( Unicode( 10 ) )
    391      suffix = Field( Unicode( 3 ) )
    392      sex = Field( Unicode( 1 ), default = u'M' )
    393      birthdate = Field( Date() )
    394      martial_status = Field( Unicode( 1 ) )
    395      social_security_number = Field( Unicode( 12 ) )
    396      passport_number = Field( Unicode( 20 ) )
    397      passport_expiry_date = Field( Date() )
    398      is_staff = Field( Boolean, default = False, index = True )
    399      is_superuser = Field( Boolean, default = False, index = True )
    400      picture = Field( camelot.types.Image( upload_to = 'person-pictures' ), deferred = True )
    401      comment = Field( camelot.types.RichText() )
    402      employers = OneToMany( 'EmployerEmployee', inverse = 'established_to', cascade='all, delete, delete-orphan' )
    403

    404      @property
    405 -    def note(self):
    406          for person in self.__class__.query.filter_by(first_name=self.first_name, last_name=self.last_name):
    407              if person!=self:
    408                  return _('A person with the same name allready exists')
    409

    410      @property
    411 -    def name( self ):
    412          # we don't use full name in here, because for new objects, full name will be None, since

    413          # it needs to be fetched from the db first

    414          return u'%s %s' % ( self.first_name, self.last_name )
    415

    416 -    def __unicode__( self ):
    417          return self.name
    418

    419 -    class Admin( Party.Admin ):
    420          verbose_name = _( 'Person' )
    421          verbose_name_plural = _( 'Persons' )
    422          list_display = ['first_name', 'last_name', 'email', 'phone']
    423          form_display = TabForm( [( _('Basic'), Form( [HBoxForm( [Form( [WidgetOnlyForm('note'), 'first_name', 'last_name', 'sex'] ),

    424                                                            Form( ['picture', ] ),

    425                                                           ] ),

    426                                                           'contact_mechanisms', 'comment', ], scrollbars = False ) ),

    427                                  ( _('Official'), Form( ['birthdate', 'social_security_number', 'passport_number',

    428                                                          'passport_expiry_date', 'addresses', ], scrollbars = False ) ),

    429                                  ( _('Work'), Form( ['employers', 'directed_organizations', 'shares'], scrollbars = False ) ),

    430                                  ( _('Status'), Form( ['status'] ) ),

    431                                  ] )
    432          field_attributes = dict( Party.Admin.field_attributes )
    433          field_attributes['note'] = {'delegate':delegates.NoteDelegate}
    434

    435  Person = documented_entity()( Person )
    436

    437 -class GeographicBoundary( Entity ):
    438      using_options( tablename = 'geographic_boundary' )
    439      code = Field( Unicode( 10 ) )
    440      name = Field( Unicode( 40 ), required = True )
    441

    442      @ColumnProperty
    443 -    def full_name( self ):
    444          return self.code + ' ' + self.name
    445

    446 -    def __unicode__( self ):
    447          return u'%s %s' % ( self.code, self.name )
    448

    449 -class Country( GeographicBoundary ):
    450      using_options( tablename = 'geographic_boundary_country', inheritance = 'multi' )
    451

    452      @classmethod
    453 -    def getOrCreate( cls, code, name ):
    454          country = Country.query.filter_by( code = code ).first()
    455          if not country:
    456              from elixir import session
    457              country = Country( code = code, name = name )
    458              session.flush( [country] )
    459          return country
    460

    461 -    class Admin( EntityAdmin ):
    462          form_size = ( 700, 150 )
    463          verbose_name = _('Country')
    464          verbose_name_plural = _('Countries')
    465          list_display = ['name', 'code']
    466

    467 -class City( GeographicBoundary ):
    468      using_options( tablename = 'geographic_boundary_city', inheritance = 'multi' )
    469      country = ManyToOne( 'Country', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    470

    471      @classmethod
    472 -    def getOrCreate( cls, country, code, name ):
    473          city = City.query.filter_by( code = code, country = country ).first()
    474          if not city:
    475              from elixir import session
    476              city = City( code = code, name = name, country = country )
    477              session.flush( [city] )
    478          return city
    479

    480 -    class Admin( EntityAdmin ):
    481          verbose_name = _('City')
    482          verbose_name_plural = _('Cities')
    483          form_size = ( 700, 150 )
    484          list_display = ['code', 'name', 'country']
    485

    486 -class Address( Entity ):
    487      using_options( tablename = 'address' )
    488      street1 = Field( Unicode( 128 ), required = True )
    489      street2 = Field( Unicode( 128 ) )
    490      city = ManyToOne( 'City', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    491      is_synchronized( 'synchronized', lazy = True )
    492

    493      @ColumnProperty
    494 -    def name( self ):
    495          return sql.select( [self.street1 + ', ' + GeographicBoundary.full_name],

    496                             whereclause = (GeographicBoundary.id==self.city_geographicboundary_id))
    497

    498      @classmethod
    499 -    def getOrCreate( cls, street1, street2, city ):
    500          address = cls.query.filter_by( street1 = street1, street2 = street2, city = city ).first()
    501          if not address:
    502              from elixir import session
    503              address = cls( street1 = street1, street2 = street2, city = city )
    504              session.flush( [address] )
    505          return address
    506

    507 -    def __unicode__( self ):
    508          return u'%s, %s' % ( self.street1 or '', self.city or '' )
    509

    510 -    def showMap( self ):
    511          from PyQt4 import QtGui, QtCore
    512          QtGui.QDesktopServices.openUrl ( QtCore.QUrl( 'http://www.google.be/maps?f=q&source=s_q&geocode=%s&q=%s+%s' % ( self.city.country.code, self.street1, self.city.name ) ) )
    513

    514 -    class Admin( EntityAdmin ):
    515          verbose_name = _('Address')
    516          verbose_name_plural = _('Addresses')
    517          list_display = ['street1', 'street2', 'city']
    518          form_size = ( 700, 150 )
    519          field_attributes = {'street1':{'minimal_column_width':30}}
    520          form_actions = [FormActionFromModelFunction( 'Show on map', lambda address:address.showMap() )]
    521

    522  Address = documented_entity()( Address )
    523

    524 -class PartyAddress( Entity ):
    525      using_options( tablename = 'party_address' )
    526      party = ManyToOne( 'Party', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    527      address = ManyToOne( 'Address', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    528      from_date = Field( Date(), default = datetime.date.today, required = True, index = True )
    529      thru_date = Field( Date(), default = end_of_times, required = True, index = True )
    530      comment = Field( Unicode( 256 ) )
    531

    532      @ColumnProperty
    533 -    def party_name( self ):
    534          return sql.select( [Party.full_name],

    535                             whereclause = (Party.id==self.party_id))
    536

    537      @ColumnProperty
    538 -    def address_name( self ):
    539          return sql.select( [Address.name],

    540                             whereclause = (Address.id==self.address_id))
    541

    542 -    def __unicode__( self ):
    543          return '%s : %s' % ( unicode( self.party ), unicode( self.address ) )
    544

    545 -    def showMap( self ):
    546          if self.address:
    547              self.address.showMap()
    548

    549 -    class Admin( EntityAdmin ):
    550          verbose_name = _('Address')
    551          verbose_name_plural = _('Addresses')
    552          list_search = ['party_name', 'address_name']
    553          list_display = ['party_name', 'address_name', 'comment']
    554          form_display = ['party', 'address', 'comment', 'from_date', 'thru_date']
    555          form_size = ( 700, 200 )
    556          form_actions = [FormActionFromModelFunction( 'Show on map', lambda address:address.showMap() )]
    557          field_attributes = dict(party_name=dict(editable=False, name='Party', minimal_column_width=30),

    558                                  address_name=dict(editable=False, name='Address', minimal_column_width=30))
    559

    560 -class PartyAddressRoleType( Entity ):
    561      using_options( tablename = 'party_address_role_type' )
    562      code = Field( Unicode( 10 ) )
    563      description = Field( Unicode( 40 ) )
    564

    565 -    class Admin( EntityAdmin ):
    566          verbose_name = _('Address role type')
    567          list_display = ['code', 'description']
    568

    569 -class PartyAuthentication( Entity ):
    570      using_options( tablename = 'party_authentication' )
    571      address = ManyToOne( 'AuthenticationMechanism', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    572      from_date = Field( Date(), default = datetime.date.today, required = True, index = True )
    573      thru_date = Field( Date(), default = end_of_times, required = True, index = True )
    574      comment = Field( Unicode( 256 ) )
    575

    576 -class ContactMechanism( Entity ):
    577      using_options( tablename = 'contact_mechanism' )
    578      mechanism = Field( camelot.types.VirtualAddress( 256 ), required = True )
    579      party_address = ManyToOne( 'PartyAddress', ondelete = 'set null', onupdate = 'cascade' )
    580

    581 -    def __unicode__( self ):
    582          if self.mechanism:
    583              return u'%s : %s' % ( self.mechanism[0], self.mechanism[1] )
    584

    585 -    class Admin( EntityAdmin ):
    586          form_size = ( 700, 150 )
    587          verbose_name = _('Contact mechanism')
    588          list_display = ['mechanism']
    589          form_display = Form( ['mechanism', 'party_address'] )
    590          field_attributes = {'mechanism':{'minimal_column_width':25}}
    591

    592  ContactMechanism = documented_entity()( ContactMechanism )
    593

    594 -class PartyContactMechanism( Entity ):
    595      using_options( tablename = 'party_contact_mechanism' )
    596      party = ManyToOne( 'Party', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    597      contact_mechanism = ManyToOne( 'ContactMechanism', required = True, ondelete = 'cascade', onupdate = 'cascade' )
    598      from_date = Field( Date(), default = datetime.date.today, required = True, index = True )
    599      thru_date = Field( Date(), default = end_of_times, index = True )
    600      comment = Field( Unicode( 256 ) )
    601

    602      @ColumnProperty
    603 -    def mechanism( self ):
    604          return sql.select( [ContactMechanism.mechanism],

    605                             whereclause = (ContactMechanism.id==self.contact_mechanism_id))
    606

    607      @ColumnProperty
    608 -    def party_name( self ):
    609          return sql.select( [Party.full_name],

    610                             whereclause = (Party.id==self.party_id))
    611

    612 -    def __unicode__( self ):
    613          return unicode( self.contact_mechanism )
    614

    615      Admin = PartyContactMechanismAdmin
    616