diff --git a/src/angular/planit/src/app/app.module.ts b/src/angular/planit/src/app/app.module.ts index 461ebe3a6..a389ddf75 100644 --- a/src/angular/planit/src/app/app.module.ts +++ b/src/angular/planit/src/app/app.module.ts @@ -25,6 +25,7 @@ import { AccountCreateService } from './core/services/account-create.service'; import { apiHttpProvider } from './core/services/api-http.provider'; import { AuthService } from './core/services/auth.service'; import { CacheService } from './core/services/cache.service'; +import { RiskService } from './core/services/risk.service'; import { UserService } from './core/services/user.service'; import { WeatherEventService } from './core/services/weather-event.service'; @@ -80,6 +81,7 @@ const appRoutes: Routes = [ apiHttpProvider, AuthService, CacheService, + RiskService, UserService, WeatherEventService, ], diff --git a/src/angular/planit/src/app/assessment/assessment-overview.component.html b/src/angular/planit/src/app/assessment/assessment-overview.component.html index e20864041..c8112fa18 100644 --- a/src/angular/planit/src/app/assessment/assessment-overview.component.html +++ b/src/angular/planit/src/app/assessment/assessment-overview.component.html @@ -33,12 +33,12 @@

Your city's risks

+ [potentialImpact]="risk.impactMagnitude"> - {{ risk.potentialImpact }} - {{ risk.adaptiveCapacity }} + {{ risk.impactMagnitude }} + {{ risk.adaptiveCapacityDescription }}
diff --git a/src/angular/planit/src/app/assessment/assessment-overview.component.ts b/src/angular/planit/src/app/assessment/assessment-overview.component.ts index 4315e8c68..46e7c8e15 100644 --- a/src/angular/planit/src/app/assessment/assessment-overview.component.ts +++ b/src/angular/planit/src/app/assessment/assessment-overview.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { Risk } from '../shared'; +import { RiskService } from '../core/services/risk.service'; @Component({ selector: 'va-overview', @@ -9,54 +10,13 @@ import { Risk } from '../shared'; export class AssessmentOverviewComponent implements OnInit { public risks: Risk[]; - constructor () {} + constructor (private riskService: RiskService) {} ngOnInit() { - this.risks = [{ - name: 'Heat on the elderly', - hazard: 'heat', - communitySystem: 'elderly', - potentialImpact: 0, - adaptiveCapacity: 2, - indicators: [ - {name: 'Extreme Heat Events', url: '#'}, - {name: 'Heat Wave Incidents', url: '#'}, - {name: 'Heat Wave Duration Index', url: '#'} - ] - }, { - name: 'Heat on asphalt', - hazard: 'heat', - communitySystem: 'asphalt', - potentialImpact: 1, - adaptiveCapacity: 1, - indicators: [ - {name: 'Cooling Degree Days', url: '#'}, - {name: 'Heat Wave Incidents', url: '#'}, - {name: 'Heat Wave Duration Index', url: '#'} - ] - }, { - name: 'Extreme cold days on agriculture', - hazard: 'extreme_cold', - communitySystem: 'agriculture', - potentialImpact: 2, - adaptiveCapacity: 0, - indicators: [ - {name: 'Accumulated Freezing Degree Days', url: '#'}, - {name: 'Extreme Cold Events', url: '#'}, - {name: 'Frost Days', url: '#'}, - ] - }, - { - name: 'Water-bourne disease on ecological function', - hazard: 'water_bourne_disease', - communitySystem: 'ecological_function', - potentialImpact: 2, - adaptiveCapacity: 2, - indicators: [ - {name: 'Total Precipitation', url: '#'}, - {name: 'Precipitation Threshold', url: '#'}, - {name: 'Extreme Precipitation Events', url: '#'}, - ] - }]; + + this.riskService.list().subscribe(risks => { + this.risks = risks; + }); } } + diff --git a/src/angular/planit/src/app/assessment/risk-popover/risk-popover.component.html b/src/angular/planit/src/app/assessment/risk-popover/risk-popover.component.html index af407ee03..938990113 100644 --- a/src/angular/planit/src/app/assessment/risk-popover/risk-popover.component.html +++ b/src/angular/planit/src/app/assessment/risk-popover/risk-popover.component.html @@ -2,14 +2,14 @@

Donec dictum hendrerit dui, nec dictum ante molestie eleifend. Quisque accumsan nisl lectus, in vulputate erat iaculis quis. Aenean at nisl vehicula, fermentum leo vel, ultricies sapien.

Related Indicators

-

- {{ indicator.name }} +

+ {{ indicator }}

{{ risk.name }} + popoverTitle="{{ risk.weatherEvent.name }} on {{ risk.communitySystem.name }}" + placement="right">{{ risk.weatherEvent.name }} on {{ risk.communitySystem.name }}
diff --git a/src/angular/planit/src/app/core/services/risk.service.ts b/src/angular/planit/src/app/core/services/risk.service.ts new file mode 100644 index 000000000..4799ad05c --- /dev/null +++ b/src/angular/planit/src/app/core/services/risk.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs/Rx'; + +import { Risk } from '../../shared/models/risk.model'; +import { PlanItApiHttp } from './api-http.service'; +import { environment } from '../../../environments/environment'; + +@Injectable() +export class RiskService { + + constructor(private apiHttp: PlanItApiHttp) {} + + list(): Observable { + const url = `${environment.apiUrl}/api/risks/`; + return this.apiHttp.get(url).map(resp => { + const vals = resp.json() || []; + return vals.map(r => r as Risk); + }); + } + +} diff --git a/src/angular/planit/src/app/risk-wizard/steps/identify-step.component.ts b/src/angular/planit/src/app/risk-wizard/steps/identify-step.component.ts index bceece4d3..738851186 100644 --- a/src/angular/planit/src/app/risk-wizard/steps/identify-step.component.ts +++ b/src/angular/planit/src/app/risk-wizard/steps/identify-step.component.ts @@ -39,8 +39,8 @@ export class IdentifyStepComponent extends WizardStepComponent implements fromData(risk: Risk): IdentifyStepFormModel { return { - hazard: risk.hazard, - communitySystem: risk.communitySystem + hazard: risk.impactDescription, + communitySystem: risk.communitySystem.name }; } @@ -60,8 +60,8 @@ export class IdentifyStepComponent extends WizardStepComponent implements } toData(data: IdentifyStepFormModel, risk: Risk) { - risk.hazard = data.hazard; - risk.communitySystem = data.communitySystem; + risk.impactDescription = data.hazard; + risk.communitySystem.name = data.communitySystem; return risk; } } diff --git a/src/angular/planit/src/app/shared/index.ts b/src/angular/planit/src/app/shared/index.ts index 984592251..44c395d74 100644 --- a/src/angular/planit/src/app/shared/index.ts +++ b/src/angular/planit/src/app/shared/index.ts @@ -1,3 +1,4 @@ +export { CommunitySystem } from './models/community-system.model'; export { Concern } from './models/concern.model'; export { Risk } from './models/risk.model'; export { User } from './models/user.model'; diff --git a/src/angular/planit/src/app/shared/models/community-system.model.ts b/src/angular/planit/src/app/shared/models/community-system.model.ts new file mode 100644 index 000000000..46ea888d7 --- /dev/null +++ b/src/angular/planit/src/app/shared/models/community-system.model.ts @@ -0,0 +1,3 @@ +export class CommunitySystem { + name: string; +} diff --git a/src/angular/planit/src/app/shared/models/concern.model.ts b/src/angular/planit/src/app/shared/models/concern.model.ts index 0d88a038b..c661fc0ef 100644 --- a/src/angular/planit/src/app/shared/models/concern.model.ts +++ b/src/angular/planit/src/app/shared/models/concern.model.ts @@ -1,4 +1,5 @@ export class Concern { + id: number; indicator: string; isRelative: boolean; tagline: string; diff --git a/src/angular/planit/src/app/shared/models/risk.model.ts b/src/angular/planit/src/app/shared/models/risk.model.ts index 4dcedb003..bee67d2c3 100644 --- a/src/angular/planit/src/app/shared/models/risk.model.ts +++ b/src/angular/planit/src/app/shared/models/risk.model.ts @@ -1,10 +1,19 @@ +import { CommunitySystem } from './community-system.model'; +import { WeatherEvent } from './weather-event.model'; +import { Indicator } from 'climate-change-components'; + export class Risk { - name: string; - communitySystem: string; - hazard: string; - potentialImpact?: number; - adaptiveCapacity?: number; - indicators: [{name: string, url: string}]; + id?: string; + weatherEvent: WeatherEvent; + communitySystem: CommunitySystem; + probability?: string; + frequency?: string; + intensity?: string; + impactMagnitude?: string; + impactDescription?: string; + adaptiveCapacity?: string; + relatedAdaptiveValues?: string[]; + adaptiveCapacityDescription?: string; constructor(object: Object) { Object.assign(this, object); diff --git a/src/angular/planit/src/app/shared/models/weather-event.model.ts b/src/angular/planit/src/app/shared/models/weather-event.model.ts index 50fa34551..c950c935e 100644 --- a/src/angular/planit/src/app/shared/models/weather-event.model.ts +++ b/src/angular/planit/src/app/shared/models/weather-event.model.ts @@ -3,6 +3,7 @@ import { Indicator } from 'climate-change-components'; export class WeatherEvent { name: string; + coastalOnly: boolean; concern?: Concern; indicators?: string[]; displayClass: string; diff --git a/src/django/planit/urls.py b/src/django/planit/urls.py index 2ecfaab9d..10d5b4246 100644 --- a/src/django/planit/urls.py +++ b/src/django/planit/urls.py @@ -22,14 +22,14 @@ from climate_api.views import ClimateAPIProxyView import planit_data.views as planit_data_views -import action_steps.views as action_steps_views from users.views import CurrentUserView, PlanitObtainAuthToken, OrganizationViewSet, UserViewSet router = routers.DefaultRouter() +router.register(r'community-system', planit_data_views.CommunitySystemViewSet) router.register(r'organizations', OrganizationViewSet) router.register(r'users', UserViewSet) router.register(r'risks', planit_data_views.OrganizationRiskView, base_name='organizationrisk') -router.register(r'collaborators', action_steps_views.CollaboratorViewSet) +router.register(r'weather-event', planit_data_views.WeatherEventViewSet) urlpatterns = [ url(r'^api/climate-api/(?P.*)$', diff --git a/src/django/planit_data/serializers.py b/src/django/planit_data/serializers.py index aea0225fa..80707b4d8 100644 --- a/src/django/planit_data/serializers.py +++ b/src/django/planit_data/serializers.py @@ -9,8 +9,15 @@ ) -class ConcernSerializer(serializers.ModelSerializer): +class CommunitySystemSerializer(serializers.ModelSerializer): + """Serialize community systems.""" + class Meta: + model = CommunitySystem + fields = ('name',) + +class ConcernSerializer(serializers.ModelSerializer): + """Serialize concerns.""" indicator = serializers.SlugRelatedField( many=False, read_only=True, @@ -35,17 +42,30 @@ class Meta: fields = ('id', 'indicator', 'isRelative',) -class OrganizationRiskSerializer(serializers.ModelSerializer): - weatherEvent = serializers.PrimaryKeyRelatedField( - many=False, - queryset=WeatherEvent.objects.all(), - source='weather_event' - ) - communitySystem = serializers.PrimaryKeyRelatedField( +class WeatherEventSerializer(serializers.ModelSerializer): + """Serialize weather events with only keys for related fields.""" + concern = serializers.PrimaryKeyRelatedField( many=False, - queryset=CommunitySystem.objects.all(), - source='community_system' + queryset=Concern.objects.all() ) + coastalOnly = serializers.BooleanField(source='coastal_only') + indicators = serializers.SlugRelatedField(many=True, read_only=True, slug_field='name') + displayClass = serializers.CharField(source='display_class') + + class Meta: + model = WeatherEvent + fields = ('name', 'coastalOnly', 'concern', 'indicators', 'displayClass') + + +class WeatherEventWithConcernSerializer(WeatherEventSerializer): + """Serialize weather events, with related concerns.""" + concern = ConcernSerializer() + + +class OrganizationRiskSerializer(serializers.ModelSerializer): + """Serialize organization risks for viewing, with related models.""" + weatherEvent = WeatherEventSerializer(source='weather_event') + communitySystem = CommunitySystemSerializer(source='community_system') impactMagnitude = serializers.ChoiceField(source='impact_magnitude', required=False, allow_blank=True, @@ -61,6 +81,29 @@ class OrganizationRiskSerializer(serializers.ModelSerializer): adaptiveCapacityDescription = serializers.CharField(source='adaptive_capacity_description', required=False, allow_blank=True) + class Meta: + model = OrganizationRisk + fields = ('id', 'weatherEvent', 'communitySystem', 'probability', 'frequency', + 'intensity', 'impactMagnitude', 'impactDescription', 'adaptiveCapacity', + 'relatedAdaptiveValues', 'adaptiveCapacityDescription') + + +class OrganizationRiskCreateSerializer(OrganizationRiskSerializer): + """Serializer for creating and updating risks. + + Takes ID for related weather event and community system. + """ + weatherEvent = serializers.PrimaryKeyRelatedField( + many=False, + queryset=WeatherEvent.objects.all(), + source='weather_event' + ) + communitySystem = serializers.PrimaryKeyRelatedField( + many=False, + queryset=CommunitySystem.objects.all(), + source='community_system' + ) + def create(self, validated_data): # Pulling the organization from the request instead of as a serialized field # ensures that users can't modify a different organization's risks @@ -72,28 +115,10 @@ def create(self, validated_data): return OrganizationRisk.objects.create(organization=organization, **validated_data) - class Meta: - model = OrganizationRisk - fields = ('id', 'weatherEvent', 'communitySystem', 'probability', 'frequency', - 'intensity', 'impactMagnitude', 'impactDescription', 'adaptiveCapacity', - 'relatedAdaptiveValues', 'adaptiveCapacityDescription') - - -class WeatherEventSerializer(serializers.ModelSerializer): - - concern = ConcernSerializer() - coastalOnly = serializers.BooleanField(source='coastal_only') - indicators = serializers.SlugRelatedField(many=True, read_only=True, slug_field='name') - displayClass = serializers.CharField(source='display_class') - - class Meta: - model = WeatherEvent - fields = ('name', 'coastalOnly', 'concern', 'indicators', 'displayClass') - class WeatherEventRankSerializer(serializers.ModelSerializer): - - weatherEvent = WeatherEventSerializer(source='weather_event') + """Serialize weather events by rank.""" + weatherEvent = WeatherEventWithConcernSerializer(source='weather_event') class Meta: model = WeatherEventRank diff --git a/src/django/planit_data/tests/test_serializers.py b/src/django/planit_data/tests/test_serializers.py index 187d6c68c..22d03e76b 100644 --- a/src/django/planit_data/tests/test_serializers.py +++ b/src/django/planit_data/tests/test_serializers.py @@ -4,7 +4,11 @@ from rest_framework.test import APIRequestFactory from planit_data.models import CommunitySystem, Concern, Indicator, WeatherEvent -from planit_data.serializers import ConcernSerializer, OrganizationRiskSerializer +from planit_data.serializers import ( + ConcernSerializer, + OrganizationRiskCreateSerializer, +) + from users.models import PlanItLocation, PlanItOrganization, PlanItUser @@ -70,7 +74,7 @@ def test_context_request_can_be_set_afterwards(self, calculate_mock): serializer.data -class OrganizationRiskSerializerTestCase(TestCase): +class OrganizationRiskCreateSerializerTestCase(TestCase): def setUp(self): self.user = PlanItUser.objects.create_user('mike@mike.phl', 'Mike', 'M', password='mike12345') @@ -89,17 +93,19 @@ def setUp(self): def test_create_context_requires_request(self): """Ensure the Serializer raises an error if the context does not have a request""" - serializer = OrganizationRiskSerializer(data={'weatherEvent': self.weather_event.id, + serializer = OrganizationRiskCreateSerializer(data={'weatherEvent': self.weather_event.id, 'communitySystem': self.community_system.id}) - serializer.is_valid() + if not serializer.is_valid(): + print(serializer.errors) with self.assertRaises(ValueError): serializer.save() def test_create_context_works_with_request(self): """Ensure the Serializer works if the context does have a request""" - serializer = OrganizationRiskSerializer(data={'weatherEvent': self.weather_event.id, + serializer = OrganizationRiskCreateSerializer(data={'weatherEvent': self.weather_event.id, 'communitySystem': self.community_system.id}, - context={'request': self.request}) - serializer.is_valid() + context={'request': self.request}) + if not serializer.is_valid(): + print(serializer.errors) # No exception serializer.save() diff --git a/src/django/planit_data/views.py b/src/django/planit_data/views.py index 2e4babf9b..30b59ea81 100644 --- a/src/django/planit_data/views.py +++ b/src/django/planit_data/views.py @@ -4,11 +4,21 @@ from rest_framework.views import APIView from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet -from planit_data.models import Concern, OrganizationRisk, WeatherEventRank +from planit_data.models import ( + CommunitySystem, + Concern, + OrganizationRisk, + WeatherEvent, + WeatherEventRank, +) + from planit_data.serializers import ( ConcernSerializer, + CommunitySystemSerializer, + OrganizationRiskCreateSerializer, OrganizationRiskSerializer, WeatherEventRankSerializer, + WeatherEventSerializer, ) @@ -18,17 +28,35 @@ class ConcernViewSet(ReadOnlyModelViewSet): permission_classes = [IsAuthenticated] +class CommunitySystemViewSet(ReadOnlyModelViewSet): + queryset = CommunitySystem.objects.all() + serializer_class = CommunitySystemSerializer + permission_classes = [IsAuthenticated] + pagination_class = None + + class OrganizationRiskView(ModelViewSet): model_class = OrganizationRisk permission_classes = [IsAuthenticated] - serializer_class = OrganizationRiskSerializer pagination_class = None + def get_serializer_class(self): + if self.action == 'update' or self.action == 'create' or self.action == 'partial_update': + return OrganizationRiskCreateSerializer + return OrganizationRiskSerializer + def get_queryset(self): org_id = self.request.user.primary_organization_id return OrganizationRisk.objects.filter(organization_id=org_id) +class WeatherEventViewSet(ReadOnlyModelViewSet): + queryset = WeatherEvent.objects.all() + permission_classes = [IsAuthenticated] + serializer_class = WeatherEventSerializer + pagination_class = None + + class WeatherEventRankView(APIView): model_class = WeatherEventRank