1 """GNUmed demographics object.
2
3 This is a patient object intended to let a useful client-side
4 API crystallize from actual use in true XP fashion.
5
6 license: GPL
7 """
8
9 __version__ = "$Revision: 1.106 $"
10 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>, I.Haywood <ihaywood@gnu.org>"
11
12
13 import sys
14 import os
15 import os.path
16 import logging
17
18
19
20 if __name__ == '__main__':
21 sys.path.insert(0, '../../')
22 from Gnumed.pycommon import gmDispatcher
23 from Gnumed.pycommon import gmBusinessDBObject
24 from Gnumed.pycommon import gmPG2
25 from Gnumed.pycommon import gmTools
26
27
28 _log = logging.getLogger('gm.business')
29 _log.info(__version__)
30
31
32
33
34 _SQL_get_tag_image = u"SELECT * FROM ref.v_tag_images_no_data WHERE %s"
35
36 -class cTagImage(gmBusinessDBObject.cBusinessDBObject):
37
38 _cmd_fetch_payload = _SQL_get_tag_image % u"pk_tag_image = %s"
39 _cmds_store_payload = [
40 u"""
41 UPDATE ref.tag_image SET
42 description = gm.nullify_empty_string(%(description)s),
43 filename = gm.nullify_empty_string(%(filename)s)
44 WHERE
45 pk = %(pk_tag_image)s
46 AND
47 xmin = %(xmin_tag_image)s
48 RETURNING
49 pk as pk_tag_image,
50 xmin as xmin_tag_image
51 """
52 ]
53 _updatable_fields = [u'description', u'filename']
54
56
57 if self._payload[self._idx['size']] == 0:
58 return None
59
60 if filename is None:
61 suffix = None
62
63 if self._payload[self._idx['filename']] is not None:
64 name, suffix = os.path.splitext(self._payload[self._idx['filename']])
65 suffix = suffix.strip()
66 if suffix == u'':
67 suffix = None
68
69 filename = gmTools.get_unique_filename (
70 prefix = 'gm-tag_image-',
71 suffix = suffix
72 )
73
74 success = gmPG2.bytea2file (
75 data_query = {
76 'cmd': u'SELECT substring(image from %(start)s for %(size)s) FROM ref.tag_image WHERE pk = %(pk)s',
77 'args': {'pk': self.pk_obj}
78 },
79 filename = filename,
80 chunk_size = aChunkSize,
81 data_size = self._payload[self._idx['size']]
82 )
83
84 if success:
85 return filename
86
87 return None
88
104
106 if order_by is None:
107 order_by = u'true'
108 else:
109 order_by = 'true ORDER BY %s' % order_by
110
111 cmd = _SQL_get_tag_image % order_by
112 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
113 return [ cTagImage(row = {'data': r, 'idx': idx, 'pk_field': 'pk_tag_image'}) for r in rows ]
114
116
117 args = {u'desc': description, u'img': u''}
118 cmd = u"""
119 INSERT INTO ref.tag_image (
120 description,
121 image
122 ) VALUES (
123 %(desc)s,
124 %(img)s::bytea
125 )
126 RETURNING pk
127 """
128 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
129
130 return cTagImage(aPK_obj = rows[0]['pk'])
131
133 args = {'pk': tag_image}
134 cmd = u"""
135 DELETE FROM ref.tag_image
136 WHERE
137 pk = %(pk)s
138 AND
139 NOT EXISTS (
140 SELECT 1
141 FROM dem.identity_tag
142 WHERE fk_tag = %(pk)s
143 LIMIT 1
144 )
145 RETURNING 1
146 """
147 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
148 if len(rows) == 0:
149 return False
150 return True
151
152
153 _SQL_get_identity_tags = u"""SELECT * FROM dem.v_identity_tags WHERE %s"""
154
156
157 _cmd_fetch_payload = _SQL_get_identity_tags % u"pk_identity_tag = %s"
158 _cmds_store_payload = [
159 u"""
160 UPDATE dem.identity_tag SET
161 fk_tag = %(pk_tag_image)s,
162 comment = gm.nullify_empty_string(%(comment)s)
163 WHERE
164 pk = %(pk_identity_tag)s
165 AND
166 xmin = %(xmin_identity_tag)s
167 RETURNING
168 pk as pk_identity_tag,
169 xmin as xmin_identity_tag
170 """
171 ]
172 _updatable_fields = [u'fk_tag', u'comment']
173
175
176 if self._payload[self._idx['image_size']] == 0:
177 return None
178
179 if filename is None:
180 suffix = None
181
182 if self._payload[self._idx['filename']] is not None:
183 name, suffix = os.path.splitext(self._payload[self._idx['filename']])
184 suffix = suffix.strip()
185 if suffix == u'':
186 suffix = None
187
188 filename = gmTools.get_unique_filename (
189 prefix = 'gm-identity_tag-',
190 suffix = suffix
191 )
192
193 exported = gmPG2.bytea2file (
194 data_query = {
195 'cmd': u'SELECT substring(image from %(start)s for %(size)s) FROM ref.tag_image WHERE pk = %(pk)s',
196 'args': {'pk': self._payload[self._idx['pk_tag_image']]}
197 },
198 filename = filename,
199 chunk_size = aChunkSize,
200 data_size = self._payload[self._idx['image_size']]
201 )
202 if exported:
203 return filename
204
205 return None
206
207
209 cmd = u"""
210 select
211 _(name) as l10n_country, name, code, deprecated
212 from dem.country
213 order by l10n_country"""
214 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
215 return rows
216
218 cmd = u"""
219 SELECT code_country, l10n_country FROM dem.v_state WHERE l10n_state = %(region)s
220 union
221 SELECT code_country, l10n_country FROM dem.v_state WHERE state = %(region)s
222 """
223 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'region': region}}])
224 return rows
225
227
228 args = {'prov': province}
229
230 queries = []
231 if delete_urbs:
232 queries.append ({
233 'cmd': u"""
234 delete from dem.urb du
235 where
236 du.id_state = %(prov)s
237 and
238 not exists (select 1 from dem.street ds where ds.id_urb = du.id)""",
239 'args': args
240 })
241
242 queries.append ({
243 'cmd': u"""
244 delete from dem.state ds
245 where
246 ds.id = %(prov)s
247 and
248 not exists (select 1 from dem.urb du where du.id_state = ds.id)""",
249 'args': args
250 })
251
252 gmPG2.run_rw_queries(queries = queries)
253
254 return True
255
257
258 args = {'code': code, 'country': country, 'name': name}
259
260 cmd = u"""SELECT EXISTS (SELECT 1 FROM dem.state WHERE name = %(name)s)"""
261 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
262
263 if rows[0][0]:
264 return
265
266 cmd = u"""
267 INSERT INTO dem.state (
268 code, country, name
269 ) VALUES (
270 %(code)s, %(country)s, %(name)s
271 )"""
272 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
273
275 cmd = u"""
276 select
277 l10n_state, l10n_country, state, code_state, code_country, pk_state, country_deprecated
278 from dem.v_state
279 order by l10n_country, l10n_state"""
280 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
281 return rows
282
283
284
285 -class cAddress(gmBusinessDBObject.cBusinessDBObject):
286 """A class representing an address as an entity in itself.
287
288 We consider addresses to be self-complete "labels" for locations.
289 It does not depend on any people potentially living there. Thus
290 an address can get attached to as many people as we want to
291 signify that that is their place of residence/work/...
292
293 This class acts on the address as an entity. Therefore it can
294 modify the address fields. Think carefully about *modifying*
295 addresses attached to people, though. Most times when you think
296 person.modify_address() what you *really* want is as sequence of
297 person.unlink_address(old) and person.link_address(new).
298
299 Modifying an address may or may not be the proper thing to do as
300 it will transparently modify the address for *all* the people to
301 whom it is attached. In many cases you will want to create a *new*
302 address and link it to a person instead of the old address.
303 """
304 _cmd_fetch_payload = u"select * from dem.v_address where pk_address=%s"
305 _cmds_store_payload = [
306 u"""UPDATE dem.address SET
307 aux_street = %(notes_street)s,
308 subunit = %(subunit)s,
309 addendum = %(notes_subunit)s,
310 lat_lon = %(lat_lon_street)s
311 WHERE id = %(pk_address)s AND xmin = %(xmin_address)s""",
312 u"select xmin as xmin_address from dem.address where id=%(pk_address)s"
313 ]
314 _updatable_fields = ['notes_street', 'subunit', 'notes_subunit', 'lat_lon_address']
315
316 -def address_exists(country=None, state=None, urb=None, suburb=None, postcode=None, street=None, number=None, subunit=None, notes_street=None, notes_subunit=None):
317
318 where_parts = [u"""
319 code_country = %(country)s and
320 code_state = %(state)s and
321 urb = %(urb)s and
322 postcode = %(postcode)s and
323 street = %(street)s and
324 number = %(number)s"""
325 ]
326
327 if suburb is None:
328 where_parts.append(u"suburb is %(suburb)s")
329 else:
330 where_parts.append(u"suburb = %(suburb)s")
331
332 if notes_street is None:
333 where_parts.append(u"notes_street is %(notes_street)s")
334 else:
335 where_parts.append(u"notes_street = %(notes_street)s")
336
337 if subunit is None:
338 where_parts.append(u"subunit is %(subunit)s")
339 else:
340 where_parts.append(u"subunit = %(subunit)s")
341
342 if notes_subunit is None:
343 where_parts.append(u"notes_subunit is %(notes_subunit)s")
344 else:
345 where_parts.append(u"notes_subunit = %(notes_subunit)s")
346
347 cmd = u"select pk_address from dem.v_address where %s" % u" and ".join(where_parts)
348 data = {
349 'country': country,
350 'state': state,
351 'urb': urb,
352 'suburb': suburb,
353 'postcode': postcode,
354 'street': street,
355 'notes_street': notes_street,
356 'number': number,
357 'subunit': subunit,
358 'notes_subunit': notes_subunit
359 }
360
361 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': data}])
362
363 if len(rows) == 0:
364 return None
365 return rows[0][0]
366
367 -def create_address(country=None, state=None, urb=None, suburb=None, postcode=None, street=None, number=None, subunit=None):
368
369 if suburb is not None:
370 suburb = gmTools.none_if(suburb.strip(), u'')
371
372 pk_address = address_exists (
373 country = country,
374 state = state,
375 urb = urb,
376 suburb = suburb,
377 postcode = postcode,
378 street = street,
379 number = number,
380 subunit = subunit
381 )
382 if pk_address is not None:
383 return cAddress(aPK_obj=pk_address)
384
385 cmd = u"""
386 select dem.create_address (
387 %(number)s,
388 %(street)s,
389 %(postcode)s,
390 %(urb)s,
391 %(state)s,
392 %(country)s,
393 %(subunit)s
394 )"""
395 args = {
396 'number': number,
397 'street': street,
398 'postcode': postcode,
399 'urb': urb,
400 'state': state,
401 'country': country,
402 'subunit': subunit
403 }
404 queries = [{'cmd': cmd, 'args': args}]
405
406 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
407 adr = cAddress(aPK_obj=rows[0][0])
408
409 if suburb is not None:
410 queries = [{
411
412 'cmd': u"update dem.street set suburb = %(suburb)s where id=%(pk_street)s and suburb is Null",
413 'args': {'suburb': suburb, 'pk_street': adr['pk_street']}
414 }]
415 rows, idx = gmPG2.run_rw_queries(queries = queries)
416
417 return adr
418
420 cmd = u"delete from dem.address where id=%s"
421 rows, idx = gmPG2.run_rw_queries(queries=[{'cmd': cmd, 'args': [address['pk_address']]}])
422 return True
423
425 cmd = u'select id as pk, name, _(name) as l10n_name from dem.address_type'
426 rows, idx = gmPG2.run_rw_queries(queries=[{'cmd': cmd}])
427 return rows
428
430
431 if order_by is None:
432 order_by = u''
433 else:
434 order_by = u'ORDER BY %s' % order_by
435
436 cmd = u"SELECT * FROM dem.v_address %s" % order_by
437 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
438 return [ cAddress(row = {'data': r, 'idx': idx, 'pk_field': u'pk_address'}) for r in rows ]
439
440
442
443 _cmd_fetch_payload = u"SELECT * FROM dem.v_pat_addresses WHERE pk_address = %s"
444 _cmds_store_payload = [
445 u"""UPDATE dem.lnk_person_org_address SET
446 id_type = %(pk_address_type)s
447 WHERE
448 id = %(pk_lnk_person_org_address)s
449 AND
450 xmin = %(xmin_lnk_person_org_address)s
451 RETURNING
452 xmin AS xmin_lnk_person_org_address
453 """
454
455 ]
456 _updatable_fields = ['pk_address_type']
457
460
461
462
464
465 _cmd_fetch_payload = u"select * from dem.v_person_comms where pk_lnk_identity2comm = %s"
466 _cmds_store_payload = [
467 u"""update dem.lnk_identity2comm set
468 fk_address = %(pk_address)s,
469 fk_type = dem.create_comm_type(%(comm_type)s),
470 url = %(url)s,
471 is_confidential = %(is_confidential)s
472 where pk = %(pk_lnk_identity2comm)s and xmin = %(xmin_lnk_identity2comm)s
473 """,
474 u"select xmin as xmin_lnk_identity2comm from dem.lnk_identity2comm where pk = %(pk_lnk_identity2comm)s"
475 ]
476 _updatable_fields = ['pk_address', 'url', 'comm_type', 'is_confidential']
477
478 -def create_comm_channel(comm_medium=None, url=None, is_confidential=False, pk_channel_type=None, pk_identity=None):
479 """Create a communications channel for a patient."""
480
481 if url is None:
482 return None
483
484
485 args = {'pat': pk_identity, 'url': url, 'secret': is_confidential}
486
487 if pk_channel_type is None:
488 args['type'] = comm_medium
489 cmd = u"""insert into dem.lnk_identity2comm (
490 fk_identity,
491 url,
492 fk_type,
493 is_confidential
494 ) values (
495 %(pat)s,
496 %(url)s,
497 dem.create_comm_type(%(type)s),
498 %(secret)s
499 )"""
500 else:
501 args['type'] = pk_channel_type
502 cmd = u"""insert into dem.lnk_identity2comm (
503 fk_identity,
504 url,
505 fk_type,
506 is_confidential
507 ) values (
508 %(pat)s,
509 %(url)s,
510 %(type)s,
511 %(secret)s
512 )"""
513
514 rows, idx = gmPG2.run_rw_queries (
515 queries = [
516 {'cmd': cmd, 'args': args},
517 {'cmd': u"select * from dem.v_person_comms where pk_lnk_identity2comm = currval(pg_get_serial_sequence('dem.lnk_identity2comm', 'pk'))"}
518 ],
519 return_data = True,
520 get_col_idx = True
521 )
522
523 return cCommChannel(row = {'pk_field': 'pk_lnk_identity2comm', 'data': rows[0], 'idx': idx})
524
526 cmd = u"DELETE FROM dem.lnk_identity2comm WHERE pk = %(pk)s AND fk_identity = %(pat)s"
527 args = {'pk': pk, 'pat': pk_patient}
528 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
529
530 __comm_channel_types = None
531
539
540
541
542 -class cOrg (gmBusinessDBObject.cBusinessDBObject):
543 """
544 Organisations
545
546 This is also the common ancestor of cIdentity, self._table is used to
547 hide the difference.
548 The aim is to be able to sanely write code which doesn't care whether
549 its talking to an organisation or an individual"""
550 _table = "org"
551
552 _cmd_fetch_payload = "select *, xmin from dem.org where id=%s"
553 _cmds_lock_rows_for_update = ["select 1 from dem.org where id=%(id)s and xmin=%(xmin)s"]
554 _cmds_store_payload = [
555 """update dem.org set
556 description=%(description)s,
557 id_category=(select id from dem.org_category where description=%(occupation)s)
558 where id=%(id)s""",
559 "select xmin from dem.org where id=%(id)s"
560 ]
561 _updatable_fields = ["description", "occupation"]
562 _service = 'personalia'
563
566
568 if not self.__cache.has_key ('addresses'):
569 self['addresses']
570 if not self.__cache.has_key ('comms'):
571 self['comms']
572 return self.__cache
573
575 """
576 Returns a list of (address dict, cIdentity) tuples
577 """
578 cmd = """select
579 vba.id,
580 vba.number,
581 vba.addendum,
582 vba.street,
583 vba.urb,
584 vba.postcode,
585 at.name,
586 lpoa.id_type,
587 vbp.pk_identity,
588 title,
589 firstnames,
590 lastnames,
591 dob,
592 cob,
593 gender,
594 pupic,
595 pk_marital_status,
596 marital_status,
597 karyotype,
598 xmin_identity,
599 preferred
600 from
601 dem.v_basic_address vba,
602 dem.lnk_person_org_address lpoa,
603 dem.address_type at,
604 dem.v_basic_person vbp
605 where
606 lpoa.id_address = vba.id
607 and lpoa.id_type = at.id
608 and lpoa.id_identity = vbp.pk_identity
609 and lpoa.id_org = %%s
610 """
611
612 rows, idx = gmPG.run_ro_query('personalia', cmd, 1, self.getId ())
613 if rows is None:
614 return []
615 elif len(rows) == 0:
616 return []
617 else:
618 return [({'pk':i[0], 'number':i[1], 'addendum':i[2], 'street':i[3], 'city':i[4], 'postcode':i[5], 'type':i[6], 'id_type':i[7]}, cIdentity (row = {'data':i[8:], 'id':idx[8:], 'pk_field':'id'})) for i in rows]
619
621 """
622 Binds a person to this organisation at this address.
623 person is a cIdentity object
624 address is a dict of {'number', 'street', 'addendum', 'city', 'postcode', 'type'}
625 type is one of the IDs returned by getAddressTypes
626 """
627 cmd = "insert into dem.lnk_person_org_address (id_type, id_address, id_org, id_identity) values (%(type)s, dem.create_address (%(number)s, %(addendum)s, %(street)s, %(city)s, %(postcode)s), %(org_id)s, %(pk_identity)s)"
628 address['pk_identity'] = person['pk_identity']
629 address['org_id'] = self.getId()
630 if not id_addr:
631 return (False, None)
632 return gmPG.run_commit2 ('personalia', [(cmd, [address])])
633
637
639 """
640 Hide the difference between org.id and v_basic_person.pk_identity
641 """
642 return self['id']
643
645 """
646 wrap mx.DateTime brokenness
647 Returns 9-tuple for use with pyhon time functions
648 """
649 return [ int(x) for x in str(mx).split(' ')[0].split('-') ] + [0,0,0, 0,0,0]
650
652 """Gets a dict matching address types to their ID"""
653 row_list = gmPG.run_ro_query('personalia', "select name, id from dem.address_type")
654 if row_list is None:
655 return {}
656 if len(row_list) == 0:
657 return {}
658 return dict (row_list)
659
661 """Gets a dictionary matching marital status types to their internal ID"""
662 row_list = gmPG.run_ro_query('personalia', "select name, pk from dem.marital_status")
663 if row_list is None:
664 return {}
665 if len(row_list) == 0:
666 return {}
667 return dict(row_list)
668
670 """Gets a dictionary of relationship types to internal id"""
671 row_list = gmPG.run_ro_query('personalia', "select description, id from dem.relation_types")
672 if row_list is None:
673 return None
674 if len (row_list) == 0:
675 return None
676 return dict(row_list)
677
678
680 cmd = """
681 select
682 dem.state.name,
683 dem.urb.postcode
684 from
685 dem.urb,
686 dem.state
687 where
688 dem.urb.id = %s and
689 dem.urb.id_state = dem.state.id"""
690 row_list = gmPG.run_ro_query('personalia', cmd, None, id_urb)
691 if not row_list:
692 return None
693 else:
694 return (row_list[0][0], row_list[0][1])
695
697 cmd = """
698 select
699 dem.state.name,
700 coalesce (dem.street.postcode, dem.urb.postcode),
701 dem.urb.name
702 from
703 dem.urb,
704 dem.state,
705 dem.street
706 where
707 dem.street.id = %s and
708 dem.street.id_urb = dem.urb.id and
709 dem.urb.id_state = dem.state.id
710 """
711 row_list = gmPG.run_ro_query('personalia', cmd, None, id_street)
712 if not row_list:
713 return None
714 else:
715 return (row_list[0][0], row_list[0][1], row_list[0][2])
716
718 row_list = gmPG.run_ro_query('personalia', "select name from dem.country where code = %s", None, country_code)
719 if not row_list:
720 return None
721 else:
722 return row_list[0][0]
723
725 row_list = gmPG.run_ro_query ('personalia', """
726 select
727 dem.urb.postcode,
728 dem.state.code,
729 dem.state.name,
730 dem.country.code,
731 dem.country.name
732 from
733 dem.urb,
734 dem.state,
735 dem.country
736 where
737 dem.urb.name = %s and
738 dem.urb.id_state = dem.state.id and
739 dem.state.country = dem.country.code""", None, town)
740 if not row_list:
741 return (None, None, None, None, None)
742 else:
743 return tuple (row_list[0])
744
745
746
748 print "received post_patient_selection notification"
749 print kwargs['kwds']
750
751
752
753
754
755 if __name__ == "__main__":
756
757 if len(sys.argv) < 2:
758 sys.exit()
759
760 if sys.argv[1] != 'test':
761 sys.exit()
762
763 import random
764
766 exists = address_exists (
767 country ='Germany',
768 state ='Sachsen',
769 urb ='Leipzig',
770 suburb ='Sellerhausen',
771 postcode ='04318',
772 street = u'Cunnersdorfer Strasse',
773 number = '11',
774 notes_subunit = '4.Stock rechts'
775 )
776 if exists is None:
777 print "address does not exist"
778 else:
779 print "address exists, primary key:", exists
780
782 address = create_address (
783 country ='DE',
784 state ='SN',
785 urb ='Leipzig',
786 suburb ='Sellerhausen',
787 postcode ='04318',
788 street = u'Cunnersdorfer Strasse',
789 number = '11'
790
791 )
792 print "created existing address"
793 print address
794
795 su = str(random.random())
796
797 address = create_address (
798 country ='DE',
799 state = 'SN',
800 urb ='Leipzig',
801 suburb ='Sellerhausen',
802 postcode ='04318',
803 street = u'Cunnersdorfer Strasse',
804 number = '11',
805
806 subunit = su
807 )
808 print "created new address with subunit", su
809 print address
810 print "deleted address:", delete_address(address)
811
815
817 region = raw_input("Please enter a region: ")
818 print "country for region [%s] is: %s" % (region, get_country_for_region(region = region))
819
821 if delete_tag_image(tag_image = 9999):
822 print "deleted tag 9999"
823 else:
824 print "did not delete tag 9999"
825 if delete_tag_image(tag_image = 1):
826 print "deleted tag 1"
827 else:
828 print "did not delete tag 1"
829
834
835
836
837
838
839
840
841
842
843 test_tag_images()
844
845 sys.exit()
846
847 gmDispatcher.connect(_post_patient_selection, 'post_patient_selection')
848 while 1:
849 pID = raw_input('a patient: ')
850 if pID == '':
851 break
852 try:
853 print pID
854 myPatient = gmPerson.cIdentity (aPK_obj = pID)
855 except:
856 _log.exception('Unable to set up patient with ID [%s]' % pID)
857 print "patient", pID, "can not be set up"
858 continue
859 print "ID ", myPatient.ID
860 print "name ", myPatient['description']
861 print "name ", myPatient['description_gender']
862 print "title ", myPatient['title']
863 print "dob ", myPatient['dob']
864 print "med age ", myPatient['medical_age']
865 for adr in myPatient.get_addresses():
866 print "address ", adr
867 print "--------------------------------------"
868
869