From 8c82b3380a1f58b216641cd035174cd856d7d3a3 Mon Sep 17 00:00:00 2001 From: Rup-Narayan-Rajbanshi Date: Mon, 7 Aug 2023 17:29:15 +0545 Subject: [PATCH] Add validation for question in admin side. --- apps/assessment_registry/admin.py | 59 +++- .../0020_alter_summaryfocus_options.py | 17 ++ .../migrations/0021_auto_20230808_1125.py | 154 ++++++++++ .../migrations/0022_auto_20230809_0512.py | 21 ++ apps/assessment_registry/models.py | 264 +++++++++++++----- apps/assessment_registry/mutation.py | 22 +- apps/assessment_registry/schema.py | 22 +- apps/assessment_registry/serializers.py | 33 +-- .../assessment_registry/tests/test_schemas.py | 2 + apps/assessment_registry/utils.py | 7 + apps/gallery/tests/test_mutations.py | 6 +- deep/schema.py | 8 +- schema.graphql | 78 +++--- utils/common.py | 9 - utils/graphene/mutation.py | 1 + 15 files changed, 531 insertions(+), 172 deletions(-) create mode 100644 apps/assessment_registry/migrations/0020_alter_summaryfocus_options.py create mode 100644 apps/assessment_registry/migrations/0021_auto_20230808_1125.py create mode 100644 apps/assessment_registry/migrations/0022_auto_20230809_0512.py create mode 100644 apps/assessment_registry/utils.py diff --git a/apps/assessment_registry/admin.py b/apps/assessment_registry/admin.py index 80aca46a95..d00f6fb0b3 100644 --- a/apps/assessment_registry/admin.py +++ b/apps/assessment_registry/admin.py @@ -5,9 +5,12 @@ MethodologyAttribute, Question, Answer, - SummaryIssue, ScoreRating, ScoreAnalyticalDensity, + Summary, + SummarySubPillarIssue, + SummaryFocus, + SummarySubDimmensionIssue, ) @@ -24,12 +27,6 @@ def save_model(self, request, obj, form, change): super().save_model(request, obj, form, change) -@admin.register(Answer) -class AnswerAdmin(admin.ModelAdmin): - list_display = ('id', 'question') - readonly_fields = ('created_by', 'modified_by', 'client_id',) - - class MethodologyAttributeInline(admin.TabularInline): model = MethodologyAttribute extra = 0 @@ -54,16 +51,56 @@ class AnalyticalDensityInline(admin.TabularInline): exclude = ('created_by', 'modified_by', 'client_id') +class SummaryInline(admin.TabularInline): + model = Summary + extra = 0 + exclude = ('created_by', 'modified_by', 'client_id') + + +class SummarySubPillarIssueInline(admin.TabularInline): + model = SummarySubPillarIssue + extra = 0 + exclude = ('created_by', 'modified_by', 'client_id') + + +class SummaryFocusInline(admin.TabularInline): + model = SummaryFocus + extra = 0 + exclude = ('created_by', 'modified_by', 'client_id') + + +class SummarySubDimmensionIssueInline(admin.TabularInline): + model = SummarySubDimmensionIssue + extra = 0 + exclude = ('created_by', 'modified_by', 'client_id') + + @admin.register(AssessmentRegistry) class AssessmentRegistryAdmin(admin.ModelAdmin): - list_display = ('id', 'lead', 'project') - + list_display = ('id', 'project', 'lead') + + autocomplete_fields = ( + 'created_by', + 'modified_by', + 'project', + 'bg_countries', + 'lead_organizations', + 'international_partners', + 'donors', + 'national_partners', + 'governments', + 'locations', + ) inlines = [ MethodologyAttributeInline, ScoreInline, AnalyticalDensityInline, AnswerInline, + SummaryInline, + SummarySubPillarIssueInline, + SummaryFocusInline, + SummarySubDimmensionIssueInline, ] - -admin.site.register(SummaryIssue) + def get_queryset(self, request): + return super().get_queryset(request).prefetch_related('project', 'lead') diff --git a/apps/assessment_registry/migrations/0020_alter_summaryfocus_options.py b/apps/assessment_registry/migrations/0020_alter_summaryfocus_options.py new file mode 100644 index 0000000000..1c9f4e9849 --- /dev/null +++ b/apps/assessment_registry/migrations/0020_alter_summaryfocus_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.17 on 2023-08-08 05:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assessment_registry', '0019_auto_20230804_0944'), + ] + + operations = [ + migrations.AlterModelOptions( + name='summaryfocus', + options={'verbose_name': 'SummaryDimmension'}, + ), + ] diff --git a/apps/assessment_registry/migrations/0021_auto_20230808_1125.py b/apps/assessment_registry/migrations/0021_auto_20230808_1125.py new file mode 100644 index 0000000000..8d4db71871 --- /dev/null +++ b/apps/assessment_registry/migrations/0021_auto_20230808_1125.py @@ -0,0 +1,154 @@ +# Generated by Django 3.2.17 on 2023-08-08 11:25 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assessment_registry', '0020_alter_summaryfocus_options'), + ] + + operations = [ + migrations.AlterField( + model_name='additionaldocument', + name='document_type', + field=models.IntegerField(choices=[(1, 'Assessment database'), (2, 'Questionnaire'), (3, 'Miscellaneous')]), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='affected_groups', + field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(choices=[(100, 'All'), (1, 'All/Affected'), (2, 'All/Not Affected'), (3, 'All/Affected/Not Displaced'), (4, 'All/Affected/Displaced'), (5, 'All/Affected/Displaced/In Transit'), (6, 'All/Affected/Displaced/Migrants'), (7, 'All/Affected/Displaced/IDPs'), (8, 'All/Affected/Displced/Asylum Seeker'), (9, 'All/Affected/Displaced/Other of concerns'), (10, 'All/Affected/Displaced/Returnees'), (11, 'All/Affected/Displaced/Refugees'), (12, 'All/Affected/Displaced/Migrants/In transit'), (13, 'All/Affected/Displaced/Migrants/Permanents'), (14, 'All/Affected/Displaced/Migrants/Pendular'), (15, 'All/Affected/Not Displaced/No Host'), (16, 'All/Affected/Not Displaced/Host')]), default=list, size=None), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='bg_crisis_type', + field=models.IntegerField(choices=[(100, 'Earth Quake'), (1, 'Ground Shaking'), (2, 'Tsunami'), (3, 'Volcano'), (4, 'Volcanic Eruption'), (5, 'Mass Movement (Dry)'), (6, 'Rockfall'), (7, 'Avalance'), (8, 'Landslide'), (9, 'Subsidence'), (10, 'Extra Tropical Cyclone'), (11, 'Tropical Cyclone'), (12, 'Local/Convective Strom'), (13, 'Flood/Rain'), (14, 'General River Flood'), (15, 'Flash flood'), (16, 'Strom Surge/Coastal Flood'), (17, 'Mass Movement (Wet)'), (18, 'Extreme Temperature'), (19, 'Heat Wave'), (20, 'Cold Wave'), (21, 'Extreme Weather Condition'), (22, 'Drought'), (23, 'Wildfire'), (24, 'Population Displacement'), (25, 'Conflict')]), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='bg_preparedness', + field=models.IntegerField(choices=[(1, 'With Preparedness'), (2, 'Without Preparedness')]), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='confidentiality', + field=models.IntegerField(choices=[(1, 'Unprotected'), (2, 'Confidential')]), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='coordinated_joint', + field=models.IntegerField(choices=[(1, 'Coordinated Joint'), (2, 'Coordinated Harmonized'), (3, 'Uncoordinated')]), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='details_type', + field=models.IntegerField(choices=[(1, 'Initial'), (2, 'Rapid'), (3, 'In depth'), (4, 'Monitoring'), (5, 'Other')]), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='external_support', + field=models.IntegerField(choices=[(1, 'External Support Received'), (2, 'No External Support Received')]), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='family', + field=models.IntegerField(choices=[(100, 'Displacement Traking Matrix'), (1, 'Multi Cluster Initial and Rapid Assessment (MIRA)'), (2, 'Multi sectorial Needs Assessment (MSNA)'), (3, 'Emergency Food Security Assessment (EFSA)'), (4, 'Comprehensive Food Security and Vulnerability Analysis(CFSVA)'), (5, 'Protection Monitoring'), (6, 'Humanitarian Needs Overview (HNO)'), (7, 'Briefing note'), (8, 'Registration'), (9, 'IDPs profiling exercise'), (10, 'Census'), (11, 'Refugee and Migrant Response Plan (RMRP)'), (12, 'Refugee Response Plan (RRP)'), (13, 'Smart Nutrition Survey'), (14, 'Other')]), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='focuses', + field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(choices=[(100, 'Context'), (1, 'Shock/Event'), (2, 'Displacement'), (3, 'Casualties'), (4, 'Information and Communication'), (5, 'Humaniterian Access'), (6, 'Impact'), (7, 'Humanitarian Conditions'), (8, 'People at risk'), (9, 'Priorities & Preferences'), (10, 'Response and Capacities')]), default=list, size=None), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='frequency', + field=models.IntegerField(choices=[(1, 'One off'), (2, 'Regular')]), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='language', + field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(choices=[(1, 'English'), (2, 'French'), (3, 'Spanish'), (4, 'Portugese'), (5, 'Arabic')]), size=None), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='protection_info_mgmts', + field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(choices=[(100, 'Protection Monitoring'), (1, 'Protection Needs Assessment'), (2, 'Case Management'), (3, 'Population Data'), (4, 'Protection Response M&E'), (5, 'Communicating with(in) Affected Communities'), (6, 'Security & Situational Awareness'), (7, 'Sectoral System/Other')]), blank=True, null=True, size=None), + ), + migrations.AlterField( + model_name='assessmentregistry', + name='sectors', + field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(choices=[(100, 'Food Security'), (1, 'Heath'), (2, 'Shelter'), (3, 'Wash'), (4, 'Protection'), (5, 'Nutrition'), (6, 'Livelihood'), (7, 'Education'), (8, 'Logistics'), (9, 'Inter/Cross Sector')]), default=list, size=None), + ), + migrations.AlterField( + model_name='methodologyattribute', + name='data_collection_technique', + field=models.IntegerField(blank=True, choices=[(100, 'Secondary Data Review'), (1, 'Key Informant Interview'), (2, 'Direct Observation'), (3, 'Community Group Discussion'), (4, 'Focus Group Discussion'), (5, 'Household Interview'), (6, 'Individual Interview'), (7, 'Satellite Imagery')], null=True), + ), + migrations.AlterField( + model_name='methodologyattribute', + name='proximity', + field=models.IntegerField(blank=True, choices=[(1, 'Face-to-Face'), (2, 'Remote'), (3, 'Mixed')], null=True), + ), + migrations.AlterField( + model_name='methodologyattribute', + name='sampling_approach', + field=models.IntegerField(blank=True, choices=[(1, 'Non-Random Selection'), (2, 'Random Selection'), (3, 'Full Enumeration')], null=True), + ), + migrations.AlterField( + model_name='methodologyattribute', + name='unit_of_analysis', + field=models.IntegerField(blank=True, choices=[(100, 'Crisis'), (1, 'Country'), (2, 'Region'), (3, 'Province/governorate/prefecture'), (4, 'Department/District'), (5, 'Sub-District/Country'), (6, 'Municipality'), (7, 'Neighborhood/Quartier'), (8, 'Community/Site'), (9, 'Affected group'), (10, 'Household'), (11, 'Individual')], null=True), + ), + migrations.AlterField( + model_name='methodologyattribute', + name='unit_of_reporting', + field=models.IntegerField(blank=True, choices=[(100, 'Crisis'), (1, 'Country'), (2, 'Region'), (3, 'Province/governorate/prefecture'), (4, 'Department/District'), (5, 'Sub-District/Country'), (6, 'Municipality'), (7, 'Neighborhood/Quartier'), (8, 'Community/Site'), (9, 'Affected group'), (10, 'Household'), (11, 'Individual')], null=True), + ), + migrations.AlterField( + model_name='question', + name='sector', + field=models.IntegerField(choices=[(100, 'Relevance'), (1, 'Comprehensiveness'), (2, 'Ethics'), (3, 'Methodological rigor'), (4, 'Analytical value'), (5, 'Timeliness'), (6, 'Effective Communication'), (7, 'Use'), (8, 'People-centered and inclusive'), (9, 'Accountability to affected populations'), (10, 'Do not harm'), (11, 'Designed with a purpose'), (12, 'Competency and capacity'), (13, 'Impartiality'), (14, 'Coordination and data minimization'), (15, 'Joint Analysis'), (16, 'Acknowledge dissenting voices in joint needs analysis'), (17, 'Informed consent, confidentiality and data security'), (18, 'Sharing results (data and analysis)'), (19, 'Tranparency between actors'), (20, 'Minimum technical standards')]), + ), + migrations.AlterField( + model_name='question', + name='sub_sector', + field=models.IntegerField(choices=[(100, 'Relevance'), (1, 'Geographic comprehensiveness'), (2, 'Sectoral comprehensiveness'), (3, 'Affected and vulnerabel groups comprehensiveness'), (4, 'Safety and protection'), (5, 'Humanitarian Principles'), (6, 'Contribution'), (7, 'Transparency'), (8, 'Mitigating Bias'), (9, 'Participation'), (10, 'Context specificity'), (11, 'Ananlytical standards'), (12, 'Descriptions'), (13, 'Explanation'), (14, 'Interpretation'), (15, 'Anticipation'), (16, 'Timeliness'), (17, 'User-friendly presentation'), (18, 'Active dissemination'), (19, 'Use for collective planning'), (20, 'Buy-in and use by humanitarian clusters/sectors'), (21, 'Buy-in and use by UN agencies'), (22, 'Buy-in and use by international non-governmental organizations (NGOs)'), (23, 'Buy-in and use by local non-governmental organization (local NGOs)'), (24, 'Buy-in and use by member of Red Cross/Red Cresent Movement'), (25, 'Buy-in and use by donors'), (26, 'Buy-in and use by naional and local government agencies'), (27, 'Buy-in and use by development and stabilization actors')]), + ), + migrations.AlterField( + model_name='scoreanalyticaldensity', + name='analysis_level_covered', + field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(choices=[(100, 'Issues/unmet needs are detailed'), (1, 'Issues/unmet needs are priotized/ranked'), (2, 'Causes or underlying mechanisms behind issues/unmet needs are detailed'), (3, 'Causes or underlying mechanisms behind issues/unmet needs are priotized/ranked'), (4, 'Severity of some/all issues/unmet_needs_is_detailed'), (5, 'Future issues/unmet needs are detailed'), (6, 'Future issues/unmet needs are priotized/ranked'), (7, 'Severity of some/all future issues/unmet_needs_is_detailed'), (8, 'Recommnedations/interventions are detailed'), (9, 'Recommnedations/interventions are priotized/ranked')]), default=list, size=None), + ), + migrations.AlterField( + model_name='scoreanalyticaldensity', + name='figure_provided', + field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(choices=[(100, 'Total population in the assessed areas'), (1, 'Total population exposed to the shock/event'), (2, 'Total populaiton affected/living in the affected area'), (3, 'Total population facing humanitarian access constraints'), (4, 'Total populaiton in need'), (5, 'Total population in critical need'), (6, 'Total population in severe need'), (7, 'Total population in moderate need'), (9, 'Total population at risk/vulnerable'), (10, 'Total population reached by assistance')]), default=list, size=None), + ), + migrations.AlterField( + model_name='scoreanalyticaldensity', + name='sector', + field=models.IntegerField(choices=[(100, 'Food Security'), (1, 'Heath'), (2, 'Shelter'), (3, 'Wash'), (4, 'Protection'), (5, 'Nutrition'), (6, 'Livelihood'), (7, 'Education'), (8, 'Logistics'), (9, 'Inter/Cross Sector')]), + ), + migrations.AlterField( + model_name='scorerating', + name='score_type', + field=models.IntegerField(choices=[(100, 'Relevance'), (1, 'Comprehensiveness'), (2, 'Timeliness'), (3, 'Granularity'), (4, 'Comparability'), (5, 'Source reability'), (6, 'Methods'), (7, 'Triangulation'), (8, 'Plausibility'), (9, 'Inclusiveness'), (10, 'Assumptions'), (11, 'Corroboration'), (12, 'Structured Ananlytical Technique'), (13, 'Consensus'), (14, 'Reproducibility'), (15, 'Clearly Articulated Result'), (16, 'Level Of Confidence'), (17, 'Illustration'), (18, 'Sourced data and evidence'), (19, 'Clearly stated outliers')]), + ), + migrations.AlterField( + model_name='summaryissue', + name='sub_dimmension', + field=models.IntegerField(blank=True, choices=[(100, 'Drivers'), (1, 'Impact on People'), (2, 'Impact On System, Network And Services'), (3, 'Living Standards'), (4, 'Coping Mechanisms'), (5, 'Physical And Mental Well Being'), (6, 'Needs (Population)'), (7, 'Needs (Humanitarian)'), (8, 'Interventions (Population)'), (9, 'Interventions (Humanitarian)'), (10, 'Demographic Groups'), (11, 'Groups With Specific Needs'), (12, 'Geographical Areas'), (13, 'People At Risks'), (14, 'Focal Issues')], null=True), + ), + migrations.AlterField( + model_name='summaryissue', + name='sub_pillar', + field=models.IntegerField(blank=True, choices=[(100, 'Politics'), (1, 'Demography'), (2, 'Socio-Cultural'), (3, 'Environment'), (4, 'Security & Stability'), (5, 'Economics'), (6, 'Characteristics'), (7, 'Drivers and Aggravating Factors'), (8, 'Mitigating Factors'), (9, 'Hazards & Threats'), (10, 'Characteristics'), (11, 'Push Factors'), (12, 'Pull Factors'), (13, 'Intentions'), (14, 'Local Integrations'), (15, 'Source & Means'), (16, 'Challanges & Barriers'), (17, 'Knowledge & Info Gaps (Humanitarian)'), (18, 'Knowledge & Info Gaps (Population)'), (19, 'Population To Relief'), (20, 'Relief To Population'), (21, 'Physical & Security')], null=True), + ), + migrations.AlterField( + model_name='summarysubdimmensionissue', + name='focus', + field=models.IntegerField(choices=[(100, 'Food Security'), (1, 'Heath'), (2, 'Shelter'), (3, 'Wash'), (4, 'Protection'), (5, 'Nutrition'), (6, 'Livelihood'), (7, 'Education'), (8, 'Logistics'), (9, 'Inter/Cross Sector')]), + ), + ] diff --git a/apps/assessment_registry/migrations/0022_auto_20230809_0512.py b/apps/assessment_registry/migrations/0022_auto_20230809_0512.py new file mode 100644 index 0000000000..e7ae149679 --- /dev/null +++ b/apps/assessment_registry/migrations/0022_auto_20230809_0512.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.17 on 2023-08-09 05:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assessment_registry', '0021_auto_20230808_1125'), + ] + + operations = [ + migrations.RemoveField( + model_name='assessmentregistry', + name='final_score', + ), + migrations.RemoveField( + model_name='assessmentregistry', + name='matrix_score', + ), + ] diff --git a/apps/assessment_registry/models.py b/apps/assessment_registry/models.py index 2c97a72383..adadcf6612 100644 --- a/apps/assessment_registry/models.py +++ b/apps/assessment_registry/models.py @@ -1,5 +1,7 @@ from django.db import models from django.contrib.postgres.fields import ArrayField +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ from user_resource.models import UserResource from geo.models import Region @@ -11,7 +13,8 @@ class AssessmentRegistry(UserResource): class CrisisType(models.IntegerChoices): - EARTH_QUAKE = 0, 'Earth Quake' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + EARTH_QUAKE = 100, 'Earth Quake' GROUND_SHAKING = 1, 'Ground Shaking' TSUNAMI = 2, 'Tsunami' VOLCANO = 3, 'Volcano' @@ -39,27 +42,28 @@ class CrisisType(models.IntegerChoices): CONFLICT = 25, 'Conflict' class PreparednessType(models.IntegerChoices): - WITH_PREPAREDNESS = 0, 'With Preparedness' - WITHOUT_PREPAREDNESS = 1, 'Without Preparedness' + WITH_PREPAREDNESS = 1, 'With Preparedness' + WITHOUT_PREPAREDNESS = 2, 'Without Preparedness' class ExternalSupportType(models.IntegerChoices): - EXTERNAL_SUPPORT_RECIEVED = 0, 'External Support Received' - NO_EXTERNAL_SUPPORT_RECEIVED = 1, 'No External Support Received' + EXTERNAL_SUPPORT_RECIEVED = 1, 'External Support Received' + NO_EXTERNAL_SUPPORT_RECEIVED = 2, 'No External Support Received' class CoordinationType(models.IntegerChoices): - COORDINATED = 0, 'Coordinated Joint' - HARMONIZED = 1, 'Coordinated Harmonized' - UNCOORDINATED = 2, 'Uncoordinated' + COORDINATED = 1, 'Coordinated Joint' + HARMONIZED = 2, 'Coordinated Harmonized' + UNCOORDINATED = 3, 'Uncoordinated' class Type(models.IntegerChoices): - INITIAL = 0, 'Initial' - RAPID = 1, 'Rapid' - IN_DEPTH = 2, 'In depth' - MONITORING = 3, 'Monitoring' - OTHER = 4, 'Other' + INITIAL = 1, 'Initial' + RAPID = 2, 'Rapid' + IN_DEPTH = 3, 'In depth' + MONITORING = 4, 'Monitoring' + OTHER = 5, 'Other' class FamilyType(models.IntegerChoices): - DISPLACEMENT_TRAKING_MATRIX = 0, 'Displacement Traking Matrix' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + DISPLACEMENT_TRAKING_MATRIX = 100, 'Displacement Traking Matrix' MULTI_CLUSTER_INITIAL_AND_RAPID_ASSESSMENT = 1, 'Multi Cluster Initial and Rapid Assessment (MIRA)' MULTI_SECTORIAL_NEEDS_ASSESSMENT = 2, 'Multi sectorial Needs Assessment (MSNA)' EMERGENCY_FOOD_SECURITY_ASSESSMENT = 3, 'Emergency Food Security Assessment (EFSA)' @@ -77,22 +81,23 @@ class FamilyType(models.IntegerChoices): OTHER = 14, 'Other' class FrequencyType(models.IntegerChoices): - ONE_OFF = 0, 'One off' - REGULAR = 1, 'Regular' + ONE_OFF = 1, 'One off' + REGULAR = 2, 'Regular' class ConfidentialityType(models.IntegerChoices): - UNPROTECTED = 0, 'Unprotected' - CONFIDENTIAL = 1, 'Confidential' + UNPROTECTED = 1, 'Unprotected' + CONFIDENTIAL = 2, 'Confidential' class Language(models.IntegerChoices): - ENGLISH = 0, 'English' - FRENCH = 1, 'French' - SPANISH = 2, 'Spanish' - PORTUGESE = 3, 'Portugese' - ARABIC = 4, 'Arabic' + ENGLISH = 1, 'English' + FRENCH = 2, 'French' + SPANISH = 3, 'Spanish' + PORTUGESE = 4, 'Portugese' + ARABIC = 5, 'Arabic' class FocusType(models.IntegerChoices): - CONTEXT = 0, 'Context' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + CONTEXT = 100, 'Context' SHOCK_EVENT = 1, 'Shock/Event' DISPLACEMENT = 2, 'Displacement' CASUALTIES = 3, 'Casualties' @@ -105,7 +110,8 @@ class FocusType(models.IntegerChoices): RESPONSE_AND_CAPACITIES = 10, 'Response and Capacities' class SectorType(models.IntegerChoices): - FOOD_SECURITY = 0, 'Food Security' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + FOOD_SECURITY = 100, 'Food Security' HEALTH = 1, 'Heath' SHELTER = 2, 'Shelter' WASH = 3, 'Wash' @@ -117,7 +123,8 @@ class SectorType(models.IntegerChoices): INTER_CROSS_SECTOR = 9, 'Inter/Cross Sector' class ProtectionInfoType(models.IntegerChoices): - PROTECTION_MONITORING = 0, 'Protection Monitoring' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + PROTECTION_MONITORING = 100, 'Protection Monitoring' PROTECTION_NEEDS_ASSESSMENT = 1, 'Protection Needs Assessment' CASE_MANAGEMENT = 2, 'Case Management' POPULATION_DATA = 3, 'Population Data' @@ -127,7 +134,8 @@ class ProtectionInfoType(models.IntegerChoices): SECTORAL_SYSTEM_OTHER = 7, 'Sectoral System/Other' class AffectedGroupType(models.IntegerChoices): - ALL = 0, 'All' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + ALL = 100, 'All' ALL_AFFECTED = 1, 'All/Affected' ALL_NOT_AFFECTED = 2, 'All/Not Affected' ALL_AFFECTED_NOT_DISPLACED = 3, 'All/Affected/Not Displaced' @@ -204,20 +212,17 @@ class AffectedGroupType(models.IntegerChoices): locations = models.ManyToManyField(GeoArea, related_name='focus_location_assessment_reg', blank=True) - # Score Fields - matrix_score = models.IntegerField(default=0) - final_score = models.IntegerField(default=0) - class Meta: ordering = ["id"] def __str__(self): - return self.project.title + return self.lead.title class MethodologyAttribute(UserResource): class CollectionTechniqueType(models.IntegerChoices): - SECONDARY_DATA_REVIEW = 0, 'Secondary Data Review' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + SECONDARY_DATA_REVIEW = 100, 'Secondary Data Review' KEY_INFORMAT_INTERVIEW = 1, 'Key Informant Interview' DIRECT_OBSERVATION = 2, 'Direct Observation' COMMUNITY_GROUP_DISCUSSION = 3, 'Community Group Discussion' @@ -227,17 +232,18 @@ class CollectionTechniqueType(models.IntegerChoices): SATELLITE_IMAGERY = 7, 'Satellite Imagery' class SamplingApproachType(models.IntegerChoices): - NON_RANDOM_SELECTION = 0, 'Non-Random Selection' - RANDOM_SELECTION = 1, 'Random Selection' - FULL_ENUMERATION = 2, 'Full Enumeration' + NON_RANDOM_SELECTION = 1, 'Non-Random Selection' + RANDOM_SELECTION = 2, 'Random Selection' + FULL_ENUMERATION = 3, 'Full Enumeration' class ProximityType(models.IntegerChoices): - FACE_TO_FACE = 0, 'Face-to-Face' - REMOTE = 1, 'Remote' - MIXED = 2, 'Mixed' + FACE_TO_FACE = 1, 'Face-to-Face' + REMOTE = 2, 'Remote' + MIXED = 3, 'Mixed' class UnitOfAnalysisType(models.IntegerChoices): - CRISIS = 0, 'Crisis' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + CRISIS = 100, 'Crisis' COUNTRY = 1, 'Country' REGION = 2, 'Region' PROVINCE_GOV_PREFECTURE = 3, 'Province/governorate/prefecture' @@ -251,7 +257,8 @@ class UnitOfAnalysisType(models.IntegerChoices): INDIVIDUAL = 11, 'Individual' class UnitOfReportingType(models.IntegerChoices): - CRISIS = 0, 'Crisis' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + CRISIS = 100, 'Crisis' COUNTRY = 1, 'Country' REGION = 2, 'Region' PROVINCE_GOV_PREFECTURE = 3, 'Province/governorate/prefecture' @@ -279,9 +286,9 @@ class UnitOfReportingType(models.IntegerChoices): class AdditionalDocument(UserResource): class DocumentType(models.IntegerChoices): - ASSESSMENT_DATABASE = 0, 'Assessment database' - QUESTIONNAIRE = 1, 'Questionnaire' - MISCELLANEOUS = 2, 'Miscellaneous' + ASSESSMENT_DATABASE = 1, 'Assessment database' + QUESTIONNAIRE = 2, 'Questionnaire' + MISCELLANEOUS = 3, 'Miscellaneous' assessment_registry = models.ForeignKey( AssessmentRegistry, @@ -297,16 +304,20 @@ class DocumentType(models.IntegerChoices): ) external_link = models.URLField(max_length=500, blank=True) + def __str__(self): + return self.file.title + class ScoreRating(UserResource): class AnalyticalStatement(models.IntegerChoices): - FIT_FOR_PURPOSE = 0, 'Fit for purpose' - TRUSTWORTHINESS = 1, 'Trustworthiness' - ANALYTICAL_RIGOR = 2, 'Analytical Rigor' - ANALYTICAL_WRITING = 3, 'Analytical Writing' + FIT_FOR_PURPOSE = 1, 'Fit for purpose' + TRUSTWORTHINESS = 2, 'Trustworthiness' + ANALYTICAL_RIGOR = 3, 'Analytical Rigor' + ANALYTICAL_WRITING = 4, 'Analytical Writing' class ScoreCriteria(models.IntegerChoices): - RELEVANCE = 0, "Relevance" + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + RELEVANCE = 100, "Relevance" COMPREHENSIVENESS = 1, "Comprehensiveness" TIMELINESS = 2, "Timeliness" GRANULARITY = 3, "Granularity" @@ -377,7 +388,8 @@ class RatingType(models.IntegerChoices): class ScoreAnalyticalDensity(UserResource): class AnalysisLevelCovered(models.IntegerChoices): - ISSUE_UNMET_NEEDS_ARE_DETAILED = 0, 'Issues/unmet needs are detailed' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + ISSUE_UNMET_NEEDS_ARE_DETAILED = 100, 'Issues/unmet needs are detailed' ISSUE_UNMET_NEEDS_ARE_PRIOTIZED_RANKED = 1, 'Issues/unmet needs are priotized/ranked' CAUSES_OR_UNDERLYING_MECHANISMS_BEHIND_ISSUES_UNMET_NEEDS_ARE_DETAILED = 2,\ 'Causes or underlying mechanisms behind issues/unmet needs are detailed' @@ -394,7 +406,8 @@ class AnalysisLevelCovered(models.IntegerChoices): 'Recommnedations/interventions are priotized/ranked' class FigureProvidedByAssessement(models.IntegerChoices): - TOTAL_POP_IN_THE_ASSESSED_AREAS = 0, 'Total population in the assessed areas' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + TOTAL_POP_IN_THE_ASSESSED_AREAS = 100, 'Total population in the assessed areas' TOTAL_POP_EXPOSED_TO_THE_SHOCK_EVENT = 1, 'Total population exposed to the shock/event' TOTAL_POP_AFFECTED_LIVING_IN_THE_AFFECTED_AREAS = 2,\ 'Total populaiton affected/living in the affected area' @@ -418,7 +431,8 @@ class FigureProvidedByAssessement(models.IntegerChoices): class Question(UserResource): class QuestionSector(models.IntegerChoices): - RELEVANCE = 0, 'Relevance' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + RELEVANCE = 100, 'Relevance' COMPREHENSIVENESS = 1, 'Comprehensiveness' ETHICS = 2, 'Ethics' METHODOLOGICAL_RIGOR = 3, 'Methodological rigor' @@ -441,7 +455,8 @@ class QuestionSector(models.IntegerChoices): MINIMUM_TECHNICAL_STANDARDS = 20, 'Minimum technical standards' class QuestionSubSector(models.IntegerChoices): - RELEVANCE = 0, 'Relevance' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + RELEVANCE = 100, 'Relevance' GEOGRAPHIC_COMPREHENSIVENESS = 1, 'Geographic comprehensiveness' SECTORAL_COMPREHENSIVENESS = 2, 'Sectoral comprehensiveness' AFFECTED_AND_VULNERABLE_GROUPS_COMPREHENSIVENESS = 3, 'Affected and vulnerabel groups comprehensiveness' @@ -472,10 +487,118 @@ class QuestionSubSector(models.IntegerChoices): 'Buy-in and use by naional and local government agencies' BUY_IN_AND_USE_BY_DEVELOPMENT_AND_STABILIZATION_ACTORS = 27, 'Buy-in and use by development and stabilization actors' + QUESTION_SECTOR_SUB_SECTOR_MAP = { + QuestionSector.RELEVANCE: [ + QuestionSubSector.RELEVANCE, + ], + QuestionSector.COMPREHENSIVENESS: [ + QuestionSubSector.GEOGRAPHIC_COMPREHENSIVENESS, + QuestionSubSector.SECTORAL_COMPREHENSIVENESS, + QuestionSubSector.AFFECTED_AND_VULNERABLE_GROUPS_COMPREHENSIVENESS, + ], + QuestionSector.ETHICS: [ + QuestionSubSector.SAFETY_AND_PROTECTION, + QuestionSubSector.HUMANITARIAN_PRINCIPLES, + QuestionSubSector.CONTRIBUTION, + ], + QuestionSector.METHODOLOGICAL_RIGOR: [ + QuestionSubSector.TRANSPARENCY, + QuestionSubSector.MITIGATING_BIAS, + QuestionSubSector.PARTICIPATION, + QuestionSubSector.CONTEXT_SPECIFICITY, + ], + QuestionSector.ANALYTICAL_VALUE: [ + QuestionSubSector.ANALYTICAL_STANDARDS, + QuestionSubSector.DESCRIPTIONS, + QuestionSubSector.EXPLANATION, + QuestionSubSector.INTERPRETATION, + QuestionSubSector.ANTICIPATION, + QuestionSubSector.TIMELINESS, + ], + QuestionSector.EFFECTIVE_COMMUNICATION: [ + QuestionSubSector.USER_FRIENDLY_PRESENTATION, + QuestionSubSector.ACTIVE_DISSEMINATION, + ], + QuestionSector.USE: [ + QuestionSubSector.USE_FOR_COLLECTIVE_PLANNING, + QuestionSubSector.BUY_IN_AND_USE_BY_HUMANITARIAN_CLUSTERS_SECTORS, + QuestionSubSector.BUY_IN_AND_USE_BY_UN_AGENCIES, + QuestionSubSector.BUY_IN_AND_USE_BY_INTERNATIONAL_NGO, + QuestionSubSector.BUY_IN_AND_USE_BY_LOCAL_NGO, + QuestionSubSector.BUY_IN_AND_USE_BY_MEMBER_OF_RED_CROSS_RED_CRESENT_MOVEMENT, + QuestionSubSector.BUY_IN_AND_USE_BY_DONORS, + QuestionSubSector.BUY_IN_AND_USE_BY_NATIONAL_AND_LOCAL_GOVERNMENT_AGENCIES, + QuestionSubSector.BUY_IN_AND_USE_BY_DEVELOPMENT_AND_STABILIZATION_ACTORS, + ], + QuestionSector.PEOPLE_CENTERED_AND_INCLUSIVE: [ + QuestionSubSector.AFFECTED_AND_VULNERABLE_GROUPS_COMPREHENSIVENESS, + QuestionSubSector.SAFETY_AND_PROTECTION, + QuestionSubSector.PARTICIPATION, + QuestionSubSector.CONTEXT_SPECIFICITY, + ], + QuestionSector.ACCOUNTABILITY_TO_AFFECTED_POPULATIONS: [ + QuestionSubSector.PARTICIPATION, + QuestionSubSector.CONTEXT_SPECIFICITY, + QuestionSubSector.DESCRIPTIONS, + QuestionSubSector.ACTIVE_DISSEMINATION, + ], + QuestionSector.DO_NOT_HARM: [ + QuestionSubSector.SAFETY_AND_PROTECTION, + ], + QuestionSector.DESIGNED_WITH_PURPOSE: [ + QuestionSubSector.RELEVANCE, + ], + QuestionSector.COMPETENCY_AND_CAPACITY: [ + QuestionSubSector.SAFETY_AND_PROTECTION, + QuestionSubSector.CONTEXT_SPECIFICITY, + ], + QuestionSector.IMPARTIALITY: [ + QuestionSubSector.HUMANITARIAN_PRINCIPLES, + ], + QuestionSector.COORDINATION_AND_DATA_MINIMIZATION: [ + QuestionSubSector.RELEVANCE, + QuestionSubSector.CONTRIBUTION, + ], + QuestionSector.JOINT_ANALYSIS: [ + QuestionSubSector.MITIGATING_BIAS, + QuestionSubSector.ANALYTICAL_STANDARDS, + ], + QuestionSector.ACKNOWLEDGE_DISSENTING_VOICES_IN_JOINT_NEEDS_ANALYSIS: [ + QuestionSubSector.ANALYTICAL_STANDARDS, + ], + QuestionSector.IFORMED_CONSENT_CONFIDENTIALITY_AND_DATA_SECURITY: [ + QuestionSubSector.SAFETY_AND_PROTECTION, + ], + QuestionSector.SHARING_RESULTS: [ + QuestionSubSector.ACTIVE_DISSEMINATION, + ], + QuestionSector.TRANSPARENCY_BETWEEN_ACTORS: [ + QuestionSubSector.TRANSPARENCY, + QuestionSubSector.ANALYTICAL_STANDARDS, + ], + QuestionSector.MINIMUM_TECHNICAL_STANDARDS: [ + QuestionSubSector.MITIGATING_BIAS, + QuestionSubSector.ANALYTICAL_STANDARDS, + ], + } + sector = models.IntegerField(choices=QuestionSector.choices) sub_sector = models.IntegerField(choices=QuestionSubSector.choices) question = models.CharField(max_length=500) + def clean(self): + sector = self.sector + sub_sector = self.sub_sector + + if hasattr(self, 'sector'): + if hasattr(self, 'sub_sector'): + if sub_sector not in Question.QUESTION_SECTOR_SUB_SECTOR_MAP[sector]: + raise ValidationError('Invalid sebsector selected for given sector provided') + + def save(self, *args, **kwargs): + self.full_clean() + super().save(*args, **kwargs) + class Meta: ordering = ["id"] @@ -499,14 +622,17 @@ class Meta: ordering = ["id"] unique_together = [["assessment_registry", "question"]] + def __str__(self): + return str(self.answer) + class Summary(UserResource): class Pillar(models.IntegerChoices): - CONTEXT = 0, 'Context' - EVENT_SHOCK = 1, 'Event/Shock' - DISPLACEMENT = 2, 'Displacement' - INFORMATION_AND_COMMUNICATION = 3, 'Information & Communication' - HUMANITARIAN_ACCESS = 4, 'Humanitarian Access' + CONTEXT = 1, 'Context' + EVENT_SHOCK = 2, 'Event/Shock' + DISPLACEMENT = 3, 'Displacement' + INFORMATION_AND_COMMUNICATION = 4, 'Information & Communication' + HUMANITARIAN_ACCESS = 5, 'Humanitarian Access' assessment_registry = models.ForeignKey( AssessmentRegistry, @@ -539,11 +665,11 @@ class SummarySubPillarIssue(UserResource): class SummaryFocus(UserResource): class Dimmension(models.IntegerChoices): - IMPACT = 0, 'Impact' - HUMANITARIAN_CONDITIONS = 1, 'Humanitarian Conditions' - PRIORITIES_AND_PREFERENCES = 2, 'Priorities & Preferences' - CONCLUSIONS = 3, 'Conclusions' - HUMANITARIAN_POPULATION_FIGURES = 4, 'Humanitarian Population Figures' + IMPACT = 1, 'Impact' + HUMANITARIAN_CONDITIONS = 2, 'Humanitarian Conditions' + PRIORITIES_AND_PREFERENCES = 3, 'Priorities & Preferences' + CONCLUSIONS = 4, 'Conclusions' + HUMANITARIAN_POPULATION_FIGURES = 5, 'Humanitarian Population Figures' assessment_registry = models.ForeignKey( AssessmentRegistry, @@ -568,10 +694,14 @@ class Dimmension(models.IntegerChoices): total_people_severly_in_need = models.IntegerField(null=True, blank=True) total_people_critically_in_need = models.IntegerField(null=True, blank=True) + class Meta: + verbose_name = _("SummaryDimmension") + class SummaryIssue(models.Model): class SubPillar(models.IntegerChoices): - POLITICS = 0, 'Politics' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + POLITICS = 100, 'Politics' DEMOGRAPHY = 1, 'Demography' SOCIO_CULTURAL = 2, 'Socio-Cultural' ENVIRONMENT = 3, 'Environment' @@ -595,7 +725,8 @@ class SubPillar(models.IntegerChoices): PHYSICAL_AND_SECURITY = 21, 'Physical & Security' class SubDimmension(models.IntegerChoices): - DRIVERS = 0, 'Drivers' + # NOTE: key 100 has to be changed to 1 for every enum and increment in ascending order. + DRIVERS = 100, 'Drivers' IMPACT_ON_PEOPLE = 1, 'Impact on People' IMPACT_ON_SYSTEM = 2, 'Impact On System, Network And Services' LIVING_STANDARDS = 3, 'Living Standards' @@ -683,6 +814,9 @@ class SubDimmension(models.IntegerChoices): label = models.CharField(max_length=220) full_label = models.CharField(max_length=220, blank=True) + def __str__(self): + return self.label + class SummarySubDimmensionIssue(UserResource): assessment_registry = models.ForeignKey( diff --git a/apps/assessment_registry/mutation.py b/apps/assessment_registry/mutation.py index 9bdcdb4e53..363c3a8da7 100644 --- a/apps/assessment_registry/mutation.py +++ b/apps/assessment_registry/mutation.py @@ -3,11 +3,12 @@ from utils.graphene.mutation import ( generate_input_type_for_serializer, PsGrapheneMutation, + GrapheneMutation, ) from deep.permissions import ProjectPermissions as PP from .models import AssessmentRegistry, SummaryIssue -from .schema import AssessmentRegistryType, IssueType +from .schema import AssessmentRegistryType, AssessmentRegistrySummaryIssueType from .serializers import ( AssessmentRegistrySerializer, IssueSerializer, @@ -17,25 +18,28 @@ 'AssessmentRegistryCreateInputType', serializer_class=AssessmentRegistrySerializer ) -IssueCreateInputType = generate_input_type_for_serializer( - 'IssueCreateInputType', +AssessmentRegistrySummaryIssueCreateInputType = generate_input_type_for_serializer( + 'AssessmentRegistrySummaryIssueCreateInputType', serializer_class=IssueSerializer ) -class CreateIssue(PsGrapheneMutation): +class CreateIssue(GrapheneMutation): class Arguments: - data = IssueCreateInputType() + data = AssessmentRegistrySummaryIssueCreateInputType() - result = graphene.Field(IssueType) + result = graphene.Field(AssessmentRegistrySummaryIssueType) serializer_class = IssueSerializer model = SummaryIssue - permissions = [] + + @classmethod + def check_permissions(cls, *args, **_): + return True # Allow all to create New Issue class CreateAssessmentRegistry(PsGrapheneMutation): class Arguments: - data = AssessmentRegistryCreateInputType() + data = AssessmentRegistryCreateInputType(required=True) result = graphene.Field(AssessmentRegistryType) serializer_class = AssessmentRegistrySerializer @@ -60,4 +64,4 @@ class ProjectMutation(): class Mutation(): - create_issue = CreateIssue.Field() + create_assessment_reg_summary_issue = CreateIssue.Field() diff --git a/apps/assessment_registry/schema.py b/apps/assessment_registry/schema.py index b89d07aa38..7245704dbe 100644 --- a/apps/assessment_registry/schema.py +++ b/apps/assessment_registry/schema.py @@ -73,11 +73,6 @@ class Meta: sub_sector_display = EnumDescription(source='get_sub_sector_display', required=False) -class SummarySubSectorType(graphene.ObjectType): - sub_sector = graphene.String() - sub_sector_value = graphene.Int() - - class SummaryOptionType(graphene.ObjectType): pillar = graphene.Field(AssessmentRegistrySummaryPillarTypeEnum, required=True) pillar_display = EnumDescription(required=True) @@ -219,11 +214,11 @@ class Meta: fields = ("id", "question", "answer",) -class IssueType(DjangoObjectType, UserResourceMixin): +class AssessmentRegistrySummaryIssueType(DjangoObjectType, UserResourceMixin): sub_pillar = graphene.Field(AssessmentRegistrySummarySubPillarTypeEnum, required=False) - sub_pillar_display = graphene.String(required=False) + sub_pillar_display = EnumDescription(required=False) sub_dimmension = graphene.Field(AssessmentRegistrySummarySubDimmensionTypeEnum, required=False) - sub_dimmension_display = graphene.String(required=False) + sub_dimmension_display = EnumDescription(required=False) class Meta: model = SummaryIssue @@ -244,7 +239,7 @@ def resolve_sub_dimmension_display(root, info, **kwargs): return None -class IssueListType(CustomDjangoListObjectType): +class AssessmentRegistrySummaryIssueListType(CustomDjangoListObjectType): class Meta: model = SummaryIssue filterset_class = IssueGQFilterSet @@ -279,7 +274,7 @@ class Meta: class SummaryFocusSubDimmensionIssueType(DjangoObjectType, UserResourceMixin): focus = graphene.Field(AssessmentRegistryFocusTypeEnum, required=False) - focus_display = graphene.String(required=False) + focus_display = EnumDescription(required=False) class Meta: model = SummarySubDimmensionIssue @@ -298,7 +293,6 @@ class Meta: "data_collection_start_date", "data_collection_end_date", "publication_date", "executive_summary", "lead_organizations", "international_partners", "donors", "national_partners", "governments", "objectives", "data_collection_techniques", "sampling", "limitations", "locations", - "matrix_score", "final_score", ) bg_crisis_type = graphene.Field(AssessmentRegistryCrisisTypeEnum, required=True) @@ -406,9 +400,9 @@ def resolve_assessment_registry_options(root, info, **kwargs): class Query(): - issue = DjangoObjectField(IssueType) - issues = DjangoPaginatedListObjectField( - IssueListType, + assessment_reg_summary_issue = DjangoObjectField(AssessmentRegistrySummaryIssueType) + assessment_reg_summary_issues = DjangoPaginatedListObjectField( + AssessmentRegistrySummaryIssueListType, pagination=PageGraphqlPagination( page_size_query_param='pageSize' ) diff --git a/apps/assessment_registry/serializers.py b/apps/assessment_registry/serializers.py index 91310a0f1d..c1ed5efded 100644 --- a/apps/assessment_registry/serializers.py +++ b/apps/assessment_registry/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers +from .utils import get_hierarchy_level from user_resource.serializers import UserResourceSerializer from deep.serializers import ProjectPropertySerializerMixin, TempClientIdMixin, IntegerIDField @@ -45,22 +46,24 @@ class Meta: ) def validate(self, data): - from utils.common import get_hierarchy_level - if data.get('sub_pillar') is not None and data.get('sub_dimmension') is not None: + sub_pillar = data.get('sub_pillar') + sub_dimmension = data.get('sub_dimmension') + parent = data.get('parent') + + if all([sub_pillar, sub_dimmension]): raise serializers.ValidationError("Cannot select both sub_pillar and sub_dimmension field.") - if data.get('parent') is not None: - if data.get('sub_pillar') is not None: - if data.get('sub_pillar') != data.get('parent').sub_pillar: - raise serializers.ValidationError("sub_pillar does not match between parent and child.") - - if data.get('sub_dimmension') is not None: - if data.get('sub_dimmension') != data.get('parent').sub_dimmension: - raise serializers.ValidationError("sub_dimmension does not match between child and parent.") - if data.get('parent'): - hierarchy_level = get_hierarchy_level(data.get('parent')) + if not any([sub_pillar, sub_dimmension]): + raise serializers.ValidationError("Either sub_pillar or sub_dimmension must be selected") + + if parent: + if sub_pillar and sub_pillar != parent.sub_pillar: + raise serializers.ValidationError("sub_pillar does not match between parent and child.") + if sub_dimmension and sub_dimmension != parent.sub_dimmension: + raise serializers.ValidationError("sub_dimmension does not match between child and parent.") + + hierarchy_level = get_hierarchy_level(parent) if hierarchy_level > 2: raise serializers.ValidationError("Cannot create issue more than two level of hierarchy") - return data @@ -113,7 +116,7 @@ class Meta: fields = ("id", "client_id", "score_type", "rating", "reason",) -class ScoreAnalyticalDensitySerializer(UserResourceSerializer): +class ScoreAnalyticalDensitySerializer(UserResourceSerializer, TempClientIdMixin): id = IntegerIDField(required=False) class Meta: @@ -198,8 +201,6 @@ class Meta: "methodology_attributes", "additional_documents", "score_ratings", - "matrix_score", - "final_score", "score_analytical_density", "cna", "summary_pillar_meta", diff --git a/apps/assessment_registry/tests/test_schemas.py b/apps/assessment_registry/tests/test_schemas.py index 05ea71d92d..11e5f3818e 100644 --- a/apps/assessment_registry/tests/test_schemas.py +++ b/apps/assessment_registry/tests/test_schemas.py @@ -36,10 +36,12 @@ def setUp(self): self.question1 = QuestionFactory.create( sector=Question.QuestionSector.RELEVANCE, sub_sector=Question.QuestionSubSector.RELEVANCE, + question='test question' ) self.question2 = QuestionFactory.create( sector=Question.QuestionSector.COMPREHENSIVENESS, sub_sector=Question.QuestionSubSector.GEOGRAPHIC_COMPREHENSIVENESS, + question='test question', ) self.country1, self.country2 = RegionFactory.create_batch(2) self.organization1, self.organization2 = OrganizationFactory.create_batch(2) diff --git a/apps/assessment_registry/utils.py b/apps/assessment_registry/utils.py new file mode 100644 index 0000000000..e833b8da5e --- /dev/null +++ b/apps/assessment_registry/utils.py @@ -0,0 +1,7 @@ +# Get heirarchy level of a django model. +def get_hierarchy_level(parent_instance): + level = 1 + while parent_instance.parent: + level += 1 + parent_instance = parent_instance.parent + return level diff --git a/apps/gallery/tests/test_mutations.py b/apps/gallery/tests/test_mutations.py index 3a27e4a393..1e0e554244 100644 --- a/apps/gallery/tests/test_mutations.py +++ b/apps/gallery/tests/test_mutations.py @@ -32,9 +32,9 @@ def setUp(self): self.user = UserFactory.create() self.force_login(self.user) - def test_upload_preview_image(self): + def test_upload_file(self): file_text = b'preview image text' - with NamedTemporaryFile(suffix='.png') as t_file: + with NamedTemporaryFile(suffix='.jpeg') as t_file: t_file.write(file_text) t_file.seek(0) response = self._client.post( @@ -57,5 +57,5 @@ def test_upload_preview_image(self): self.assertTrue(content['data']['fileUpload']['result']['file']["name"]) file_name = content['data']['fileUpload']['result']['file']["name"] file_url = content['data']['fileUpload']['result']['file']["url"] - self.assertTrue(file_name.endswith('.png')) + self.assertTrue(file_name.endswith('.jpeg')) self.assertTrue(file_url.endswith(file_name)) diff --git a/deep/schema.py b/deep/schema.py index 363ad8201f..0d0cac55fe 100644 --- a/deep/schema.py +++ b/deep/schema.py @@ -22,8 +22,8 @@ from assisted_tagging import schema as assisted_tagging_schema from unified_connector import schema as unified_connector_schema from export import schema as export_schema, mutation as export_mutation -from assessment_registry import mutation as issue_mutation -from assessment_registry import schema as issue_schema +from assessment_registry import mutation as assessment_registry_mutation +from assessment_registry import schema as assessment_registry_schema from deep_explore import schema as deep_explore_schema from gallery import mutations as gallery_mutation from deep.enums import CustomEnum @@ -41,7 +41,7 @@ class Query( unified_connector_schema.Query, export_schema.Query, deep_explore_schema.Query, - issue_schema.Query, + assessment_registry_schema.Query, # -- graphene.ObjectType ): @@ -66,7 +66,7 @@ class Mutation( notification_mutation.Mutation, export_mutation.Mutation, gallery_mutation.Mutation, - issue_mutation.Mutation, + assessment_registry_mutation.Mutation, # -- graphene.ObjectType ): diff --git a/schema.graphql b/schema.graphql index 6cf391b1bd..aaf1f66125 100644 --- a/schema.graphql +++ b/schema.graphql @@ -650,8 +650,6 @@ input AssessmentRegistryCreateInputType { methodologyAttributes: [MethodologyAttributeInputType!] additionalDocuments: [AdditionalDocumentInputType!] scoreRatings: [ScoreRatingInputType!] - matrixScore: Int - finalScore: Int scoreAnalyticalDensity: [ScoreAnalyticalDensityInputType!] cna: [CNAAnswerInputType!] summaryPillarMeta: [SummaryMetaInputType!] @@ -865,6 +863,35 @@ enum AssessmentRegistrySummaryFocusDimmensionTypeEnum { HUMANITARIAN_POPULATION_FIGURES } +input AssessmentRegistrySummaryIssueCreateInputType { + subPillar: AssessmentRegistrySummarySubPillarTypeEnum + subDimmension: AssessmentRegistrySummarySubDimmensionTypeEnum + parent: ID + label: String! +} + +type AssessmentRegistrySummaryIssueListType { + results: [AssessmentRegistrySummaryIssueType!] + totalCount: Int + page: Int + pageSize: Int +} + +type AssessmentRegistrySummaryIssueType { + id: ID! + parent: AssessmentRegistrySummaryIssueType + label: String! + fullLabel: String! + createdAt: DateTime! + modifiedAt: DateTime! + createdBy: UserType + modifiedBy: UserType + subPillar: AssessmentRegistrySummarySubPillarTypeEnum + subPillarDisplay: EnumDescription + subDimmension: AssessmentRegistrySummarySubDimmensionTypeEnum + subDimmensionDisplay: EnumDescription +} + enum AssessmentRegistrySummaryPillarTypeEnum { CONTEXT EVENT_SHOCK @@ -938,8 +965,6 @@ type AssessmentRegistryType { sampling: String limitations: String locations: [ProjectGeoAreaType!] - matrixScore: Int! - finalScore: Int! clientId: String! createdAt: DateTime! modifiedAt: DateTime! @@ -1428,7 +1453,7 @@ type CreateEntryReviewComment { type CreateIssue { errors: [GenericScalar!] ok: Boolean - result: IssueType + result: AssessmentRegistrySummaryIssueType } type CreateLead { @@ -2299,35 +2324,6 @@ input HIDLoginInputType { state: Int } -input IssueCreateInputType { - subPillar: AssessmentRegistrySummarySubPillarTypeEnum - subDimmension: AssessmentRegistrySummarySubDimmensionTypeEnum - parent: ID - label: String! -} - -type IssueListType { - results: [IssueType!] - totalCount: Int - page: Int - pageSize: Int -} - -type IssueType { - id: ID! - parent: IssueType - label: String! - fullLabel: String! - createdAt: DateTime! - modifiedAt: DateTime! - createdBy: UserType - modifiedBy: UserType - subPillar: AssessmentRegistrySummarySubPillarTypeEnum - subPillarDisplay: String - subDimmension: AssessmentRegistrySummarySubDimmensionTypeEnum - subDimmensionDisplay: String -} - type JwtTokenType { accessToken: String expiresIn: String @@ -2732,7 +2728,7 @@ type MissingPredictionReviewType { } type Mutation { - createIssue(data: IssueCreateInputType): CreateIssue + createAssessmentRegSummaryIssue(data: AssessmentRegistrySummaryIssueCreateInputType): CreateIssue fileUpload(data: FileUploadInputType!): UploadFile genericExportCreate(data: GenericExportCreateInputType!): CreateUserGenericExport genericExportCancel(id: ID!): CancelUserGenericExport @@ -3073,7 +3069,7 @@ type ProjectMembershipType { type ProjectMutationType { id: ID! title: String! - createAssessmentRegistry(data: AssessmentRegistryCreateInputType): CreateAssessmentRegistry + createAssessmentRegistry(data: AssessmentRegistryCreateInputType!): CreateAssessmentRegistry updateAssessmentRegistry(data: AssessmentRegistryCreateInputType, id: ID): UpdateAssessmentRegistry analysisPillarUpdate(data: AnalysisPillarUpdateInputType!, id: ID!): UpdateAnalysisPillar discardedEntryCreate(data: DiscardedEntryCreateInputType!): CreateAnalysisPillarDiscardedEntry @@ -3444,8 +3440,8 @@ type PublicProjectWithMembershipData { } type Query { - issue(id: ID!): IssueType - issues(label: String, parent: ID, subPillar: AssessmentRegistrySummarySubPillarTypeEnum, subDimmension: AssessmentRegistrySummarySubDimmensionTypeEnum, page: Int = 1, ordering: String, pageSize: Int): IssueListType + assessmentRegSummaryIssue(id: ID!): AssessmentRegistrySummaryIssueType + assessmentRegSummaryIssues(label: String, parent: ID, subPillar: AssessmentRegistrySummarySubPillarTypeEnum, subDimmension: AssessmentRegistrySummarySubDimmensionTypeEnum, page: Int = 1, ordering: String, pageSize: Int): AssessmentRegistrySummaryIssueListType deepExploreStats(filter: ExploreDeepFilterInputType!): ExploreDashboardStatType publicDeepExploreYearlySnapshots: [PublicExploreSnapshotType!] publicDeepExploreGlobalSnapshots: [PublicExploreSnapshotType!] @@ -3741,11 +3737,11 @@ type SummaryFocusSubDimmensionIssueType { clientId: String assessmentRegistry: AssessmentRegistryType! focus: AssessmentRegistryFocusTypeEnum - summaryIssue: IssueType! + summaryIssue: AssessmentRegistrySummaryIssueType! text: String! order: Int! leadPreviewTextRef: GenericScalar - focusDisplay: String + focusDisplay: EnumDescription } input SummaryMetaInputType { @@ -3798,7 +3794,7 @@ input SummarySubPillarIssueInputType { type SummarySubPillarIssueType { id: ID! - summaryIssue: IssueType! + summaryIssue: AssessmentRegistrySummaryIssueType! text: String! order: Int! leadPreviewTextRef: GenericScalar diff --git a/utils/common.py b/utils/common.py index f0f6a163d6..a1104a4c43 100644 --- a/utils/common.py +++ b/utils/common.py @@ -597,12 +597,3 @@ def render_string_for_graphql(text): if text == '': return None return text - - -# Get heirarchy level of a django model. -def get_hierarchy_level(parent_instance): - level = 1 - while parent_instance.parent: - level += 1 - parent_instance = parent_instance.parent - return level diff --git a/utils/graphene/mutation.py b/utils/graphene/mutation.py index aa5f1bb99c..b0d7f3af9d 100644 --- a/utils/graphene/mutation.py +++ b/utils/graphene/mutation.py @@ -288,6 +288,7 @@ class GrapheneMutation(BaseGrapheneMutation): @classmethod def perform_mutate(cls, root, info, **kwargs): + print("*******************************") data = kwargs['data'] instance, errors = cls._save_item(data, info, **kwargs) return cls(result=instance, errors=errors, ok=not errors)