Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bulk meter reading error handling for duplicate date-pairs #4467

Merged
merged 11 commits into from
Jan 12, 2024
Merged
13 changes: 12 additions & 1 deletion seed/serializers/meter_readings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Tuple

import dateutil.parser
from django.core.exceptions import ValidationError
from django.db import connection
from django.utils.timezone import make_aware
from psycopg2.extras import execute_values
Expand Down Expand Up @@ -54,6 +55,17 @@ def create(self, validated_data) -> list[MeterReading]:

return updated_readings

def validate(self, data):
# duplicate start and end date pairs will cause sql errors
date_pairs = set()
for datum in data:
date_pair = (datum.get('start_time'), datum.get('end_time'))
if date_pair in date_pairs:
raise ValidationError('Error: Each reading must have a unique combination of start_time end end_time.')
date_pairs.add(date_pair)

return data


class MeterReadingSerializer(serializers.ModelSerializer):
class Meta:
Expand Down Expand Up @@ -95,7 +107,6 @@ def create(self, validated_data) -> MeterReading:

# Convert tuple to MeterReading for response
updated_reading = MeterReading(**{field: result[i] for i, field in enumerate(meter_fields)})

return updated_reading

def to_representation(self, obj):
Expand Down
41 changes: 41 additions & 0 deletions seed/tests/test_meter_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,47 @@ def test_bulk_import(self):
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 3)

def test_bulk_import_duplicate_dates(self):
"""Bulk Meter Readings with duplicate start_time and end_time pairs will be rejected to avoid sql errors"""

property_view = self.property_view_factory.get_property_view()
url = reverse('api:v3:property-meters-list', kwargs={'property_pk': property_view.id})

payload = {
'type': 'Electric',
'source': 'Manual Entry',
'source_id': '1234567890',
}

response = self.client.post(url, data=json.dumps(payload), content_type='application/json')
meter_pk = response.json()['id']

url = reverse('api:v3:property-meter-readings-list', kwargs={'property_pk': property_view.id, 'meter_pk': meter_pk})

# prepare the data in bulk format
reading1 = {
"start_time": "2022-01-05 05:00:00",
"end_time": "2022-01-05 06:00:00",
"reading": 10,
"source_unit": "Wh (Watt-hours)",
# conversion factor is required and is the conversion from the source unit to kBTU (1 Wh = 0.00341 kBtu)
"conversion_factor": 0.00341,
}
reading2 = dict(reading1)
reading2["end_time"] = "2022-01-05 07:00:00"
payload = [reading1, reading2]

response = self.client.post(url, data=json.dumps(payload), content_type='application/json')
self.assertEqual(response.status_code, 201)
self.assertEqual(response.json()[0]['reading'], 10)
self.assertEqual(response.json()[1]['reading'], 10)

# Duplicate start and end times will be rejected
payload = [reading1, reading1]
response = self.client.post(url, data=json.dumps(payload), content_type='application/json')
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()['non_field_errors'], ['Error: Each reading must have a unique combination of start_time end end_time.'])

def test_delete_meter_readings(self):
# would be nice nice to make a factory out of the meter / meter reading requests
property_view = self.property_view_factory.get_property_view()
Expand Down