| Total Complexity | 75 |
| Total Lines | 360 |
| Duplicated Lines | 10 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Patient often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | # -*- coding: utf-8 -*- |
||
| 611 | class Patient(Person): |
||
| 612 | implements(IPatient) |
||
| 613 | security = ClassSecurityInfo() |
||
| 614 | displayContentsTab = False |
||
| 615 | schema = schema |
||
| 616 | |||
| 617 | _at_rename_after_creation = True |
||
| 618 | |||
| 619 | def _renameAfterCreation(self, check_auto_id=False): |
||
| 620 | from bika.lims.idserver import renameAfterCreation |
||
| 621 | renameAfterCreation(self) |
||
| 622 | |||
| 623 | def Title(self): |
||
| 624 | """ Return the Fullname as title """ |
||
| 625 | return safe_unicode(self.getFullname()).encode('utf-8') |
||
| 626 | |||
| 627 | security.declarePublic('getPatientID') |
||
| 628 | |||
| 629 | def getPatientID(self): |
||
| 630 | return self.getId() |
||
| 631 | |||
| 632 | security.declarePublic('getSamples') |
||
| 633 | def getSamples(self): |
||
| 634 | """ get all samples taken from this Patient """ |
||
| 635 | l = [] |
||
| 636 | for ar in self.getARs(): |
||
| 637 | sample = ar.getObject().getSample() |
||
| 638 | if sample: |
||
| 639 | l.append(sample) |
||
| 640 | return l |
||
| 641 | |||
| 642 | def getSamplesCancelled(self): |
||
| 643 | """ |
||
| 644 | Cancelled Samples |
||
| 645 | """ |
||
| 646 | workflow = getToolByName(self, 'portal_workflow') |
||
| 647 | l = self.getSamples() |
||
| 648 | return [sample for samples in l if |
||
| 649 | workflow.getInfoFor(analysis, 'review_state') == 'cancelled'] |
||
| 650 | |||
| 651 | View Code Duplication | def getSamplesPublished(self): |
|
| 652 | """ |
||
| 653 | Published Samples |
||
| 654 | """ |
||
| 655 | workflow = getToolByName(self, 'portal_workflow') |
||
| 656 | ars = self.getARs() |
||
| 657 | samples = [] |
||
| 658 | samples_uids = [] |
||
| 659 | for ar in ars: |
||
| 660 | if ar.review_state == 'published': |
||
| 661 | # Getting the object now |
||
| 662 | sample = ar.getObject().getSample() |
||
| 663 | if sample and sample.UID() not in samples_uids: |
||
| 664 | samples.append(sample.UID()) |
||
| 665 | samples_uids.append(sample) |
||
| 666 | return samples |
||
| 667 | |||
| 668 | View Code Duplication | def getSamplesOngoing(self): |
|
| 669 | """ |
||
| 670 | Ongoing on Samples |
||
| 671 | """ |
||
| 672 | workflow = getToolByName(self, 'portal_workflow') |
||
| 673 | ars = self.getARs() |
||
| 674 | states = [ |
||
| 675 | 'verified', 'to_be_sampled', 'scheduled_sampling', 'sampled', |
||
| 676 | 'to_be_preserved', 'sample_due', 'sample_prep', 'sample_received', |
||
| 677 | 'to_be_verified', ] |
||
| 678 | samples = [] |
||
| 679 | samples_uids = [] |
||
| 680 | for ar in ars: |
||
| 681 | if ar.review_state in states: |
||
| 682 | # Getting the object now |
||
| 683 | sample = ar.getObject().getSample() |
||
| 684 | if sample and sample.UID() not in samples_uids: |
||
| 685 | samples.append(sample.UID()) |
||
| 686 | samples_uids.append(sample) |
||
| 687 | return samples |
||
| 688 | |||
| 689 | def getNumberOfSamplesOngoingRatio(self): |
||
| 690 | """ |
||
| 691 | Returns the ratio between NumberOfSamplesOngoing/NumberOfSamples |
||
| 692 | """ |
||
| 693 | result = 0 |
||
| 694 | if self.getNumberOfSamples() > 0: |
||
| 695 | result = self.getNumberOfSamplesOngoing()/self.getNumberOfSamples() |
||
| 696 | return result |
||
| 697 | |||
| 698 | security.declarePublic('getARs') |
||
| 699 | def getARs(self, analysis_state=None): |
||
| 700 | bc = getToolByName(self, 'bika_catalog') |
||
| 701 | ars = bc( |
||
| 702 | portal_type='AnalysisRequest', |
||
| 703 | getPatientUID=self.UID()) |
||
| 704 | return ars |
||
| 705 | |||
| 706 | def get_clients(self): |
||
| 707 | ## Only show clients to which we have Manage AR rights. |
||
| 708 | mtool = getToolByName(self, 'portal_membership') |
||
| 709 | clientfolder = self.clients |
||
| 710 | clients = [] |
||
| 711 | for client in clientfolder.objectValues("Client"): |
||
| 712 | if not mtool.checkPermission(ManageAnalysisRequests, client): |
||
| 713 | continue |
||
| 714 | clients.append([client.UID(), client.Title()]) |
||
| 715 | clients.sort(lambda x, y: cmp(x[1].lower(), y[1].lower())) |
||
| 716 | clients.insert(0, ['', '']) |
||
| 717 | return DisplayList(clients) |
||
| 718 | |||
| 719 | def get_insurancecompanies(self): |
||
| 720 | """ |
||
| 721 | Return all the registered insurance companies. |
||
| 722 | """ |
||
| 723 | bsc = getToolByName(self, 'bika_setup_catalog') |
||
| 724 | # Void selection |
||
| 725 | ret = [('', '')] |
||
| 726 | # Other selections |
||
| 727 | for ic in bsc(portal_type = 'InsuranceCompany', |
||
| 728 | inactive_state = 'active', |
||
| 729 | sort_on = 'sortable_title'): |
||
| 730 | ret.append((ic.UID, ic.Title)) |
||
| 731 | return DisplayList(ret) |
||
| 732 | |||
| 733 | def getPatientIdentifiersStr(self): |
||
| 734 | ids = self.getPatientIdentifiers() |
||
| 735 | idsstr = '' |
||
| 736 | for idx in ids: |
||
| 737 | idsstr += idsstr == '' and idx.get('Identifier', '') or (', ' + idx.get('Identifier', '')) |
||
| 738 | return idsstr |
||
| 739 | #return self.getSendersPatientID()+" "+self.getSendersCaseID()+" "+self.getSendersSpecimenID() |
||
| 740 | |||
| 741 | def getPatientIdentifiersStrHtml(self): |
||
| 742 | ids = self.getPatientIdentifiers() |
||
| 743 | idsstr = '<table cellpadding="0" cellspacing="0" border="0" class="patientsidentifiers" style="text-align:left;width: 100%;"><tr><td>' |
||
| 744 | for idx in ids: |
||
| 745 | idsstr += "<tr><td>" + idx['IdentifierType'] + ':</td><td>' + idx['Identifier'] + "</td></tr>" |
||
| 746 | return "</table>" + idsstr |
||
| 747 | |||
| 748 | def getAgeSplitted(self): |
||
| 749 | |||
| 750 | if (self.getBirthDate()): |
||
| 751 | dob = DT2dt(self.getBirthDate()).replace(tzinfo=None) |
||
| 752 | now = datetime.today() |
||
| 753 | |||
| 754 | currentday = now.day |
||
| 755 | currentmonth = now.month |
||
| 756 | currentyear = now.year |
||
| 757 | birthday = dob.day |
||
| 758 | birthmonth = dob.month |
||
| 759 | birthyear = dob.year |
||
| 760 | ageday = currentday - birthday |
||
| 761 | agemonth = 0 |
||
| 762 | ageyear = 0 |
||
| 763 | months31days = [1, 3, 5, 7, 8, 10, 12] |
||
| 764 | |||
| 765 | if (ageday < 0): |
||
| 766 | currentmonth -= 1 |
||
| 767 | if (currentmonth < 1): |
||
| 768 | currentyear -= 1 |
||
| 769 | currentmonth = currentmonth + 12 |
||
| 770 | |||
| 771 | dayspermonth = 30 |
||
| 772 | if currentmonth in months31days: |
||
| 773 | dayspermonth = 31 |
||
| 774 | elif currentmonth == 2: |
||
| 775 | dayspermonth = 28 |
||
| 776 | if(currentyear % 4 == 0 |
||
| 777 | and (currentyear % 100 > 0 or currentyear % 400 == 0)): |
||
| 778 | dayspermonth += 1 |
||
| 779 | |||
| 780 | ageday = ageday + dayspermonth |
||
| 781 | |||
| 782 | agemonth = currentmonth - birthmonth |
||
| 783 | if (agemonth < 0): |
||
| 784 | currentyear -= 1 |
||
| 785 | agemonth = agemonth + 12 |
||
| 786 | |||
| 787 | ageyear = currentyear - birthyear |
||
| 788 | |||
| 789 | return [{'year': ageyear, |
||
| 790 | 'month': agemonth, |
||
| 791 | 'day': ageday}] |
||
| 792 | else: |
||
| 793 | return [{'year': '', |
||
| 794 | 'month': '', |
||
| 795 | 'day': ''}] |
||
| 796 | |||
| 797 | def getAge(self): |
||
| 798 | return self.getAgeSplitted()[0]['year'] |
||
| 799 | |||
| 800 | def getAgeSplittedStr(self): |
||
| 801 | splitted = self.getAgeSplitted()[0] |
||
| 802 | arr = [] |
||
| 803 | arr.append(splitted['year'] and str(splitted['year']) + 'y' or '') |
||
| 804 | arr.append(splitted['month'] and str(splitted['month']) + 'm' or '') |
||
| 805 | arr.append(splitted['day'] and str(splitted['day']) + 'd' or '') |
||
| 806 | return ' '.join(arr) |
||
| 807 | |||
| 808 | def getCountryState(self): |
||
| 809 | return self.getField('CountryState').get(self) \ |
||
| 810 | if self.getField('CountryState').get(self) \ |
||
| 811 | else self.getPhysicalAddress() |
||
| 812 | |||
| 813 | def getGuarantorID(self): |
||
| 814 | """ |
||
| 815 | If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
||
| 816 | the current patient fields. |
||
| 817 | :return: The guarantor ID (insurance number) from |
||
| 818 | """ |
||
| 819 | return self.getInsuranceNumber() if self.getPatientAsGuarantor() else self.getField('GuarantorID').get(self) |
||
| 820 | |||
| 821 | def getGuarantorSurname(self): |
||
| 822 | """ |
||
| 823 | If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
||
| 824 | the current patient fields. |
||
| 825 | """ |
||
| 826 | return self.getSurname() if self.getPatientAsGuarantor() else self.getField('GuarantorSurname').get(self) |
||
| 827 | |||
| 828 | def getGuarantorFirstname(self): |
||
| 829 | """ |
||
| 830 | If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
||
| 831 | the current patient fields. |
||
| 832 | """ |
||
| 833 | return self.getFirstname() if self.getPatientAsGuarantor() else self.getField('GuarantorFirstname').get(self) |
||
| 834 | |||
| 835 | def getGuarantorPostalAddress(self): |
||
| 836 | """ |
||
| 837 | If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
||
| 838 | the current patient fields. |
||
| 839 | """ |
||
| 840 | return self.getPostalAddress() \ |
||
| 841 | if self.getPatientAsGuarantor() \ |
||
| 842 | else self.getField('GuarantorPostalAddress').get(self) |
||
| 843 | |||
| 844 | def getGuarantorBusinessPhone(self): |
||
| 845 | """ |
||
| 846 | If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
||
| 847 | the current patient fields. |
||
| 848 | """ |
||
| 849 | return self.getBusinessPhone() \ |
||
| 850 | if self.getPatientAsGuarantor() \ |
||
| 851 | else self.getField('GuarantorBusinessPhone').get(self) |
||
| 852 | |||
| 853 | def getGuarantorHomePhone(self): |
||
| 854 | """ |
||
| 855 | If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
||
| 856 | the current patient fields. |
||
| 857 | """ |
||
| 858 | return self.getHomePhone() if self.getPatientAsGuarantor() else self.getField('GuarantorHomePhone').get(self) |
||
| 859 | |||
| 860 | def getGuarantorMobilePhone(self): |
||
| 861 | """ |
||
| 862 | If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
||
| 863 | the current patient fields. |
||
| 864 | """ |
||
| 865 | return self.getMobilePhone() \ |
||
| 866 | if self.getPatientAsGuarantor() \ |
||
| 867 | else self.getField('GuarantorMobilePhone').get(self) |
||
| 868 | |||
| 869 | def getEthnicitiesVocabulary(self, instance=None): |
||
| 870 | """ |
||
| 871 | Obtain all the ethnicities registered in the system and returns them as a list |
||
| 872 | """ |
||
| 873 | bsc = getToolByName(self, 'bika_setup_catalog') |
||
| 874 | items = [(c.UID, c.Title) \ |
||
| 875 | for c in bsc(portal_type='Ethnicity', |
||
| 876 | inactive_state = 'active')] |
||
| 877 | items.sort(lambda x,y:cmp(x[1], y[1])) |
||
| 878 | items.insert(0, ('', t(_('')))) |
||
| 879 | return DisplayList(items) |
||
| 880 | |||
| 881 | def getPatientAnalysisRequestsURL(self): |
||
| 882 | """ |
||
| 883 | Return the url pointing to the analysis requests page of the patient |
||
| 884 | :return: patient's analysis request url |
||
| 885 | """ |
||
| 886 | return "/".join([self.absolute_url(), 'analysisrequests']) |
||
| 887 | |||
| 888 | # TODO This function will will be removed on v319 |
||
| 889 | def getEthnicity(self): |
||
| 890 | """ |
||
| 891 | This function exists because we are changing the construction of ethnicities. Until now, ethnicities options were |
||
| 892 | hand-coded but now they are a new content type. So we need to pass all patient's ethnicity values, but to do |
||
| 893 | such thing, we need to create new ethnicity types on upgrade step and edit patient ethnicity field to relate them |
||
| 894 | with its corresponding ethnicity content type. |
||
| 895 | :return: |
||
| 896 | """ |
||
| 897 | return self.getEthnicity_Obj() |
||
| 898 | |||
| 899 | # TODO This function will be removed on v319 |
||
| 900 | def setEthnicity(self, value): |
||
| 901 | self.setEthnicity_Obj(value) |
||
| 902 | |||
| 903 | def getDocuments(self): |
||
| 904 | """ |
||
| 905 | Return all the multifile objects related with the patient |
||
| 906 | """ |
||
| 907 | return self.objectValues('Multifile') |
||
| 908 | |||
| 909 | def SearchableText(self): |
||
| 910 | """ |
||
| 911 | Override searchable text logic based on the requirements. |
||
| 912 | |||
| 913 | This method constructs a text blob which contains all full-text |
||
| 914 | searchable text for this content item. |
||
| 915 | https://docs.plone.org/develop/plone/searching_and_indexing/indexing.html#full-text-searching |
||
| 916 | """ |
||
| 917 | |||
| 918 | # Speed up string concatenation ops by using a buffer |
||
| 919 | entries = [] |
||
| 920 | |||
| 921 | # plain text fields we index from ourself, |
||
| 922 | # a list of accessor methods of the class |
||
| 923 | plain_text_fields = ("Title", "getFullname", "getId", |
||
| 924 | "getPrimaryReferrerID", "getPrimaryReferrerTitle", "getClientPatientID") |
||
| 925 | |||
| 926 | def read(accessor): |
||
| 927 | """ |
||
| 928 | Call a class accessor method to give a value for certain Archetypes |
||
| 929 | field. |
||
| 930 | """ |
||
| 931 | try: |
||
| 932 | value = accessor() |
||
| 933 | except: |
||
| 934 | value = "" |
||
| 935 | |||
| 936 | if value is None: |
||
| 937 | value = "" |
||
| 938 | |||
| 939 | return value |
||
| 940 | |||
| 941 | # Concatenate plain text fields as they are |
||
| 942 | for f in plain_text_fields: |
||
| 943 | accessor = getattr(self, f) |
||
| 944 | value = read(accessor) |
||
| 945 | entries.append(value) |
||
| 946 | |||
| 947 | # Adding HTML Fields to SearchableText can be uncommented if necessary |
||
| 948 | # transforms = getToolByName(self, 'portal_transforms') |
||
| 949 | # |
||
| 950 | # # Run HTML valued fields through text/plain conversion |
||
| 951 | # for f in html_fields: |
||
| 952 | # accessor = getattr(self, f) |
||
| 953 | # value = read(accessor) |
||
| 954 | # |
||
| 955 | # if value != "": |
||
| 956 | # stream = transforms.convertTo('text/plain', value, mimetype='text/html') |
||
| 957 | # value = stream.getData() |
||
| 958 | # |
||
| 959 | # entries.append(value) |
||
| 960 | |||
| 961 | # Plone accessor methods assume utf-8 |
||
| 962 | def convertToUTF8(text): |
||
| 963 | if type(text) == unicode: |
||
| 964 | return text.encode("utf-8") |
||
| 965 | return text |
||
| 966 | |||
| 967 | entries = [convertToUTF8(entry) for entry in entries] |
||
| 968 | |||
| 969 | # Concatenate all strings to one text blob |
||
| 970 | return " ".join(entries) |
||
| 971 | |||
| 975 |