diff --git a/project/animal/exports.py b/project/animal/exports.py index 22f86f64..adb4bac1 100644 --- a/project/animal/exports.py +++ b/project/animal/exports.py @@ -89,19 +89,16 @@ class EndpointFlatDataPivot(FlatFileExporter): def _get_header_row(self): return [ - 'study', - 'study_url', - 'Study HAWC ID', - 'Study Published', - - 'Experiment ID', - 'experiment', - 'experiment_url', + 'study id', + 'study name', + 'study published', + + 'experiment id', + 'experiment name', 'chemical', - 'Animal Group ID', - 'animal_group', - 'animal_group_url', + 'animal group id', + 'animal group name', 'lifestage exposed', 'lifestage assessed', 'species', @@ -114,20 +111,19 @@ def _get_header_row(self): 'treatment period', 'duration exposure', - 'endpoint_name', - 'endpoint_url', + 'endpoint id', + 'endpoint name', 'system', 'organ', 'effect', 'effect subtype', 'diagnostic', 'tags', - 'observation_time', - 'data_type', + 'observation time', + 'data type', 'doses', - 'dose_units', - 'response_units', - 'Endpoint Key', + 'dose units', + 'response units', 'low_dose', 'NOEL', @@ -141,16 +137,16 @@ def _get_header_row(self): 'BMDU', 'CSF', - 'Row Key', - 'dose_index', + 'key', + 'dose index', 'dose', - 'n', + 'N', 'incidence', 'response', - 'stdev', - 'percentControlMean', - 'percentControlLow', - 'percentControlHigh' + 'variance', + 'percent control mean', + 'percent control low', + 'percent control high' ] def _get_data_rows(self): @@ -201,19 +197,16 @@ def get_tags(e): # build endpoint-group independent data row = [ - ser['animal_group']['experiment']['study']['short_citation'], - ser['animal_group']['experiment']['study']['url'], ser['animal_group']['experiment']['study']['id'], + ser['animal_group']['experiment']['study']['short_citation'], ser['animal_group']['experiment']['study']['published'], ser['animal_group']['experiment']['id'], ser['animal_group']['experiment']['name'], - ser['animal_group']['experiment']['url'], ser['animal_group']['experiment']['chemical'], ser['animal_group']['id'], ser['animal_group']['name'], - ser['animal_group']['url'], ser['animal_group']['lifestage_exposed'], ser['animal_group']['lifestage_assessed'], ser['animal_group']['species'], @@ -227,8 +220,8 @@ def get_tags(e): ser['animal_group']['dosing_regime']), ser['animal_group']['dosing_regime']['duration_exposure_text'], + ser['id'], ser['name'], - ser['url'], ser['system'], ser['organ'], ser['effect'], @@ -240,7 +233,6 @@ def get_tags(e): get_doses_str(doses), get_dose_units(doses), ser['response_units'], - ser['id'] ] # dose-group specific information diff --git a/project/animal/migrations/0011_auto_20150723_1600.py b/project/animal/migrations/0011_auto_20150723_1600.py index a96c2979..1a9a0a60 100644 --- a/project/animal/migrations/0011_auto_20150723_1600.py +++ b/project/animal/migrations/0011_auto_20150723_1600.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): dependencies = [ ('animal', '0010_auto_20150723_1536'), ('bmd', '0002_auto_20150723_1557'), - ('epi', '0003_auto_20150723_1544'), + ('epi', '0001_initial'), ('invitro', '0002_auto_20150723_1542'), ('summary', '0003_auto_20150723_1557'), ] diff --git a/project/assessment/models.py b/project/assessment/models.py index a66194b4..27c41977 100644 --- a/project/assessment/models.py +++ b/project/assessment/models.py @@ -473,8 +473,8 @@ def get_json(self, *args, **kwargs): return an empty object. """ d = {} - if hasattr(self, 'assessedoutcome'): - d = self.assessedoutcome.get_json(*args, **kwargs) + if hasattr(self, 'outcome'): + d = self.outcome.get_json(*args, **kwargs) elif hasattr(self, 'endpoint'): d = self.endpoint.d_response(*args, **kwargs) elif hasattr(self, 'ivendpoint'): diff --git a/project/assessment/views.py b/project/assessment/views.py index cf628092..bec1d80d 100644 --- a/project/assessment/views.py +++ b/project/assessment/views.py @@ -325,17 +325,12 @@ def get_context_data(self, **kwargs): .filter(assessment_id=self.assessment.id)\ .count() - aos = self.model.assessedoutcome\ + os = self.model.outcome\ .related.related_model.objects\ .filter(assessment_id=self.assessment.id)\ .count() - aos2 = self.model.outcome\ - .related.related_model.objects\ - .filter(assessment_id=self.assessment.id)\ - .count() - - mrs = get_model('epi', 'metaresult')\ + mrs = get_model('epimeta', 'metaresult')\ .objects\ .filter(protocol__study__assessment_id=self.assessment.id)\ .count() @@ -345,13 +340,12 @@ def get_context_data(self, **kwargs): .filter(assessment_id=self.assessment.id)\ .count() - alleps = eps + aos + aos2 + mrs + iveps + alleps = eps + os + mrs + iveps context.update({ "ivendpoints": iveps, "endpoints": eps, - "assessed_outcomes": aos, - "outcomes": aos2, + "outcomes": os, "meta_results": mrs, "total_endpoints": alleps }) diff --git a/project/epi/admin.py b/project/epi/admin.py index 9a77a854..70750595 100644 --- a/project/epi/admin.py +++ b/project/epi/admin.py @@ -3,11 +3,34 @@ from . import models -class StatisticalMetricAdmin(admin.ModelAdmin): - list_display = ('metric', 'abbreviation', 'isLog', 'order', ) +class CriteriaAdmin(admin.ModelAdmin): + pass - def has_delete_permission(self, request, obj=None): - return False +class CountryAdmin(admin.ModelAdmin): -admin.site.register(models.StatisticalMetric, StatisticalMetricAdmin) + search_fields = ( + 'name', + ) + + +class AdjustmentFactorAdmin(admin.ModelAdmin): + pass + + +class EthnicityAdmin(admin.ModelAdmin): + pass + + +class ResultMetricAdmin(admin.ModelAdmin): + list_display = ( + "metric", "abbreviation", "showForestPlot", + "isLog", "reference_value", "order", + ) + + +admin.site.register(models.Criteria, CriteriaAdmin) +admin.site.register(models.Country, CountryAdmin) +admin.site.register(models.AdjustmentFactor, AdjustmentFactorAdmin) +admin.site.register(models.Ethnicity, EthnicityAdmin) +admin.site.register(models.ResultMetric, ResultMetricAdmin) diff --git a/project/epi/api.py b/project/epi/api.py index 491be4e5..e4b8dc12 100644 --- a/project/epi/api.py +++ b/project/epi/api.py @@ -5,12 +5,6 @@ from . import models, serializers -class MetaResult(AssessmentViewset): - assessment_filter_args = "protocol__study__assessment" - model = models.MetaResult - serializer_class = serializers.MetaResultSerializer - - class StudyPopulation(AssessmentViewset): assessment_filter_args = "study__assessment" model = models.StudyPopulation @@ -20,10 +14,28 @@ class StudyPopulation(AssessmentViewset): class Exposure(AssessmentViewset): assessment_filter_args = "study_population__study__assessment" model = models.Exposure - serializer_class = serializers.ExposureVerboseSerializer + serializer_class = serializers.ExposureSerializer -class AssessedOutcome(AssessmentViewset): +class Outcome(AssessmentViewset): assessment_filter_args = "assessment" - model = models.AssessedOutcome - serializer_class = serializers.AssessedOutcomeSerializer + model = models.Outcome + serializer_class = serializers.OutcomeSerializer + + +class Result(AssessmentViewset): + assessment_filter_args = "outcome__assessment" + model = models.Result + serializer_class = serializers.ResultSerializer + + +class ComparisonSet(AssessmentViewset): + assessment_filter_args = "assessment" # todo: fix + model = models.ComparisonSet + serializer_class = serializers.ComparisonSetSerializer + + +class Group(AssessmentViewset): + assessment_filter_args = "assessment" # todo: fix + model = models.Group + serializer_class = serializers.GroupSerializer diff --git a/project/epi/exports.py b/project/epi/exports.py index 5d5f0470..f6759c5a 100644 --- a/project/epi/exports.py +++ b/project/epi/exports.py @@ -1,21 +1,21 @@ from study.models import Study from utils.helper import FlatFileExporter + from . import models -class AssessedOutcomeFlatComplete(FlatFileExporter): - """ - Returns a complete export of all data required to rebuild the the - epidemiological study type from scratch. - """ +class OutcomeComplete(FlatFileExporter): def _get_header_row(self): header = [] header.extend(Study.flat_complete_header_row()) header.extend(models.StudyPopulation.flat_complete_header_row()) + header.extend(models.Outcome.flat_complete_header_row()) header.extend(models.Exposure.flat_complete_header_row()) - header.extend(models.AssessedOutcome.flat_complete_header_row()) - header.extend(models.AssessedOutcomeGroup.flat_complete_header_row()) + header.extend(models.ComparisonSet.flat_complete_header_row()) + header.extend(models.Result.flat_complete_header_row()) + header.extend(models.Group.flat_complete_header_row()) + header.extend(models.GroupResult.flat_complete_header_row()) return header def _get_data_rows(self): @@ -23,237 +23,131 @@ def _get_data_rows(self): for obj in self.queryset: ser = obj.get_json(json_encode=False) row = [] - row.extend(Study.flat_complete_data_row(ser['exposure']['study_population']['study'])) - row.extend(models.StudyPopulation.flat_complete_data_row(ser['exposure']['study_population'])) - row.extend(models.Exposure.flat_complete_data_row(ser['exposure'])) - row.extend(models.AssessedOutcome.flat_complete_data_row(ser)) - # build a row for each aog - for aog in ser['groups']: - row_copy = list(row) # clone - row_copy.extend(models.AssessedOutcomeGroup.flat_complete_data_row(aog)) - rows.append(row_copy) + row.extend(Study.flat_complete_data_row(ser['study_population']['study'])) + row.extend(models.StudyPopulation.flat_complete_data_row(ser['study_population'])) + row.extend(models.Outcome.flat_complete_data_row(ser)) + for res in ser['results']: + row_copy = list(row) + row_copy.extend(models.Exposure.flat_complete_data_row(res["comparison_set"]["exposure"])) + row_copy.extend(models.ComparisonSet.flat_complete_data_row(res["comparison_set"])) + row_copy.extend(models.Result.flat_complete_data_row(res)) + for rg in res['results']: + row_copy2 = list(row_copy) + row_copy2.extend(models.Group.flat_complete_data_row(rg["group"])) + row_copy2.extend(models.GroupResult.flat_complete_data_row(rg)) + rows.append(row_copy2) return rows -class AssessedOutcomeFlatDataPivot(FlatFileExporter): - """ - Return a subset of frequently-used data for generation of data-pivot - visualizations. - """ +class OutcomeDataPivot(FlatFileExporter): def _get_header_row(self): return [ - 'Study', - 'Study URL', - 'Study HAWC ID', - 'Study Published?', - - 'Study Population Name', - 'Study Population Key', - 'Design', - 'Study Population URL', - - 'Exposure', - 'Exposure Key', - 'Exposure Metric', - 'Exposure URL', - 'Dose Units', - - 'Assessed Outcome Name', - 'Assessed Outcome Population Description', - 'Assessed Outcome Key', - 'Diagnostic', - 'Statistical Metric', - 'Statistical Metric Abbreviation', - 'Statistical Metric Description', - 'Outcome Summary', - 'Dose Response', - 'Statistical Power', - 'Support Main Finding', + 'study id', + 'study name', + 'study published', + + 'study population id', + 'study population name', + 'design', + + 'outcome id', + 'outcome name', + 'diagnostic', + + 'comparison set id', + 'comparison set name', + + 'exposure id', + 'exposure name', + 'exposure metric', + 'dose units', + + 'result id', + 'result population description', + 'statistical metric', + 'statistical metric abbreviation', + 'statistical metric description', + 'result summary', + 'dose response', + 'statistical power', + 'CI units', - 'Exposure Group Name', - 'Exposure Group Comparative Description Name', - 'Exposure Group Order', - 'Exposure Group Numeric', + 'exposure group order', + 'exposure group name', + 'exposure group comparison name', + 'exposure group numeric', - 'Row Key', - 'Assessed Outcome Group Primary Key', + 'key', + 'result group id', 'N', - 'Estimate', - 'Lower CI', - 'Upper CI', - 'CI units', - 'SE', - 'Statistical Significance', - 'Statistical Significance (numeric)', - 'Main Finding' + 'estimate', + 'lower CI', + 'upper CI', + 'variance', + 'statistical significance', + 'statistical significance (numeric)', + 'main finding', + 'main finding support', ] def _get_data_rows(self): rows = [] for obj in self.queryset: ser = obj.get_json(json_encode=False) - row = [ - ser['exposure']['study_population']['study']['short_citation'], - ser['exposure']['study_population']['study']['url'], - ser['exposure']['study_population']['study']['id'], - ser['exposure']['study_population']['study']['published'], + ser['study_population']['study']['id'], + ser['study_population']['study']['short_citation'], + ser['study_population']['study']['published'], - ser['exposure']['study_population']['name'], - ser['exposure']['study_population']['id'], - ser['exposure']['study_population']['design'], - ser['exposure']['study_population']['url'], + ser['study_population']['id'], + ser['study_population']['name'], + ser['study_population']['design'], - ser['exposure']['exposure_form_definition'], - ser['exposure']['id'], - ser['exposure']['metric'], - ser['exposure']['url'], - ser['exposure']['metric_units']['name'], - - ser['name'], - ser['population_description'], ser['id'], + ser['name'], ser['diagnostic'], - ser['statistical_metric']['metric'], - ser['statistical_metric']['abbreviation'], - ser['statistical_metric_description'], - ser['summary'], - ser['dose_response'], - ser['statistical_power'], - ser['main_finding_support'], ] - - for group in ser['groups']: - row_copy = list(row) # clone + for res in ser['results']: + row_copy = list(row) row_copy.extend([ - group['exposure_group']['description'], - group['exposure_group']['comparative_name'], - group['exposure_group']['exposure_group_id'], - group['exposure_group']['exposure_numeric'], - - group['id'], # repeat for data-pivot key - group['id'], - group['n'], - group['estimate'], - group['lower_ci'], - group['upper_ci'], - group['ci_units'], - group['se'], - group['p_value_text'], - group['p_value'], - group['isMainFinding'] + res["comparison_set"]["id"], + res["comparison_set"]["name"], + + res["comparison_set"]["exposure"]["id"], + res["comparison_set"]["exposure"]["name"], + res["comparison_set"]["exposure"]["metric"], + res["comparison_set"]["exposure"]["metric_units"]["name"], + + res['id'], + res['population_description'], + res['metric']['metric'], + res['metric']['abbreviation'], + res['metric_description'], + res['comments'], + res['dose_response'], + res['statistical_power'], + res['ci_units'], ]) - rows.append(row_copy) - - return rows - - -class MetaResultFlatComplete(FlatFileExporter): - """ - Returns a complete export of all data required to rebuild the the - epidemiological meta-result study type from scratch. - """ - - def _get_header_row(self): - header = [] - header.extend(Study.flat_complete_header_row()) - header.extend(models.MetaProtocol.flat_complete_header_row()) - header.extend(models.MetaResult.flat_complete_header_row()) - header.extend(models.SingleResult.flat_complete_header_row()) - return header - - def _get_data_rows(self): - rows = [] - for obj in self.queryset: - ser = obj.get_json(json_encode=False) - row = [] - row.extend(Study.flat_complete_data_row(ser['protocol']['study'])) - row.extend(models.MetaProtocol.flat_complete_data_row(ser['protocol'])) - row.extend(models.MetaResult.flat_complete_data_row(ser)) - - if len(ser['single_results'])==0: - # print one-row with no single-results - rows.append(row) - else: - # print each single-result as a new row - for sr in ser['single_results']: - row_copy = list(row) # clone - row_copy.extend(models.SingleResult.flat_complete_data_row(sr)) - rows.append(row_copy) - return rows - - -class MetaResultFlatDataPivot(FlatFileExporter): - """ - Return a subset of frequently-used data for generation of data-pivot - visualizations. - """ - - def _get_header_row(self): - return [ - 'Study', - 'Study URL', - 'Study HAWC ID', - 'Study Published?', - - 'Protocol Primary Key', - 'Protocol URL', - 'Protocol Name', - 'Protocol Type', - 'Total References', - 'Identified References', - - 'Row Key', - 'Result Primary Key', - 'Result URL', - 'Result Label', - 'Health Outcome', - 'Exposure', - 'Result References', - 'Statistical Metric', - 'Statistical Metric Abbreviation', - 'N', - 'Estimate', - 'Lower CI', - 'Upper CI', - 'CI units', - 'Heterogeneity' - ] - - def _get_data_rows(self): - rows = [] - for obj in self.queryset: - ser = obj.get_json(json_encode=False) - row = [ - ser['protocol']['study']['short_citation'], - ser['protocol']['study']['url'], - ser['protocol']['study']['id'], - ser['protocol']['study']['published'], - - ser['protocol']['id'], - ser['protocol']['url'], - ser['protocol']['name'], - ser['protocol']['protocol_type'], - ser['protocol']['total_references'], - ser['protocol']['total_studies_identified'], - - ser['id'], # repeat for data-pivot key - ser['id'], - ser['url'], - ser['label'], - ser['health_outcome'], - ser['exposure_name'], - ser['number_studies'], - ser['statistical_metric']['metric'], - ser['statistical_metric']['abbreviation'], - ser['n'], - ser['estimate'], - ser['lower_ci'], - ser['upper_ci'], - ser['ci_units'], - ser['heterogeneity'], - ] - rows.append(row) - + for rg in res['results']: + row_copy2 = list(row_copy) + row_copy2.extend([ + rg['group']['group_id'], + rg['group']['name'], + rg['group']['comparative_name'], + rg['group']['numeric'], + + rg['id'], + rg['id'], # repeat for data-pivot key + rg['n'], + rg['estimate'], + rg['lower_ci'], + rg['upper_ci'], + rg['variance'], + rg['p_value_text'], + rg['p_value'], + rg['is_main_finding'], + rg['main_finding_support'], + ]) + rows.append(row_copy2) return rows diff --git a/project/epi2/fixtures/countries.json b/project/epi/fixtures/countries.json similarity index 78% rename from project/epi2/fixtures/countries.json rename to project/epi/fixtures/countries.json index fa5ed750..b22ba085 100644 --- a/project/epi2/fixtures/countries.json +++ b/project/epi/fixtures/countries.json @@ -4,7 +4,7 @@ "code": "AF", "name": "Afghanistan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 1 }, { @@ -12,7 +12,7 @@ "code": "AX", "name": "Åland Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 2 }, { @@ -20,7 +20,7 @@ "code": "AL", "name": "Albania" }, - "model": "epi2.country", + "model": "epi.country", "pk": 3 }, { @@ -28,7 +28,7 @@ "code": "DZ", "name": "Algeria" }, - "model": "epi2.country", + "model": "epi.country", "pk": 4 }, { @@ -36,7 +36,7 @@ "code": "AS", "name": "American Samoa" }, - "model": "epi2.country", + "model": "epi.country", "pk": 5 }, { @@ -44,7 +44,7 @@ "code": "AD", "name": "Andorra" }, - "model": "epi2.country", + "model": "epi.country", "pk": 6 }, { @@ -52,7 +52,7 @@ "code": "AO", "name": "Angola" }, - "model": "epi2.country", + "model": "epi.country", "pk": 7 }, { @@ -60,7 +60,7 @@ "code": "AI", "name": "Anguilla" }, - "model": "epi2.country", + "model": "epi.country", "pk": 8 }, { @@ -68,7 +68,7 @@ "code": "AQ", "name": "Antarctica" }, - "model": "epi2.country", + "model": "epi.country", "pk": 9 }, { @@ -76,7 +76,7 @@ "code": "AG", "name": "Antigua And Barbuda" }, - "model": "epi2.country", + "model": "epi.country", "pk": 10 }, { @@ -84,7 +84,7 @@ "code": "AR", "name": "Argentina" }, - "model": "epi2.country", + "model": "epi.country", "pk": 11 }, { @@ -92,7 +92,7 @@ "code": "AM", "name": "Armenia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 12 }, { @@ -100,7 +100,7 @@ "code": "AW", "name": "Aruba" }, - "model": "epi2.country", + "model": "epi.country", "pk": 13 }, { @@ -108,7 +108,7 @@ "code": "AU", "name": "Australia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 14 }, { @@ -116,7 +116,7 @@ "code": "AT", "name": "Austria" }, - "model": "epi2.country", + "model": "epi.country", "pk": 15 }, { @@ -124,7 +124,7 @@ "code": "AZ", "name": "Azerbaijan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 16 }, { @@ -132,7 +132,7 @@ "code": "BS", "name": "Bahamas" }, - "model": "epi2.country", + "model": "epi.country", "pk": 17 }, { @@ -140,7 +140,7 @@ "code": "BH", "name": "Bahrain" }, - "model": "epi2.country", + "model": "epi.country", "pk": 18 }, { @@ -148,7 +148,7 @@ "code": "BD", "name": "Bangladesh" }, - "model": "epi2.country", + "model": "epi.country", "pk": 19 }, { @@ -156,7 +156,7 @@ "code": "BB", "name": "Barbados" }, - "model": "epi2.country", + "model": "epi.country", "pk": 20 }, { @@ -164,7 +164,7 @@ "code": "BY", "name": "Belarus" }, - "model": "epi2.country", + "model": "epi.country", "pk": 21 }, { @@ -172,7 +172,7 @@ "code": "BE", "name": "Belgium" }, - "model": "epi2.country", + "model": "epi.country", "pk": 22 }, { @@ -180,7 +180,7 @@ "code": "BZ", "name": "Belize" }, - "model": "epi2.country", + "model": "epi.country", "pk": 23 }, { @@ -188,7 +188,7 @@ "code": "BJ", "name": "Benin" }, - "model": "epi2.country", + "model": "epi.country", "pk": 24 }, { @@ -196,7 +196,7 @@ "code": "BM", "name": "Bermuda" }, - "model": "epi2.country", + "model": "epi.country", "pk": 25 }, { @@ -204,7 +204,7 @@ "code": "BT", "name": "Bhutan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 26 }, { @@ -212,7 +212,7 @@ "code": "BO", "name": "Bolivia, Plurinational State Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 27 }, { @@ -220,7 +220,7 @@ "code": "BQ", "name": "Bonaire, Sint Eustatius And Saba" }, - "model": "epi2.country", + "model": "epi.country", "pk": 28 }, { @@ -228,7 +228,7 @@ "code": "BA", "name": "Bosnia And Herzegovina" }, - "model": "epi2.country", + "model": "epi.country", "pk": 29 }, { @@ -236,7 +236,7 @@ "code": "BW", "name": "Botswana" }, - "model": "epi2.country", + "model": "epi.country", "pk": 30 }, { @@ -244,7 +244,7 @@ "code": "BV", "name": "Bouvet Island" }, - "model": "epi2.country", + "model": "epi.country", "pk": 31 }, { @@ -252,7 +252,7 @@ "code": "BR", "name": "Brazil" }, - "model": "epi2.country", + "model": "epi.country", "pk": 32 }, { @@ -260,7 +260,7 @@ "code": "IO", "name": "British Indian Ocean Territory" }, - "model": "epi2.country", + "model": "epi.country", "pk": 33 }, { @@ -268,7 +268,7 @@ "code": "BN", "name": "Brunei Darussalam" }, - "model": "epi2.country", + "model": "epi.country", "pk": 34 }, { @@ -276,7 +276,7 @@ "code": "BG", "name": "Bulgaria" }, - "model": "epi2.country", + "model": "epi.country", "pk": 35 }, { @@ -284,7 +284,7 @@ "code": "BF", "name": "Burkina Faso" }, - "model": "epi2.country", + "model": "epi.country", "pk": 36 }, { @@ -292,7 +292,7 @@ "code": "BI", "name": "Burundi" }, - "model": "epi2.country", + "model": "epi.country", "pk": 37 }, { @@ -300,7 +300,7 @@ "code": "KH", "name": "Cambodia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 38 }, { @@ -308,7 +308,7 @@ "code": "CM", "name": "Cameroon" }, - "model": "epi2.country", + "model": "epi.country", "pk": 39 }, { @@ -316,7 +316,7 @@ "code": "CA", "name": "Canada" }, - "model": "epi2.country", + "model": "epi.country", "pk": 40 }, { @@ -324,7 +324,7 @@ "code": "CV", "name": "Cape Verde" }, - "model": "epi2.country", + "model": "epi.country", "pk": 41 }, { @@ -332,7 +332,7 @@ "code": "KY", "name": "Cayman Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 42 }, { @@ -340,7 +340,7 @@ "code": "CF", "name": "Central African Republic" }, - "model": "epi2.country", + "model": "epi.country", "pk": 43 }, { @@ -348,7 +348,7 @@ "code": "TD", "name": "Chad" }, - "model": "epi2.country", + "model": "epi.country", "pk": 44 }, { @@ -356,7 +356,7 @@ "code": "CL", "name": "Chile" }, - "model": "epi2.country", + "model": "epi.country", "pk": 45 }, { @@ -364,7 +364,7 @@ "code": "CN", "name": "China" }, - "model": "epi2.country", + "model": "epi.country", "pk": 46 }, { @@ -372,7 +372,7 @@ "code": "CX", "name": "Christmas Island" }, - "model": "epi2.country", + "model": "epi.country", "pk": 47 }, { @@ -380,7 +380,7 @@ "code": "CC", "name": "Cocos (Keeling) Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 48 }, { @@ -388,7 +388,7 @@ "code": "CO", "name": "Colombia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 49 }, { @@ -396,7 +396,7 @@ "code": "KM", "name": "Comoros" }, - "model": "epi2.country", + "model": "epi.country", "pk": 50 }, { @@ -404,7 +404,7 @@ "code": "CG", "name": "Congo" }, - "model": "epi2.country", + "model": "epi.country", "pk": 51 }, { @@ -412,7 +412,7 @@ "code": "CD", "name": "Congo, The Democratic Republic Of The" }, - "model": "epi2.country", + "model": "epi.country", "pk": 52 }, { @@ -420,7 +420,7 @@ "code": "CK", "name": "Cook Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 53 }, { @@ -428,7 +428,7 @@ "code": "CR", "name": "Costa Rica" }, - "model": "epi2.country", + "model": "epi.country", "pk": 54 }, { @@ -436,7 +436,7 @@ "code": "CI", "name": "Côte D'Ivoire" }, - "model": "epi2.country", + "model": "epi.country", "pk": 55 }, { @@ -444,7 +444,7 @@ "code": "HR", "name": "Croatia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 56 }, { @@ -452,7 +452,7 @@ "code": "CU", "name": "Cuba" }, - "model": "epi2.country", + "model": "epi.country", "pk": 57 }, { @@ -460,7 +460,7 @@ "code": "CW", "name": "Curaçao" }, - "model": "epi2.country", + "model": "epi.country", "pk": 58 }, { @@ -468,7 +468,7 @@ "code": "CY", "name": "Cyprus" }, - "model": "epi2.country", + "model": "epi.country", "pk": 59 }, { @@ -476,7 +476,7 @@ "code": "CZ", "name": "Czech Republic" }, - "model": "epi2.country", + "model": "epi.country", "pk": 60 }, { @@ -484,7 +484,7 @@ "code": "DK", "name": "Denmark" }, - "model": "epi2.country", + "model": "epi.country", "pk": 61 }, { @@ -492,7 +492,7 @@ "code": "DJ", "name": "Djibouti" }, - "model": "epi2.country", + "model": "epi.country", "pk": 62 }, { @@ -500,7 +500,7 @@ "code": "DM", "name": "Dominica" }, - "model": "epi2.country", + "model": "epi.country", "pk": 63 }, { @@ -508,7 +508,7 @@ "code": "DO", "name": "Dominican Republic" }, - "model": "epi2.country", + "model": "epi.country", "pk": 64 }, { @@ -516,7 +516,7 @@ "code": "EC", "name": "Ecuador" }, - "model": "epi2.country", + "model": "epi.country", "pk": 65 }, { @@ -524,7 +524,7 @@ "code": "EG", "name": "Egypt" }, - "model": "epi2.country", + "model": "epi.country", "pk": 66 }, { @@ -532,7 +532,7 @@ "code": "SV", "name": "El Salvador" }, - "model": "epi2.country", + "model": "epi.country", "pk": 67 }, { @@ -540,7 +540,7 @@ "code": "GQ", "name": "Equatorial Guinea" }, - "model": "epi2.country", + "model": "epi.country", "pk": 68 }, { @@ -548,7 +548,7 @@ "code": "ER", "name": "Eritrea" }, - "model": "epi2.country", + "model": "epi.country", "pk": 69 }, { @@ -556,7 +556,7 @@ "code": "EE", "name": "Estonia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 70 }, { @@ -564,7 +564,7 @@ "code": "ET", "name": "Ethiopia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 71 }, { @@ -572,7 +572,7 @@ "code": "FK", "name": "Falkland Islands (Malvinas)" }, - "model": "epi2.country", + "model": "epi.country", "pk": 72 }, { @@ -580,7 +580,7 @@ "code": "FO", "name": "Faroe Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 73 }, { @@ -588,7 +588,7 @@ "code": "FJ", "name": "Fiji" }, - "model": "epi2.country", + "model": "epi.country", "pk": 74 }, { @@ -596,7 +596,7 @@ "code": "FI", "name": "Finland" }, - "model": "epi2.country", + "model": "epi.country", "pk": 75 }, { @@ -604,7 +604,7 @@ "code": "FR", "name": "France" }, - "model": "epi2.country", + "model": "epi.country", "pk": 76 }, { @@ -612,7 +612,7 @@ "code": "GF", "name": "French Guiana" }, - "model": "epi2.country", + "model": "epi.country", "pk": 77 }, { @@ -620,7 +620,7 @@ "code": "PF", "name": "French Polynesia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 78 }, { @@ -628,7 +628,7 @@ "code": "TF", "name": "French Southern Territories" }, - "model": "epi2.country", + "model": "epi.country", "pk": 79 }, { @@ -636,7 +636,7 @@ "code": "GA", "name": "Gabon" }, - "model": "epi2.country", + "model": "epi.country", "pk": 80 }, { @@ -644,7 +644,7 @@ "code": "GM", "name": "Gambia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 81 }, { @@ -652,7 +652,7 @@ "code": "GE", "name": "Georgia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 82 }, { @@ -660,7 +660,7 @@ "code": "DE", "name": "Germany" }, - "model": "epi2.country", + "model": "epi.country", "pk": 83 }, { @@ -668,7 +668,7 @@ "code": "GH", "name": "Ghana" }, - "model": "epi2.country", + "model": "epi.country", "pk": 84 }, { @@ -676,7 +676,7 @@ "code": "GI", "name": "Gibraltar" }, - "model": "epi2.country", + "model": "epi.country", "pk": 85 }, { @@ -684,7 +684,7 @@ "code": "GR", "name": "Greece" }, - "model": "epi2.country", + "model": "epi.country", "pk": 86 }, { @@ -692,7 +692,7 @@ "code": "GL", "name": "Greenland" }, - "model": "epi2.country", + "model": "epi.country", "pk": 87 }, { @@ -700,7 +700,7 @@ "code": "GD", "name": "Grenada" }, - "model": "epi2.country", + "model": "epi.country", "pk": 88 }, { @@ -708,7 +708,7 @@ "code": "GP", "name": "Guadeloupe" }, - "model": "epi2.country", + "model": "epi.country", "pk": 89 }, { @@ -716,7 +716,7 @@ "code": "GU", "name": "Guam" }, - "model": "epi2.country", + "model": "epi.country", "pk": 90 }, { @@ -724,7 +724,7 @@ "code": "GT", "name": "Guatemala" }, - "model": "epi2.country", + "model": "epi.country", "pk": 91 }, { @@ -732,7 +732,7 @@ "code": "GG", "name": "Guernsey" }, - "model": "epi2.country", + "model": "epi.country", "pk": 92 }, { @@ -740,7 +740,7 @@ "code": "GN", "name": "Guinea" }, - "model": "epi2.country", + "model": "epi.country", "pk": 93 }, { @@ -748,7 +748,7 @@ "code": "GW", "name": "Guinea-Bissau" }, - "model": "epi2.country", + "model": "epi.country", "pk": 94 }, { @@ -756,7 +756,7 @@ "code": "GY", "name": "Guyana" }, - "model": "epi2.country", + "model": "epi.country", "pk": 95 }, { @@ -764,7 +764,7 @@ "code": "HT", "name": "Haiti" }, - "model": "epi2.country", + "model": "epi.country", "pk": 96 }, { @@ -772,7 +772,7 @@ "code": "HM", "name": "Heard Island And Mcdonald Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 97 }, { @@ -780,7 +780,7 @@ "code": "VA", "name": "Holy See (Vatican City State)" }, - "model": "epi2.country", + "model": "epi.country", "pk": 98 }, { @@ -788,7 +788,7 @@ "code": "HN", "name": "Honduras" }, - "model": "epi2.country", + "model": "epi.country", "pk": 99 }, { @@ -796,7 +796,7 @@ "code": "HK", "name": "Hong Kong" }, - "model": "epi2.country", + "model": "epi.country", "pk": 100 }, { @@ -804,7 +804,7 @@ "code": "HU", "name": "Hungary" }, - "model": "epi2.country", + "model": "epi.country", "pk": 101 }, { @@ -812,7 +812,7 @@ "code": "IS", "name": "Iceland" }, - "model": "epi2.country", + "model": "epi.country", "pk": 102 }, { @@ -820,7 +820,7 @@ "code": "IN", "name": "India" }, - "model": "epi2.country", + "model": "epi.country", "pk": 103 }, { @@ -828,7 +828,7 @@ "code": "ID", "name": "Indonesia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 104 }, { @@ -836,7 +836,7 @@ "code": "IR", "name": "Iran, Islamic Republic Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 105 }, { @@ -844,7 +844,7 @@ "code": "IQ", "name": "Iraq" }, - "model": "epi2.country", + "model": "epi.country", "pk": 106 }, { @@ -852,7 +852,7 @@ "code": "IE", "name": "Ireland" }, - "model": "epi2.country", + "model": "epi.country", "pk": 107 }, { @@ -860,7 +860,7 @@ "code": "IM", "name": "Isle Of Man" }, - "model": "epi2.country", + "model": "epi.country", "pk": 108 }, { @@ -868,7 +868,7 @@ "code": "IL", "name": "Israel" }, - "model": "epi2.country", + "model": "epi.country", "pk": 109 }, { @@ -876,7 +876,7 @@ "code": "IT", "name": "Italy" }, - "model": "epi2.country", + "model": "epi.country", "pk": 110 }, { @@ -884,7 +884,7 @@ "code": "JM", "name": "Jamaica" }, - "model": "epi2.country", + "model": "epi.country", "pk": 111 }, { @@ -892,7 +892,7 @@ "code": "JP", "name": "Japan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 112 }, { @@ -900,7 +900,7 @@ "code": "JE", "name": "Jersey" }, - "model": "epi2.country", + "model": "epi.country", "pk": 113 }, { @@ -908,7 +908,7 @@ "code": "JO", "name": "Jordan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 114 }, { @@ -916,7 +916,7 @@ "code": "KZ", "name": "Kazakhstan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 115 }, { @@ -924,7 +924,7 @@ "code": "KE", "name": "Kenya" }, - "model": "epi2.country", + "model": "epi.country", "pk": 116 }, { @@ -932,7 +932,7 @@ "code": "KI", "name": "Kiribati" }, - "model": "epi2.country", + "model": "epi.country", "pk": 117 }, { @@ -940,7 +940,7 @@ "code": "KP", "name": "Korea, Democratic People's Republic Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 118 }, { @@ -948,7 +948,7 @@ "code": "KR", "name": "Korea, Republic Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 119 }, { @@ -956,7 +956,7 @@ "code": "KW", "name": "Kuwait" }, - "model": "epi2.country", + "model": "epi.country", "pk": 120 }, { @@ -964,7 +964,7 @@ "code": "KG", "name": "Kyrgyzstan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 121 }, { @@ -972,7 +972,7 @@ "code": "LA", "name": "Lao People's Democratic Republic" }, - "model": "epi2.country", + "model": "epi.country", "pk": 122 }, { @@ -980,7 +980,7 @@ "code": "LV", "name": "Latvia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 123 }, { @@ -988,7 +988,7 @@ "code": "LB", "name": "Lebanon" }, - "model": "epi2.country", + "model": "epi.country", "pk": 124 }, { @@ -996,7 +996,7 @@ "code": "LS", "name": "Lesotho" }, - "model": "epi2.country", + "model": "epi.country", "pk": 125 }, { @@ -1004,7 +1004,7 @@ "code": "LR", "name": "Liberia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 126 }, { @@ -1012,7 +1012,7 @@ "code": "LY", "name": "Libya" }, - "model": "epi2.country", + "model": "epi.country", "pk": 127 }, { @@ -1020,7 +1020,7 @@ "code": "LI", "name": "Liechtenstein" }, - "model": "epi2.country", + "model": "epi.country", "pk": 128 }, { @@ -1028,7 +1028,7 @@ "code": "LT", "name": "Lithuania" }, - "model": "epi2.country", + "model": "epi.country", "pk": 129 }, { @@ -1036,7 +1036,7 @@ "code": "LU", "name": "Luxembourg" }, - "model": "epi2.country", + "model": "epi.country", "pk": 130 }, { @@ -1044,7 +1044,7 @@ "code": "MO", "name": "Macao" }, - "model": "epi2.country", + "model": "epi.country", "pk": 131 }, { @@ -1052,7 +1052,7 @@ "code": "MK", "name": "Macedonia, The Former Yugoslav Republic Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 132 }, { @@ -1060,7 +1060,7 @@ "code": "MG", "name": "Madagascar" }, - "model": "epi2.country", + "model": "epi.country", "pk": 133 }, { @@ -1068,7 +1068,7 @@ "code": "MW", "name": "Malawi" }, - "model": "epi2.country", + "model": "epi.country", "pk": 134 }, { @@ -1076,7 +1076,7 @@ "code": "MY", "name": "Malaysia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 135 }, { @@ -1084,7 +1084,7 @@ "code": "MV", "name": "Maldives" }, - "model": "epi2.country", + "model": "epi.country", "pk": 136 }, { @@ -1092,7 +1092,7 @@ "code": "ML", "name": "Mali" }, - "model": "epi2.country", + "model": "epi.country", "pk": 137 }, { @@ -1100,7 +1100,7 @@ "code": "MT", "name": "Malta" }, - "model": "epi2.country", + "model": "epi.country", "pk": 138 }, { @@ -1108,7 +1108,7 @@ "code": "MH", "name": "Marshall Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 139 }, { @@ -1116,7 +1116,7 @@ "code": "MQ", "name": "Martinique" }, - "model": "epi2.country", + "model": "epi.country", "pk": 140 }, { @@ -1124,7 +1124,7 @@ "code": "MR", "name": "Mauritania" }, - "model": "epi2.country", + "model": "epi.country", "pk": 141 }, { @@ -1132,7 +1132,7 @@ "code": "MU", "name": "Mauritius" }, - "model": "epi2.country", + "model": "epi.country", "pk": 142 }, { @@ -1140,7 +1140,7 @@ "code": "YT", "name": "Mayotte" }, - "model": "epi2.country", + "model": "epi.country", "pk": 143 }, { @@ -1148,7 +1148,7 @@ "code": "MX", "name": "Mexico" }, - "model": "epi2.country", + "model": "epi.country", "pk": 144 }, { @@ -1156,7 +1156,7 @@ "code": "FM", "name": "Micronesia, Federated States Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 145 }, { @@ -1164,7 +1164,7 @@ "code": "MD", "name": "Moldova, Republic Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 146 }, { @@ -1172,7 +1172,7 @@ "code": "MC", "name": "Monaco" }, - "model": "epi2.country", + "model": "epi.country", "pk": 147 }, { @@ -1180,7 +1180,7 @@ "code": "MN", "name": "Mongolia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 148 }, { @@ -1188,7 +1188,7 @@ "code": "ME", "name": "Montenegro" }, - "model": "epi2.country", + "model": "epi.country", "pk": 149 }, { @@ -1196,7 +1196,7 @@ "code": "MS", "name": "Montserrat" }, - "model": "epi2.country", + "model": "epi.country", "pk": 150 }, { @@ -1204,7 +1204,7 @@ "code": "MA", "name": "Morocco" }, - "model": "epi2.country", + "model": "epi.country", "pk": 151 }, { @@ -1212,7 +1212,7 @@ "code": "MZ", "name": "Mozambique" }, - "model": "epi2.country", + "model": "epi.country", "pk": 152 }, { @@ -1220,7 +1220,7 @@ "code": "MM", "name": "Myanmar" }, - "model": "epi2.country", + "model": "epi.country", "pk": 153 }, { @@ -1228,7 +1228,7 @@ "code": "NA", "name": "Namibia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 154 }, { @@ -1236,7 +1236,7 @@ "code": "NR", "name": "Nauru" }, - "model": "epi2.country", + "model": "epi.country", "pk": 155 }, { @@ -1244,7 +1244,7 @@ "code": "NP", "name": "Nepal" }, - "model": "epi2.country", + "model": "epi.country", "pk": 156 }, { @@ -1252,7 +1252,7 @@ "code": "NL", "name": "Netherlands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 157 }, { @@ -1260,7 +1260,7 @@ "code": "NC", "name": "New Caledonia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 158 }, { @@ -1268,7 +1268,7 @@ "code": "NZ", "name": "New Zealand" }, - "model": "epi2.country", + "model": "epi.country", "pk": 159 }, { @@ -1276,7 +1276,7 @@ "code": "NI", "name": "Nicaragua" }, - "model": "epi2.country", + "model": "epi.country", "pk": 160 }, { @@ -1284,7 +1284,7 @@ "code": "NE", "name": "Niger" }, - "model": "epi2.country", + "model": "epi.country", "pk": 161 }, { @@ -1292,7 +1292,7 @@ "code": "NG", "name": "Nigeria" }, - "model": "epi2.country", + "model": "epi.country", "pk": 162 }, { @@ -1300,7 +1300,7 @@ "code": "NU", "name": "Niue" }, - "model": "epi2.country", + "model": "epi.country", "pk": 163 }, { @@ -1308,7 +1308,7 @@ "code": "NF", "name": "Norfolk Island" }, - "model": "epi2.country", + "model": "epi.country", "pk": 164 }, { @@ -1316,7 +1316,7 @@ "code": "MP", "name": "Northern Mariana Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 165 }, { @@ -1324,7 +1324,7 @@ "code": "NO", "name": "Norway" }, - "model": "epi2.country", + "model": "epi.country", "pk": 166 }, { @@ -1332,7 +1332,7 @@ "code": "OM", "name": "Oman" }, - "model": "epi2.country", + "model": "epi.country", "pk": 167 }, { @@ -1340,7 +1340,7 @@ "code": "PK", "name": "Pakistan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 168 }, { @@ -1348,7 +1348,7 @@ "code": "PW", "name": "Palau" }, - "model": "epi2.country", + "model": "epi.country", "pk": 169 }, { @@ -1356,7 +1356,7 @@ "code": "PS", "name": "Palestine, State Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 170 }, { @@ -1364,7 +1364,7 @@ "code": "PA", "name": "Panama" }, - "model": "epi2.country", + "model": "epi.country", "pk": 171 }, { @@ -1372,7 +1372,7 @@ "code": "PG", "name": "Papua New Guinea" }, - "model": "epi2.country", + "model": "epi.country", "pk": 172 }, { @@ -1380,7 +1380,7 @@ "code": "PY", "name": "Paraguay" }, - "model": "epi2.country", + "model": "epi.country", "pk": 173 }, { @@ -1388,7 +1388,7 @@ "code": "PE", "name": "Peru" }, - "model": "epi2.country", + "model": "epi.country", "pk": 174 }, { @@ -1396,7 +1396,7 @@ "code": "PH", "name": "Philippines" }, - "model": "epi2.country", + "model": "epi.country", "pk": 175 }, { @@ -1404,7 +1404,7 @@ "code": "PN", "name": "Pitcairn" }, - "model": "epi2.country", + "model": "epi.country", "pk": 176 }, { @@ -1412,7 +1412,7 @@ "code": "PL", "name": "Poland" }, - "model": "epi2.country", + "model": "epi.country", "pk": 177 }, { @@ -1420,7 +1420,7 @@ "code": "PT", "name": "Portugal" }, - "model": "epi2.country", + "model": "epi.country", "pk": 178 }, { @@ -1428,7 +1428,7 @@ "code": "PR", "name": "Puerto Rico" }, - "model": "epi2.country", + "model": "epi.country", "pk": 179 }, { @@ -1436,7 +1436,7 @@ "code": "QA", "name": "Qatar" }, - "model": "epi2.country", + "model": "epi.country", "pk": 180 }, { @@ -1444,7 +1444,7 @@ "code": "RE", "name": "Réunion" }, - "model": "epi2.country", + "model": "epi.country", "pk": 181 }, { @@ -1452,7 +1452,7 @@ "code": "RO", "name": "Romania" }, - "model": "epi2.country", + "model": "epi.country", "pk": 182 }, { @@ -1460,7 +1460,7 @@ "code": "RU", "name": "Russian Federation" }, - "model": "epi2.country", + "model": "epi.country", "pk": 183 }, { @@ -1468,7 +1468,7 @@ "code": "RW", "name": "Rwanda" }, - "model": "epi2.country", + "model": "epi.country", "pk": 184 }, { @@ -1476,7 +1476,7 @@ "code": "BL", "name": "Saint Barthélemy" }, - "model": "epi2.country", + "model": "epi.country", "pk": 185 }, { @@ -1484,7 +1484,7 @@ "code": "SH", "name": "Saint Helena, Ascension And Tristan Da Cunha" }, - "model": "epi2.country", + "model": "epi.country", "pk": 186 }, { @@ -1492,7 +1492,7 @@ "code": "KN", "name": "Saint Kitts And Nevis" }, - "model": "epi2.country", + "model": "epi.country", "pk": 187 }, { @@ -1500,7 +1500,7 @@ "code": "LC", "name": "Saint Lucia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 188 }, { @@ -1508,7 +1508,7 @@ "code": "MF", "name": "Saint Martin (French Part)" }, - "model": "epi2.country", + "model": "epi.country", "pk": 189 }, { @@ -1516,7 +1516,7 @@ "code": "PM", "name": "Saint Pierre And Miquelon" }, - "model": "epi2.country", + "model": "epi.country", "pk": 190 }, { @@ -1524,7 +1524,7 @@ "code": "VC", "name": "Saint Vincent And The Grenadines" }, - "model": "epi2.country", + "model": "epi.country", "pk": 191 }, { @@ -1532,7 +1532,7 @@ "code": "WS", "name": "Samoa" }, - "model": "epi2.country", + "model": "epi.country", "pk": 192 }, { @@ -1540,7 +1540,7 @@ "code": "SM", "name": "San Marino" }, - "model": "epi2.country", + "model": "epi.country", "pk": 193 }, { @@ -1548,7 +1548,7 @@ "code": "ST", "name": "Sao Tome And Principe" }, - "model": "epi2.country", + "model": "epi.country", "pk": 194 }, { @@ -1556,7 +1556,7 @@ "code": "SA", "name": "Saudi Arabia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 195 }, { @@ -1564,7 +1564,7 @@ "code": "SN", "name": "Senegal" }, - "model": "epi2.country", + "model": "epi.country", "pk": 196 }, { @@ -1572,7 +1572,7 @@ "code": "RS", "name": "Serbia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 197 }, { @@ -1580,7 +1580,7 @@ "code": "SC", "name": "Seychelles" }, - "model": "epi2.country", + "model": "epi.country", "pk": 198 }, { @@ -1588,7 +1588,7 @@ "code": "SL", "name": "Sierra Leone" }, - "model": "epi2.country", + "model": "epi.country", "pk": 199 }, { @@ -1596,7 +1596,7 @@ "code": "SG", "name": "Singapore" }, - "model": "epi2.country", + "model": "epi.country", "pk": 200 }, { @@ -1604,7 +1604,7 @@ "code": "SX", "name": "Sint Maarten (Dutch Part)" }, - "model": "epi2.country", + "model": "epi.country", "pk": 201 }, { @@ -1612,7 +1612,7 @@ "code": "SK", "name": "Slovakia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 202 }, { @@ -1620,7 +1620,7 @@ "code": "SI", "name": "Slovenia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 203 }, { @@ -1628,7 +1628,7 @@ "code": "SB", "name": "Solomon Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 204 }, { @@ -1636,7 +1636,7 @@ "code": "SO", "name": "Somalia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 205 }, { @@ -1644,7 +1644,7 @@ "code": "ZA", "name": "South Africa" }, - "model": "epi2.country", + "model": "epi.country", "pk": 206 }, { @@ -1652,7 +1652,7 @@ "code": "GS", "name": "South Georgia And The South Sandwich Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 207 }, { @@ -1660,7 +1660,7 @@ "code": "SS", "name": "South Sudan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 208 }, { @@ -1668,7 +1668,7 @@ "code": "ES", "name": "Spain" }, - "model": "epi2.country", + "model": "epi.country", "pk": 209 }, { @@ -1676,7 +1676,7 @@ "code": "LK", "name": "Sri Lanka" }, - "model": "epi2.country", + "model": "epi.country", "pk": 210 }, { @@ -1684,7 +1684,7 @@ "code": "SD", "name": "Sudan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 211 }, { @@ -1692,7 +1692,7 @@ "code": "SR", "name": "Suriname" }, - "model": "epi2.country", + "model": "epi.country", "pk": 212 }, { @@ -1700,7 +1700,7 @@ "code": "SJ", "name": "Svalbard And Jan Mayen" }, - "model": "epi2.country", + "model": "epi.country", "pk": 213 }, { @@ -1708,7 +1708,7 @@ "code": "SZ", "name": "Swaziland" }, - "model": "epi2.country", + "model": "epi.country", "pk": 214 }, { @@ -1716,7 +1716,7 @@ "code": "SE", "name": "Sweden" }, - "model": "epi2.country", + "model": "epi.country", "pk": 215 }, { @@ -1724,7 +1724,7 @@ "code": "CH", "name": "Switzerland" }, - "model": "epi2.country", + "model": "epi.country", "pk": 216 }, { @@ -1732,7 +1732,7 @@ "code": "SY", "name": "Syrian Arab Republic" }, - "model": "epi2.country", + "model": "epi.country", "pk": 217 }, { @@ -1740,7 +1740,7 @@ "code": "TW", "name": "Taiwan, Province Of China" }, - "model": "epi2.country", + "model": "epi.country", "pk": 218 }, { @@ -1748,7 +1748,7 @@ "code": "TJ", "name": "Tajikistan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 219 }, { @@ -1756,7 +1756,7 @@ "code": "TZ", "name": "Tanzania, United Republic Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 220 }, { @@ -1764,7 +1764,7 @@ "code": "TH", "name": "Thailand" }, - "model": "epi2.country", + "model": "epi.country", "pk": 221 }, { @@ -1772,7 +1772,7 @@ "code": "TL", "name": "Timor-Leste" }, - "model": "epi2.country", + "model": "epi.country", "pk": 222 }, { @@ -1780,7 +1780,7 @@ "code": "TG", "name": "Togo" }, - "model": "epi2.country", + "model": "epi.country", "pk": 223 }, { @@ -1788,7 +1788,7 @@ "code": "TK", "name": "Tokelau" }, - "model": "epi2.country", + "model": "epi.country", "pk": 224 }, { @@ -1796,7 +1796,7 @@ "code": "TO", "name": "Tonga" }, - "model": "epi2.country", + "model": "epi.country", "pk": 225 }, { @@ -1804,7 +1804,7 @@ "code": "TT", "name": "Trinidad And Tobago" }, - "model": "epi2.country", + "model": "epi.country", "pk": 226 }, { @@ -1812,7 +1812,7 @@ "code": "TN", "name": "Tunisia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 227 }, { @@ -1820,7 +1820,7 @@ "code": "TR", "name": "Turkey" }, - "model": "epi2.country", + "model": "epi.country", "pk": 228 }, { @@ -1828,7 +1828,7 @@ "code": "TM", "name": "Turkmenistan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 229 }, { @@ -1836,7 +1836,7 @@ "code": "TC", "name": "Turks And Caicos Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 230 }, { @@ -1844,7 +1844,7 @@ "code": "TV", "name": "Tuvalu" }, - "model": "epi2.country", + "model": "epi.country", "pk": 231 }, { @@ -1852,7 +1852,7 @@ "code": "UG", "name": "Uganda" }, - "model": "epi2.country", + "model": "epi.country", "pk": 232 }, { @@ -1860,7 +1860,7 @@ "code": "UA", "name": "Ukraine" }, - "model": "epi2.country", + "model": "epi.country", "pk": 233 }, { @@ -1868,7 +1868,7 @@ "code": "AE", "name": "United Arab Emirates" }, - "model": "epi2.country", + "model": "epi.country", "pk": 234 }, { @@ -1876,7 +1876,7 @@ "code": "GB", "name": "United Kingdom" }, - "model": "epi2.country", + "model": "epi.country", "pk": 235 }, { @@ -1884,7 +1884,7 @@ "code": "US", "name": "United States" }, - "model": "epi2.country", + "model": "epi.country", "pk": 236 }, { @@ -1892,7 +1892,7 @@ "code": "UM", "name": "United States Minor Outlying Islands" }, - "model": "epi2.country", + "model": "epi.country", "pk": 237 }, { @@ -1900,7 +1900,7 @@ "code": "UY", "name": "Uruguay" }, - "model": "epi2.country", + "model": "epi.country", "pk": 238 }, { @@ -1908,7 +1908,7 @@ "code": "UZ", "name": "Uzbekistan" }, - "model": "epi2.country", + "model": "epi.country", "pk": 239 }, { @@ -1916,7 +1916,7 @@ "code": "VU", "name": "Vanuatu" }, - "model": "epi2.country", + "model": "epi.country", "pk": 240 }, { @@ -1924,7 +1924,7 @@ "code": "VE", "name": "Venezuela, Bolivarian Republic Of" }, - "model": "epi2.country", + "model": "epi.country", "pk": 241 }, { @@ -1932,7 +1932,7 @@ "code": "VN", "name": "Viet Nam" }, - "model": "epi2.country", + "model": "epi.country", "pk": 242 }, { @@ -1940,7 +1940,7 @@ "code": "VG", "name": "Virgin Islands, British" }, - "model": "epi2.country", + "model": "epi.country", "pk": 243 }, { @@ -1948,7 +1948,7 @@ "code": "VI", "name": "Virgin Islands, U.S." }, - "model": "epi2.country", + "model": "epi.country", "pk": 244 }, { @@ -1956,7 +1956,7 @@ "code": "WF", "name": "Wallis And Futuna" }, - "model": "epi2.country", + "model": "epi.country", "pk": 245 }, { @@ -1964,7 +1964,7 @@ "code": "EH", "name": "Western Sahara" }, - "model": "epi2.country", + "model": "epi.country", "pk": 246 }, { @@ -1972,7 +1972,7 @@ "code": "YE", "name": "Yemen" }, - "model": "epi2.country", + "model": "epi.country", "pk": 247 }, { @@ -1980,7 +1980,7 @@ "code": "ZM", "name": "Zambia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 248 }, { @@ -1988,7 +1988,7 @@ "code": "ZW", "name": "Zimbabwe" }, - "model": "epi2.country", + "model": "epi.country", "pk": 249 }, { @@ -1996,7 +1996,7 @@ "code": "", "name": "Unknown/Unspecified" }, - "model": "epi2.country", + "model": "epi.country", "pk": 250 }, { @@ -2004,7 +2004,7 @@ "code": "", "name": "Socialist Federal Republic of Yugoslavia" }, - "model": "epi2.country", + "model": "epi.country", "pk": 251 } ] diff --git a/project/epi2/fixtures/ethnicity.json b/project/epi/fixtures/ethnicity.json similarity index 85% rename from project/epi2/fixtures/ethnicity.json rename to project/epi/fixtures/ethnicity.json index 01f48e77..9f02035f 100644 --- a/project/epi2/fixtures/ethnicity.json +++ b/project/epi/fixtures/ethnicity.json @@ -5,7 +5,7 @@ "name": "American Indian or Alaskan Native", "created": "2013-02-16T14:30:00.001Z" }, - "model": "epi2.ethnicity", + "model": "epi.ethnicity", "pk": 1 }, { @@ -14,7 +14,7 @@ "name": "Asian", "created": "2013-02-16T14:30:00.001Z" }, - "model": "epi2.ethnicity", + "model": "epi.ethnicity", "pk": 2 }, { @@ -23,7 +23,7 @@ "name": "Black or African American", "created": "2013-02-16T14:30:00.001Z" }, - "model": "epi2.ethnicity", + "model": "epi.ethnicity", "pk": 3 }, { @@ -32,7 +32,7 @@ "name": "Hispanic/Latino", "created": "2013-02-16T14:30:00.001Z" }, - "model": "epi2.ethnicity", + "model": "epi.ethnicity", "pk": 4 }, { @@ -41,7 +41,7 @@ "name": "Native American of Other Pacific Islander", "created": "2013-02-16T14:30:00.001Z" }, - "model": "epi2.ethnicity", + "model": "epi.ethnicity", "pk": 5 }, { @@ -50,7 +50,7 @@ "name": "Two or More Races", "created": "2013-02-16T14:30:00.001Z" }, - "model": "epi2.ethnicity", + "model": "epi.ethnicity", "pk": 6 }, { @@ -59,7 +59,7 @@ "name": "White", "created": "2013-02-16T14:30:00.001Z" }, - "model": "epi2.ethnicity", + "model": "epi.ethnicity", "pk": 7 }, { @@ -68,7 +68,7 @@ "name": "Unknown/Unspecified", "created": "2013-02-16T14:30:00.001Z" }, - "model": "epi2.ethnicity", + "model": "epi.ethnicity", "pk": 8 }, { @@ -77,7 +77,7 @@ "name": "Other", "created": "2013-02-16T14:30:00.001Z" }, - "model": "epi2.ethnicity", + "model": "epi.ethnicity", "pk": 9 } ] diff --git a/project/epi2/fixtures/resultmetric.json b/project/epi/fixtures/resultmetric.json similarity index 87% rename from project/epi2/fixtures/resultmetric.json rename to project/epi/fixtures/resultmetric.json index 8245d797..10f38447 100644 --- a/project/epi2/fixtures/resultmetric.json +++ b/project/epi/fixtures/resultmetric.json @@ -8,7 +8,7 @@ "order": 0, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 1 }, { @@ -20,7 +20,7 @@ "order": 1, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 2 }, { @@ -32,7 +32,7 @@ "order": 2, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 3 }, { @@ -44,7 +44,7 @@ "order": 3, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 4 }, { @@ -56,7 +56,7 @@ "order": 4, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 5 }, { @@ -68,7 +68,7 @@ "order": 5, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 6 }, { @@ -80,7 +80,7 @@ "order": 6, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 7 }, { @@ -92,7 +92,7 @@ "order": 7, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 8 }, { @@ -104,7 +104,7 @@ "order": 8, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 9 }, { @@ -116,7 +116,7 @@ "order": 9, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 10 }, { @@ -128,7 +128,7 @@ "order": 10, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 11 }, { @@ -140,7 +140,7 @@ "order": 11, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 12 }, { @@ -152,7 +152,7 @@ "order": 12, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 13 }, { @@ -164,7 +164,7 @@ "order": 13, "reference_value": null }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 14 }, { @@ -176,7 +176,7 @@ "order": 14, "reference_value": 0 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 15 }, { @@ -188,7 +188,7 @@ "order": 15, "reference_value": null }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 16 }, { @@ -200,7 +200,7 @@ "order": 16, "reference_value": null }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 17 }, { @@ -212,7 +212,7 @@ "order": 50, "reference_value": null }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 18 }, { @@ -224,7 +224,7 @@ "order": 17, "reference_value": null }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 19 }, { @@ -236,7 +236,7 @@ "order": 18, "reference_value": null }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 20 }, { @@ -248,7 +248,7 @@ "order": 19, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 21 }, { @@ -260,7 +260,7 @@ "order": 20, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 22 }, { @@ -272,7 +272,7 @@ "order": 21, "reference_value": 1 }, - "model": "epi2.ResultMetric", + "model": "epi.ResultMetric", "pk": 23 } ] diff --git a/project/epi/forms.py b/project/epi/forms.py index 8ed34674..78f0b5f9 100644 --- a/project/epi/forms.py +++ b/project/epi/forms.py @@ -1,51 +1,124 @@ -from copy import copy - from django import forms +from django.db.models import Q +from django.core.urlresolvers import reverse from django.forms.models import BaseModelFormSet, modelformset_factory -from django.forms.widgets import CheckboxInput, TextInput -from collections import OrderedDict +from django.utils.functional import curry from crispy_forms import layout as cfl -from crispy_forms import bootstrap as cfb from selectable import forms as selectable from assessment.lookups import BaseEndpointLookup, EffectTagLookup -from utils.forms import FormsetWithIgnoredFields, anyNull, BaseFormHelper +from utils.forms import BaseFormHelper, CopyAsNewSelectorForm from . import models, lookups +class CriteriaForm(forms.ModelForm): + + CREATE_LEGEND = u"Create new study criteria" + + CREATE_HELP_TEXT = u""" + Create a epidemiology study criteria. Study criteria can be applied to + study populations as inclusion criteria, exclusion criteria, or + confounding criteria. They are assessment-specific. Please take care + not to duplicate existing factors.""" + + class Meta: + model = models.Criteria + exclude = ('assessment', ) + + def __init__(self, *args, **kwargs): + assessment = kwargs.pop('parent', None) + super(CriteriaForm, self).__init__(*args, **kwargs) + self.fields['description'].widget = selectable.AutoCompleteWidget( + lookup_class=lookups.CriteriaLookup, + allow_new=True) + self.instance.assessment = assessment + for fld in self.fields.keys(): + self.fields[fld].widget.attrs['class'] = 'span12' + self.fields['description'].widget.update_query_parameters( + {'related': self.instance.assessment.id}) + self.helper = self.setHelper() + + def clean(self): + super(CriteriaForm, self).clean() + # assessment-description unique-together constraint check must be + # added since assessment is not included on form + pk = getattr(self.instance, 'pk', None) + crits = models.Criteria.objects \ + .filter(assessment=self.instance.assessment, + description=self.cleaned_data.get('description', "")) \ + .exclude(pk=pk) + + if crits.count() > 0: + self.add_error("description", "Must be unique for assessment") + + return self.cleaned_data + + def setHelper(self): + for fld in self.fields.keys(): + widget = self.fields[fld].widget + if type(widget) != forms.CheckboxInput: + widget.attrs['class'] = 'span12' + + inputs = { + "legend_text": self.CREATE_LEGEND, + "help_text": self.CREATE_HELP_TEXT, + "form_actions": [ + cfl.Submit('save', 'Save'), + cfl.HTML("""Cancel"""), + ] + } + + helper = BaseFormHelper(self, **inputs) + helper.form_class = None + return helper + + class StudyPopulationForm(forms.ModelForm): + CREATE_LEGEND = u"Create new study-population" + + CREATE_HELP_TEXT = u""" + Create a new study population. Each study-population is a + associated with an epidemiology study. There may be + multiple study populations with a single study, + though this is typically unlikely.""" + + UPDATE_HELP_TEXT = u"Update an existing study-population." + + CRITERION_FIELDS = [ + "inclusion_criteria", + "exclusion_criteria", + "confounding_criteria" + ] + + CRITERION_TYPE_CW = { + "inclusion_criteria": "I", + "exclusion_criteria": "E", + "confounding_criteria": "C", + } + inclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.StudyCriteriaLookup, + lookup_class=lookups.CriteriaLookup, required=False) exclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.StudyCriteriaLookup, + lookup_class=lookups.CriteriaLookup, required=False) confounding_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.StudyCriteriaLookup, + lookup_class=lookups.CriteriaLookup, required=False) class Meta: - fields = ('name', 'design', - 'country', 'region', 'state', - 'sex', 'ethnicity', 'fraction_male', 'fraction_male_calculated', - 'n', 'starting_n', - 'age_mean', 'age_mean_type', 'age_calculated', - 'age_description', 'age_sd', 'age_sd_type', - 'age_lower', 'age_lower_type', 'age_upper', 'age_upper_type', - 'inclusion_criteria', - 'exclusion_criteria', - 'confounding_criteria') model = models.StudyPopulation - exclude = ('study', ) + exclude = ('study', 'criteria') def __init__(self, *args, **kwargs): study = kwargs.pop('parent', None) super(StudyPopulationForm, self).__init__(*args, **kwargs) + self.fields['comments'] = self.fields.pop('comments') # move to end self.fields['region'].widget = selectable.AutoCompleteWidget( lookup_class=lookups.RegionLookup, allow_new=True) @@ -54,236 +127,116 @@ def __init__(self, *args, **kwargs): allow_new=True) if study: self.instance.study = study + + for fld in self.CRITERION_FIELDS: + self.fields[fld].widget.update_query_parameters( + {'related': self.instance.study.assessment_id}) + if self.instance.id: + self.fields[fld].initial = getattr(self.instance, fld) + self.helper = self.setHelper() + def save_criteria(self): + """ + StudyPopulationCriteria is a through model; requires the criteria type. + We save the m2m relations using the additional information from the + field-name + """ + self.instance.spcriteria.all().delete() + objs = [] + for field in self.CRITERION_FIELDS: + for criteria in self.cleaned_data.get(field, []): + objs.append(models.StudyPopulationCriteria( + criteria=criteria, + study_population=self.instance, + criteria_type=self.CRITERION_TYPE_CW[field])) + models.StudyPopulationCriteria.objects.bulk_create(objs) + + def save(self, commit=True): + instance = super(StudyPopulationForm, self).save(commit) + if commit: + self.save_criteria() + return instance + def setHelper(self): - for fld in ["inclusion_criteria", "exclusion_criteria", "confounding_criteria"]: - self.fields[fld].widget.update_query_parameters({'related': self.instance.study.assessment_id}) for fld in self.fields.keys(): widget = self.fields[fld].widget if type(widget) != forms.CheckboxInput: - if fld in ['inclusion_criteria', 'exclusion_criteria', 'confounding_criteria']: - widget.attrs['class'] = 'span11' + if fld in self.CRITERION_FIELDS: + widget.attrs['class'] = 'span10' else: widget.attrs['class'] = 'span12' + if type(widget) == forms.Textarea: + widget.attrs['rows'] = 3 if self.instance.id: inputs = { "legend_text": u"Update {}".format(self.instance), - "help_text": u"Update an existing study-population.", + "help_text": self.UPDATE_HELP_TEXT, "cancel_url": self.instance.get_absolute_url() } else: inputs = { - "legend_text": u"Create new study-population", - "help_text": u""" - Create a new study population. Each study-population is a - associated with an epidemiology study. There may be - multiple study populations with a single study, - though this is typically unlikely.""", + "legend_text": self.CREATE_LEGEND, + "help_text": self.CREATE_HELP_TEXT, "cancel_url": self.instance.study.get_absolute_url() } helper = BaseFormHelper(self, **inputs) helper.form_class = None helper.add_fluid_row('name', 2, "span6") + helper.add_fluid_row('age_profile', 2, "span6") helper.add_fluid_row('country', 3, "span4") - helper.add_fluid_row('sex', 4, "span3") - helper.add_fluid_row('n', 2, "span6") - helper.add_fluid_row('age_mean', 3, "span4") - helper.add_fluid_row('age_description', 3, "span4") - helper.add_fluid_row('age_lower', 4, "span3") - - url = '{% url "epi:studycriteria_create" assessment.pk %}' - helper.add_adder("addIncCriteria", "Add new criteria", url) - helper.add_adder("addExcCriteria", "Add new criteria", url) - helper.add_adder("addConCriteria", "Add new criteria", url) - - return helper - - -class ExposureForm(forms.ModelForm): - - class Meta: - model = models.Exposure - exclude = ('study_population', ) - - def __init__(self, *args, **kwargs): - study_population = kwargs.pop('parent', None) - super(ExposureForm, self).__init__(*args, **kwargs) - if study_population: - self.instance.study_population = study_population - self.helper = self.setHelper() - - def setHelper(self): - self.fields['exposure_form_definition'].widget = TextInput() - - for fld in ('metric', 'metric_description', 'analytical_method', - 'control_description'): - self.fields[fld].widget.attrs['rows'] = 3 - - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if type(widget) != forms.CheckboxInput: - widget.attrs['class'] = 'span12' + helper.add_fluid_row('eligible_n', 3, "span4") + helper.add_fluid_row('inclusion_criteria', 3, "span4") - if self.instance.id: - inputs = { - "legend_text": u"Update {}".format(self.instance), - "help_text": u"Update an existing exposure.", - "cancel_url": self.instance.get_absolute_url() - } - else: - inputs = { - "legend_text": u"Create new exposure", - "help_text": u""" - Create a new exposure. An exposure is a description of - the metric used to evaluate an individual's exposure. - Each exposure is associated with a particular study - population, and there may be multiple exposure - metrics for that population.""", - "cancel_url": self.instance.study_population.get_absolute_url() - } + url = reverse('epi:studycriteria_create', + kwargs={'pk': self.instance.study.assessment.pk}) + helper.addBtnLayout(helper.layout[6], 0, url, "Create criteria", "span4") + helper.addBtnLayout(helper.layout[6], 1, url, "Create criteria", "span4") + helper.addBtnLayout(helper.layout[6], 2, url, "Create criteria", "span4") - helper = BaseFormHelper(self, **inputs) - helper.form_class = None - helper.add_header("inhalation", "Known exposure routes") - helper.add_header("metric", "Additional exposure information") - helper.add_fluid_row('inhalation', 6, "span2") - helper.add_fluid_row('metric', 3, "span4") - helper.add_fluid_row('analytical_method', 2, "span6") return helper -class ExposureGroupForm(forms.ModelForm): - - class Meta: - fields = ('description', - 'exposure_numeric', - 'comparative_name', - 'sex', - 'ethnicity', - 'fraction_male', - 'fraction_male_calculated', - 'n', - 'starting_n', - 'exposure_n', - 'age_mean', - 'age_mean_type', - 'age_calculated', - 'age_description', - 'age_sd', - 'age_sd_type', - 'age_lower', - 'age_lower_type', - 'age_upper', - 'age_upper_type') - model = models.ExposureGroup - - def __init__(self, *args, **kwargs): - super(ExposureGroupForm, self).__init__(*args, **kwargs) - for key in ('exposure_numeric', 'fraction_male', 'fraction_male_calculated', - 'n', 'starting_n', 'exposure_n', 'age_mean', 'age_mean_type', - 'age_calculated', 'age_description', 'age_sd', - 'age_sd_type', 'age_lower', 'age_lower_type', 'age_upper', - 'age_upper_type'): - self.fields[key].widget.attrs['class'] = 'input-small' - - for key in ('description', 'sex', 'comparative_name'): - self.fields[key].widget.attrs['class'] = 'input-medium' - - -class BaseEGFormSet(BaseModelFormSet): - - def __init__(self, *args, **kwargs): - self.exposure = kwargs.pop('exposure', None) - super(BaseEGFormSet, self).__init__(*args, **kwargs) - - def clean(self): - super(BaseEGFormSet, self).clean() - # check that all descriptions are unique - descriptions = [] - for form in self.forms: - if form.is_valid() and form.clean(): - description = form.cleaned_data['description'] - if description in descriptions: - raise forms.ValidationError("Exposure-groups must have unique descriptions.") - descriptions.append(description) - - # update new forms with exposure_group_id, first get max ID - max_exposure_group_id = -1 - for form in self.forms: - if form.instance.exposure_group_id > max_exposure_group_id: - max_exposure_group_id = form.instance.exposure_group_id - - # now increment - for form in self.extra_forms: - if form.is_valid() and form.clean(): - if self.exposure: - form.instance.exposure = self.exposure - max_exposure_group_id += 1 - form.instance.exposure_group_id = max_exposure_group_id +class StudyPopulationSelectorForm(CopyAsNewSelectorForm): + label = 'Study Population' + lookup_class = lookups.StudyPopulationByStudyLookup - # check that there is at least one exposure-group - count = len(filter(lambda f: f.is_valid() and f.clean(), self.forms)) - if count < 1: - raise forms.ValidationError("At least one-exposure group is required.") - - def rebuild_exposure_group_id(self): - # the exposure-group-id must start at zero and continue sequentially; - # this method rebuilds the exposure-groups-ides in increasing order - # properly. Note: cannot add into clean() because has_changed flag - # may not have been set. - deleted_ids = sorted([ - form.instance.exposure_group_id - for form in self. - deleted_forms], reverse=True) - for deleted_id in deleted_ids: - for form in self.forms: - if ((form not in self.deleted_forms) and - (form.instance.exposure_group_id > deleted_id)): - form.instance.exposure_group_id -= 1 - form.instance.save() - - -EGFormSet = modelformset_factory( - models.ExposureGroup, - form=ExposureGroupForm, - formset=BaseEGFormSet, - can_delete=True, - extra=1) -BlankEGFormSet = modelformset_factory( - models.ExposureGroup, - form=ExposureGroupForm, - formset=BaseEGFormSet, - extra=1) +class AdjustmentFactorForm(forms.ModelForm): + CREATE_LEGEND = u"Create new adjustment factor" -class FactorForm(forms.ModelForm): + CREATE_HELP_TEXT = u""" + Create a new adjustment factor. Adjustment factors can be applied to + outcomes as applied or considered factors. + They are assessment-specific. + Please take care not to duplicate existing factors.""" class Meta: - model = models.Factor + model = models.AdjustmentFactor exclude = ('assessment', ) def __init__(self, *args, **kwargs): assessment = kwargs.pop('parent', None) - super(FactorForm, self).__init__(*args, **kwargs) + super(AdjustmentFactorForm, self).__init__(*args, **kwargs) self.fields['description'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.FactorLookup, + lookup_class=lookups.AdjustmentFactorLookup, allow_new=True) self.instance.assessment = assessment for fld in self.fields.keys(): self.fields[fld].widget.attrs['class'] = 'span12' self.fields['description'].widget.update_query_parameters( {'related': self.instance.assessment.id}) + self.helper = self.setHelper() def clean(self): - super(FactorForm, self).clean() + super(AdjustmentFactorForm, self).clean() # assessment-description unique-together constraint check must be # added since assessment is not included on form pk = getattr(self.instance, 'pk', None) - crits = models.Factor.objects \ + crits = models.AdjustmentFactor.objects \ .filter(assessment=self.instance.assessment, description=self.cleaned_data.get('description', "")) \ .exclude(pk=pk) @@ -293,109 +246,135 @@ def clean(self): return self.cleaned_data + def setHelper(self): + for fld in self.fields.keys(): + widget = self.fields[fld].widget + if type(widget) != forms.CheckboxInput: + widget.attrs['class'] = 'span12' + + inputs = { + "legend_text": self.CREATE_LEGEND, + "help_text": self.CREATE_HELP_TEXT, + "form_actions": [ + cfl.Submit('save', 'Save'), + cfl.HTML("""Cancel"""), + ] + } + + helper = BaseFormHelper(self, **inputs) + helper.form_class = None + return helper + -class StudyCriteriaForm(forms.ModelForm): +class ExposureForm(forms.ModelForm): + + HELP_TEXT_CREATE = """Create a new exposure.""" + HELP_TEXT_UPDATE = """Update an existing exposure.""" class Meta: - model = models.StudyCriteria - exclude = ('assessment', ) + model = models.Exposure + exclude = ('study_population', ) def __init__(self, *args, **kwargs): - assessment = kwargs.pop('parent', None) - super(StudyCriteriaForm, self).__init__(*args, **kwargs) - self.fields['description'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.StudyCriteriaLookup, - allow_new=True) - self.instance.assessment = assessment + study_population = kwargs.pop('parent', None) + super(ExposureForm, self).__init__(*args, **kwargs) + if study_population: + self.instance.study_population = study_population + self.helper = self.setHelper() + + def setHelper(self): for fld in self.fields.keys(): - self.fields[fld].widget.attrs['class'] = 'span12' - self.fields['description'].widget.update_query_parameters( - {'related': self.instance.assessment.id}) + widget = self.fields[fld].widget + if type(widget) != forms.CheckboxInput: + if fld in ["metric_units"]: + widget.attrs['class'] = 'span10' + else: + widget.attrs['class'] = 'span12' - def clean(self): - super(StudyCriteriaForm, self).clean() - # assessment-description unique-together constraint check must be - # added since assessment is not included on form - pk = getattr(self.instance, 'pk', None) - crits = models.StudyCriteria.objects \ - .filter(assessment=self.instance.assessment, - description=self.cleaned_data.get('description', "")) \ - .exclude(pk=pk) + if type(widget) == forms.Textarea: + widget.attrs['rows'] = 3 - if crits.count() > 0: - self.add_error("description", "Must be unique for assessment") + if self.instance.id: + inputs = { + "legend_text": u"Update {}".format(self.instance), + "help_text": self.HELP_TEXT_UPDATE, + "cancel_url": self.instance.get_absolute_url() + } + else: + inputs = { + "legend_text": u"Create new exposure", + "help_text": self.HELP_TEXT_CREATE, + "cancel_url": self.instance.study_population.get_absolute_url() + } - return self.cleaned_data + helper = BaseFormHelper(self, **inputs) + helper.form_class = None + helper.add_fluid_row('inhalation', 6, "span2") + helper.add_fluid_row('measured', 3, "span4") + helper.add_fluid_row('metric_description', 3, "span4") + helper.add_fluid_row('duration', 2, "span6") + url = reverse( + 'assessment:dose_units_create', + kwargs={'pk': self.instance.study_population.study.assessment_id} + ) + helper.addBtnLayout(helper.layout[4], 2, url, "Create units", "span4") -class AssessedOutcomeForm(forms.ModelForm): + return helper - HELP_TEXT_CREATE = """Create a new assessed outcome. An assessed - outcome is an response measured in an epidemiological study, - associated with an exposure-metric. The overall assessed outcome is - described, and then quantitative differences in response based on - different exposure-metric groups is detailed below. - """ - HELP_TEXT_UPDATE = """Create a new assessed outcome. An assessed + +class ExposureSelectorForm(CopyAsNewSelectorForm): + label = 'Exposure' + lookup_class = lookups.ExposureByStudyPopulationLookup + + +class OutcomeForm(forms.ModelForm): + + HELP_TEXT_CREATE = """Create a new outcome. An outcome is an response measured in an epidemiological study, - associated with an exposure-metric. The overall assessed outcome is + associated with an exposure-metric. The overall outcome is described, and then quantitative differences in response based on different exposure-metric groups is detailed below. """ - - adjustment_factors = selectable.AutoCompleteSelectMultipleField( - help_text="All factors which were included in final model", - lookup_class=lookups.FactorLookup, - required=False) - - confounders_considered = selectable.AutoCompleteSelectMultipleField( - label="Adjustment factors considered", - help_text="All factors which were examined (including those which were included in final model)", - lookup_class=lookups.FactorLookup, - required=False) + HELP_TEXT_UPDATE = """Update an existing outcome.""" class Meta: - model = models.AssessedOutcome - exclude = ('assessment', 'exposure') + model = models.Outcome + exclude = ('assessment', 'study_population') def __init__(self, *args, **kwargs): assessment = kwargs.pop('assessment', None) - exposure = kwargs.pop('parent', None) - super(AssessedOutcomeForm, self).__init__(*args, **kwargs) + study_population = kwargs.pop('parent', None) + super(OutcomeForm, self).__init__(*args, **kwargs) self.fields['name'].widget = selectable.AutoCompleteWidget( lookup_class=BaseEndpointLookup, allow_new=True) + self.fields['system'].widget = selectable.AutoCompleteWidget( + lookup_class=lookups.SystemLookup, + allow_new=True) + self.fields['effect'].widget = selectable.AutoCompleteWidget( + lookup_class=lookups.EffectLookup, + allow_new=True) self.fields['effects'].widget = selectable.AutoCompleteSelectMultipleWidget( lookup_class=EffectTagLookup) self.fields['effects'].help_text = 'Tags used to help categorize effect description.' if assessment: self.instance.assessment = assessment - if exposure: - self.instance.exposure = exposure - - self.fields['adjustment_factors'].widget.update_query_parameters( - {'related': self.instance.assessment.id}) - self.fields['confounders_considered'].widget.update_query_parameters( - {'related': self.instance.assessment.id}) - - self.fields['main_finding'].queryset = self.fields['main_finding']\ - .queryset.filter(exposure=self.instance.exposure) + if study_population: + self.instance.study_population = study_population self.helper = self.setHelper() def setHelper(self): - for fld in ('diagnostic_description', 'summary', 'prevalence_incidence', - 'statistical_power_details', 'dose_response_details', - 'statistical_metric_description'): - self.fields[fld].widget.attrs['rows'] = 3 - for fld in self.fields.keys(): widget = self.fields[fld].widget if type(widget) != forms.CheckboxInput: - if fld in ["adjustment_factors", "confounders_considered", "effects"]: - widget.attrs['class'] = 'span11' + if fld in ["effects"]: + widget.attrs['class'] = 'span10' else: widget.attrs['class'] = 'span12' + if type(widget) == forms.Textarea: + widget.attrs['rows'] = 3 if self.instance.id: inputs = { @@ -405,319 +384,413 @@ def setHelper(self): } else: inputs = { - "legend_text": u"Create new exposure", + "legend_text": u"Create new outcome", "help_text": self.HELP_TEXT_CREATE, - "cancel_url": self.instance.exposure.get_absolute_url() + "cancel_url": self.instance.study_population.get_absolute_url() } helper = BaseFormHelper(self, **inputs) helper.form_class = None - helper.add_fluid_row('effects', 2, "span6") + helper.add_fluid_row('name', 4, "span3") helper.add_fluid_row('diagnostic', 2, "span6") - helper.add_fluid_row('summary', 2, "span6") - helper.add_fluid_row('adjustment_factors', 2, "span6") - helper.add_fluid_row('dose_response', 2, "span6") - helper.add_fluid_row('statistical_power', 2, "span6") - helper.add_fluid_row('main_finding', 2, "span6") - helper.add_fluid_row('statistical_metric', 2, "span6") - url = "{% url 'assessment:effect_tag_create' assessment.pk %}" - helper.add_adder("addEffectTags", "Add new effect tag", url) - - url = "{% url 'epi:factor_create' assessment.pk %}" - helper.add_adder("addAdj", "Add new adjustment factor", url) - helper.add_adder("addAdjCons", "Add new adjustment factor", url) + url = reverse( + 'assessment:effect_tag_create', + kwargs={'pk': self.instance.assessment.pk} + ) + helper.addBtnLayout(helper.layout[2], 1, url, "Add new effect tag", "span3") return helper -class AOGForm(forms.ModelForm): +class OutcomeSelectorForm(CopyAsNewSelectorForm): + label = 'Outcome' + lookup_class = lookups.OutcomeByStudyPopulationLookup + + +class ComparisonSet(forms.ModelForm): + + HELP_TEXT_CREATE = """Create a new comparison set. Each group is a + collection of people, and all groups in this collection are + comparable to one-another. For example, you may a new comparison set + which contains two groups: cases and controls. Alternatively, for + cohort-based studies, you may create a new comparison set with four + different groups, one for each quartile of exposure based on exposure + measurements. + """ + HELP_TEXT_UPDATE = """Update an existing comparison set.""" class Meta: - fields = ('exposure_group', 'n', 'estimate', 'se', 'ci_units', - 'lower_ci', 'upper_ci', 'p_value', 'p_value_qualifier') - exclude = ('assessed_outcome', ) - model = models.AssessedOutcomeGroup + model = models.ComparisonSet + exclude = ('study_population', 'outcome') def __init__(self, *args, **kwargs): - super(AOGForm, self).__init__(*args, **kwargs) - instance = getattr(self, 'instance', None) - self.fields['exposure_group'].widget.attrs['readOnly'] = True - self.fields['exposure_group'].widget.attrs['class'] = 'eg_fields' - for key in ('n', 'estimate', 'se', 'ci_units', 'lower_ci', - 'upper_ci', 'p_value', 'p_value_qualifier'): - self.fields[key].widget.attrs['class'] = 'input-small' - - def clean_exposure_group(self): - instance = getattr(self, 'instance', None) - if instance and instance.pk: - return instance.exposure_group + self.parent = kwargs.pop('parent', None) + super(ComparisonSet, self).__init__(*args, **kwargs) + if self.parent: + if type(self.parent) == models.StudyPopulation: + self.instance.study_population = self.parent + elif type(self.parent) == models.Outcome: + self.instance.outcome = self.parent + + filters = {} + if self.instance.study_population: + filters["study_population"] = self.instance.study_population else: - return self.cleaned_data['exposure_group'] + filters["study_population"] = self.instance.outcome.study_population + self.fields["exposure"].queryset = self.fields["exposure"].queryset.filter(**filters) + self.helper = self.setHelper() -class BaseAOGFormSet(BaseModelFormSet): - pass + def setHelper(self): + for fld in self.fields.keys(): + widget = self.fields[fld].widget + if type(widget) != forms.CheckboxInput: + widget.attrs['class'] = 'span12' + if type(widget) == forms.Textarea: + widget.attrs['rows'] = 3 + + if self.instance.id: + if self.instance.outcome: + url = self.instance.outcome.get_absolute_url() + else: + url = self.instance.study_population.get_absolute_url() + inputs = { + "legend_text": u"Update {}".format(self.instance), + "help_text": self.HELP_TEXT_UPDATE, + "cancel_url": url + } + else: + inputs = { + "legend_text": u"Create new comparison set", + "help_text": self.HELP_TEXT_CREATE, + "cancel_url": self.parent.get_absolute_url() + } + helper = BaseFormHelper(self, **inputs) + helper.form_class = None + return helper -AOGFormSet = modelformset_factory( - models.AssessedOutcomeGroup, - form=AOGForm, - formset=BaseAOGFormSet, - extra=0) +class ComparisonSetByStudyPopulationSelectorForm(CopyAsNewSelectorForm): + label = 'Comparison set' + lookup_class = lookups.ComparisonSetByStudyPopulationLookup -class MetaProtocolForm(forms.ModelForm): - inclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.StudyCriteriaLookup, - required=False) +class ComparisonSetByOutcomeSelectorForm(CopyAsNewSelectorForm): + label = 'Comparison set' + lookup_class = lookups.ComparisonSetByOutcomeLookup - exclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.StudyCriteriaLookup, - required=False) + +class GroupForm(forms.ModelForm): class Meta: - model = models.MetaProtocol - exclude = ('study', ) + model = models.Group + exclude = ('comparison_set', 'group_id') + + +class SingleGroupForm(GroupForm): + + HELP_TEXT_UPDATE = """Update an existing group and group descriptions.""" def __init__(self, *args, **kwargs): - parent = kwargs.pop('parent', None) - super(MetaProtocolForm, self).__init__(*args, **kwargs) + super(SingleGroupForm, self).__init__(*args, **kwargs) + self.helper = self.setHelper() + + def setHelper(self): for fld in self.fields.keys(): - if fld in ('lit_search_notes', 'notes'): - self.fields[fld].widget.attrs['rows'] = 3 widget = self.fields[fld].widget - if type(widget) != CheckboxInput: + if type(widget) != forms.CheckboxInput: widget.attrs['class'] = 'span12' - if parent: - self.instance.study = parent - self.fields['inclusion_criteria'].widget.update_query_parameters( - {'related': self.instance.study.assessment_id}) - self.fields['exclusion_criteria'].widget.update_query_parameters( - {'related': self.instance.study.assessment_id}) + if type(widget) == forms.Textarea: + widget.attrs['rows'] = 3 + + inputs = { + "legend_text": u"Update {}".format(self.instance), + "help_text": self.HELP_TEXT_UPDATE, + "cancel_url": self.instance.get_absolute_url() + } + + helper = BaseFormHelper(self, **inputs) + helper.form_class = None + helper.add_fluid_row('name', 3, "span4") + helper.add_fluid_row('sex', 2, "span6") + helper.add_fluid_row('eligible_n', 3, "span4") + return helper -class MetaResultForm(forms.ModelForm): +class BaseGroupFormset(BaseModelFormSet): - adjustment_factors = selectable.AutoCompleteSelectMultipleField( - help_text="All factors which were included in final model", - lookup_class=lookups.FactorLookup, - required=False) + def clean(self): + super(BaseGroupFormset, self).clean() - class Meta: - model = models.MetaResult - exclude = ('protocol', ) + # check that there is at least one exposure-group + count = len(filter(lambda f: f.is_valid() and f.clean(), self.forms)) + if count < 1: + raise forms.ValidationError("At least one group is required.") - def __init__(self, *args, **kwargs): - parent = kwargs.pop('parent', None) - assessment_id = kwargs.pop('assessment_id') - super(MetaResultForm, self).__init__(*args, **kwargs) - self.fields['health_outcome'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.MetaResultHealthOutcomeLookup, - allow_new=True) +GroupFormset = modelformset_factory( + models.Group, + form=GroupForm, + formset=BaseGroupFormset, + can_delete=True, + extra=0) - self.fields['exposure_name'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.MetaResultExposureNameLookup, - allow_new=True) - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if fld in ('health_outcome_notes', 'statistical_notes', - 'notes', 'exposure_details'): - self.fields[fld].widget.attrs['rows'] = 3 - if type(widget) != CheckboxInput: - widget.attrs['class'] = 'span12' - if parent: - self.instance.protocol = parent +BlankGroupFormset = modelformset_factory( + models.Group, + form=GroupForm, + formset=BaseGroupFormset, + can_delete=False, + extra=1) + + +class GroupNumericalDescriptionsForm(forms.ModelForm): + + class Meta: + model = models.GroupNumericalDescriptions + exclude = ('group', ) + + +class BaseGroupNumericalDescriptionsFormset(BaseModelFormSet): + pass - self.fields['adjustment_factors'].widget.update_query_parameters( - {'related': assessment_id}) - self.fields['health_outcome'].widget.update_query_parameters( - {'related': assessment_id}) - self.fields['exposure_name'].widget.update_query_parameters( - {'related': assessment_id}) +GroupNumericalDescriptionsFormset = modelformset_factory( + models.GroupNumericalDescriptions, + form=GroupNumericalDescriptionsForm, + formset=BaseGroupNumericalDescriptionsFormset, + can_delete=True, + extra=1) -class SingleResultForm(forms.ModelForm): - resultSelector = forms.ChoiceField( - label="Results-data type", - choices=((0, "Add new results"), - (1, "Use existing results")), - initial=0) +class ResultForm(forms.ModelForm): - ao = selectable.AutoCompleteSelectField( - lookup_class=lookups.AssessedOutcomeByStudyLookup, - label='Assessed Outcome', - required=False, - widget=selectable.AutoComboboxSelectWidget) + HELP_TEXT_CREATE = """Describe results found for measured outcome.""" + HELP_TEXT_UPDATE = """Update results found for measured outcome.""" + ADJUSTMENT_FIELDS = ["factors_applied", "factors_considered"] - outcome_group = selectable.AutoCompleteSelectField( - lookup_class=lookups.AssessedOutcomeGroupByAOLookup, - label='Assessed Outcome Group', - required=False, - widget=selectable.AutoComboboxSelectWidget) + factors_applied = selectable.AutoCompleteSelectMultipleField( + help_text="All factors included in final model", + lookup_class=lookups.AdjustmentFactorLookup, + required=False) + + factors_considered = selectable.AutoCompleteSelectMultipleField( + label="Adjustment factors considered", + help_text="Factors considered, but not included in the final model", + lookup_class=lookups.AdjustmentFactorLookup, + required=False) class Meta: - model = models.SingleResult - fields = ('study', 'exposure_name', 'weight', 'outcome_group', - 'n', 'estimate', 'lower_ci', - 'upper_ci', 'ci_units', 'notes') + model = models.Result + exclude = ('outcome', 'adjustment_factors') def __init__(self, *args, **kwargs): - parent = kwargs.pop("parent", None) - assessment = kwargs.pop("assessment", None) - super(SingleResultForm, self).__init__(*args, **kwargs) - - # re-order with custom-fields: https://djangosnippets.org/snippets/759/ - order = ('resultSelector', 'study', 'ao', 'outcome_group', - 'exposure_name', 'weight', 'n', - 'estimate', 'lower_ci', 'upper_ci', 'ci_units', 'notes') - tmp = copy(self.fields) - self.fields = OrderedDict() - for item in order: - self.fields[item] = tmp[item] - - def updateClasses(fields, cls): - for fld in fields: - self.fields[fld].widget.attrs["class"] = cls + outcome = kwargs.pop('parent', None) + super(ResultForm, self).__init__(*args, **kwargs) + self.fields['comments'] = self.fields.pop('comments') # move to end - if assessment: - # used with a single form; not used in formset_factory - return forms.ModelChoiceField( - queryset=self.fields["study"].queryset.filter( - assessment=assessment, study_type=1)) + if outcome: + self.instance.outcome = outcome + else: + outcome = self.instance.outcome + + self.fields["comparison_set"].queryset = models.ComparisonSet.objects\ + .filter( + Q(study_population=outcome.study_population) | + Q(outcome=outcome) + ) + + for fld in self.ADJUSTMENT_FIELDS: + self.fields[fld].widget.update_query_parameters( + {'related': self.instance.outcome.assessment_id}) + if self.instance.id: + self.fields[fld].initial = getattr(self.instance, fld) + self.helper = self.setHelper() + + def save_factors(self): + """ + Adjustment factors is a through model; requires the inclusion type. + We save the m2m relations using the additional information from the + field-name + """ + self.instance.resfactors.all().delete() + objs = [] + + applied = self.cleaned_data.get("factors_applied", []) + objs.extend([ + models.ResultAdjustmentFactor( + adjustment_factor=af, + result=self.instance, + included_in_final_model=True) + for af in applied + ]) + + considered = self.cleaned_data.get("factors_considered", []) + considered = set(considered) - set(applied) + objs.extend([ + models.ResultAdjustmentFactor( + adjustment_factor=af, + result=self.instance, + included_in_final_model=False) + for af in considered + ]) + + models.ResultAdjustmentFactor.objects.bulk_create(objs) + + def save(self, commit=True): + instance = super(ResultForm, self).save(commit) + if commit: + self.save_factors() + return instance + + def setHelper(self): for fld in self.fields.keys(): widget = self.fields[fld].widget - if fld == "notes": + if type(widget) != forms.CheckboxInput: + if fld in self.ADJUSTMENT_FIELDS: + widget.attrs['class'] = 'span10' + else: + widget.attrs['class'] = 'span12' + if type(widget) == forms.Textarea: widget.attrs['rows'] = 3 - updateClasses(("resultSelector", ), "unstyled singleResultType") - updateClasses(("study", "exposure_name", "weight", "notes"), "span12") - updateClasses(("ao", "outcome_group", ), "span11 isAOG") - updateClasses(("n", "estimate", "lower_ci", "upper_ci", 'ci_units'), "span12 isntAOG") - self.fields['study'].widget.attrs["class"] += " studySearch" - self.fields['ao'].widget.attrs["class"] += " aoSearch" - self.fields['outcome_group'].widget.attrs["class"] += " aogSearch" - - if parent: - self.instance.meta_result = parent - - def clean_weight(self): - weight = self.cleaned_data.get('weight') - # todo: removed check, since can't specify instance in formset. - # Ideally would be able to check this. - # if weight is None and self.instance.meta_result.protocol.protocol_type == 0: - # raise forms.ValidationError("For meta-analysis epidemiological protocols, the weight-field is required") - return weight - - def clean(self): - cleaned_data = super(SingleResultForm, self).clean() - - if int(cleaned_data.get('resultSelector')) == 0: - if anyNull(cleaned_data, ('n', 'estimate', 'lower_ci', 'upper_ci')): - raise forms.ValidationError( - "If manually entering single-study data, " - "N, Risk estimate, and upper and lower CI are required.") + if self.instance.id: + inputs = { + "legend_text": u"Update {}".format(self.instance), + "help_text": self.HELP_TEXT_UPDATE, + "cancel_url": self.instance.get_absolute_url() + } else: - if anyNull(cleaned_data, ('study', 'ao', 'outcome_group')): - raise forms.ValidationError( - "If entering single-study data using an Assessed Outcome Group, " - "Study, Assessed-Outcome, and Assessed-Outcome Group are required.") - - return cleaned_data + inputs = { + "legend_text": u"Create new set of results", + "help_text": self.HELP_TEXT_CREATE, + "cancel_url": self.instance.outcome.get_absolute_url() + } + helper = BaseFormHelper(self, **inputs) + helper.form_class = None -class EmptySingleResultFormset(FormsetWithIgnoredFields): - ignored_fields = ['resultSelector'] + helper.add_fluid_row('metric', 2, "span6") + helper.add_fluid_row('data_location', 2, "span6") + helper.add_fluid_row('dose_response', 3, "span4") + helper.add_fluid_row('statistical_power', 3, "span4") + helper.add_fluid_row('factors_applied', 2, "span6") + helper.add_fluid_row('estimate_type', 3, "span4") - def get_queryset(self): - return models.SingleResult.objects.none() + url = reverse('epi:adjustmentfactor_create', + kwargs={'pk': self.instance.outcome.assessment_id}) + helper.addBtnLayout(helper.layout[8], 0, url, "Add new adjustment factor", "span6") + helper.addBtnLayout(helper.layout[8], 1, url, "Add new adjustment factor", "span6") + return helper -class LoadedSingleResultFormset(FormsetWithIgnoredFields): - ignored_fields = ['resultSelector'] +class ResultSelectorForm(CopyAsNewSelectorForm): + label = 'Result' + lookup_class = lookups.ResultByOutcomeLookup -SingleResultFormset = modelformset_factory( - models.SingleResult, - can_delete=True, - form=SingleResultForm, - formset=LoadedSingleResultFormset, - extra=1) +class ResultUpdateForm(ResultForm): -def meta_result_clean_update_formset(formset, assessment): - # cleanup required to get the formset in usable-shape - for form in formset.forms: - form.fields['study'].queryset = form.fields['study'].queryset.filter(assessment=assessment, study_type=1) - if form.instance and form.instance.outcome_group: - form.initial['ao'] = form.instance.outcome_group.assessed_outcome.pk - form.initial['resultSelector'] = 1 + def __init__(self, *args, **kwargs): + super(ResultUpdateForm, self).__init__(*args, **kwargs) + self.fields['comparison_set'].widget.attrs['disabled'] = True -class StudyPopulationSelectorForm(forms.Form): +class GroupResultForm(forms.ModelForm): - selector = selectable.AutoCompleteSelectField( - lookup_class=lookups.StudyPopulationByStudyLookup, - label='Study Population', - widget=selectable.AutoComboboxSelectWidget) + class Meta: + model = models.GroupResult + exclude = ('result', ) def __init__(self, *args, **kwargs): - study_id = kwargs.pop('study_id') - super(StudyPopulationSelectorForm, self).__init__(*args, **kwargs) + study_population = kwargs.pop('study_population', None) + outcome = kwargs.pop('outcome', None) + result = kwargs.pop('result', None) + super(GroupResultForm, self).__init__(*args, **kwargs) + self.fields["group"].queryset = models.Group.objects\ + .filter( + Q(comparison_set__study_population=study_population) | + Q(comparison_set__outcome=outcome) + ) + if result: + self.instance.result = result + self.helper = self.setHelper() + + def setHelper(self): for fld in self.fields.keys(): - self.fields[fld].widget.attrs['class'] = 'span11' - self.fields['selector'].widget.update_query_parameters( - {'related': study_id}) + widget = self.fields[fld].widget + if fld == "group": + widget.attrs['class'] = "groupField" + widget.attrs['style'] = "display: none;" + if fld == "n": + widget.attrs['class'] = "nField" + helper = BaseFormHelper(self) + helper.form_tag = False -class ExposureSelectorForm(forms.Form): + return helper - selector = selectable.AutoCompleteSelectField( - lookup_class=lookups.ExposureByStudyLookup, - label='Exposure', - widget=selectable.AutoComboboxSelectWidget) - def __init__(self, *args, **kwargs): - study_id = kwargs.pop('study_id') - super(ExposureSelectorForm, self).__init__(*args, **kwargs) - for fld in self.fields.keys(): - self.fields[fld].widget.attrs['class'] = 'span11' - self.fields['selector'].widget.update_query_parameters( - {'related': study_id}) +class BaseGroupResultFormset(BaseModelFormSet): + def __init__(self, **kwargs): + study_population = kwargs.pop('study_population', None) + outcome = kwargs.pop('outcome', None) + self.result = kwargs.pop('result', None) + super(BaseGroupResultFormset, self).__init__(**kwargs) + self.form = curry( + self.form, + study_population=study_population, + outcome=outcome, + result=self.result + ) -class AssesedOutcomeSelectorForm(forms.Form): + def clean(self): + super(BaseGroupResultFormset, self).clean() - selector = selectable.AutoCompleteSelectField( - lookup_class=lookups.AssessedOutcomeByStudyLookup, - label='Assessed Outcome', - widget=selectable.AutoComboboxSelectWidget) + # check that there is at least one result-group + count = len(filter(lambda f: f.is_valid() and f.clean(), self.forms)) + if count < 1: + raise forms.ValidationError("At least one group is required.") - def __init__(self, *args, **kwargs): - study_id = kwargs.pop('study_id') - super(AssesedOutcomeSelectorForm, self).__init__(*args, **kwargs) - for fld in self.fields.keys(): - self.fields[fld].widget.attrs['class'] = 'span11' - self.fields['selector'].widget.update_query_parameters( - {'related': study_id}) + mfs = 0 + for form in self.forms: + if form.cleaned_data['is_main_finding']: + mfs += 1 + if mfs > 1: + raise forms.ValidationError("Only one-group can be the main-finding.") -class MetaResultSelectorForm(forms.Form): + # Ensure all groups in group collection are accounted for, and no other + # groups exist + group_ids = [form.cleaned_data['group'].id for form in self.forms] + if self.result: + comparison_set_id = self.result.comparison_set_id + else: + comparison_set_id = int(self.data['comparison_set']) + selectedGroups = models.Group.objects\ + .filter(id__in=group_ids, comparison_set_id=comparison_set_id) + allGroups = models.Group.objects\ + .filter(comparison_set_id=comparison_set_id) + if selectedGroups.count() != allGroups.count(): + raise forms.ValidationError("Missing group(s) in this comparison set") + + +GroupResultFormset = modelformset_factory( + models.GroupResult, + form=GroupResultForm, + formset=BaseGroupResultFormset, + can_delete=False, + extra=0) - selector = selectable.AutoCompleteSelectField( - lookup_class=lookups.MetaResultByStudyLookup, - label='Meta Result', - widget=selectable.AutoComboboxSelectWidget) - def __init__(self, *args, **kwargs): - study_id = kwargs.pop("study_id") - super(MetaResultSelectorForm, self).__init__(*args, **kwargs) - for fld in self.fields.keys(): - self.fields[fld].widget.attrs['class'] = 'span11' - self.fields['selector'].widget.update_query_parameters( - {'related': study_id}) +BlankGroupResultFormset = modelformset_factory( + models.GroupResult, + form=GroupResultForm, + formset=BaseGroupResultFormset, + can_delete=False, + extra=1) diff --git a/project/epi/lookups.py b/project/epi/lookups.py index bd96f41e..3b36d568 100644 --- a/project/epi/lookups.py +++ b/project/epi/lookups.py @@ -1,11 +1,11 @@ from selectable.registry import registry from utils.lookups import DistinctStringLookup, RelatedLookup + from . import models class StudyPopulationByStudyLookup(RelatedLookup): - # Return names of study populations available for a particular study model = models.StudyPopulation search_fields = ('name__icontains', ) related_filter = 'study_id' @@ -21,100 +21,68 @@ class StateLookup(DistinctStringLookup): distinct_field = "state" -class StudyCriteriaLookup(RelatedLookup): - model = models.StudyCriteria +class CriteriaLookup(RelatedLookup): + model = models.Criteria search_fields = ('description__icontains', ) related_filter = 'assessment_id' -class ExposureByStudyLookup(StudyPopulationByStudyLookup): - # Return names of exposures available for a particular study - model = models.Exposure - search_fields = ('exposure_form_definition__icontains', ) - related_filter = 'study_population__study_id' - - def get_item_label(self, obj): - return u"{} | {}".format(obj.study_population, obj) - - def get_item_value(self, obj): - return self.get_item_label(obj) - - -class FactorLookup(RelatedLookup): - model = models.Factor +class AdjustmentFactorLookup(RelatedLookup): + model = models.AdjustmentFactor search_fields = ('description__icontains', ) - related_filter = 'assessment__id' - + related_filter = 'assessment_id' -class AssessedOutcomeByStudyLookup(StudyPopulationByStudyLookup): - # Return names of assessed outcomes available for a particular study - model = models.AssessedOutcome - search_fields = ( - 'name__icontains', - 'exposure__exposure_form_definition__icontains' - ) - related_filter = 'exposure__study_population__study' - def get_query(self, request, term): - return super(AssessedOutcomeByStudyLookup, self)\ - .get_query(request, term).order_by('exposure') +class ComparisonSetByStudyPopulationLookup(RelatedLookup): + model = models.ComparisonSet + search_fields = ('name__icontains', ) + related_filter = 'study_population_id' - def get_item_label(self, obj): - return u"{} | {} | {}".format(obj.exposure.study_population, obj.exposure, obj) - def get_item_value(self, obj): - return self.get_item_label(obj) +class ComparisonSetByOutcomeLookup(ComparisonSetByStudyPopulationLookup): + related_filter = 'outcome_id' -class AssessedOutcomeGroupByAOLookup(RelatedLookup): - model = models.AssessedOutcomeGroup - related_filter = 'assessed_outcome' +class ExposureByStudyPopulationLookup(RelatedLookup): + model = models.Exposure + search_fields = ('name__icontains', ) + related_filter = 'study_population_id' -class MetaResultByStudyLookup(RelatedLookup): - model = models.MetaResult - search_fields = ('label__icontains', ) - related_filter = 'protocol__study' +class OutcomeByStudyPopulationLookup(RelatedLookup): + model = models.Outcome + search_fields = ('name__icontains', ) + related_filter = 'study_population_id' -class MetaResultHealthOutcomeLookup(DistinctStringLookup): - model = models.MetaResult - distinct_field = 'health_outcome' - search_fields = ('health_outcome__icontains', ) +class EffectLookup(DistinctStringLookup): + model = models.Outcome + distinct_field = "effect" - def get_query(self, request, term): - try: - id_ = int(request.GET.get('related', -1)) - except Exception: - id_ = -1 - return self.model.objects.filter( - protocol__study__assessment_id=id_, - health_outcome__icontains=term) +class SystemLookup(DistinctStringLookup): + model = models.Outcome + distinct_field = "system" -class MetaResultExposureNameLookup(DistinctStringLookup): - model = models.MetaResult - distinct_field = 'exposure_name' - search_fields = ('exposure_name__icontains', ) - def get_query(self, request, term): - try: - id_ = int(request.GET.get('related', -1)) - except Exception: - id_ = -1 - return self.model.objects.filter( - protocol__study__assessment_id=id_, - exposure_name__icontains=term) +class ResultByOutcomeLookup(RelatedLookup): + model = models.Result + search_fields = ( + 'metric__metric__icontains', + 'comparison_set__name__icontains' + ) + related_filter = 'outcome_id' registry.register(StudyPopulationByStudyLookup) registry.register(RegionLookup) registry.register(StateLookup) -registry.register(StudyCriteriaLookup) -registry.register(ExposureByStudyLookup) -registry.register(FactorLookup) -registry.register(AssessedOutcomeByStudyLookup) -registry.register(AssessedOutcomeGroupByAOLookup) -registry.register(MetaResultByStudyLookup) -registry.register(MetaResultHealthOutcomeLookup) -registry.register(MetaResultExposureNameLookup) +registry.register(CriteriaLookup) +registry.register(AdjustmentFactorLookup) +registry.register(ExposureByStudyPopulationLookup) +registry.register(ComparisonSetByStudyPopulationLookup) +registry.register(ComparisonSetByOutcomeLookup) +registry.register(OutcomeByStudyPopulationLookup) +registry.register(EffectLookup) +registry.register(SystemLookup) +registry.register(ResultByOutcomeLookup) diff --git a/project/epi/migrations/0001_initial.py b/project/epi/migrations/0001_initial.py index c9f7f81c..589b7fc4 100644 --- a/project/epi/migrations/0001_initial.py +++ b/project/epi/migrations/0001_initial.py @@ -2,350 +2,326 @@ from __future__ import unicode_literals from django.db import models, migrations -import django.db.models.deletion import django.core.validators class Migration(migrations.Migration): dependencies = [ - ('animal', '0001_initial'), - ('assessment', '0001_initial'), + ('assessment', '0006_auto_20150724_1151'), ('study', '0001_initial'), ] operations = [ migrations.CreateModel( - name='AssessedOutcome', + name='AdjustmentFactor', fields=[ - ('baseendpoint_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='assessment.BaseEndpoint')), - ('data_location', models.CharField(help_text=b'Details on where the data are found in the literature (ex: Figure 1, Table 2, etc.)', max_length=128, blank=True)), - ('population_description', models.CharField(help_text=b'Detailed description of the population being studied for this outcome, which may be a subset of the entire study-population. For example, "US (national) NHANES 2003-2008, Hispanic children 6-18 years, \xe2\x99\x82\xe2\x99\x80 (n=797)"', max_length=128, blank=True)), - ('diagnostic', models.PositiveSmallIntegerField(choices=[(0, b'not reported'), (1, b'medical professional or test'), (2, b'medical records'), (3, b'self-reported')])), - ('diagnostic_description', models.TextField()), - ('outcome_n', models.PositiveIntegerField(null=True, verbose_name=b'Outcome N', blank=True)), - ('summary', models.TextField(help_text=b'Summarize main findings of outcome, or describe why no details are presented (for example, "no association (data not shown)")', blank=True)), - ('prevalence_incidence', models.TextField(blank=True)), - ('dose_response', models.PositiveSmallIntegerField(default=0, help_text=b'Was a dose-response trend observed?', verbose_name=b'Dose Response Trend', choices=[(0, b'not-applicable'), (1, b'monotonic'), (2, b'non-monotonic'), (3, b'no trend'), (4, b'not reported')])), - ('dose_response_details', models.TextField(blank=True)), - ('statistical_power', models.PositiveSmallIntegerField(default=0, help_text=b'Is the study sufficiently powered?', choices=[(0, b'not reported or calculated'), (1, b'appears to be adequately powered (sample size met)'), (2, b'somewhat underpowered (sample size is 75% to <100% of recommended)'), (3, b'underpowered (sample size is 50 to <75% required)'), (4, b'severely underpowered (sample size is <50% required)')])), - ('statistical_power_details', models.TextField(blank=True)), - ('main_finding_support', models.PositiveSmallIntegerField(default=1, help_text=b'Are the results supportive of the main-finding?', choices=[(3, b'not-reported'), (2, b'supportive'), (1, b'inconclusive'), (0, b'not-supportive')])), - ('statistical_metric_description', models.TextField(help_text=b'Add additional text describing the statistical metric used, if needed.', blank=True)), + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('description', models.TextField()), + ('created', models.DateTimeField(auto_now_add=True)), + ('last_updated', models.DateTimeField(auto_now=True)), + ('assessment', models.ForeignKey(to='assessment.Assessment')), ], options={ + 'ordering': ('description',), }, - bases=('assessment.baseendpoint',), ), migrations.CreateModel( - name='AssessedOutcomeGroup', + name='ComparisonSet', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('n', models.PositiveIntegerField(help_text=b'Individuals in group where outcome was measured', null=True, blank=True)), - ('estimate', models.FloatField(help_text=b'Central tendency estimate for group', null=True, blank=True)), - ('se', models.FloatField(help_text=b'Standard error estimate for group', null=True, verbose_name=b'Standard Error (SE)', blank=True)), - ('lower_ci', models.FloatField(help_text=b'Numerical value for lower-confidence interval', null=True, verbose_name=b'Lower CI', blank=True)), - ('upper_ci', models.FloatField(help_text=b'Numerical value for upper-confidence interval', null=True, verbose_name=b'Upper CI', blank=True)), - ('ci_units', models.FloatField(default=0.95, help_text=b'A 95% CI is written as 0.95.', null=True, verbose_name=b'Confidence Interval (CI)', blank=True)), - ('p_value_qualifier', models.CharField(default=b'-', max_length=1, verbose_name=b'p-value qualifier', choices=[(b'<', b'<'), (b'=', b'='), (b'-', b'n.s.')])), - ('p_value', models.FloatField(null=True, verbose_name=b'p-value', blank=True)), + ('name', models.CharField(max_length=256)), + ('description', models.TextField(blank=True)), ('created', models.DateTimeField(auto_now_add=True)), ('last_updated', models.DateTimeField(auto_now=True)), - ('assessed_outcome', models.ForeignKey(related_name='groups', to='epi.AssessedOutcome')), ], options={ - 'ordering': ('exposure_group__exposure_group_id',), + 'ordering': ('name',), + }, + ), + migrations.CreateModel( + name='Country', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('code', models.CharField(max_length=2, blank=True)), + ('name', models.CharField(unique=True, max_length=64)), + ], + options={ + 'ordering': ('name',), + 'verbose_name_plural': 'Countries', + }, + ), + migrations.CreateModel( + name='Criteria', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('description', models.TextField()), + ('created', models.DateTimeField(auto_now_add=True)), + ('last_updated', models.DateTimeField(auto_now=True)), + ('assessment', models.ForeignKey(to='assessment.Assessment')), + ], + options={ + 'ordering': ('description',), + 'verbose_name_plural': 'Criteria', }, - bases=(models.Model,), ), migrations.CreateModel( name='Ethnicity', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('ethnicity', models.CharField(max_length=1, choices=[(b'I', b'American Indian or Alaskan Native'), (b'A', b'Asian'), (b'B', b'Black or African American'), (b'H', b'Hispanic/Latino'), (b'P', b'Native American of Other Pacific Islander'), (b'M', b'Two or More Races'), (b'W', b'White'), (b'O', b'Other'), (b'U', b'Unknown/Unspecified')])), + ('name', models.CharField(unique=True, max_length=64)), ('created', models.DateTimeField(auto_now_add=True)), ('last_updated', models.DateTimeField(auto_now=True)), ], options={ + 'verbose_name_plural': 'Ethnicities', }, - bases=(models.Model,), ), migrations.CreateModel( name='Exposure', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(help_text=b'Name of exposure and exposure-route', max_length=128)), ('inhalation', models.BooleanField(default=False)), ('dermal', models.BooleanField(default=False)), ('oral', models.BooleanField(default=False)), ('in_utero', models.BooleanField(default=False)), ('iv', models.BooleanField(default=False, verbose_name=b'Intravenous (IV)')), ('unknown_route', models.BooleanField(default=False)), - ('exposure_form_definition', models.TextField(help_text=b'Name of exposure-route')), - ('metric', models.TextField(verbose_name=b'Measurement Metric')), + ('measured', models.CharField(max_length=128, verbose_name=b'What was measured', blank=True)), + ('metric', models.CharField(max_length=128, verbose_name=b'Measurement Metric')), ('metric_description', models.TextField(verbose_name=b'Measurement Description')), ('analytical_method', models.TextField(help_text=b'Include details on the lab-techniques for exposure measurement in samples.')), - ('control_description', models.TextField()), - ('exposure_description', models.CharField(help_text=b'May be used to describe the exposure distribution, for example, "2.05 \xc2\xb5g/g creatinine (urine), geometric mean; 25th percentile = 1.18, 75th percentile = 3.33"', max_length=128, blank=True)), + ('sampling_period', models.CharField(help_text=b'Exposure sampling period', max_length=128, blank=True)), + ('duration', models.CharField(help_text=b'Exposure duration', max_length=128, blank=True)), + ('exposure_distribution', models.CharField(help_text=b'May be used to describe the exposure distribution, for example, "2.05 \xc2\xb5g/g creatinine (urine), geometric mean; 25th percentile = 1.18, 75th percentile = 3.33"', max_length=128, blank=True)), + ('description', models.TextField(blank=True)), ('created', models.DateTimeField(auto_now_add=True)), ('last_updated', models.DateTimeField(auto_now=True)), - ('metric_units', models.ForeignKey(to='animal.DoseUnits')), + ('metric_units', models.ForeignKey(to='assessment.DoseUnits')), ], options={ - 'ordering': ('exposure_form_definition',), + 'ordering': ('name',), + 'verbose_name': 'Exposure', + 'verbose_name_plural': 'Exposures', }, - bases=(models.Model,), ), migrations.CreateModel( - name='ExposureGroup', + name='Group', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('sex', models.CharField(max_length=1, choices=[(b'U', b'Not reported'), (b'M', b'Male'), (b'F', b'Female'), (b'B', b'Male and Female')])), - ('fraction_male', models.FloatField(blank=True, help_text=b'Expects a value between 0 and 1, inclusive (leave blank if unknown)', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), - ('fraction_male_calculated', models.BooleanField(default=False, help_text=b'Was the fraction-male value calculated/estimated from literature?')), - ('age_mean', models.FloatField(null=True, verbose_name=b'Age central estimate', blank=True)), - ('age_mean_type', models.PositiveSmallIntegerField(default=0, verbose_name=b'Age central estimate type', choices=[(0, None), (1, b'mean'), (2, b'geometric mean'), (3, b'median')])), - ('age_calculated', models.BooleanField(default=False, help_text=b'Were age values calculated/estimated from literature?')), - ('age_description', models.CharField(help_text=b'Age description if numeric ages do not make sense for this study-population (ex: longitudinal studies)', max_length=128, blank=True)), - ('age_sd', models.FloatField(null=True, verbose_name=b'Age variance', blank=True)), - ('age_sd_type', models.PositiveSmallIntegerField(default=0, verbose_name=b'Age variance type', choices=[(0, None), (1, b'SD'), (2, b'SEM')])), - ('age_lower', models.FloatField(null=True, blank=True)), - ('age_lower_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'lower limit')])), - ('age_upper', models.FloatField(null=True, blank=True)), - ('age_upper_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'upper limit')])), - ('n', models.PositiveIntegerField(null=True, blank=True)), - ('starting_n', models.PositiveIntegerField(null=True, blank=True)), + ('group_id', models.PositiveSmallIntegerField()), + ('name', models.CharField(max_length=256)), + ('numeric', models.FloatField(help_text=b'Numerical value, can be used for sorting', null=True, verbose_name=b'Numerical value (sorting)', blank=True)), + ('comparative_name', models.CharField(help_text=b'Group and value, displayed in plots, for example "1.5-2.5(Q2) vs \xe2\x89\xa41.5(Q1)", or if only one group is available, "4.8\xc2\xb10.2 (mean\xc2\xb1SEM)"', max_length=64, verbose_name=b'Comparative Name', blank=True)), + ('sex', models.CharField(default=b'U', max_length=1, choices=[(b'U', b'Not reported'), (b'M', b'Male'), (b'F', b'Female'), (b'B', b'Male and Female')])), + ('eligible_n', models.PositiveIntegerField(null=True, verbose_name=b'Eligible N', blank=True)), + ('invited_n', models.PositiveIntegerField(null=True, verbose_name=b'Invited N', blank=True)), + ('participant_n', models.PositiveIntegerField(null=True, verbose_name=b'Participant N', blank=True)), + ('isControl', models.NullBooleanField(default=None, choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], help_text=b'Should this group be interpreted as a null/control group', verbose_name=b'Control?')), + ('comments', models.TextField(help_text=b'Any other comments related to this group', blank=True)), ('created', models.DateTimeField(auto_now_add=True)), ('last_updated', models.DateTimeField(auto_now=True)), - ('description', models.CharField(max_length=256)), - ('exposure_numeric', models.FloatField(help_text=b'Should be an exposure-value used for sorting', null=True, verbose_name=b'Low exposure value (sorting)', blank=True)), - ('comparative_name', models.CharField(help_text=b'Should include effect-group and comparative group, for example "1.5-2.5(Q2) vs \xe2\x89\xa41.5(Q1)", or if only one group is available, "4.8\xc2\xb10.2 (mean\xc2\xb1SEM)"', max_length=64, verbose_name=b'Comparative Name', blank=True)), - ('exposure_group_id', models.PositiveSmallIntegerField()), - ('exposure_n', models.PositiveSmallIntegerField(help_text=b'Final N used for exposure group', null=True, blank=True)), - ('ethnicity', models.ManyToManyField(to='epi.Ethnicity', blank=True)), - ('exposure', models.ForeignKey(related_name='groups', to='epi.Exposure')), + ('comparison_set', models.ForeignKey(related_name='groups', to='epi.ComparisonSet')), + ('ethnicities', models.ManyToManyField(to='epi.Ethnicity', blank=True)), ], options={ - 'ordering': ('exposure_group_id',), + 'ordering': ('comparison_set', 'group_id'), }, - bases=(models.Model,), ), migrations.CreateModel( - name='Factor', + name='GroupNumericalDescriptions', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('description', models.TextField()), + ('description', models.CharField(help_text=b'Description if numeric ages do not make sense for this study-population (ex: longitudinal studies)', max_length=128)), + ('mean', models.FloatField(null=True, verbose_name=b'Central estimate', blank=True)), + ('mean_type', models.PositiveSmallIntegerField(default=0, verbose_name=b'Central estimate type', choices=[(0, None), (1, b'mean'), (2, b'geometric mean'), (3, b'median'), (4, b'other')])), + ('is_calculated', models.BooleanField(default=False, help_text=b'Was value calculated/estimated from literature?')), + ('variance', models.FloatField(null=True, blank=True)), + ('variance_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'SD'), (2, b'SEM'), (3, b'GSD'), (4, b'other')])), + ('lower', models.FloatField(null=True, blank=True)), + ('lower_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'lower limit'), (2, b'5% CI'), (3, b'other')])), + ('upper', models.FloatField(null=True, blank=True)), + ('upper_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'upper limit'), (2, b'95% CI'), (3, b'other')])), + ('group', models.ForeignKey(related_name='descriptions', to='epi.Group')), + ], + ), + migrations.CreateModel( + name='GroupResult', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('n', models.PositiveIntegerField(help_text=b'Individuals in group where outcome was measured', null=True, blank=True)), + ('estimate', models.FloatField(help_text=b'Central tendency estimate for group', null=True, blank=True)), + ('variance', models.FloatField(help_text=b'Variance estimate for group', null=True, verbose_name=b'Variance', blank=True)), + ('lower_ci', models.FloatField(help_text=b'Numerical value for lower-confidence interval', null=True, verbose_name=b'Lower CI', blank=True)), + ('upper_ci', models.FloatField(help_text=b'Numerical value for upper-confidence interval', null=True, verbose_name=b'Upper CI', blank=True)), + ('p_value_qualifier', models.CharField(default=b'-', max_length=1, verbose_name=b'p-value qualifier', choices=[(b' ', b'-'), (b'-', b'n.s.'), (b'<', b'<'), (b'=', b'='), (b'>', b'>')])), + ('p_value', models.FloatField(blank=True, null=True, verbose_name=b'p-value', validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)])), + ('is_main_finding', models.BooleanField(help_text=b'Is this the main-finding for this outcome?', verbose_name=b'Main finding')), + ('main_finding_support', models.PositiveSmallIntegerField(default=1, help_text=b'Are the results supportive of the main-finding?', choices=[(3, b'not-reported'), (2, b'supportive'), (1, b'inconclusive'), (0, b'not-supportive')])), ('created', models.DateTimeField(auto_now_add=True)), ('last_updated', models.DateTimeField(auto_now=True)), - ('assessment', models.ForeignKey(to='assessment.Assessment')), + ('group', models.ForeignKey(related_name='results', to='epi.Group')), ], options={ - 'ordering': ('description',), + 'ordering': ('result', 'group__comparison_set_id'), }, - bases=(models.Model,), ), migrations.CreateModel( - name='MetaProtocol', + name='Outcome', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=128, verbose_name=b'Protocol name')), - ('protocol_type', models.PositiveSmallIntegerField(default=0, choices=[(0, b'Meta-analysis'), (1, b'Pooled-analysis')])), - ('lit_search_strategy', models.PositiveSmallIntegerField(default=0, verbose_name=b'Literature search strategy', choices=[(0, b'Systematic'), (1, b'Other')])), - ('lit_search_notes', models.TextField(verbose_name=b'Literature search notes', blank=True)), - ('lit_search_start_date', models.DateField(null=True, verbose_name=b'Literature search start-date', blank=True)), - ('lit_search_end_date', models.DateField(null=True, verbose_name=b'Literature search end-date', blank=True)), - ('total_references', models.PositiveIntegerField(help_text=b'References identified through initial literature-search before application of inclusion/exclusion criteria', null=True, verbose_name=b'Total number of references found', blank=True)), - ('total_studies_identified', models.PositiveIntegerField(help_text=b'Total references identified for inclusion after application of literature review and screening criteria', verbose_name=b'Total number of studies identified')), - ('notes', models.TextField(blank=True)), + ('baseendpoint_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='assessment.BaseEndpoint')), + ('system', models.CharField(help_text=b'Relevant biological system', max_length=128, blank=True)), + ('effect', models.CharField(help_text=b'Effect, using common-vocabulary', max_length=128, blank=True)), + ('diagnostic', models.PositiveSmallIntegerField(choices=[(0, b'not reported'), (1, b'medical professional or test'), (2, b'medical records'), (3, b'self-reported'), (4, b'questionnaire'), (5, b'hospital admission'), (6, b'other')])), + ('diagnostic_description', models.TextField()), + ('outcome_n', models.PositiveIntegerField(null=True, verbose_name=b'Outcome N', blank=True)), + ('summary', models.TextField(help_text=b'Summarize main findings of outcome, or describe why no details are presented (for example, "no association (data not shown)")', blank=True)), ], - options={ - 'ordering': ('name',), - }, - bases=(models.Model,), + bases=('assessment.baseendpoint',), ), migrations.CreateModel( - name='MetaResult', + name='Result', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('label', models.CharField(max_length=128)), + ('metric_description', models.TextField(help_text=b'Add additional text describing the metric used, if needed.', blank=True)), ('data_location', models.CharField(help_text=b'Details on where the data are found in the literature (ex: Figure 1, Table 2, etc.)', max_length=128, blank=True)), - ('health_outcome', models.CharField(max_length=128)), - ('health_outcome_notes', models.TextField(blank=True)), - ('exposure_name', models.CharField(max_length=128)), - ('exposure_details', models.TextField(blank=True)), - ('number_studies', models.PositiveSmallIntegerField()), - ('statistical_notes', models.TextField(blank=True)), - ('n', models.PositiveIntegerField(help_text=b'Number of individuals included from all analyses')), - ('estimate', models.FloatField()), - ('lower_ci', models.FloatField(help_text=b'Numerical value for lower-confidence interval', verbose_name=b'Lower CI')), - ('upper_ci', models.FloatField(help_text=b'Numerical value for upper-confidence interval', verbose_name=b'Upper CI')), + ('population_description', models.CharField(help_text=b'Detailed description of the population being studied forthis outcome, which may be a subset of the entirestudy-population. For example, "US (national) NHANES2003-2008, Hispanic children 6-18 years, \xe2\x99\x82\xe2\x99\x80 (n=797)"', max_length=128, blank=True)), + ('dose_response', models.PositiveSmallIntegerField(default=0, help_text=b'Was a trend observed?', verbose_name=b'Dose Response Trend', choices=[(0, b'not-applicable'), (1, b'monotonic'), (2, b'non-monotonic'), (3, b'no trend'), (4, b'not reported')])), + ('dose_response_details', models.TextField(blank=True)), + ('prevalence_incidence', models.CharField(max_length=128, verbose_name=b'Overall incidence prevalence', blank=True)), + ('statistical_power', models.PositiveSmallIntegerField(default=0, help_text=b'Is the study sufficiently powered?', choices=[(0, b'not reported or calculated'), (1, b'appears to be adequately powered (sample size met)'), (2, b'somewhat underpowered (sample size is 75% to <100% of recommended)'), (3, b'underpowered (sample size is 50 to <75% required)'), (4, b'severely underpowered (sample size is <50% required)')])), + ('statistical_power_details', models.TextField(blank=True)), + ('trend_test', models.CharField(help_text='Enter result, if available (ex: p=0.015, p\u22640.05, n.s., etc.)', max_length=128, verbose_name=b'Trend test result', blank=True)), + ('estimate_type', models.PositiveSmallIntegerField(default=0, verbose_name=b'Central estimate type', choices=[(0, None), (1, b'mean'), (2, b'geometric mean'), (3, b'median'), (5, b'point'), (4, b'other')])), + ('variance_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'SD'), (2, b'SEM'), (3, b'GSD'), (4, b'other')])), ('ci_units', models.FloatField(default=0.95, help_text=b'A 95% CI is written as 0.95.', null=True, verbose_name=b'Confidence Interval (CI)', blank=True)), - ('heterogeneity', models.CharField(max_length=256, blank=True)), - ('notes', models.TextField(blank=True)), - ('adjustment_factors', models.ManyToManyField(help_text=b'All factors which were included in final model', related_name='meta_adjustments', null=True, to='epi.Factor', blank=True)), - ('protocol', models.ForeignKey(related_name='results', to='epi.MetaProtocol')), + ('comments', models.TextField(help_text=b'Summarize main findings of outcome, or describe why no details are presented (for example, "no association (data not shown)")', blank=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('last_updated', models.DateTimeField(auto_now=True)), ], - options={ - 'ordering': ('label',), - }, - bases=(models.Model,), ), migrations.CreateModel( - name='SingleResult', + name='ResultAdjustmentFactor', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('exposure_name', models.CharField(help_text=b'Enter a descriptive-name for the single study result (e.g., "Smith et al. 2000, obese-males")', max_length=128)), - ('weight', models.FloatField(blank=True, help_text=b'For meta-analysis, enter the fraction-weight assigned for each result (leave-blank for pooled analyses)', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), - ('n', models.PositiveIntegerField(help_text=b'Enter the number of observations for this result', null=True, blank=True)), - ('estimate', models.FloatField(help_text=b'Enter the numerical risk-estimate presented for this result', null=True, blank=True)), - ('lower_ci', models.FloatField(help_text=b'Numerical value for lower-confidence interval', null=True, verbose_name=b'Lower CI', blank=True)), - ('upper_ci', models.FloatField(help_text=b'Numerical value for upper-confidence interval', null=True, verbose_name=b'Upper CI', blank=True)), - ('ci_units', models.FloatField(default=0.95, help_text=b'A 95% CI is written as 0.95.', null=True, verbose_name=b'Confidence Interval (CI)', blank=True)), - ('notes', models.TextField(blank=True)), - ('meta_result', models.ForeignKey(related_name='single_results', to='epi.MetaResult')), - ('outcome_group', models.ForeignKey(related_name='single_results', blank=True, to='epi.AssessedOutcomeGroup', null=True)), - ('study', models.ForeignKey(related_name='single_results', blank=True, to='study.Study', null=True)), + ('included_in_final_model', models.BooleanField(default=True)), + ('adjustment_factor', models.ForeignKey(related_name='resfactors', to='epi.AdjustmentFactor')), + ('result', models.ForeignKey(related_name='resfactors', to='epi.Result')), ], - options={ - }, - bases=(models.Model,), ), migrations.CreateModel( - name='StatisticalMetric', + name='ResultMetric', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('metric', models.CharField(unique=True, max_length=128)), ('abbreviation', models.CharField(max_length=32)), - ('isLog', models.BooleanField(default=True, help_text=b'When plotting, use a log base 10 scale', verbose_name=b'Log-results')), + ('isLog', models.BooleanField(default=True, help_text=b'When plotting, use a log base 10 scale', verbose_name=b'Display as log')), + ('showForestPlot', models.BooleanField(default=True, help_text=b'Does forest-plot representation of result make sense?', verbose_name=b'Show on forest plot')), + ('reference_value', models.FloatField(default=1, help_text=b'Null hypothesis value for reference, if applicable', null=True, blank=True)), ('order', models.PositiveSmallIntegerField(help_text=b'Order as they appear in option-list')), ], options={ 'ordering': ('order',), }, - bases=(models.Model,), ), migrations.CreateModel( - name='StudyCriteria', + name='StudyPopulation', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('description', models.TextField()), + ('name', models.CharField(max_length=256)), + ('design', models.CharField(max_length=2, choices=[(b'CO', b'Cohort'), (b'CX', b'Cohort (Retrospective)'), (b'CY', b'Cohort (Prospective)'), (b'CC', b'Case-control'), (b'NC', b'Nested case-control'), (b'CR', b'Case report'), (b'SE', b'Case series'), (b'CT', b'Controlled trial'), (b'CS', b'Cross-sectional')])), + ('age_profile', models.CharField(help_text=b'Age profile of population (ex: adults, children, pregnant women, etc.)', max_length=128, blank=True)), + ('source', models.CharField(help_text=b'Population source (ex: general population, environmental exposure, occupational cohort)', max_length=128, blank=True)), + ('region', models.CharField(max_length=128, blank=True)), + ('state', models.CharField(max_length=128, blank=True)), + ('eligible_n', models.PositiveIntegerField(null=True, verbose_name=b'Eligible N', blank=True)), + ('invited_n', models.PositiveIntegerField(null=True, verbose_name=b'Invited N', blank=True)), + ('participant_n', models.PositiveIntegerField(null=True, verbose_name=b'Participant N', blank=True)), + ('comments', models.TextField(help_text=b'Note matching criteria, etc.', blank=True)), ('created', models.DateTimeField(auto_now_add=True)), ('last_updated', models.DateTimeField(auto_now=True)), - ('assessment', models.ForeignKey(to='assessment.Assessment')), + ('country', models.ForeignKey(to='epi.Country')), ], options={ - 'ordering': ('description',), + 'ordering': ('name',), }, - bases=(models.Model,), ), migrations.CreateModel( - name='StudyPopulation', + name='StudyPopulationCriteria', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('sex', models.CharField(max_length=1, choices=[(b'U', b'Not reported'), (b'M', b'Male'), (b'F', b'Female'), (b'B', b'Male and Female')])), - ('fraction_male', models.FloatField(blank=True, help_text=b'Expects a value between 0 and 1, inclusive (leave blank if unknown)', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), - ('fraction_male_calculated', models.BooleanField(default=False, help_text=b'Was the fraction-male value calculated/estimated from literature?')), - ('age_mean', models.FloatField(null=True, verbose_name=b'Age central estimate', blank=True)), - ('age_mean_type', models.PositiveSmallIntegerField(default=0, verbose_name=b'Age central estimate type', choices=[(0, None), (1, b'mean'), (2, b'geometric mean'), (3, b'median')])), - ('age_calculated', models.BooleanField(default=False, help_text=b'Were age values calculated/estimated from literature?')), - ('age_description', models.CharField(help_text=b'Age description if numeric ages do not make sense for this study-population (ex: longitudinal studies)', max_length=128, blank=True)), - ('age_sd', models.FloatField(null=True, verbose_name=b'Age variance', blank=True)), - ('age_sd_type', models.PositiveSmallIntegerField(default=0, verbose_name=b'Age variance type', choices=[(0, None), (1, b'SD'), (2, b'SEM')])), - ('age_lower', models.FloatField(null=True, blank=True)), - ('age_lower_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'lower limit')])), - ('age_upper', models.FloatField(null=True, blank=True)), - ('age_upper_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'upper limit')])), - ('n', models.PositiveIntegerField(null=True, blank=True)), - ('starting_n', models.PositiveIntegerField(null=True, blank=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('name', models.CharField(max_length=256)), - ('design', models.CharField(max_length=2, choices=[(b'CC', b'Case-control'), (b'CS', b'Cross-sectional'), (b'CP', b'Prospective'), (b'RT', b'Retrospective'), (b'CT', b'Controlled trial'), (b'SE', b'Case-series'), (b'CR', b'Case-report')])), - ('country', models.CharField(max_length=2, choices=[(b'AF', 'Afghanistan'), (b'AX', '\xc5land Islands'), (b'AL', 'Albania'), (b'DZ', 'Algeria'), (b'AS', 'American Samoa'), (b'AD', 'Andorra'), (b'AO', 'Angola'), (b'AI', 'Anguilla'), (b'AQ', 'Antarctica'), (b'AG', 'Antigua And Barbuda'), (b'AR', 'Argentina'), (b'AM', 'Armenia'), (b'AW', 'Aruba'), (b'AU', 'Australia'), (b'AT', 'Austria'), (b'AZ', 'Azerbaijan'), (b'BS', 'Bahamas'), (b'BH', 'Bahrain'), (b'BD', 'Bangladesh'), (b'BB', 'Barbados'), (b'BY', 'Belarus'), (b'BE', 'Belgium'), (b'BZ', 'Belize'), (b'BJ', 'Benin'), (b'BM', 'Bermuda'), (b'BT', 'Bhutan'), (b'BO', 'Bolivia, Plurinational State Of'), (b'BQ', 'Bonaire, Sint Eustatius And Saba'), (b'BA', 'Bosnia And Herzegovina'), (b'BW', 'Botswana'), (b'BV', 'Bouvet Island'), (b'BR', 'Brazil'), (b'IO', 'British Indian Ocean Territory'), (b'BN', 'Brunei Darussalam'), (b'BG', 'Bulgaria'), (b'BF', 'Burkina Faso'), (b'BI', 'Burundi'), (b'KH', 'Cambodia'), (b'CM', 'Cameroon'), (b'CA', 'Canada'), (b'CV', 'Cape Verde'), (b'KY', 'Cayman Islands'), (b'CF', 'Central African Republic'), (b'TD', 'Chad'), (b'CL', 'Chile'), (b'CN', 'China'), (b'CX', 'Christmas Island'), (b'CC', 'Cocos (Keeling) Islands'), (b'CO', 'Colombia'), (b'KM', 'Comoros'), (b'CG', 'Congo'), (b'CD', 'Congo, The Democratic Republic Of The'), (b'CK', 'Cook Islands'), (b'CR', 'Costa Rica'), (b'CI', "C\xf4te D'Ivoire"), (b'HR', 'Croatia'), (b'CU', 'Cuba'), (b'CW', 'Cura\xe7ao'), (b'CY', 'Cyprus'), (b'CZ', 'Czech Republic'), (b'DK', 'Denmark'), (b'DJ', 'Djibouti'), (b'DM', 'Dominica'), (b'DO', 'Dominican Republic'), (b'EC', 'Ecuador'), (b'EG', 'Egypt'), (b'SV', 'El Salvador'), (b'GQ', 'Equatorial Guinea'), (b'ER', 'Eritrea'), (b'EE', 'Estonia'), (b'ET', 'Ethiopia'), (b'FK', 'Falkland Islands (Malvinas)'), (b'FO', 'Faroe Islands'), (b'FJ', 'Fiji'), (b'FI', 'Finland'), (b'FR', 'France'), (b'GF', 'French Guiana'), (b'PF', 'French Polynesia'), (b'TF', 'French Southern Territories'), (b'GA', 'Gabon'), (b'GM', 'Gambia'), (b'GE', 'Georgia'), (b'DE', 'Germany'), (b'GH', 'Ghana'), (b'GI', 'Gibraltar'), (b'GR', 'Greece'), (b'GL', 'Greenland'), (b'GD', 'Grenada'), (b'GP', 'Guadeloupe'), (b'GU', 'Guam'), (b'GT', 'Guatemala'), (b'GG', 'Guernsey'), (b'GN', 'Guinea'), (b'GW', 'Guinea-Bissau'), (b'GY', 'Guyana'), (b'HT', 'Haiti'), (b'HM', 'Heard Island And Mcdonald Islands'), (b'VA', 'Holy See (Vatican City State)'), (b'HN', 'Honduras'), (b'HK', 'Hong Kong'), (b'HU', 'Hungary'), (b'IS', 'Iceland'), (b'IN', 'India'), (b'ID', 'Indonesia'), (b'IR', 'Iran, Islamic Republic Of'), (b'IQ', 'Iraq'), (b'IE', 'Ireland'), (b'IM', 'Isle Of Man'), (b'IL', 'Israel'), (b'IT', 'Italy'), (b'JM', 'Jamaica'), (b'JP', 'Japan'), (b'JE', 'Jersey'), (b'JO', 'Jordan'), (b'KZ', 'Kazakhstan'), (b'KE', 'Kenya'), (b'KI', 'Kiribati'), (b'KP', "Korea, Democratic People's Republic Of"), (b'KR', 'Korea, Republic Of'), (b'KW', 'Kuwait'), (b'KG', 'Kyrgyzstan'), (b'LA', "Lao People's Democratic Republic"), (b'LV', 'Latvia'), (b'LB', 'Lebanon'), (b'LS', 'Lesotho'), (b'LR', 'Liberia'), (b'LY', 'Libya'), (b'LI', 'Liechtenstein'), (b'LT', 'Lithuania'), (b'LU', 'Luxembourg'), (b'MO', 'Macao'), (b'MK', 'Macedonia, The Former Yugoslav Republic Of'), (b'MG', 'Madagascar'), (b'MW', 'Malawi'), (b'MY', 'Malaysia'), (b'MV', 'Maldives'), (b'ML', 'Mali'), (b'MT', 'Malta'), (b'MH', 'Marshall Islands'), (b'MQ', 'Martinique'), (b'MR', 'Mauritania'), (b'MU', 'Mauritius'), (b'YT', 'Mayotte'), (b'MX', 'Mexico'), (b'FM', 'Micronesia, Federated States Of'), (b'MD', 'Moldova, Republic Of'), (b'MC', 'Monaco'), (b'MN', 'Mongolia'), (b'ME', 'Montenegro'), (b'MS', 'Montserrat'), (b'MA', 'Morocco'), (b'MZ', 'Mozambique'), (b'MM', 'Myanmar'), (b'NA', 'Namibia'), (b'NR', 'Nauru'), (b'NP', 'Nepal'), (b'NL', 'Netherlands'), (b'NC', 'New Caledonia'), (b'NZ', 'New Zealand'), (b'NI', 'Nicaragua'), (b'NE', 'Niger'), (b'NG', 'Nigeria'), (b'NU', 'Niue'), (b'NF', 'Norfolk Island'), (b'MP', 'Northern Mariana Islands'), (b'NO', 'Norway'), (b'OM', 'Oman'), (b'PK', 'Pakistan'), (b'PW', 'Palau'), (b'PS', 'Palestine, State Of'), (b'PA', 'Panama'), (b'PG', 'Papua New Guinea'), (b'PY', 'Paraguay'), (b'PE', 'Peru'), (b'PH', 'Philippines'), (b'PN', 'Pitcairn'), (b'PL', 'Poland'), (b'PT', 'Portugal'), (b'PR', 'Puerto Rico'), (b'QA', 'Qatar'), (b'RE', 'R\xe9union'), (b'RO', 'Romania'), (b'RU', 'Russian Federation'), (b'RW', 'Rwanda'), (b'BL', 'Saint Barth\xe9lemy'), (b'SH', 'Saint Helena, Ascension And Tristan Da Cunha'), (b'KN', 'Saint Kitts And Nevis'), (b'LC', 'Saint Lucia'), (b'MF', 'Saint Martin (French Part)'), (b'PM', 'Saint Pierre And Miquelon'), (b'VC', 'Saint Vincent And The Grenadines'), (b'WS', 'Samoa'), (b'SM', 'San Marino'), (b'ST', 'Sao Tome And Principe'), (b'SA', 'Saudi Arabia'), (b'SN', 'Senegal'), (b'RS', 'Serbia'), (b'SC', 'Seychelles'), (b'SL', 'Sierra Leone'), (b'SG', 'Singapore'), (b'SX', 'Sint Maarten (Dutch Part)'), (b'SK', 'Slovakia'), (b'SI', 'Slovenia'), (b'SB', 'Solomon Islands'), (b'SO', 'Somalia'), (b'ZA', 'South Africa'), (b'GS', 'South Georgia And The South Sandwich Islands'), (b'SS', 'South Sudan'), (b'ES', 'Spain'), (b'LK', 'Sri Lanka'), (b'SD', 'Sudan'), (b'SR', 'Suriname'), (b'SJ', 'Svalbard And Jan Mayen'), (b'SZ', 'Swaziland'), (b'SE', 'Sweden'), (b'CH', 'Switzerland'), (b'SY', 'Syrian Arab Republic'), (b'TW', 'Taiwan, Province Of China'), (b'TJ', 'Tajikistan'), (b'TZ', 'Tanzania, United Republic Of'), (b'TH', 'Thailand'), (b'TL', 'Timor-Leste'), (b'TG', 'Togo'), (b'TK', 'Tokelau'), (b'TO', 'Tonga'), (b'TT', 'Trinidad And Tobago'), (b'TN', 'Tunisia'), (b'TR', 'Turkey'), (b'TM', 'Turkmenistan'), (b'TC', 'Turks And Caicos Islands'), (b'TV', 'Tuvalu'), (b'UG', 'Uganda'), (b'UA', 'Ukraine'), (b'AE', 'United Arab Emirates'), (b'GB', 'United Kingdom'), (b'US', 'United States'), (b'UM', 'United States Minor Outlying Islands'), (b'UY', 'Uruguay'), (b'UZ', 'Uzbekistan'), (b'VU', 'Vanuatu'), (b'VE', 'Venezuela, Bolivarian Republic Of'), (b'VN', 'Viet Nam'), (b'VG', 'Virgin Islands, British'), (b'VI', 'Virgin Islands, U.S.'), (b'WF', 'Wallis And Futuna'), (b'EH', 'Western Sahara'), (b'YE', 'Yemen'), (b'ZM', 'Zambia'), (b'ZW', 'Zimbabwe')])), - ('region', models.CharField(max_length=128, blank=True)), - ('state', models.CharField(max_length=128, blank=True)), - ('confounding_criteria', models.ManyToManyField(related_name='confounding_criteria', null=True, to='epi.StudyCriteria', blank=True)), - ('ethnicity', models.ManyToManyField(to='epi.Ethnicity', blank=True)), - ('exclusion_criteria', models.ManyToManyField(related_name='exclusion_criteria', null=True, to='epi.StudyCriteria', blank=True)), - ('inclusion_criteria', models.ManyToManyField(related_name='inclusion_criteria', null=True, to='epi.StudyCriteria', blank=True)), - ('study', models.ForeignKey(related_name='study_populations', to='study.Study')), + ('criteria_type', models.CharField(max_length=1, choices=[(b'I', b'Inclusion'), (b'E', b'Exclusion'), (b'C', b'Confounding')])), + ('criteria', models.ForeignKey(related_name='spcriteria', to='epi.Criteria')), + ('study_population', models.ForeignKey(related_name='spcriteria', to='epi.StudyPopulation')), ], - options={ - 'ordering': ('name',), - }, - bases=(models.Model,), - ), - migrations.AlterUniqueTogether( - name='studycriteria', - unique_together=set([('assessment', 'description')]), ), migrations.AddField( - model_name='metaresult', - name='statistical_metric', - field=models.ForeignKey(to='epi.StatisticalMetric'), - preserve_default=True, + model_name='studypopulation', + name='criteria', + field=models.ManyToManyField(related_name='populations', through='epi.StudyPopulationCriteria', to='epi.Criteria'), ), migrations.AddField( - model_name='metaprotocol', - name='exclusion_criteria', - field=models.ManyToManyField(related_name='meta_exclusion_criteria', null=True, to='epi.StudyCriteria', blank=True), - preserve_default=True, + model_name='studypopulation', + name='study', + field=models.ForeignKey(related_name='study_populations', to='study.Study'), ), migrations.AddField( - model_name='metaprotocol', - name='inclusion_criteria', - field=models.ManyToManyField(related_name='meta_inclusion_criteria', null=True, to='epi.StudyCriteria', blank=True), - preserve_default=True, + model_name='result', + name='adjustment_factors', + field=models.ManyToManyField(related_name='outcomes', through='epi.ResultAdjustmentFactor', to='epi.AdjustmentFactor', blank=True), ), migrations.AddField( - model_name='metaprotocol', - name='study', - field=models.ForeignKey(related_name='meta_protocols', to='study.Study'), - preserve_default=True, + model_name='result', + name='comparison_set', + field=models.ForeignKey(related_name='results', to='epi.ComparisonSet'), ), - migrations.AlterUniqueTogether( - name='factor', - unique_together=set([('assessment', 'description')]), + migrations.AddField( + model_name='result', + name='metric', + field=models.ForeignKey(related_name='results', to='epi.ResultMetric', help_text=b' '), ), migrations.AddField( - model_name='exposure', - name='study_population', - field=models.ForeignKey(related_name='exposures', to='epi.StudyPopulation'), - preserve_default=True, + model_name='result', + name='outcome', + field=models.ForeignKey(related_name='results', to='epi.Outcome'), ), migrations.AddField( - model_name='assessedoutcomegroup', - name='exposure_group', - field=models.ForeignKey(help_text=b'Exposure-group related to this assessed outcome group', to='epi.ExposureGroup'), - preserve_default=True, + model_name='outcome', + name='study_population', + field=models.ForeignKey(related_name='outcomes', to='epi.StudyPopulation'), ), migrations.AddField( - model_name='assessedoutcome', - name='adjustment_factors', - field=models.ManyToManyField(help_text=b'All factors which were included in final model', related_name='adjustments', null=True, to='epi.Factor', blank=True), - preserve_default=True, + model_name='groupresult', + name='result', + field=models.ForeignKey(related_name='results', to='epi.Result'), ), migrations.AddField( - model_name='assessedoutcome', - name='confounders_considered', - field=models.ManyToManyField(related_name='confounders', to='epi.Factor', blank=True, help_text=b'All factors which were examined (including those which were included in final model)', null=True, verbose_name=b'Adjustment factors considered'), - preserve_default=True, + model_name='Exposure', + name='study_population', + field=models.ForeignKey(related_name='exposures', to='epi.StudyPopulation'), ), migrations.AddField( - model_name='assessedoutcome', + model_name='comparisonset', name='exposure', - field=models.ForeignKey(related_name='outcomes', to='epi.Exposure'), - preserve_default=True, + field=models.ForeignKey(related_name='comparison_sets', blank=True, to='epi.Exposure', help_text=b'Exposure-group associated with this group', null=True), ), migrations.AddField( - model_name='assessedoutcome', - name='main_finding', - field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='epi.ExposureGroup', help_text=b'When a study did not report a statistically significant association use the highest exposure group compared with the referent group (e.g., fourth quartile vs. first quartile). When a study reports a statistically significant association use the lowest exposure group with a statistically significant association (e.g., third quartile vs. first quartile). When associations were non-monotonic in nature, select main findings on a case-by-case basis.', null=True, verbose_name=b'Main finding'), - preserve_default=True, + model_name='comparisonset', + name='outcome', + field=models.ForeignKey(related_name='comparison_sets', to='epi.Outcome', null=True), ), migrations.AddField( - model_name='assessedoutcome', - name='statistical_metric', - field=models.ForeignKey(to='epi.StatisticalMetric'), - preserve_default=True, + model_name='comparisonset', + name='study_population', + field=models.ForeignKey(related_name='comparison_sets', to='epi.StudyPopulation', null=True), + ), + migrations.AlterUniqueTogether( + name='criteria', + unique_together=set([('assessment', 'description')]), + ), + migrations.AlterUniqueTogether( + name='adjustmentfactor', + unique_together=set([('assessment', 'description')]), ), ] diff --git a/project/epi/migrations/0002_auto_20150629_1327.py b/project/epi/migrations/0002_auto_20150629_1327.py deleted file mode 100644 index b41e3431..00000000 --- a/project/epi/migrations/0002_auto_20150629_1327.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('epi', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='assessedoutcome', - name='adjustment_factors', - field=models.ManyToManyField(help_text=b'All factors which were included in final model', related_name='adjustments', to='epi.Factor', blank=True), - ), - migrations.AlterField( - model_name='assessedoutcome', - name='confounders_considered', - field=models.ManyToManyField(help_text=b'All factors which were examined (including those which were included in final model)', related_name='confounders', verbose_name=b'Adjustment factors considered', to='epi.Factor', blank=True), - ), - migrations.AlterField( - model_name='metaprotocol', - name='exclusion_criteria', - field=models.ManyToManyField(related_name='meta_exclusion_criteria', to='epi.StudyCriteria', blank=True), - ), - migrations.AlterField( - model_name='metaprotocol', - name='inclusion_criteria', - field=models.ManyToManyField(related_name='meta_inclusion_criteria', to='epi.StudyCriteria', blank=True), - ), - migrations.AlterField( - model_name='metaresult', - name='adjustment_factors', - field=models.ManyToManyField(help_text=b'All factors which were included in final model', related_name='meta_adjustments', to='epi.Factor', blank=True), - ), - migrations.AlterField( - model_name='studypopulation', - name='confounding_criteria', - field=models.ManyToManyField(related_name='confounding_criteria', to='epi.StudyCriteria', blank=True), - ), - migrations.AlterField( - model_name='studypopulation', - name='exclusion_criteria', - field=models.ManyToManyField(related_name='exclusion_criteria', to='epi.StudyCriteria', blank=True), - ), - migrations.AlterField( - model_name='studypopulation', - name='inclusion_criteria', - field=models.ManyToManyField(related_name='inclusion_criteria', to='epi.StudyCriteria', blank=True), - ), - ] diff --git a/project/epi2/migrations/0002_load_fixtures.py b/project/epi/migrations/0002_load_fixtures.py similarity index 75% rename from project/epi2/migrations/0002_load_fixtures.py rename to project/epi/migrations/0002_load_fixtures.py index f54a0c13..b4998c0a 100644 --- a/project/epi2/migrations/0002_load_fixtures.py +++ b/project/epi/migrations/0002_load_fixtures.py @@ -16,27 +16,27 @@ def load_fixture(apps, schema_editor): call_command( 'loaddata', os.path.join(fixture_dir, 'ethnicity.json'), - app_label='epi2') + app_label='epi') call_command( 'loaddata', os.path.join(fixture_dir, 'countries.json'), - app_label='epi2') + app_label='epi') call_command( 'loaddata', os.path.join(fixture_dir, 'resultmetric.json'), - app_label='epi2') + app_label='epi') def unload_fixture(apps, schema_editor): - apps.get_model("epi2", "Ethnicity").objects.all().delete() - apps.get_model("epi2", "Country").objects.all().delete() - apps.get_model("epi2", "ResultMetric").objects.all().delete() + apps.get_model("epi", "Ethnicity").objects.all().delete() + apps.get_model("epi", "Country").objects.all().delete() + apps.get_model("epi", "ResultMetric").objects.all().delete() class Migration(migrations.Migration): dependencies = [ - ('epi2', '0001_initial'), + ('epi', '0001_initial'), ] operations = [ diff --git a/project/epi/migrations/0003_auto_20150723_1544.py b/project/epi/migrations/0003_auto_20150723_1544.py deleted file mode 100644 index d8dba4ff..00000000 --- a/project/epi/migrations/0003_auto_20150723_1544.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('epi', '0002_auto_20150629_1327'), - ('animal', '0010_auto_20150723_1536'), - ] - - state_operations = [ - migrations.AlterField( - model_name='exposure', - name='metric_units', - field=models.ForeignKey(to='assessment.DoseUnits'), - ), - ] - - operations = [ - migrations.SeparateDatabaseAndState( - state_operations=state_operations) - ] diff --git a/project/epi/models.py b/project/epi/models.py index 7be9ba62..16943d22 100644 --- a/project/epi/models.py +++ b/project/epi/models.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # -*- coding: utf8 -*- - -import hashlib -import json +from operator import xor from django.db import models from django.core.urlresolvers import reverse @@ -13,11 +11,11 @@ import reversion from assessment.models import BaseEndpoint -from assessment.serializers import AssessmentSerializer -from utils.helper import HAWCDjangoJSONEncoder, SerializerHelper +from utils.models import get_crumbs +from utils.helper import SerializerHelper -class StudyCriteria(models.Model): +class Criteria(models.Model): assessment = models.ForeignKey( 'assessment.Assessment') description = models.TextField() @@ -29,454 +27,93 @@ class StudyCriteria(models.Model): class Meta: ordering = ('description', ) unique_together = ('assessment', 'description') + verbose_name_plural = "Criteria" def __unicode__(self): return self.description -class Ethnicity(models.Model): +class Country(models.Model): + code = models.CharField( + blank=True, + max_length=2) + name = models.CharField( + unique=True, + max_length=64) - # https://www.fsd1.org/powerschool/Documents/PDFs/Federal_Race_Ethnicity_Guidelines.pdf - ETHNICITY_CHOICES = ( - ('I', 'American Indian or Alaskan Native'), - ('A', 'Asian'), - ('B', 'Black or African American'), - ('H', 'Hispanic/Latino'), - ('P', 'Native American of Other Pacific Islander'), - ('M', 'Two or More Races'), - ('W', 'White'), - ('O', 'Other'), - ('U', 'Unknown/Unspecified')) - - ethnicity = models.CharField( - max_length=1, - choices=ETHNICITY_CHOICES) - created = models.DateTimeField( - auto_now_add=True) - last_updated = models.DateTimeField( - auto_now=True) + class Meta: + ordering = ('name', ) + verbose_name_plural = "Countries" def __unicode__(self): - return self.get_ethnicity_display() - - -class Demographics(models.Model): - - SEX_CHOICES = ( - ("U", "Not reported"), - ("M", "Male"), - ("F", "Female"), - ("B", "Male and Female")) - - MEAN_TYPE_CHOICES = ( - (0, None), - (1, "mean"), - (2, "geometric mean"), - (3, "median")) - - SD_TYPE_CHOICES = ( - (0, None), - (1, "SD"), - (2, "SEM")) - - AGE_LOWER_LIMIT_CHOICES = ( - (0, None), - (1, 'lower limit')) + return self.name - AGE_UPPER_LIMIT_CHOICES = ( - (0, None), - (1, 'upper limit')) - sex = models.CharField( - max_length=1, - choices=SEX_CHOICES) - ethnicity = models.ManyToManyField( - Ethnicity, - blank=True) - fraction_male = models.FloatField( - blank=True, - null=True, - help_text="Expects a value between 0 and 1, inclusive (leave blank if unknown)", - validators=[MinValueValidator(0), MaxValueValidator(1)]) - fraction_male_calculated = models.BooleanField( - default=False, - help_text="Was the fraction-male value calculated/estimated from literature?") - age_mean = models.FloatField( - blank=True, - null=True, - verbose_name='Age central estimate') - age_mean_type = models.PositiveSmallIntegerField( - choices=MEAN_TYPE_CHOICES, - verbose_name="Age central estimate type", - default=0) - age_calculated = models.BooleanField( - default=False, - help_text="Were age values calculated/estimated from literature?") - age_description = models.CharField( - max_length=128, - blank=True, - help_text="Age description if numeric ages do not make sense for this " - "study-population (ex: longitudinal studies)") - age_sd = models.FloatField( - blank=True, - null=True, - verbose_name='Age variance') - age_sd_type = models.PositiveSmallIntegerField( - choices=SD_TYPE_CHOICES, - verbose_name="Age variance type", - default=0) - age_lower = models.FloatField( - blank=True, - null=True) - age_lower_type = models.PositiveSmallIntegerField( - choices=AGE_LOWER_LIMIT_CHOICES, - default=0) - age_upper = models.FloatField( - blank=True, - null=True) - age_upper_type = models.PositiveSmallIntegerField( - choices=AGE_UPPER_LIMIT_CHOICES, - default=0) - n = models.PositiveIntegerField( - blank=True, - null=True) - starting_n = models.PositiveIntegerField( - blank=True, - null=True) +class AdjustmentFactor(models.Model): + assessment = models.ForeignKey( + 'assessment.Assessment') + description = models.TextField() created = models.DateTimeField( auto_now_add=True) last_updated = models.DateTimeField( auto_now=True) class Meta: - abstract = True - - @staticmethod - def flat_complete_header_row(prefix="-"): - return ( - prefix+'sex', - prefix+'ethnicity', - prefix+'fraction_male', - prefix+'fraction_male_calculated', - prefix+'age_mean', - prefix+'age_mean_type', - prefix+'age_calculated', - prefix+'age_description', - prefix+'age_sd', - prefix+'age_sd_type', - prefix+'age_lower', - prefix+'age_lower_type', - prefix+'age_upper', - prefix+'age_upper_type', - prefix+'n' - ) - - @staticmethod - def flat_complete_data_row(ser): - return ( - ser['sex'], - '|'.join(ser['ethnicity']), - ser['fraction_male'], - ser['fraction_male_calculated'], - ser['age_mean'], - ser['age_mean_type'], - ser['age_calculated'], - ser['age_description'], - ser['age_sd'], - ser['age_sd_type'], - ser['age_lower'], - ser['age_lower_type'], - ser['age_upper'], - ser['age_upper_type'], - ser['n'] - ) + unique_together = ('assessment', 'description') + ordering = ('description', ) - def get_ethnicity_list(self): - eths = [] - for eth in self.ethnicity.all(): - eths.append(eth.get_ethnicity_display()) - return ', '.join(eths) + def __unicode__(self): + return self.description -class Factor(models.Model): - assessment = models.ForeignKey( - 'assessment.Assessment') - description = models.TextField() +class Ethnicity(models.Model): + name = models.CharField( + max_length=64, + unique=True) created = models.DateTimeField( auto_now_add=True) last_updated = models.DateTimeField( auto_now=True) class Meta: - unique_together = ('assessment', 'description') - ordering = ('description', ) + verbose_name_plural = "Ethnicities" def __unicode__(self): - return self.description + return self.name -class StudyPopulation(Demographics): +class StudyPopulationCriteria(models.Model): + CRITERIA_TYPE = ( + ("I", "Inclusion"), + ("E", "Exclusion"), + ("C", "Confounding") + ) + criteria = models.ForeignKey( + 'Criteria', + related_name='spcriteria') + study_population = models.ForeignKey( + 'StudyPopulation', + related_name='spcriteria') + criteria_type = models.CharField( + max_length=1, + choices=CRITERIA_TYPE) + - EPI_STUDY_DESIGN_CHOICES = ( +class StudyPopulation(models.Model): + + DESIGN_CHOICES = ( + ('CO', 'Cohort'), + ('CX', 'Cohort (Retrospective)'), + ('CY', 'Cohort (Prospective)'), ('CC', 'Case-control'), - ('CS', 'Cross-sectional'), - ('CP', 'Prospective'), - ('RT', 'Retrospective'), + ('NC', 'Nested case-control'), + ('CR', 'Case report'), + ('SE', 'Case series'), ('CT', 'Controlled trial'), - ('SE', 'Case-series'), - ('CR', 'Case-report')) - - # https://www.iso.org/obp/ui/ - EPI_STUDY_COUNTRY_CHOICES = ( - ("AF", u"Afghanistan"), - ("AX", u"Åland Islands"), - ("AL", u"Albania"), - ("DZ", u"Algeria"), - ("AS", u"American Samoa"), - ("AD", u"Andorra"), - ("AO", u"Angola"), - ("AI", u"Anguilla"), - ("AQ", u"Antarctica"), - ("AG", u"Antigua And Barbuda"), - ("AR", u"Argentina"), - ("AM", u"Armenia"), - ("AW", u"Aruba"), - ("AU", u"Australia"), - ("AT", u"Austria"), - ("AZ", u"Azerbaijan"), - ("BS", u"Bahamas"), - ("BH", u"Bahrain"), - ("BD", u"Bangladesh"), - ("BB", u"Barbados"), - ("BY", u"Belarus"), - ("BE", u"Belgium"), - ("BZ", u"Belize"), - ("BJ", u"Benin"), - ("BM", u"Bermuda"), - ("BT", u"Bhutan"), - ("BO", u"Bolivia, Plurinational State Of"), - ("BQ", u"Bonaire, Sint Eustatius And Saba"), - ("BA", u"Bosnia And Herzegovina"), - ("BW", u"Botswana"), - ("BV", u"Bouvet Island"), - ("BR", u"Brazil"), - ("IO", u"British Indian Ocean Territory"), - ("BN", u"Brunei Darussalam"), - ("BG", u"Bulgaria"), - ("BF", u"Burkina Faso"), - ("BI", u"Burundi"), - ("KH", u"Cambodia"), - ("CM", u"Cameroon"), - ("CA", u"Canada"), - ("CV", u"Cape Verde"), - ("KY", u"Cayman Islands"), - ("CF", u"Central African Republic"), - ("TD", u"Chad"), - ("CL", u"Chile"), - ("CN", u"China"), - ("CX", u"Christmas Island"), - ("CC", u"Cocos (Keeling) Islands"), - ("CO", u"Colombia"), - ("KM", u"Comoros"), - ("CG", u"Congo"), - ("CD", u"Congo, The Democratic Republic Of The"), - ("CK", u"Cook Islands"), - ("CR", u"Costa Rica"), - ("CI", u"Côte D'Ivoire"), - ("HR", u"Croatia"), - ("CU", u"Cuba"), - ("CW", u"Curaçao"), - ("CY", u"Cyprus"), - ("CZ", u"Czech Republic"), - ("DK", u"Denmark"), - ("DJ", u"Djibouti"), - ("DM", u"Dominica"), - ("DO", u"Dominican Republic"), - ("EC", u"Ecuador"), - ("EG", u"Egypt"), - ("SV", u"El Salvador"), - ("GQ", u"Equatorial Guinea"), - ("ER", u"Eritrea"), - ("EE", u"Estonia"), - ("ET", u"Ethiopia"), - ("FK", u"Falkland Islands (Malvinas)"), - ("FO", u"Faroe Islands"), - ("FJ", u"Fiji"), - ("FI", u"Finland"), - ("FR", u"France"), - ("GF", u"French Guiana"), - ("PF", u"French Polynesia"), - ("TF", u"French Southern Territories"), - ("GA", u"Gabon"), - ("GM", u"Gambia"), - ("GE", u"Georgia"), - ("DE", u"Germany"), - ("GH", u"Ghana"), - ("GI", u"Gibraltar"), - ("GR", u"Greece"), - ("GL", u"Greenland"), - ("GD", u"Grenada"), - ("GP", u"Guadeloupe"), - ("GU", u"Guam"), - ("GT", u"Guatemala"), - ("GG", u"Guernsey"), - ("GN", u"Guinea"), - ("GW", u"Guinea-Bissau"), - ("GY", u"Guyana"), - ("HT", u"Haiti"), - ("HM", u"Heard Island And Mcdonald Islands"), - ("VA", u"Holy See (Vatican City State)"), - ("HN", u"Honduras"), - ("HK", u"Hong Kong"), - ("HU", u"Hungary"), - ("IS", u"Iceland"), - ("IN", u"India"), - ("ID", u"Indonesia"), - ("IR", u"Iran, Islamic Republic Of"), - ("IQ", u"Iraq"), - ("IE", u"Ireland"), - ("IM", u"Isle Of Man"), - ("IL", u"Israel"), - ("IT", u"Italy"), - ("JM", u"Jamaica"), - ("JP", u"Japan"), - ("JE", u"Jersey"), - ("JO", u"Jordan"), - ("KZ", u"Kazakhstan"), - ("KE", u"Kenya"), - ("KI", u"Kiribati"), - ("KP", u"Korea, Democratic People's Republic Of"), - ("KR", u"Korea, Republic Of"), - ("KW", u"Kuwait"), - ("KG", u"Kyrgyzstan"), - ("LA", u"Lao People's Democratic Republic"), - ("LV", u"Latvia"), - ("LB", u"Lebanon"), - ("LS", u"Lesotho"), - ("LR", u"Liberia"), - ("LY", u"Libya"), - ("LI", u"Liechtenstein"), - ("LT", u"Lithuania"), - ("LU", u"Luxembourg"), - ("MO", u"Macao"), - ("MK", u"Macedonia, The Former Yugoslav Republic Of"), - ("MG", u"Madagascar"), - ("MW", u"Malawi"), - ("MY", u"Malaysia"), - ("MV", u"Maldives"), - ("ML", u"Mali"), - ("MT", u"Malta"), - ("MH", u"Marshall Islands"), - ("MQ", u"Martinique"), - ("MR", u"Mauritania"), - ("MU", u"Mauritius"), - ("YT", u"Mayotte"), - ("MX", u"Mexico"), - ("FM", u"Micronesia, Federated States Of"), - ("MD", u"Moldova, Republic Of"), - ("MC", u"Monaco"), - ("MN", u"Mongolia"), - ("ME", u"Montenegro"), - ("MS", u"Montserrat"), - ("MA", u"Morocco"), - ("MZ", u"Mozambique"), - ("MM", u"Myanmar"), - ("NA", u"Namibia"), - ("NR", u"Nauru"), - ("NP", u"Nepal"), - ("NL", u"Netherlands"), - ("NC", u"New Caledonia"), - ("NZ", u"New Zealand"), - ("NI", u"Nicaragua"), - ("NE", u"Niger"), - ("NG", u"Nigeria"), - ("NU", u"Niue"), - ("NF", u"Norfolk Island"), - ("MP", u"Northern Mariana Islands"), - ("NO", u"Norway"), - ("OM", u"Oman"), - ("PK", u"Pakistan"), - ("PW", u"Palau"), - ("PS", u"Palestine, State Of"), - ("PA", u"Panama"), - ("PG", u"Papua New Guinea"), - ("PY", u"Paraguay"), - ("PE", u"Peru"), - ("PH", u"Philippines"), - ("PN", u"Pitcairn"), - ("PL", u"Poland"), - ("PT", u"Portugal"), - ("PR", u"Puerto Rico"), - ("QA", u"Qatar"), - ("RE", u"Réunion"), - ("RO", u"Romania"), - ("RU", u"Russian Federation"), - ("RW", u"Rwanda"), - ("BL", u"Saint Barthélemy"), - ("SH", u"Saint Helena, Ascension And Tristan Da Cunha"), - ("KN", u"Saint Kitts And Nevis"), - ("LC", u"Saint Lucia"), - ("MF", u"Saint Martin (French Part)"), - ("PM", u"Saint Pierre And Miquelon"), - ("VC", u"Saint Vincent And The Grenadines"), - ("WS", u"Samoa"), - ("SM", u"San Marino"), - ("ST", u"Sao Tome And Principe"), - ("SA", u"Saudi Arabia"), - ("SN", u"Senegal"), - ("RS", u"Serbia"), - ("SC", u"Seychelles"), - ("SL", u"Sierra Leone"), - ("SG", u"Singapore"), - ("SX", u"Sint Maarten (Dutch Part)"), - ("SK", u"Slovakia"), - ("SI", u"Slovenia"), - ("SB", u"Solomon Islands"), - ("SO", u"Somalia"), - ("ZA", u"South Africa"), - ("GS", u"South Georgia And The South Sandwich Islands"), - ("SS", u"South Sudan"), - ("ES", u"Spain"), - ("LK", u"Sri Lanka"), - ("SD", u"Sudan"), - ("SR", u"Suriname"), - ("SJ", u"Svalbard And Jan Mayen"), - ("SZ", u"Swaziland"), - ("SE", u"Sweden"), - ("CH", u"Switzerland"), - ("SY", u"Syrian Arab Republic"), - ("TW", u"Taiwan, Province Of China"), - ("TJ", u"Tajikistan"), - ("TZ", u"Tanzania, United Republic Of"), - ("TH", u"Thailand"), - ("TL", u"Timor-Leste"), - ("TG", u"Togo"), - ("TK", u"Tokelau"), - ("TO", u"Tonga"), - ("TT", u"Trinidad And Tobago"), - ("TN", u"Tunisia"), - ("TR", u"Turkey"), - ("TM", u"Turkmenistan"), - ("TC", u"Turks And Caicos Islands"), - ("TV", u"Tuvalu"), - ("UG", u"Uganda"), - ("UA", u"Ukraine"), - ("AE", u"United Arab Emirates"), - ("GB", u"United Kingdom"), - ("US", u"United States"), - ("UM", u"United States Minor Outlying Islands"), - ("UY", u"Uruguay"), - ("UZ", u"Uzbekistan"), - ("VU", u"Vanuatu"), - ("VE", u"Venezuela, Bolivarian Republic Of"), - ("VN", u"Viet Nam"), - ("VG", u"Virgin Islands, British"), - ("VI", u"Virgin Islands, U.S."), - ("WF", u"Wallis And Futuna"), - ("EH", u"Western Sahara"), - ("YE", u"Yemen"), - ("ZM", u"Zambia"), - ("ZW", u"Zimbabwe")) + ('CS', 'Cross-sectional'), + ) + + OUTCOME_GROUP_DESIGNS = ("CC", "NC") study = models.ForeignKey( 'study.Study', @@ -485,239 +122,156 @@ class StudyPopulation(Demographics): max_length=256) design = models.CharField( max_length=2, - choices=EPI_STUDY_DESIGN_CHOICES) - country = models.CharField( - max_length=2, - choices=EPI_STUDY_COUNTRY_CHOICES) + choices=DESIGN_CHOICES) + age_profile = models.CharField( + max_length=128, + blank=True, + help_text="Age profile of population (ex: adults, children, " + "pregnant women, etc.)") + source = models.CharField( + max_length=128, + blank=True, + help_text="Population source (ex: general population, environmental " + "exposure, occupational cohort)") + country = models.ForeignKey( + Country) region = models.CharField( max_length=128, blank=True) state = models.CharField( max_length=128, blank=True) - inclusion_criteria = models.ManyToManyField( - StudyCriteria, - related_name='inclusion_criteria', - blank=True) - exclusion_criteria = models.ManyToManyField( - StudyCriteria, - related_name='exclusion_criteria', - blank=True) - confounding_criteria = models.ManyToManyField( - StudyCriteria, - related_name='confounding_criteria', - blank=True) - - class Meta: - ordering = ('name', ) - - def __unicode__(self): - return self.name - - def get_location(self): - loc = "" - if self.country: - loc += self.get_country_display() - if self.state: - loc += " ({s})".format(s=self.state) - if self.region: - loc += " ({r})".format(r=self.region) - return loc - - def get_absolute_url(self): - return reverse('epi:sp_detail', kwargs={'pk': self.pk}) - - def get_assessment(self): - return self.study.get_assessment() - - def get_json(self, json_encode=True): - return SerializerHelper.get_serialized(self, json=json_encode, from_cache=False) + eligible_n = models.PositiveIntegerField( + blank=True, + null=True, + verbose_name="Eligible N") + invited_n = models.PositiveIntegerField( + blank=True, + null=True, + verbose_name="Invited N") + participant_n = models.PositiveIntegerField( + blank=True, + null=True, + verbose_name="Participant N") + criteria = models.ManyToManyField( + Criteria, + through=StudyPopulationCriteria, + related_name='populations') + comments = models.TextField( + blank=True, + help_text="Note matching criteria, etc.") + created = models.DateTimeField( + auto_now_add=True) + last_updated = models.DateTimeField( + auto_now=True) @staticmethod def flat_complete_header_row(): return ( - 'sp-pk', - 'sp-url', - 'sp-name', - 'sp-design', - 'sp-country', - 'sp-region', - 'sp-state', - 'sp-inclusion_criteria', - 'sp-exclusion_criteria', - 'sp-confounding_criteria' - ) + Demographics.flat_complete_header_row(prefix='sp-') + "sp-id", + "sp-url", + "sp-name", + "sp-design", + "sp-age_profile", + "sp-source", + "sp-country", + "sp-region", + "sp-state", + "sp-eligible_n", + "sp-invited_n", + "sp-participant_n", + "sp-inclusion_criteria", + "sp-exclusion_criteria", + "sp-confounding_criteria", + "sp-comments", + "sp-created", + "sp-last_updated", + ) @staticmethod def flat_complete_data_row(ser): - return ( - ser['id'], - ser['url'], - ser['name'], - ser['design'], - ser['country'], - ser['region'], - ser['state'], - '|'.join(ser['inclusion_criteria']), - '|'.join(ser['exclusion_criteria']), - '|'.join(ser['confounding_criteria']) - ) + Demographics.flat_complete_data_row(ser) + def getCriteriaList(lst, filt): + return u'|'.join([ + d['description'] for d in + filter(lambda (d): d['criteria_type'] == filt, lst) + ]) -class Exposure(models.Model): - study_population = models.ForeignKey( - StudyPopulation, - related_name="exposures") - inhalation = models.BooleanField( - default=False) - dermal = models.BooleanField( - default=False) - oral = models.BooleanField( - default=False) - in_utero = models.BooleanField( - default=False) - iv = models.BooleanField( - default=False, - verbose_name="Intravenous (IV)") - unknown_route = models.BooleanField( - default=False) - exposure_form_definition = models.TextField( - help_text='Name of exposure-route') - metric = models.TextField( - verbose_name="Measurement Metric") - metric_units = models.ForeignKey( - 'assessment.DoseUnits') - metric_description = models.TextField( - verbose_name="Measurement Description") - analytical_method = models.TextField( - help_text="Include details on the lab-techniques for exposure measurement in samples.") - control_description = models.TextField() - exposure_description = models.CharField( - max_length=128, - blank=True, - help_text='May be used to describe the exposure distribution, for ' - 'example, "2.05 µg/g creatinine (urine), geometric mean; ' - '25th percentile = 1.18, 75th percentile = 3.33"') - created = models.DateTimeField( - auto_now_add=True) - last_updated = models.DateTimeField( - auto_now=True) + return ( + ser["id"], + ser["url"], + ser["name"], + ser["design"], + ser["age_profile"], + ser["source"], + ser["country"], + ser["region"], + ser["state"], + ser["eligible_n"], + ser["invited_n"], + ser["participant_n"], + getCriteriaList(ser['criteria'], 'Inclusion'), + getCriteriaList(ser['criteria'], 'Exclusion'), + getCriteriaList(ser['criteria'], 'Confounding'), + ser["comments"], + ser["created"], + ser["last_updated"], + ) class Meta: - ordering = ('exposure_form_definition', ) - - def __unicode__(self): - return self.exposure_form_definition + ordering = ('name', ) def get_absolute_url(self): - return reverse('epi:exposure_detail', kwargs={'pk': self.pk}) + return reverse('epi:sp_detail', kwargs={'pk': self.pk}) def get_assessment(self): - return self.study_population.get_assessment() + return self.study.get_assessment() - @staticmethod - def flat_complete_header_row(): - return ( - 'exposure-pk', - 'exposure-url', - 'exposure-inhalation', - 'exposure-dermal', - 'exposure-oral', - 'exposure-in_utero', - 'exposure-iv', - 'exposure-unknown_route', - 'exposure-exposure_form_definition', - 'exposure-metric', - 'exposure-metric_units', - 'exposure-metric_description', - 'exposure-analytical_method', - 'exposure-control_description', - 'exposure-exposure_description' - ) + @property + def inclusion_criteria(self): + return self.criteria.filter(spcriteria__criteria_type="I") - @staticmethod - def flat_complete_data_row(ser): - return ( - ser['id'], - ser['url'], - ser['inhalation'], - ser['dermal'], - ser['oral'], - ser['in_utero'], - ser['iv'], - ser['unknown_route'], - ser['exposure_form_definition'], - ser['metric'], - ser['metric_units']['name'], - ser['metric_description'], - ser['analytical_method'], - ser['control_description'], - ser['exposure_description'] - ) + @property + def exclusion_criteria(self): + return self.criteria.filter(spcriteria__criteria_type="E") + @property + def confounding_criteria(self): + return self.criteria.filter(spcriteria__criteria_type="C") -class StatisticalMetric(models.Model): - metric = models.CharField( - max_length=128, - unique=True) - abbreviation = models.CharField( - max_length=32) - isLog = models.BooleanField( - default=True, - verbose_name="Log-results", - help_text="When plotting, use a log base 10 scale") - order = models.PositiveSmallIntegerField( - help_text="Order as they appear in option-list") + def __unicode__(self): + return self.name - class Meta: - ordering = ('order', ) + def get_crumbs(self): + return get_crumbs(self, self.study) - def __unicode__(self): - return self.metric + def can_create_sets(self): + return self.design not in self.OUTCOME_GROUP_DESIGNS -class AssessedOutcome(BaseEndpoint): +class Outcome(BaseEndpoint): DIAGNOSTIC_CHOICES = ( (0, 'not reported'), (1, 'medical professional or test'), (2, 'medical records'), - (3, 'self-reported')) - - MAIN_FINDING_CHOICES = ( - (3, "not-reported"), - (2, "supportive"), - (1, "inconclusive"), - (0, "not-supportive")) + (3, 'self-reported'), + (4, 'questionnaire'), + (5, 'hospital admission'), + (6, 'other'), + ) - DOSE_RESPONSE_CHOICES = ( - (0, "not-applicable"), - (1, "monotonic"), - (2, "non-monotonic"), - (3, "no trend"), - (4, "not reported")) - - STATISTICAL_POWER_CHOICES = ( - (0, 'not reported or calculated'), - (1, 'appears to be adequately powered (sample size met)'), - (2, 'somewhat underpowered (sample size is 75% to <100% of recommended)'), - (3, 'underpowered (sample size is 50 to <75% required)'), - (4, 'severely underpowered (sample size is <50% required)')) - - exposure = models.ForeignKey( - Exposure, + study_population = models.ForeignKey( + StudyPopulation, related_name='outcomes') - data_location = models.CharField( + system = models.CharField( max_length=128, blank=True, - help_text="Details on where the data are found in the literature " - "(ex: Figure 1, Table 2, etc.)") - population_description = models.CharField( + help_text="Relevant biological system") + effect = models.CharField( max_length=128, - help_text='Detailed description of the population being studied for this outcome, ' - 'which may be a subset of the entire study-population. For example, ' - '"US (national) NHANES 2003-2008, Hispanic children 6-18 years, ♂♀ (n=797)"', - blank=True) + blank=True, + help_text="Effect, using common-vocabulary") diagnostic = models.PositiveSmallIntegerField( choices=DIAGNOSTIC_CHOICES) diagnostic_description = models.TextField() @@ -725,105 +279,43 @@ class AssessedOutcome(BaseEndpoint): blank=True, null=True, verbose_name="Outcome N") - summary = models.TextField(blank=True, + summary = models.TextField( + blank=True, help_text='Summarize main findings of outcome, or describe why no ' 'details are presented (for example, "no association ' '(data not shown)")') - prevalence_incidence = models.TextField( - blank=True) - adjustment_factors = models.ManyToManyField(Factor, - help_text="All factors which were included in final model", - related_name='adjustments', - blank=True) - confounders_considered = models.ManyToManyField(Factor, - verbose_name= "Adjustment factors considered", - help_text="All factors which were examined " - "(including those which were included in final model)", - related_name='confounders', - blank=True) - dose_response = models.PositiveSmallIntegerField( - verbose_name="Dose Response Trend", - help_text="Was a dose-response trend observed?", - default=0, - choices=DOSE_RESPONSE_CHOICES) - dose_response_details = models.TextField( - blank=True) - statistical_power = models.PositiveSmallIntegerField( - help_text="Is the study sufficiently powered?", - default=0, - choices=STATISTICAL_POWER_CHOICES) - statistical_power_details = models.TextField( - blank=True) - main_finding = models.ForeignKey( - 'epi.ExposureGroup', - blank=True, - null=True, - on_delete=models.SET_NULL, - verbose_name="Main finding", - help_text="When a study did not report a statistically significant " - "association use the highest exposure group compared with " - "the referent group (e.g., fourth quartile vs. first " - "quartile). When a study reports a statistically " - "significant association use the lowest exposure group " - "with a statistically significant association (e.g., " - "third quartile vs. first quartile). When associations " - "were non-monotonic in nature, select main findings " - "on a case-by-case basis.") - main_finding_support = models.PositiveSmallIntegerField( - choices=MAIN_FINDING_CHOICES, - help_text="Are the results supportive of the main-finding?", - default=1) - statistical_metric = models.ForeignKey( - StatisticalMetric) - statistical_metric_description = models.TextField( - blank=True, - help_text="Add additional text describing the statistical metric used, if needed.") + + def get_json(self, json_encode=True): + return SerializerHelper.get_serialized(self, json=json_encode) @classmethod def delete_caches(cls, ids): SerializerHelper.delete_caches(cls, ids) def get_absolute_url(self): - return reverse('epi:assessedoutcome_detail', kwargs={'pk': self.pk}) + return reverse('epi:outcome_detail', kwargs={'pk': self.pk}) - def get_json(self, json_encode=True): - return SerializerHelper.get_serialized(self, json=json_encode) + def get_crumbs(self): + return get_crumbs(self, self.study_population) - def get_prior_versions_json(self): - """ Get prior versions of object. """ - versions = reversion.get_for_object(self) - versions_json = [] - for version in versions: - fields = version.field_dict - try: - fields['statistical_metric'] = unicode(StatisticalMetric.objects.get(pk=fields['statistical_metric'])) - except Exception: - fields['statistical_metric'] = "-" - return json.dumps(versions_json, cls=HAWCDjangoJSONEncoder) + def can_create_sets(self): + return not self.study_population.can_create_sets() @staticmethod def flat_complete_header_row(): return ( - 'assessed_outcome-pk', - 'assessed_outcome-url', - 'assessed_outcome-name', - 'assessed_outcome-data_location', - 'assessed_outcome-population_description', - 'assessed_outcome-effects', - 'assessed_outcome-diagnostic', - 'assessed_outcome-diagnostic_description', - 'assessed_outcome-outcome_n', - 'assessed_outcome-summary', - 'assessed_outcome-prevalence_incidence', - 'assessed_outcome-adjustment_factors', - 'assessed_outcome-adjustment_factors_considered', - 'assessed_outcome-main_finding_support', - 'assessed_outcome-dose_response', - 'assessed_outcome-dose_response_details', - 'assessed_outcome-statistical_power', - 'assessed_outcome-statistical_power_details', - 'assessed_outcome-statistical_metric', - 'assessed_outcome-statistical_metric_description' + "outcome-id", + "outcome-url", + "outcome-name", + "outcome-effects", + "outcome-system", + "outcome-effect", + "outcome-diagnostic", + "outcome-diagnostic_description", + "outcome-outcome_n", + "outcome-summary", + "outcome-created", + "outcome-last_updated", ) @staticmethod @@ -832,814 +324,794 @@ def flat_complete_data_row(ser): ser['id'], ser['url'], ser['name'], - ser['data_location'], - ser['population_description'], '|'.join([unicode(d['name']) for d in ser['effects']]), + ser['system'], + ser['effect'], ser['diagnostic'], ser['diagnostic_description'], ser['outcome_n'], ser['summary'], - ser['prevalence_incidence'], - '|'.join(ser['adjustment_factors']), - '|'.join(ser['confounders_considered']), - ser['main_finding_support'], - ser['dose_response'], - ser['dose_response_details'], - ser['statistical_power'], - ser['statistical_power_details'], - ser['statistical_metric']['metric'], - ser['statistical_metric_description'] + ser['created'], + ser['last_updated'], ) - @classmethod - def get_docx_template_context(cls, assessment, queryset): - """ - Given a queryset of assessed outcomes, invert the cached results to build - a top-down data hierarchy from study to assessed outcome. We use this - approach since our assessed outcomes are cached, so while it may require - more computation, its close to free on database access. - """ - - def getStatMethods(ao): - v = { - "adjustments_list": ao["adjustments_list"], - "statistical_metric": ao["statistical_metric"]["metric"], - "statistical_metric_description": ao["statistical_metric_description"], - "endpoints": [] - } - k = u"{}|{}|{}".format(v["adjustments_list"], v["statistical_metric"], v["statistical_metric_description"]) - return hashlib.md5(k.encode('UTF-8')).hexdigest(), v - - def getCustomAge(sp): - txt = sp["age_description"] - - rng = sp["age_lower"] is not None and sp["age_upper"] is not None - if rng: - rng = u"{}-{} years".format(sp["age_lower"], sp["age_upper"]) - - if sp["age_mean"] is not None: - txt = u"{} ({})".format(sp["age_mean"], sp["age_mean_type"]) - if rng: - txt += u", from {}".format(rng) - elif rng: - txt = rng - - return txt - - outcomes = [ - SerializerHelper.get_serialized(obj, json=False) - for obj in queryset - ] - studies = {} - - # flip dictionary nesting - for thisAO in outcomes: - thisExp = thisAO["exposure"] - thisSP = thisAO["exposure"]["study_population"] - thisStudy = thisAO["exposure"]["study_population"]["study"] - - study = studies.get(thisStudy["id"]) - if study is None: - study = thisStudy - study["sps"] = {} - studies[study["id"]] = study - - sp = study["sps"].get(thisSP["id"]) - if sp is None: - sp = thisSP - sp["_age"] = getCustomAge(sp) - sp["ethnicities"] = u', '.join(sp["ethnicity"]) - sp["inclusion_list"] = u', '.join(sp["inclusion_criteria"]) - sp["exclusion_list"] = u', '.join(sp["exclusion_criteria"]) - sp["exposures"] = {} - study["sps"][sp["id"]] = sp - - exposure = sp["exposures"].get(thisExp["id"]) - if exposure is None: - exposure = thisExp - exposure["aos"] = {} - exposure["statistical_methods"] = {} - sp["exposures"][exposure["id"]] = exposure - - ao = exposure["aos"].get(thisAO["id"]) - if ao is None: - ao = thisAO - ao["adjustments_list"] = u', '.join(sorted(ao["adjustment_factors"])) - exposure["aos"][ao["id"]] = ao - - key, val = getStatMethods(ao) - stat_methods = exposure['statistical_methods'].get(key) - if not stat_methods: - exposure['statistical_methods'][key] = val - stat_methods = val - stat_methods["endpoints"].append(ao["name"]) - - # convert value dictionaries to lists - studies = sorted( - studies.values(), - key=lambda obj: (obj["short_citation"].lower())) - for study in studies: - study["sps"] = sorted( - study["sps"].values(), - key=lambda obj: (obj["name"].lower())) - for sp in study["sps"]: - sp["exposures"] = sorted( - sp["exposures"].values(), - key=lambda obj: (obj["exposure_form_definition"].lower())) - for exp in sp["exposures"]: - exp["aos"] = sorted( - exp["aos"].values(), - key=lambda obj: (obj["name"].lower())) - for i, ao in enumerate(exp["aos"]): - ao["character"] = chr(65+i) - exp["statistical_methods"] = exp["statistical_methods"].values() - for obj in exp["statistical_methods"]: - obj["endpoints_list"] = u"; ".join(obj["endpoints"]) - - return { - "assessment": AssessmentSerializer().to_representation(assessment), - "studies": studies - } - - -class ExposureGroup(Demographics): - exposure = models.ForeignKey( - Exposure, - related_name='groups') - description = models.CharField( + +class ComparisonSet(models.Model): + study_population = models.ForeignKey( + StudyPopulation, + related_name='comparison_sets', + null=True) + outcome = models.ForeignKey( + Outcome, + related_name='comparison_sets', + null=True) + name = models.CharField( max_length=256) - exposure_numeric = models.FloatField( - verbose_name='Low exposure value (sorting)', - help_text='Should be an exposure-value used for sorting', - blank=True, null=True) - comparative_name = models.CharField( - max_length=64, - verbose_name="Comparative Name", - help_text='Should include effect-group and comparative group, for example ' - '"1.5-2.5(Q2) vs ≤1.5(Q1)", or if only one group is available, ' - '"4.8±0.2 (mean±SEM)"', - blank=True) - exposure_group_id = models.PositiveSmallIntegerField() - exposure_n = models.PositiveSmallIntegerField( + exposure = models.ForeignKey( + "Exposure", + related_name="comparison_sets", + help_text="Exposure-group associated with this group", blank=True, - null=True, - help_text="Final N used for exposure group") + null=True) + description = models.TextField( + blank=True) + created = models.DateTimeField( + auto_now_add=True) + last_updated = models.DateTimeField( + auto_now=True) class Meta: - ordering = ('exposure_group_id', ) + ordering = ('name', ) - def __unicode__(self): - return self.description + def save(self, *args, **kwargs): + if not xor(self.outcome is None, self.study_population is None): + raise ValueError("An outcome or study-population is required.") + super(ComparisonSet, self).save(*args, **kwargs) + + def get_absolute_url(self): + return reverse('epi:cs_detail', kwargs={'pk': self.pk}) def get_assessment(self): - return self.exposure.get_assessment() + if self.outcome: + return self.outcome.get_assessment() + else: + return self.study_population.get_assessment() + + def __unicode__(self): + return self.name + + def get_crumbs(self): + if self.outcome: + return get_crumbs(self, self.outcome) + else: + return get_crumbs(self, self.study_population) @staticmethod def flat_complete_header_row(): return ( - 'exposure_group-pk', - 'exposure_group-description', - 'exposure_group-exposure_numeric', - 'exposure_group-comparative_name', - 'exposure_group-exposure_group_id', - 'exposure_group-exposure_n' - ) + Demographics.flat_complete_header_row(prefix='exposure-group-') + "cs-id", + "cs-url", + "cs-name", + "cs-description", + "cs-created", + "cs-last_updated", + ) @staticmethod def flat_complete_data_row(ser): return ( - ser['id'], - ser['description'], - ser['exposure_numeric'], - ser['comparative_name'], - ser['exposure_group_id'], - ser['exposure_n'] - ) + Demographics.flat_complete_data_row(ser) + ser["id"], + ser["url"], + ser["name"], + ser["description"], + ser["created"], + ser["last_updated"], + ) -class AssessedOutcomeGroup(models.Model): +class Group(models.Model): + SEX_CHOICES = ( + ("U", "Not reported"), + ("M", "Male"), + ("F", "Female"), + ("B", "Male and Female")) - P_VALUE_QUALIFIER_CHOICES = ( - ('<', '<'), - ('=', '='), - ('-', 'n.s.')) + IS_CONTROL_CHOICES = ( + (True, "Yes"), + (False, "No"), + (None, "N/A"), + ) - exposure_group = models.ForeignKey( - ExposureGroup, - help_text="Exposure-group related to this assessed outcome group") - assessed_outcome = models.ForeignKey( - AssessedOutcome, + comparison_set = models.ForeignKey( + ComparisonSet, related_name="groups") - n = models.PositiveIntegerField( + group_id = models.PositiveSmallIntegerField() + name = models.CharField( + max_length=256) + numeric = models.FloatField( + verbose_name='Numerical value (sorting)', + help_text='Numerical value, can be used for sorting', blank=True, - null=True, - help_text="Individuals in group where outcome was measured") - estimate = models.FloatField( + null=True) + comparative_name = models.CharField( + max_length=64, + verbose_name="Comparative Name", + help_text='Group and value, displayed in plots, for example ' + '"1.5-2.5(Q2) vs ≤1.5(Q1)", or if only one group is ' + 'available, "4.8±0.2 (mean±SEM)"', + blank=True) + sex = models.CharField( + max_length=1, + default="U", + choices=SEX_CHOICES) + ethnicities = models.ManyToManyField( + Ethnicity, + blank=True) + eligible_n = models.PositiveIntegerField( blank=True, null=True, - help_text="Central tendency estimate for group") - se = models.FloatField( + verbose_name="Eligible N") + invited_n = models.PositiveIntegerField( blank=True, null=True, - verbose_name='Standard Error (SE)', - help_text="Standard error estimate for group") - lower_ci = models.FloatField( + verbose_name="Invited N") + participant_n = models.PositiveIntegerField( blank=True, null=True, - verbose_name='Lower CI', - help_text="Numerical value for lower-confidence interval") - upper_ci = models.FloatField( + verbose_name="Participant N") + isControl = models.NullBooleanField( + verbose_name="Control?", + default=None, + choices=IS_CONTROL_CHOICES, + help_text="Should this group be interpreted as a null/control group") + comments = models.TextField( + blank=True, + help_text="Any other comments related to this group") + created = models.DateTimeField( + auto_now_add=True) + last_updated = models.DateTimeField( + auto_now=True) + + class Meta: + ordering = ('comparison_set', 'group_id', ) + + def get_absolute_url(self): + return reverse('epi:g_detail', kwargs={'pk': self.pk}) + + def get_assessment(self): + return self.comparison_set.get_assessment() + + def __unicode__(self): + return self.name + + def get_crumbs(self): + return get_crumbs(self, self.comparison_set) + + @staticmethod + def flat_complete_header_row(): + return ( + "group-id", + "group-group_id", + "group-name", + "group-numeric", + "group-comparative_name", + "group-sex", + "group-ethnicities", + "group-eligible_n", + "group-invited_n", + "group-participant_n", + "group-isControl", + "group-comments", + "group-created", + "group-last_updated", + ) + + @staticmethod + def flat_complete_data_row(ser): + return ( + ser['id'], + ser['group_id'], + ser['name'], + ser['numeric'], + ser['comparative_name'], + ser['sex'], + u"|".join([d["name"] for d in ser['ethnicities']]), + ser['eligible_n'], + ser['invited_n'], + ser['participant_n'], + ser['isControl'], + ser['comments'], + ser['created'], + ser['last_updated'], + ) + + +class Exposure(models.Model): + study_population = models.ForeignKey( + StudyPopulation, + related_name='exposures') + name = models.CharField( + max_length=128, + help_text='Name of exposure and exposure-route') + inhalation = models.BooleanField( + default=False) + dermal = models.BooleanField( + default=False) + oral = models.BooleanField( + default=False) + in_utero = models.BooleanField( + default=False) + iv = models.BooleanField( + default=False, + verbose_name="Intravenous (IV)") + unknown_route = models.BooleanField( + default=False) + measured = models.CharField( + max_length=128, blank=True, - null=True, - verbose_name='Upper CI', - help_text="Numerical value for upper-confidence interval") - ci_units = models.FloatField( + verbose_name="What was measured") + metric = models.CharField( + max_length=128, + verbose_name="Measurement Metric") + metric_units = models.ForeignKey( + 'assessment.DoseUnits') + metric_description = models.TextField( + verbose_name="Measurement Description") + analytical_method = models.TextField( + help_text="Include details on the lab-techniques for exposure " + "measurement in samples.") + sampling_period = models.CharField( + max_length=128, + help_text='Exposure sampling period', + blank=True) + duration = models.CharField( + max_length=128, blank=True, - null=True, - default=0.95, - verbose_name='Confidence Interval (CI)', - help_text='A 95% CI is written as 0.95.') - p_value_qualifier = models.CharField( - max_length=1, - choices=P_VALUE_QUALIFIER_CHOICES, - default="-", - verbose_name='p-value qualifier') - p_value = models.FloatField( + help_text='Exposure duration') + exposure_distribution = models.CharField( + max_length=128, blank=True, - null=True, - verbose_name='p-value') + help_text='May be used to describe the exposure distribution, for ' + 'example, "2.05 µg/g creatinine (urine), geometric mean; ' + '25th percentile = 1.18, 75th percentile = 3.33"') + description = models.TextField( + blank=True) created = models.DateTimeField( auto_now_add=True) last_updated = models.DateTimeField( auto_now=True) class Meta: - ordering = ('exposure_group__exposure_group_id', ) + ordering = ('name', ) + verbose_name = "Exposure" + verbose_name_plural = "Exposures" def __unicode__(self): - txt = u"%s: %s" % (self.exposure_group, self.estimate) - if self.se: - txt += u" (SE: %s)" % (self.se) - if self.lower_ci and self.upper_ci: - txt += u" (%s-%s)" % (self.lower_ci, self.upper_ci) - return txt - - @property - def p_value_text(self): - if self.p_value is None: - return "n.s." - txt = self.get_p_value_qualifier_display() - if txt != "n.s.": - txt += str(self.p_value) - return txt + return self.name def get_assessment(self): - return self.assessed_outcome.get_assessment() + return self.study_population.get_assessment() - @property - def estimate_formatted(self): - txt = "-" - if self.estimate: - txt = unicode(self.estimate) - if (self.lower_ci is not None and self.upper_ci is not None): - txt += u' ({}, {})'.format(self.lower_ci, self.upper_ci) - return txt + def get_absolute_url(self): + return reverse('epi:exp_detail', kwargs={'pk': self.pk}) - @property - def isMainFinding(self): - return self.assessed_outcome.main_finding_id == self.exposure_group_id + def get_crumbs(self): + return get_crumbs(self, self.study_population) @staticmethod def flat_complete_header_row(): return ( - 'assessed_outcome_group-pk', - 'assessed_outcome_group-n', - 'assessed_outcome_group-estimate', - 'assessed_outcome_group-se', - 'assessed_outcome_group-lower_ci', - 'assessed_outcome_group-upper_ci', - 'assessed_outcome_group-ci_units', - 'assessed_outcome_group-main_finding', # AssessedOutcome.main_finding - 'assessed_outcome_group-p_value_text' - ) + ExposureGroup.flat_complete_header_row() + "exposure-id", + "exposure-url", + "exposure-name", + "exposure-inhalation", + "exposure-dermal", + "exposure-oral", + "exposure-in_utero", + "exposure-iv", + "exposure-unknown_route", + "exposure-measured", + "exposure-metric", + "exposure-metric_units_id", + "exposure-metric_units_name", + "exposure-metric_description", + "exposure-analytical_method", + "exposure-sampling_period", + "exposure-duration", + "exposure-exposure_distribution", + "exposure-description", + "exposure-created", + "exposure-last_updated", + ) @staticmethod def flat_complete_data_row(ser): return ( - ser['id'], - ser['n'], - ser['estimate'], - ser['se'], - ser['lower_ci'], - ser['upper_ci'], - ser['ci_units'], - ser['isMainFinding'], - ser['p_value_text'] - ) + ExposureGroup.flat_complete_data_row(ser['exposure_group']) + ser["id"], + ser["url"], + ser["name"], + ser["inhalation"], + ser["dermal"], + ser["oral"], + ser["in_utero"], + ser["iv"], + ser["unknown_route"], + ser["measured"], + ser["metric"], + ser["metric_units"]["id"], + ser["metric_units"]["name"], + ser["metric_description"], + ser["analytical_method"], + ser["sampling_period"], + ser["duration"], + ser["exposure_distribution"], + ser["description"], + ser["created"], + ser["last_updated"], + ) + +class GroupNumericalDescriptions(models.Model): + + MEAN_TYPE_CHOICES = ( + (0, None), + (1, "mean"), + (2, "geometric mean"), + (3, "median"), + (4, "other")) -class MetaProtocol(models.Model): + VARIANCE_TYPE_CHOICES = ( + (0, None), + (1, "SD"), + (2, "SEM"), + (3, "GSD"), + (4, "other")) - META_PROTOCOL_CHOICES = ( - (0, "Meta-analysis"), - (1, "Pooled-analysis")) + LOWER_LIMIT_CHOICES = ( + (0, None), + (1, 'lower limit'), + (2, '5% CI'), + (3, 'other')) - META_LIT_SEARCH_CHOICES = ( - (0, "Systematic"), - (1, "Other")) + UPPER_LIMIT_CHOICES = ( + (0, None), + (1, 'upper limit'), + (2, '95% CI'), + (3, 'other')) - study = models.ForeignKey('study.Study', - related_name="meta_protocols") - name = models.CharField( - verbose_name="Protocol name", - max_length=128) - protocol_type = models.PositiveSmallIntegerField( - choices=META_PROTOCOL_CHOICES, + group = models.ForeignKey( + Group, + related_name="descriptions") + description = models.CharField( + max_length=128, + help_text="Description if numeric ages do not make sense for this " + "study-population (ex: longitudinal studies)") + mean = models.FloatField( + blank=True, + null=True, + verbose_name='Central estimate') + mean_type = models.PositiveSmallIntegerField( + choices=MEAN_TYPE_CHOICES, + verbose_name="Central estimate type", default=0) - lit_search_strategy = models.PositiveSmallIntegerField( - verbose_name="Literature search strategy", - choices=META_LIT_SEARCH_CHOICES, + is_calculated = models.BooleanField( + default=False, + help_text="Was value calculated/estimated from literature?") + variance = models.FloatField( + blank=True, + null=True) + variance_type = models.PositiveSmallIntegerField( + choices=VARIANCE_TYPE_CHOICES, default=0) - lit_search_notes = models.TextField( - verbose_name="Literature search notes", - blank=True) - lit_search_start_date = models.DateField( - verbose_name="Literature search start-date", + lower = models.FloatField( blank=True, null=True) - lit_search_end_date = models.DateField( - verbose_name="Literature search end-date", + lower_type = models.PositiveSmallIntegerField( + choices=LOWER_LIMIT_CHOICES, + default=0) + upper = models.FloatField( blank=True, null=True) - total_references = models.PositiveIntegerField( - verbose_name="Total number of references found", - help_text="References identified through initial literature-search " - "before application of inclusion/exclusion criteria", + upper_type = models.PositiveSmallIntegerField( + choices=UPPER_LIMIT_CHOICES, + default=0) + + def __unicode__(self): + return self.description + + +class ResultMetric(models.Model): + metric = models.CharField( + max_length=128, + unique=True) + abbreviation = models.CharField( + max_length=32) + isLog = models.BooleanField( + default=True, + verbose_name="Display as log", + help_text="When plotting, use a log base 10 scale") + showForestPlot = models.BooleanField( + default=True, + verbose_name="Show on forest plot", + help_text="Does forest-plot representation of result make sense?") + reference_value = models.FloatField( + help_text="Null hypothesis value for reference, if applicable", + default=1, blank=True, null=True) - inclusion_criteria = models.ManyToManyField( - StudyCriteria, - related_name='meta_inclusion_criteria', - blank=True) - exclusion_criteria = models.ManyToManyField( - StudyCriteria, - related_name='meta_exclusion_criteria', - blank=True) - total_studies_identified = models.PositiveIntegerField( - verbose_name="Total number of studies identified", - help_text="Total references identified for inclusion after application " - "of literature review and screening criteria") - notes = models.TextField(blank=True) + order = models.PositiveSmallIntegerField( + help_text="Order as they appear in option-list") class Meta: - ordering = ('name', ) + ordering = ('order', ) def __unicode__(self): - return self.name + return self.metric - def get_assessment(self): - return self.study.get_assessment() - def get_absolute_url(self): - return reverse('epi:mp_detail', kwargs={'pk': self.pk}) +class ResultAdjustmentFactor(models.Model): + adjustment_factor = models.ForeignKey('AdjustmentFactor', + related_name='resfactors') + result = models.ForeignKey('Result', + related_name='resfactors') + included_in_final_model = models.BooleanField(default=True) - def get_json(self, json_encode=True): - return SerializerHelper.get_serialized(self, json=json_encode, from_cache=False) - @staticmethod - def flat_complete_header_row(): - return ( - 'meta_protocol-pk', - 'meta_protocol-url', - 'meta_protocol-name', - 'meta_protocol-protocol_type', - 'meta_protocol-lit_search_strategy', - 'meta_protocol-lit_search_notes', - 'meta_protocol-lit_search_start_date', - 'meta_protocol-lit_search_end_date', - 'meta_protocol-total_references', - 'meta_protocol-inclusion_criteria', - 'meta_protocol-exclusion_criteria', - 'meta_protocol-total_studies_identified', - 'meta_protocol-notes', - ) +class Result(models.Model): - @staticmethod - def flat_complete_data_row(ser): - return ( - ser['id'], - ser['url'], - ser['name'], - ser['protocol_type'], - ser['lit_search_strategy'], - ser['lit_search_notes'], - ser['lit_search_start_date'], - ser['lit_search_end_date'], - ser['total_references'], - u'|'.join(ser['inclusion_criteria']), - u'|'.join(ser['exclusion_criteria']), - ser['total_studies_identified'], - ser['notes'] - ) + DOSE_RESPONSE_CHOICES = ( + (0, "not-applicable"), + (1, "monotonic"), + (2, "non-monotonic"), + (3, "no trend"), + (4, "not reported")) + + STATISTICAL_POWER_CHOICES = ( + (0, 'not reported or calculated'), + (1, 'appears to be adequately powered (sample size met)'), + (2, 'somewhat underpowered (sample size is 75% to <100% of recommended)'), + (3, 'underpowered (sample size is 50 to <75% required)'), + (4, 'severely underpowered (sample size is <50% required)')) + + ESTIMATE_TYPE_CHOICES = ( + (0, None), + (1, "mean"), + (2, "geometric mean"), + (3, "median"), + (5, "point"), + (4, "other"), + ) + VARIANCE_TYPE_CHOICES = ( + (0, None), + (1, "SD"), + (2, "SEM"), + (3, "GSD"), + (4, "other")) -class MetaResult(models.Model): - protocol = models.ForeignKey( - MetaProtocol, + outcome = models.ForeignKey( + Outcome, + related_name="results") + comparison_set = models.ForeignKey( + ComparisonSet, related_name="results") - label = models.CharField( - max_length=128) + metric = models.ForeignKey( + ResultMetric, + related_name="results", + help_text=" ") + metric_description = models.TextField( + blank=True, + help_text="Add additional text describing the metric used, if needed.") data_location = models.CharField( max_length=128, blank=True, help_text="Details on where the data are found in the literature " "(ex: Figure 1, Table 2, etc.)") - health_outcome = models.CharField( - max_length=128) - health_outcome_notes = models.TextField( + population_description = models.CharField( + max_length=128, + help_text='Detailed description of the population being studied for' + 'this outcome, which may be a subset of the entire' + 'study-population. For example, "US (national) NHANES' + '2003-2008, Hispanic children 6-18 years, ♂♀ (n=797)"', blank=True) - exposure_name = models.CharField( - max_length=128) - exposure_details = models.TextField( + dose_response = models.PositiveSmallIntegerField( + verbose_name="Dose Response Trend", + help_text="Was a trend observed?", + default=0, + choices=DOSE_RESPONSE_CHOICES) + dose_response_details = models.TextField( blank=True) - number_studies = models.PositiveSmallIntegerField() - statistical_metric = models.ForeignKey( - StatisticalMetric) - statistical_notes = models.TextField( + prevalence_incidence = models.CharField( + max_length=128, + verbose_name="Overall incidence prevalence", blank=True) - n = models.PositiveIntegerField( - help_text="Number of individuals included from all analyses") - estimate = models.FloatField() - lower_ci = models.FloatField( - verbose_name="Lower CI", - help_text="Numerical value for lower-confidence interval") - upper_ci = models.FloatField( - verbose_name="Upper CI", - help_text="Numerical value for upper-confidence interval") + statistical_power = models.PositiveSmallIntegerField( + help_text="Is the study sufficiently powered?", + default=0, + choices=STATISTICAL_POWER_CHOICES) + statistical_power_details = models.TextField( + blank=True) + trend_test = models.CharField( + verbose_name="Trend test result", + max_length=128, + blank=True, + help_text=u"Enter result, if available (ex: p=0.015, p≤0.05, n.s., etc.)") + adjustment_factors = models.ManyToManyField( + AdjustmentFactor, + through=ResultAdjustmentFactor, + related_name='outcomes', + blank=True) + estimate_type = models.PositiveSmallIntegerField( + choices=ESTIMATE_TYPE_CHOICES, + verbose_name="Central estimate type", + default=0) + variance_type = models.PositiveSmallIntegerField( + choices=VARIANCE_TYPE_CHOICES, + default=0) ci_units = models.FloatField( blank=True, null=True, default=0.95, verbose_name='Confidence Interval (CI)', help_text='A 95% CI is written as 0.95.') - heterogeneity = models.CharField( - max_length=256, - blank=True) - adjustment_factors = models.ManyToManyField( - Factor, - help_text="All factors which were included in final model", - related_name='meta_adjustments', - blank=True) - notes = models.TextField( - blank=True) + comments = models.TextField( + blank=True, + help_text='Summarize main findings of outcome, or describe why no ' + 'details are presented (for example, "no association ' + '(data not shown)")') + created = models.DateTimeField( + auto_now_add=True) + last_updated = models.DateTimeField( + auto_now=True) - class Meta: - ordering = ('label', ) + @property + def factors_applied(self): + return self.adjustment_factors\ + .filter(resfactors__included_in_final_model=True) + + @property + def factors_considered(self): + return self.adjustment_factors\ + .filter(resfactors__included_in_final_model=False) def __unicode__(self): - return self.label + return u"{0}: {1}".format(self.comparison_set, self.metric) def get_assessment(self): - return self.protocol.get_assessment() + return self.outcome.get_assessment() def get_absolute_url(self): - return reverse('epi:mr_detail', kwargs={'pk': self.pk}) - - @property - def estimate_formatted(self): - txt = "-" - if self.estimate: - txt = unicode(self.estimate) - if (self.lower_ci and self.upper_ci): - txt += u' ({}, {})'.format(self.lower_ci, self.upper_ci) - return txt - - @classmethod - def delete_caches(cls, pks): - SerializerHelper.delete_caches(cls, pks) + return reverse('epi:result_detail', kwargs={'pk': self.pk}) - def get_json(self, json_encode=True): - return SerializerHelper.get_serialized(self, json=json_encode) + def get_crumbs(self): + return get_crumbs(self, self.outcome) @staticmethod def flat_complete_header_row(): return ( - 'meta_result-pk', - 'meta_result-url', - 'meta_result-label', - 'meta_result-data_location', - 'meta_result-health_outcome', - 'meta_result-health_outcome_notes', - 'meta_result-exposure_name', - 'meta_result-exposure_details', - 'meta_result-number_studies', - 'meta_result-statistical_metric', - 'meta_result-statistical_notes', - 'meta_result-n', - 'meta_result-estimate', - 'meta_result-lower_ci', - 'meta_result-upper_ci', - 'meta_result-ci_units', - 'meta_result-heterogeneity', - 'meta_result-adjustment_factors', - 'meta_result-notes', + "metric-id", + "metric-name", + "metric-abbreviation", + "result-id", + "result-metric_description", + "result-data_location", + "result-population_description", + "result-dose_response", + "result-dose_response_details", + "result-prevalence_incidence", + "result-statistical_power", + "result-statistical_power_details", + "result-trend_test", + "result-adjustment_factors", + "result-adjustment_factors_considered", + "result-estimate_type", + "result-variance_type", + "result-ci_units", + "result-comments", + "result-created", + "result-last_updated", ) @staticmethod def flat_complete_data_row(ser): + + def getFactorList(lst, isIncluded): + return u'|'.join([ + d['description'] for d in + filter(lambda (d): d['included_in_final_model'] == isIncluded, lst) + ]) + return ( + ser['metric']['id'], + ser['metric']['metric'], + ser['metric']['abbreviation'], ser['id'], - ser['url'], - ser['label'], + ser['metric_description'], ser['data_location'], - ser['health_outcome'], - ser['health_outcome_notes'], - ser['exposure_name'], - ser['exposure_details'], - ser['number_studies'], - ser['statistical_metric']['metric'], - ser['statistical_notes'], - ser['n'], - ser['estimate'], - ser['lower_ci'], - ser['upper_ci'], + ser['population_description'], + ser['dose_response'], + ser['dose_response_details'], + ser['prevalence_incidence'], + ser['statistical_power'], + ser['statistical_power_details'], + ser['trend_test'], + getFactorList(ser['factors'], True), + getFactorList(ser['factors'], False), + ser['estimate_type'], + ser['variance_type'], ser['ci_units'], - ser['heterogeneity'], - u'|'.join(ser['adjustment_factors']), - ser['notes'], + ser['comments'], + ser['created'], + ser['last_updated'], ) - @classmethod - def get_docx_template_context(cls, assessment, queryset): - """ - Given a queryset of meta-results, invert the cached results to build - a top-down data hierarchy from study to meta-result. We use this - approach since our meta-results are cached, so while it may require - more computation, its close to free on database access. - """ - - def getStatMethods(mr): - key = u"{}|{}".format( - mr["adjustments_list"], - mr["statistical_notes"] - ) - return key, mr - - results = [ - SerializerHelper.get_serialized(obj, json=False) - for obj in queryset - ] - studies = {} - - # flip dictionary nesting - for thisMr in results: - thisPro = thisMr["protocol"] - thisStudy = thisMr["protocol"]["study"] - - study = studies.get(thisStudy["id"]) - if study is None: - study = thisStudy - study["protocols"] = {} - studies[study["id"]] = study - - pro = study["protocols"].get(thisPro["id"]) - if pro is None: - pro = thisPro - pro["inclusion_list"] = u', '.join(pro["inclusion_criteria"]) - pro["exclusion_list"] = u', '.join(pro["exclusion_criteria"]) - pro["results"] = {} - pro["statistical_methods"] = {} - study["protocols"][pro["id"]] = pro - - mr = pro["results"].get(thisMr["id"]) - if mr is None: - mr = thisMr - mr["ci_percent"] = int(mr["ci_units"]*100.) - mr["adjustments_list"] = u', '.join(sorted(mr["adjustment_factors"])) - pro["results"][mr["id"]] = mr - - statKey, statVal = getStatMethods(thisMr) - stats = pro["statistical_methods"].get(statKey) - if stats is None: - stats = statVal - pro["statistical_methods"][statKey] = statVal - stats["sm_endpoints"] = [] - stats["sm_endpoints"].append(thisMr) - - # convert value dictionaries to lists - studies = sorted( - studies.values(), - key=lambda obj: (obj["short_citation"].lower())) - for study in studies: - study["protocols"] = sorted( - study["protocols"].values(), - key=lambda obj: (obj["name"].lower())) - for pro in study["protocols"]: - pro["results"] = sorted( - pro["results"].values(), - key=lambda obj: (obj["label"].lower())) - pro["statistical_methods"] = pro["statistical_methods"].values() - for obj in pro["statistical_methods"]: - obj["sm_endpoints"] = u"; ".join([ d["label"] for d in obj["sm_endpoints"] ]) - - return { - "assessment": AssessmentSerializer().to_representation(assessment), - "studies": studies - } - - -class SingleResult(models.Model): - meta_result = models.ForeignKey( - MetaResult, - related_name="single_results") - study = models.ForeignKey( - 'study.Study', - related_name="single_results", - blank=True, - null=True) - outcome_group = models.ForeignKey( - AssessedOutcomeGroup, - related_name="single_results", - blank=True, - null=True) - exposure_name = models.CharField( - max_length=128, - help_text='Enter a descriptive-name for the single study result ' - '(e.g., "Smith et al. 2000, obese-males")') - weight = models.FloatField( - blank=True, - null=True, - validators=[MinValueValidator(0),MaxValueValidator(1)], - help_text="For meta-analysis, enter the fraction-weight assigned for " - "each result (leave-blank for pooled analyses)") + +class GroupResult(models.Model): + + P_VALUE_QUALIFIER_CHOICES = ( + (' ', '-'), + ('-', 'n.s.'), + ('<', '<'), + ('=', '='), + ('>', '>'), + ) + + MAIN_FINDING_CHOICES = ( + (3, "not-reported"), + (2, "supportive"), + (1, "inconclusive"), + (0, "not-supportive")) + + result = models.ForeignKey( + Result, + related_name="results") + group = models.ForeignKey( + Group, + related_name="results") n = models.PositiveIntegerField( blank=True, null=True, - help_text="Enter the number of observations for this result") + help_text="Individuals in group where outcome was measured") estimate = models.FloatField( blank=True, null=True, - help_text="Enter the numerical risk-estimate presented for this result") + help_text="Central tendency estimate for group") + variance = models.FloatField( + blank=True, + null=True, + verbose_name='Variance', + help_text="Variance estimate for group") lower_ci = models.FloatField( blank=True, null=True, - verbose_name="Lower CI", + verbose_name='Lower CI', help_text="Numerical value for lower-confidence interval") upper_ci = models.FloatField( blank=True, null=True, - verbose_name="Upper CI", + verbose_name='Upper CI', help_text="Numerical value for upper-confidence interval") - ci_units = models.FloatField( + p_value_qualifier = models.CharField( + max_length=1, + choices=P_VALUE_QUALIFIER_CHOICES, + default="-", + verbose_name='p-value qualifier') + p_value = models.FloatField( blank=True, null=True, - default=0.95, - verbose_name='Confidence Interval (CI)', - help_text='A 95% CI is written as 0.95.') - notes = models.TextField( - blank=True) + verbose_name='p-value', + validators=[MinValueValidator(0.), MaxValueValidator(1.)]) + is_main_finding = models.BooleanField( + blank=True, + verbose_name="Main finding", + help_text="Is this the main-finding for this outcome?") + main_finding_support = models.PositiveSmallIntegerField( + choices=MAIN_FINDING_CHOICES, + help_text="Are the results supportive of the main-finding?", + default=1) + created = models.DateTimeField( + auto_now_add=True) + last_updated = models.DateTimeField( + auto_now=True) - def __unicode__(self): - return self.exposure_name + class Meta: + ordering = ('result', 'group__group_id') @property - def estimate_formatted(self): - txt = "-" - if self.estimate: - txt = unicode(self.estimate) - if (self.lower_ci and self.upper_ci): - txt += u' ({}, {})'.format(self.lower_ci, self.upper_ci) + def p_value_text(self): + txt = self.get_p_value_qualifier_display() + if self.p_value is not None: + if txt in ["=", "-", "n.s."]: + txt = u"{0:g}".format(self.p_value) + else: + txt = u"{0}{1:g}".format(txt, self.p_value) + return txt @staticmethod def flat_complete_header_row(): return ( - 'single_result-pk', - 'single_result-study', - 'single_result-outcome_group', - 'single_result-exposure_name', - 'single_result-weight', - 'single_result-n', - 'single_result-estimate', - 'single_result-lower_ci', - 'single_result-upper_ci', - 'single_result-ci_units', - 'single_result-notes', + "result_group-id", + "result_group-n", + "result_group-estimate", + "result_group-variance", + "result_group-lower_ci", + "result_group-upper_ci", + "result_group-p_value_qualifier", + "result_group-p_value", + "result_group-is_main_finding", + "result_group-main_finding_support", + "result_group-created", + "result_group-last_updated", ) @staticmethod def flat_complete_data_row(ser): - - study = None - try: - study = ser['study']['id'] - except TypeError: - pass - - aog = None - try: - aog = ser['outcome_group']['id'] - except TypeError: - pass - return ( - ser['id'], - study, - aog, - ser['exposure_name'], - ser['weight'], - ser['n'], - ser['estimate'], - ser['lower_ci'], - ser['upper_ci'], - ser['ci_units'], - ser['notes'], + ser["id"], + ser["n"], + ser["estimate"], + ser["variance"], + ser["lower_ci"], + ser["upper_ci"], + ser["p_value_qualifier"], + ser["p_value"], + ser["is_main_finding"], + ser["main_finding_support"], + ser["created"], + ser["last_updated"], ) @receiver(post_save, sender=StudyPopulation) @receiver(pre_delete, sender=StudyPopulation) +@receiver(post_save, sender=ComparisonSet) +@receiver(pre_delete, sender=ComparisonSet) @receiver(post_save, sender=Exposure) @receiver(pre_delete, sender=Exposure) -@receiver(post_save, sender=AssessedOutcome) -@receiver(pre_delete, sender=AssessedOutcome) -@receiver(post_save, sender=ExposureGroup) -@receiver(pre_delete, sender=ExposureGroup) -@receiver(post_save, sender=AssessedOutcomeGroup) -@receiver(pre_delete, sender=AssessedOutcomeGroup) -def invalidate_assessed_outcome_cache(sender, instance, **kwargs): +@receiver(post_save, sender=Group) +@receiver(pre_delete, sender=Group) +@receiver(post_save, sender=Outcome) +@receiver(pre_delete, sender=Outcome) +@receiver(post_save, sender=Result) +@receiver(pre_delete, sender=Result) +@receiver(post_save, sender=GroupResult) +@receiver(pre_delete, sender=GroupResult) +def invalidate_outcome_cache(sender, instance, **kwargs): + ids = [] instance_type = type(instance) filters = {} if instance_type is StudyPopulation: - filters["exposure__study_population"] = instance.id + filters["study_population_id"] = instance.id + elif instance_type is ComparisonSet: + filters["results__comparison_set_id"] = instance.id elif instance_type is Exposure: - filters["exposure"] = instance.id - elif instance_type is AssessedOutcome: - ids = [instance.id] - elif instance_type is ExposureGroup: - filters["exposure"] = instance.exposure_id - elif instance_type is AssessedOutcomeGroup: - ids = [instance.assessed_outcome_id] - - if len(filters)>0: - ids = AssessedOutcome.objects.filter(**filters).values_list('id', flat=True) - - AssessedOutcome.delete_caches(ids) - - -@receiver(post_save, sender=ExposureGroup) -def create_new_aogs(sender, instance, created, **kwargs): - # When a new exposure-group is created, create new default assessed-outcome - # group for all assessed-outcomes with the exposure-group's exposure, and - # invalidate old assessed-outcome cache. - if not created: - return - aos = AssessedOutcome.objects\ - .filter(exposure=instance.exposure)\ - .values_list('id', flat=True) - aogs = [] - for ao in aos: - kwargs = { - "exposure_group_id": instance.id, - "assessed_outcome_id": ao - } - aogs.append(AssessedOutcomeGroup(**kwargs)) - AssessedOutcomeGroup.objects.bulk_create(aogs) - AssessedOutcome.delete_caches(aos) - - -@receiver(post_save, sender=MetaProtocol) -@receiver(pre_delete, sender=MetaProtocol) -@receiver(post_save, sender=MetaResult) -@receiver(pre_delete, sender=MetaResult) -@receiver(post_save, sender=SingleResult) -@receiver(pre_delete, sender=SingleResult) -def invalidate_meta_result_cache(sender, instance, **kwargs): - instance_type = type(instance) - filters = {} - if instance_type is MetaProtocol: - filters["protocol"] = instance.id - elif instance_type is MetaResult: + filters["results__comparison_set__exposure_id"] = instance.id + elif instance_type is Group: + filters["results__comparison_set__groups"] = instance.id + elif instance_type is Outcome: ids = [instance.id] - elif instance_type is SingleResult: - ids = [instance.meta_result_id] + elif instance_type is Result: + ids = [instance.outcome_id] + elif instance_type is GroupResult: + ids = [instance.result.outcome_id] - if len(filters)>0: - ids = MetaResult.objects.filter(**filters).values_list('id', flat=True) + if len(filters) > 0: + ids = Outcome.objects.filter(**filters).values_list('id', flat=True) - MetaResult.delete_caches(ids) + Outcome.delete_caches(ids) +reversion.register(Country) +reversion.register(Criteria) reversion.register(Ethnicity) -reversion.register(Factor) -reversion.register(StudyPopulation) -reversion.register(StudyCriteria) +reversion.register(StudyPopulationCriteria) +reversion.register(AdjustmentFactor) +reversion.register(ResultAdjustmentFactor) +reversion.register(StudyPopulation, follow=('country', 'spcriteria')) +reversion.register(ComparisonSet) reversion.register(Exposure) -reversion.register(AssessedOutcome, - follow=('groups', 'adjustment_factors', 'confounders_considered', 'baseendpoint_ptr')) -reversion.register(ExposureGroup) -reversion.register(AssessedOutcomeGroup, follow=('assessed_outcome', )) -reversion.register(MetaProtocol, - follow=('inclusion_criteria', 'exclusion_criteria')) -reversion.register(MetaResult, follow=('adjustment_factors', )) -reversion.register(SingleResult) +reversion.register(Outcome, follow=('effects',)) +reversion.register(Group, follow=('ethnicities',)) +reversion.register(Result, follow=('adjustment_factors', 'resfactors', 'results')) +reversion.register(GroupResult) diff --git a/project/epi/serializers.py b/project/epi/serializers.py index ad56e7d9..39eb3849 100644 --- a/project/epi/serializers.py +++ b/project/epi/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from assessment.serializers import EffectTagsSerializer +from assessment.serializers import EffectTagsSerializer, DoseUnitsSerializer from study.serializers import StudySerializer from utils.helper import SerializerHelper @@ -8,185 +8,179 @@ from . import models -class StudyCriteriaSerializer(serializers.ModelSerializer): +class EthnicitySerializer(serializers.ModelSerializer): class Meta: - model = models.StudyCriteria + model = models.Ethnicity + fields = ('id', 'name') -class StatisticalMetricSerializer(serializers.ModelSerializer): +class CountrySerializer(serializers.ModelSerializer): class Meta: - model = models.StatisticalMetric + model = models.Country + fields = ('name', 'id') -class StudyPopulationSerializer(serializers.ModelSerializer): - study = StudySerializer() - ethnicity = serializers.StringRelatedField(many=True) - inclusion_criteria = serializers.StringRelatedField(many=True) - exclusion_criteria = serializers.StringRelatedField(many=True) - confounding_criteria = serializers.StringRelatedField(many=True) - - def to_representation(self, instance): - ret = super(StudyPopulationSerializer, self).to_representation(instance) - ret['url'] = instance.get_absolute_url() - ret['design'] = instance.get_design_display() - ret['country'] = instance.get_country_display() - ret['sex'] = instance.get_sex_display() - ret['age_sd_type'] = instance.get_age_sd_type_display() - ret['age_mean_type'] = instance.get_age_mean_type_display() - ret['age_lower_type'] = instance.get_age_lower_type_display() - ret['age_upper_type'] = instance.get_age_upper_type_display() - return ret +class StudyPopulationCriteriaSerializer(serializers.ModelSerializer): + id = serializers.ReadOnlyField(source="criteria.id") + description = serializers.ReadOnlyField(source="criteria.description") + criteria_type = serializers.CharField(source='get_criteria_type_display', read_only=True) class Meta: - model = models.StudyPopulation + model = models.StudyPopulationCriteria + fields = ('id', 'description', 'criteria_type') -class ExposureSerializer(serializers.ModelSerializer): - study_population = StudyPopulationSerializer() - - def to_representation(self, instance): - ret = super(ExposureSerializer, self).to_representation(instance) - ret['url'] = instance.get_absolute_url() - return ret +class ExposureLinkSerializer(serializers.ModelSerializer): + url = serializers.CharField(source='get_absolute_url', read_only=True) class Meta: model = models.Exposure - depth = 1 - + fields = ('id', 'name', 'url') -class ExposureGroupSerializer(serializers.ModelSerializer): - ethnicity = serializers.StringRelatedField(many=True) - def to_representation(self, instance): - ret = super(ExposureGroupSerializer, self).to_representation(instance) - ret['sex'] = instance.get_sex_display() - ret['age_sd'] = instance.get_age_sd_type_display() - ret['age_mean_type'] = instance.get_age_mean_type_display() - ret['age_lower_type'] = instance.get_age_lower_type_display() - ret['age_upper_type'] = instance.get_age_upper_type_display() - return ret +class OutcomeLinkSerializer(serializers.ModelSerializer): + url = serializers.CharField(source='get_absolute_url', read_only=True) class Meta: - model = models.ExposureGroup + model = models.Outcome + fields = ('id', 'name', 'url') -class ExposureVerboseSerializer(ExposureSerializer): - groups = ExposureGroupSerializer(many=True) +class GroupNumericalDescriptionsSerializer(serializers.ModelSerializer): + mean_type = serializers.CharField(source='get_mean_type_display', read_only=True) + variance_type = serializers.CharField(source='get_variance_type_display', read_only=True) + lower_type = serializers.CharField(source='get_lower_type_display', read_only=True) + upper_type = serializers.CharField(source='get_upper_type_display', read_only=True) + class Meta: + model = models.GroupNumericalDescriptions + exclude = ('group', ) -class AssessedOutcomeGroupSerializer(serializers.ModelSerializer): - assessed_outcome = serializers.PrimaryKeyRelatedField(read_only=True) - exposure_group = ExposureGroupSerializer() - def to_representation(self, instance): - ret = super(AssessedOutcomeGroupSerializer, self).to_representation(instance) - ret['p_value_text'] = instance.p_value_text - ret['isMainFinding'] = instance.isMainFinding - ret['estimateFormatted'] = instance.estimate_formatted - if ret['n'] is None: - ret['n'] = ret['exposure_group']['n'] or "-" - return ret +class GroupSerializer(serializers.ModelSerializer): + sex = serializers.CharField(source='get_sex_display', read_only=True) + descriptions = GroupNumericalDescriptionsSerializer(many=True) + ethnicities = EthnicitySerializer(many=True) + url = serializers.CharField(source='get_absolute_url', read_only=True) class Meta: - model = models.AssessedOutcomeGroup + model = models.Group -class AssessedOutcomeSerializer(serializers.ModelSerializer): - assessment = serializers.PrimaryKeyRelatedField(read_only=True) - main_finding = serializers.PrimaryKeyRelatedField(read_only=True) - exposure = ExposureSerializer() - statistical_metric = StatisticalMetricSerializer() - effects = EffectTagsSerializer() - groups = AssessedOutcomeGroupSerializer(many=True) - adjustment_factors = serializers.StringRelatedField(many=True) - confounders_considered = serializers.StringRelatedField(many=True) - - def to_representation(self, instance): - ret = super(AssessedOutcomeSerializer, self).to_representation(instance) - ret['url'] = instance.get_absolute_url() - ret['diagnostic'] = instance.get_diagnostic_display() - ret['dose_response'] = instance.get_dose_response_display() - ret['statistical_power'] = instance.get_statistical_power_display() - ret['main_finding_support'] = instance.get_main_finding_support_display() - return ret +class ResultMetricSerializer(serializers.ModelSerializer): class Meta: - model = models.AssessedOutcome + model = models.ResultMetric + + +class SimpleExposureSerializer(serializers.ModelSerializer): + url = serializers.CharField(source='get_absolute_url', read_only=True) + metric_units = DoseUnitsSerializer() + class Meta: + model = models.Exposure -class AssessedOutcomeShallowSerializer(serializers.ModelSerializer): - def to_representation(self, instance): - ret = super(AssessedOutcomeShallowSerializer, self).to_representation(instance) - ret['url'] = instance.get_absolute_url() - return ret +class ComparisonSetLinkSerializer(serializers.ModelSerializer): + url = serializers.CharField(source='get_absolute_url', read_only=True) class Meta: - model = models.AssessedOutcome + model = models.ComparisonSet + fields = ('id', 'name', 'url') -class AssessedOutcomeGroupVerboseSerializer(serializers.ModelSerializer): - assessed_outcome = AssessedOutcomeShallowSerializer() +class StudyPopulationSerializer(serializers.ModelSerializer): + study = StudySerializer() + criteria = StudyPopulationCriteriaSerializer(source='spcriteria', many=True) + outcomes = OutcomeLinkSerializer(many=True) + exposures = ExposureLinkSerializer(many=True) + can_create_sets = serializers.BooleanField(read_only=True) + comparison_sets = ComparisonSetLinkSerializer(many=True) + country = serializers.CharField(source='country.name', read_only=True) + url = serializers.CharField(source='get_absolute_url', read_only=True) + design = serializers.CharField(source='get_design_display', read_only=True) + + class Meta: + model = models.StudyPopulation - def to_representation(self, instance): - ret = super(AssessedOutcomeGroupVerboseSerializer, self).to_representation(instance) - ret['estimateFormatted'] = instance.estimate_formatted - return ret + +class ExposureSerializer(serializers.ModelSerializer): + study_population = StudyPopulationSerializer() + url = serializers.CharField(source='get_absolute_url', read_only=True) + metric_units = DoseUnitsSerializer() class Meta: - model = models.AssessedOutcomeGroup + model = models.Exposure -class SingleResultSerializer(serializers.ModelSerializer): - study = StudySerializer() - outcome_group = AssessedOutcomeGroupVerboseSerializer() - meta_result = serializers.PrimaryKeyRelatedField(read_only=True) +class GroupResultSerializer(serializers.ModelSerializer): + main_finding_support = serializers.CharField(source='get_main_finding_support_display', read_only=True) + p_value_qualifier = serializers.CharField(source='get_p_value_qualifier_display', read_only=True) + p_value_text = serializers.CharField(read_only=True) + group = GroupSerializer() - def to_representation(self, instance): - ret = super(SingleResultSerializer, self).to_representation(instance) - if instance.outcome_group is None: - ret['estimateFormatted'] = instance.estimate_formatted - return ret + class Meta: + model = models.GroupResult + + +class ResultAdjustmentFactorSerializer(serializers.ModelSerializer): + id = serializers.ReadOnlyField(source="adjustment_factor.id") + description = serializers.ReadOnlyField(source="adjustment_factor.description") class Meta: - model = models.SingleResult + model = models.ResultAdjustmentFactor + fields = ('id', 'description', 'included_in_final_model') -class MetaProtocolSerializer(serializers.ModelSerializer): - study = StudySerializer() - inclusion_criteria = serializers.StringRelatedField(many=True) - exclusion_criteria = serializers.StringRelatedField(many=True) +class SimpleComparisonSetSerializer(serializers.ModelSerializer): + url = serializers.CharField(source='get_absolute_url', read_only=True) + exposure = SimpleExposureSerializer() - def to_representation(self, instance): - ret = super(MetaProtocolSerializer, self).to_representation(instance) - ret['url'] = instance.get_absolute_url() - ret['protocol_type'] = instance.get_protocol_type_display() - ret['lit_search_strategy'] = instance.get_lit_search_strategy_display() - return ret + class Meta: + model = models.ComparisonSet + + +class ResultSerializer(serializers.ModelSerializer): + metric = ResultMetricSerializer() + factors = ResultAdjustmentFactorSerializer(source='resfactors', many=True) + dose_response = serializers.CharField(source='get_dose_response_display', read_only=True) + statistical_power = serializers.CharField(source='get_statistical_power_display', read_only=True) + url = serializers.CharField(source='get_absolute_url', read_only=True) + results = GroupResultSerializer(many=True) + variance_type = serializers.CharField(source='get_variance_type_display', read_only=True) + estimate_type = serializers.CharField(source='get_estimate_type_display', read_only=True) + full_name = serializers.CharField(source='__unicode__', read_only=True) + comparison_set = SimpleComparisonSetSerializer() class Meta: - model = models.MetaProtocol + model = models.Result + exclude = ('adjustment_factors', ) -class MetaResultSerializer(serializers.ModelSerializer): - protocol = MetaProtocolSerializer() - statistical_metric = StatisticalMetricSerializer() - single_results = SingleResultSerializer(many=True) - adjustment_factors = serializers.StringRelatedField(many=True) +class OutcomeSerializer(serializers.ModelSerializer): + study_population = StudyPopulationSerializer() + can_create_sets = serializers.BooleanField(read_only=True) + effects = EffectTagsSerializer() + diagnostic = serializers.CharField(source='get_diagnostic_display', read_only=True) + url = serializers.CharField(source='get_absolute_url', read_only=True) + results = ResultSerializer(many=True) + comparison_sets = ComparisonSetLinkSerializer(many=True) - def to_representation(self, instance): - ret = super(MetaResultSerializer, self).to_representation(instance) - ret['url'] = instance.get_absolute_url() - ret['estimateFormatted'] = instance.estimate_formatted - return ret + class Meta: + model = models.Outcome + + +class ComparisonSetSerializer(serializers.ModelSerializer): + url = serializers.CharField(source='get_absolute_url', read_only=True) + exposure = ExposureSerializer() + outcome = OutcomeSerializer() + study_population = StudyPopulationSerializer() + groups = GroupSerializer(many=True) class Meta: - model = models.MetaResult + model = models.ComparisonSet -SerializerHelper.add_serializer(models.StudyPopulation, StudyPopulationSerializer) -SerializerHelper.add_serializer(models.AssessedOutcome, AssessedOutcomeSerializer) -SerializerHelper.add_serializer(models.MetaProtocol, MetaProtocolSerializer) -SerializerHelper.add_serializer(models.MetaResult, MetaResultSerializer) +SerializerHelper.add_serializer(models.Outcome, OutcomeSerializer) diff --git a/project/epi/urls.py b/project/epi/urls.py index e6da3a49..92909ca6 100644 --- a/project/epi/urls.py +++ b/project/epi/urls.py @@ -2,163 +2,135 @@ from rest_framework.routers import DefaultRouter -from . import views, api +from . import api, views + router = DefaultRouter() -router.register(r'meta-result', api.MetaResult, base_name="meta-result") router.register(r'study-population', api.StudyPopulation, base_name="study-population") router.register(r'exposure', api.Exposure, base_name="exposure") -router.register(r'assessed-outcome', api.AssessedOutcome, base_name="assessed-outcome") - -urlpatterns = [ - - # overall views - url(r'^assessment/(?P\d+)/full-export/$', - views.FullExport.as_view(), - name='export'), - - url(r'^assessment/(?P\d+)/assessed-outcomes/$', - views.AssessedOutcomeList.as_view(), - name='assessedoutcome_list'), +router.register(r'outcome', api.Outcome, base_name="outcome") +router.register(r'result', api.Result, base_name="result") +router.register(r'comparison-set', api.ComparisonSet, base_name="set") +router.register(r'group', api.Group, base_name="group") - url(r'^assessment/(?P\d+)/report/$', - views.AssessedOutcomeReport.as_view(), - name='ao_report'), - url(r'^assessment/(?P\d+)/meta-result-report/$', - views.MetaResultReport.as_view(), - name='mr_report'), +urlpatterns = [ - url(r'^assessment/(?P\d+)/meta-result-full-export/$', - views.MetaResultFullExport.as_view(), - name='mr_export'), + url(r'^api/', include(router.urls, namespace='api')), - # study-criteria views + # Criteria url(r'^assessment/(?P\d+)/study-criteria/create/$', views.StudyCriteriaCreate.as_view(), name='studycriteria_create'), - # study-population views + # Adjustment factors + url(r'^assessment/(?P\d+)/adjustment-factor/create/$', + views.AdjustmentFactorCreate.as_view(), + name='adjustmentfactor_create'), + + # Study population url(r'^study/(?P\d+)/study-population/create/$', views.StudyPopulationCreate.as_view(), name='sp_create'), - url(r'^study/(?P\d+)/study-population/copy-as-new-selector/$', views.StudyPopulationCopyAsNewSelector.as_view(), name='sp_copy_selector'), - url(r'^study-population/(?P\d+)/$', views.StudyPopulationDetail.as_view(), name='sp_detail'), - url(r'^study-population/(?P\d+)/update/$', views.StudyPopulationUpdate.as_view(), name='sp_update'), - url(r'^study-population/(?P\d+)/delete/$', views.StudyPopulationDelete.as_view(), name='sp_delete'), - # exposure views - url(r'^study-population/(?P\d+)/exposure/create/$', + # Exposure + url(r'^study/(?P\d+)/exposure/create/$', views.ExposureCreate.as_view(), - name='exposure_create'), - - url(r'^study-population/(?P\d+)/exposure/copy-as-new-selector/$', + name='exp_create'), + url(r'^study/(?P\d+)/exposure/copy-as-new-selector/$', views.ExposureCopyAsNewSelector.as_view(), - name='exposure_copy_selector'), - + name='exp_copy_selector'), url(r'^exposure/(?P\d+)/$', views.ExposureDetail.as_view(), - name='exposure_detail'), - + name='exp_detail'), url(r'^exposure/(?P\d+)/update/$', views.ExposureUpdate.as_view(), - name='exposure_update'), - + name='exp_update'), url(r'^exposure/(?P\d+)/delete/$', views.ExposureDelete.as_view(), - name='exposure_delete'), - - # factor views - url(r'^assessment/(?P\d+)/factors/create/$', - views.FactorCreate.as_view(), - name='factor_create'), - - # assessed-outcome views - url(r'^exposure/(?P\d+)/assessed-outcome/create/$', - views.AssessedOutcomeCreate.as_view(), - name='assessedoutcome_create'), - - url(r'^exposure/(?P\d+)/assessed-outcome/copy-as-new-selector/$', - views.AssessedOutcomeCopyAsNewSelector.as_view(), - name='assessedoutcome_copy_selector'), - - url(r'^assessed-outcome/(?P\d+)/$', - views.AssessedOutcomeDetail.as_view(), - name='assessedoutcome_detail'), - - url(r'^assessed-outcome/(?P\d+)/update/$', - views.AssessedOutcomeUpdate.as_view(), - name='assessedoutcome_update'), - - url(r'^assessed-outcome/(?P\d+)/versions/$', - views.AssessedOutcomeVersions.as_view(), - name='assessedoutcome_versions'), - - url(r'^assessed-outcome/(?P\d+)/delete/$', - views.AssessedOutcomeDelete.as_view(), - name='assessedoutcome_delete'), - - # meta-protocol views - url(r'^study/(?P\d+)/meta-protocol/create/$', - views.MetaProtocolCreate.as_view(), - name='mp_create'), - - url(r'^meta-protocol/(?P\d+)/$', - views.MetaProtocolDetail.as_view(), - name='mp_detail'), - - url(r'^meta-protocol/(?P\d+)/json/$', - views.MetaProtocolJSON.as_view(), - name='mp_json'), - - url(r'^meta-protocol/(?P\d+)/update/$', - views.MetaProtocolUpdate.as_view(), - name='mp_update'), - - url(r'^meta-protocol/(?P\d+)/delete/$', - views.MetaProtocolDelete.as_view(), - name='mp_delete'), - - # meta-result views - url(r'^assessment/(?P\d+)/meta-results/$', - views.MetaResultList.as_view(), - name='metaresult_list'), - - url(r'^meta-protocol/(?P\d+)/meta-result/create/$', - views.MetaResultCreate.as_view(), - name='mr_create'), - - url(r'^meta-protocol/(?P\d+)/meta-result/copy-as-new-selector/$', - views.MetaResultCopyAsNewSelector.as_view(), - name='mr_copy_selector'), - - url(r'^meta-result/(?P\d+)/$', - views.MetaResultDetail.as_view(), - name='mr_detail'), - - url(r'^meta-result/(?P\d+)/json/$', - views.MetaResultJSON.as_view(), - name='mr_json'), - - url(r'^meta-result/(?P\d+)/update/$', - views.MetaResultUpdate.as_view(), - name='mr_update'), - - url(r'^meta-result/(?P\d+)/delete/$', - views.MetaResultDelete.as_view(), - name='mr_delete'), + name='exp_delete'), + + # Outcome + url(r'^assessment/(?P\d+)/export/$', + views.OutcomeExport.as_view(), + name='outcome_export'), + url(r'^assessment/(?P\d+)/outcomes/$', + views.OutcomeList.as_view(), + name='outcome_list'), + url(r'^study-population/(?P\d+)/outcome/create/$', + views.OutcomeCreate.as_view(), + name='outcome_create'), + url(r'^study-population/(?P\d+)/outcome/copy-as-new-selector/$', + views.OutcomeCopyAsNewSelector.as_view(), + name='outcome_copy_selector'), + url(r'^outcome/(?P\d+)/$', + views.OutcomeDetail.as_view(), + name='outcome_detail'), + url(r'^outcome/(?P\d+)/update/$', + views.OutcomeUpdate.as_view(), + name='outcome_update'), + url(r'^outcome/(?P\d+)/delete/$', + views.OutcomeDelete.as_view(), + name='outcome_delete'), + + # Results + url(r'^outcome/(?P\d+)/result/create/$', + views.ResultCreate.as_view(), + name='result_create'), + url(r'^outcome/(?P\d+)/result/copy-as-new-selector/$', + views.ResultCopyAsNewSelector.as_view(), + name='result_copy_selector'), + url(r'^result/(?P\d+)/$', + views.ResultDetail.as_view(), + name='result_detail'), + url(r'^result/(?P\d+)/update/$', + views.ResultUpdate.as_view(), + name='result_update'), + url(r'^result/(?P\d+)/delete/$', + views.ResultDelete.as_view(), + name='result_delete'), + + # Comparison set + url(r'^study-population/(?P\d+)/comparison-set/create/$', + views.ComparisonSetCreate.as_view(), + name='cs_create'), + url(r'^study-population/(?P\d+)/comparison-set/copy-as-new-selector/$', + views.ComparisonSetStudyPopCopySelector.as_view(), + name='cs_copy_selector'), + url(r'^outcome/(?P\d+)/comparison-set/create/$', + views.ComparisonSetOutcomeCreate.as_view(), + name='cs_outcome_create'), + url(r'^outcome/(?P\d+)/comparison-set/copy-as-new-selector/$', + views.ComparisonSetOutcomeCopySelector.as_view(), + name='cs_outcome_copy_selector'), + url(r'^comparison-set/(?P\d+)/$', + views.ComparisonSetDetail.as_view(), + name='cs_detail'), + url(r'^comparison-set/(?P\d+)/update/$', + views.ComparisonSetUpdate.as_view(), + name='cs_update'), + url(r'^comparison-set/(?P\d+)/delete/$', + views.ComparisonSetDelete.as_view(), + name='cs_delete'), + + # Groups (in comparison set) + url(r'^group/(?P\d+)/$', + views.GroupDetail.as_view(), + name='g_detail'), + url(r'^group/(?P\d+)/update/$', + views.GroupUpdate.as_view(), + name='g_update'), - url(r'^api/', include(router.urls)), ] diff --git a/project/epi/views.py b/project/epi/views.py index 16085eb2..473285dd 100644 --- a/project/epi/views.py +++ b/project/epi/views.py @@ -1,20 +1,25 @@ -from django.core.urlresolvers import reverse -from django.forms.models import modelformset_factory -from django.http import HttpResponse - -from assessment.models import Assessment from utils.views import (BaseDetail, BaseDelete, BaseVersion, BaseUpdate, BaseCreate, BaseCreateWithFormset, BaseUpdateWithFormset, CloseIfSuccessMixin, BaseList, GenerateReport) +from assessment.models import Assessment from study.models import Study from study.views import StudyRead -from . import forms, models, exports +from . import forms, exports, models -# Study populations +# Study criteria +class StudyCriteriaCreate(CloseIfSuccessMixin, BaseCreate): + success_message = 'Criteria created.' + parent_model = Assessment + parent_template_name = 'assessment' + model = models.Criteria + form_class = forms.CriteriaForm + + +# Study population class StudyPopulationCreate(BaseCreate): success_message = 'Study-population created.' parent_model = Study @@ -23,6 +28,15 @@ class StudyPopulationCreate(BaseCreate): form_class = forms.StudyPopulationForm +class StudyPopulationCopyAsNewSelector(StudyRead): + template_name = 'epi/studypopulation_copy_selector.html' + + def get_context_data(self, **kwargs): + context = super(StudyPopulationCopyAsNewSelector, self).get_context_data(**kwargs) + context['form'] = forms.StudyPopulationSelectorForm(parent_id=self.object.id) + return context + + class StudyPopulationDetail(BaseDetail): model = models.StudyPopulation @@ -38,179 +52,58 @@ class StudyPopulationDelete(BaseDelete): model = models.StudyPopulation def get_success_url(self): - self.parent = self.object.study - return reverse("study:detail", kwargs={"pk": self.parent.pk}) + return self.object.study.get_absolute_url() -class StudyPopulationCopyAsNewSelector(StudyRead): - # Select an existing assessed outcome as a template for a new one - template_name = 'epi/studypopulation_copy_selector.html' - - def get_context_data(self, **kwargs): - context = super(StudyPopulationCopyAsNewSelector, self).get_context_data(**kwargs) - context['form'] = forms.StudyPopulationSelectorForm(study_id=self.object.id) - return context +# Factors +class AdjustmentFactorCreate(CloseIfSuccessMixin, BaseCreate): + success_message = 'Adjustment factor created.' + parent_model = Assessment + parent_template_name = 'assessment' + model = models.AdjustmentFactor + form_class = forms.AdjustmentFactorForm -# Exposures -class ExposureCreate(BaseCreateWithFormset): +# Exposure +class ExposureCreate(BaseCreate): success_message = 'Exposure created.' parent_model = models.StudyPopulation parent_template_name = 'study_population' model = models.Exposure form_class = forms.ExposureForm - formset_factory = forms.EGFormSet - - def post_object_save(self, form, formset): - # Bind newly created exposure to exposure-group instance - for i, form in enumerate(formset.forms): - form.instance.exposure_group_id = i - form.instance.exposure = self.object - - def build_initial_formset_factory(self): - return forms.BlankEGFormSet(queryset=models.ExposureGroup.objects.none()) - - -class ExposureDetail(BaseDetail): - model = models.Exposure - - -class ExposureDelete(BaseDelete): - success_message = "Exposure deleted." - model = models.Exposure - - def get_success_url(self): - self.parent = self.object.study_population - return reverse("epi:sp_detail", kwargs={"pk": self.parent.pk}) - - -class ExposureUpdate(BaseUpdateWithFormset): - success_message = "Exposure updated." - model = models.Exposure - form_class = forms.ExposureForm - formset_factory = forms.EGFormSet - - def build_initial_formset_factory(self): - return forms.EGFormSet(queryset=self.object.groups.all() - .order_by('exposure_group_id')) - - def post_object_save(self, form, formset): - for form in formset: - form.instance.exposure = self.object - - def post_formset_save(self, form, formset): - formset.rebuild_exposure_group_id() class ExposureCopyAsNewSelector(StudyPopulationDetail): - # Select an existing assessed outcome as a template for a new one template_name = 'epi/exposure_copy_selector.html' def get_context_data(self, **kwargs): context = super(ExposureCopyAsNewSelector, self).get_context_data(**kwargs) - context['form'] = forms.ExposureSelectorForm(study_id=self.object.study.id) + context['form'] = forms.ExposureSelectorForm(parent_id=self.object.id) return context -# Study criteria -class StudyCriteriaCreate(CloseIfSuccessMixin, BaseCreate): - success_message = 'Epidemiology study-population criteria created.' - parent_model = Assessment - parent_template_name = 'assessment' - model = models.StudyCriteria - form_class = forms.StudyCriteriaForm - - -# Factors -class FactorCreate(CloseIfSuccessMixin, BaseCreate): - success_message = 'Factor created.' - parent_model = Assessment - parent_template_name = 'assessment' - model = models.Factor - form_class = forms.FactorForm - - -# Assessed outcomes -class AssessedOutcomeCreate(BaseCreateWithFormset): - success_message = 'Assessed-outcome created.' - parent_model = models.Exposure - parent_template_name = 'object' - model = models.AssessedOutcome - form_class = forms.AssessedOutcomeForm - formset_factory = forms.AOGFormSet - - def get_form_kwargs(self): - kwargs = super(AssessedOutcomeCreate, self).get_form_kwargs() - kwargs['assessment'] = self.assessment - return kwargs - - def post_object_save(self, form, formset): - # Bind newly created assessment outcome to assessment group instances - for form in formset.forms: - form.instance.assessed_outcome = self.object - - def build_initial_formset_factory(self): - initial = self.parent.groups.all().order_by('exposure_group_id').values('pk') - for v in initial: - v['exposure_group'] = v.pop('pk') - AOGFormset = modelformset_factory(models.AssessedOutcomeGroup, - form=forms.AOGForm, - extra=len(initial)) - return AOGFormset(queryset=models.AssessedOutcomeGroup.objects.none(), - initial=initial) - - -class AssessedOutcomeDetail(BaseDetail): - model = models.AssessedOutcome - - -class AssessedOutcomeVersions(BaseVersion): - model = models.AssessedOutcome - template_name = "epi/assessedoutcome_versions.html" - +class ExposureDetail(BaseDetail): + model = models.Exposure -class AssessedOutcomeUpdate(BaseUpdateWithFormset): - success_message = "Assessment Outcome updated." - model = models.AssessedOutcome - form_class = forms.AssessedOutcomeForm - formset_factory = forms.AOGFormSet - def build_initial_formset_factory(self): - return forms.AOGFormSet(queryset=self.object.groups.all() \ - .order_by('exposure_group__exposure_group_id')) +class ExposureUpdate(BaseUpdate): + success_message = "Study Population updated." + model = models.Exposure + form_class = forms.ExposureForm -class AssessedOutcomeDelete(BaseDelete): - success_message = "Assessment Outcome deleted." - model = models.AssessedOutcome +class ExposureDelete(BaseDelete): + success_message = "Study Population deleted." + model = models.Exposure def get_success_url(self): - self.parent = self.object.exposure - return reverse("epi:exposure_detail", kwargs={"pk": self.parent.pk}) + return self.object.study_population.get_absolute_url() -class AssessedOutcomeReport(GenerateReport): +# Outcome +class OutcomeList(BaseList): parent_model = Assessment - model = models.AssessedOutcome - report_type = 3 - - def get_queryset(self): - filters = {"assessment": self.assessment} - perms = super(AssessedOutcomeReport, self).get_obj_perms() - if not perms['edit'] or self.onlyPublished: - filters["exposure__study_population__study__published"] = True - return self.model.objects.filter(**filters) - - def get_filename(self): - return "epidemiology.docx" - - def get_context(self, queryset): - return self.model.get_docx_template_context(self.assessment, queryset) - - -class AssessedOutcomeList(BaseList): - parent_model = Assessment - model = models.AssessedOutcome + model = models.Outcome def get_paginate_by(self, qs): val = 25 @@ -224,17 +117,17 @@ def get_queryset(self): filters = {"assessment": self.assessment} perms = self.get_obj_perms() if not perms['edit']: - filters["exposure__study_population__study__published"] = True + filters["study_population__study__published"] = True return self.model.objects.filter(**filters).order_by('name') -class FullExport(AssessedOutcomeList): +class OutcomeExport(OutcomeList): """ Full XLS data export for the epidemiology outcome. """ def get(self, request, *args, **kwargs): self.object_list = self.get_queryset() - exporter = exports.AssessedOutcomeFlatComplete( + exporter = exports.OutcomeComplete( self.object_list, export_format="excel", filename='{}-epi'.format(self.assessment), @@ -242,189 +135,215 @@ def get(self, request, *args, **kwargs): return exporter.build_response() -class AssessedOutcomeCopyAsNewSelector(ExposureDetail): - # Select an existing assessed outcome as a template for a new one - template_name = 'epi/assessedoutcome_copy_selector.html' +class OutcomeCreate(BaseCreate): + success_message = 'Outcome created.' + parent_model = models.StudyPopulation + parent_template_name = 'study_population' + model = models.Outcome + form_class = forms.OutcomeForm + + def get_form_kwargs(self): + kwargs = super(OutcomeCreate, self).get_form_kwargs() + kwargs['assessment'] = self.assessment + return kwargs + + +class OutcomeCopyAsNewSelector(StudyPopulationDetail): + template_name = 'epi/outcome_copy_selector.html' def get_context_data(self, **kwargs): - context = super(AssessedOutcomeCopyAsNewSelector, self).get_context_data(**kwargs) - context['form'] = forms.AssesedOutcomeSelectorForm(study_id=self.object.study_population.study.id) + context = super(OutcomeCopyAsNewSelector, self).get_context_data(**kwargs) + context['form'] = forms.OutcomeSelectorForm(parent_id=self.object.id) return context -# MetaProtocol -class MetaProtocolCreate(BaseCreate): - success_message = 'Meta-protocol created.' - parent_model = Study - parent_template_name = 'study' - model = models.MetaProtocol - form_class = forms.MetaProtocolForm +class OutcomeDetail(BaseDetail): + model = models.Outcome -class MetaProtocolDetail(BaseDetail): - model = models.MetaProtocol +class OutcomeUpdate(BaseUpdate): + success_message = "Outcome updated." + model = models.Outcome + form_class = forms.OutcomeForm -class MetaProtocolJSON(MetaProtocolDetail): - http_method_names = ('get', ) +class OutcomeDelete(BaseDelete): + success_message = "Outcome deleted." + model = models.Outcome + + def get_success_url(self): + return self.object.study_population.get_absolute_url() + + +# Result +class ResultCreate(BaseCreateWithFormset): + success_message = 'Result created.' + parent_model = models.Outcome + parent_template_name = 'outcome' + model = models.Result + form_class = forms.ResultForm + formset_factory = forms.GroupResultFormset + + def post_object_save(self, form, formset): + for form in formset.forms: + form.instance.result = self.object + + def get_formset_kwargs(self): + return { + "outcome": self.parent, + "study_population": self.parent.study_population + } + + def build_initial_formset_factory(self): + return forms.BlankGroupResultFormset( + queryset=models.GroupResult.objects.none(), + **self.get_formset_kwargs()) - def get(self, request, *args, **kwargs): - self.object = self.get_object() - return HttpResponse(self.object.get_json(json_encode=True), content_type="application/json") +class ResultCopyAsNewSelector(OutcomeDetail): + template_name = 'epi/result_copy_selector.html' + + def get_context_data(self, **kwargs): + context = super(ResultCopyAsNewSelector, self).get_context_data(**kwargs) + context['form'] = forms.ResultSelectorForm(parent_id=self.object.id) + return context + + +class ResultDetail(BaseDetail): + model = models.Result -class MetaProtocolUpdate(BaseUpdate): - success_message = "Meta-protocol updated." - model = models.MetaProtocol - form_class = forms.MetaProtocolForm +class ResultUpdate(BaseUpdateWithFormset): + success_message = "Result updated." + model = models.Result + form_class = forms.ResultUpdateForm + formset_factory = forms.GroupResultFormset + + def build_initial_formset_factory(self): + return forms.GroupResultFormset( + queryset=self.object.results.all(), + **self.get_formset_kwargs()) + + def get_formset_kwargs(self): + return { + "study_population": self.object.outcome.study_population, + "outcome": self.object.outcome, + "result": self.object + } + + def post_object_save(self, form, formset): + # delete other results not associated with the selected collection + models.GroupResult.objects\ + .filter(result=self.object)\ + .exclude(group__comparison_set=self.object.comparison_set)\ + .delete() -class MetaProtocolDelete(BaseDelete): - success_message = "Meta-protocol deleted." - model = models.MetaProtocol + +class ResultDelete(BaseDelete): + success_message = "Result deleted." + model = models.Result def get_success_url(self): - self.parent = self.object.study - return reverse("study:detail", kwargs={"pk": self.parent.pk}) + return self.object.outcome.get_absolute_url() -# MetaResult -class MetaResultCreate(BaseCreateWithFormset): - success_message = 'Meta-Result created.' - parent_model = models.MetaProtocol - parent_template_name = 'protocol' - model = models.MetaResult - form_class = forms.MetaResultForm - formset_factory = forms.SingleResultFormset +# Comparison set + group +class ComparisonSetCreate(BaseCreateWithFormset): + success_message = 'Groups created.' + parent_model = models.StudyPopulation + parent_template_name = 'study_population' + model = models.ComparisonSet + form_class = forms.ComparisonSet + formset_factory = forms.GroupFormset def post_object_save(self, form, formset): - # Bind newly created single-result outcome to meta-result instance + group_id = 0 for form in formset.forms: - form.instance.meta_result = self.object + form.instance.comparison_set = self.object + if form.is_valid() and form not in formset.deleted_forms: + form.instance.group_id = group_id + if form.has_changed() is False: + form.instance.save() # ensure new group_id saved to db + group_id += 1 def build_initial_formset_factory(self): + return forms.BlankGroupFormset( + queryset=models.Group.objects.none()) - def get_initial_data(field, **kwargs): - formfield = field.formfield(**kwargs) - if field.name == "study": - formfield.queryset = formfield.queryset.filter( - assessment=self.assessment, study_type=1) - return formfield - return modelformset_factory( - models.SingleResult, - form=forms.SingleResultForm, - formset=forms.EmptySingleResultFormset, - formfield_callback=get_initial_data, - extra=1) - - def get_form_kwargs(self): - kwargs = super(MetaResultCreate, self).get_form_kwargs() - kwargs['assessment_id'] = self.assessment.id - return kwargs +class ComparisonSetOutcomeCreate(ComparisonSetCreate): + parent_model = models.Outcome + parent_template_name = 'outcome' -class MetaResultCopyAsNewSelector(MetaProtocolDetail): - # Select an existing meta-result as a template for a new one - template_name = 'epi/metaresult_copy_selector.html' +class ComparisonSetStudyPopCopySelector(StudyPopulationDetail): + template_name = 'epi/comparisonset_sp_copy_selector.html' def get_context_data(self, **kwargs): - context = super(MetaResultCopyAsNewSelector, self).get_context_data(**kwargs) - context['form'] = forms.MetaResultSelectorForm(study_id=self.object.study_id) + context = super(ComparisonSetStudyPopCopySelector, self).get_context_data(**kwargs) + context['form'] = forms.ComparisonSetByStudyPopulationSelectorForm(parent_id=self.object.id) return context -class MetaResultDetail(BaseDetail): - model = models.MetaResult +class ComparisonSetOutcomeCopySelector(OutcomeDetail): + template_name = 'epi/comparisonset_outcome_copy_selector.html' + def get_context_data(self, **kwargs): + context = super(ComparisonSetOutcomeCopySelector, self).get_context_data(**kwargs) + context['form'] = forms.ComparisonSetByOutcomeSelectorForm(parent_id=self.object.id) + return context -class MetaResultJSON(MetaResultDetail): - http_method_names = ('get', ) - def get(self, request, *args, **kwargs): - self.object = self.get_object() - return HttpResponse(self.object.get_json(json_encode=True), content_type="application/json") +class ComparisonSetDetail(BaseDetail): + model = models.ComparisonSet -class MetaResultUpdate(BaseUpdateWithFormset): - success_message = "Meta-Result updated." - model = models.MetaResult - form_class = forms.MetaResultForm - formset_factory = forms.SingleResultFormset +class ComparisonSetUpdate(BaseUpdateWithFormset): + success_message = "Comparison set updated." + model = models.ComparisonSet + form_class = forms.ComparisonSet + formset_factory = forms.GroupFormset def build_initial_formset_factory(self): - formset = forms.SingleResultFormset(queryset=self.object.single_results.all().order_by('pk')) - forms.meta_result_clean_update_formset(formset, self.assessment) - return formset - - def get_form_kwargs(self): - kwargs = super(MetaResultUpdate, self).get_form_kwargs() - kwargs['assessment_id'] = self.assessment.id - return kwargs + return forms.GroupFormset(queryset=self.object.groups.all() + .order_by('group_id')) def post_object_save(self, form, formset): - # Bind newly single-result outcome to meta-result instance, if adding new + group_id = 0 for form in formset.forms: - form.instance.meta_result = self.object + form.instance.comparison_set = self.object + if form.is_valid() and form not in formset.deleted_forms: + form.instance.group_id = group_id + if form.has_changed() is False: + form.instance.save() # ensure new group_id saved to db + group_id += 1 -class MetaResultDelete(BaseDelete): - success_message = "Meta-Result deleted." - model = models.MetaResult +class ComparisonSetDelete(BaseDelete): + success_message = "Comparison set deleted." + model = models.ComparisonSet def get_success_url(self): - self.parent = self.object.protocol - return reverse("epi:mp_detail", kwargs={"pk": self.parent.pk}) - - -class MetaResultReport(GenerateReport): - parent_model = Assessment - model = models.MetaResult - report_type = 4 + if self.object.study_population: + return self.object.study_population.get_absolute_url() + else: + return self.object.outcome.get_absolute_url() - def get_queryset(self): - filters = {"protocol__study__assessment": self.assessment} - perms = super(MetaResultReport, self).get_obj_perms() - if not perms['edit'] or self.onlyPublished: - filters["protocol__study__published"] = True - return self.model.objects.filter(**filters) - - def get_filename(self): - return "meta-results.docx" - - def get_context(self, queryset): - return self.model.get_docx_template_context(self.assessment, queryset) +class GroupDetail(BaseDetail): + model = models.Group -class MetaResultList(BaseList): - parent_model = Assessment - model = models.MetaResult - - def get_paginate_by(self, qs): - val = 25 - try: - val = int(self.request.GET.get('paginate_by', val)) - except ValueError: - pass - return val - def get_queryset(self): - filters = {"protocol__study__assessment": self.assessment} - perms = self.get_obj_perms() - if not perms['edit']: - filters["protocol__study__published"] = True - return self.model.objects.filter(**filters).order_by('label') +class GroupUpdate(BaseUpdateWithFormset): + success_message = "Groups updated." + model = models.Group + form_class = forms.SingleGroupForm + formset_factory = forms.GroupNumericalDescriptionsFormset + def build_initial_formset_factory(self): + return forms.GroupNumericalDescriptionsFormset( + queryset=self.object.descriptions.all()) -class MetaResultFullExport(MetaResultList): - """ - Full XLS data export for the epidemiology meta-analyses. - """ - def get(self, request, *args, **kwargs): - self.object_list = self.get_queryset() - exporter = exports.MetaResultFlatComplete( - self.object_list, - export_format="excel", - filename='{}-epi-meta-analysis'.format(self.assessment), - sheet_name='epi-meta-analysis') - return exporter.build_response() + def post_object_save(self, form, formset): + for form in formset: + form.instance.group = self.object diff --git a/project/epi2/__init__.py b/project/epi2/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/project/epi2/admin.py b/project/epi2/admin.py deleted file mode 100644 index 70750595..00000000 --- a/project/epi2/admin.py +++ /dev/null @@ -1,36 +0,0 @@ -from django.contrib import admin - -from . import models - - -class CriteriaAdmin(admin.ModelAdmin): - pass - - -class CountryAdmin(admin.ModelAdmin): - - search_fields = ( - 'name', - ) - - -class AdjustmentFactorAdmin(admin.ModelAdmin): - pass - - -class EthnicityAdmin(admin.ModelAdmin): - pass - - -class ResultMetricAdmin(admin.ModelAdmin): - list_display = ( - "metric", "abbreviation", "showForestPlot", - "isLog", "reference_value", "order", - ) - - -admin.site.register(models.Criteria, CriteriaAdmin) -admin.site.register(models.Country, CountryAdmin) -admin.site.register(models.AdjustmentFactor, AdjustmentFactorAdmin) -admin.site.register(models.Ethnicity, EthnicityAdmin) -admin.site.register(models.ResultMetric, ResultMetricAdmin) diff --git a/project/epi2/api.py b/project/epi2/api.py deleted file mode 100644 index a3a9d406..00000000 --- a/project/epi2/api.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import absolute_import - -from assessment.api.views import AssessmentViewset - -from . import models, serializers - - -class StudyPopulation(AssessmentViewset): - assessment_filter_args = "study__assessment" - model = models.StudyPopulation - serializer_class = serializers.StudyPopulationSerializer - - -class Exposure(AssessmentViewset): - assessment_filter_args = "study_population__study__assessment" - model = models.Exposure2 - serializer_class = serializers.ExposureSerializer - - -class Outcome(AssessmentViewset): - assessment_filter_args = "assessment" - model = models.Outcome - serializer_class = serializers.OutcomeSerializer - - -class Result(AssessmentViewset): - assessment_filter_args = "outcome__assessment" - model = models.Result - serializer_class = serializers.ResultSerializer - - -class ComparisonSet(AssessmentViewset): - assessment_filter_args = "assessment" # todo: fix - model = models.ComparisonSet - serializer_class = serializers.ComparisonSetSerializer - - -class Group(AssessmentViewset): - assessment_filter_args = "assessment" # todo: fix - model = models.Group - serializer_class = serializers.GroupSerializer diff --git a/project/epi2/exports.py b/project/epi2/exports.py deleted file mode 100644 index fbb1f7f5..00000000 --- a/project/epi2/exports.py +++ /dev/null @@ -1,159 +0,0 @@ -from study.models import Study -from utils.helper import FlatFileExporter - -from . import models - - -class OutcomeComplete(FlatFileExporter): - - def _get_header_row(self): - header = [] - header.extend(Study.flat_complete_header_row()) - header.extend(models.StudyPopulation.flat_complete_header_row()) - header.extend(models.Outcome.flat_complete_header_row()) - header.extend(models.Exposure2.flat_complete_header_row()) - header.extend(models.ComparisonSet.flat_complete_header_row()) - header.extend(models.Result.flat_complete_header_row()) - header.extend(models.Group.flat_complete_header_row()) - header.extend(models.GroupResult.flat_complete_header_row()) - return header - - def _get_data_rows(self): - rows = [] - for obj in self.queryset: - ser = obj.get_json(json_encode=False) - row = [] - row.extend(Study.flat_complete_data_row(ser['study_population']['study'])) - row.extend(models.StudyPopulation.flat_complete_data_row(ser['study_population'])) - row.extend(models.Outcome.flat_complete_data_row(ser)) - for res in ser['results']: - row_copy = list(row) - row_copy.extend(models.Exposure2.flat_complete_data_row(res["comparison_set"]["exposure"])) - row_copy.extend(models.ComparisonSet.flat_complete_data_row(res["comparison_set"])) - row_copy.extend(models.Result.flat_complete_data_row(res)) - for rg in res['results']: - row_copy2 = list(row_copy) - row_copy2.extend(models.Group.flat_complete_data_row(rg["group"])) - row_copy2.extend(models.GroupResult.flat_complete_data_row(rg)) - rows.append(row_copy2) - return rows - - -class OutcomeDataPivot(FlatFileExporter): - - def _get_header_row(self): - return [ - 'Study', - 'Study URL', - 'Study HAWC ID', - 'Study Published?', - - 'Study Population Name', - 'Study Population Key', - 'Design', - 'Study Population URL', - - 'Assessed Outcome Key', - 'Assessed Outcome Name', - 'Diagnostic', - - 'Comparison Set ID', - 'Comparison Set name', - - 'Exposure', - 'Exposure Key', - 'Exposure Metric', - 'Exposure URL', - 'Dose Units', - - 'Result ID', - 'Assessed Outcome Population Description', - 'Statistical Metric', - 'Statistical Metric Abbreviation', - 'Statistical Metric Description', - 'Outcome Summary', - 'Dose Response', - 'Statistical Power', - 'CI units', - - 'Exposure Group Name', - 'Exposure Group Comparative Description Name', - 'Exposure Group Order', - 'Exposure Group Numeric', - - 'Row Key', - 'Assessed Outcome Group Primary Key', - 'N', - 'Estimate', - 'Lower CI', - 'Upper CI', - 'SE', - 'Statistical Significance', - 'Statistical Significance (numeric)', - 'Main Finding', - 'Support Main Finding', - ] - - def _get_data_rows(self): - rows = [] - for obj in self.queryset: - ser = obj.get_json(json_encode=False) - row = [ - ser['study_population']['study']['short_citation'], - ser['study_population']['study']['url'], - ser['study_population']['study']['id'], - ser['study_population']['study']['published'], - - ser['study_population']['name'], - ser['study_population']['id'], - ser['study_population']['design'], - ser['study_population']['url'], - - ser['id'], - ser['name'], - ser['diagnostic'], - ] - for res in ser['results']: - row_copy = list(row) - row_copy.extend([ - res["comparison_set"]["id"], - res["comparison_set"]["name"], - - res["comparison_set"]["exposure"]["name"], - res["comparison_set"]["exposure"]["id"], - res["comparison_set"]["exposure"]["metric"], - res["comparison_set"]["exposure"]["url"], - res["comparison_set"]["exposure"]["metric_units"]["name"], - - res['id'], - res['population_description'], - res['metric']['metric'], - res['metric']['abbreviation'], - res['metric_description'], - res['comments'], - res['dose_response'], - res['statistical_power'], - res['ci_units'], - ]) - for rg in res['results']: - row_copy2 = list(row_copy) - row_copy2.extend([ - rg['group']['name'], - rg['group']['comparative_name'], - rg['group']['group_id'], - rg['group']['numeric'], - - rg['id'], # repeat for data-pivot key - rg['id'], - rg['n'], - rg['estimate'], - rg['lower_ci'], - rg['upper_ci'], - rg['variance'], - rg['p_value_text'], - rg['p_value'], - rg['is_main_finding'], - rg['main_finding_support'], - ]) - rows.append(row_copy2) - return rows diff --git a/project/epi2/forms.py b/project/epi2/forms.py deleted file mode 100644 index 6caf6fb3..00000000 --- a/project/epi2/forms.py +++ /dev/null @@ -1,796 +0,0 @@ -from django import forms -from django.db.models import Q -from django.core.urlresolvers import reverse -from django.forms.models import BaseModelFormSet, modelformset_factory -from django.utils.functional import curry - -from crispy_forms import layout as cfl -from selectable import forms as selectable - -from assessment.lookups import BaseEndpointLookup, EffectTagLookup -from utils.forms import BaseFormHelper, CopyAsNewSelectorForm - -from . import models, lookups - - -class CriteriaForm(forms.ModelForm): - - CREATE_LEGEND = u"Create new study criteria" - - CREATE_HELP_TEXT = u""" - Create a epidemiology study criteria. Study criteria can be applied to - study populations as inclusion criteria, exclusion criteria, or - confounding criteria. They are assessment-specific. Please take care - not to duplicate existing factors.""" - - class Meta: - model = models.Criteria - exclude = ('assessment', ) - - def __init__(self, *args, **kwargs): - assessment = kwargs.pop('parent', None) - super(CriteriaForm, self).__init__(*args, **kwargs) - self.fields['description'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.CriteriaLookup, - allow_new=True) - self.instance.assessment = assessment - for fld in self.fields.keys(): - self.fields[fld].widget.attrs['class'] = 'span12' - self.fields['description'].widget.update_query_parameters( - {'related': self.instance.assessment.id}) - self.helper = self.setHelper() - - def clean(self): - super(CriteriaForm, self).clean() - # assessment-description unique-together constraint check must be - # added since assessment is not included on form - pk = getattr(self.instance, 'pk', None) - crits = models.Criteria.objects \ - .filter(assessment=self.instance.assessment, - description=self.cleaned_data.get('description', "")) \ - .exclude(pk=pk) - - if crits.count() > 0: - self.add_error("description", "Must be unique for assessment") - - return self.cleaned_data - - def setHelper(self): - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if type(widget) != forms.CheckboxInput: - widget.attrs['class'] = 'span12' - - inputs = { - "legend_text": self.CREATE_LEGEND, - "help_text": self.CREATE_HELP_TEXT, - "form_actions": [ - cfl.Submit('save', 'Save'), - cfl.HTML("""Cancel"""), - ] - } - - helper = BaseFormHelper(self, **inputs) - helper.form_class = None - return helper - - -class StudyPopulationForm(forms.ModelForm): - - CREATE_LEGEND = u"Create new study-population" - - CREATE_HELP_TEXT = u""" - Create a new study population. Each study-population is a - associated with an epidemiology study. There may be - multiple study populations with a single study, - though this is typically unlikely.""" - - UPDATE_HELP_TEXT = u"Update an existing study-population." - - CRITERION_FIELDS = [ - "inclusion_criteria", - "exclusion_criteria", - "confounding_criteria" - ] - - CRITERION_TYPE_CW = { - "inclusion_criteria": "I", - "exclusion_criteria": "E", - "confounding_criteria": "C", - } - - inclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.CriteriaLookup, - required=False) - - exclusion_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.CriteriaLookup, - required=False) - - confounding_criteria = selectable.AutoCompleteSelectMultipleField( - lookup_class=lookups.CriteriaLookup, - required=False) - - class Meta: - model = models.StudyPopulation - exclude = ('study', 'criteria') - - def __init__(self, *args, **kwargs): - study = kwargs.pop('parent', None) - super(StudyPopulationForm, self).__init__(*args, **kwargs) - self.fields['comments'] = self.fields.pop('comments') # move to end - self.fields['region'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.RegionLookup, - allow_new=True) - self.fields['state'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.StateLookup, - allow_new=True) - if study: - self.instance.study = study - - for fld in self.CRITERION_FIELDS: - self.fields[fld].widget.update_query_parameters( - {'related': self.instance.study.assessment_id}) - if self.instance.id: - self.fields[fld].initial = getattr(self.instance, fld) - - self.helper = self.setHelper() - - def save_criteria(self): - """ - StudyPopulationCriteria is a through model; requires the criteria type. - We save the m2m relations using the additional information from the - field-name - """ - self.instance.spcriteria.all().delete() - objs = [] - for field in self.CRITERION_FIELDS: - for criteria in self.cleaned_data.get(field, []): - objs.append(models.StudyPopulationCriteria( - criteria=criteria, - study_population=self.instance, - criteria_type=self.CRITERION_TYPE_CW[field])) - models.StudyPopulationCriteria.objects.bulk_create(objs) - - def save(self, commit=True): - instance = super(StudyPopulationForm, self).save(commit) - if commit: - self.save_criteria() - return instance - - def setHelper(self): - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if type(widget) != forms.CheckboxInput: - if fld in self.CRITERION_FIELDS: - widget.attrs['class'] = 'span10' - else: - widget.attrs['class'] = 'span12' - if type(widget) == forms.Textarea: - widget.attrs['rows'] = 3 - - if self.instance.id: - inputs = { - "legend_text": u"Update {}".format(self.instance), - "help_text": self.UPDATE_HELP_TEXT, - "cancel_url": self.instance.get_absolute_url() - } - else: - inputs = { - "legend_text": self.CREATE_LEGEND, - "help_text": self.CREATE_HELP_TEXT, - "cancel_url": self.instance.study.get_absolute_url() - } - - helper = BaseFormHelper(self, **inputs) - helper.form_class = None - helper.add_fluid_row('name', 2, "span6") - helper.add_fluid_row('age_profile', 2, "span6") - helper.add_fluid_row('country', 3, "span4") - helper.add_fluid_row('eligible_n', 3, "span4") - helper.add_fluid_row('inclusion_criteria', 3, "span4") - - url = reverse('epi2:studycriteria_create', - kwargs={'pk': self.instance.study.assessment.pk}) - helper.addBtnLayout(helper.layout[6], 0, url, "Create criteria", "span4") - helper.addBtnLayout(helper.layout[6], 1, url, "Create criteria", "span4") - helper.addBtnLayout(helper.layout[6], 2, url, "Create criteria", "span4") - - return helper - - -class StudyPopulationSelectorForm(CopyAsNewSelectorForm): - label = 'Study Population' - lookup_class = lookups.StudyPopulationByStudyLookup - - -class AdjustmentFactorForm(forms.ModelForm): - - CREATE_LEGEND = u"Create new adjustment factor" - - CREATE_HELP_TEXT = u""" - Create a new adjustment factor. Adjustment factors can be applied to - outcomes as applied or considered factors. - They are assessment-specific. - Please take care not to duplicate existing factors.""" - - class Meta: - model = models.AdjustmentFactor - exclude = ('assessment', ) - - def __init__(self, *args, **kwargs): - assessment = kwargs.pop('parent', None) - super(AdjustmentFactorForm, self).__init__(*args, **kwargs) - self.fields['description'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.AdjustmentFactorLookup, - allow_new=True) - self.instance.assessment = assessment - for fld in self.fields.keys(): - self.fields[fld].widget.attrs['class'] = 'span12' - self.fields['description'].widget.update_query_parameters( - {'related': self.instance.assessment.id}) - self.helper = self.setHelper() - - def clean(self): - super(AdjustmentFactorForm, self).clean() - # assessment-description unique-together constraint check must be - # added since assessment is not included on form - pk = getattr(self.instance, 'pk', None) - crits = models.AdjustmentFactor.objects \ - .filter(assessment=self.instance.assessment, - description=self.cleaned_data.get('description', "")) \ - .exclude(pk=pk) - - if crits.count() > 0: - self.add_error("description", "Must be unique for assessment") - - return self.cleaned_data - - def setHelper(self): - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if type(widget) != forms.CheckboxInput: - widget.attrs['class'] = 'span12' - - inputs = { - "legend_text": self.CREATE_LEGEND, - "help_text": self.CREATE_HELP_TEXT, - "form_actions": [ - cfl.Submit('save', 'Save'), - cfl.HTML("""Cancel"""), - ] - } - - helper = BaseFormHelper(self, **inputs) - helper.form_class = None - return helper - - -class ExposureForm(forms.ModelForm): - - HELP_TEXT_CREATE = """Create a new exposure.""" - HELP_TEXT_UPDATE = """Update an existing exposure.""" - - class Meta: - model = models.Exposure2 - exclude = ('study_population', ) - - def __init__(self, *args, **kwargs): - study_population = kwargs.pop('parent', None) - super(ExposureForm, self).__init__(*args, **kwargs) - if study_population: - self.instance.study_population = study_population - self.helper = self.setHelper() - - def setHelper(self): - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if type(widget) != forms.CheckboxInput: - if fld in ["metric_units"]: - widget.attrs['class'] = 'span10' - else: - widget.attrs['class'] = 'span12' - - if type(widget) == forms.Textarea: - widget.attrs['rows'] = 3 - - if self.instance.id: - inputs = { - "legend_text": u"Update {}".format(self.instance), - "help_text": self.HELP_TEXT_UPDATE, - "cancel_url": self.instance.get_absolute_url() - } - else: - inputs = { - "legend_text": u"Create new exposure", - "help_text": self.HELP_TEXT_CREATE, - "cancel_url": self.instance.study_population.get_absolute_url() - } - - helper = BaseFormHelper(self, **inputs) - helper.form_class = None - helper.add_fluid_row('inhalation', 6, "span2") - helper.add_fluid_row('measured', 3, "span4") - helper.add_fluid_row('metric_description', 3, "span4") - helper.add_fluid_row('duration', 2, "span6") - - url = reverse( - 'assessment:dose_units_create', - kwargs={'pk': self.instance.study_population.study.assessment_id} - ) - helper.addBtnLayout(helper.layout[4], 2, url, "Create units", "span4") - - return helper - - -class ExposureSelectorForm(CopyAsNewSelectorForm): - label = 'Exposure' - lookup_class = lookups.ExposureByStudyPopulationLookup - - -class OutcomeForm(forms.ModelForm): - - HELP_TEXT_CREATE = """Create a new outcome. An - outcome is an response measured in an epidemiological study, - associated with an exposure-metric. The overall outcome is - described, and then quantitative differences in response based on - different exposure-metric groups is detailed below. - """ - HELP_TEXT_UPDATE = """Update an existing outcome.""" - - class Meta: - model = models.Outcome - exclude = ('assessment', 'study_population') - - def __init__(self, *args, **kwargs): - assessment = kwargs.pop('assessment', None) - study_population = kwargs.pop('parent', None) - super(OutcomeForm, self).__init__(*args, **kwargs) - self.fields['name'].widget = selectable.AutoCompleteWidget( - lookup_class=BaseEndpointLookup, - allow_new=True) - self.fields['system'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.SystemLookup, - allow_new=True) - self.fields['effect'].widget = selectable.AutoCompleteWidget( - lookup_class=lookups.EffectLookup, - allow_new=True) - self.fields['effects'].widget = selectable.AutoCompleteSelectMultipleWidget( - lookup_class=EffectTagLookup) - self.fields['effects'].help_text = 'Tags used to help categorize effect description.' - if assessment: - self.instance.assessment = assessment - if study_population: - self.instance.study_population = study_population - - self.helper = self.setHelper() - - def setHelper(self): - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if type(widget) != forms.CheckboxInput: - if fld in ["effects"]: - widget.attrs['class'] = 'span10' - else: - widget.attrs['class'] = 'span12' - if type(widget) == forms.Textarea: - widget.attrs['rows'] = 3 - - if self.instance.id: - inputs = { - "legend_text": u"Update {}".format(self.instance), - "help_text": self.HELP_TEXT_UPDATE, - "cancel_url": self.instance.get_absolute_url() - } - else: - inputs = { - "legend_text": u"Create new outcome", - "help_text": self.HELP_TEXT_CREATE, - "cancel_url": self.instance.study_population.get_absolute_url() - } - - helper = BaseFormHelper(self, **inputs) - helper.form_class = None - helper.add_fluid_row('name', 4, "span3") - helper.add_fluid_row('diagnostic', 2, "span6") - - url = reverse( - 'assessment:effect_tag_create', - kwargs={'pk': self.instance.assessment.pk} - ) - helper.addBtnLayout(helper.layout[2], 1, url, "Add new effect tag", "span3") - - return helper - - -class OutcomeSelectorForm(CopyAsNewSelectorForm): - label = 'Outcome' - lookup_class = lookups.OutcomeByStudyPopulationLookup - - -class ComparisonSet(forms.ModelForm): - - HELP_TEXT_CREATE = """Create a new comparison set. Each group is a - collection of people, and all groups in this collection are - comparable to one-another. For example, you may a new comparison set - which contains two groups: cases and controls. Alternatively, for - cohort-based studies, you may create a new comparison set with four - different groups, one for each quartile of exposure based on exposure - measurements. - """ - HELP_TEXT_UPDATE = """Update an existing comparison set.""" - - class Meta: - model = models.ComparisonSet - exclude = ('study_population', 'outcome') - - def __init__(self, *args, **kwargs): - self.parent = kwargs.pop('parent', None) - super(ComparisonSet, self).__init__(*args, **kwargs) - if self.parent: - if type(self.parent) == models.StudyPopulation: - self.instance.study_population = self.parent - elif type(self.parent) == models.Outcome: - self.instance.outcome = self.parent - - filters = {} - if self.instance.study_population: - filters["study_population"] = self.instance.study_population - else: - filters["study_population"] = self.instance.outcome.study_population - self.fields["exposure"].queryset = self.fields["exposure"].queryset.filter(**filters) - - self.helper = self.setHelper() - - def setHelper(self): - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if type(widget) != forms.CheckboxInput: - widget.attrs['class'] = 'span12' - if type(widget) == forms.Textarea: - widget.attrs['rows'] = 3 - - if self.instance.id: - if self.instance.outcome: - url = self.instance.outcome.get_absolute_url() - else: - url = self.instance.study_population.get_absolute_url() - inputs = { - "legend_text": u"Update {}".format(self.instance), - "help_text": self.HELP_TEXT_UPDATE, - "cancel_url": url - } - else: - inputs = { - "legend_text": u"Create new comparison set", - "help_text": self.HELP_TEXT_CREATE, - "cancel_url": self.parent.get_absolute_url() - } - - helper = BaseFormHelper(self, **inputs) - helper.form_class = None - return helper - - -class ComparisonSetByStudyPopulationSelectorForm(CopyAsNewSelectorForm): - label = 'Comparison set' - lookup_class = lookups.ComparisonSetByStudyPopulationLookup - - -class ComparisonSetByOutcomeSelectorForm(CopyAsNewSelectorForm): - label = 'Comparison set' - lookup_class = lookups.ComparisonSetByOutcomeLookup - - -class GroupForm(forms.ModelForm): - - class Meta: - model = models.Group - exclude = ('comparison_set', 'group_id') - - -class SingleGroupForm(GroupForm): - - HELP_TEXT_UPDATE = """Update an existing group and group descriptions.""" - - def __init__(self, *args, **kwargs): - super(SingleGroupForm, self).__init__(*args, **kwargs) - self.helper = self.setHelper() - - def setHelper(self): - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if type(widget) != forms.CheckboxInput: - widget.attrs['class'] = 'span12' - if type(widget) == forms.Textarea: - widget.attrs['rows'] = 3 - - inputs = { - "legend_text": u"Update {}".format(self.instance), - "help_text": self.HELP_TEXT_UPDATE, - "cancel_url": self.instance.get_absolute_url() - } - - helper = BaseFormHelper(self, **inputs) - helper.form_class = None - helper.add_fluid_row('name', 3, "span4") - helper.add_fluid_row('sex', 2, "span6") - helper.add_fluid_row('eligible_n', 3, "span4") - return helper - - -class BaseGroupFormset(BaseModelFormSet): - - def clean(self): - super(BaseGroupFormset, self).clean() - - # check that there is at least one exposure-group - count = len(filter(lambda f: f.is_valid() and f.clean(), self.forms)) - if count < 1: - raise forms.ValidationError("At least one group is required.") - - -GroupFormset = modelformset_factory( - models.Group, - form=GroupForm, - formset=BaseGroupFormset, - can_delete=True, - extra=0) - - -BlankGroupFormset = modelformset_factory( - models.Group, - form=GroupForm, - formset=BaseGroupFormset, - can_delete=False, - extra=1) - - -class GroupNumericalDescriptionsForm(forms.ModelForm): - - class Meta: - model = models.GroupNumericalDescriptions - exclude = ('group', ) - - -class BaseGroupNumericalDescriptionsFormset(BaseModelFormSet): - pass - - -GroupNumericalDescriptionsFormset = modelformset_factory( - models.GroupNumericalDescriptions, - form=GroupNumericalDescriptionsForm, - formset=BaseGroupNumericalDescriptionsFormset, - can_delete=True, - extra=1) - - -class ResultForm(forms.ModelForm): - - HELP_TEXT_CREATE = """Describe results found for measured outcome.""" - HELP_TEXT_UPDATE = """Update results found for measured outcome.""" - ADJUSTMENT_FIELDS = ["factors_applied", "factors_considered"] - - factors_applied = selectable.AutoCompleteSelectMultipleField( - help_text="All factors included in final model", - lookup_class=lookups.AdjustmentFactorLookup, - required=False) - - factors_considered = selectable.AutoCompleteSelectMultipleField( - label="Adjustment factors considered", - help_text="Factors considered, but not included in the final model", - lookup_class=lookups.AdjustmentFactorLookup, - required=False) - - class Meta: - model = models.Result - exclude = ('outcome', 'adjustment_factors') - - def __init__(self, *args, **kwargs): - outcome = kwargs.pop('parent', None) - super(ResultForm, self).__init__(*args, **kwargs) - self.fields['comments'] = self.fields.pop('comments') # move to end - - if outcome: - self.instance.outcome = outcome - else: - outcome = self.instance.outcome - - self.fields["comparison_set"].queryset = models.ComparisonSet.objects\ - .filter( - Q(study_population=outcome.study_population) | - Q(outcome=outcome) - ) - - for fld in self.ADJUSTMENT_FIELDS: - self.fields[fld].widget.update_query_parameters( - {'related': self.instance.outcome.assessment_id}) - if self.instance.id: - self.fields[fld].initial = getattr(self.instance, fld) - - self.helper = self.setHelper() - - def save_factors(self): - """ - Adjustment factors is a through model; requires the inclusion type. - We save the m2m relations using the additional information from the - field-name - """ - self.instance.resfactors.all().delete() - objs = [] - - applied = self.cleaned_data.get("factors_applied", []) - objs.extend([ - models.ResultAdjustmentFactor( - adjustment_factor=af, - result=self.instance, - included_in_final_model=True) - for af in applied - ]) - - considered = self.cleaned_data.get("factors_considered", []) - considered = set(considered) - set(applied) - objs.extend([ - models.ResultAdjustmentFactor( - adjustment_factor=af, - result=self.instance, - included_in_final_model=False) - for af in considered - ]) - - models.ResultAdjustmentFactor.objects.bulk_create(objs) - - def save(self, commit=True): - instance = super(ResultForm, self).save(commit) - if commit: - self.save_factors() - return instance - - def setHelper(self): - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if type(widget) != forms.CheckboxInput: - if fld in self.ADJUSTMENT_FIELDS: - widget.attrs['class'] = 'span10' - else: - widget.attrs['class'] = 'span12' - if type(widget) == forms.Textarea: - widget.attrs['rows'] = 3 - - if self.instance.id: - inputs = { - "legend_text": u"Update {}".format(self.instance), - "help_text": self.HELP_TEXT_UPDATE, - "cancel_url": self.instance.get_absolute_url() - } - else: - inputs = { - "legend_text": u"Create new set of results", - "help_text": self.HELP_TEXT_CREATE, - "cancel_url": self.instance.outcome.get_absolute_url() - } - - helper = BaseFormHelper(self, **inputs) - helper.form_class = None - - helper.add_fluid_row('metric', 2, "span6") - helper.add_fluid_row('data_location', 2, "span6") - helper.add_fluid_row('dose_response', 3, "span4") - helper.add_fluid_row('statistical_power', 3, "span4") - helper.add_fluid_row('factors_applied', 2, "span6") - helper.add_fluid_row('estimate_type', 3, "span4") - - url = reverse('epi2:adjustmentfactor_create', - kwargs={'pk': self.instance.outcome.assessment_id}) - helper.addBtnLayout(helper.layout[8], 0, url, "Add new adjustment factor", "span6") - helper.addBtnLayout(helper.layout[8], 1, url, "Add new adjustment factor", "span6") - - return helper - - -class ResultSelectorForm(CopyAsNewSelectorForm): - label = 'Result' - lookup_class = lookups.ResultByOutcomeLookup - - -class ResultUpdateForm(ResultForm): - - def __init__(self, *args, **kwargs): - super(ResultUpdateForm, self).__init__(*args, **kwargs) - self.fields['comparison_set'].widget.attrs['disabled'] = True - - -class GroupResultForm(forms.ModelForm): - - class Meta: - model = models.GroupResult - exclude = ('result', ) - - def __init__(self, *args, **kwargs): - study_population = kwargs.pop('study_population', None) - outcome = kwargs.pop('outcome', None) - result = kwargs.pop('result', None) - super(GroupResultForm, self).__init__(*args, **kwargs) - self.fields["group"].queryset = models.Group.objects\ - .filter( - Q(comparison_set__study_population=study_population) | - Q(comparison_set__outcome=outcome) - ) - if result: - self.instance.result = result - self.helper = self.setHelper() - - def setHelper(self): - for fld in self.fields.keys(): - widget = self.fields[fld].widget - if fld == "group": - widget.attrs['class'] = "groupField" - widget.attrs['style'] = "display: none;" - if fld == "n": - widget.attrs['class'] = "nField" - - helper = BaseFormHelper(self) - helper.form_tag = False - - return helper - - -class BaseGroupResultFormset(BaseModelFormSet): - - def __init__(self, **kwargs): - study_population = kwargs.pop('study_population', None) - outcome = kwargs.pop('outcome', None) - self.result = kwargs.pop('result', None) - super(BaseGroupResultFormset, self).__init__(**kwargs) - self.form = curry( - self.form, - study_population=study_population, - outcome=outcome, - result=self.result - ) - - def clean(self): - super(BaseGroupResultFormset, self).clean() - - # check that there is at least one result-group - count = len(filter(lambda f: f.is_valid() and f.clean(), self.forms)) - if count < 1: - raise forms.ValidationError("At least one group is required.") - - mfs = 0 - for form in self.forms: - if form.cleaned_data['is_main_finding']: - mfs += 1 - - if mfs > 1: - raise forms.ValidationError("Only one-group can be the main-finding.") - - # Ensure all groups in group collection are accounted for, and no other - # groups exist - group_ids = [form.cleaned_data['group'].id for form in self.forms] - if self.result: - comparison_set_id = self.result.comparison_set_id - else: - comparison_set_id = int(self.data['comparison_set']) - selectedGroups = models.Group.objects\ - .filter(id__in=group_ids, comparison_set_id=comparison_set_id) - allGroups = models.Group.objects\ - .filter(comparison_set_id=comparison_set_id) - if selectedGroups.count() != allGroups.count(): - raise forms.ValidationError("Missing group(s) in this comparison set") - - -GroupResultFormset = modelformset_factory( - models.GroupResult, - form=GroupResultForm, - formset=BaseGroupResultFormset, - can_delete=False, - extra=0) - - -BlankGroupResultFormset = modelformset_factory( - models.GroupResult, - form=GroupResultForm, - formset=BaseGroupResultFormset, - can_delete=False, - extra=1) diff --git a/project/epi2/lookups.py b/project/epi2/lookups.py deleted file mode 100644 index b4f7d4b9..00000000 --- a/project/epi2/lookups.py +++ /dev/null @@ -1,88 +0,0 @@ -from selectable.registry import registry - -from utils.lookups import DistinctStringLookup, RelatedLookup - -from . import models - - -class StudyPopulationByStudyLookup(RelatedLookup): - model = models.StudyPopulation - search_fields = ('name__icontains', ) - related_filter = 'study_id' - - -class RegionLookup(DistinctStringLookup): - model = models.StudyPopulation - distinct_field = "region" - - -class StateLookup(DistinctStringLookup): - model = models.StudyPopulation - distinct_field = "state" - - -class CriteriaLookup(RelatedLookup): - model = models.Criteria - search_fields = ('description__icontains', ) - related_filter = 'assessment_id' - - -class AdjustmentFactorLookup(RelatedLookup): - model = models.AdjustmentFactor - search_fields = ('description__icontains', ) - related_filter = 'assessment_id' - - -class ComparisonSetByStudyPopulationLookup(RelatedLookup): - model = models.ComparisonSet - search_fields = ('name__icontains', ) - related_filter = 'study_population_id' - - -class ComparisonSetByOutcomeLookup(ComparisonSetByStudyPopulationLookup): - related_filter = 'outcome_id' - - -class ExposureByStudyPopulationLookup(RelatedLookup): - model = models.Exposure2 - search_fields = ('name__icontains', ) - related_filter = 'study_population_id' - - -class OutcomeByStudyPopulationLookup(RelatedLookup): - model = models.Outcome - search_fields = ('name__icontains', ) - related_filter = 'study_population_id' - - -class EffectLookup(DistinctStringLookup): - model = models.Outcome - distinct_field = "effect" - - -class SystemLookup(DistinctStringLookup): - model = models.Outcome - distinct_field = "system" - - -class ResultByOutcomeLookup(RelatedLookup): - model = models.Result - search_fields = ( - 'metric__metric__icontains', - 'comparison_set__name__icontains' - ) - related_filter = 'outcome_id' - - -registry.register(StudyPopulationByStudyLookup) -registry.register(RegionLookup) -registry.register(StateLookup) -registry.register(CriteriaLookup) -registry.register(AdjustmentFactorLookup) -registry.register(ExposureByStudyPopulationLookup) -registry.register(ComparisonSetByStudyPopulationLookup) -registry.register(ComparisonSetByOutcomeLookup) -registry.register(OutcomeByStudyPopulationLookup) -registry.register(EffectLookup) -registry.register(SystemLookup) -registry.register(ResultByOutcomeLookup) diff --git a/project/epi2/migrations/0001_initial.py b/project/epi2/migrations/0001_initial.py deleted file mode 100644 index dbf5797a..00000000 --- a/project/epi2/migrations/0001_initial.py +++ /dev/null @@ -1,327 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import django.core.validators - - -class Migration(migrations.Migration): - - dependencies = [ - ('assessment', '0006_auto_20150724_1151'), - ('study', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='AdjustmentFactor', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('description', models.TextField()), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('assessment', models.ForeignKey(to='assessment.Assessment')), - ], - options={ - 'ordering': ('description',), - }, - ), - migrations.CreateModel( - name='ComparisonSet', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=256)), - ('description', models.TextField(blank=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ], - options={ - 'ordering': ('name',), - }, - ), - migrations.CreateModel( - name='Country', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('code', models.CharField(max_length=2, blank=True)), - ('name', models.CharField(unique=True, max_length=64)), - ], - options={ - 'ordering': ('name',), - 'verbose_name_plural': 'Countries', - }, - ), - migrations.CreateModel( - name='Criteria', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('description', models.TextField()), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('assessment', models.ForeignKey(to='assessment.Assessment')), - ], - options={ - 'ordering': ('description',), - 'verbose_name_plural': 'Criteria', - }, - ), - migrations.CreateModel( - name='Ethnicity', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(unique=True, max_length=64)), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ], - options={ - 'verbose_name_plural': 'Ethnicities', - }, - ), - migrations.CreateModel( - name='Exposure2', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(help_text=b'Name of exposure and exposure-route', max_length=128)), - ('inhalation', models.BooleanField(default=False)), - ('dermal', models.BooleanField(default=False)), - ('oral', models.BooleanField(default=False)), - ('in_utero', models.BooleanField(default=False)), - ('iv', models.BooleanField(default=False, verbose_name=b'Intravenous (IV)')), - ('unknown_route', models.BooleanField(default=False)), - ('measured', models.CharField(max_length=128, verbose_name=b'What was measured', blank=True)), - ('metric', models.CharField(max_length=128, verbose_name=b'Measurement Metric')), - ('metric_description', models.TextField(verbose_name=b'Measurement Description')), - ('analytical_method', models.TextField(help_text=b'Include details on the lab-techniques for exposure measurement in samples.')), - ('sampling_period', models.CharField(help_text=b'Exposure sampling period', max_length=128, blank=True)), - ('duration', models.CharField(help_text=b'Exposure duration', max_length=128, blank=True)), - ('exposure_distribution', models.CharField(help_text=b'May be used to describe the exposure distribution, for example, "2.05 \xc2\xb5g/g creatinine (urine), geometric mean; 25th percentile = 1.18, 75th percentile = 3.33"', max_length=128, blank=True)), - ('description', models.TextField(blank=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('metric_units', models.ForeignKey(to='assessment.DoseUnits')), - ], - options={ - 'ordering': ('name',), - 'verbose_name': 'Exposure', - 'verbose_name_plural': 'Exposures', - }, - ), - migrations.CreateModel( - name='Group', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('group_id', models.PositiveSmallIntegerField()), - ('name', models.CharField(max_length=256)), - ('numeric', models.FloatField(help_text=b'Numerical value, can be used for sorting', null=True, verbose_name=b'Numerical value (sorting)', blank=True)), - ('comparative_name', models.CharField(help_text=b'Group and value, displayed in plots, for example "1.5-2.5(Q2) vs \xe2\x89\xa41.5(Q1)", or if only one group is available, "4.8\xc2\xb10.2 (mean\xc2\xb1SEM)"', max_length=64, verbose_name=b'Comparative Name', blank=True)), - ('sex', models.CharField(default=b'U', max_length=1, choices=[(b'U', b'Not reported'), (b'M', b'Male'), (b'F', b'Female'), (b'B', b'Male and Female')])), - ('eligible_n', models.PositiveIntegerField(null=True, verbose_name=b'Eligible N', blank=True)), - ('invited_n', models.PositiveIntegerField(null=True, verbose_name=b'Invited N', blank=True)), - ('participant_n', models.PositiveIntegerField(null=True, verbose_name=b'Participant N', blank=True)), - ('isControl', models.NullBooleanField(default=None, choices=[(True, b'Yes'), (False, b'No'), (None, b'N/A')], help_text=b'Should this group be interpreted as a null/control group', verbose_name=b'Control?')), - ('comments', models.TextField(help_text=b'Any other comments related to this group', blank=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('comparison_set', models.ForeignKey(related_name='groups', to='epi2.ComparisonSet')), - ('ethnicities', models.ManyToManyField(to='epi2.Ethnicity', blank=True)), - ], - options={ - 'ordering': ('comparison_set', 'group_id'), - }, - ), - migrations.CreateModel( - name='GroupNumericalDescriptions', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('description', models.CharField(help_text=b'Description if numeric ages do not make sense for this study-population (ex: longitudinal studies)', max_length=128)), - ('mean', models.FloatField(null=True, verbose_name=b'Central estimate', blank=True)), - ('mean_type', models.PositiveSmallIntegerField(default=0, verbose_name=b'Central estimate type', choices=[(0, None), (1, b'mean'), (2, b'geometric mean'), (3, b'median'), (4, b'other')])), - ('is_calculated', models.BooleanField(default=False, help_text=b'Was value calculated/estimated from literature?')), - ('variance', models.FloatField(null=True, blank=True)), - ('variance_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'SD'), (2, b'SEM'), (3, b'GSD'), (4, b'other')])), - ('lower', models.FloatField(null=True, blank=True)), - ('lower_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'lower limit'), (2, b'5% CI'), (3, b'other')])), - ('upper', models.FloatField(null=True, blank=True)), - ('upper_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'upper limit'), (2, b'95% CI'), (3, b'other')])), - ('group', models.ForeignKey(related_name='descriptions', to='epi2.Group')), - ], - ), - migrations.CreateModel( - name='GroupResult', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('n', models.PositiveIntegerField(help_text=b'Individuals in group where outcome was measured', null=True, blank=True)), - ('estimate', models.FloatField(help_text=b'Central tendency estimate for group', null=True, blank=True)), - ('variance', models.FloatField(help_text=b'Variance estimate for group', null=True, verbose_name=b'Variance', blank=True)), - ('lower_ci', models.FloatField(help_text=b'Numerical value for lower-confidence interval', null=True, verbose_name=b'Lower CI', blank=True)), - ('upper_ci', models.FloatField(help_text=b'Numerical value for upper-confidence interval', null=True, verbose_name=b'Upper CI', blank=True)), - ('p_value_qualifier', models.CharField(default=b'-', max_length=1, verbose_name=b'p-value qualifier', choices=[(b' ', b'-'), (b'-', b'n.s.'), (b'<', b'<'), (b'=', b'='), (b'>', b'>')])), - ('p_value', models.FloatField(blank=True, null=True, verbose_name=b'p-value', validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)])), - ('is_main_finding', models.BooleanField(help_text=b'Is this the main-finding for this outcome?', verbose_name=b'Main finding')), - ('main_finding_support', models.PositiveSmallIntegerField(default=1, help_text=b'Are the results supportive of the main-finding?', choices=[(3, b'not-reported'), (2, b'supportive'), (1, b'inconclusive'), (0, b'not-supportive')])), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('group', models.ForeignKey(related_name='results', to='epi2.Group')), - ], - options={ - 'ordering': ('result', 'group__comparison_set_id'), - }, - ), - migrations.CreateModel( - name='Outcome', - fields=[ - ('baseendpoint_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='assessment.BaseEndpoint')), - ('system', models.CharField(help_text=b'Relevant biological system', max_length=128, blank=True)), - ('effect', models.CharField(help_text=b'Effect, using common-vocabulary', max_length=128, blank=True)), - ('diagnostic', models.PositiveSmallIntegerField(choices=[(0, b'not reported'), (1, b'medical professional or test'), (2, b'medical records'), (3, b'self-reported'), (4, b'questionnaire'), (5, b'hospital admission'), (6, b'other')])), - ('diagnostic_description', models.TextField()), - ('outcome_n', models.PositiveIntegerField(null=True, verbose_name=b'Outcome N', blank=True)), - ('summary', models.TextField(help_text=b'Summarize main findings of outcome, or describe why no details are presented (for example, "no association (data not shown)")', blank=True)), - ], - bases=('assessment.baseendpoint',), - ), - migrations.CreateModel( - name='Result', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('metric_description', models.TextField(help_text=b'Add additional text describing the metric used, if needed.', blank=True)), - ('data_location', models.CharField(help_text=b'Details on where the data are found in the literature (ex: Figure 1, Table 2, etc.)', max_length=128, blank=True)), - ('population_description', models.CharField(help_text=b'Detailed description of the population being studied forthis outcome, which may be a subset of the entirestudy-population. For example, "US (national) NHANES2003-2008, Hispanic children 6-18 years, \xe2\x99\x82\xe2\x99\x80 (n=797)"', max_length=128, blank=True)), - ('dose_response', models.PositiveSmallIntegerField(default=0, help_text=b'Was a trend observed?', verbose_name=b'Dose Response Trend', choices=[(0, b'not-applicable'), (1, b'monotonic'), (2, b'non-monotonic'), (3, b'no trend'), (4, b'not reported')])), - ('dose_response_details', models.TextField(blank=True)), - ('prevalence_incidence', models.CharField(max_length=128, verbose_name=b'Overall incidence prevalence', blank=True)), - ('statistical_power', models.PositiveSmallIntegerField(default=0, help_text=b'Is the study sufficiently powered?', choices=[(0, b'not reported or calculated'), (1, b'appears to be adequately powered (sample size met)'), (2, b'somewhat underpowered (sample size is 75% to <100% of recommended)'), (3, b'underpowered (sample size is 50 to <75% required)'), (4, b'severely underpowered (sample size is <50% required)')])), - ('statistical_power_details', models.TextField(blank=True)), - ('trend_test', models.CharField(help_text='Enter result, if available (ex: p=0.015, p\u22640.05, n.s., etc.)', max_length=128, verbose_name=b'Trend test result', blank=True)), - ('estimate_type', models.PositiveSmallIntegerField(default=0, verbose_name=b'Central estimate type', choices=[(0, None), (1, b'mean'), (2, b'geometric mean'), (3, b'median'), (5, b'point'), (4, b'other')])), - ('variance_type', models.PositiveSmallIntegerField(default=0, choices=[(0, None), (1, b'SD'), (2, b'SEM'), (3, b'GSD'), (4, b'other')])), - ('ci_units', models.FloatField(default=0.95, help_text=b'A 95% CI is written as 0.95.', null=True, verbose_name=b'Confidence Interval (CI)', blank=True)), - ('comments', models.TextField(help_text=b'Summarize main findings of outcome, or describe why no details are presented (for example, "no association (data not shown)")', blank=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ], - ), - migrations.CreateModel( - name='ResultAdjustmentFactor', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('included_in_final_model', models.BooleanField(default=True)), - ('adjustment_factor', models.ForeignKey(related_name='resfactors', to='epi2.AdjustmentFactor')), - ('result', models.ForeignKey(related_name='resfactors', to='epi2.Result')), - ], - ), - migrations.CreateModel( - name='ResultMetric', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('metric', models.CharField(unique=True, max_length=128)), - ('abbreviation', models.CharField(max_length=32)), - ('isLog', models.BooleanField(default=True, help_text=b'When plotting, use a log base 10 scale', verbose_name=b'Display as log')), - ('showForestPlot', models.BooleanField(default=True, help_text=b'Does forest-plot representation of result make sense?', verbose_name=b'Show on forest plot')), - ('reference_value', models.FloatField(default=1, help_text=b'Null hypothesis value for reference, if applicable', null=True, blank=True)), - ('order', models.PositiveSmallIntegerField(help_text=b'Order as they appear in option-list')), - ], - options={ - 'ordering': ('order',), - }, - ), - migrations.CreateModel( - name='StudyPopulation', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=256)), - ('design', models.CharField(max_length=2, choices=[(b'CO', b'Cohort'), (b'CC', b'Case-control'), (b'NC', b'Nested case-control'), (b'CR', b'Case report'), (b'SE', b'Case series'), (b'CT', b'Controlled trial'), (b'CS', b'Cross sectional')])), - ('age_profile', models.CharField(help_text=b'Age profile of population (ex: adults, children, pregnant women, etc.)', max_length=128, blank=True)), - ('source', models.CharField(help_text=b'Population source (ex: general population, environmental exposure, occupational cohort)', max_length=128, blank=True)), - ('region', models.CharField(max_length=128, blank=True)), - ('state', models.CharField(max_length=128, blank=True)), - ('eligible_n', models.PositiveIntegerField(null=True, verbose_name=b'Eligible N', blank=True)), - ('invited_n', models.PositiveIntegerField(null=True, verbose_name=b'Invited N', blank=True)), - ('participant_n', models.PositiveIntegerField(null=True, verbose_name=b'Participant N', blank=True)), - ('comments', models.TextField(help_text=b'Note matching criteria, etc.', blank=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('last_updated', models.DateTimeField(auto_now=True)), - ('country', models.ForeignKey(to='epi2.Country')), - ], - options={ - 'ordering': ('name',), - }, - ), - migrations.CreateModel( - name='StudyPopulationCriteria', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('criteria_type', models.CharField(max_length=1, choices=[(b'I', b'Inclusion'), (b'E', b'Exclusion'), (b'C', b'Confounding')])), - ('criteria', models.ForeignKey(related_name='spcriteria', to='epi2.Criteria')), - ('study_population', models.ForeignKey(related_name='spcriteria', to='epi2.StudyPopulation')), - ], - ), - migrations.AddField( - model_name='studypopulation', - name='criteria', - field=models.ManyToManyField(related_name='populations', through='epi2.StudyPopulationCriteria', to='epi2.Criteria'), - ), - migrations.AddField( - model_name='studypopulation', - name='study', - field=models.ForeignKey(related_name='study_populations2', to='study.Study'), - ), - migrations.AddField( - model_name='result', - name='adjustment_factors', - field=models.ManyToManyField(related_name='outcomes', through='epi2.ResultAdjustmentFactor', to='epi2.AdjustmentFactor', blank=True), - ), - migrations.AddField( - model_name='result', - name='comparison_set', - field=models.ForeignKey(related_name='results', to='epi2.ComparisonSet'), - ), - migrations.AddField( - model_name='result', - name='metric', - field=models.ForeignKey(related_name='results', to='epi2.ResultMetric', help_text=b' '), - ), - migrations.AddField( - model_name='result', - name='outcome', - field=models.ForeignKey(related_name='results', to='epi2.Outcome'), - ), - migrations.AddField( - model_name='outcome', - name='study_population', - field=models.ForeignKey(related_name='outcomes', to='epi2.StudyPopulation'), - ), - migrations.AddField( - model_name='groupresult', - name='result', - field=models.ForeignKey(related_name='results', to='epi2.Result'), - ), - migrations.AddField( - model_name='exposure2', - name='study_population', - field=models.ForeignKey(related_name='exposures', to='epi2.StudyPopulation'), - ), - migrations.AddField( - model_name='comparisonset', - name='exposure', - field=models.ForeignKey(related_name='comparison_sets', blank=True, to='epi2.Exposure2', help_text=b'Exposure-group associated with this group', null=True), - ), - migrations.AddField( - model_name='comparisonset', - name='outcome', - field=models.ForeignKey(related_name='comparison_sets', to='epi2.Outcome', null=True), - ), - migrations.AddField( - model_name='comparisonset', - name='study_population', - field=models.ForeignKey(related_name='comparison_sets', to='epi2.StudyPopulation', null=True), - ), - migrations.AlterUniqueTogether( - name='criteria', - unique_together=set([('assessment', 'description')]), - ), - migrations.AlterUniqueTogether( - name='adjustmentfactor', - unique_together=set([('assessment', 'description')]), - ), - ] diff --git a/project/epi2/migrations/__init__.py b/project/epi2/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/project/epi2/serializers.py b/project/epi2/serializers.py deleted file mode 100644 index c28ffd9e..00000000 --- a/project/epi2/serializers.py +++ /dev/null @@ -1,186 +0,0 @@ -from rest_framework import serializers - -from assessment.serializers import EffectTagsSerializer, DoseUnitsSerializer -from study.serializers import StudySerializer - -from utils.helper import SerializerHelper - -from . import models - - -class EthnicitySerializer(serializers.ModelSerializer): - - class Meta: - model = models.Ethnicity - fields = ('id', 'name') - - -class CountrySerializer(serializers.ModelSerializer): - - class Meta: - model = models.Country - fields = ('name', 'id') - - -class StudyPopulationCriteriaSerializer(serializers.ModelSerializer): - id = serializers.ReadOnlyField(source="criteria.id") - description = serializers.ReadOnlyField(source="criteria.description") - criteria_type = serializers.CharField(source='get_criteria_type_display', read_only=True) - - class Meta: - model = models.StudyPopulationCriteria - fields = ('id', 'description', 'criteria_type') - - -class ExposureLinkSerializer(serializers.ModelSerializer): - url = serializers.CharField(source='get_absolute_url', read_only=True) - - class Meta: - model = models.Exposure2 - fields = ('id', 'name', 'url') - - -class OutcomeLinkSerializer(serializers.ModelSerializer): - url = serializers.CharField(source='get_absolute_url', read_only=True) - - class Meta: - model = models.Outcome - fields = ('id', 'name', 'url') - - -class GroupNumericalDescriptionsSerializer(serializers.ModelSerializer): - mean_type = serializers.CharField(source='get_mean_type_display', read_only=True) - variance_type = serializers.CharField(source='get_variance_type_display', read_only=True) - lower_type = serializers.CharField(source='get_lower_type_display', read_only=True) - upper_type = serializers.CharField(source='get_upper_type_display', read_only=True) - - class Meta: - model = models.GroupNumericalDescriptions - exclude = ('group', ) - - -class GroupSerializer(serializers.ModelSerializer): - sex = serializers.CharField(source='get_sex_display', read_only=True) - descriptions = GroupNumericalDescriptionsSerializer(many=True) - ethnicities = EthnicitySerializer(many=True) - url = serializers.CharField(source='get_absolute_url', read_only=True) - - class Meta: - model = models.Group - - -class ResultMetricSerializer(serializers.ModelSerializer): - - class Meta: - model = models.ResultMetric - - -class SimpleExposureSerializer(serializers.ModelSerializer): - url = serializers.CharField(source='get_absolute_url', read_only=True) - metric_units = DoseUnitsSerializer() - - class Meta: - model = models.Exposure2 - - -class ComparisonSetLinkSerializer(serializers.ModelSerializer): - url = serializers.CharField(source='get_absolute_url', read_only=True) - - class Meta: - model = models.ComparisonSet - fields = ('id', 'name', 'url') - - -class StudyPopulationSerializer(serializers.ModelSerializer): - study = StudySerializer() - criteria = StudyPopulationCriteriaSerializer(source='spcriteria', many=True) - outcomes = OutcomeLinkSerializer(many=True) - exposures = ExposureLinkSerializer(many=True) - can_create_sets = serializers.BooleanField(read_only=True) - comparison_sets = ComparisonSetLinkSerializer(many=True) - country = serializers.CharField(source='country.name', read_only=True) - url = serializers.CharField(source='get_absolute_url', read_only=True) - design = serializers.CharField(source='get_design_display', read_only=True) - - class Meta: - model = models.StudyPopulation - - -class ExposureSerializer(serializers.ModelSerializer): - study_population = StudyPopulationSerializer() - url = serializers.CharField(source='get_absolute_url', read_only=True) - metric_units = DoseUnitsSerializer() - - class Meta: - model = models.Exposure2 - - -class GroupResultSerializer(serializers.ModelSerializer): - main_finding_support = serializers.CharField(source='get_main_finding_support_display', read_only=True) - p_value_qualifier = serializers.CharField(source='get_p_value_qualifier_display', read_only=True) - p_value_text = serializers.CharField(read_only=True) - group = GroupSerializer() - - class Meta: - model = models.GroupResult - - -class ResultAdjustmentFactorSerializer(serializers.ModelSerializer): - id = serializers.ReadOnlyField(source="adjustment_factor.id") - description = serializers.ReadOnlyField(source="adjustment_factor.description") - - class Meta: - model = models.ResultAdjustmentFactor - fields = ('id', 'description', 'included_in_final_model') - - -class SimpleComparisonSetSerializer(serializers.ModelSerializer): - url = serializers.CharField(source='get_absolute_url', read_only=True) - exposure = SimpleExposureSerializer() - - class Meta: - model = models.ComparisonSet - - -class ResultSerializer(serializers.ModelSerializer): - metric = ResultMetricSerializer() - factors = ResultAdjustmentFactorSerializer(source='resfactors', many=True) - dose_response = serializers.CharField(source='get_dose_response_display', read_only=True) - statistical_power = serializers.CharField(source='get_statistical_power_display', read_only=True) - url = serializers.CharField(source='get_absolute_url', read_only=True) - results = GroupResultSerializer(many=True) - variance_type = serializers.CharField(source='get_variance_type_display', read_only=True) - estimate_type = serializers.CharField(source='get_estimate_type_display', read_only=True) - full_name = serializers.CharField(source='__unicode__', read_only=True) - comparison_set = SimpleComparisonSetSerializer() - - class Meta: - model = models.Result - exclude = ('adjustment_factors', ) - - -class OutcomeSerializer(serializers.ModelSerializer): - study_population = StudyPopulationSerializer() - can_create_sets = serializers.BooleanField(read_only=True) - effects = EffectTagsSerializer() - diagnostic = serializers.CharField(source='get_diagnostic_display', read_only=True) - url = serializers.CharField(source='get_absolute_url', read_only=True) - results = ResultSerializer(many=True) - comparison_sets = ComparisonSetLinkSerializer(many=True) - - class Meta: - model = models.Outcome - - -class ComparisonSetSerializer(serializers.ModelSerializer): - url = serializers.CharField(source='get_absolute_url', read_only=True) - exposure = ExposureSerializer() - outcome = OutcomeSerializer() - study_population = StudyPopulationSerializer() - groups = GroupSerializer(many=True) - - class Meta: - model = models.ComparisonSet - - -SerializerHelper.add_serializer(models.Outcome, OutcomeSerializer) diff --git a/project/epi2/urls.py b/project/epi2/urls.py deleted file mode 100644 index 92909ca6..00000000 --- a/project/epi2/urls.py +++ /dev/null @@ -1,136 +0,0 @@ -from django.conf.urls import url, include - -from rest_framework.routers import DefaultRouter - -from . import api, views - - -router = DefaultRouter() -router.register(r'study-population', api.StudyPopulation, base_name="study-population") -router.register(r'exposure', api.Exposure, base_name="exposure") -router.register(r'outcome', api.Outcome, base_name="outcome") -router.register(r'result', api.Result, base_name="result") -router.register(r'comparison-set', api.ComparisonSet, base_name="set") -router.register(r'group', api.Group, base_name="group") - - -urlpatterns = [ - - url(r'^api/', include(router.urls, namespace='api')), - - # Criteria - url(r'^assessment/(?P\d+)/study-criteria/create/$', - views.StudyCriteriaCreate.as_view(), - name='studycriteria_create'), - - # Adjustment factors - url(r'^assessment/(?P\d+)/adjustment-factor/create/$', - views.AdjustmentFactorCreate.as_view(), - name='adjustmentfactor_create'), - - # Study population - url(r'^study/(?P\d+)/study-population/create/$', - views.StudyPopulationCreate.as_view(), - name='sp_create'), - url(r'^study/(?P\d+)/study-population/copy-as-new-selector/$', - views.StudyPopulationCopyAsNewSelector.as_view(), - name='sp_copy_selector'), - url(r'^study-population/(?P\d+)/$', - views.StudyPopulationDetail.as_view(), - name='sp_detail'), - url(r'^study-population/(?P\d+)/update/$', - views.StudyPopulationUpdate.as_view(), - name='sp_update'), - url(r'^study-population/(?P\d+)/delete/$', - views.StudyPopulationDelete.as_view(), - name='sp_delete'), - - # Exposure - url(r'^study/(?P\d+)/exposure/create/$', - views.ExposureCreate.as_view(), - name='exp_create'), - url(r'^study/(?P\d+)/exposure/copy-as-new-selector/$', - views.ExposureCopyAsNewSelector.as_view(), - name='exp_copy_selector'), - url(r'^exposure/(?P\d+)/$', - views.ExposureDetail.as_view(), - name='exp_detail'), - url(r'^exposure/(?P\d+)/update/$', - views.ExposureUpdate.as_view(), - name='exp_update'), - url(r'^exposure/(?P\d+)/delete/$', - views.ExposureDelete.as_view(), - name='exp_delete'), - - # Outcome - url(r'^assessment/(?P\d+)/export/$', - views.OutcomeExport.as_view(), - name='outcome_export'), - url(r'^assessment/(?P\d+)/outcomes/$', - views.OutcomeList.as_view(), - name='outcome_list'), - url(r'^study-population/(?P\d+)/outcome/create/$', - views.OutcomeCreate.as_view(), - name='outcome_create'), - url(r'^study-population/(?P\d+)/outcome/copy-as-new-selector/$', - views.OutcomeCopyAsNewSelector.as_view(), - name='outcome_copy_selector'), - url(r'^outcome/(?P\d+)/$', - views.OutcomeDetail.as_view(), - name='outcome_detail'), - url(r'^outcome/(?P\d+)/update/$', - views.OutcomeUpdate.as_view(), - name='outcome_update'), - url(r'^outcome/(?P\d+)/delete/$', - views.OutcomeDelete.as_view(), - name='outcome_delete'), - - # Results - url(r'^outcome/(?P\d+)/result/create/$', - views.ResultCreate.as_view(), - name='result_create'), - url(r'^outcome/(?P\d+)/result/copy-as-new-selector/$', - views.ResultCopyAsNewSelector.as_view(), - name='result_copy_selector'), - url(r'^result/(?P\d+)/$', - views.ResultDetail.as_view(), - name='result_detail'), - url(r'^result/(?P\d+)/update/$', - views.ResultUpdate.as_view(), - name='result_update'), - url(r'^result/(?P\d+)/delete/$', - views.ResultDelete.as_view(), - name='result_delete'), - - # Comparison set - url(r'^study-population/(?P\d+)/comparison-set/create/$', - views.ComparisonSetCreate.as_view(), - name='cs_create'), - url(r'^study-population/(?P\d+)/comparison-set/copy-as-new-selector/$', - views.ComparisonSetStudyPopCopySelector.as_view(), - name='cs_copy_selector'), - url(r'^outcome/(?P\d+)/comparison-set/create/$', - views.ComparisonSetOutcomeCreate.as_view(), - name='cs_outcome_create'), - url(r'^outcome/(?P\d+)/comparison-set/copy-as-new-selector/$', - views.ComparisonSetOutcomeCopySelector.as_view(), - name='cs_outcome_copy_selector'), - url(r'^comparison-set/(?P\d+)/$', - views.ComparisonSetDetail.as_view(), - name='cs_detail'), - url(r'^comparison-set/(?P\d+)/update/$', - views.ComparisonSetUpdate.as_view(), - name='cs_update'), - url(r'^comparison-set/(?P\d+)/delete/$', - views.ComparisonSetDelete.as_view(), - name='cs_delete'), - - # Groups (in comparison set) - url(r'^group/(?P\d+)/$', - views.GroupDetail.as_view(), - name='g_detail'), - url(r'^group/(?P\d+)/update/$', - views.GroupUpdate.as_view(), - name='g_update'), - -] diff --git a/project/epi2/views.py b/project/epi2/views.py deleted file mode 100644 index 1cceb598..00000000 --- a/project/epi2/views.py +++ /dev/null @@ -1,349 +0,0 @@ -from utils.views import (BaseDetail, BaseDelete, - BaseVersion, BaseUpdate, BaseCreate, - BaseCreateWithFormset, BaseUpdateWithFormset, - CloseIfSuccessMixin, BaseList, GenerateReport) - -from assessment.models import Assessment -from study.models import Study -from study.views import StudyRead - -from . import forms, exports, models - - -# Study criteria -class StudyCriteriaCreate(CloseIfSuccessMixin, BaseCreate): - success_message = 'Criteria created.' - parent_model = Assessment - parent_template_name = 'assessment' - model = models.Criteria - form_class = forms.CriteriaForm - - -# Study population -class StudyPopulationCreate(BaseCreate): - success_message = 'Study-population created.' - parent_model = Study - parent_template_name = 'study' - model = models.StudyPopulation - form_class = forms.StudyPopulationForm - - -class StudyPopulationCopyAsNewSelector(StudyRead): - template_name = 'epi2/studypopulation_copy_selector.html' - - def get_context_data(self, **kwargs): - context = super(StudyPopulationCopyAsNewSelector, self).get_context_data(**kwargs) - context['form'] = forms.StudyPopulationSelectorForm(parent_id=self.object.id) - return context - - -class StudyPopulationDetail(BaseDetail): - model = models.StudyPopulation - - -class StudyPopulationUpdate(BaseUpdate): - success_message = "Study Population updated." - model = models.StudyPopulation - form_class = forms.StudyPopulationForm - - -class StudyPopulationDelete(BaseDelete): - success_message = "Study Population deleted." - model = models.StudyPopulation - - def get_success_url(self): - return self.object.study.get_absolute_url() - - -# Factors -class AdjustmentFactorCreate(CloseIfSuccessMixin, BaseCreate): - success_message = 'Adjustment factor created.' - parent_model = Assessment - parent_template_name = 'assessment' - model = models.AdjustmentFactor - form_class = forms.AdjustmentFactorForm - - -# Exposure -class ExposureCreate(BaseCreate): - success_message = 'Exposure created.' - parent_model = models.StudyPopulation - parent_template_name = 'study_population' - model = models.Exposure2 - form_class = forms.ExposureForm - - -class ExposureCopyAsNewSelector(StudyPopulationDetail): - template_name = 'epi2/exposure_copy_selector.html' - - def get_context_data(self, **kwargs): - context = super(ExposureCopyAsNewSelector, self).get_context_data(**kwargs) - context['form'] = forms.ExposureSelectorForm(parent_id=self.object.id) - return context - - -class ExposureDetail(BaseDetail): - model = models.Exposure2 - - -class ExposureUpdate(BaseUpdate): - success_message = "Study Population updated." - model = models.Exposure2 - form_class = forms.ExposureForm - - -class ExposureDelete(BaseDelete): - success_message = "Study Population deleted." - model = models.Exposure2 - - def get_success_url(self): - return self.object.study_population.get_absolute_url() - - -# Outcome -class OutcomeList(BaseList): - parent_model = Assessment - model = models.Outcome - - def get_paginate_by(self, qs): - val = 25 - try: - val = int(self.request.GET.get('paginate_by', val)) - except ValueError: - pass - return val - - def get_queryset(self): - filters = {"assessment": self.assessment} - perms = self.get_obj_perms() - if not perms['edit']: - filters["study_population__study__published"] = True - return self.model.objects.filter(**filters).order_by('name') - - -class OutcomeExport(OutcomeList): - """ - Full XLS data export for the epidemiology outcome. - """ - def get(self, request, *args, **kwargs): - self.object_list = self.get_queryset() - exporter = exports.OutcomeComplete( - self.object_list, - export_format="excel", - filename='{}-epi'.format(self.assessment), - sheet_name='epi') - return exporter.build_response() - - -class OutcomeCreate(BaseCreate): - success_message = 'Outcome created.' - parent_model = models.StudyPopulation - parent_template_name = 'study_population' - model = models.Outcome - form_class = forms.OutcomeForm - - def get_form_kwargs(self): - kwargs = super(OutcomeCreate, self).get_form_kwargs() - kwargs['assessment'] = self.assessment - return kwargs - - -class OutcomeCopyAsNewSelector(StudyPopulationDetail): - template_name = 'epi2/outcome_copy_selector.html' - - def get_context_data(self, **kwargs): - context = super(OutcomeCopyAsNewSelector, self).get_context_data(**kwargs) - context['form'] = forms.OutcomeSelectorForm(parent_id=self.object.id) - return context - - -class OutcomeDetail(BaseDetail): - model = models.Outcome - - -class OutcomeUpdate(BaseUpdate): - success_message = "Outcome updated." - model = models.Outcome - form_class = forms.OutcomeForm - - -class OutcomeDelete(BaseDelete): - success_message = "Outcome deleted." - model = models.Outcome - - def get_success_url(self): - return self.object.study_population.get_absolute_url() - - -# Result -class ResultCreate(BaseCreateWithFormset): - success_message = 'Result created.' - parent_model = models.Outcome - parent_template_name = 'outcome' - model = models.Result - form_class = forms.ResultForm - formset_factory = forms.GroupResultFormset - - def post_object_save(self, form, formset): - for form in formset.forms: - form.instance.result = self.object - - def get_formset_kwargs(self): - return { - "outcome": self.parent, - "study_population": self.parent.study_population - } - - def build_initial_formset_factory(self): - return forms.BlankGroupResultFormset( - queryset=models.GroupResult.objects.none(), - **self.get_formset_kwargs()) - - -class ResultCopyAsNewSelector(OutcomeDetail): - template_name = 'epi2/result_copy_selector.html' - - def get_context_data(self, **kwargs): - context = super(ResultCopyAsNewSelector, self).get_context_data(**kwargs) - context['form'] = forms.ResultSelectorForm(parent_id=self.object.id) - return context - - -class ResultDetail(BaseDetail): - model = models.Result - - -class ResultUpdate(BaseUpdateWithFormset): - success_message = "Result updated." - model = models.Result - form_class = forms.ResultUpdateForm - formset_factory = forms.GroupResultFormset - - def build_initial_formset_factory(self): - return forms.GroupResultFormset( - queryset=self.object.results.all(), - **self.get_formset_kwargs()) - - def get_formset_kwargs(self): - return { - "study_population": self.object.outcome.study_population, - "outcome": self.object.outcome, - "result": self.object - } - - def post_object_save(self, form, formset): - # delete other results not associated with the selected collection - models.GroupResult.objects\ - .filter(result=self.object)\ - .exclude(group__comparison_set=self.object.comparison_set)\ - .delete() - - -class ResultDelete(BaseDelete): - success_message = "Result deleted." - model = models.Result - - def get_success_url(self): - return self.object.outcome.get_absolute_url() - - -# Comparison set + group -class ComparisonSetCreate(BaseCreateWithFormset): - success_message = 'Groups created.' - parent_model = models.StudyPopulation - parent_template_name = 'study_population' - model = models.ComparisonSet - form_class = forms.ComparisonSet - formset_factory = forms.GroupFormset - - def post_object_save(self, form, formset): - group_id = 0 - for form in formset.forms: - form.instance.comparison_set = self.object - if form.is_valid() and form not in formset.deleted_forms: - form.instance.group_id = group_id - if form.has_changed() is False: - form.instance.save() # ensure new group_id saved to db - group_id += 1 - - def build_initial_formset_factory(self): - return forms.BlankGroupFormset( - queryset=models.Group.objects.none()) - - -class ComparisonSetOutcomeCreate(ComparisonSetCreate): - parent_model = models.Outcome - parent_template_name = 'outcome' - - -class ComparisonSetStudyPopCopySelector(StudyPopulationDetail): - template_name = 'epi2/comparisonset_sp_copy_selector.html' - - def get_context_data(self, **kwargs): - context = super(ComparisonSetStudyPopCopySelector, self).get_context_data(**kwargs) - context['form'] = forms.ComparisonSetByStudyPopulationSelectorForm(parent_id=self.object.id) - return context - - -class ComparisonSetOutcomeCopySelector(OutcomeDetail): - template_name = 'epi2/comparisonset_outcome_copy_selector.html' - - def get_context_data(self, **kwargs): - context = super(ComparisonSetOutcomeCopySelector, self).get_context_data(**kwargs) - context['form'] = forms.ComparisonSetByOutcomeSelectorForm(parent_id=self.object.id) - return context - - -class ComparisonSetDetail(BaseDetail): - model = models.ComparisonSet - - -class ComparisonSetUpdate(BaseUpdateWithFormset): - success_message = "Comparison set updated." - model = models.ComparisonSet - form_class = forms.ComparisonSet - formset_factory = forms.GroupFormset - - def build_initial_formset_factory(self): - return forms.GroupFormset(queryset=self.object.groups.all() - .order_by('group_id')) - - def post_object_save(self, form, formset): - group_id = 0 - for form in formset.forms: - form.instance.comparison_set = self.object - if form.is_valid() and form not in formset.deleted_forms: - form.instance.group_id = group_id - if form.has_changed() is False: - form.instance.save() # ensure new group_id saved to db - group_id += 1 - - -class ComparisonSetDelete(BaseDelete): - success_message = "Comparison set deleted." - model = models.ComparisonSet - - def get_success_url(self): - if self.object.study_population: - return self.object.study_population.get_absolute_url() - else: - return self.object.outcome.get_absolute_url() - - -class GroupDetail(BaseDetail): - model = models.Group - - -class GroupUpdate(BaseUpdateWithFormset): - success_message = "Groups updated." - model = models.Group - form_class = forms.SingleGroupForm - formset_factory = forms.GroupNumericalDescriptionsFormset - - def build_initial_formset_factory(self): - return forms.GroupNumericalDescriptionsFormset( - queryset=self.object.descriptions.all()) - - def post_object_save(self, form, formset): - for form in formset: - form.instance.group = self.object diff --git a/project/epimeta/exports.py b/project/epimeta/exports.py index 90affb93..02c01385 100644 --- a/project/epimeta/exports.py +++ b/project/epimeta/exports.py @@ -27,12 +27,12 @@ def _get_data_rows(self): row.extend(models.MetaProtocol.flat_complete_data_row(ser['protocol'])) row.extend(models.MetaResult.flat_complete_data_row(ser)) - if len(ser['single_results2']) == 0: + if len(ser['single_results']) == 0: # print one-row with no single-results rows.append(row) else: # print each single-result as a new row - for sr in ser['single_results2']: + for sr in ser['single_results']: row_copy = list(row) # clone row_copy.extend(models.SingleResult.flat_complete_data_row(sr)) rows.append(row_copy) @@ -47,33 +47,30 @@ class MetaResultFlatDataPivot(FlatFileExporter): def _get_header_row(self): return [ - 'Study', - 'Study URL', - 'Study HAWC ID', - 'Study Published?', + 'study id', + 'study name', + 'study published', - 'Protocol Primary Key', - 'Protocol URL', - 'Protocol Name', - 'Protocol Type', - 'Total References', - 'Identified References', + 'protocol id', + 'protocol name', + 'protocol type', + 'total references', + 'identified references', - 'Row Key', - 'Result Primary Key', - 'Result URL', - 'Result Label', - 'Health Outcome', - 'Exposure', - 'Result References', - 'Statistical Metric', - 'Statistical Metric Abbreviation', + 'key', + 'meta result id', + 'meta result label', + 'health outcome', + 'exposure', + 'result references', + 'statistical metric', + 'statistical metric abbreviation', 'N', - 'Estimate', - 'Lower CI', - 'Upper CI', + 'estimate', + 'lower CI', + 'upper CI', 'CI units', - 'Heterogeneity' + 'heterogeneity' ] def _get_data_rows(self): @@ -81,13 +78,11 @@ def _get_data_rows(self): for obj in self.queryset: ser = obj.get_json(json_encode=False) row = [ - ser['protocol']['study']['short_citation'], - ser['protocol']['study']['url'], ser['protocol']['study']['id'], + ser['protocol']['study']['short_citation'], ser['protocol']['study']['published'], ser['protocol']['id'], - ser['protocol']['url'], ser['protocol']['name'], ser['protocol']['protocol_type'], ser['protocol']['total_references'], @@ -95,7 +90,6 @@ def _get_data_rows(self): ser['id'], # repeat for data-pivot key ser['id'], - ser['url'], ser['label'], ser['health_outcome'], ser['exposure_name'], diff --git a/project/epimeta/forms.py b/project/epimeta/forms.py index 60fbfe5e..34190ce9 100644 --- a/project/epimeta/forms.py +++ b/project/epimeta/forms.py @@ -6,7 +6,7 @@ from selectable import forms as selectable from utils.forms import BaseFormHelper -from epi2.lookups import AdjustmentFactorLookup, CriteriaLookup +from epi.lookups import AdjustmentFactorLookup, CriteriaLookup from . import models, lookups @@ -84,7 +84,7 @@ def setHelper(self): helper.add_fluid_row('lit_search_start_date', 3, "span4") helper.add_fluid_row('inclusion_criteria', 2, "span6") - url = reverse('epi2:studycriteria_create', + url = reverse('epi:studycriteria_create', kwargs={'pk': self.instance.study.assessment.pk}) helper.addBtnLayout(helper.layout[5], 0, url, "Create criteria", "span6") helper.addBtnLayout(helper.layout[5], 1, url, "Create criteria", "span6") @@ -172,7 +172,7 @@ def setHelper(self): helper.add_fluid_row('adjustment_factors', 2, "span6") url = reverse( - 'epi2:adjustmentfactor_create', + 'epi:adjustmentfactor_create', kwargs={'pk': self.instance.protocol.study.assessment.pk} ) helper.addBtnLayout(helper.layout[8], 0, url, "Create criteria", "span6") diff --git a/project/epimeta/migrations/0001_initial.py b/project/epimeta/migrations/0001_initial.py index d16262bf..98112463 100644 --- a/project/epimeta/migrations/0001_initial.py +++ b/project/epimeta/migrations/0001_initial.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): dependencies = [ ('study', '0001_initial'), - ('epi2', '0001_initial'), + ('epi', '0001_initial'), ] operations = [ @@ -26,9 +26,9 @@ class Migration(migrations.Migration): ('total_references', models.PositiveIntegerField(help_text=b'References identified through initial literature-search before application of inclusion/exclusion criteria', null=True, verbose_name=b'Total number of references found', blank=True)), ('total_studies_identified', models.PositiveIntegerField(help_text=b'Total references identified for inclusion after application of literature review and screening criteria', verbose_name=b'Total number of studies identified')), ('notes', models.TextField(blank=True)), - ('exclusion_criteria', models.ManyToManyField(related_name='meta_exclusion_criteria', to='epi2.Criteria', blank=True)), - ('inclusion_criteria', models.ManyToManyField(related_name='meta_inclusion_criteria', to='epi2.Criteria', blank=True)), - ('study', models.ForeignKey(related_name='meta_protocols2', to='study.Study')), + ('exclusion_criteria', models.ManyToManyField(related_name='meta_exclusion_criteria', to='epi.Criteria', blank=True)), + ('inclusion_criteria', models.ManyToManyField(related_name='meta_inclusion_criteria', to='epi.Criteria', blank=True)), + ('study', models.ForeignKey(related_name='meta_protocols', to='study.Study')), ], options={ 'ordering': ('name',), @@ -53,9 +53,9 @@ class Migration(migrations.Migration): ('upper_ci', models.FloatField(help_text=b'Numerical value for upper-confidence interval', verbose_name=b'Upper CI')), ('ci_units', models.FloatField(default=0.95, help_text=b'A 95% CI is written as 0.95.', null=True, verbose_name=b'Confidence Interval (CI)', blank=True)), ('notes', models.TextField(blank=True)), - ('adjustment_factors', models.ManyToManyField(help_text=b'All factors which were included in final model', related_name='meta_adjustments', to='epi2.AdjustmentFactor', blank=True)), - ('metric', models.ForeignKey(to='epi2.ResultMetric')), - ('protocol', models.ForeignKey(related_name='results2', to='epimeta.MetaProtocol')), + ('adjustment_factors', models.ManyToManyField(help_text=b'All factors which were included in final model', related_name='meta_adjustments', to='epi.AdjustmentFactor', blank=True)), + ('metric', models.ForeignKey(to='epi.ResultMetric')), + ('protocol', models.ForeignKey(related_name='results', to='epimeta.MetaProtocol')), ], options={ 'ordering': ('label',), @@ -73,8 +73,8 @@ class Migration(migrations.Migration): ('upper_ci', models.FloatField(help_text=b'Numerical value for upper-confidence interval', null=True, verbose_name=b'Upper CI', blank=True)), ('ci_units', models.FloatField(default=0.95, help_text=b'A 95% CI is written as 0.95.', null=True, verbose_name=b'Confidence Interval (CI)', blank=True)), ('notes', models.TextField(blank=True)), - ('meta_result', models.ForeignKey(related_name='single_results2', to='epimeta.MetaResult')), - ('study', models.ForeignKey(related_name='single_results2', blank=True, to='study.Study', null=True)), + ('meta_result', models.ForeignKey(related_name='single_results', to='epimeta.MetaResult')), + ('study', models.ForeignKey(related_name='single_results', blank=True, to='study.Study', null=True)), ], ), ] diff --git a/project/epimeta/models.py b/project/epimeta/models.py index c39d440f..93fa9f38 100644 --- a/project/epimeta/models.py +++ b/project/epimeta/models.py @@ -10,7 +10,7 @@ import reversion from assessment.serializers import AssessmentSerializer -from epi2.models import Criteria, ResultMetric, AdjustmentFactor +from epi.models import Criteria, ResultMetric, AdjustmentFactor from utils.helper import SerializerHelper from utils.models import get_crumbs @@ -26,7 +26,7 @@ class MetaProtocol(models.Model): (1, "Other")) study = models.ForeignKey('study.Study', - related_name="meta_protocols2") + related_name="meta_protocols") name = models.CharField( verbose_name="Protocol name", max_length=128) @@ -126,7 +126,7 @@ def flat_complete_data_row(ser): class MetaResult(models.Model): protocol = models.ForeignKey( MetaProtocol, - related_name="results2") + related_name="results") label = models.CharField( max_length=128) data_location = models.CharField( @@ -337,10 +337,10 @@ def getStatMethods(mr): class SingleResult(models.Model): meta_result = models.ForeignKey( MetaResult, - related_name="single_results2") + related_name="single_results") study = models.ForeignKey( 'study.Study', - related_name="single_results2", + related_name="single_results", blank=True, null=True) exposure_name = models.CharField( @@ -458,7 +458,7 @@ def invalidate_meta_result_cache(sender, instance, **kwargs): ) reversion.register( MetaResult, - follow=('adjustment_factors', 'single_results2') + follow=('adjustment_factors', 'single_results') ) reversion.register( SingleResult diff --git a/project/epimeta/serializers.py b/project/epimeta/serializers.py index a26e14a8..f4900f1d 100644 --- a/project/epimeta/serializers.py +++ b/project/epimeta/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from epi2.serializers import ResultMetricSerializer +from epi.serializers import ResultMetricSerializer from study.serializers import StudySerializer from utils.helper import SerializerHelper @@ -35,7 +35,7 @@ class MetaProtocolSerializer(serializers.ModelSerializer): url = serializers.ReadOnlyField(source="get_absolute_url") protocol_type = serializers.ReadOnlyField(source="get_protocol_type_display") lit_search_strategy = serializers.ReadOnlyField(source="get_lit_search_strategy_display") - results2 = MetaResultLinkSerializer(many=True) + results = MetaResultLinkSerializer(many=True) class Meta: model = models.MetaProtocol @@ -46,7 +46,7 @@ class MetaResultSerializer(serializers.ModelSerializer): url = serializers.ReadOnlyField(source="get_absolute_url") metric = ResultMetricSerializer() adjustment_factors = serializers.StringRelatedField(many=True) - single_results2 = SingleResultSerializer(many=True) + single_results = SingleResultSerializer(many=True) def to_representation(self, instance): ret = super(MetaResultSerializer, self).to_representation(instance) diff --git a/project/epimeta/views.py b/project/epimeta/views.py index 8c1ae76c..eae9bbee 100644 --- a/project/epimeta/views.py +++ b/project/epimeta/views.py @@ -88,7 +88,7 @@ def get_formset_kwargs(self): def build_initial_formset_factory(self): return forms.SingleResultFormset( - queryset=self.object.single_results2.all().order_by('pk'), + queryset=self.object.single_results.all().order_by('pk'), **self.get_formset_kwargs()) def get_form_kwargs(self): diff --git a/project/hawc/settings/base.py b/project/hawc/settings/base.py index dbcf8e7d..7e19a848 100644 --- a/project/hawc/settings/base.py +++ b/project/hawc/settings/base.py @@ -99,7 +99,6 @@ 'study', 'animal', 'epi', - 'epi2', 'epimeta', 'invitro', 'bmd', diff --git a/project/hawc/urls.py b/project/hawc/urls.py index 6dd01cca..be218ab0 100644 --- a/project/hawc/urls.py +++ b/project/hawc/urls.py @@ -31,8 +31,6 @@ include('animal.urls', namespace='animal')), url(r'^epi/', include('epi.urls', namespace='epi')), - url(r'^epi2/', - include('epi2.urls', namespace='epi2')), url(r'^epi-meta/', include('epimeta.urls', namespace='meta')), url(r'^in-vitro/', diff --git a/project/invitro/exports.py b/project/invitro/exports.py index acfb9359..ac43aa91 100644 --- a/project/invitro/exports.py +++ b/project/invitro/exports.py @@ -7,45 +7,43 @@ class IVEndpointFlatDataPivot(FlatFileExporter): def _get_header_row(self): header = [ - 'Study', - 'Study HAWC ID', - 'Study identifier', - 'Study URL', - 'Study Published', - - 'Chemical name', - 'Chemical HAWC ID', - 'Chemical CAS', - 'Chemical purity', - - 'IVExperiment HAWC ID', - 'IVExperiment URL', - 'Cell species', - 'Cell sex', - 'Cell type', - 'Cell tissue', + 'study id', + 'study name', + 'study identifier', + 'study published', + + 'chemical id', + 'chemical name', + 'chemical CAS', + 'chemical purity', + + 'IVExperiment id', + 'cell species', + 'cell sex', + 'cell type', + 'cell tissue', 'Dose units', 'Metabolic activation', 'Transfection', + 'key', + 'IVEndpoint id', 'IVEndpoint name', - 'IVEndpoint HAWC ID', - 'IVEndpoint URL', 'IVEndpoint description tags', - 'Assay type', - 'Endpoint description', - 'Endpoint response units', - 'Observation time', - 'Observation time units', + 'assay type', + 'endpoint description', + 'endpoint response units', + 'observation time', + 'observation time units', 'NOEL', 'LOEL', - 'Monotonicity', - 'Overall pattern', - 'Trend test result', - 'Minimum dose', - 'Maximum dose', - 'Number of doses' + 'monotonicity', + 'overall pattern', + 'trend test result', + 'minimum dose', + 'maximum dose', + 'number of doses', ] num_cats = 0 @@ -107,19 +105,17 @@ def getDose(ser, tag): bm_values = [bm["value"] for bm in ser["benchmarks"]] row = [ - ser['experiment']['study']['short_citation'], ser['experiment']['study']['id'], + ser['experiment']['study']['short_citation'], ser['experiment']['study']['study_identifier'], - ser['experiment']['study']['url'], ser['experiment']['study']['published'], - ser['chemical']['name'], ser['chemical']['id'], + ser['chemical']['name'], ser['chemical']['cas'], ser['chemical']['purity'], ser['experiment']['id'], - ser['experiment']['url'], ser['experiment']['cell_type']['species'], ser['experiment']['cell_type']['sex'], ser['experiment']['cell_type']['cell_type'], @@ -129,9 +125,9 @@ def getDose(ser, tag): ser['experiment']['metabolic_activation'], ser['experiment']['transfection'], - ser['name'], + ser['id'], # repeat for data-pivot key ser['id'], - ser['url'], + ser['name'], '|'.join([d['name'] for d in ser['effects']]), ser['assay_type'], ser['short_description'], diff --git a/project/static/epi/js/models2.js b/project/static/epi/js/models.js similarity index 98% rename from project/static/epi/js/models2.js rename to project/static/epi/js/models.js index 46eda9ec..620fc998 100644 --- a/project/static/epi/js/models2.js +++ b/project/static/epi/js/models.js @@ -6,7 +6,7 @@ var StudyPopulation = function(data){ }; _.extend(StudyPopulation, { get_object: function(id, cb){ - $.get('/epi2/api/study-population/{0}/'.printf(id), function(d){ + $.get('/epi/api/study-population/{0}/'.printf(id), function(d){ cb(new StudyPopulation(d)); }); }, @@ -98,7 +98,7 @@ var Exposure = function(data){ }; _.extend(Exposure, { get_object: function(id, cb){ - $.get('/epi2/api/exposure/{0}/'.printf(id), function(d){ + $.get('/epi/api/exposure/{0}/'.printf(id), function(d){ cb(new Exposure(d)); }); }, @@ -187,7 +187,7 @@ var ComparisonSet = function(data){ }; _.extend(ComparisonSet, { get_object: function(id, cb){ - $.get('/epi2/api/comparison-set/{0}/'.printf(id), function(d){ + $.get('/epi/api/comparison-set/{0}/'.printf(id), function(d){ cb(new ComparisonSet(d)); }); }, @@ -293,7 +293,7 @@ var Group = function(data){ }; _.extend(Group, { get_object: function(id, cb){ - $.get('/epi2/api/group/{0}/'.printf(id), function(d){ + $.get('/epi/api/group/{0}/'.printf(id), function(d){ cb(new Group(d)); }); }, @@ -510,7 +510,7 @@ var Outcome = function(data){ }; _.extend(Outcome, { get_object: function(id, cb){ - $.get('/epi2/api/outcome/{0}/'.printf(id), function(d){ + $.get('/epi/api/outcome/{0}/'.printf(id), function(d){ cb(new Outcome(d)); }); }, @@ -635,7 +635,7 @@ var Result = function(data){ }; _.extend(Result, { get_object: function(id, cb){ - $.get('/epi2/api/result/{0}/'.printf(id), function(d){ + $.get('/epi/api/result/{0}/'.printf(id), function(d){ cb(new Result(d)); }); }, diff --git a/project/static/epimeta/js/models.js b/project/static/epimeta/js/models.js index 28f86952..13aa446b 100644 --- a/project/static/epimeta/js/models.js +++ b/project/static/epimeta/js/models.js @@ -62,8 +62,8 @@ MetaProtocol.prototype = { }; $el.append("

Results

"); - if (this.data.results2.length>0){ - $el.append(HAWCUtils.buildUL(this.data.results2, liFunc)); + if (this.data.results.length>0){ + $el.append(HAWCUtils.buildUL(this.data.results, liFunc)); } else { $el.append("

No results are available for this protocol.

"); } @@ -94,7 +94,7 @@ _.extend(MetaResult, { MetaResult.prototype = { _unpack_single_results: function(){ var single_results = this.single_results; - this.data.single_results2.forEach(function(v,i){ + this.data.single_results.forEach(function(v,i){ single_results.push(new SingleStudyResult(v)); }); this.data.single_results = []; diff --git a/project/static/summary/js/data_pivot.js b/project/static/summary/js/data_pivot.js index e31d5b8b..5e788754 100644 --- a/project/static/summary/js/data_pivot.js +++ b/project/static/summary/js/data_pivot.js @@ -179,7 +179,7 @@ _.extend(DataPivot, { // add data-pivot row-level key and index row._dp_y = i; - row._dp_pk = row['Row Key'] || i; + row._dp_pk = row['key'] || i; return row; }, @@ -303,7 +303,7 @@ DataPivot.prototype = { this.data_headers = data_headers; }, build_settings: function(){ - + this.dpe_options = DataPivotExtension.get_options(this); var self = this, build_description_tab = function(){ var tab = $('
'), @@ -1348,7 +1348,7 @@ var _DataPivot_settings_description = function(data_pivot, values){ "header_style": this.data_pivot.style_manager.add_select("texts", values.header_style), "text_style": this.data_pivot.style_manager.add_select("texts", values.text_style), "max_width": $(''), - "dpe": $('').html(DataPivotExtension.get_options(data_pivot)) + "dpe": $('').html(this.data_pivot.dpe_options) }; // set default values @@ -1416,7 +1416,7 @@ var _DataPivot_settings_pointdata = function(data_pivot, values){ "header_name": $(''), "marker_style": this.data_pivot.style_manager.add_select(style_type, values.marker_style), "conditional_formatting": this.conditional_formatter.data, - "dpe": $('').html(DataPivotExtension.get_options(data_pivot)) + "dpe": $('').html(this.data_pivot.dpe_options) }; // set default values @@ -2641,8 +2641,8 @@ _.extend(DataPivot_visualization.prototype, D3Plot.prototype, { obj.style(property, d._styles['points_' + i][property]); } }) - .style('cursor', function(d){return(datum._dpe_datatype)?'pointer':'auto';}) - .on("click", function(d){if(datum._dpe_datatype){self.dpe.render_plottip(datum, d);}}); + .style('cursor', function(d){return(datum._dpe_key)?'pointer':'auto';}) + .on("click", function(d){if(datum._dpe_key){self.dpe.render_plottip(datum, d);}}); }); this.g_labels = this.vis.append("g"); @@ -2714,9 +2714,9 @@ _.extend(DataPivot_visualization.prototype, D3Plot.prototype, { "col": j, "text": txt.toLocaleString(), "style": v._styles['text_' + j], - "cursor": (desc._dpe_datatype)?'pointer':'auto', + "cursor": (desc._dpe_key)?'pointer':'auto', "onclick": function(){ - if(desc._dpe_datatype)self.dpe.render_plottip(desc, v); + if(desc._dpe_key)self.dpe.render_plottip(desc, v); } }) }); @@ -2880,126 +2880,140 @@ _.extend(DataPivot_visualization.prototype, D3Plot.prototype, { DataPivotExtension = function(){}; _.extend(DataPivotExtension, { + values: [ + { + _dpe_name: "study", + _dpe_key: "study id", + _dpe_cls: Study, + _dpe_option_txt: "Show study", + }, + { + _dpe_name: "experiment", + _dpe_key: "experiment id", + _dpe_cls: Experiment, + _dpe_option_txt: "Show experiment", + }, + { + _dpe_name: "animal_group", + _dpe_key: "animal group id", + _dpe_cls: AnimalGroup, + _dpe_option_txt: "Show animal group", + }, + { + _dpe_name: "endpoint", + _dpe_key: "endpoint id", + _dpe_cls: Endpoint, + _dpe_option_txt: "Show endpoint (basic)", + _dpe_options: { + complete: false + }, + }, + { + _dpe_name: "endpoint_complete", + _dpe_key: "endpoint id", + _dpe_cls: Endpoint, + _dpe_option_txt: "Show endpoint (complete)", + _dpe_options: { + complete: true + }, + }, + { + _dpe_name: "study_population", + _dpe_key: "study population id", + _dpe_cls: StudyPopulation, + _dpe_option_txt: "Show study population", + }, + { + _dpe_name: "comparison_set", + _dpe_key: "comparison set id", + _dpe_cls: ComparisonSet, + _dpe_option_txt: "Show comparison set", + }, + { + _dpe_name: "exposure", + _dpe_key: "exposure id", + _dpe_cls: Exposure, + _dpe_option_txt: "Show exposure", + }, + { + _dpe_name: "outcome", + _dpe_key: "outcome id", + _dpe_cls: Outcome, + _dpe_option_txt: "Show outcome", + }, + { + _dpe_name: "result", + _dpe_key: "result id", + _dpe_cls: Result, + _dpe_option_txt: "Show result", + }, + { + _dpe_name: "meta_protocol", + _dpe_key: "protocol id", + _dpe_cls: MetaProtocol, + _dpe_option_txt: "Show protocol", + }, + { + _dpe_name: "meta_result", + _dpe_key: "meta result id", + _dpe_cls: MetaResult, + _dpe_option_txt: "Show meta result", + }, + { + _dpe_name: "iv_chemical", + _dpe_key: "chemical id", + _dpe_cls: IVChemical, + _dpe_option_txt: "Show chemical", + }, + { + _dpe_name: "iv_experiment", + _dpe_key: "IVExperiment id", + _dpe_cls: IVExperiment, + _dpe_option_txt: "Show experiment", + }, + { + _dpe_name: "iv_endpoint", + _dpe_key: "IVEndpoint id", + _dpe_cls: IVEndpoint, + _dpe_option_txt: "Show endpoint", + } + ], + extByName: function(){ + return _.indexBy(DataPivotExtension.values, '_dpe_name'); + }, + extByColumnKey: function(){ + return _.groupBy(DataPivotExtension.values, '_dpe_key'); + }, update_extensions: function(obj, key){ - var map = d3.map({ - "study": { - _dpe_key: "Study HAWC ID", - _dpe_datatype: "study", - _dpe_cls: Study - }, - "experiment": { - _dpe_key: "Experiment ID", - _dpe_datatype: "experiment", - _dpe_cls: Experiment - }, - "animal_group": { - _dpe_key: "Animal Group ID", - _dpe_datatype: "animal_group", - _dpe_cls: AnimalGroup - }, - "endpoint": { - _dpe_key: "Endpoint Key", - _dpe_datatype: "endpoint", - _dpe_cls: Endpoint, - options: { - complete: false - } - }, - "endpoint_complete": { - _dpe_key: "Endpoint Key", - _dpe_datatype: "endpoint", - _dpe_cls: Endpoint, - options: { - complete: true - } - }, - "study_population": { - _dpe_key: "Study Population Key", - _dpe_datatype: "study_population", - _dpe_cls: StudyPopulation - }, - "comparison_set": { - _dpe_key: "Comparison Set ID", - _dpe_datatype: "comparison_set", - _dpe_cls: ComparisonSet - }, - "exposure": { - _dpe_key: "Exposure Key", - _dpe_datatype: "exposure", - _dpe_cls: Exposure - }, - "assessed_outcome": { - _dpe_key: "Assessed Outcome Key", - _dpe_datatype: "assessed_outcome", - _dpe_cls: Outcome - }, - "result": { - _dpe_key: "Result ID", - _dpe_datatype: "result", - _dpe_cls: Result - }, - "meta_protocol": { - _dpe_key: "Protocol Primary Key", - _dpe_datatype: "meta_protocol", - _dpe_cls: MetaProtocol - }, - "meta_result": { - _dpe_key: "Result Primary Key", - _dpe_datatype: "meta_result", - _dpe_cls: MetaResult - }, - "iv_chemical": { - _dpe_key: "Chemical HAWC ID", - _dpe_datatype: "iv_chemical", - _dpe_cls: IVChemical - }, - "iv_experiment": { - _dpe_key: "IVExperiment HAWC ID", - _dpe_datatype: "iv_experiment", - _dpe_cls: IVExperiment - }, - "iv_endpoint": { - _dpe_key: "IVEndpoint HAWC ID", - _dpe_datatype: "iv_endpoint", - _dpe_cls: IVEndpoint - } - }), - match = map.get(key); - + var dpe_keys = DataPivotExtension.extByName(), + match = dpe_keys[key]; if (match){ - $.extend(obj, match); + _.extend(obj, match); } else { console.log("Unrecognized DPE key: {0}".printf(key)); } }, get_options: function(dp){ // extension options dependent on available data-columns - var opts = [''.printf(DataPivot.NULL_CASE)]; + var build_opt = function(val, txt){ + return ''.printf(val, txt) + }, + opts = [ + build_opt(DataPivot.NULL_CASE, DataPivot.NULL_CASE) + ], + headers; if (dp.data.length>0){ - var headers = d3.set(d3.map(dp.data[0]).keys()), - options = d3.map({ - "Study HAWC ID": [''], - "Study Population Key": [''], - "Exposure Key": [''], - "Comparison Set ID": [''], - "Protocol Primary Key": [''], - "Result Primary Key": [''], - "Experiment ID": [''], - "Animal Group ID": [''], - "Endpoint Key": [ - '', - '' - ], - "Assessed Outcome Key": [''], - "Result ID": [''], - "Chemical HAWC ID": [''], - "IVExperiment HAWC ID": [''], - "IVEndpoint HAWC ID": [''], - }); - - options.entries().forEach(function(v){ - if(headers.has(v.key)) opts.push.apply(opts, v.value); + headers = d3.set(d3.map(dp.data[0]).keys()); + _.each(DataPivotExtension.extByColumnKey(), function(vals, key){ + if(headers.has(key)){ + opts.push.apply( + opts, + vals.map(function(d){ + return build_opt(d._dpe_name, d._dpe_option_txt); + }) + ); + } }); } return opts; @@ -3007,10 +3021,10 @@ _.extend(DataPivotExtension, { }); DataPivotExtension.prototype = { render_plottip: function(settings, datarow){ - var Cls = settings._dpe_cls, - key = settings._dpe_key, - options = settings.options - Cls.displayAsModal(datarow[key], options); + settings._dpe_cls.displayAsModal( + datarow[settings._dpe_key], + settings._dpe_options + ); } }; diff --git a/project/study/models.py b/project/study/models.py index 475c00ae..9add0211 100644 --- a/project/study/models.py +++ b/project/study/models.py @@ -434,10 +434,10 @@ def invalidate_caches_study(sender, instance, **kwargs): Model = get_model('animal', 'Endpoint') filters["animal_group__experiment__study"] = instance.id elif instance.study_type == 1: - Model = get_model('epi', 'AssessedOutcome') - filters["exposure__study_population__study"] = instance.id + Model = get_model('epi', 'Outcome') + filters["study_population__study"] = instance.id elif instance.study_type == 4: - Model = get_model('epi', 'MetaResult') + Model = get_model('epimeta', 'MetaResult') filters["protocol__study"] = instance.id Study.delete_caches([instance.id]) diff --git a/project/summary/forms.py b/project/summary/forms.py index 0f43bfc3..b11c90c0 100644 --- a/project/summary/forms.py +++ b/project/summary/forms.py @@ -159,8 +159,10 @@ def setInitialValues(self): if k in [ "animal_group__experiment__study__in", - "exposure__study_population__study__in", - "experiment__study__in"]: + "study_population__study__in", + "experiment__study__in", + "protocol__study__in", + ]: self.fields["prefilter_study"].initial = True self.fields["studies"].initial = v @@ -210,12 +212,17 @@ def setPrefilters(self, data): if data.get('prefilter_study') is True: studies = data.get("studies", []) - if data.get('evidence_type') == 1: # Epi - prefilters["exposure__study_population__study__in"] = studies - elif data.get('evidence_type') == 2: # in-vitro - prefilters["experiment__study__in"] = studies - else: # assume bioassay + evidence_type = data.get('evidence_type', None) + if evidence_type == 0: # Bioassay prefilters["animal_group__experiment__study__in"] = studies + if evidence_type == 1: # Epi + prefilters["study_population__study__in"] = studies + elif evidence_type == 2: # in-vitro + prefilters["experiment__study__in"] = studies + elif evidence_type == 4: # meta + prefilters["protocol__study__in"] = studies + else: + raise ValueError("Unknown evidence type") if data.get('prefilter_system') is True: prefilters["system__in"] = data.get("systems", []) diff --git a/project/summary/models.py b/project/summary/models.py index 2e439cb0..0ec86226 100644 --- a/project/summary/models.py +++ b/project/summary/models.py @@ -8,13 +8,13 @@ from study.models import Study from animal.models import Endpoint -from epi2.models import Outcome +from epi.models import Outcome from epimeta.models import MetaResult from invitro.models import IVEndpoint from comments.models import Comment from animal.exports import EndpointFlatDataPivot -from epi2.exports import OutcomeDataPivot +from epi.exports import OutcomeDataPivot from epimeta.exports import MetaResultFlatDataPivot from invitro.exports import IVEndpointFlatDataPivot @@ -560,6 +560,7 @@ class Prefilter(object): """ @classmethod def setFiltersFromForm(cls, filters, d): + evidence_type = d.get('evidence_type') if d.get('prefilter_system'): filters["system__in"] = d.getlist('systems') @@ -574,15 +575,28 @@ def setFiltersFromForm(cls, filters, d): if d.get('prefilter_study'): studies = d.get("studies", []) - if d.get('evidence_type') == 1: # Epi - filters["exposure__study_population__study__in"] = studies - elif d.get('evidence_type') == 2: # in-vitro - filters["experiment__study__in"] = studies - else: # assume bioassay + if evidence_type == 0: # Bioassay filters["animal_group__experiment__study__in"] = studies + if evidence_type == 1: # Epi + filters["study_population__study__in"] = studies + elif evidence_type == 2: # in-vitro + filters["experiment__study__in"] = studies + elif evidence_type == 4: # meta + filters["protocol__study__in"] = studies + else: + raise ValueError("Unknown evidence type") if d.get("published_only"): - filters["animal_group__experiment__study__published"] = True + if evidence_type == 0: # Bioassay + filters["animal_group__experiment__study__published"] = True + if evidence_type == 1: # Epi + filters["study_population__study__published"] = True + elif evidence_type == 2: # in-vitro + filters["experiment__study__published"] = True + elif evidence_type == 4: # meta + filters["protocol__study__published"] = True + else: + raise ValueError("Unknown evidence type") @classmethod def setFiltersFromObj(cls, filters, prefilters): diff --git a/project/templates/assessment/assessment_downloads.html b/project/templates/assessment/assessment_downloads.html index 28f54152..bd12742a 100644 --- a/project/templates/assessment/assessment_downloads.html +++ b/project/templates/assessment/assessment_downloads.html @@ -43,7 +43,7 @@
  • Epidemiology data
    - Download
    + Download

    Microsoft Excel spreadsheet

  • diff --git a/project/templates/assessment/baseendpoint_list.html b/project/templates/assessment/baseendpoint_list.html index fff20fe5..e85c24fd 100644 --- a/project/templates/assessment/baseendpoint_list.html +++ b/project/templates/assessment/baseendpoint_list.html @@ -23,7 +23,7 @@ {% endif %} {% if outcomes > 0 %} -
  • +
  • {{outcomes}} epidemiological outcomes assessed
  • {% endif %} diff --git a/project/templates/base.html b/project/templates/base.html index ac0fcdf7..2c902007 100644 --- a/project/templates/base.html +++ b/project/templates/base.html @@ -130,7 +130,7 @@ - + diff --git a/project/templates/epi/_epistudy_list.html b/project/templates/epi/_epistudy_list.html deleted file mode 100644 index 632f92b6..00000000 --- a/project/templates/epi/_epistudy_list.html +++ /dev/null @@ -1,21 +0,0 @@ -{% if object_list %} - - - - - - - - - - - {% for object in object_list %} - - - - {% endfor %} - -
    Short Citation
    {{object}}
    -{% else %} -

    No epidemiology studies are available.

    -{% endif %} diff --git a/project/templates/epi/_study_population_list.html b/project/templates/epi/_study_population_list.html deleted file mode 100644 index b27eec80..00000000 --- a/project/templates/epi/_study_population_list.html +++ /dev/null @@ -1,40 +0,0 @@ -{% if object_list %} - - - {%if with_study %} - - - - - {% else %} - - - - {% endif %} - - - - {%if with_study %} - - {% endif %} - - - - - - - {% for object in object_list %} - - {%if with_study %} - - {% endif %} - - - - - {% endfor %} - -
    StudyNameDesignLocation
    {{object.study}}{{object}}{{object.get_design_display}}{{object.get_location}}
    -{% else %} -

    No study-populations are available.

    -{% endif %} diff --git a/project/templates/epi2/adjustmentfactor_form.html b/project/templates/epi/adjustmentfactor_form.html similarity index 100% rename from project/templates/epi2/adjustmentfactor_form.html rename to project/templates/epi/adjustmentfactor_form.html diff --git a/project/templates/epi/assessedoutcome_confirm_delete.html b/project/templates/epi/assessedoutcome_confirm_delete.html deleted file mode 100644 index 553d1413..00000000 --- a/project/templates/epi/assessedoutcome_confirm_delete.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends 'epi/assessedoutcome_detail.html' %} - -{% block title %} - {{ block.super }} | Delete -{% endblock title %} - -{% block breadcrumbs_self %} -
  • {{object}}/
  • -
  • Delete/
  • -{% endblock breadcrumbs_self %} - -{% block content %} - {{ block.super }} - {% include "hawc/_delete_block.html" with name="assessed outcome" notes="" %} -{% endblock content %} diff --git a/project/templates/epi/assessedoutcome_copy_selector.html b/project/templates/epi/assessedoutcome_copy_selector.html deleted file mode 100644 index 52405388..00000000 --- a/project/templates/epi/assessedoutcome_copy_selector.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends 'epi/exposure_detail.html' %} - - -{% load add_class %} -{% load selectable_tags %} - -{% block title %} - {{block.super}} | Copy Assessed Outcome -{% endblock title %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - -{% block breadcrumbs_self %} -
  • {{object}}/
  • -
  • Copy outcome/
  • -{% endblock breadcrumbs_self %} - -{% block content %} - - {% include "hawc/_copy_as_new.html" with name="assessed outcome" notes="Select an existing assessed outcome as a template to create a new one." %} - -{% endblock content %} - -{% block extrajs %} - {{ form.media }} - -{% endblock extrajs %} - diff --git a/project/templates/epi/assessedoutcome_detail.html b/project/templates/epi/assessedoutcome_detail.html deleted file mode 100644 index be6a7013..00000000 --- a/project/templates/epi/assessedoutcome_detail.html +++ /dev/null @@ -1,56 +0,0 @@ -{% extends 'portal.html' %} - - -{% block title %}{{assessment}} | {{object.exposure.study_population.study}} | {{object.exposure.study_population}} | {{object.exposure}} | {{object}} {% endblock title %} - -{% block breadcrumbs %} -
  • {{assessment}}/
  • -
  • {{object.exposure.study_population.study}}/
  • -
  • {{object.exposure.study_population|truncatechars:40}}/
  • -
  • {{object.exposure}}/
  • - {% block breadcrumbs_self %} -
  • {{object}}/
  • - {% endblock breadcrumbs_self %} -{% endblock %} - -{% block content %} - -

    {{object}} - {% if obj_perms.edit and crud == "Read" %} - - {% endif %} -

    - -

    Assessed outcome description

    -
    - - {% if object.groups.count > 0 %} -

    Results by exposure-group

    -
    -
    -



    - {% endif %} -{% endblock %} - -{% block extrajs %} - -{% endblock %} diff --git a/project/templates/epi/assessedoutcome_form.html b/project/templates/epi/assessedoutcome_form.html deleted file mode 100644 index ff0ef048..00000000 --- a/project/templates/epi/assessedoutcome_form.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends 'portal.html' %} - -{% load selectable_tags %} -{% load crispy_forms_tags %} - -{% block title %} - {{assessment}} | - {% if crud == "Create" %} - {{object.study_population.study}} | {{object.study_population}} | {{object}} | Create Assessed Outcome - {% elif crud == "Update" %} - {{object.exposure.study_population.study}} | {{object.exposure.study_population}} | {{object.exposure}} | Update {{object}} - {% endif %} -{% endblock title %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - -{% block breadcrumbs %} -
  • {{assessment}}/
  • - {% if crud == "Create" %} -
  • {{object.study_population.study}}/
  • -
  • {{object.study_population|truncatechars:40}}/
  • -
  • {{object}}/
  • -
  • Create Asessed Outcome
  • - {% elif crud == "Update" %} -
  • {{object.exposure.study_population.study}}/
  • -
  • {{object.exposure.study_population|truncatechars:40}}/
  • -
  • {{object.exposure}}/
  • -
  • {{object}}/
  • -
  • Update
  • - {% endif %} -{% endblock %} - -{% block content %} - -
    - {% crispy form %} -
    - -
    - Assessed Outcome Groups -

    - Response data for each individual assessed outcome - (hover over headers for more details). -

    - {% include "hawc/_formset_table_template.html" with showDeleteRow=False %} -
    - -{% endblock %} - -{% block extrajs %} - {{ form.media }} - -{% endblock extrajs %} diff --git a/project/templates/epi/assessedoutcome_list.html b/project/templates/epi/assessedoutcome_list.html deleted file mode 100644 index 946c4253..00000000 --- a/project/templates/epi/assessedoutcome_list.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends 'portal.html' %} -{% load add_class %} - -{% block title %}{{assessment}} | Assessed outcomes | HAWC {% endblock title %} - -{% block breadcrumbs %} -
  • {{ assessment }}/
  • -
  • Endpoints/
  • -
  • Epidemiological outcomes/
  • -{% endblock %} - -{% block content %} - -

    Assessed outcomes ({{page_obj.paginator.count}} found)

    - -
    -
      - {% for object in object_list %} -
    • {{object}}
    • - {% empty %} -
    • No assessed outcomes are available.
    • - {% endfor %} -
    -
    - - {% if is_paginated %} - - {% endif %} - -{% endblock content %} diff --git a/project/templates/epi/assessedoutcome_versions.html b/project/templates/epi/assessedoutcome_versions.html deleted file mode 100644 index 06301106..00000000 --- a/project/templates/epi/assessedoutcome_versions.html +++ /dev/null @@ -1,76 +0,0 @@ -{% extends 'portal.html' %} - -{% block title %}{{assessment}} | {{object.exposure.study_population.study}} | {{object.exposure.study_population}} | {{object.exposure}} | {{object}} | Versions {% endblock title %} - -{% block breadcrumbs %} -
  • {{assessment}}/
  • -
  • {{object.exposure.study_population.study}}/
  • -
  • {{object.exposure.study_population|truncatechars:40}}/
  • -
  • {{object.exposure}}/
  • -
  • {{object}}/
  • -
  • Versions/
  • -{% endblock %} - - -{% block content %} -

    Prior Versions of {{object}}

    - -

    AJS to revise here. First, get just print raw table. Then, get confounder m2m values and add. Finally, get aop values and build table.

    - -
    -
    -

    Comparison

    - - - - - - - - - - - - - - - - - - - - - - -
    FieldCurrent
    Additions to the primary version shown in green.
    Deletions to primary version shown in red.
    Assessed Outcome Name
    Main Findings
    Statistical Metric
    Outcome N
    Diagnostic Description
    Prevalence Incidence
    Date Created
    Last Updated
    -
    -
    -

    Version List

    - - - - - - - - - - -
    {{object}} versions
    - (hover for instructions) -
    Primary version highlighted in blue.
    Secondary version highlighted in red.
    -
    -
    - -{% endblock content %} - -{% block extrajs %} - -{% endblock extrajs %} diff --git a/project/templates/epi2/comparisonset_confirm_delete.html b/project/templates/epi/comparisonset_confirm_delete.html similarity index 90% rename from project/templates/epi2/comparisonset_confirm_delete.html rename to project/templates/epi/comparisonset_confirm_delete.html index 073b750d..3c9fe15c 100644 --- a/project/templates/epi2/comparisonset_confirm_delete.html +++ b/project/templates/epi/comparisonset_confirm_delete.html @@ -1,4 +1,4 @@ -{% extends 'epi2/comparisonset_detail.html' %} +{% extends 'epi/comparisonset_detail.html' %} {% block title %} {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} diff --git a/project/templates/epi2/comparisonset_detail.html b/project/templates/epi/comparisonset_detail.html similarity index 85% rename from project/templates/epi2/comparisonset_detail.html rename to project/templates/epi/comparisonset_detail.html index 3faaee73..ba2de7bb 100644 --- a/project/templates/epi2/comparisonset_detail.html +++ b/project/templates/epi/comparisonset_detail.html @@ -18,8 +18,8 @@ {% endif %} diff --git a/project/templates/epi2/comparisonset_form.html b/project/templates/epi/comparisonset_form.html similarity index 100% rename from project/templates/epi2/comparisonset_form.html rename to project/templates/epi/comparisonset_form.html diff --git a/project/templates/epi2/comparisonset_outcome_copy_selector.html b/project/templates/epi/comparisonset_outcome_copy_selector.html similarity index 84% rename from project/templates/epi2/comparisonset_outcome_copy_selector.html rename to project/templates/epi/comparisonset_outcome_copy_selector.html index cdd8b552..e6149a61 100644 --- a/project/templates/epi2/comparisonset_outcome_copy_selector.html +++ b/project/templates/epi/comparisonset_outcome_copy_selector.html @@ -1,4 +1,4 @@ -{% extends 'epi2/outcome_detail.html' %} +{% extends 'epi/outcome_detail.html' %} {% load add_class %} @@ -25,7 +25,7 @@ new HAWCUtils.InitialForm({ "form": $('form'), - "base_url": "{% url 'epi2:cs_outcome_create' object.pk %}" + "base_url": "{% url 'epi:cs_outcome_create' object.pk %}" }); }); diff --git a/project/templates/epi2/comparisonset_sp_copy_selector.html b/project/templates/epi/comparisonset_sp_copy_selector.html similarity index 84% rename from project/templates/epi2/comparisonset_sp_copy_selector.html rename to project/templates/epi/comparisonset_sp_copy_selector.html index 83a6c9fd..a72870db 100644 --- a/project/templates/epi2/comparisonset_sp_copy_selector.html +++ b/project/templates/epi/comparisonset_sp_copy_selector.html @@ -1,4 +1,4 @@ -{% extends 'epi2/studypopulation_detail.html' %} +{% extends 'epi/studypopulation_detail.html' %} {% load add_class %} @@ -25,7 +25,7 @@ new HAWCUtils.InitialForm({ "form": $('form'), - "base_url": "{% url 'epi2:cs_create' object.pk %}" + "base_url": "{% url 'epi:cs_create' object.pk %}" }); }); diff --git a/project/templates/epi2/criteria_form.html b/project/templates/epi/criteria_form.html similarity index 100% rename from project/templates/epi2/criteria_form.html rename to project/templates/epi/criteria_form.html diff --git a/project/templates/epi/exposure_confirm_delete.html b/project/templates/epi/exposure_confirm_delete.html index 53147bef..ddeb80ca 100644 --- a/project/templates/epi/exposure_confirm_delete.html +++ b/project/templates/epi/exposure_confirm_delete.html @@ -1,15 +1,14 @@ {% extends 'epi/exposure_detail.html' %} {% block title %} - {{ block.super }} | Delete + {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} {% endblock title %} -{% block breadcrumbs_self %} -
  • {{object}}/
  • -
  • Delete/
  • -{% endblock breadcrumbs_self %} +{% block breadcrumbs %} + {% include "hawc/breadcrumbs.html" with crumbs=object.get_crumbs crud=crud %} +{% endblock %} {% block content %} {{ block.super }} - {% include "hawc/_delete_block.html" with name="exposure" notes="This will remove individual exposure-groups and assessed outcomes related to this exposure." %} + {% include "hawc/_delete_block.html" with name="exposure" notes="" %} {% endblock content %} diff --git a/project/templates/epi/exposure_copy_selector.html b/project/templates/epi/exposure_copy_selector.html index 1b8d1870..8981ba95 100644 --- a/project/templates/epi/exposure_copy_selector.html +++ b/project/templates/epi/exposure_copy_selector.html @@ -1,6 +1,5 @@ {% extends 'epi/studypopulation_detail.html' %} - {% load add_class %} {% load selectable_tags %} @@ -12,11 +11,6 @@ {% include_ui_theme %} {% endblock %} -{% block breadcrumbs_self %} -
  • {{object}}/
  • -
  • Copy outcome/
  • -{% endblock breadcrumbs_self %} - {% block content %} {% include "hawc/_copy_as_new.html" with name="exposure" notes="Select an existing exposure as a template to create a new one." %} @@ -30,7 +24,7 @@ new HAWCUtils.InitialForm({ "form": $('form'), - "base_url": "{% url 'epi:exposure_create' object.pk %}" + "base_url": "{% url 'epi:exp_create' object.pk %}" }); }); diff --git a/project/templates/epi/exposure_detail.html b/project/templates/epi/exposure_detail.html index c9d2957c..c4c604b1 100644 --- a/project/templates/epi/exposure_detail.html +++ b/project/templates/epi/exposure_detail.html @@ -1,63 +1,36 @@ {% extends 'portal.html' %} - -{% block title %}{{assessment}} | {{object.study_population.study}} | {{object.study_population}} | {{object}} {% endblock title %} +{% block title %} + {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} +{% endblock title %} {% block breadcrumbs %} -
  • {{assessment}}/
  • -
  • {{object.study_population.study}}/
  • -
  • {{object.study_population|truncatechars:40}}/
  • - {% block breadcrumbs_self %} -
  • {{object}}/
  • - {% endblock breadcrumbs_self %} + {% include "hawc/breadcrumbs.html" with crumbs=object.get_crumbs crud=crud %} {% endblock %} {% block content %} - -

    {{object}} - {% if obj_perms.edit and crud == "Read" %} - - {% endif %} -

    - - {% if crud == "Read" %} -

    Assessed Outcomes

    -
      - {% for ao in object.outcomes.all %} -
    • {{ao}}
    • - {% empty %} -
    • No assessed-outcomes are available for this exposure.
    • - {% endfor %} -
    - {% endif %} - +

    {{object.name}} + {% if obj_perms.edit and crud == "Read" %} + + {% endif %} +

    +
    {% endblock %} {% block extrajs %} {% endblock %} diff --git a/project/templates/epi/exposure_form.html b/project/templates/epi/exposure_form.html index 5587c0f1..aa82e257 100644 --- a/project/templates/epi/exposure_form.html +++ b/project/templates/epi/exposure_form.html @@ -1,61 +1,20 @@ {% extends 'portal.html' %} +{% load selectable_tags %} {% load crispy_forms_tags %} -{% load add_class %} {% block title %} - {{assessment}} | - {% if crud == "Create" %} - {{study_population.study}} | {{study_population}} | Create Exposure - {% elif crud == "Update" %} - {{object.study_population.study}} | {{object.study_population}} | Update {{object}} - {% endif %} + {% include "hawc/siteTitle.html" with crumbs=form.instance.get_crumbs crud=crud %} {% endblock title %} +{% block extrastyle %} + {% include_ui_theme %} +{% endblock %} + {% block breadcrumbs %} -
  • {{assessment}}/
  • - {% if crud == "Create" %} -
  • {{study_population.study}}/
  • -
  • {{study_population|truncatechars:40}}/
  • -
  • Create Exposure
  • - {% elif crud == "Update" %} -
  • {{object.study_population.study}}/
  • -
  • {{object.study_population|truncatechars:40}}/
  • -
  • {{object}}/
  • -
  • Update
  • - {% endif %} + {% include "hawc/breadcrumbs.html" with crumbs=form.instance.get_crumbs crud=crud %} {% endblock %} {% block content %} - -
    {% crispy form %} -
    - -
    - {# Exposure Group formset #} - Exposure Groups - - - {% if crud == "Create" %} - Exposure-groups are associated with each exposure, and each is a subset of the exposure population. The total number of individuals in all exposure groups should equal the total number of individuals in the exposure-population. For example, exposure-group descriptions may be "quartile 1 (≤1.0)", "quartile 2 (1.0-2.5)", etc.

    - Once created, additional exposure groups cannot be added. It is assumed that the first exposure-group has the lowest exposure, with each subsequent group having a greater exposure. Up to a maximum of eight exposure-groups can be added.

    - {% endif %} - {% include "hawc/_formset_table_template.html" with showDeleteRow=True %} -
    - {% endblock %} - -{% block extrajs %} - -{% endblock extrajs %} diff --git a/project/templates/epi/factor_form.html b/project/templates/epi/factor_form.html deleted file mode 100644 index ffce549d..00000000 --- a/project/templates/epi/factor_form.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends 'base.html' %} - -{% load add_class %} -{% load selectable_tags %} - -{% block title %}{{assessment}} | Create Factor {% endblock title %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - -{% block main_content %} - -
    - - {% csrf_token %} - -
    -
    - Create new factor - Create a new factor. Factors can be applied to assessment outcomes as adjustment factors or confounders considered. They are assessment-specific. Please take care not to duplicate existing factors.
    - - {{ form.non_field_errors|add_class:"alert alert-error" }} - - {% for field in form %} -
    - {{field.label_tag|add_class:"control-label"}} -
    - {{field}} - {{field.help_text}} -
    - {{field.errors|add_class:"alert alert-error"}} -
    - {% endfor %} -
    -
    -
    -
    - -
    -
    -
    - -{% endblock main_content %} - -{% block extrajs %} - {{ form.media }} -{% endblock extrajs %} diff --git a/project/templates/epi2/group_detail.html b/project/templates/epi/group_detail.html similarity index 92% rename from project/templates/epi2/group_detail.html rename to project/templates/epi/group_detail.html index 02425484..a3b54cf4 100644 --- a/project/templates/epi2/group_detail.html +++ b/project/templates/epi/group_detail.html @@ -17,7 +17,7 @@ {% endif %} diff --git a/project/templates/epi2/group_form.html b/project/templates/epi/group_form.html similarity index 100% rename from project/templates/epi2/group_form.html rename to project/templates/epi/group_form.html diff --git a/project/templates/epi2/outcome_confirm_delete.html b/project/templates/epi/outcome_confirm_delete.html similarity index 90% rename from project/templates/epi2/outcome_confirm_delete.html rename to project/templates/epi/outcome_confirm_delete.html index 42a1161b..1096e243 100644 --- a/project/templates/epi2/outcome_confirm_delete.html +++ b/project/templates/epi/outcome_confirm_delete.html @@ -1,4 +1,4 @@ -{% extends 'epi2/outcome_detail.html' %} +{% extends 'epi/outcome_detail.html' %} {% block title %} {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} diff --git a/project/templates/epi2/outcome_copy_selector.html b/project/templates/epi/outcome_copy_selector.html similarity index 83% rename from project/templates/epi2/outcome_copy_selector.html rename to project/templates/epi/outcome_copy_selector.html index ab978ce6..701d15db 100644 --- a/project/templates/epi2/outcome_copy_selector.html +++ b/project/templates/epi/outcome_copy_selector.html @@ -1,4 +1,4 @@ -{% extends 'epi2/studypopulation_detail.html' %} +{% extends 'epi/studypopulation_detail.html' %} {% load add_class %} @@ -25,7 +25,7 @@ new HAWCUtils.InitialForm({ "form": $('form'), - "base_url": "{% url 'epi2:outcome_create' object.pk %}" + "base_url": "{% url 'epi:outcome_create' object.pk %}" }); }); diff --git a/project/templates/epi2/outcome_detail.html b/project/templates/epi/outcome_detail.html similarity index 68% rename from project/templates/epi2/outcome_detail.html rename to project/templates/epi/outcome_detail.html index ddd3b073..dba8b7c4 100644 --- a/project/templates/epi2/outcome_detail.html +++ b/project/templates/epi/outcome_detail.html @@ -18,15 +18,15 @@ diff --git a/project/templates/epi2/outcome_form.html b/project/templates/epi/outcome_form.html similarity index 100% rename from project/templates/epi2/outcome_form.html rename to project/templates/epi/outcome_form.html diff --git a/project/templates/epi2/outcome_list.html b/project/templates/epi/outcome_list.html similarity index 100% rename from project/templates/epi2/outcome_list.html rename to project/templates/epi/outcome_list.html diff --git a/project/templates/epi2/result_confirm_delete.html b/project/templates/epi/result_confirm_delete.html similarity index 91% rename from project/templates/epi2/result_confirm_delete.html rename to project/templates/epi/result_confirm_delete.html index 612015b6..e051486c 100644 --- a/project/templates/epi2/result_confirm_delete.html +++ b/project/templates/epi/result_confirm_delete.html @@ -1,4 +1,4 @@ -{% extends 'epi2/result_detail.html' %} +{% extends 'epi/result_detail.html' %} {% block title %} {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} diff --git a/project/templates/epi2/result_copy_selector.html b/project/templates/epi/result_copy_selector.html similarity index 83% rename from project/templates/epi2/result_copy_selector.html rename to project/templates/epi/result_copy_selector.html index 5b12a60a..47ea03a7 100644 --- a/project/templates/epi2/result_copy_selector.html +++ b/project/templates/epi/result_copy_selector.html @@ -1,4 +1,4 @@ -{% extends 'epi2/studypopulation_detail.html' %} +{% extends 'epi/studypopulation_detail.html' %} {% load add_class %} @@ -25,7 +25,7 @@ new HAWCUtils.InitialForm({ "form": $('form'), - "base_url": "{% url 'epi2:result_create' object.pk %}" + "base_url": "{% url 'epi:result_create' object.pk %}" }); }); diff --git a/project/templates/epi2/result_detail.html b/project/templates/epi/result_detail.html similarity index 84% rename from project/templates/epi2/result_detail.html rename to project/templates/epi/result_detail.html index df861ba1..47b6d141 100644 --- a/project/templates/epi2/result_detail.html +++ b/project/templates/epi/result_detail.html @@ -18,8 +18,8 @@ {% endif %} diff --git a/project/templates/epi2/result_form.html b/project/templates/epi/result_form.html similarity index 98% rename from project/templates/epi2/result_form.html rename to project/templates/epi/result_form.html index b91e384a..22a356fc 100644 --- a/project/templates/epi2/result_form.html +++ b/project/templates/epi/result_form.html @@ -83,7 +83,7 @@ // bind formset change to group-change $('#id_comparison_set').change(function(){ var val = parseInt(this.value), - url = "/epi2/api/comparison-set/{0}/".printf(val); + url = "/epi/api/comparison-set/{0}/".printf(val); if (_.isNaN(val)){ updateFormset(); } else if (val !== comparison_set_id || isNew){ diff --git a/project/templates/epi/studycriteria_form.html b/project/templates/epi/studycriteria_form.html deleted file mode 100644 index 1c91cae1..00000000 --- a/project/templates/epi/studycriteria_form.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends 'base.html' %} - -{% load add_class %} -{% load selectable_tags %} - -{% block title %}{{assessment}} | {{ crud }} Study Criteria {% endblock title %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - -{% block main_content %} - -
    - - {% csrf_token %} - -
    -
    - Create new study criteria - Create a epidemiology study criteria. Study criteria can be applied to study populations as inclusion criteria, exclusion criteria, or confounding criteria. They are assessment-specific. Please take care not to duplicate existing factors.
    - - {{ form.non_field_errors|add_class:"alert alert-error" }} - - {% for field in form %} -
    - {{field.label_tag|add_class:"control-label"}} -
    - {{field}} - {{field.help_text}} -
    - {{field.errors|add_class:"alert alert-error"}} -
    - {% endfor %} -
    -
    -
    -
    - -
    -
    -
    - -{% endblock main_content %} - -{% block extrajs %} - {{ form.media }} -{% endblock extrajs %} diff --git a/project/templates/epi/studypopulation_confirm_delete.html b/project/templates/epi/studypopulation_confirm_delete.html index 477d1040..d69ff8d1 100644 --- a/project/templates/epi/studypopulation_confirm_delete.html +++ b/project/templates/epi/studypopulation_confirm_delete.html @@ -1,15 +1,14 @@ {% extends 'epi/studypopulation_detail.html' %} {% block title %} - {{ block.super }} | Delete + {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} {% endblock title %} -{% block breadcrumbs_self %} -
  • {{object|truncatechars:40}}/
  • -
  • Delete/
  • -{% endblock breadcrumbs_self %} +{% block breadcrumbs %} + {% include "hawc/breadcrumbs.html" with crumbs=object.get_crumbs crud=crud %} +{% endblock %} {% block content %} {{ block.super }} - {% include "hawc/_delete_block.html" with name="study population" notes="This will remove all exposures and assessed outcomes related to this population." %} + {% include "hawc/_delete_block.html" with name="study population" notes="This will remove all content associated with this population." %} {% endblock content %} diff --git a/project/templates/epi/studypopulation_detail.html b/project/templates/epi/studypopulation_detail.html index 2ad008cf..ba0c534f 100644 --- a/project/templates/epi/studypopulation_detail.html +++ b/project/templates/epi/studypopulation_detail.html @@ -1,59 +1,47 @@ {% extends 'portal.html' %} -{% block title %}{{assessment}} | {{object.study}} | Study Population {% endblock title %} +{% block title %} + {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} +{% endblock title %} {% block breadcrumbs %} -
  • {{assessment}}/
  • -
  • {{object.study}}/
  • - {% block breadcrumbs_self %} -
  • {{object|truncatechars:40}}/
  • - {% endblock breadcrumbs_self %} + {% include "hawc/breadcrumbs.html" with crumbs=object.get_crumbs crud=crud %} {% endblock %} {% block content %} - -

    {{object}} - {% if obj_perms.edit and crud == "Read" %} - - {% endif %} -

    - -
    - - {% if crud == "Read" %} -

    Available exposures

    -
      - {% for exposure in object.exposures.all %} -
    • {{exposure}}
    • - {% empty %} -
    • No exposures are available for this study population.
    • - {% endfor %} -
    - {% endif %} - +

    {{object.name}} + {% if obj_perms.edit and crud == "Read" %} + + {% endif %} +

    +
    {% endblock %} {% block extrajs %} {% endblock %} diff --git a/project/templates/epi/studypopulation_form.html b/project/templates/epi/studypopulation_form.html index dc850345..cf46668b 100644 --- a/project/templates/epi/studypopulation_form.html +++ b/project/templates/epi/studypopulation_form.html @@ -3,13 +3,9 @@ {% load selectable_tags %} {% load crispy_forms_tags %} + {% block title %} - {{assessment}} | - {% if crud == "Create" %} - {{study}} | Create Study Population - {% elif crud == "Update" %} - {{object.study}} | Update {{object}} - {% endif %} + {% include "hawc/siteTitle.html" with crumbs=form.instance.get_crumbs crud=crud %} {% endblock title %} {% block extrastyle %} @@ -17,15 +13,7 @@ {% endblock %} {% block breadcrumbs %} -
  • {{assessment}}/
  • - {% if crud == "Create" %} -
  • {{study}}/
  • -
  • Create Study Population
  • - {% elif crud == "Update" %} -
  • {{object.study}}/
  • -
  • {{object}}/
  • -
  • Update
  • - {% endif %} + {% include "hawc/breadcrumbs.html" with crumbs=form.instance.get_crumbs crud=crud %} {% endblock %} {% block content %} @@ -34,4 +22,19 @@ {% block extrajs %} {{ form.media }} + {% endblock extrajs %} diff --git a/project/templates/epi2/exposure2_confirm_delete.html b/project/templates/epi2/exposure2_confirm_delete.html deleted file mode 100644 index 5386d473..00000000 --- a/project/templates/epi2/exposure2_confirm_delete.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'epi2/exposure2_detail.html' %} - -{% block title %} - {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} -{% endblock title %} - -{% block breadcrumbs %} - {% include "hawc/breadcrumbs.html" with crumbs=object.get_crumbs crud=crud %} -{% endblock %} - -{% block content %} - {{ block.super }} - {% include "hawc/_delete_block.html" with name="exposure" notes="" %} -{% endblock content %} diff --git a/project/templates/epi2/exposure2_detail.html b/project/templates/epi2/exposure2_detail.html deleted file mode 100644 index 6c7805f9..00000000 --- a/project/templates/epi2/exposure2_detail.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends 'portal.html' %} - -{% block title %} - {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} -{% endblock title %} - -{% block breadcrumbs %} - {% include "hawc/breadcrumbs.html" with crumbs=object.get_crumbs crud=crud %} -{% endblock %} - -{% block content %} -

    {{object.name}} - {% if obj_perms.edit and crud == "Read" %} - - {% endif %} -

    -
    -{% endblock %} - - -{% block extrajs %} - -{% endblock %} diff --git a/project/templates/epi2/exposure2_form.html b/project/templates/epi2/exposure2_form.html deleted file mode 100644 index aa82e257..00000000 --- a/project/templates/epi2/exposure2_form.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends 'portal.html' %} - -{% load selectable_tags %} -{% load crispy_forms_tags %} - -{% block title %} - {% include "hawc/siteTitle.html" with crumbs=form.instance.get_crumbs crud=crud %} -{% endblock title %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - -{% block breadcrumbs %} - {% include "hawc/breadcrumbs.html" with crumbs=form.instance.get_crumbs crud=crud %} -{% endblock %} - -{% block content %} - {% crispy form %} -{% endblock %} diff --git a/project/templates/epi2/exposure_copy_selector.html b/project/templates/epi2/exposure_copy_selector.html deleted file mode 100644 index 444dec4d..00000000 --- a/project/templates/epi2/exposure_copy_selector.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends 'epi2/studypopulation_detail.html' %} - -{% load add_class %} -{% load selectable_tags %} - -{% block title %} - {{block.super}} | Copy Exposure -{% endblock title %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - -{% block content %} - - {% include "hawc/_copy_as_new.html" with name="exposure" notes="Select an existing exposure as a template to create a new one." %} - -{% endblock content %} - -{% block extrajs %} - {{ form.media }} - -{% endblock extrajs %} - diff --git a/project/templates/epi2/studypopulation_confirm_delete.html b/project/templates/epi2/studypopulation_confirm_delete.html deleted file mode 100644 index 9b2e3468..00000000 --- a/project/templates/epi2/studypopulation_confirm_delete.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'epi2/studypopulation_detail.html' %} - -{% block title %} - {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} -{% endblock title %} - -{% block breadcrumbs %} - {% include "hawc/breadcrumbs.html" with crumbs=object.get_crumbs crud=crud %} -{% endblock %} - -{% block content %} - {{ block.super }} - {% include "hawc/_delete_block.html" with name="study population" notes="This will remove all content associated with this population." %} -{% endblock content %} diff --git a/project/templates/epi2/studypopulation_copy_selector.html b/project/templates/epi2/studypopulation_copy_selector.html deleted file mode 100644 index a57e5c0d..00000000 --- a/project/templates/epi2/studypopulation_copy_selector.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends 'study/study_detail.html' %} - - -{% load add_class %} -{% load selectable_tags %} - -{% block title %} - {{block.super}} | Copy Study Population -{% endblock title %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - -{% block breadcrumbs_self %} -
  • {{object}}/
  • -
  • Copy study population/
  • -{% endblock breadcrumbs_self %} - -{% block content %} - - {% include "hawc/_copy_as_new.html" with name="study population" notes="Select an existing study population as a template to create a new one." %} - -{% endblock content %} - -{% block extrajs %} - {{ form.media }} - -{% endblock extrajs %} - diff --git a/project/templates/epi2/studypopulation_detail.html b/project/templates/epi2/studypopulation_detail.html deleted file mode 100644 index 88ecc084..00000000 --- a/project/templates/epi2/studypopulation_detail.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends 'portal.html' %} - -{% block title %} - {% include "hawc/siteTitle.html" with crumbs=object.get_crumbs crud=crud %} -{% endblock title %} - -{% block breadcrumbs %} - {% include "hawc/breadcrumbs.html" with crumbs=object.get_crumbs crud=crud %} -{% endblock %} - -{% block content %} -

    {{object.name}} - {% if obj_perms.edit and crud == "Read" %} - - {% endif %} -

    -
    -{% endblock %} - - -{% block extrajs %} - -{% endblock %} diff --git a/project/templates/epi2/studypopulation_form.html b/project/templates/epi2/studypopulation_form.html deleted file mode 100644 index cf46668b..00000000 --- a/project/templates/epi2/studypopulation_form.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends 'portal.html' %} - -{% load selectable_tags %} -{% load crispy_forms_tags %} - - -{% block title %} - {% include "hawc/siteTitle.html" with crumbs=form.instance.get_crumbs crud=crud %} -{% endblock title %} - -{% block extrastyle %} - {% include_ui_theme %} -{% endblock %} - -{% block breadcrumbs %} - {% include "hawc/breadcrumbs.html" with crumbs=form.instance.get_crumbs crud=crud %} -{% endblock %} - -{% block content %} - {% crispy form %} -{% endblock %} - -{% block extrajs %} - {{ form.media }} - -{% endblock extrajs %} diff --git a/project/templates/study/study_detail.html b/project/templates/study/study_detail.html index 32b4d29f..4d6f80a4 100644 --- a/project/templates/study/study_detail.html +++ b/project/templates/study/study_detail.html @@ -44,8 +44,8 @@
  • Create new
  • {% elif object.study_type == 1 %}
  • Study Population
  • -
  • Create new
  • -
  • Copy from existing
  • +
  • Create new
  • +
  • Copy from existing
  • {% elif object.study_type == 4 %}
  • Meta-analysis
  • Create new
  • @@ -76,7 +76,7 @@ {% elif study.study_type == 1 %}

    Available study populations

      - {% for obj in object.study_populations2.all %} + {% for obj in object.study_populations.all %}
    • {{obj}}
    • {% endfor %}
    @@ -85,7 +85,7 @@ {% include "invitro/_experiment_list.html" with object_list=object.ivexperiments.all %} {% elif study.study_type == 4 %}

    Available epidemiological meta-analyses

    - {% include "epimeta/_metaprotocol_list.html" with object_list=object.meta_protocols2.all %} + {% include "epimeta/_metaprotocol_list.html" with object_list=object.meta_protocols.all %} {% endif %}