Package vita ::
Package modules ::
Package eden ::
Module pr
|
|
1
2
3 """ Sahana Eden Person Registry Model
4
5 @copyright: 2009-2012 (c) Sahana Software Foundation
6 @license: MIT
7
8 Permission is hereby granted, free of charge, to any person
9 obtaining a copy of this software and associated documentation
10 files (the "Software"), to deal in the Software without
11 restriction, including without limitation the rights to use,
12 copy, modify, merge, publish, distribute, sublicense, and/or sell
13 copies of the Software, and to permit persons to whom the
14 Software is furnished to do so, subject to the following
15 conditions:
16
17 The above copyright notice and this permission notice shall be
18 included in all copies or substantial portions of the Software.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 OTHER DEALINGS IN THE SOFTWARE.
28 """
29
30 __all__ = ["S3PersonEntity",
31 "S3OrgAuthModel",
32 "S3PersonModel",
33 "S3GroupModel",
34 "S3ContactModel",
35 "S3PersonAddressModel",
36 "S3PersonImageModel",
37 "S3PersonIdentityModel",
38 "S3SavedSearch",
39 "S3PersonPresence",
40 "S3PersonDescription",
41
42 "pr_pentity_represent",
43 "pr_person_represent",
44 "pr_person_comment",
45 "pr_rheader",
46
47 "pr_contacts",
48 "pr_profile",
49
50 "pr_update_affiliations",
51 "pr_add_affiliation",
52 "pr_remove_affiliation",
53
54 "pr_get_pe_id",
55
56 "pr_define_role",
57 "pr_delete_role",
58 "pr_add_to_role",
59 "pr_remove_from_role",
60
61 "pr_realm",
62 "pr_get_role_paths",
63 "pr_get_role_branches",
64 "pr_get_path",
65 "pr_get_ancestors",
66 "pr_get_descendants",
67 "pr_ancestors",
68 "pr_descendants",
69
70 "pr_rebuild_path",
71 "pr_role_rebuild_path"]
72
73 import gluon.contrib.simplejson as json
74
75 from gluon import *
76 from gluon.dal import Row
77 from gluon.storage import Storage
78 from gluon.sqlhtml import RadioWidget
79
80 from layouts import *
81 from eden.layouts import S3AddResourceLink
82
83 OU = 1
84 OTHER_ROLE = 9
88 """ Person Super-Entity """
89
90 names = ["pr_pentity",
91 "pr_affiliation",
92 "pr_role",
93 "pr_role_types",
94 "pr_role_id",
95 "pr_pe_label",
96 "pr_pe_types"]
97
99
100 db = current.db
101 T = current.T
102 s3 = current.response.s3
103
104 add_component = self.add_component
105 configure = self.configure
106 crud_strings = s3.crud_strings
107 define_table = self.define_table
108 meta_fields= s3.meta_fields
109 super_entity = self.super_entity
110 super_key = self.super_key
111 super_link = self.super_link
112
113 YES = T("yes")
114 NO = T("no")
115 UNKNOWN_OPT = current.messages.UNKNOWN_OPT
116
117
118
119
120
121
122
123
124 pe_types = Storage(pr_person = T("Person"),
125 pr_group = T("Group"),
126 org_organisation = T("Organization"),
127 org_office = T("Office"),
128
129
130
131
132
133 dvi_body = T("Body"))
134
135 tablename = "pr_pentity"
136 table = super_entity(tablename, "pe_id", pe_types,
137 Field("type"),
138 Field("pe_label", length=128))
139
140
141 pentity_search = S3PentitySearch(name = "pentity_search_simple",
142 label = T("Name and/or ID"),
143 comment = T(""),
144 field = ["pe_label"])
145
146 pentity_search.pentity_represent = pr_pentity_represent
147
148
149 configure(tablename,
150 editable=False,
151 deletable=False,
152 listadd=False,
153 onaccept=self.pr_pentity_onaccept,
154 search_method=pentity_search)
155
156
157 pr_pe_label = S3ReusableField("pe_label", length=128,
158 label = T("ID Tag Number"),
159 requires = IS_NULL_OR(IS_NOT_ONE_OF(db,
160 "pr_pentity.pe_label")))
161
162
163 pe_id = super_key(table)
164 add_component("pr_contact_emergency", pr_pentity=pe_id)
165 add_component("pr_address", pr_pentity=pe_id)
166 add_component("pr_image", pr_pentity=pe_id)
167 add_component("pr_contact", pr_pentity=pe_id)
168 add_component("pr_note", pr_pentity=pe_id)
169 add_component("pr_physical_description",
170 pr_pentity=dict(joinby=pe_id,
171 multiple=False))
172 add_component("dvi_identification",
173 pr_pentity=dict(joinby=pe_id,
174 multiple=False))
175 add_component("dvi_effects",
176 pr_pentity=dict(joinby=pe_id,
177 multiple=False))
178 add_component("dvi_checklist",
179 pr_pentity=dict(joinby=pe_id,
180 multiple=False))
181
182
183
184 add_component("gis_config",
185 pr_pentity=dict(joinby=pe_id,
186 multiple=False))
187
188
189
190
191 utable = current.auth.settings.table_user
192 tablename = "pr_person_user"
193 table = define_table(tablename,
194 super_link("pe_id", "pr_pentity"),
195 Field("user_id", utable),
196 *meta_fields())
197
198
199
200
201 role_types = {
202 1:T("Organizational Units"),
203 2:T("Membership"),
204 3:T("Association"),
205 9:T("Other")
206 }
207 tablename = "pr_role"
208 table = define_table(tablename,
209
210 super_link("pe_id", "pr_pentity",
211 label=T("Corporate Entity"),
212 readable=True,
213 writable=True),
214
215 Field("role_type", "integer",
216 requires = IS_IN_SET(role_types, zero=None),
217 represent = lambda opt: \
218 role_types.get(opt, UNKNOWN_OPT)),
219
220 Field("role", notnull=True),
221
222 Field("path",
223 readable = False,
224 writable = False),
225
226 Field("entity_type", "string",
227 requires = IS_EMPTY_OR(IS_IN_SET(pe_types, zero=T("ANY"))),
228 represent = lambda opt: pe_types.get(opt, UNKNOWN_OPT)),
229
230 Field("sub_type", "integer",
231 readable = False,
232 writable = False),
233 *meta_fields())
234
235
236 table.pe_id.requires = IS_ONE_OF(db, "pr_pentity.pe_id",
237 pr_pentity_represent, sort=True)
238 table.pe_id.represent = pr_pentity_represent
239
240
241 crud_strings[tablename] = Storage(
242 title_create = T("Add Role"),
243 title_display = T("Role Details"),
244 title_list = T("Roles"),
245 title_update = T("Edit Role"),
246 title_search = T("Search Roles"),
247 subtitle_create = T("Add New Role"),
248 subtitle_list = T("List of Roles"),
249 label_list_button = T("List Roles"),
250 label_create_button = T("Add Role"),
251 label_delete_button = T("Delete Role"),
252 msg_record_created = T("Role added"),
253 msg_record_modified = T("Role updated"),
254 msg_record_deleted = T("Role deleted"),
255 msg_list_empty = T("No Roles defined"))
256
257
258 configure(tablename,
259 onvalidation=self.pr_role_onvalidation)
260
261
262 role_id = S3ReusableField("role_id", db.pr_role,
263 requires = IS_ONE_OF(db, "pr_role.id",
264 self.pr_role_represent),
265 represent = self.pr_role_represent,
266 label = T("Role"),
267 ondelete = "CASCADE")
268
269
270
271
272 tablename = "pr_affiliation"
273 table = define_table(tablename,
274 role_id(),
275 super_link("pe_id", "pr_pentity",
276 label=T("Entity"),
277 readable=True,
278 writable=True),
279 *meta_fields())
280
281 table.pe_id.requires = IS_ONE_OF(db, "pr_pentity.pe_id",
282 pr_pentity_represent, sort=True)
283 table.pe_id.represent = pr_pentity_represent
284
285
286 crud_strings[tablename] = Storage(
287 title_create = T("Add Affiliation"),
288 title_display = T("Affiliation Details"),
289 title_list = T("Affiliations"),
290 title_update = T("Edit Affiliation"),
291 title_search = T("Search Affiliations"),
292 subtitle_create = T("Add New Affiliation"),
293 subtitle_list = T("List of Affiliations"),
294 label_list_button = T("List Affiliations"),
295 label_create_button = T("Add Affiliation"),
296 label_delete_button = T("Delete Affiliation"),
297 msg_record_created = T("Affiliation added"),
298 msg_record_modified = T("Affiliation updated"),
299 msg_record_deleted = T("Affiliation deleted"),
300 msg_list_empty = T("No Affiliations defined"))
301
302
303 configure(tablename,
304 onaccept=self.pr_affiliation_onaccept,
305 ondelete=self.pr_affiliation_ondelete)
306
307
308
309
310 return Storage(
311 pr_pe_types=pe_types,
312 pr_pe_label=pr_pe_label,
313 pr_role_types=role_types,
314 pr_role_id=role_id,
315 )
316
317
318 @staticmethod
320 """
321 Represent an entity role
322
323 @param role_id: the pr_role record ID
324 """
325
326 db = current.db
327 s3db = current.s3db
328
329 table = s3db.pr_role
330 role = db(table.id == role_id).select(table.role,
331 table.pe_id,
332 limitby=(0, 1)).first()
333 if role:
334 entity = pr_pentity_represent(role.pe_id)
335 return "%s: %s" % (entity, role.role)
336 else:
337 return current.messages.NONE
338
339
340 @staticmethod
342 """
343 Clear descendant paths if role type has changed
344
345 @param form: the CRUD form
346 """
347
348 db = current.db
349 s3db = current.s3db
350
351 formvars = form.vars
352 if not formvars:
353 return
354 if "role_type" in formvars:
355 role_id = form.record_id
356 if not role_id:
357 return
358 role_type = formvars.role_type
359 rtable = s3db.pr_role
360 role = db(rtable.id == role_id).select(rtable.role_type,
361 limitby=(0, 1)).first()
362 if role and str(role.role_type) != str(role_type):
363
364 if str(role_type) != str(OU):
365 formvars["path"] = None
366 s3db.pr_role_rebuild_path(role_id, clear=True)
367 return
368
369
370 @staticmethod
372 """
373 Update organisation affiliations for org_site instances.
374 """
375
376 db = current.db
377 s3db = current.s3db
378
379 ptable = s3db.pr_pentity
380
381 pe_id = form.vars.pe_id
382 pe = db(ptable.pe_id == pe_id).select(ptable.instance_type,
383 limitby=(0, 1)).first()
384 if pe:
385 itable = s3db.table(pe.instance_type, None)
386 if itable and \
387 "site_id" in itable.fields and \
388 "organisation_id" in itable.fields:
389 q = itable.pe_id == pe_id
390 instance = db(q).select(itable.pe_id,
391 itable.organisation_id,
392 limitby=(0, 1)).first()
393 if instance:
394 s3db.pr_update_affiliations("org_site", instance)
395 return
396
397
398 @staticmethod
400 """
401 Remove duplicate affiliations and clear descendant paths (to
402 trigger lazy rebuild)
403
404 @param form: the CRUD form
405 """
406
407 db = current.db
408 s3db = current.s3db
409 manager = current.manager
410
411 atable = s3db.pr_affiliation
412
413 formvars = form.vars
414 role_id = formvars["role_id"]
415 pe_id = formvars["pe_id"]
416 record_id = formvars["id"]
417
418 if role_id and pe_id and record_id:
419
420 query = (atable.id != record_id) & \
421 (atable.role_id == role_id) & \
422 (atable.pe_id == pe_id)
423 deleted_fk = {"role_id": role_id, "pe_id": pe_id}
424 data = {"deleted": True,
425 "role_id": None,
426 "pe_id": None,
427 "deleted_fk": json.dumps(deleted_fk)}
428 db(query).update(**data)
429
430 s3db.pr_rebuild_path(pe_id, clear=True)
431 return
432
433
434 @staticmethod
436 """
437 Clear descendant paths, also called indirectly via
438 ondelete-CASCADE when a role gets deleted.
439
440 @param row: the deleted row
441 """
442
443 db = current.db
444 s3db = current.s3db
445
446 atable = s3db.pr_affiliation
447
448 if row and row.id:
449 query = atable.id == row.id
450 record = db(query).select(atable.deleted_fk,
451 limitby=(0, 1)).first()
452 else:
453 return
454 if record:
455 data = json.loads(record.deleted_fk)
456 pe_id = data.get("pe_id", None)
457 if pe_id:
458 s3db.pr_rebuild_path(pe_id, clear=True)
459 return
460
463 """ Organisation-based Authorization Model """
464
465 names = ["pr_delegation"]
466
468
469 auth = current.auth
470 s3 = current.response.s3
471 role_id = current.s3db.pr_role_id
472
473 define_table = self.define_table
474 super_link = self.super_link
475 meta_fields = s3.meta_fields
476
477
478
479
480
481
482 gtable = auth.settings.table_group
483 tablename = "pr_delegation"
484 table = define_table(tablename,
485 role_id(),
486 Field("group_id", gtable,
487 ondelete="CASCADE"),
488 *meta_fields())
489
490
491 return Storage()
492
495 """ Persons and Groups """
496
497 names = ["pr_person",
498 "pr_person_user",
499 "pr_gender",
500 "pr_gender_opts",
501 "pr_age_group",
502 "pr_age_group_opts",
503 "pr_person_id",
504 ]
505
507
508 T = current.T
509 db = current.db
510 request = current.request
511 s3 = current.response.s3
512 gis = current.gis
513 settings = current.deployment_settings
514
515 pe_label = self.pr_pe_label
516
517 location_id = self.gis_location_id
518
519 messages = current.messages
520 UNKNOWN_OPT = messages.UNKNOWN_OPT
521 SELECT_LOCATION = messages.SELECT_LOCATION
522
523 define_table = self.define_table
524 super_link = self.super_link
525 add_component = self.add_component
526
527
528
529
530 pr_gender_opts = {
531 1:"",
532 2:T("female"),
533 3:T("male")
534 }
535 pr_gender = S3ReusableField("gender", "integer",
536 requires = IS_IN_SET(pr_gender_opts, zero=None),
537 default = 1,
538 label = T("Gender"),
539 represent = lambda opt: \
540 pr_gender_opts.get(opt, UNKNOWN_OPT))
541
542 pr_age_group_opts = {
543 1:T("unknown"),
544 2:T("Infant (0-1)"),
545 3:T("Child (2-11)"),
546 4:T("Adolescent (12-20)"),
547 5:T("Adult (21-50)"),
548 6:T("Senior (50+)")
549 }
550 pr_age_group = S3ReusableField("age_group", "integer",
551 requires = IS_IN_SET(pr_age_group_opts,
552 zero=None),
553 default = 1,
554 label = T("Age Group"),
555 represent = lambda opt: \
556 pr_age_group_opts.get(opt,
557 UNKNOWN_OPT))
558
559 pr_marital_status_opts = {
560 1:T("unknown"),
561 2:T("single"),
562 3:T("married"),
563 4:T("separated"),
564 5:T("divorced"),
565 6:T("widowed"),
566 9:T("other")
567 }
568
569 pr_religion_opts = settings.get_L10n_religions()
570
571 pr_impact_tags = {
572 1: T("injured"),
573 4: T("diseased"),
574 2: T("displaced"),
575 5: T("separated from family"),
576 3: T("suffered financial losses")
577 }
578
579 if settings.get_L10n_mandatory_lastname():
580 last_name_validate = IS_NOT_EMPTY(error_message = T("Please enter a last name"))
581 else:
582 last_name_validate = None
583
584 s3_date_format = settings.get_L10n_date_format()
585
586 tablename = "pr_person"
587 table = define_table(tablename,
588 super_link("pe_id", "pr_pentity"),
589 super_link("track_id", "sit_trackable"),
590 location_id(readable=False,
591 writable=False),
592 pe_label(comment = DIV(DIV(_class="tooltip",
593 _title="%s|%s" % (T("ID Tag Number"),
594 T("Number or Label on the identification tag this person is wearing (if any)."))))),
595 Field("missing", "boolean",
596 readable=False,
597 writable=False,
598 default=False,
599 represent = lambda missing: \
600 (missing and ["missing"] or [""])[0]),
601 Field("volunteer", "boolean",
602 readable=False,
603 writable=False,
604 default=False),
605 Field("first_name",
606 notnull=True,
607 default = "?" if current.auth.permission.format != "html" else "",
608 length=64,
609
610
611 requires = IS_NOT_EMPTY(error_message = T("Please enter a first name")),
612 comment = DIV(_class="tooltip",
613 _title="%s|%s" % (T("First name"),
614 T("The first or only name of the person (mandatory)."))),
615 label = T("First Name")),
616 Field("middle_name",
617 length=64,
618 label = T("Middle Name")),
619 Field("last_name",
620 length=64,
621 label = T("Last Name"),
622 requires = last_name_validate),
623 Field("initials",
624 length=8,
625 label = T("Initials")),
626 Field("preferred_name",
627 label = T("Preferred Name"),
628 comment = DIV(DIV(_class="tooltip",
629 _title="%s|%s" % (T("Preferred Name"),
630 T("The name to be used when calling for or directly addressing the person (optional).")))),
631 length=64),
632 Field("local_name",
633 label = T("Local Name"),
634 comment = DIV(DIV(_class="tooltip",
635 _title="%s|%s" % (T("Local Name"),
636 T("Name of the person in local language and script (optional)."))))),
637 pr_gender(label = T("Gender")),
638 Field("date_of_birth", "date",
639 label = T("Date of Birth"),
640 requires = [IS_EMPTY_OR(IS_DATE_IN_RANGE(
641 format = s3_date_format,
642 maximum=request.utcnow.date(),
643 error_message="%s %%(max)s!" %
644 T("Enter a valid date before")))],
645 widget = S3DateWidget(past=1320,
646 future=0)),
647 pr_age_group(label = T("Age group")),
648 Field("nationality",
649 requires = IS_NULL_OR(IS_IN_SET_LAZY(
650 lambda: gis.get_countries(key_type="code"),
651 zero = SELECT_LOCATION)),
652 label = T("Nationality"),
653 comment = DIV(DIV(_class="tooltip",
654 _title="%s|%s" % (T("Nationality"),
655 T("Nationality of the person.")))),
656 represent = lambda code: \
657 gis.get_country(code, key_type="code") or UNKNOWN_OPT),
658 Field("religion", length=128,
659 label = T("Religion"),
660 requires = IS_NULL_OR(IS_IN_SET(pr_religion_opts)),
661 represent = lambda opt: \
662 pr_religion_opts.get(opt, UNKNOWN_OPT),
663 ),
664 Field("occupation", length=128,
665 label = T("Profession"),
666 ),
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683 Field("opt_in",
684 "string",
685 default=False,
686 label = T("Receive updates"),
687 comment = DIV(DIV(_class="tooltip",
688 _title="%s|%s" % (T("Mailing list"),
689 T("By selecting this you agree that we may contact you.")))),
690 ),
691 s3.comments(),
692 *(s3.lx_fields() + s3.meta_fields()))
693
694
695 ADD_PERSON = current.messages.ADD_PERSON
696 LIST_PERSONS = T("List Persons")
697 s3.crud_strings[tablename] = Storage(
698 title_create = T("Add a Person"),
699 title_display = T("Person Details"),
700 title_list = LIST_PERSONS,
701 title_update = T("Edit Person Details"),
702 title_search = T("Search Persons"),
703 subtitle_create = ADD_PERSON,
704 subtitle_list = T("Persons"),
705 label_list_button = LIST_PERSONS,
706 label_create_button = ADD_PERSON,
707 label_delete_button = T("Delete Person"),
708 msg_record_created = T("Person added"),
709 msg_record_modified = T("Person details updated"),
710 msg_record_deleted = T("Person deleted"),
711 msg_list_empty = T("No Persons currently registered"))
712
713
714 if current.deployment_settings.get_auth_opt_in_to_email():
715 table.opt_in.readable = True
716 table.opt_in.writable = True
717 else:
718 table.opt_in.readable = False
719 table.opt_in.writable = False
720
721
722 pr_person_search = S3PersonSearch(
723 name="person_search_simple",
724 label=T("Name and/or ID"),
725 comment=T("To search for a person, enter any of the first, middle or last names and/or an ID number of a person, separated by spaces. You may use % as wildcard. Press 'Search' without input to list all persons."),
726 field=["pe_label",
727 "first_name",
728 "middle_name",
729 "last_name",
730 "local_name",
731 "identity.value"
732 ])
733
734
735 self.configure(tablename,
736 super_entity=("pr_pentity", "sit_trackable"),
737 list_fields = ["id",
738 "first_name",
739 "middle_name",
740 "last_name",
741
742 "gender",
743 "age_group",
744 (T("Organization"), "hrm_human_resource:organisation_id$name")
745 ],
746 onvalidation=self.pr_person_onvalidation,
747 search_method=pr_person_search,
748 deduplicate=self.person_deduplicate,
749 main="first_name",
750 extra="last_name")
751
752 person_id_comment = pr_person_comment(
753 T("Person"),
754 T("Type the first few characters of one of the Person's names."),
755 child="person_id")
756
757 person_id = S3ReusableField("person_id", db.pr_person,
758 sortby = ["first_name", "middle_name", "last_name"],
759 requires = IS_NULL_OR(IS_ONE_OF(db, "pr_person.id",
760 pr_person_represent,
761 orderby="pr_person.first_name",
762 sort=True,
763 error_message=T("Person must be specified!"))),
764 represent = pr_person_represent,
765 label = T("Person"),
766 comment = person_id_comment,
767 ondelete = "RESTRICT",
768 widget = S3PersonAutocompleteWidget())
769
770
771 add_component("pr_group_membership", pr_person="person_id")
772 add_component("pr_identity", pr_person="person_id")
773 add_component("pr_save_search", pr_person="person_id")
774 add_component("msg_subscription", pr_person="person_id")
775
776
777 add_component("hrm_human_resource", pr_person="person_id")
778 add_component("member_membership", pr_person="person_id")
779
780
781 add_component("hrm_certification", pr_person="person_id")
782 add_component("hrm_competency", pr_person="person_id")
783 add_component("hrm_credential", pr_person="person_id")
784 add_component("hrm_experience", pr_person="person_id")
785
786 add_component("hrm_training", pr_person="person_id")
787
788
789 add_component("asset_asset", pr_person="assigned_to_id")
790
791
792
793
794 return Storage(
795 pr_gender = pr_gender,
796 pr_gender_opts = pr_gender_opts,
797 pr_age_group = pr_age_group,
798 pr_age_group_opts = pr_age_group_opts,
799 pr_person_id = person_id,
800 )
801
802
803 @staticmethod
805 """ Onvalidation callback """
806
807 try:
808 age = int(form.vars.get("age_group", None))
809 except (ValueError, TypeError):
810 age = None
811 dob = form.vars.get("date_of_birth", None)
812
813 if age and age != 1 and dob:
814 now = request.utcnow
815 dy = int((now.date() - dob).days / 365.25)
816 if dy < 0:
817 ag = 1
818 elif dy < 2:
819 ag = 2
820 elif dy < 12:
821 ag = 3
822 elif dy < 21:
823 ag = 4
824 elif dy < 51:
825 ag = 5
826 else:
827 ag = 6
828
829 if age != ag:
830 form.errors.age_group = T("Age group does not match actual age.")
831 return False
832
833 return True
834
835
836 @staticmethod
838 """ Import item deduplication """
839
840 db = current.db
841 s3db = current.s3db
842
843
844 if item.id:
845 return
846 if item.tablename == "pr_person":
847 ptable = s3db.pr_person
848 ctable = s3db.pr_contact
849
850
851 fname = "first_name" in item.data and item.data.first_name
852 lname = "last_name" in item.data and item.data.last_name
853 if fname and lname:
854
855
856
857
858
859
860
861
862
863
864
865
866 query = (ptable.first_name.lower() == fname.lower()) & \
867 (ptable.last_name.lower() == lname.lower())
868 email = False
869 for citem in item.components:
870 if citem.tablename == "pr_contact":
871 if "contact_method" in citem.data and \
872 citem.data.contact_method == "EMAIL":
873 email = citem.data.value
874 if email != False:
875 query = query & \
876 (ptable.pe_id == ctable.pe_id) & \
877 (ctable.value.lower() == email.lower())
878
879 else:
880
881 initials = "initials" in item.data and item.data.initials
882 if not initials:
883
884 return
885 query = (ptable.initials.lower() == initials.lower())
886
887
888 _duplicate = db(query).select(ptable.id,
889 limitby=(0, 1)).first()
890 if _duplicate:
891 item.id = _duplicate.id
892 item.data.id = _duplicate.id
893 item.method = item.METHOD.UPDATE
894 for citem in item.components:
895 citem.method = citem.METHOD.UPDATE
896
899 """ Groups """
900
901 names = ["pr_group",
902 "pr_group_id",
903 "pr_group_represent",
904 "pr_group_membership"]
905
907
908 T = current.T
909 db = current.db
910 request = current.request
911 s3 = current.response.s3
912
913 person_id = self.pr_person_id
914
915 messages = current.messages
916 NONE = messages.NONE
917 UNKNOWN_OPT = messages.UNKNOWN_OPT
918
919 comments = s3.comments
920 configure = self.configure
921 crud_strings = s3.crud_strings
922 define_table = self.define_table
923 meta_fields = s3.meta_fields
924
925
926
927
928 pr_group_types = {
929 1:T("Family"),
930 2:T("Tourist Group"),
931 3:T("Relief Team"),
932 4:T("other"),
933 5:T("Mailing Lists"),
934 }
935
936 tablename = "pr_group"
937 table = define_table(tablename,
938 self.super_link("pe_id", "pr_pentity"),
939 Field("group_type", "integer",
940 requires = IS_IN_SET(pr_group_types, zero=None),
941 default = 4,
942 label = T("Group Type"),
943 represent = lambda opt: \
944 pr_group_types.get(opt, UNKNOWN_OPT)),
945 Field("system", "boolean",
946 default=False,
947 readable=False,
948 writable=False),
949 Field("name",
950 label=T("Group Name"),
951 requires = IS_NOT_EMPTY()),
952 Field("description",
953 label=T("Group Description")),
954 comments(),
955 *meta_fields())
956
957
958 table.description.comment = DIV(DIV(_class="tooltip",
959 _title="%s|%s" % (T("Group description"),
960 T("A brief description of the group (optional)"))))
961
962
963 ADD_GROUP = T("Add Group")
964 LIST_GROUPS = T("List Groups")
965 crud_strings[tablename] = Storage(
966 title_create = ADD_GROUP,
967 title_display = T("Group Details"),
968 title_list = LIST_GROUPS,
969 title_update = T("Edit Group"),
970 title_search = T("Search Groups"),
971 subtitle_create = T("Add New Group"),
972 subtitle_list = T("Groups"),
973 label_list_button = LIST_GROUPS,
974 label_create_button = ADD_GROUP,
975 label_delete_button = T("Delete Group"),
976 msg_record_created = T("Group added"),
977 msg_record_modified = T("Group updated"),
978 msg_record_deleted = T("Group deleted"),
979 msg_list_empty = T("No Groups currently registered"))
980
981
982 ADD_GROUP = T("Add Mailing List")
983 LIST_GROUPS = T("Mailing List")
984 mailing_list_crud_strings = Storage(
985 title_create = ADD_GROUP,
986 title_display = T("Mailing List Details"),
987 title_list = LIST_GROUPS,
988 title_update = T("Edit Mailing List"),
989 title_search = T("Search Mailing Lists"),
990 subtitle_create = T("Add New Mailing List"),
991 subtitle_list = T("Mailing Lists"),
992 label_list_button = LIST_GROUPS,
993 label_create_button = ADD_GROUP,
994 label_delete_button = T("Delete Mailing List"),
995 msg_record_created = T("Mailing list added"),
996 msg_record_modified = T("Mailing list updated"),
997 msg_record_deleted = T("Mailing list deleted"),
998 msg_list_empty = T("No Mailing List currently established"))
999
1000
1001 configure(tablename,
1002 super_entity="pr_pentity",
1003 deduplicate=self.group_deduplicate,
1004 main="name",
1005 extra="description")
1006
1007
1008 group_represent = lambda id: (id and [db.pr_group[id].name] or [NONE])[0]
1009 group_id = S3ReusableField("group_id", db.pr_group,
1010 sortby="name",
1011 requires = IS_NULL_OR(IS_ONE_OF(db, "pr_group.id",
1012 "%(id)s: %(name)s",
1013 filterby="system",
1014 filter_opts=(False,))),
1015 represent = group_represent,
1016 comment=S3AddResourceLink(c="pr",
1017 f="group",
1018 label=s3.crud_strings.pr_group.label_create_button,
1019 title=T("Create Group Entry"),
1020 tooltip=T("Create a group entry in the registry.")),
1021 ondelete = "RESTRICT")
1022
1023
1024 self.add_component("pr_group_membership", pr_group="group_id")
1025
1026
1027
1028
1029 resourcename = "group_membership"
1030 tablename = "pr_group_membership"
1031 table = define_table(tablename,
1032 group_id(label = T("Group"),
1033 ondelete="CASCADE"),
1034 person_id(label = T("Person"),
1035 ondelete="CASCADE"),
1036 Field("group_head", "boolean",
1037 label = T("Group Head"),
1038 default=False),
1039 Field("description",
1040 label = T("Description")),
1041 comments(),
1042 *meta_fields())
1043
1044
1045 table.group_head.represent = lambda group_head: \
1046 (group_head and [T("yes")] or [""])[0]
1047
1048
1049 request = current.request
1050 if request.function in ("person", "group_membership"):
1051 crud_strings[tablename] = Storage(
1052 title_create = T("Add Membership"),
1053 title_display = T("Membership Details"),
1054 title_list = T("Memberships"),
1055 title_update = T("Edit Membership"),
1056 title_search = T("Search Membership"),
1057 subtitle_create = T("Add New Membership"),
1058 subtitle_list = T("Current Memberships"),
1059 label_list_button = T("List All Memberships"),
1060 label_create_button = T("Add Membership"),
1061 label_delete_button = T("Delete Membership"),
1062 msg_record_created = T("Membership added"),
1063 msg_record_modified = T("Membership updated"),
1064 msg_record_deleted = T("Membership deleted"),
1065 msg_list_empty = T("No Memberships currently registered"))
1066
1067 elif request.function == "group":
1068 crud_strings[tablename] = Storage(
1069 title_create = T("Add Member"),
1070 title_display = T("Membership Details"),
1071 title_list = T("Group Members"),
1072 title_update = T("Edit Membership"),
1073 title_search = T("Search Member"),
1074 subtitle_create = T("Add New Member"),
1075 subtitle_list = T("Current Group Members"),
1076 label_list_button = T("List Members"),
1077 label_create_button = T("Add Group Member"),
1078 label_delete_button = T("Delete Membership"),
1079 msg_record_created = T("Group Member added"),
1080 msg_record_modified = T("Membership updated"),
1081 msg_record_deleted = T("Membership deleted"),
1082 msg_list_empty = T("No Members currently registered"))
1083
1084
1085 configure(tablename,
1086 onaccept = self.group_membership_onaccept,
1087 ondelete = self.group_membership_onaccept,
1088 list_fields=["id",
1089 "group_id",
1090 "person_id",
1091 "group_head",
1092 "description"
1093 ])
1094
1095
1096
1097
1098 return Storage(
1099 pr_group_id = group_id,
1100 pr_group_represent = group_represent,
1101 pr_mailing_list_crud_strings = mailing_list_crud_strings
1102 )
1103
1104
1105 @staticmethod
1107 """ Group de-duplication """
1108
1109 if item.id:
1110 return
1111 if item.tablename == "pr_group":
1112 table = item.table
1113 name = item.data.get("name", None)
1114
1115 query = (table.name == name) & \
1116 (table.deleted != True)
1117 duplicate = current.db(query).select(table.id,
1118 limitby=(0, 1)).first()
1119 if duplicate:
1120 item.id = duplicate.id
1121 item.method = item.METHOD.UPDATE
1122 return
1123
1124
1125 @staticmethod
1127 """
1128 Remove any duplicate memberships and update affiliations
1129 """
1130
1131 db = current.db
1132 s3db = current.s3db
1133
1134 mtable = s3db.pr_group_membership
1135
1136 if hasattr(form, "vars"):
1137 _id = form.vars.id
1138 elif isinstance(form, Row) and "id" in form:
1139 _id = form.id
1140 else:
1141 return
1142 if _id:
1143 record = db(mtable.id == _id).select(limitby=(0, 1)).first()
1144 else:
1145 return
1146 if record:
1147 person_id = record.person_id
1148 group_id = record.group_id
1149 if person_id and group_id and not record.deleted:
1150 query = (mtable.person_id == person_id) & \
1151 (mtable.group_id == group_id) & \
1152 (mtable.id != record.id) & \
1153 (mtable.deleted != True)
1154 deleted_fk = {"person_id": person_id,
1155 "group_id": group_id}
1156 db(query).update(deleted = True,
1157 person_id = None,
1158 group_id = None,
1159 deleted_fk = json.dumps(deleted_fk))
1160 pr_update_affiliations(mtable, record)
1161 return
1162
1312
1315 """ Addresses for Persons """
1316
1317 names = ["pr_address",
1318 "pr_address_type_opts"
1319 ]
1320
1322
1323 T = current.T
1324 db = current.db
1325 request = current.request
1326 s3 = current.response.s3
1327
1328 location_id = self.gis_location_id
1329
1330 UNKNOWN_OPT = current.messages.UNKNOWN_OPT
1331
1332
1333
1334
1335 pr_address_type_opts = {
1336 1:T("Current Home Address"),
1337 2:T("Permanent Home Address"),
1338 3:T("Office Address"),
1339
1340 9:T("Other Address")
1341 }
1342
1343 tablename = "pr_address"
1344 table = self.define_table(tablename,
1345 self.super_link("pe_id", "pr_pentity"),
1346 Field("type", "integer",
1347 requires = IS_IN_SET(pr_address_type_opts, zero=None),
1348 widget = RadioWidget.widget,
1349 default = 1,
1350 label = T("Address Type"),
1351 represent = lambda opt: \
1352 pr_address_type_opts.get(opt, UNKNOWN_OPT)),
1353 location_id(),
1354 s3.comments(),
1355 *(s3.address_fields() + s3.meta_fields()))
1356
1357 table.pe_id.requires = IS_ONE_OF(db, "pr_pentity.pe_id",
1358 pr_pentity_represent,
1359 orderby="instance_type",
1360 filterby="instance_type",
1361 filter_opts=("pr_person", "pr_group"))
1362
1363
1364 if not self.settings.get_gis_building_name():
1365 table.building_name.readable = False
1366
1367
1368 ADD_ADDRESS = T("Add Address")
1369 LIST_ADDRESS = T("List of addresses")
1370 s3.crud_strings[tablename] = Storage(
1371 title_create = ADD_ADDRESS,
1372 title_display = T("Address Details"),
1373 title_list = LIST_ADDRESS,
1374 title_update = T("Edit Address"),
1375 title_search = T("Search Addresses"),
1376 subtitle_create = T("Add New Address"),
1377 subtitle_list = T("Addresses"),
1378 label_list_button = LIST_ADDRESS,
1379 label_create_button = ADD_ADDRESS,
1380 msg_record_created = T("Address added"),
1381 msg_record_modified = T("Address updated"),
1382 msg_record_deleted = T("Address deleted"),
1383 msg_list_empty = T("There is no address for this person yet. Add new address."))
1384
1385
1386 self.configure(tablename,
1387 onaccept=self.address_onaccept,
1388 onvalidation=s3.address_onvalidation,
1389 deduplicate=self.address_deduplicate,
1390 list_fields = ["id",
1391 "type",
1392 "address",
1393 "postcode",
1394
1395 "L3",
1396 "L2",
1397 "L1",
1398 "L0"
1399 ])
1400
1401
1402
1403
1404 return Storage(
1405 pr_address_type_opts = pr_address_type_opts
1406 )
1407
1408
1409 @staticmethod
1411 """
1412 Updates the Base Location to be the same as the Address
1413
1414 If the base location hasn't yet been set or if this is specifically
1415 requested
1416 """
1417
1418 db = current.db
1419 s3db = current.s3db
1420 request = current.request
1421 settings = current.deployment_settings
1422 lx_update = current.response.s3.lx_update
1423
1424 vars = form.vars
1425 location_id = vars.location_id
1426 pe_id = vars.pe_id
1427
1428 if location_id:
1429 person = None
1430 table = s3db.pr_person
1431 if "base_location" in request.vars and \
1432 request.vars.base_location == "on":
1433
1434 S3Tracker()(s3db.pr_pentity, pe_id).set_base_location(location_id)
1435 person = db(table.pe_id == pe_id).select(table.id,
1436 limitby=(0, 1)).first()
1437 if person:
1438
1439 lx_update(table, person.id)
1440 else:
1441
1442 query = (table.pe_id == pe_id)
1443 person = db(query).select(table.id,
1444 table.location_id).first()
1445 if person and not person.location_id:
1446
1447 S3Tracker()(s3db.pr_pentity, pe_id).set_base_location(location_id)
1448
1449 lx_update(table, person.id)
1450
1451 if person and str(vars.type) == "1":
1452 if settings.has_module("hrm"):
1453
1454 htable = s3db.hrm_human_resource
1455 query = (htable.person_id == person.id) & \
1456 (htable.type == 2) & \
1457 (htable.deleted != True)
1458 hrs = db(query).select(htable.id)
1459 for hr in hrs:
1460 db(htable.id == hr.id).update(location_id=location_id)
1461
1462 lx_update(htable, hr.id)
1463 if settings.has_module("member"):
1464
1465 mtable = s3db.member_membership
1466 query = (mtable.person_id == person.id) & \
1467 (mtable.deleted != True)
1468 members = db(query).select(mtable.id)
1469 for member in members:
1470 db(mtable.id == member.id).update(location_id=location_id)
1471
1472 lx_update(mtable, member.id)
1473 return
1474
1475
1476 @staticmethod
1478 """ Address de-duplication """
1479
1480 if item.id:
1481 return
1482 if item.tablename == "pr_address":
1483 table = item.table
1484 pe_id = item.data.get("pe_id", None)
1485 address = item.data.get("address", None)
1486
1487 if pe_id is None:
1488 return
1489
1490 query = (table.pe_id == pe_id) & \
1491 (table.address == address) & \
1492 (table.deleted != True)
1493 duplicate = current.db(query).select(table.id,
1494 limitby=(0, 1)).first()
1495 if duplicate:
1496 item.id = duplicate.id
1497 item.method = item.METHOD.UPDATE
1498 return
1499
1502 """ Images for Persons """
1503
1504 names = ["pr_image"]
1505
1507
1508 T = current.T
1509 db = current.db
1510 request = current.request
1511 s3 = current.response.s3
1512
1513 UNKNOWN_OPT = current.messages.UNKNOWN_OPT
1514
1515
1516
1517
1518 pr_image_type_opts = {
1519 1:T("Photograph"),
1520 2:T("Sketch"),
1521 3:T("Fingerprint"),
1522 4:T("X-Ray"),
1523 5:T("Document Scan"),
1524 9:T("other")
1525 }
1526
1527 tablename = "pr_image"
1528 table = self.define_table(tablename,
1529 self.super_link("pe_id", "pr_pentity"),
1530 Field("profile", "boolean",
1531 default = False,
1532 label = T("Profile Picture?")
1533 ),
1534 Field("type", "integer",
1535 requires = IS_IN_SET(pr_image_type_opts, zero=None),
1536 default = 1,
1537 label = T("Image Type"),
1538 represent = lambda opt: pr_image_type_opts.get(opt,
1539 UNKNOWN_OPT)),
1540 Field("title", label=T("Title"),
1541 requires = IS_NOT_EMPTY(),
1542 comment = DIV(_class="tooltip",
1543 _title="%s|%s" % (T("Title"),
1544 T("Specify a descriptive title for the image.")))),
1545 Field("image", "upload", autodelete=True,
1546 represent = lambda image: image and \
1547 DIV(A(IMG(_src=URL(c="default", f="download",
1548 args=image),
1549 _height=60, _alt=T("View Image")),
1550 _href=URL(c="default", f="download",
1551 args=image))) or T("No Image"),
1552 comment = DIV(_class="tooltip",
1553 _title="%s|%s" % (T("Image"),
1554 T("Upload an image file here. If you don't upload an image file, then you must specify its location in the URL field.")))),
1555 Field("url",
1556 label = T("URL"),
1557 represent = lambda url: url and \
1558 DIV(A(IMG(_src=url, _height=60), _href=url)) or T("None"),
1559 comment = DIV(_class="tooltip",
1560 _title="%s|%s" % (T("URL"),
1561 T("The URL of the image file. If you don't upload an image file, then you must specify its location here.")))),
1562 Field("description",
1563 label=T("Description"),
1564 comment = DIV(_class="tooltip",
1565 _title="%s|%s" % (T("Description"),
1566 T("Give a brief description of the image, e.g. what can be seen where on the picture (optional).")))),
1567 s3.comments(),
1568 *s3.meta_fields())
1569
1570
1571 LIST_IMAGES = T("List Images")
1572 s3.crud_strings[tablename] = Storage(
1573 title_create = T("Image"),
1574 title_display = T("Image Details"),
1575 title_list = LIST_IMAGES,
1576 title_update = T("Edit Image Details"),
1577 title_search = T("Search Images"),
1578 subtitle_create = T("Add New Image"),
1579 subtitle_list = T("Images"),
1580 label_list_button = LIST_IMAGES,
1581 label_create_button = T("Add Image"),
1582 label_delete_button = T("Delete Image"),
1583 msg_record_created = T("Image added"),
1584 msg_record_modified = T("Image updated"),
1585 msg_record_deleted = T("Image deleted"),
1586 msg_list_empty = T("No Images currently registered"))
1587
1588
1589 self.configure(tablename,
1590 onaccept = self.pr_image_onaccept,
1591 onvalidation = self.pr_image_onvalidation,
1592 mark_required = ["url", "image"],
1593 list_fields=["id",
1594 "title",
1595 "profile",
1596 "type",
1597 "image",
1598 "url",
1599 "description"
1600 ])
1601
1602
1603
1604
1605 return Storage()
1606
1607
1608 @staticmethod
1610 """
1611 If this is the profile image then remove this flag from all
1612 others for this person.
1613 """
1614
1615 db = current.db
1616 s3db = current.s3db
1617 table = s3db.pr_image
1618
1619 vars = form.vars
1620 id = vars.id
1621 profile = vars.profile
1622 url = vars.url
1623 newfilename = vars.image_newfilename
1624 if profile == 'False':
1625 profile = False
1626
1627 if newfilename and not url:
1628
1629 url = URL(c="default", f="download", args=newfilename)
1630 query = (table.id == id)
1631 db(query).update(url = url)
1632
1633 if profile:
1634
1635 query = (table.id == id)
1636 pe = db(query).select(table.pe_id,
1637 limitby=(0, 1)).first()
1638 if pe:
1639 pe_id = pe.pe_id
1640
1641 query = (table.pe_id == pe_id) & \
1642 (table.id != id)
1643 db(query).update(profile = False)
1644
1645
1646 @staticmethod
1648 """ Image form validation """
1649
1650 db = current.db
1651 s3db = current.s3db
1652 request = current.request
1653
1654 vars = form.vars
1655 table = s3db.pr_image
1656 image = vars.image
1657 url = vars.url
1658
1659 if not hasattr(image, "file"):
1660 id = request.post_vars.id
1661 if id:
1662 record = db(table.id == id).select(table.image,
1663 limitby=(0, 1)).first()
1664 if record:
1665 image = record.image
1666
1667 if not hasattr(image, "file") and not image and not url:
1668 form.errors.image = \
1669 form.errors.url = T("Either file upload or image URL required.")
1670 return
1671
1674 """ Identities for Persons """
1675
1676 names = ["pr_identity"]
1677
1679
1680 T = current.T
1681 db = current.db
1682 request = current.request
1683 s3 = current.response.s3
1684
1685 person_id = self.pr_person_id
1686
1687 UNKNOWN_OPT = current.messages.UNKNOWN_OPT
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704 pr_id_type_opts = {
1705 1:T("Passport"),
1706 2:T("National ID Card"),
1707 3:T("Driving License"),
1708
1709 99:T("other")
1710 }
1711
1712 tablename = "pr_identity"
1713 table = self.define_table(tablename,
1714 person_id(label = T("Person"),
1715 ondelete="CASCADE"),
1716 Field("type", "integer",
1717 requires = IS_IN_SET(pr_id_type_opts, zero=None),
1718 default = 1,
1719 label = T("ID type"),
1720 represent = lambda opt: \
1721 pr_id_type_opts.get(opt,
1722 UNKNOWN_OPT)),
1723 Field("value"),
1724 Field("description"),
1725 Field("country_code", length=4),
1726 Field("ia_name", label = T("Issuing Authority")),
1727
1728
1729 s3.comments(),
1730 *s3.meta_fields())
1731
1732
1733 table.value.requires = [IS_NOT_EMPTY(),
1734 IS_NOT_ONE_OF(db, "%s.value" % tablename)]
1735
1736
1737 ADD_IDENTITY = T("Add Identity")
1738 s3.crud_strings[tablename] = Storage(
1739 title_create = ADD_IDENTITY,
1740 title_display = T("Identity Details"),
1741 title_list = T("Known Identities"),
1742 title_update = T("Edit Identity"),
1743 title_search = T("Search Identity"),
1744 subtitle_create = T("Add New Identity"),
1745 subtitle_list = T("Current Identities"),
1746 label_list_button = T("List Identities"),
1747 label_create_button = ADD_IDENTITY,
1748 msg_record_created = T("Identity added"),
1749 msg_record_modified = T("Identity updated"),
1750 msg_record_deleted = T("Identity deleted"),
1751 msg_list_empty = T("No Identities currently registered"))
1752
1753
1754 self.configure(tablename,
1755 list_fields=["id",
1756 "type",
1757 "type",
1758 "value",
1759 "country_code",
1760 "ia_name"
1761 ])
1762
1763
1764
1765
1766 return Storage()
1767
1771 """ Saved Searches """
1772
1773 names = ["pr_save_search"]
1774
1776
1777 T = current.T
1778 db = current.db
1779 auth = current.auth
1780 request = current.request
1781 s3 = current.response.s3
1782
1783 pr_person = self.table("pr_person")
1784 person_id = s3.pr_person_id
1785
1786
1787
1788
1789 tablename = "pr_save_search"
1790 table = self.define_table(tablename,
1791 Field("user_id", "integer",
1792 readable = False,
1793 writable = False,
1794 default = auth.user_id),
1795 Field("search_vars","text",
1796 label = T("Search Criteria"),
1797 represent=lambda id:search_vars_represent(id)),
1798 Field("subscribed","boolean",
1799 default=False),
1800 person_id(label = T("Person"),
1801 ondelete="CASCADE",
1802 default = auth.s3_logged_in_person()),
1803 *s3.meta_fields())
1804
1805
1806
1807 s3.crud_strings[tablename] = Storage(
1808 title_create = T("Save Search"),
1809 title_display = T("Saved Search Details"),
1810 title_list = T("Saved Searches"),
1811 title_update = T("Edit Saved Search"),
1812 title_search = T("Search Saved Searches"),
1813 subtitle_create = T("Add Saved Search"),
1814 subtitle_list = T("Saved Searches"),
1815 label_list_button = T("List Saved Searches"),
1816 label_create_button = T("Save Search"),
1817 label_delete_button = T("Delete Saved Search"),
1818 msg_record_created = T("Saved Search added"),
1819 msg_record_modified = T("Saved Search updated"),
1820 msg_record_deleted = T("Saved Search deleted"),
1821 msg_list_empty = T("No Search saved"))
1822
1823
1824 self.configure(tablename,
1825 insertable = False,
1826 editable = False,
1827 listadd = False,
1828 deletable = True,
1829 list_fields=["search_vars"])
1830
1831
1832
1833
1834 return Storage()
1835
1839 """
1840 Presence Log for Persons
1841
1842 @todo: deprecate
1843 currently still used by CR
1844 """
1845
1846 names = ["pr_presence",
1847 "pr_trackable_types",
1848 "pr_default_trackable",
1849 "pr_presence_opts",
1850 "pr_presence_conditions",
1851 "pr_default_presence"]
1852
1854
1855 T = current.T
1856 db = current.db
1857 auth = current.auth
1858 request = current.request
1859 s3 = current.response.s3
1860
1861 person_id = self.pr_person_id
1862 location_id = self.gis_location_id
1863
1864 ADD_LOCATION = current.messages.ADD_LOCATION
1865 UNKNOWN_OPT = current.messages.UNKNOWN_OPT
1866
1867 datetime_represent = S3DateTime.datetime_represent
1868
1869
1870 pr_trackable_types = {
1871 1:current.T("Person"),
1872 2:current.T("Group"),
1873 3:current.T("Body"),
1874 4:current.T("Object"),
1875 5:current.T("Organization"),
1876 6:current.T("Office"),
1877 }
1878 pr_default_trackable = 1
1879
1880
1881 pr_presence_opts = Storage(
1882 SEEN = 1,
1883 TRANSIT = 2,
1884 PROCEDURE = 3,
1885 TRANSITIONAL_PRESENCE = (1, 2, 3),
1886 CHECK_IN = 11,
1887 CONFIRMED = 12,
1888 DECEASED = 13,
1889 LOST = 14,
1890 PERSISTANT_PRESENCE = (11, 12, 13, 14),
1891 TRANSFER = 21,
1892 CHECK_OUT = 22,
1893 ABSENCE = (21, 22),
1894 MISSING = 99
1895 )
1896 opts = pr_presence_opts
1897 pr_presence_conditions = Storage({
1898
1899 opts.SEEN: current.T("Seen"),
1900 opts.TRANSIT: current.T("Transit"),
1901 opts.PROCEDURE: current.T("Procedure"),
1902
1903
1904 opts.CHECK_IN: current.T("Check-In"),
1905 opts.CONFIRMED: current.T("Confirmed"),
1906 opts.DECEASED: current.T("Deceased"),
1907 opts.LOST: current.T("Lost"),
1908
1909
1910 opts.TRANSFER: current.T("Transfer"),
1911 opts.CHECK_OUT: current.T("Check-Out"),
1912
1913
1914 opts.MISSING: current.T("Missing"),
1915 })
1916 pr_default_presence = 1
1917
1918 resourcename = "presence"
1919 tablename = "pr_presence"
1920 table = self.define_table(tablename,
1921 self.super_link("pe_id", "pr_pentity"),
1922 self.super_link("sit_id", "sit_situation"),
1923 person_id("observer",
1924 label=T("Observer"),
1925 default = auth.s3_logged_in_person(),
1926 comment=pr_person_comment(title=T("Observer"),
1927 comment=T("Person who has actually seen the person/group."),
1928 child="observer")),
1929 Field("shelter_id", "integer",
1930 readable = False,
1931 writable = False),
1932 location_id(widget=S3LocationAutocompleteWidget(),
1933 comment=S3AddResourceLink(c="gis",
1934 f="location",
1935 label=ADD_LOCATION,
1936 title=T("Current Location"),
1937 tooltip=T("The Current Location of the Person/Group, which can be general (for Reporting) or precise (for displaying on a Map). Enter a few characters to search from available locations."))),
1938 Field("location_details",
1939 comment = DIV(_class="tooltip",
1940 _title="%s|%s" % (T("Location Details"),
1941 T("Specific Area (e.g. Building/Room) within the Location that this Person/Group is seen.")))),
1942 Field("datetime", "datetime",
1943 label = T("Date/Time"),
1944 default = request.utcnow,
1945 requires = IS_UTC_DATETIME(allow_future=False),
1946 widget = S3DateTimeWidget(future=0),
1947 represent = lambda val: datetime_represent(val, utc=True)),
1948 Field("presence_condition", "integer",
1949 requires = IS_IN_SET(pr_presence_conditions,
1950 zero=None),
1951 default = pr_default_presence,
1952 label = T("Presence Condition"),
1953 represent = lambda opt: \
1954 pr_presence_conditions.get(opt, UNKNOWN_OPT)),
1955 Field("proc_desc",
1956 label = T("Procedure"),
1957 comment = DIV(DIV(_class="tooltip",
1958 _title="%s|%s" % (T("Procedure"),
1959 T('Describe the procedure which this record relates to (e.g. "medical examination")'))))),
1960 location_id("orig_id",
1961 label=T("Origin"),
1962 widget = S3LocationAutocompleteWidget(),
1963 comment=S3AddResourceLink(c="gis",
1964 f="location",
1965 label=ADD_LOCATION,
1966 title=T("Origin"),
1967 tooltip=T("The Location the Person has come from, which can be general (for Reporting) or precise (for displaying on a Map). Enter a few characters to search from available locations."))
1968 ),
1969
1970 location_id("dest_id",
1971 label=T("Destination"),
1972 widget = S3LocationAutocompleteWidget(),
1973 comment=S3AddResourceLink(c="gis",
1974 f="location",
1975 label=ADD_LOCATION,
1976 title=T("Destination"),
1977 tooltip=T("The Location the Person is going to, which can be general (for Reporting) or precise (for displaying on a Map). Enter a few characters to search from available locations."))
1978 ),
1979
1980 Field("comment"),
1981 Field("closed", "boolean",
1982 default=False,
1983 readable = False,
1984 writable = False),
1985 *s3.meta_fields())
1986
1987
1988 ADD_LOG_ENTRY = T("Add Log Entry")
1989 s3.crud_strings[tablename] = Storage(
1990 title_create = ADD_LOG_ENTRY,
1991 title_display = T("Log Entry Details"),
1992 title_list = T("Presence Log"),
1993 title_update = T("Edit Log Entry"),
1994 title_search = T("Search Log Entry"),
1995 subtitle_create = T("Add New Log Entry"),
1996 subtitle_list = T("Current Log Entries"),
1997 label_list_button = T("List Log Entries"),
1998 label_create_button = ADD_LOG_ENTRY,
1999 msg_record_created = T("Log entry added"),
2000 msg_record_modified = T("Log entry updated"),
2001 msg_record_deleted = T("Log entry deleted"),
2002 msg_list_empty = T("No Presence Log Entries currently registered"))
2003
2004
2005 self.configure(tablename,
2006 super_entity = "sit_situation",
2007 onvalidation = self.presence_onvalidation,
2008 onaccept = self.presence_onaccept,
2009 delete_onaccept = self.presence_onaccept,
2010 list_fields = ["id",
2011 "datetime",
2012 "location_id",
2013 "shelter_id",
2014 "presence_condition",
2015 "orig_id",
2016 "dest_id"
2017 ],
2018 main="time",
2019 extra="location_details")
2020
2021
2022
2023
2024 return Storage(
2025 pr_trackable_types=pr_trackable_types,
2026 pr_default_trackable=pr_default_trackable,
2027 pr_presence_opts=pr_presence_opts,
2028 pr_presence_conditions=pr_presence_conditions,
2029 pr_default_presence=pr_default_presence
2030 )
2031
2032
2033 @staticmethod
2035 """ Presence record validation """
2036
2037 db = current.db
2038 s3db = current.s3db
2039 s3 = current.response.s3
2040
2041 table = s3db.pr_presence
2042 popts = s3.pr_presence_opts
2043 shelter_table = s3db.cr_shelter
2044
2045 location = form.vars.location_id
2046 shelter = form.vars.shelter_id
2047
2048 if shelter and shelter_table is not None:
2049 set = db(shelter_table.id == shelter)
2050 row = set.select(shelter_table.location_id, limitby=(0, 1)).first()
2051 if row:
2052 location = form.vars.location_id = row.location_id
2053 else:
2054 shelter = None
2055
2056 if location or shelter:
2057 return
2058
2059 condition = form.vars.presence_condition
2060 if condition:
2061 try:
2062 condition = int(condition)
2063 except ValueError:
2064 condition = None
2065 else:
2066 condition = table.presence_condition.default
2067 form.vars.presence_condition = condition
2068
2069 if condition:
2070 if condition in popts.PERSISTANT_PRESENCE or \
2071 condition in popts.ABSENCE:
2072 if not form.vars.id:
2073 if table.location_id.default or \
2074 table.shelter_id.default:
2075 return
2076 else:
2077 record = db(table.id == form.vars.id).select(table.location_id,
2078 table.shelter_id,
2079 limitby=(0, 1)).first()
2080 if record and \
2081 record.location_id or record.shelter_id:
2082 return
2083 else:
2084 return
2085 else:
2086 return
2087
2088 form.errors.location_id = \
2089 form.errors.shelter_id = T("Either a shelter or a location must be specified")
2090 return
2091
2092
2093 @staticmethod
2095 """
2096 Update the presence log of a person entity
2097
2098 - mandatory to be called as onaccept routine at any modification
2099 of pr_presence records
2100
2101 """
2102
2103 db = current.db
2104 s3db = current.s3db
2105 s3 = current.response.s3
2106
2107 table = s3db.pr_presence
2108 popts = s3.pr_presence_opts
2109
2110 if isinstance(form, (int, long, str)):
2111 id = form
2112 elif hasattr(form, "vars"):
2113 id = form.vars.id
2114 else:
2115 id = form.id
2116
2117 presence = db(table.id == id).select(table.ALL, limitby=(0,1)).first()
2118 if not presence:
2119 return
2120 else:
2121 condition = presence.presence_condition
2122
2123 pe_id = presence.pe_id
2124 datetime = presence.datetime
2125 if not datetime or not pe_id:
2126 return
2127
2128 this_entity = ((table.pe_id == pe_id) & (table.deleted == False))
2129 earlier = (table.datetime < datetime)
2130 later = (table.datetime > datetime)
2131 same_place = ((table.location_id == presence.location_id) |
2132 (table.shelter_id == presence.shelter_id))
2133 is_present = (table.presence_condition.belongs(popts.PERSISTANT_PRESENCE))
2134 is_absent = (table.presence_condition.belongs(popts.ABSENCE))
2135 is_missing = (table.presence_condition == popts.MISSING)
2136
2137 if not presence.deleted:
2138
2139 if condition in popts.TRANSITIONAL_PRESENCE:
2140 if presence.closed:
2141 db(table.id == id).update(closed=False)
2142
2143 elif condition in popts.PERSISTANT_PRESENCE:
2144 if not presence.closed:
2145 query = this_entity & earlier & (is_present | is_missing) & \
2146 (table.closed == False)
2147 db(query).update(closed=True)
2148
2149 query = this_entity & later & \
2150 (is_present | (is_absent & same_place))
2151 if db(query).count():
2152 db(table.id == id).update(closed=True)
2153
2154 elif condition in popts.ABSENCE:
2155 query = this_entity & earlier & is_present & same_place
2156 db(query).update(closed=True)
2157
2158 if not presence.closed:
2159 db(table.id == id).update(closed=True)
2160
2161 if not presence.closed:
2162
2163
2164 query = this_entity & is_present
2165 presence = db(query).select(table.ALL, orderby=~table.datetime, limitby=(0,1)).first()
2166 if presence and presence.closed:
2167 later = (table.datetime > presence.datetime)
2168 query = this_entity & later & is_absent & same_place
2169 if not db(query).count():
2170 db(table.id == presence.id).update(closed=False)
2171
2172
2173 query = this_entity & is_missing
2174 presence = db(query).select(table.ALL, orderby=~table.datetime, limitby=(0,1)).first()
2175 if presence and presence.closed:
2176 later = (table.datetime > presence.datetime)
2177 query = this_entity & later & is_present
2178 if not db(query).count():
2179 db(table.id == presence.id).update(closed=False)
2180
2181 pentity = db(db.pr_pentity.pe_id == pe_id).select(db.pr_pentity.instance_type,
2182 limitby=(0,1)).first()
2183 if pentity and pentity.instance_type == "pr_person":
2184 query = this_entity & is_missing & (table.closed == False)
2185 if db(query).count():
2186 db(db.pr_person.pe_id == pe_id).update(missing = True)
2187 else:
2188 db(db.pr_person.pe_id == pe_id).update(missing = False)
2189
2190 return
2191
2194 """ Additional tables for DVI/MPR """
2195
2196 names = ["pr_note", "pr_physical_description"]
2197
2199
2200 T = current.T
2201 db = current.db
2202 auth = current.auth
2203 request = current.request
2204 s3 = current.response.s3
2205
2206 person_id = self.pr_person_id
2207 location_id = self.gis_location_id
2208
2209 UNKNOWN_OPT = current.messages.UNKNOWN_OPT
2210
2211
2212
2213
2214
2215
2216 person_status = {
2217 1: T("missing"),
2218 2: T("found"),
2219 3: T("deceased"),
2220 9: T("none")
2221 }
2222
2223 resourcename = "note"
2224 tablename = "pr_note"
2225 table = self.define_table(tablename,
2226 self.super_link("pe_id", "pr_pentity"),
2227
2228
2229 Field("confirmed", "boolean",
2230 default=False,
2231 readable=False,
2232 writable=False),
2233 Field("closed", "boolean",
2234 default=False,
2235 readable=False,
2236 writable=False),
2237 Field("status", "integer",
2238 requires=IS_IN_SET(person_status,
2239 zero=None),
2240 default=9,
2241 label=T("Status"),
2242 represent=lambda opt: \
2243 person_status.get(opt, UNKNOWN_OPT)),
2244 Field("timestmp", "datetime",
2245 label=T("Date/Time"),
2246 requires=[IS_EMPTY_OR(IS_UTC_DATETIME_IN_RANGE())],
2247 widget = S3DateTimeWidget(),
2248 default=request.utcnow),
2249 Field("note_text", "text",
2250 label=T("Text")),
2251 Field("note_contact", "text",
2252 label=T("Contact Info"),
2253 readable=False,
2254 writable=False),
2255 location_id(label=T("Last known location")),
2256 *s3.meta_fields())
2257
2258
2259 ADD_NOTE = T("New Entry")
2260 s3.crud_strings[tablename] = Storage(
2261 title_create = ADD_NOTE,
2262 title_display = T("Journal Entry Details"),
2263 title_list = T("Journal"),
2264 title_update = T("Edit Entry"),
2265 title_search = T("Search Entries"),
2266 subtitle_create = T("Add New Entry"),
2267 subtitle_list = T("Current Entries"),
2268 label_list_button = T("See All Entries"),
2269 label_create_button = ADD_NOTE,
2270 msg_record_created = T("Journal entry added"),
2271 msg_record_modified = T("Journal entry updated"),
2272 msg_record_deleted = T("Journal entry deleted"),
2273 msg_list_empty = T("No entry available"))
2274
2275
2276 self.configure(tablename,
2277 list_fields=["id",
2278 "timestmp",
2279 "location_id",
2280 "note_text",
2281 "status"],
2282 editable=False,
2283 onaccept=self.note_onaccept,
2284 ondelete=self.note_onaccept)
2285
2286
2287
2288
2289 pr_race_opts = {
2290 1: T("caucasoid"),
2291 2: T("mongoloid"),
2292 3: T("negroid"),
2293 99: T("other")
2294 }
2295
2296 pr_complexion_opts = {
2297 1: T("light"),
2298 2: T("medium"),
2299 3: T("dark"),
2300 99: T("other")
2301 }
2302
2303 pr_height_opts = {
2304 1: T("short"),
2305 2: T("average"),
2306 3: T("tall")
2307 }
2308
2309 pr_weight_opts = {
2310 1: T("slim"),
2311 2: T("average"),
2312 3: T("fat")
2313 }
2314
2315
2316 pr_blood_type_opts = ("A+", "A-", "B+", "B-", "AB+", "AB-", "0+", "0-")
2317
2318 pr_eye_color_opts = {
2319 1: T("blue"),
2320 2: T("grey"),
2321 3: T("green"),
2322 4: T("brown"),
2323 5: T("black"),
2324 99: T("other")
2325 }
2326
2327 pr_hair_color_opts = {
2328 1: T("blond"),
2329 2: T("brown"),
2330 3: T("black"),
2331 4: T("red"),
2332 5: T("grey"),
2333 6: T("white"),
2334 99: T("see comment")
2335 }
2336
2337 pr_hair_style_opts = {
2338 1: T("straight"),
2339 2: T("wavy"),
2340 3: T("curly"),
2341 99: T("see comment")
2342 }
2343
2344 pr_hair_length_opts = {
2345 1: T("short<6cm"),
2346 2: T("medium<12cm"),
2347 3: T("long>12cm"),
2348 4: T("shaved"),
2349 99: T("see comment")
2350 }
2351
2352 pr_hair_baldness_opts = {
2353 1: T("forehead"),
2354 2: T("sides"),
2355 3: T("tonsure"),
2356 4: T("total"),
2357 99: T("see comment")
2358 }
2359
2360 pr_facial_hair_type_opts = {
2361 1: T("none"),
2362 2: T("Moustache"),
2363 3: T("Goatee"),
2364 4: T("Whiskers"),
2365 5: T("Full beard"),
2366 99: T("see comment")
2367 }
2368
2369 pr_facial_hair_length_opts = {
2370 1: T("short"),
2371 2: T("medium"),
2372 3: T("long"),
2373 4: T("shaved")
2374 }
2375
2376
2377 pr_ethnicity_opts = [
2378 "American Indian",
2379 "Alaskan",
2380 "Asian",
2381 "African American",
2382 "Hispanic or Latino",
2383 "Native Hawaiian",
2384 "Pacific Islander",
2385 "Two or more",
2386 "Unspecified",
2387 "White"
2388 ]
2389
2390 resourcename = "physical_description"
2391 tablename = "pr_physical_description"
2392 table = self.define_table(tablename,
2393 self.super_link("pe_id", "pr_pentity"),
2394
2395 Field("race", "integer",
2396 requires = IS_EMPTY_OR(IS_IN_SET(pr_race_opts)),
2397 label = T("Race"),
2398 represent = lambda opt: \
2399 pr_race_opts.get(opt, UNKNOWN_OPT)),
2400 Field("complexion", "integer",
2401 requires = IS_EMPTY_OR(IS_IN_SET(pr_complexion_opts)),
2402 label = T("Complexion"),
2403 represent = lambda opt: \
2404 pr_complexion_opts.get(opt, UNKNOWN_OPT)),
2405 Field("ethnicity",
2406
2407 length=64),
2408
2409
2410 Field("height", "integer",
2411 requires = IS_EMPTY_OR(IS_IN_SET(pr_height_opts)),
2412 label = T("Height"),
2413 represent = lambda opt: \
2414 pr_height_opts.get(opt, UNKNOWN_OPT)),
2415 Field("height_cm", "integer",
2416 requires = IS_NULL_OR(IS_INT_IN_RANGE(0, 300)),
2417 label = T("Height (cm)")),
2418 Field("weight", "integer",
2419 requires = IS_EMPTY_OR(IS_IN_SET(pr_weight_opts)),
2420 label = T("Weight"),
2421 represent = lambda opt: \
2422 pr_weight_opts.get(opt, UNKNOWN_OPT)),
2423 Field("weight_kg", "integer",
2424 requires = IS_NULL_OR(IS_INT_IN_RANGE(0, 500)),
2425 label = T("Weight (kg)")),
2426
2427
2428 Field("blood_type",
2429 requires = IS_EMPTY_OR(IS_IN_SET(pr_blood_type_opts)),
2430 label = T("Blood Type (AB0)"),
2431 represent = lambda opt: opt or UNKNOWN_OPT),
2432 Field("eye_color", "integer",
2433 requires = IS_EMPTY_OR(IS_IN_SET(pr_eye_color_opts)),
2434 label = T("Eye Color"),
2435 represent = lambda opt: \
2436 pr_eye_color_opts.get(opt, UNKNOWN_OPT)),
2437
2438
2439 Field("hair_color", "integer",
2440 requires = IS_EMPTY_OR(IS_IN_SET(pr_hair_color_opts)),
2441 label = T("Hair Color"),
2442 represent = lambda opt: \
2443 pr_hair_color_opts.get(opt, UNKNOWN_OPT)),
2444 Field("hair_style", "integer",
2445 requires = IS_EMPTY_OR(IS_IN_SET(pr_hair_style_opts)),
2446 label = T("Hair Style"),
2447 represent = lambda opt: \
2448 pr_hair_style_opts.get(opt, UNKNOWN_OPT)),
2449 Field("hair_length", "integer",
2450 requires = IS_EMPTY_OR(IS_IN_SET(pr_hair_length_opts)),
2451 label = T("Hair Length"),
2452 represent = lambda opt: \
2453 pr_hair_length_opts.get(opt, UNKNOWN_OPT)),
2454 Field("hair_baldness", "integer",
2455 requires = IS_EMPTY_OR(IS_IN_SET(pr_hair_baldness_opts)),
2456 label = T("Baldness"),
2457 represent = lambda opt: \
2458 pr_hair_baldness_opts.get(opt, UNKNOWN_OPT)),
2459 Field("hair_comment"),
2460
2461
2462 Field("facial_hair_type", "integer",
2463 requires = IS_EMPTY_OR(IS_IN_SET(pr_facial_hair_type_opts)),
2464 label = T("Facial hair, type"),
2465 represent = lambda opt: \
2466 pr_facial_hair_type_opts.get(opt, UNKNOWN_OPT)),
2467 Field("facial_hair_color", "integer",
2468 requires = IS_EMPTY_OR(IS_IN_SET(pr_hair_color_opts)),
2469 label = T("Facial hair, color"),
2470 represent = lambda opt: \
2471 pr_hair_color_opts.get(opt, UNKNOWN_OPT)),
2472 Field("facial_hair_length", "integer",
2473 requires = IS_EMPTY_OR(IS_IN_SET(pr_facial_hair_length_opts)),
2474 label = T("Facial hear, length"),
2475 represent = lambda opt: \
2476 pr_facial_hair_length_opts.get(opt, UNKNOWN_OPT)),
2477 Field("facial_hair_comment"),
2478
2479
2480 Field("body_hair"),
2481 Field("skin_marks", "text"),
2482
2483
2484 Field("medical_conditions", "text"),
2485
2486
2487 Field("other_details", "text"),
2488
2489 s3.comments(),
2490 *s3.meta_fields())
2491
2492
2493 table.height_cm.comment = DIV(DIV(_class="tooltip",
2494 _title="%s|%s" % (T("Height"),
2495 T("The body height (crown to heel) in cm."))))
2496 table.weight_kg.comment = DIV(DIV(_class="tooltip",
2497 _title="%s|%s" % (T("Weight"),
2498 T("The weight in kg."))))
2499
2500 table.pe_id.readable = False
2501 table.pe_id.writable = False
2502
2503
2504
2505
2506
2507
2508
2509
2510 @staticmethod
2512 """ Update missing status for person """
2513
2514 db = current.db
2515 s3db = current.s3db
2516
2517 pe_table = s3db.pr_pentity
2518 ntable = s3db.pr_note
2519 ptable = s3db.pr_person
2520
2521 if isinstance(form, (int, long, str)):
2522 _id = form
2523 elif hasattr(form, "vars"):
2524 _id = form.vars.id
2525 else:
2526 _id = form.id
2527
2528 note = ntable[_id]
2529 if not note:
2530 return
2531
2532 query = (ntable.pe_id == note.pe_id) & \
2533 (ntable.deleted != True)
2534 mq = query & ntable.status == 1
2535 fq = query & ntable.status.belongs((2, 3))
2536 mr = db(mq).select(ntable.id,
2537 ntable.timestmp,
2538 orderby=~ntable.timestmp,
2539 limitby=(0, 1)).first()
2540 fr = db(fq).select(ntable.id,
2541 ntable.timestmp,
2542 orderby=~ntable.timestmp,
2543 limitby=(0, 1)).first()
2544 missing = False
2545 if mr and not fr or fr.timestmp < mr.timestmp:
2546 missing = True
2547 query = ptable.pe_id == note.pe_id
2548 db(query).update(missing=missing)
2549 if note.deleted:
2550 try:
2551 location_id = form.location_id
2552 except:
2553 pass
2554 else:
2555 ttable = s3db.sit_presence
2556 query = (ptable.pe_id == note.pe_id) & \
2557 (ttable.uuid == ptable.uuid) & \
2558 (ttable.location_id == location_id) & \
2559 (ttable.timestmp == note.timestmp)
2560 if note.location_id:
2561 tracker = S3Tracker()
2562 tracker(query).set_location(note.location_id,
2563 timestmp=note.timestmp)
2564 return
2565
2571 """ Represent a Person Entity in option fields or list views """
2572
2573 T = current.T
2574 db = current.db
2575 s3db = current.s3db
2576
2577 if not id:
2578 return current.messages.NONE
2579
2580 pe_str = T("None (no such record)")
2581
2582 pe_table = s3db.pr_pentity
2583 pe = db(pe_table.pe_id == id).select(pe_table.instance_type,
2584 pe_table.pe_label,
2585 limitby=(0, 1)).first()
2586 if not pe:
2587 return pe_str
2588
2589 instance_type = pe.instance_type
2590 instance_type_nice = pe_table.instance_type.represent(instance_type)
2591
2592 table = s3db.table(instance_type, None)
2593 if not table:
2594 return pe_str
2595
2596 label = pe.pe_label or default_label
2597
2598 if instance_type == "pr_person":
2599 person = db(table.pe_id == id).select(
2600 table.first_name, table.middle_name, table.last_name,
2601 limitby=(0, 1)).first()
2602 if person:
2603 if show_label:
2604 pe_str = "%s %s (%s)" % (s3_fullname(person),
2605 label, instance_type_nice)
2606 else:
2607 pe_str = "%s (%s)" % (s3_fullname(person),
2608 instance_type_nice)
2609 elif instance_type == "pr_group":
2610 group = db(table.pe_id == id).select(table.name,
2611 limitby=(0, 1)).first()
2612 if group:
2613 pe_str = "%s (%s)" % (group.name, instance_type_nice)
2614 elif instance_type == "org_organisation":
2615 organisation = db(table.pe_id == id).select(table.name,
2616 limitby=(0, 1)).first()
2617 if organisation:
2618 pe_str = "%s (%s)" % (organisation.name, instance_type_nice)
2619 elif instance_type == "org_office":
2620 office = db(table.pe_id == id).select(table.name,
2621 limitby=(0, 1)).first()
2622 if office:
2623 pe_str = "%s (%s)" % (office.name, instance_type_nice)
2624 else:
2625 pe_str = "[%s] (%s)" % (label,
2626 instance_type_nice)
2627 return pe_str
2628
2631 """ Representation """
2632
2633 if not id:
2634 return current.messages.NONE
2635
2636 db = current.db
2637 s3db = current.s3db
2638 cache = current.cache
2639
2640 table = s3db.pr_person
2641
2642 def _represent(id):
2643 if isinstance(id, Row):
2644 person = id
2645 id = person.id
2646 else:
2647 person = db(table.id == id).select(table.first_name,
2648 table.middle_name,
2649 table.last_name,
2650 limitby=(0, 1)).first()
2651 if person:
2652 return s3_fullname(person)
2653 else:
2654 return None
2655
2656 name = cache.ram("pr_person_%s" % id,
2657 lambda: _represent(id), time_expire=10)
2658 return name
2659
2674
2677 """
2678 Person Registry resource headers
2679 - used in PR, HRM, DVI, MPR, MSG, VOL
2680 """
2681
2682 T = current.T
2683 db = current.db
2684 s3db = current.s3db
2685 gis = current.gis
2686 s3 = current.response.s3
2687
2688 tablename, record = s3_rheader_resource(r)
2689
2690 if r.representation == "html":
2691 rheader_tabs = s3_rheader_tabs(r, tabs)
2692
2693 if tablename == "pr_person":
2694 person = record
2695 if person:
2696 s3 = current.response.s3
2697 ptable = r.table
2698 itable = s3db.pr_image
2699 query = (itable.pe_id == record.pe_id) & \
2700 (itable.profile == True)
2701 image = db(query).select(itable.image,
2702 limitby=(0, 1)).first()
2703 if image:
2704 image = TD(itable.image.represent(image.image),
2705 _rowspan=3)
2706 else:
2707 image = ""
2708 rheader = DIV(TABLE(
2709 TR(TH("%s: " % T("Name")),
2710 s3_fullname(person),
2711 TH("%s: " % T("ID Tag Number")),
2712 "%(pe_label)s" % person,
2713 image),
2714 TR(TH("%s: " % T("Date of Birth")),
2715 "%s" % (person.date_of_birth or T("unknown")),
2716 TH("%s: " % T("Gender")),
2717 "%s" % s3.pr_gender_opts.get(person.gender, T("unknown"))),
2718
2719 TR(TH("%s: " % T("Nationality")),
2720 "%s" % (gis.get_country(person.nationality, key_type="code") or T("unknown")),
2721 TH("%s: " % T("Age Group")),
2722 "%s" % s3.pr_age_group_opts.get(person.age_group, T("unknown"))),
2723
2724 ), rheader_tabs)
2725 return rheader
2726
2727 elif tablename == "pr_group":
2728 group = record
2729 if group:
2730 table = s3db.pr_group_membership
2731 query = (table.group_id == record.id) & \
2732 (table.group_head == True)
2733 leader = db(query).select(table.person_id,
2734 limitby=(0, 1)).first()
2735 if leader:
2736 leader = s3_fullname(leader.person_id)
2737 else:
2738 leader = ""
2739 rheader = DIV(TABLE(
2740 TR(TH("%s: " % T("Name")),
2741 group.name,
2742 TH("%s: " % T("Leader")) if leader else "",
2743 leader),
2744 TR(TH("%s: " % T("Description")),
2745 group.description or "",
2746 TH(""),
2747 "")
2748 ), rheader_tabs)
2749 return rheader
2750
2751 return None
2752
2901
2904 """
2905 Custom Method to provide the auth_user profile as a Tab of the Person
2906 @ToDo: Complete this (currently unfinished)
2907 """
2908
2909 if r.http != "GET":
2910 r.error(405, current.manager.ERROR.BAD_METHOD)
2911
2912 T = current.T
2913 db = current.db
2914 s3db = current.s3db
2915
2916 person = r.record
2917
2918
2919 ltable = s3db.pr_person_user
2920 query = (ltable.pe_id == person.pe_id)
2921 profile = db(query).select(limitby=(0, 1)).first()
2922
2923 form = current.auth()
2924
2925
2926 response.view = "pr/profile.html"
2927
2928
2929 rheader = s3db.hrm_rheader(r)
2930
2931 return dict(
2932 title = T("Profile"),
2933 rheader = rheader,
2934 form = form,
2935 )
2936
2942 """
2943 Update OU affiliations related to this record
2944
2945 @param table: the table
2946 @param record: the record
2947 """
2948
2949 if hasattr(table, "_tablename"):
2950 rtype = table._tablename
2951 else:
2952 rtype = table
2953
2954 db = current.db
2955 s3db = current.s3db
2956
2957 if rtype == "hrm_human_resource":
2958
2959
2960 htable = s3db.hrm_human_resource
2961 if not isinstance(record, Row):
2962 record = db(htable.id == record).select(htable.ALL,
2963 limitby=(0, 1)).first()
2964 if not record:
2965 return
2966
2967
2968 update = pr_human_resource_update_affiliations
2969 person_id = None
2970 if record.deleted_fk:
2971 try:
2972 person_id = json.loads(record.deleted_fk)["person_id"]
2973 except:
2974 pass
2975 if person_id:
2976 update(person_id)
2977 if person_id != record.person_id:
2978 person_id = record.person_id
2979 if person_id:
2980 update(person_id)
2981
2982 elif rtype == "pr_group_membership":
2983
2984 mtable = s3db.pr_group_membership
2985 if not isinstance(record, Row):
2986 record = db(mtable.id == record).select(mtable.ALL,
2987 limitby=(0, 1)).first()
2988 if not record:
2989 return
2990 pr_group_update_affiliations(record)
2991
2992 elif rtype == "org_organisation_branch":
2993
2994 ltable = s3db.org_organisation_branch
2995 if not isinstance(record, Row):
2996 record = db(ltable.id == record).select(ltable.ALL,
2997 limitby=(0, 1)).first()
2998 if not record:
2999 return
3000 pr_organisation_update_affiliations(record)
3001
3002 elif rtype == "org_site":
3003
3004 pr_site_update_affiliations(record)
3005
3006 return
3007
3010 """
3011 Update affiliations for a branch organisation
3012
3013 @param record: the org_organisation_branch record
3014 """
3015
3016 db = current.db
3017 s3db = current.s3db
3018
3019 if record.deleted and record.deleted_fk:
3020 try:
3021 fk = json.loads(record.deleted_fk)
3022 branch_id = fk["branch_id"]
3023 except:
3024 return
3025 else:
3026 branch_id = record.branch_id
3027
3028 BRANCHES = "Branches"
3029
3030 otable = s3db.org_organisation
3031 btable = otable.with_alias("branch")
3032 ltable = s3db.org_organisation_branch
3033 rtable = s3db.pr_role
3034 atable = s3db.pr_affiliation
3035 etable = s3db.pr_pentity
3036
3037 o = otable._tablename
3038 b = btable._tablename
3039 r = rtable._tablename
3040
3041
3042 query = (ltable.branch_id == branch_id) & \
3043 (ltable.deleted != True)
3044 left = [otable.on(ltable.organisation_id == otable.id),
3045 btable.on(ltable.branch_id == btable.id)]
3046 rows = db(query).select(otable.pe_id, btable.pe_id, left=left)
3047 current_memberships = [(row[o].pe_id, row[b].pe_id) for row in rows]
3048
3049
3050 query = (rtable.deleted != True) & \
3051 (rtable.role == BRANCHES) & \
3052 (rtable.pe_id == etable.pe_id) & \
3053 (etable.instance_type == o) & \
3054 (atable.deleted != True) & \
3055 (atable.role_id == rtable.id) & \
3056 (atable.pe_id == btable.pe_id) & \
3057 (btable.id == branch_id)
3058 rows = db(query).select(rtable.pe_id, btable.pe_id)
3059 current_affiliations = [(row[r].pe_id, row[b].pe_id) for row in rows]
3060
3061
3062 for a in current_affiliations:
3063 org, branch = a
3064 if a not in current_memberships:
3065 pr_remove_affiliation(org, branch, role=BRANCHES)
3066 else:
3067 current_memberships.remove(a)
3068
3069 for m in current_memberships:
3070 org, branch = m
3071 pr_add_affiliation(org, branch, role=BRANCHES, role_type=OU)
3072 return
3073
3076 """
3077 Update affiliations for group memberships, currently this makes
3078 all members of a group organisational units of the group.
3079
3080 @param record: the pr_membership record
3081 """
3082
3083 db = current.db
3084 s3db = current.s3db
3085
3086 MEMBERS = "Members"
3087
3088 if record.deleted and record.deleted_fk:
3089 try:
3090 fk = json.loads(record.deleted_fk)
3091 person_id = fk["person_id"]
3092 except:
3093 return
3094 else:
3095 person_id = record.person_id
3096
3097 ptable = s3db.pr_person
3098 gtable = s3db.pr_group
3099 mtable = s3db.pr_group_membership
3100 rtable = s3db.pr_role
3101 atable = s3db.pr_affiliation
3102 etable = s3db.pr_pentity
3103
3104 g = gtable._tablename
3105 r = rtable._tablename
3106 p = ptable._tablename
3107
3108
3109 query = (mtable.person_id == person_id) & \
3110 (mtable.deleted != True)
3111 left = [ptable.on(mtable.person_id == ptable.id),
3112 gtable.on(mtable.group_id == gtable.id)]
3113 rows = db(query).select(ptable.pe_id, gtable.pe_id, left=left)
3114 current_memberships = [(row[g].pe_id, row[p].pe_id) for row in rows]
3115
3116
3117 query = (rtable.deleted != True) & \
3118 (rtable.role == MEMBERS) & \
3119 (rtable.pe_id == etable.pe_id) & \
3120 (etable.instance_type == g) & \
3121 (atable.deleted != True) & \
3122 (atable.role_id == rtable.id) & \
3123 (atable.pe_id == ptable.pe_id) & \
3124 (ptable.id == person_id)
3125 rows = db(query).select(ptable.pe_id, rtable.pe_id)
3126 current_affiliations = [(row[r].pe_id, row[p].pe_id) for row in rows]
3127
3128
3129 for a in current_affiliations:
3130 group, person = a
3131 if a not in current_memberships:
3132 pr_remove_affiliation(group, person, role=MEMBERS)
3133 else:
3134 current_memberships.remove(a)
3135
3136 for m in current_memberships:
3137 group, person = m
3138 pr_add_affiliation(group, person, role=MEMBERS, role_type=OU)
3139 return
3140
3143 """
3144 Update the affiliations of an org_site instance
3145
3146 @param record: the org_site instance record
3147 """
3148
3149 db = current.db
3150 s3db = current.s3db
3151
3152 SITES = "Sites"
3153
3154 otable = s3db.org_organisation
3155 stable = s3db.org_site
3156 rtable = s3db.pr_role
3157 ptable = s3db.pr_pentity
3158 atable = s3db.pr_affiliation
3159
3160 o_pe_id = None
3161 s_pe_id = record.pe_id
3162
3163 organisation_id = record.organisation_id
3164 if organisation_id:
3165 org = db(otable.id == organisation_id).select(otable.pe_id,
3166 limitby=(0, 1)).first()
3167 if org:
3168 o_pe_id = org.pe_id
3169 if s_pe_id:
3170 query = (atable.deleted != True) & \
3171 (atable.pe_id == s_pe_id) & \
3172 (rtable.deleted != True) & \
3173 (rtable.id == atable.role_id) & \
3174 (rtable.role == SITES) & \
3175 (ptable.pe_id == rtable.pe_id) & \
3176 (ptable.instance_type == str(otable))
3177 rows = db(query).select(rtable.pe_id)
3178 seen = False
3179 for row in rows:
3180 if o_pe_id == None or o_pe_id != row.pe_id:
3181 pr_remove_affiliation(row.pe_id, s_pe_id, role=SITES)
3182 elif o_pe_id == row.pe_id:
3183 seen = True
3184 if o_pe_id and not seen:
3185 pr_add_affiliation(o_pe_id, s_pe_id, role=SITES, role_type=OU)
3186 return
3187
3190 """
3191 Update all affiliations related to the HR records of a person
3192
3193 @param person_id: the person record ID
3194 """
3195
3196 db = current.db
3197 s3db = current.s3db
3198
3199 STAFF = "Staff"
3200 VOLUNTEER = "Volunteer"
3201
3202 update = pr_human_resource_update_affiliations
3203
3204 etable = s3db.pr_pentity
3205 ptable = s3db.pr_person
3206 rtable = s3db.pr_role
3207 atable = s3db.pr_affiliation
3208 htable = s3db.hrm_human_resource
3209 otable = s3db.org_organisation
3210 stable = s3db.org_site
3211
3212 h = htable._tablename
3213 s = stable._tablename
3214 o = otable._tablename
3215 r = rtable._tablename
3216 e = etable._tablename
3217
3218
3219 pe_id = s3db.pr_get_pe_id("pr_person", person_id)
3220
3221
3222 query = (htable.person_id == person_id) & \
3223 (htable.status == 1) & \
3224 (htable.type.belongs((1,2))) & \
3225 (htable.deleted != True)
3226 left = [otable.on(htable.organisation_id == otable.id),
3227 stable.on(htable.site_id == stable.site_id)]
3228 rows = db(query).select(htable.site_id,
3229 htable.type,
3230 otable.pe_id,
3231 stable.uuid,
3232 stable.instance_type,
3233 left=left)
3234
3235
3236 masters = {STAFF:[], VOLUNTEER:[]}
3237 sites = Storage()
3238 for row in rows:
3239 if row[h].type == 1:
3240 role = STAFF
3241 site_id = row[h].site_id
3242 site_pe_id = None
3243 if site_id and site_id not in sites:
3244 itable = s3db.table(row[s].instance_type, None)
3245 if itable and "pe_id" in itable.fields:
3246 q = itable.site_id == site_id
3247 site = db(q).select(itable.pe_id,
3248 limitby=(0, 1)).first()
3249 if site:
3250 site_pe_id = sites[site_id] = site.pe_id
3251 else:
3252 site_pe_id = sites[site_id]
3253 if site_pe_id and site_pe_id not in masters[role]:
3254 masters[role].append(site_pe_id)
3255 elif row[h].type == 2:
3256 role = VOLUNTEER
3257 else:
3258 continue
3259 org_pe_id = row[o].pe_id
3260 if org_pe_id and org_pe_id not in masters[role]:
3261 masters[role].append(org_pe_id)
3262
3263
3264 query = (ptable.id == person_id) & \
3265 (atable.deleted != True) & \
3266 (atable.pe_id == ptable.pe_id) & \
3267 (rtable.deleted != True) & \
3268 (rtable.id == atable.role_id) & \
3269 (rtable.role.belongs((STAFF, VOLUNTEER))) & \
3270 (etable.pe_id == rtable.pe_id) & \
3271 (etable.instance_type.belongs((o, s)))
3272 affiliations = db(query).select(rtable.id,
3273 rtable.pe_id,
3274 rtable.role,
3275 etable.instance_type)
3276
3277
3278 for a in affiliations:
3279 pe = a[r].pe_id
3280 role = a[r].role
3281 if role in masters:
3282 if pe not in masters[role]:
3283 pr_remove_affiliation(pe, pe_id, role=role)
3284 else:
3285 masters[role].remove(pe)
3286
3287
3288 for role in masters:
3289 if role == VOLUNTEER:
3290 role_type = OTHER_ROLE
3291 else:
3292 role_type = OU
3293 for m in masters[role]:
3294 pr_add_affiliation(m, pe_id, role=role, role_type=role_type)
3295
3296 return
3297
3300 """
3301 Add a new affiliation record
3302
3303 @param master: the master entity, either as PE-ID or as tuple
3304 (instance_type, instance_id)
3305 @param affiliate: the affiliated entity, either as PE-ID or as tuple
3306 (instance_type, instance_id)
3307 @param role: the role to add the affiliate to (will be created if it
3308 doesn't yet exist)
3309 @param role_type: the type of the role, defaults to OU
3310 """
3311
3312 db = current.db
3313 s3db = current.s3db
3314
3315 if not role:
3316 return
3317
3318 master_pe = pr_get_pe_id(master)
3319 affiliate_pe = pr_get_pe_id(affiliate)
3320
3321 role_id = None
3322 if master_pe and affiliate_pe:
3323 rtable = s3db.pr_role
3324 query = (rtable.pe_id == master_pe) & \
3325 (rtable.role == role) & \
3326 (rtable.deleted != True)
3327 row = db(query).select(limitby=(0, 1)).first()
3328 if not row:
3329 data = {"pe_id": master_pe,
3330 "role": role,
3331 "role_type": role_type}
3332 role_id = rtable.insert(**data)
3333 else:
3334 role_id = row.id
3335 if role_id:
3336 pr_add_to_role(role_id, affiliate_pe)
3337 return role_id
3338
3341 """
3342 Remove affiliation records
3343
3344 @param master: the master entity, either as PE-ID or as tuple
3345 (instance_type, instance_id), if this is None, then
3346 all affiliations with all entities will be removed
3347 @param affiliate: the affiliated entity, either as PE-ID or as tuple
3348 (instance_type, instance_id)
3349 @param affiliate: the affiliated PE, either as pe_id or as tuple
3350 (instance_type, instance_id)
3351 @param role: name of the role to remove the affiliate from, if None,
3352 the affiliate will be removed from all roles
3353 """
3354
3355 db = current.db
3356 s3db = current.s3db
3357
3358 master_pe = pr_get_pe_id(master)
3359 affiliate_pe = pr_get_pe_id(affiliate)
3360
3361 if affiliate_pe:
3362 atable = s3db.pr_affiliation
3363 rtable = s3db.pr_role
3364 query = (atable.pe_id == affiliate_pe) & \
3365 (atable.role_id == rtable.id)
3366 if master_pe:
3367 query &= (rtable.pe_id == master_pe)
3368 if role:
3369 query &= (rtable.role == role)
3370 rows = db(query).select(rtable.id)
3371 for row in rows:
3372 pr_remove_from_role(row.id, affiliate_pe)
3373 return
3374
3375
3376
3377
3378
3379 -def pr_get_pe_id(entity, record_id=None):
3380 """
3381 Get the PE-ID of an instance record
3382
3383 @param entity: the entity, either a tablename, a tuple (tablename,
3384 record_id), a Row of the instance type, or a PE-ID
3385 @param record_id: the record ID, if entity is a tablename
3386
3387 @returns: the PE-ID
3388 """
3389
3390 db = current.db
3391 s3db = current.s3db
3392 if record_id is not None:
3393 table, _id = entity, record_id
3394 elif isinstance(entity, (tuple, list)):
3395 table, _id = entity
3396 elif isinstance(entity, Row):
3397 if "pe_id" in entity:
3398 return entity["pe_id"]
3399 else:
3400 for f in entity.values():
3401 if isinstance(f, Row) and "pe_id" in f:
3402 return f["pe_id"]
3403 return None
3404 elif isinstance(entity, (long, int)) or \
3405 isinstance(entity, basestring) and entity.isdigit():
3406 return entity
3407 else:
3408 return None
3409 if not hasattr(table, "_tablename"):
3410 table = s3db.table(table, None)
3411 record = None
3412 if table:
3413 if "pe_id" in table.fields and _id:
3414 record = db(table._id==_id).select(table.pe_id,
3415 limitby=(0, 1)).first()
3416 elif _id:
3417 key = table._id.name
3418 if key == "pe_id":
3419 return _id
3420 if key != "id" and "instance_type" in table.fields:
3421 s = db(table._id==_id).select(table.instance_type,
3422 limitby=(0, 1)).first()
3423 else:
3424 return None
3425 if not s:
3426 return None
3427 table = s3db.table(s.instance_type, None)
3428 if table and "pe_id" in table.fields:
3429 record = db(table[key] == _id).select(table.pe_id,
3430 limitby=(0, 1)).first()
3431 else:
3432 return None
3433 if record:
3434 return record.pe_id
3435 return None
3436
3437
3438
3439
3440
3441 -def pr_define_role(pe_id,
3442 role=None,
3443 role_type=None,
3444 entity_type=None,
3445 sub_type=None):
3446 """
3447 Back-end method to define a new affiliates-role for a person entity
3448
3449 @param pe_id: the person entity ID
3450 @param role: the role name
3451 @param role_type: the role type (from pr_role_types), default 9
3452 @param entity_type: limit selection in CRUD forms to this entity type
3453 @param sub_type: limit selection in CRUD forms to this entity sub-type
3454
3455 @returns: the role ID
3456 """
3457
3458 db = current.db
3459 s3db = current.s3db
3460
3461 if not pe_id:
3462 return
3463 if role_type not in s3db.pr_role_types:
3464 role_type = 9
3465
3466 data = {"pe_id": pe_id,
3467 "role": role,
3468 "role_type": role_type,
3469 "entity_type": entity_type,
3470 "sub_type": sub_type}
3471
3472 rtable = s3db.pr_role
3473 if role:
3474 query = (rtable.pe_id == pe_id) & \
3475 (rtable.role == role)
3476 duplicate = db(query).select(rtable.id,
3477 rtable.role_type,
3478 limitby=(0, 1)).first()
3479 else:
3480 duplicate = None
3481 if duplicate:
3482 if duplicate.role_type != role_type:
3483
3484 if str(role_type) != str(OU):
3485 data["path"] = None
3486 s3db.pr_role_rebuild_path(duplicate.id, clear=True)
3487 duplicate.update_record(**data)
3488 record_id = duplicate.id
3489 else:
3490 record_id = rtable.insert(**data)
3491 return record_id
3492
3495 """
3496 Back-end method to delete a role
3497
3498 @param role_id: the role ID
3499 """
3500
3501 manager = current.manager
3502 resource = manager.define_resource("pr", "role", id=role_id)
3503 return resource.delete()
3504
3507 """
3508 Back-end method to add a person entity to a role.
3509
3510 @param role_id: the role ID
3511 @param pe_id: the person entity ID
3512
3513 @todo: update descendant paths only if the role is a OU role
3514 """
3515
3516 db = current.db
3517 s3db = current.s3db
3518
3519 atable = s3db.pr_affiliation
3520
3521
3522 query = (atable.role_id == role_id) & (atable.pe_id == pe_id)
3523 affiliation = db(query).select(limitby=(0, 1)).first()
3524 if affiliation is None:
3525
3526 atable.insert(role_id=role_id, pe_id=pe_id)
3527
3528 pr_rebuild_path(pe_id, clear=True)
3529 return
3530
3533 """
3534 Back-end method to remove a person entity from a role.
3535
3536 @param role_id: the role ID
3537 @param pe_id: the person entity ID
3538
3539 @todo: update descendant paths only if the role is a OU role
3540 """
3541
3542 db = current.db
3543 s3db = current.s3db
3544
3545 atable = s3db.pr_affiliation
3546
3547 query = (atable.role_id == role_id) & (atable.pe_id == pe_id)
3548 affiliation = db(query).select(limitby=(0, 1)).first()
3549 if affiliation is not None:
3550
3551 deleted_fk = {"role_id": role_id, "pe_id": pe_id}
3552 data = {"deleted": True,
3553 "role_id": None,
3554 "pe_id": None,
3555 "deleted_fk": json.dumps(deleted_fk)}
3556 affiliation.update_record(**data)
3557
3558 pr_rebuild_path(pe_id, clear=True)
3559 return
3560
3566 """
3567 Get the ancestor paths of the ancestor OUs this person entity
3568 is affiliated with, sorted by roles.
3569
3570 Used by gis.set_config()
3571
3572 @param pe_id: the person entity ID
3573 @param roles: list of roles to limit the search
3574 @param role_types: list of role types to limit the search
3575
3576 @returns: a Storage() of S3MultiPaths with the role names as keys
3577
3578 @note: role_types is ignored if roles gets specified
3579 """
3580
3581 db = current.db
3582 s3db = current.s3db
3583
3584 atable = s3db.pr_affiliation
3585 rtable = s3db.pr_role
3586
3587 query = (atable.deleted != True) & \
3588 (atable.role_id == rtable.id) & \
3589 (atable.pe_id == pe_id) & \
3590 (rtable.deleted != True)
3591
3592 if roles is not None:
3593
3594 if not isinstance(roles, (list, tuple)):
3595 roles = [roles]
3596 query &= (rtable.role.belongs(roles))
3597 elif role_types is not None:
3598
3599 if not isinstance(role_types, (list, tuple)):
3600 role_types = [role_types]
3601 query &= (rtable.role_type.belongs(role_types))
3602
3603 rows = db(query).select(rtable.role, rtable.path, rtable.pe_id)
3604
3605 role_paths = Storage()
3606 for role in rows:
3607 name = role.role
3608 if name in role_paths:
3609 multipath = role_paths[name]
3610 multipath.append([role.pe_id])
3611 else:
3612 multipath = S3MultiPath([role.pe_id])
3613 path = pr_get_path(role.pe_id)
3614 multipath.extend(role.pe_id, path, cut=pe_id)
3615 role_paths[name] = multipath.clean()
3616
3617 return role_paths
3618
3619
3620 -def pr_get_role_branches(pe_id,
3621 roles=None,
3622 role_types=None,
3623 entity_type=None):
3624 """
3625 Get all descendants of the immediate ancestors of the entity
3626 within these roles/role types
3627
3628 @param pe_id: the person entity ID
3629 @param roles: list of roles to limit the search
3630 @param role_types: list of role types to limit the search
3631 @param entity_type: limit the result to this entity type
3632
3633 @returns: a list of PE-IDs
3634
3635 @note: role_types is ignored if roles gets specified
3636 """
3637
3638 db = current.db
3639 s3db = current.s3db
3640
3641 rtable = s3db.pr_role
3642 atable = s3db.pr_affiliation
3643 etable = s3db.pr_pentity
3644
3645 query = (atable.deleted != True) & \
3646 (atable.pe_id == pe_id) & \
3647 (atable.role_id == rtable.id) & \
3648 (rtable.pe_id == etable.pe_id)
3649
3650 if roles is not None:
3651
3652 if not isinstance(roles, (list, tuple)):
3653 roles = [roles]
3654 query &= (rtable.role.belongs(roles))
3655
3656 elif role_types is not None:
3657
3658 if not isinstance(role_types, (list, tuple)):
3659 role_types = [role_types]
3660 query &= (rtable.role_type.belongs(role_types))
3661
3662 rows = db(query).select(rtable.pe_id, etable.instance_type)
3663
3664
3665 rtn = rtable._tablename
3666 etn = etable._tablename
3667
3668
3669 nodes = [r[rtn].pe_id for r in rows]
3670
3671
3672 result = [r[rtn].pe_id for r in rows
3673 if entity_type is None or r[etn].instance_type == entity_type]
3674
3675
3676 branches = pr_get_descendants(nodes, entity_type=entity_type)
3677
3678 return result + branches
3679
3682 """
3683 Get all ancestor paths of a person entity
3684
3685 @param pe_id: the person entity ID
3686
3687 @returns: an S3MultiPath instance
3688 """
3689
3690 db = current.db
3691 s3db = current.s3db
3692
3693 atable = s3db.pr_affiliation
3694 rtable = s3db.pr_role
3695
3696 query = (atable.deleted != True) & \
3697 (atable.role_id == rtable.id) & \
3698 (atable.pe_id == pe_id) & \
3699 (rtable.deleted != True) & \
3700 (rtable.role_type == OU)
3701 roles = db(query).select(rtable.ALL)
3702 multipath = S3MultiPath()
3703 append = multipath.append
3704 for role in roles:
3705 path = S3MultiPath([role.pe_id])
3706 if role.path is None:
3707 ppath = pr_role_rebuild_path(role)
3708 else:
3709 ppath = S3MultiPath(role.path)
3710 path.extend(role.pe_id, ppath, cut=pe_id)
3711 for p in path.paths:
3712 append(p)
3713 return multipath.clean()
3714
3717 """
3718 Find all ancestor entities of a person entity in the OU hierarchy
3719 (performs a path lookup where paths are available, otherwise rebuilds
3720 paths).
3721
3722 @param pe_id: the person entity ID
3723 @param roles: list of roles to limit the search
3724
3725 @returns: a list of PE-IDs
3726 """
3727
3728 db = current.db
3729 s3db = current.s3db
3730
3731 atable = s3db.pr_affiliation
3732 rtable = s3db.pr_role
3733
3734 query = (atable.deleted != True) & \
3735 (atable.role_id == rtable.id) & \
3736 (atable.pe_id == pe_id) & \
3737 (rtable.deleted != True) & \
3738 (rtable.role_type == OU)
3739
3740 roles = db(query).select(rtable.ALL)
3741
3742 paths = []
3743 append = paths.append
3744 for role in roles:
3745 path = S3MultiPath([role.pe_id])
3746 if role.path is None:
3747 ppath = pr_role_rebuild_path(role)
3748 else:
3749 ppath = S3MultiPath(role.path)
3750 path.extend(role.pe_id, ppath, cut=pe_id)
3751 append(path)
3752 ancestors = S3MultiPath.all_nodes(paths)
3753
3754 return ancestors
3755
3758 """
3759 Get the default realm (=the immediate OU ancestors) of an entity
3760
3761 @param entity: the entity (pe_id)
3762 """
3763
3764 db = current.db
3765 s3db = current.s3db
3766
3767 atable = s3db.pr_affiliation
3768 rtable = s3db.pr_role
3769
3770 if not entity:
3771 return []
3772 query = (atable.deleted != True) & \
3773 (atable.role_id == rtable.id) & \
3774 (atable.pe_id == entity) & \
3775 (rtable.deleted != True) & \
3776 (rtable.role_type == OU)
3777 rows = db(query).select(rtable.pe_id)
3778 realm = [row.pe_id for row in rows]
3779 return realm
3780
3783 """
3784 Find all ancestor entities of the given entities in the
3785 OU hierarchy.
3786
3787 @param pe_id: the person entity ID
3788 @param roles: list of roles to limit the search
3789
3790 @returns: Storage of lists of PE-IDs
3791 """
3792
3793 db = current.db
3794 s3db = current.s3db
3795
3796 atable = s3db.pr_affiliation
3797 rtable = s3db.pr_role
3798
3799 if not entities:
3800 return Storage()
3801
3802 query = (atable.deleted != True) & \
3803 (atable.role_id == rtable.id) & \
3804 (atable.pe_id.belongs(entities)) & \
3805 (rtable.deleted != True) & \
3806 (rtable.role_type == OU)
3807 rows = db(query).select(rtable.ALL, atable.pe_id)
3808
3809 ancestors = Storage([(pe_id, []) for pe_id in entities])
3810 r = rtable._tablename
3811 a = atable._tablename
3812 for row in rows:
3813 pe_id = row[a].pe_id
3814 paths = ancestors[pe_id]
3815 role = row[r]
3816 path = S3MultiPath([role.pe_id])
3817 if role.path is None:
3818 ppath = pr_role_rebuild_path(role)
3819 else:
3820 ppath = S3MultiPath(role.path)
3821 path.extend(role.pe_id, ppath, cut=pe_id)
3822 paths.append(path)
3823 for pe_id in ancestors:
3824 ancestors[pe_id] = S3MultiPath.all_nodes(ancestors[pe_id])
3825 return ancestors
3826
3829
3830 db = current.db
3831 s3db = current.s3db
3832
3833 pe_ids = [i for i in pe_ids if i not in skip]
3834 if not pe_ids:
3835 return []
3836
3837 etable = s3db.pr_pentity
3838 rtable = s3db.pr_role
3839 atable = s3db.pr_affiliation
3840
3841 query = (rtable.deleted != True) & \
3842 (rtable.pe_id.belongs(pe_ids)) & \
3843 (rtable.role_type == OU) & \
3844 (atable.role_id == rtable.id) & \
3845 (atable.deleted != True) & \
3846 (etable.pe_id == atable.pe_id)
3847
3848 rows = db(query).select(rtable.pe_id,
3849 atable.pe_id,
3850 etable.instance_type)
3851 r = rtable._tablename
3852 e = etable._tablename
3853 a = atable._tablename
3854
3855 nodes = []
3856 result = Storage()
3857 for row in rows:
3858 parent = row[r].pe_id
3859 child = row[a].pe_id
3860 if row[e].instance_type != "pr_person":
3861 if parent not in result:
3862 result[parent] = [child]
3863 else:
3864 result[parent].append(child)
3865 if child not in nodes:
3866 nodes.append(child)
3867
3868 skip = skip + pe_ids
3869 descendants = pr_descendants(nodes, skip=skip)
3870
3871 for child in descendants:
3872 for parent in result:
3873 if child in result[parent]:
3874 for node in descendants[child]:
3875 if node not in result[parent]:
3876 result[parent].append(node)
3877 for x in result:
3878 for y in result:
3879 if x in result[y]:
3880 result[y].extend([i for i in result[x] if i not in result[y] and i != y])
3881
3882 return result
3883
3886 """
3887 Find descendant entities of a person entity in the OU hierarchy
3888 (performs a real search, not a path lookup).
3889
3890 @param pe_ids: person entity ID or list of IDs
3891 @param skip: list of person entity IDs to skip during descending
3892
3893 @returns: a list of PE-IDs
3894 """
3895
3896 db = current.db
3897 s3db = current.s3db
3898
3899 etable = s3db.pr_pentity
3900 rtable = s3db.pr_role
3901 atable = s3db.pr_affiliation
3902
3903 en = etable._tablename
3904 an = atable._tablename
3905
3906 if type(pe_ids) is not list:
3907 pe_ids = [pe_ids]
3908 pe_ids = [i for i in pe_ids if i not in skip]
3909 if not pe_ids:
3910 return []
3911 query = (rtable.deleted != True) & \
3912 (rtable.pe_id.belongs(pe_ids)) & \
3913 (~(rtable.pe_id.belongs(skip))) &\
3914 (rtable.role_type == OU) & \
3915 (atable.deleted != True) & \
3916 (atable.role_id == rtable.id) & \
3917 (etable.pe_id == atable.pe_id)
3918 skip = skip + pe_ids
3919 rows = db(query).select(atable.pe_id,
3920 etable.instance_type)
3921 nodes = [(r[an].pe_id, r[en].instance_type) for r in rows]
3922 result = []
3923 append = result.append
3924 for n in nodes:
3925 if n not in result:
3926 append(n)
3927 node_ids = [n[0] for n in result]
3928 descendants = pr_get_descendants(node_ids, skip=skip, ids=False)
3929 for d in descendants:
3930 if d not in result:
3931 append(d)
3932
3933 if ids:
3934 return [n[0]
3935 for n in result
3936 if entity_type is None or n[1] == entity_type]
3937 else:
3938 return result
3939
3945 """
3946 Rebuild the ancestor path of all roles in the OU hierarchy a person
3947 entity defines.
3948
3949 @param pe_id: the person entity ID
3950 @param clear: clear paths in descendant roles (triggers lazy rebuild)
3951 """
3952
3953 db = current.db
3954 s3db = current.s3db
3955
3956 if isinstance(pe_id, Row):
3957 pe_id = row.pe_id
3958
3959 rtable = s3db.pr_role
3960 query = (rtable.deleted != True) & \
3961 (rtable.pe_id == pe_id) & \
3962 (rtable.role_type == OU)
3963 db(query).update(path=None)
3964 roles = db(query).select()
3965 for role in roles:
3966 if role.path is None:
3967 pr_role_rebuild_path(role, clear=clear)
3968 return
3969
3972 """
3973 Rebuild the ancestor path of a role within the OU hierarchy
3974
3975 @param role_id: the role ID
3976 @param skip: list of role IDs to skip during recursion
3977 @param clear: clear paths in descendant roles (triggers lazy rebuild)
3978 """
3979
3980 db = current.db
3981 s3db = current.s3db
3982
3983 rtable = s3db.pr_role
3984 atable = s3db.pr_affiliation
3985
3986 if isinstance(role_id, Row):
3987 role = role_id
3988 role_id = role.id
3989 else:
3990 query = (rtable.deleted != True) & \
3991 (rtable.id == role_id)
3992 role = db(query).select(limitby=(0, 1)).first()
3993 if not role:
3994 return None
3995 pe_id = role.pe_id
3996
3997 if role_id in skip:
3998 return role.path
3999 skip = skip + [role_id]
4000
4001 if role.role_type != OU:
4002 path = None
4003 else:
4004
4005 query = (atable.deleted != True) & \
4006 (atable.pe_id == pe_id) & \
4007 (rtable.deleted != True) & \
4008 (rtable.id == atable.role_id) & \
4009 (rtable.role_type == OU)
4010 parent_roles = db(query).select(rtable.ALL)
4011
4012
4013 path = S3MultiPath()
4014 for prole in parent_roles:
4015 path.append([prole.pe_id])
4016 if prole.path is None:
4017 ppath = pr_role_rebuild_path(prole, skip=skip)
4018 else:
4019 ppath = S3MultiPath(prole.path)
4020 if ppath is not None:
4021 path.extend(prole.pe_id, ppath, cut=pe_id)
4022 db(rtable.id == role_id).update(path=str(path))
4023
4024
4025 if clear:
4026 query = (rtable.deleted != True) & \
4027 (rtable.path.like("%%|%s|%%" % pe_id)) & \
4028 (~(rtable.id.belongs(skip)))
4029 db(query).update(path=None)
4030
4031 return path
4032
4033
4034