1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
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()
67 return datetime.date( year = 2400, month = 12, day = 31 )
68
69 from camelot.model.type_and_status import type_3_status
78
85
97
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' )
102 established_to = ManyToOne( 'Person', required = True, ondelete = 'cascade', onupdate = 'cascade' )
103
104 @ColumnProperty
107
108 @ColumnProperty
111
112 @ColumnProperty
115
118
119 - class Admin( PartyRelationship.Admin ):
124
130
136
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
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
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
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 ):
177
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
193
199
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 ):
214
220
227
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
242
246
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
261
262 @ColumnProperty
272
273 @ColumnProperty
283
284 @ColumnProperty
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']
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
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
333
334 @property
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 )
361
382
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
409
410 @property
415
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 )
448
449 -class Country( GeographicBoundary ):
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
479
480 - class Admin( EntityAdmin ):
485
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
497
498 @classmethod
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
508 return u'%s, %s' % ( self.street1 or '', self.city or '' )
509
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 ):
521
522 Address = documented_entity()( Address )
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
536
537 @ColumnProperty
541
543 return '%s : %s' % ( unicode( self.party ), unicode( self.address ) )
544
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
568
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
591
592 ContactMechanism = documented_entity()( ContactMechanism )
616