camelot.camelot.model.authentication

Package camelot :: Package camelot :: Package model :: Module authentication |
[hide private]
[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  """Set of classes to store persons, organizations, relationships and

     28  permissions

     29

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

     31  by Len Silverston, Chapter 2

     32  """
     33

     34  import camelot
     35  import camelot.types
     36

     37  from camelot.model import *
     38  from camelot.model.synchronization import *
     39

     40  __metadata__ = metadata
     41

     42  from camelot.view.elixir_admin import EntityAdmin
     43  from camelot.view.forms import Form, TabForm, VBoxForm, HBoxForm, WidgetOnlyForm
     44  import datetime
     45

     46  _current_person_ = None
     47

     48 -def end_of_times():
     49    return datetime.date(year=2400, month=12, day=31)
     50

     51 -def getCurrentPerson():
     52    """Get the currently logged in person"""
     53    global _current_person_
     54    if not _current_person_:
     55      import getpass
     56      _current_person_ = Person.getOrCreatePerson(unicode(getpass.getuser()))
     57    return _current_person_
     58

     59 -def updateLastLogin():
     60    """Update the last login of the current person to now"""
     61    from elixir import session
     62    person = getCurrentPerson()
     63    person.last_login = datetime.datetime.now()
     64    session.flush([person])
     65

     66 -class PartyRelationship(Entity):
     67    using_options(tablename='party_relationship')
     68    from_date = Field(Date(), default=datetime.date.today, required=True, index=True)
     69    thru_date = Field(Date(), default=end_of_times, required=True, index=True)
     70    comment = Field(Text)
     71    is_synchronized('synchronized', lazy=True)
     72

     73 -class EmployerEmployee(PartyRelationship):
     74    """Relation from employer to employee"""
     75    using_options(tablename='party_relationship_empl', inheritance='multi')
     76    established_from = ManyToOne('Organization', required=True, ondelete='cascade', onupdate='cascade')
     77    established_to = ManyToOne('Person', required=True, ondelete='cascade', onupdate='cascade')
     78

     79 -  class EmployeeAdmin(EntityAdmin):
     80      name = 'Employees'
     81      list_display = ['established_to', 'from_date', 'thru_date']
     82      fields = ['established_to', 'comment', 'from_date', 'thru_date']
     83      field_attributes = {'established_to':{'name':'Name'}}
     84

     85 -  class EmployerAdmin(EntityAdmin):
     86      name = 'Employers'
     87      list_display = ['established_from', 'from_date', 'thru_date']
     88      fields = ['established_from', 'comment', 'from_date', 'thru_date']
     89      field_attributes = {'established_from':{'name':'Name'}}
     90

     91 -class DirectedDirector(PartyRelationship):
     92    """Relation from a directed organization to a director"""
     93    using_options(tablename='party_relationship_dir', inheritance='multi')
     94    established_from = ManyToOne('Organization', required=True, ondelete='cascade', onupdate='cascade')
     95    established_to = ManyToOne('Person', required=True, ondelete='cascade', onupdate='cascade')
     96    title = Field(Unicode(256))
     97    representing = OneToMany('RepresentedRepresentor', inverse='established_to')
     98

     99 -  class DirectorAdmin(EntityAdmin):
    100      name = 'Directors'
    101      list_display = ['established_to', 'from_date', 'thru_date']
    102      fields = ['established_to', 'title', 'from_date', 'thru_date', 'representing', 'comment']
    103      field_attributes = {'established_to':{'name':'Name'}}
    104

    105 -  class DirectedAdmin(EntityAdmin):
    106      name = 'Directed organizations'
    107      list_display = ['established_from', 'from_date', 'thru_date']
    108      fields = ['established_from', 'from_date', 'thru_date', 'representing', 'comment']
    109      field_attributes = {'established_from':{'name':'Name'}}
    110

    111 -class RepresentedRepresentor(Entity):
    112    """Relation from a represented party to the director representing the party"""
    113    using_options(tablename='party_representor')
    114    from_date = Field(Date(), default=datetime.date.today, required=True, index=True)
    115    thru_date = Field(Date(), default=end_of_times, required=True, index=True)
    116    comment = Field(Text)
    117    established_from = ManyToOne('Party', required=True, ondelete='cascade', onupdate='cascade')
    118    established_to = ManyToOne('DirectedDirector', required=True, ondelete='cascade', onupdate='cascade')
    119

    120 -  class Admin(EntityAdmin):
    121      name = 'Representing'
    122      list_display = ['established_from', 'from_date', 'thru_date', 'comment']
    123      field_attributes = {'established_from':{'name':'Name'}}
    124

    125 -class SupplierCustomer(PartyRelationship):
    126    """Relation from supplier to customer"""
    127    using_options(tablename='party_relationship_suppl', inheritance='multi')
    128    established_from = ManyToOne('Party', required=True, ondelete='cascade', onupdate='cascade')
    129    established_to = ManyToOne('Party', required=True, ondelete='cascade', onupdate='cascade')
    130

    131 -  class CustomerAdmin(EntityAdmin):
    132      name = 'Customers'
    133      list_display = ['established_to',]
    134      fields = ['established_to', 'comment', 'from_date', 'thru_date']
    135      field_attributes = {'established_to':{'name':'Name'}}
    136

    137 -  class SupplierAdmin(EntityAdmin):
    138      name = 'Suppliers'
    139      list_display = ['established_from',]
    140      fields = ['established_from', 'comment', 'from_date', 'thru_date']
    141      field_attributes = {'established_from':{'name':'Name'}}
    142

    143 -class SharedShareholder(PartyRelationship):
    144    """Relation from a shared organization to a shareholder"""
    145    using_options(tablename='party_relationship_shares', inheritance='multi')
    146    established_from = ManyToOne('Organization', required=True, ondelete='cascade', onupdate='cascade')
    147    established_to = ManyToOne('Party', required=True, ondelete='cascade', onupdate='cascade')
    148    shares = Field(Integer())
    149

    150 -  class ShareholderAdmin(EntityAdmin):
    151      name = 'Shareholders'
    152      list_display = ['established_to', 'shares', 'from_date', 'thru_date']
    153      fields = ['established_to', 'shares', 'from_date', 'thru_date', 'comment']
    154      field_attributes = {'established_to':{'name':'Shareholder name'}}
    155

    156 -  class SharedAdmin(EntityAdmin):
    157      name = 'Shares'
    158      list_display = ['established_from', 'shares', 'from_date', 'thru_date']
    159      fields = ['established_from', 'shares', 'from_date', 'thru_date', 'comment']
    160      field_attributes = {'established_from':{'name':'Name'}}
    161

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

    164    organisations in building authentication systems, contact management or CRM"""
    165    using_options(tablename='party')
    166    is_synchronized('synchronized', lazy=True)
    167    addresses = OneToMany('PartyAddress', lazy=True)
    168    contact_mechanisms = OneToMany('PartyContactMechanism', lazy=True)
    169

    170    @property
    171 -  def name(self):
    172      return unicode(self)
    173

    174 -  class Admin(EntityAdmin):
    175      name = 'Parties'
    176      list_display = ['name']
    177      fields = ['suppliers', 'customers', 'addresses']
    178      field_attributes = dict(suppliers={'admin':SupplierCustomer.SupplierAdmin},

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

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

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

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

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

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

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

    186                              sex=dict(choices=lambda obj:[(u'M',u'Male'), (u'F',u'Female')],),

    187                              )
    188

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

    191    businesses and groups of individuals"""
    192    using_options(tablename='organization', inheritance='multi')
    193    name = Field(Unicode(50), required=True, index=True)
    194    logo = Field(camelot.types.Image(upload_to='organization-logo'), deferred=True)
    195    tax_id = Field(Unicode(20))
    196    directors = OneToMany('DirectedDirector', inverse='established_from')
    197    employees = OneToMany('EmployerEmployee', inverse='established_from')
    198    suppliers = OneToMany('SupplierCustomer', inverse='established_to')
    199    customers = OneToMany('SupplierCustomer', inverse='established_from')
    200    shareholders = OneToMany('SharedShareholder', inverse='established_from')
    201    shares = OneToMany('SharedShareholder', inverse='established_to')
    202

    203 -  def __unicode__(self):
    204      return self.name
    205

    206    @property
    207 -  def number_of_shares_issued(self):
    208      return sum((shareholder.shares for shareholder in self.shareholders), 0)
    209

    210 -  class Admin(Party.Admin):
    211      name = 'Organizations'
    212      section = 'relations'
    213      list_display = ['name', 'tax_id',]
    214      form = TabForm([('Basic', Form(['name', 'tax_id', 'addresses', 'contact_mechanisms'])),

    215                      ('Employment', Form(['employees'])),

    216                      ('Customers', Form(['customers'])),

    217                      ('Suppliers', Form(['suppliers'])),

    218                      ('Corporate', Form(['directors', 'shareholders', 'shares'])),

    219                      ('Branding', Form(['logo'])), ])
    220

    221 -class Person(Party):
    222    """Person represents natural persons, these can be given access to the system

    223    and as such require a username.

    224

    225    Username is required, other fields are optional, there is no password because

    226    authentication is supposed to happen through the operating system services or

    227    other.

    228    """
    229    using_options(tablename='person', inheritance='multi')
    230    username = Field(Unicode(40), required=True, index=True, unique=True)
    231    first_name = Field(Unicode(40))
    232    last_name =  Field(Unicode(40))
    233    middle_name = Field(Unicode(40))
    234    personal_title = Field(Unicode(10))
    235    suffix = Field(Unicode(3))
    236    sex = Field(Unicode(1), default=u'M')
    237    birthdate = Field(Date())
    238    martial_status = Field(Unicode(1))
    239    social_security_number = Field(Unicode(12))
    240    passport_number = Field(Unicode(20))
    241    passport_expiry_date = Field(Date())
    242    is_staff = Field(Boolean, default=False, index=True)
    243    is_active = Field(Boolean, default=True, index=True)
    244    is_superuser = Field(Boolean, default=False, index=True)
    245    last_login = Field(DateTime(), default=datetime.datetime.now)
    246    date_joined = Field(DateTime(), default=datetime.datetime.now)
    247    picture = Field(camelot.types.Image(upload_to='person-pictures'), deferred=True)
    248    comment = Field(Text)
    249    employers = OneToMany('EmployerEmployee', inverse='established_to')
    250    directed_organizations = OneToMany('DirectedDirector', inverse='established_to')
    251    shares = OneToMany('SharedShareholder', inverse='established_to')
    252

    253    @property
    254 -  def name(self):
    255      if self.last_name and self.first_name:
    256        return u'%s %s'%(self.last_name, self.first_name)
    257      else:
    258        return self.username
    259

    260 -  def __unicode__(self):
    261      return self.name
    262

    263    @classmethod
    264 -  def getOrCreatePerson(cls, username):
    265      person = cls.query.filter_by(username=username).first()
    266      if not person:
    267        person = cls(username=username)
    268        from elixir import session
    269        session.flush([person])
    270      return person
    271

    272 -  class Admin(Party.Admin):
    273      name = 'Persons'
    274      section = 'relations'
    275      list_display = ['username', 'first_name', 'last_name', ]
    276      list_filter = ['is_active', 'is_staff', 'is_superuser']
    277      form = TabForm([('Basic', Form([HBoxForm([Form(['username', 'first_name', 'last_name', 'sex']),

    278                                                Form(['is_staff', 'is_active', 'is_superuser',]),

    279                                                Form(['picture',]),

    280                                                ]),

    281                                                'contact_mechanisms',  'comment',], scrollbars=True)),

    282                      ('Official', Form(['birthdate', 'social_security_number', 'passport_number','passport_expiry_date','addresses',], scrollbars=True)),

    283                      ('Work', Form(['employers', 'directed_organizations', 'shares'], scrollbars=True))

    284                      ])
    285

    286 -class GeographicBoundary(Entity):
    287    using_options(tablename='geographic_boundary')
    288    code = Field(Unicode(10))
    289    name = Field(Unicode(40), required=True)
    290

    291 -  def __unicode__(self):
    292      return u'%s %s'%(self.code, self.name)
    293

    294 -class Country(GeographicBoundary):
    295    using_options(tablename='geographic_boundary_country', inheritance='multi')
    296

    297    @classmethod
    298 -  def getOrCreate(cls, code, name):
    299      country = Country.query.filter_by(code=code).first()
    300      if not country:
    301        from elixir import session
    302        country = Country(code=code, name=name)
    303        session.flush([country])
    304      return country
    305

    306 -  class Admin(EntityAdmin):
    307      name = 'Countries'
    308      list_display = ['name', 'code']
    309

    310 -class City(GeographicBoundary):
    311    using_options(tablename='geographic_boundary_city', inheritance='multi')
    312    country = ManyToOne('Country', required=True, ondelete='cascade', onupdate='cascade')
    313

    314    @classmethod
    315 -  def getOrCreate(cls, country, code, name):
    316      city = City.query.filter_by(code=code, country=country).first()
    317      if not city:
    318        from elixir import session
    319        city = City(code=code, name=name, country=country)
    320        session.flush([city])
    321      return city
    322

    323 -  class Admin(EntityAdmin):
    324      name = 'Cities'
    325      list_display = ['code', 'name', 'country']
    326

    327 -class Address(Entity):
    328    using_options(tablename='address')
    329    street1 = Field(Unicode(128), required=True)
    330    street2 = Field(Unicode(128))
    331    city = ManyToOne('City', required=True, ondelete='cascade', onupdate='cascade')
    332    is_synchronized('synchronized', lazy=True)
    333

    334 -  def __unicode__(self):
    335      return u'%s, %s'%(self.street1, self.city)
    336

    337 -  def showMap(self):
    338      from PyQt4 import QtGui, QtCore
    339      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)))
    340

    341 -  class Admin(EntityAdmin):
    342      name = 'Addresses'
    343      list_display = ['street1', 'street2', 'city']
    344      form_actions = [('Show map',lambda address:address.showMap())]
    345

    346 -class PartyAddressRoleType(Entity):
    347    using_options(tablename='party_address_role_type')
    348    code = Field(Unicode(10))
    349    description = Field(Unicode(40))
    350

    351 -  class Admin(EntityAdmin):
    352      name = 'Address role type'
    353      list_display = ['code', 'description']
    354

    355 -class PartyAddress(Entity):
    356    using_options(tablename='party_address')
    357    party = ManyToOne('Party', required=True, ondelete='cascade', onupdate='cascade')
    358    address = ManyToOne('Address', required=True, ondelete='cascade', onupdate='cascade')
    359    from_date = Field(Date(), default=datetime.date.today, required=True, index=True)
    360    thru_date = Field(Date(), default=end_of_times, required=True, index=True)
    361    comment = Field(Unicode(256))
    362

    363 -  def __unicode__(self):
    364      return '%s : %s'%(unicode(self.party), unicode(self.address))
    365

    366 -  def showMap(self):
    367      if self.address:
    368        self.address.showMap()
    369

    370 -  class Admin(EntityAdmin):
    371      name = 'Address'
    372      list_display = ['address', 'comment']
    373      fields = ['address', 'comment', 'from_date', 'thru_date']
    374      form_actions = [('Show map',lambda address:address.showMap())]
    375

    376 -class ContactMechanism(Entity):
    377    using_options(tablename='contact_mechanism')
    378    mechanism = Field(camelot.types.VirtualAddress(256), required=True)
    379    party_address = ManyToOne('PartyAddress', ondelete='set null', onupdate='cascade')
    380

    381 -  def __unicode__(self):
    382      return self.mechanism[1]
    383

    384 -  class Admin(EntityAdmin):
    385      name = 'Contact mechanism'
    386      list_display = ['mechanism']
    387      form = Form(['mechanism', 'party_address'])
    388

    389 -class PartyContactMechanism(Entity):
    390    using_options(tablename='party_contact_mechanism')
    391    party = ManyToOne('Party', required=True, ondelete='cascade', onupdate='cascade')
    392    contact_mechanism = ManyToOne('ContactMechanism', required=True, ondelete='cascade', onupdate='cascade')
    393    from_date = Field(Date(), default=datetime.date.today, required=True, index=True)
    394    thru_date = Field(Date(), default=end_of_times, index=True)
    395    comment = Field(Unicode(256))
    396

    397 -  def __unicode__(self):
    398      return unicode(self.contact_mechanism)
    399

    400 -  class Admin(EntityAdmin):
    401      name = 'Party contact mechanisms'
    402      list_display = ['contact_mechanism', 'comment', 'from_date',]
    403      form = Form(['contact_mechanism', 'comment', 'from_date', 'thru_date',])
    404