Skip to content
This repository was archived by the owner on Aug 28, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/angular/planit/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -80,6 +81,7 @@ const appRoutes: Routes = [
apiHttpProvider,
AuthService,
CacheService,
RiskService,
UserService,
WeatherEventService,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ <h2>Your city's risks</h2>
<tr *ngFor="let risk of risks">
<th><va-adaptive-need-box
[adaptiveCapacity]="risk.adaptiveCapacity"
[potentialImpact]="risk.potentialImpact"></va-adaptive-need-box></th>
[potentialImpact]="risk.impactMagnitude"></va-adaptive-need-box></th>
<th>
<va-risk-popover [risk]="risk"></va-risk-popover>
</th>
<th>{{ risk.potentialImpact }}</th>
<th>{{ risk.adaptiveCapacity }}</th>
<th>{{ risk.impactMagnitude }}</th>
<th>{{ risk.adaptiveCapacityDescription }}</th>
<th><button class="button">Assess</button></th>
<th>
<div class="btn-group" dropdown>
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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;
});
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
<p>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.
</p>
<h4>Related Indicators</h4>
<p *ngFor="let indicator of risk.indicators">
<a href="{{ indicator.url }}">{{ indicator.name }}</a>
<p *ngFor="let indicator of risk.weatherEvent.indicators">
<a href="#">{{ indicator }}</a>
</p>
</ng-template>

<div class="va-risk" [popover]="riskPopoverTemplate"
container="body"
outsideClick="true"
popoverTitle="{{ risk.name }}"
placement="right">{{ risk.name }}
popoverTitle="{{ risk.weatherEvent.name }} on {{ risk.communitySystem.name }}"
placement="right">{{ risk.weatherEvent.name }} on {{ risk.communitySystem.name }}
</div>
22 changes: 22 additions & 0 deletions src/angular/planit/src/app/core/services/risk.service.ts
Original file line number Diff line number Diff line change
@@ -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<Risk[]> {
const url = `${environment.apiUrl}/api/risks/`;
return this.apiHttp.get(url).map(resp => {
const vals = resp.json() || [];
return vals.map(r => r as Risk);
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export class IdentifyStepComponent extends WizardStepComponent<Risk> implements

fromData(risk: Risk): IdentifyStepFormModel {
return {
hazard: risk.hazard,
communitySystem: risk.communitySystem
hazard: risk.impactDescription,
communitySystem: risk.communitySystem.name
};
}

Expand All @@ -60,8 +60,8 @@ export class IdentifyStepComponent extends WizardStepComponent<Risk> 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;
}
}
1 change: 1 addition & 0 deletions src/angular/planit/src/app/shared/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class CommunitySystem {
name: string;
}
1 change: 1 addition & 0 deletions src/angular/planit/src/app/shared/models/concern.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export class Concern {
id: number;
indicator: string;
isRelative: boolean;
tagline: string;
Expand Down
21 changes: 15 additions & 6 deletions src/angular/planit/src/app/shared/models/risk.model.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Indicator } from 'climate-change-components';

export class WeatherEvent {
name: string;
coastalOnly: boolean;
concern?: Concern;
indicators?: string[];
displayClass: string;
Expand Down
4 changes: 2 additions & 2 deletions src/django/planit/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<route>.*)$',
Expand Down
85 changes: 55 additions & 30 deletions src/django/planit_data/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid making pointless API calls when using WeatherEventSerializer as a part of OrganizationRiskSerializer, consider splitting it into two classes like so:

class WeatherEventSerializer(serializers.ModelSerializer):
    coastalOnly = serializers.BooleanField(source='coastal_only')
    # Other fields

    class Meta:
        model = WeatherEvent
        fields = ('name', 'coastalOnly', 'indicators', 'displayClass')

class WeatherEventWithConcernSerializer(WeatherEventSerializer):
    concern = ConcernSerializer()

    class Meta:
        fields = ('name', 'coastalOnly', 'concern', 'indicators', 'displayClass')

Then you can continue to use WeatherEventSerializer in OrganizationRiskSerializer and use WeatherEventWithConcernSerializer in the WeatherEventRankSerializer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying we should exclude concerns from the weather events serialized with the organization risks?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes



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,
Expand All @@ -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
Expand All @@ -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
Expand Down
20 changes: 13 additions & 7 deletions src/django/planit_data/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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')
Expand All @@ -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()
Loading