Skip to content
Open
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
99 changes: 47 additions & 52 deletions src/webapp/apps/events/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class NotesListSerializer(fields.ListField):
child = NoteSerializer()

def get_attribute(self, instance):
notes_map = self.context.get('notes_map')
if notes_map is not None:
return notes_map.get(instance.id, [])

return Note.current.filter(event=instance.id).order_by('-created')


Expand Down Expand Up @@ -85,8 +89,14 @@ def validate_notes(self, notes):
return notes

def get_is_closure(self, obj):
ids = [impact['id'] for impact in obj.impacts]
return TrafficImpact.objects.filter(id__in=ids, closed=True).count() > 0
closed_ids = self.context.get('closed_impact_ids')

if closed_ids is None:
ids = [impact['id'] for impact in (obj.impacts or []) if isinstance(impact, dict)]
return TrafficImpact.objects.filter(id__in=ids, closed=True).exists()

event_impact_ids = [impact['id'] for impact in (obj.impacts or []) if isinstance(impact, dict)]
return any(impact_id in closed_ids for impact_id in event_impact_ids)

def get_last_inactivated(self, obj):
return obj.meta.get('last_inactivated')
Expand Down Expand Up @@ -136,27 +146,22 @@ def to_internal_value(self, data):

def to_representation(self, instance):
obj = super().to_representation(instance)

# have to remove meta here to avoid sending it, rather than as an
# excluded field, because otherwise meta doesn't get populated on the
# way in through key movement and to_internal_value()
if 'meta' in obj:
del obj['meta']

if obj.get('type') == 'ROAD_CONDITION':
if instance.segment:
obj['location']['start']['name'] = instance.segment.name

obj['polygon'] = instance.geometry.buffer_with_style(.01, end_cap_style=2, join_style=2).coords[0]

# Only serialize segment for road conditions
if obj.get('type') != 'ROAD_CONDITION' and 'segment' in obj:
del obj['segment']

# Only serialize chainup for chainups
if obj.get('type') != 'CHAIN_UP' and 'chainup' in obj:
del obj['chainup']

conditions = obj.get('conditions') or []

id_to_label = self.context.get('condition_labels')

if id_to_label is None:
condition_ids = [c if isinstance(c, int) else c.get('id') for c in conditions]
id_to_label = dict(Condition.objects.filter(id__in=condition_ids).values_list('id', 'label'))

normalized = []
for condition in conditions:
cid = condition if isinstance(condition, int) else condition.get('id')
label = id_to_label.get(cid)
if label:
normalized.append({'id': cid, 'label': label})

obj['conditions'] = normalized
return obj

def is_automatically_approved(self, data):
Expand Down Expand Up @@ -320,37 +325,27 @@ def get_first_reported(self, obj):
}

def to_representation(self, instance):
"""Override to normalize conditions to {id, label} objects."""
obj = super().to_representation(instance)

conditions = obj.get('conditions') or []
if conditions:
condition_ids = [
condition for condition in conditions
if isinstance(condition, int)
]
condition_ids.extend([
condition.get('id') for condition in conditions
if isinstance(condition, dict) and isinstance(condition.get('id'), int)
])
id_to_label = dict(
Condition.objects.filter(id__in=condition_ids).values_list('id', 'label')
)
normalized = []
for condition in conditions:
if isinstance(condition, int):
label = id_to_label.get(condition)
if label is not None:
normalized.append({'id': condition, 'label': label})
elif isinstance(condition, dict):
condition_id = condition.get('id')
if isinstance(condition_id, int):
normalized.append({
'id': condition_id,
'label': condition.get('label') or id_to_label.get(condition_id),
})
obj['conditions'] = normalized


id_to_label = self.context.get('condition_labels', {})

normalized = []
for condition in conditions:
c_id = None
c_label = None

if isinstance(condition, int):
c_id = condition
c_label = id_to_label.get(c_id)
elif isinstance(condition, dict):
c_id = condition.get('id')
c_label = condition.get('label') or id_to_label.get(c_id)

if c_id and c_label:
normalized.append({'id': c_id, 'label': c_label})

obj['conditions'] = normalized
return obj

class Meta:
Expand Down
89 changes: 80 additions & 9 deletions src/webapp/apps/events/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,78 @@


class Events(viewsets.ModelViewSet):
queryset = Event.last.all()
queryset = Event.last.all().select_related(
'user',
'segment__route',
'chainup__route',
'chainup__area',
'service_area',
).prefetch_related(
'service_area__parent',
'chainup__area__parent',
)
serializer_class = EventSerializer
lookup_field = 'id'
permission_classes = [AllowAny]

def get_serializer_context(self):
context = super().get_serializer_context()

if self.action in ['list', 'retrieve', 'history', 'diffs', 'toggle', 'clear', 'confirm']:
try:
if self.detail:
event_ids = [self.kwargs.get('id')]
else:
queryset = self.filter_queryset(self.get_queryset())
event_ids = list(queryset.values_list('id', flat=True))

first_versions = Event.objects.filter(
id__in=event_ids,
approved=True
).order_by('id', 'version').distinct('id').select_related('user')

context['first_reported_map'] = {
v.id: {'user': v.user, 'created': v.created}
for v in first_versions
}

notes_qs = Note.current.filter(event__in=event_ids).order_by('-created')
notes_map = {}
for note in notes_qs:
if note.event not in notes_map:
notes_map[note.event] = []
notes_map[note.event].append(note)
context['notes_map'] = notes_map

except Exception:
context['first_reported_map'] = {}
context['notes_map'] = {}

context['closed_impact_ids'] = set(
TrafficImpact.objects.filter(closed=True).values_list('id', flat=True)
)

context['condition_labels'] = dict(
Condition.objects.values_list('id', 'label')
)

return context

@action(detail=True)
def history(self, request, id):
event = self.get_object()
queryset = Event.objects.filter(id=event.id)
serializer = EventHistorySerializer(queryset, many=True)
serializer = EventHistorySerializer(queryset, many=True, context=self.get_serializer_context())
return Response(serializer.data)

@action(detail=True)
def diffs(self, request, id):
event = self.get_object()
queryset = Event.objects.filter(id=event.id)
serializer = EventDiffSerializer(queryset, many=True)
serializer = EventDiffSerializer(queryset, many=True, context=self.get_serializer_context())
return Response(serializer.data)

def partial_update(self, request, *args, **kwargs):
# need to do this here, otherwise serializer doesn't pick up user via
# default field value
return super().partial_update(request, *args, **kwargs)


Expand All @@ -64,7 +114,13 @@ def validate_allowed_segments(user, segPks):


class RoadConditions(Events):
queryset = Event.current.filter(event_type=EventType.ROAD_CONDITION, from_bulk=True)
queryset = Event.current.filter(event_type=EventType.ROAD_CONDITION, from_bulk=True).select_related(
'user',
'segment__route',
'service_area',
).prefetch_related(
'service_area__parent',
)
serializer_class = RcSerializer

@action(detail=False, methods=['post'], url_path='clear')
Expand All @@ -84,7 +140,7 @@ def clear_rcs(self, request):
for event in existing_events:
event.status = 'Inactive'
event.save()
cleared_events.append(RcSerializer(event).data)
cleared_events.append(RcSerializer(event, context=self.get_serializer_context()).data)

return Response({'status': status.HTTP_202_ACCEPTED, 'data': cleared_events}, status=status.HTTP_202_ACCEPTED)

Expand Down Expand Up @@ -180,7 +236,13 @@ def bulk_update_rcs(self, request):


class ChainUps(Events):
queryset = Event.current.filter(event_type=EventType.CHAIN_UP, from_bulk=True)
queryset = Event.current.filter(event_type=EventType.CHAIN_UP, from_bulk=True).select_related(
'user',
'chainup__route',
'chainup__area',
).prefetch_related(
'chainup__area__parent',
)
serializer_class = ChainUpEventSerializer
permission_classes = [Approver]

Expand Down Expand Up @@ -266,7 +328,16 @@ def reconfirm_chainups(self, request):


class Pending(viewsets.ModelViewSet):
queryset = Event.pending.all()
queryset = Event.pending.all().select_related(
'user',
'segment__route',
'chainup__route',
'chainup__area',
).prefetch_related(
'service_area',
'service_area__parent',
'chainup__area__parent',
)
serializer_class = PendingSerializer
lookup_field = 'id'
permission_classes = [IsAuthenticated]
Expand Down
6 changes: 3 additions & 3 deletions src/webapp/apps/organizations/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)

class OrganizationAPIView(ModelViewSet):
queryset = Organization.objects.all().order_by('name')
queryset = Organization.objects.all().prefetch_related('users', 'service_areas').order_by('name')
serializer_class = OrganizationSerializer
permission_classes = [permissions.IsAdminUser]

Expand Down Expand Up @@ -64,7 +64,7 @@ class ServiceAreaAPIView(ModelViewSet):
permission_classes = [permissions.IsAuthenticated]

def get_queryset(self):
return ServiceArea.objects.exclude(parent=None).order_by('sortingOrder')
return ServiceArea.objects.exclude(parent=None).select_related('parent').order_by('sortingOrder')

@action(detail=True)
def boundary(self, request, pk):
Expand All @@ -83,7 +83,7 @@ class DistrictAPIView(ModelViewSet):
permission_classes = [permissions.IsAuthenticated]

def get_queryset(self):
return ServiceArea.objects.filter(parent=None).order_by('sortingOrder')
return ServiceArea.objects.filter(parent=None).prefetch_related('children').order_by('sortingOrder')

@action(detail=True)
def boundary(self, request, pk):
Expand Down
12 changes: 10 additions & 2 deletions src/webapp/apps/segments/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ class Meta:
exclude = ["geometry"]

def get_area(self, obj):
sa = ServiceArea.objects.filter(segments__contains=int(obj.id)).exclude(parent=None).first()
return sa.id if sa else None
if 'segment_to_area' not in self.context:
areas = ServiceArea.objects.exclude(parent=None).order_by('id').values_list('id', 'segments')
mapping = {}
for area_id, segment_ids in areas:
if segment_ids:
for sid in segment_ids:
mapping.setdefault(str(sid), area_id)
self.context['segment_to_area'] = mapping

return self.context['segment_to_area'].get(str(obj.id))


class ChainUpSerializer(serializers.ModelSerializer):
Expand Down
13 changes: 11 additions & 2 deletions src/webapp/apps/users/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class RIDEUserSerializer(serializers.ModelSerializer):

class Meta:
model = RIDEUser
fields = "__all__"
exclude = ["password", "user_permissions", "groups"]

def _get_social_account(self, obj):
# prefetch_related makes .all() use the cache, .first() does not
Expand All @@ -33,7 +33,16 @@ def get_social_provider(self, obj):
return social_account.provider

def get_is_approver(self, obj):
return obj.is_approver
if obj.is_superuser:
return True
for perm in obj.user_permissions.all():
if perm.codename == 'approve_ride_events' and perm.content_type.app_label == 'users':
return True
for group in obj.groups.all():
for perm in group.permissions.all():
if perm.codename == 'approve_ride_events' and perm.content_type.app_label == 'users':
return True
return False


class RIDEGroupSerializer(serializers.ModelSerializer):
Expand Down
3 changes: 2 additions & 1 deletion src/webapp/apps/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class RIDEUserAPIView(ModelViewSet):
queryset = RIDEUser.objects.prefetch_related(
'socialaccount_set',
'organizations',
'user_permissions',
'user_permissions__content_type',
'groups__permissions__content_type',
).all()
serializer_class = RIDEUserSerializer
permission_classes = [permissions.IsAdminUser]
Expand Down
Loading