From 7d1a21166a1443f0bd803f751f268f80eb91dead Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Mon, 19 Jun 2023 16:28:44 -0300 Subject: [PATCH 01/21] Create equipment inventory and hide old maintenance reports. --- api/templates/base.html | 22 +- api/wx/admin.py | 30 +- api/wx/migrations/0023_auto_20230613_2140.py | 44 ++ ...24_equipment_equipmenttype_manufacturer.py | 61 ++ api/wx/models.py | 32 ++ .../equipment_inventory.html | 524 ++++++++++++++++++ api/wx/urls.py | 5 + api/wx/views.py | 154 +++++ 8 files changed, 858 insertions(+), 14 deletions(-) create mode 100644 api/wx/migrations/0023_auto_20230613_2140.py create mode 100644 api/wx/migrations/0024_equipment_equipmenttype_manufacturer.py create mode 100644 api/wx/templates/wx/maintenance_reports/equipment_inventory.html diff --git a/api/templates/base.html b/api/templates/base.html index 19c91ce..c4047dc 100644 --- a/api/templates/base.html +++ b/api/templates/base.html @@ -17,7 +17,7 @@ /> {% if request.user.is_staff or request.user.is_superuser %} -
  • - buildMAINTENANCE +
  • +
    buildMAINTENANCE
    +
  • - {% endif %} + {% endif %} {% if request.user.is_staff or request.user.is_superuser %} @@ -746,6 +754,7 @@

    SURFACE

    const localGroups = [ 'stations-group', + 'maintenance-group', 'data-group', 'products-group', 'quality-control-group' @@ -764,7 +773,6 @@

    SURFACE

    'wave-data', 'product-compare', 'yearly-average', - 'maintenance-group', 'spatial-analysis', 'extremes-means', 'quality-control', @@ -772,7 +780,9 @@

    SURFACE

    'data-inventory', 'range-threshold', 'step-threshold', - 'persist-threshold' + 'persist-threshold', + 'equipement-inventory', + 'maintenance-reports' ] const menuList = "{{USER_PERMISSIONS|safe}}"; diff --git a/api/wx/admin.py b/api/wx/admin.py index b637257..940b1b0 100644 --- a/api/wx/admin.py +++ b/api/wx/admin.py @@ -315,19 +315,18 @@ def backup_duration(self, obj): return obj.finished_at - obj.started_at return None - @admin.register(models.ElementDecoder) class ElementDecoder(admin.ModelAdmin): search_fields = ("element_name", "variable__name", "decoder__name") list_display = ("element_name", "variable_id", "decoder") -admin.site.register(models.StationComponent) -class StationProfileComponentAdmin(admin.ModelAdmin): - list_display = ('name', 'description') +# admin.site.register(models.StationComponent) +# class StationComponentAdmin(admin.ModelAdmin): +# list_display = ('name', 'description') -@admin.register(models.StationProfileComponent) -class StationProfileComponentAdmin(admin.ModelAdmin): - list_display = ('profile', 'presentation_order', 'station_component') +# @admin.register(models.StationProfileComponent) +# class StationProfileComponentAdmin(admin.ModelAdmin): +# list_display = ('profile', 'presentation_order', 'station_component') @admin.register(models.VisitType) class VisitTypeAdmin(admin.ModelAdmin): @@ -335,4 +334,19 @@ class VisitTypeAdmin(admin.ModelAdmin): @admin.register(models.Technician) class TechnicianAdmin(admin.ModelAdmin): - list_display = ("name",) \ No newline at end of file + list_display = ("name",) + + +@admin.register(models.Manufacturer) +class ManufacturerAdmin(admin.ModelAdmin): + list_display = ("name",) + +@admin.register(models.EquipmentType) +class EquipmentTypeAdmin(admin.ModelAdmin): + list_display = ("name",) + +@admin.register(models.Equipment) +class EquipmentAdmin(admin.ModelAdmin): + list_display = ("equipment_type", "manufacturer", "model", "serial_number", "acquired", "first_deployed") + + diff --git a/api/wx/migrations/0023_auto_20230613_2140.py b/api/wx/migrations/0023_auto_20230613_2140.py new file mode 100644 index 0000000..c5b2bcd --- /dev/null +++ b/api/wx/migrations/0023_auto_20230613_2140.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.13 on 2023-06-13 21:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wx', '0022_maintenancereport_maintenancereportstationcomponent_stationcomponent_stationprofilecomponent_technic'), + ] + + operations = [ + migrations.AlterField( + model_name='station', + name='begin_date', + field=models.DateTimeField(null=True), + ), + migrations.AlterField( + model_name='station', + name='communication_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='wx.stationcommunication'), + ), + migrations.AlterField( + model_name='station', + name='country', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='wx.country'), + ), + migrations.AlterField( + model_name='station', + name='elevation', + field=models.FloatField(null=True), + ), + migrations.AlterField( + model_name='station', + name='region', + field=models.CharField(max_length=256, null=True), + ), + migrations.AlterField( + model_name='station', + name='watershed', + field=models.CharField(max_length=256, null=True), + ), + ] diff --git a/api/wx/migrations/0024_equipment_equipmenttype_manufacturer.py b/api/wx/migrations/0024_equipment_equipmenttype_manufacturer.py new file mode 100644 index 0000000..2db103a --- /dev/null +++ b/api/wx/migrations/0024_equipment_equipmenttype_manufacturer.py @@ -0,0 +1,61 @@ +# Generated by Django 3.2.13 on 2023-06-19 19:18 + +import ckeditor.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wx', '0023_auto_20230613_2140'), + ] + + operations = [ + migrations.CreateModel( + name='EquipmentType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=64)), + ('description', models.CharField(max_length=256)), + ('report_template', ckeditor.fields.RichTextField(blank=True, null=True)), + ], + options={ + 'verbose_name': 'equipment type', + 'verbose_name_plural': 'equipment types', + }, + ), + migrations.CreateModel( + name='Manufacturer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=64)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Equipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('model', models.CharField(max_length=64)), + ('serial_number', models.CharField(max_length=64)), + ('acquired', models.DateField()), + ('first_deployed', models.DateField(blank=True, null=True)), + ('equipment_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.equipmenttype')), + ('manufacturer', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.manufacturer')), + ], + options={ + 'verbose_name': 'equipment', + 'verbose_name_plural': 'equipments', + 'unique_together': {('equipment_type', 'serial_number')}, + }, + ), + ] diff --git a/api/wx/models.py b/api/wx/models.py index 11bbb9b..2d16068 100644 --- a/api/wx/models.py +++ b/api/wx/models.py @@ -1375,3 +1375,35 @@ class ComponentClassification(models.TextChoices): class Meta: unique_together = ('maintenance_report', 'station_component') +class Manufacturer(BaseModel): + name = models.CharField(max_length=64) + + def __str__(self): + return self.name + +class EquipmentType(BaseModel): + name = models.CharField(max_length=64) + description = models.CharField(max_length=256) + # https://stackoverflow.com/questions/7946861/how-can-i-add-a-wsywyg-editor-to-django-admin + report_template = RichTextField(blank=True, null=True) + + class Meta: + verbose_name = "equipment type" + verbose_name_plural = "equipment types" + + def __str__(self): + return self.name + + +class Equipment(BaseModel): + equipment_type = models.ForeignKey(EquipmentType, on_delete=models.DO_NOTHING) + manufacturer = models.ForeignKey(Manufacturer, on_delete=models.DO_NOTHING) + model = models.CharField(max_length=64) + serial_number = models.CharField(max_length=64) + acquired = models.DateField() + first_deployed = models.DateField(blank=True, null=True) + + class Meta: + unique_together = ("equipment_type", "serial_number") + verbose_name = "equipment" + verbose_name_plural = "equipments" \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html new file mode 100644 index 0000000..3d087b2 --- /dev/null +++ b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html @@ -0,0 +1,524 @@ +{% extends "base.html" %} {% block content %} {% load static %} +
    +
    + + + + [[ request_error_message ]] + + + + + + + + + + Creating a new equipment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create Equipment + + + Cancel + + + + + + + + + Creating a new equipment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create Equipment + + + Cancel + + + + + + + + + Do you want to delete this equipment? + + + + Equipment Type: [[equipment.equipment_type]] + + + Manufacturer: [[equipment.manufacturer]] + + + Model: [[equipment.model]] + + + Serial Number: [[equipment.serial_number]] + + + + + Delete Equipment + + + Cancel + + + + + + + +
    + Equipment Inventory
    +
    +
    + + + + + + + + {% if perms.wx.add_equipment %} + + New Equipment + + {% endif %} + + + + + + + + + + +
    +
    +
    +
    +{% endblock %} {% block localjavascript %} + + +{% endblock %} \ No newline at end of file diff --git a/api/wx/urls.py b/api/wx/urls.py index 5fa32cf..5779b41 100644 --- a/api/wx/urls.py +++ b/api/wx/urls.py @@ -129,6 +129,11 @@ path('wx/stations/stations_monitoring/get/', views.get_stationsmonitoring_map_data), path('wx/stations/stations_monitoring/get//', views.get_stationsmonitoring_station_data), path('wx/stations/stations_monitoring/get///', views.get_stationsmonitoring_chart_data), + path('wx/maintenance_reports/equipment_inventory/', views.get_equipment_inventory, name="equipment-inventory"), + path('wx/maintenance_reports/equipment_inventory/get/', views.get_equipment_inventory_data), + path('wx/maintenance_reports/equipment_inventory/create/', views.create_equipment), + path('wx/maintenance_reports/equipment_inventory/delete/', views.delete_equipment), + path('wx/maintenance_reports/equipment_inventory/update/', views.update_equipment), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/api/wx/views.py b/api/wx/views.py index 1e8bf43..7538aac 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -3984,6 +3984,160 @@ def get_wave_data(request): return JsonResponse(charts) + +from wx.models import Equipment, EquipmentType, Manufacturer +from django.core.serializers import serialize + +@require_http_methods(["GET"]) +def get_equipment_inventory(request): + template = loader.get_template('wx/maintenance_reports/equipment_inventory.html') + context = {} + + return HttpResponse(template.render(context, request)) + +@require_http_methods(["GET"]) +def get_equipment_inventory_data(request): + equipment_types = EquipmentType.objects.all() + manufacturers = Manufacturer.objects.all() + equipments = Equipment.objects.all() + + equipment_list = [] + for equipment in equipments: + equipment_dict = { + 'equipment_id': equipment.id, + 'equipment_type': equipment_types.get(id=equipment.equipment_type_id).name, + 'equipment_type_id': equipment.equipment_type_id, + 'manufacturer': manufacturers.get(id=equipment.manufacturer_id).name, + 'manufacturer_id': equipment.manufacturer_id, + 'model': equipment.model, + 'serial_number': equipment.serial_number, + 'acquired': equipment.acquired, + 'first_deployed': equipment.first_deployed, + 'last_deployed': '---', + 'location': 'Office', + 'condition': '---', + } + equipment_list.append(equipment_dict) + + response = { + 'equipments': equipment_list, + 'equipment_types': list(equipment_types.values()), + 'manufacturers': list(manufacturers.values()) + } + return JsonResponse(response, status=status.HTTP_200_OK) + +@require_http_methods(["POST"]) +def create_equipment(request): + equipment_type_id = request.GET.get('equipment_type', None) + manufacturer_id = request.GET.get('manufacturer', None) + model = request.GET.get('model', None) + serial_number = request.GET.get('serial_number', None) + acquired = request.GET.get('acquired', None) + first_deployed = request.GET.get('first_deployed', None) + + equipment_type = EquipmentType.objects.get(id=equipment_type_id) + manufacturer = Manufacturer.objects.get(id=manufacturer_id) + + try: + equipment = Equipment.objects.get( + equipment_type=equipment_type, + serial_number = serial_number, + ) + + message = 'Already exist an equipment of equipment type ' + message += equipment_type.name + message += ' and serial number ' + message += equipment.serial_number + + response = {'message': message} + + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + except ObjectDoesNotExist: + now = datetime.datetime.now() + + equipment = Equipment.objects.create( + created_at = now, + updated_at = now, + equipment_type = equipment_type, + manufacturer = manufacturer, + model = model, + serial_number = serial_number, + acquired = acquired, + first_deployed = first_deployed, + ) + + response = {'equipment_id': equipment.id} + + return JsonResponse(response, status=status.HTTP_200_OK) + +@require_http_methods(["POST"]) +def update_equipment(request): + equipment_id = request.GET.get('equipment_id', None) + equipment_type_id = request.GET.get('equipment_type', None) + manufacturer_id = request.GET.get('manufacturer', None) + model = request.GET.get('model', None) + serial_number = request.GET.get('serial_number', None) + acquired = request.GET.get('acquired', None) + first_deployed = request.GET.get('first_deployed', None) + + print(equipment_id) + + equipment_type = EquipmentType.objects.get(id=equipment_type_id) + manufacturer = Manufacturer.objects.get(id=manufacturer_id) + + try: + equipment = Equipment.objects.get( + equipment_type=equipment_type, + serial_number = serial_number, + ) + + message = 'Already exist an equipment of equipment type ' + message += equipment_type.name + message += ' and serial number ' + message += equipment.serial_number + + response = {'message': message} + + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + except ObjectDoesNotExist: + try: + equipment = Equipment.objects.get(id=equipment_id) + + now = datetime.datetime.now() + + equipment.updated_at = now + equipment.equipment_type = equipment_type + equipment.manufacturer = manufacturer + equipment.model = model + equipment.serial_number = serial_number + equipment.acquired = acquired + equipment.first_deployed = first_deployed + equipment.save() + + except ObjectDoesNotExist: + message = "Object not found" + response = {'message': message} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def delete_equipment(request): + equipment_id = request.GET.get('equipment_id', None) + try: + equipment = Equipment.objects.get(id=equipment_id) + equipment.delete() + except ObjectDoesNotExist: + pass + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + @require_http_methods(["GET"]) def get_maintenance_reports(request): # Maintenance report page template = loader.get_template('wx/maintenance_reports/maintenance_reports.html') From d3da8bee71b4fb97f824e100af5a600a3f17f2c0 Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Tue, 20 Jun 2023 18:08:40 -0300 Subject: [PATCH 02/21] Adding new fields --- api/wx/admin.py | 8 +- ...ipmenttype_foundingsource_manufacturer.py} | 22 +- api/wx/models.py | 10 +- .../equipment_inventory.html | 296 +++++++++--------- api/wx/views.py | 89 ++++-- 5 files changed, 238 insertions(+), 187 deletions(-) rename api/wx/migrations/{0024_equipment_equipmenttype_manufacturer.py => 0024_equipment_equipmenttype_foundingsource_manufacturer.py} (69%) diff --git a/api/wx/admin.py b/api/wx/admin.py index 940b1b0..9c96d8b 100644 --- a/api/wx/admin.py +++ b/api/wx/admin.py @@ -343,10 +343,14 @@ class ManufacturerAdmin(admin.ModelAdmin): @admin.register(models.EquipmentType) class EquipmentTypeAdmin(admin.ModelAdmin): - list_display = ("name",) + list_display = ("name",) + +@admin.register(models.FoundingSource) +class FoundingSourceAdmin(admin.ModelAdmin): + list_display = ("name",) @admin.register(models.Equipment) class EquipmentAdmin(admin.ModelAdmin): - list_display = ("equipment_type", "manufacturer", "model", "serial_number", "acquired", "first_deployed") + list_display = ("equipment_type", "manufacturer", "model", "serial_number", "acquisition_date", "first_deploy_date", "last_calibration_date") diff --git a/api/wx/migrations/0024_equipment_equipmenttype_manufacturer.py b/api/wx/migrations/0024_equipment_equipmenttype_foundingsource_manufacturer.py similarity index 69% rename from api/wx/migrations/0024_equipment_equipmenttype_manufacturer.py rename to api/wx/migrations/0024_equipment_equipmenttype_foundingsource_manufacturer.py index 2db103a..3f37d2f 100644 --- a/api/wx/migrations/0024_equipment_equipmenttype_manufacturer.py +++ b/api/wx/migrations/0024_equipment_equipmenttype_foundingsource_manufacturer.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.13 on 2023-06-19 19:18 +# Generated by Django 3.2.13 on 2023-06-20 14:33 import ckeditor.fields from django.db import migrations, models @@ -27,6 +27,18 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'equipment types', }, ), + migrations.CreateModel( + name='FoundingSource', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=128)), + ], + options={ + 'abstract': False, + }, + ), migrations.CreateModel( name='Manufacturer', fields=[ @@ -47,9 +59,13 @@ class Migration(migrations.Migration): ('updated_at', models.DateTimeField(auto_now=True)), ('model', models.CharField(max_length=64)), ('serial_number', models.CharField(max_length=64)), - ('acquired', models.DateField()), - ('first_deployed', models.DateField(blank=True, null=True)), + ('acquisition_date', models.DateField()), + ('first_deploy_date', models.DateField(blank=True, null=True)), + ('last_calibration_date', models.DateField(blank=True, null=True)), + ('next_calibration_date', models.DateField(blank=True, null=True)), + ('decomission_date', models.DateField(blank=True, null=True)), ('equipment_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.equipmenttype')), + ('founding_source', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.foundingsource')), ('manufacturer', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.manufacturer')), ], options={ diff --git a/api/wx/models.py b/api/wx/models.py index 2d16068..922485d 100644 --- a/api/wx/models.py +++ b/api/wx/models.py @@ -1394,14 +1394,20 @@ class Meta: def __str__(self): return self.name +class FoundingSource(BaseModel): + name = models.CharField(max_length=128) class Equipment(BaseModel): equipment_type = models.ForeignKey(EquipmentType, on_delete=models.DO_NOTHING) manufacturer = models.ForeignKey(Manufacturer, on_delete=models.DO_NOTHING) + founding_source = models.ForeignKey(FoundingSource, on_delete=models.DO_NOTHING) model = models.CharField(max_length=64) serial_number = models.CharField(max_length=64) - acquired = models.DateField() - first_deployed = models.DateField(blank=True, null=True) + acquisition_date = models.DateField() + first_deploy_date = models.DateField(blank=True, null=True) + last_calibration_date = models.DateField(blank=True, null=True) + next_calibration_date = models.DateField(blank=True, null=True) + decomission_date = models.DateField(blank=True, null=True) class Meta: unique_together = ("equipment_type", "serial_number") diff --git a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html index 3d087b2..2a388d5 100644 --- a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html +++ b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html @@ -14,11 +14,15 @@ > - + - + Creating a new equipment + + Updating equipment + + @@ -49,7 +53,8 @@ autocomplete="off" > - + + - - + + + + + + - + + + + - - - - - - Create Equipment - - - Cancel - - - - - - - - - Creating a new equipment - - - - - - - - - - - - - - + + + + + - + - + - - - + + + + - + Create Equipment + + Update Equipment + Cancel @@ -312,7 +287,7 @@ {% if perms.wx.add_equipment %} - + New Equipment {% endif %} @@ -331,7 +306,7 @@ {% if perms.wx.change_equipment %} Update Equipment @@ -368,28 +343,39 @@ request_error: false, dialog_new: false, dialog_del: false, - dialog_update: false, + equipments: [], equipment_types: [], + manufacturers: [], + founding_sources: [], + is_new_equipment: true, equipment: { equipment_id: null, equipment_type: null, equipment_type_id: null, + founding_source: null, + founding_source_id: null, manufacturer: null, manufacturer_id: null, model: null, serial_number: null, - acquired: moment().format('YYYY-MM-DD'), - acquired_menu: false, - first_deployed: moment().format('YYYY-MM-DD'), - first_deployed_menu: false, + acquisition_date: moment().format('YYYY-MM-DD'), + acquisition_date_menu: false, + first_deploy_date: moment().format('YYYY-MM-DD'), + first_deploy_date_menu: false, + last_calibration_date: moment().format('YYYY-MM-DD'), + last_calibration_date_menu: false, + next_calibration_date: moment().format('YYYY-MM-DD'), + next_calibration_date_menu: false, + decomission_date: moment().format('YYYY-MM-DD'), + decomission_date_menu: false, }, headers: [ { text: 'Equipment Type', align: 'left', value: 'equipment_type' }, { text: 'Manufacturer', align: 'center',value: 'manufacturer' }, { text: 'Model', align: 'center', value: 'model' }, { text: 'Serial Number', align: 'center', value: 'serial_number' }, - { text: 'Acquired', align: 'center', value: 'acquired' }, - { text: 'First Deployed', align: 'center', value: 'first_deployed' }, + { text: 'Acquired', align: 'center', value: 'acquisition_date' }, + { text: 'First Deployed', align: 'center', value: 'first_deploy_date' }, { text: 'Last Deployed', align: 'center', value: 'last_deployed' }, { text: 'Location', align: 'center', value: 'location' }, { text: 'Condition', align: 'center', value: 'condition' }, @@ -399,7 +385,7 @@ }, computed: { shouldClearEquipment() { - return !(this.dialog_new || this.dialog_del || this.dialog_update); + return !(this.dialog_new || this.dialog_del); } }, watch: { @@ -409,14 +395,22 @@ equipment_id: null, equipment_type: null, equipment_type_id: null, + founding_source: null, + founding_source_id: null, manufacturer: null, manufacturer_id: null, model: null, serial_number: null, - acquired: moment().format('YYYY-MM-DD'), - acquired_menu: false, - first_deployed: moment().format('YYYY-MM-DD'), - first_deployed_menu: false, + acquisition_date: moment().format('YYYY-MM-DD'), + acquisition_date_menu: false, + first_deploy_date: moment().format('YYYY-MM-DD'), + first_deploy_date_menu: false, + last_calibration_date: moment().format('YYYY-MM-DD'), + last_calibration_date_menu: false, + next_calibration_date: moment().format('YYYY-MM-DD'), + next_calibration_date_menu: false, + decomission_date: moment().format('YYYY-MM-DD'), + decomission_date_menu: false, }; } } @@ -432,6 +426,7 @@ this.equipments = res.data['equipments']; this.equipment_types = res.data['equipment_types']; this.manufacturers = res.data['manufacturers']; + this.founding_sources = res.data['founding_sources'] }); }, createEquipment(){ @@ -440,17 +435,20 @@ method: 'post', url: `/wx/maintenance_reports/equipment_inventory/create/`, params: { - 'equipment_type': this.equipment.equipment_type, - 'manufacturer': this.equipment.manufacturer, - 'model': this.equipment.model, + 'equipment_type': this.equipment.equipment_type_id, + 'manufacturer': this.equipment.manufacturer_id, + 'founding_source': this.equipment.founding_source_id, + 'model': this.equipment.model, 'serial_number': this.equipment.serial_number, - 'acquired': this.equipment.acquired, - 'first_deployed': this.equipment.first_deployed + 'acquisition_date': this.equipment.acquisition_date, + 'first_deploy_date': this.equipment.first_deploy_date, + 'last_calibration_date': this.equipment.last_calibration_date, + 'next_calibration_date': this.equipment.next_calibration_date, + 'decomission_date': this.equipment.decomission_date, }, }) .then( - res => { - this.getEquipmentData(); + res => { }) .catch((error) => { console.log("error", error.response); @@ -459,21 +457,31 @@ }) .finally(() => { this.loading = false; + this.getEquipmentData(); }); }, - deleteEquipment(equipment_id){ + updateEquipment(){ this.loading = true; axios({ method: 'post', - url: `/wx/maintenance_reports/equipment_inventory/delete/`, + url: `/wx/maintenance_reports/equipment_inventory/update/`, params: { - 'equipment_id': equipment_id, + 'equipment_id': this.equipment.equipment_id, + 'equipment_type': this.equipment.equipment_type_id, + 'manufacturer': this.equipment.manufacturer_id, + 'founding_source': this.equipment.founding_source_id, + 'model': this.equipment.model, + 'serial_number': this.equipment.serial_number, + 'acquisition_date': this.equipment.acquisition_date, + 'first_deploy_date': this.equipment.first_deploy_date, + 'last_calibration_date': this.equipment.last_calibration_date, + 'next_calibration_date': this.equipment.next_calibration_date, + 'decomission_date': this.decomission_date }, - }) + }) .then( - res => { - this.getEquipmentData(); - }) + res => { + }) .catch((error) => { console.log("error", error.response); this.request_error_message = error.response.data.message; @@ -481,21 +489,16 @@ }) .finally(() => { this.loading = false; + this.getEquipmentData(); }); - }, - updateEquipment(){ + }, + deleteEquipment(equipment_id){ this.loading = true; axios({ method: 'post', - url: `/wx/maintenance_reports/equipment_inventory/update/`, + url: `/wx/maintenance_reports/equipment_inventory/delete/`, params: { - 'equipment_id': this.equipment.equipment_id, - 'equipment_type': this.equipment.equipment_type_id, - 'manufacturer': this.equipment.manufacturer_id, - 'model': this.equipment.model, - 'serial_number': this.equipment.serial_number, - 'acquired': this.equipment.acquired, - 'first_deployed': this.equipment.first_deployed + 'equipment_id': equipment_id, }, }) .then( @@ -510,8 +513,7 @@ .finally(() => { this.loading = false; }); - - }, + }, }, created() { axios.defaults.xsrfHeaderName = "X-CSRFToken"; diff --git a/api/wx/views.py b/api/wx/views.py index 7538aac..098c95a 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -3985,9 +3985,10 @@ def get_wave_data(request): return JsonResponse(charts) -from wx.models import Equipment, EquipmentType, Manufacturer +from wx.models import Equipment, EquipmentType, Manufacturer, FoundingSource from django.core.serializers import serialize + @require_http_methods(["GET"]) def get_equipment_inventory(request): template = loader.get_template('wx/maintenance_reports/equipment_inventory.html') @@ -3995,11 +3996,13 @@ def get_equipment_inventory(request): return HttpResponse(template.render(context, request)) + @require_http_methods(["GET"]) def get_equipment_inventory_data(request): equipment_types = EquipmentType.objects.all() manufacturers = Manufacturer.objects.all() equipments = Equipment.objects.all() + founding_sources = FoundingSource.objects.all() equipment_list = [] for equipment in equipments: @@ -4007,12 +4010,17 @@ def get_equipment_inventory_data(request): 'equipment_id': equipment.id, 'equipment_type': equipment_types.get(id=equipment.equipment_type_id).name, 'equipment_type_id': equipment.equipment_type_id, + 'founding_source': founding_sources.get(id=equipment.founding_source_id).name, + 'founding_source_id': equipment.founding_source_id, 'manufacturer': manufacturers.get(id=equipment.manufacturer_id).name, 'manufacturer_id': equipment.manufacturer_id, 'model': equipment.model, 'serial_number': equipment.serial_number, - 'acquired': equipment.acquired, - 'first_deployed': equipment.first_deployed, + 'acquisition_date': equipment.acquisition_date, + 'first_deploy_date': equipment.first_deploy_date, + 'last_calibration_date': equipment.last_calibration_date, + 'next_calibration_date': equipment.next_calibration_date, + 'decomission_date': equipment.decomission_date, 'last_deployed': '---', 'location': 'Office', 'condition': '---', @@ -4022,21 +4030,28 @@ def get_equipment_inventory_data(request): response = { 'equipments': equipment_list, 'equipment_types': list(equipment_types.values()), - 'manufacturers': list(manufacturers.values()) + 'manufacturers': list(manufacturers.values()), + 'founding_sources': list(founding_sources.values()) } return JsonResponse(response, status=status.HTTP_200_OK) + @require_http_methods(["POST"]) def create_equipment(request): equipment_type_id = request.GET.get('equipment_type', None) manufacturer_id = request.GET.get('manufacturer', None) + founding_source_id = request.GET.get('founding_source', None) model = request.GET.get('model', None) serial_number = request.GET.get('serial_number', None) - acquired = request.GET.get('acquired', None) - first_deployed = request.GET.get('first_deployed', None) + acquisition_date = request.GET.get('acquisition_date', None) + first_deploy_date = request.GET.get('first_deploy_date', None) + last_calibration_date = request.GET.get('last_calibration_date', None) + next_calibration_date = request.GET.get('next_calibration_date', None) + decomission_date = request.GET.get('decomission_date', None) equipment_type = EquipmentType.objects.get(id=equipment_type_id) manufacturer = Manufacturer.objects.get(id=manufacturer_id) + founding_source = FoundingSource.objects.get(id=founding_source_id) try: equipment = Equipment.objects.get( @@ -4061,65 +4076,73 @@ def create_equipment(request): updated_at = now, equipment_type = equipment_type, manufacturer = manufacturer, + founding_source = founding_source, model = model, serial_number = serial_number, - acquired = acquired, - first_deployed = first_deployed, + acquisition_date = acquisition_date, + first_deploy_date = first_deploy_date, + last_calibration_date = last_calibration_date, + next_calibration_date = next_calibration_date, + decomission_date = decomission_date, ) response = {'equipment_id': equipment.id} return JsonResponse(response, status=status.HTTP_200_OK) + @require_http_methods(["POST"]) def update_equipment(request): equipment_id = request.GET.get('equipment_id', None) equipment_type_id = request.GET.get('equipment_type', None) manufacturer_id = request.GET.get('manufacturer', None) + founding_source_id = request.GET.get('founding_source', None) model = request.GET.get('model', None) serial_number = request.GET.get('serial_number', None) - acquired = request.GET.get('acquired', None) - first_deployed = request.GET.get('first_deployed', None) - - print(equipment_id) + acquisition_date = request.GET.get('acquisition_date', None) + first_deploy_date = request.GET.get('first_deploy_date', None) + last_calibration_date = request.GET.get('last_calibration_date', None) + next_calibration_date = request.GET.get('next_calibration_date', None) + decomission_date = request.GET.get('decomission_date', None) equipment_type = EquipmentType.objects.get(id=equipment_type_id) manufacturer = Manufacturer.objects.get(id=manufacturer_id) + founding_source = FoundingSource.objects.get(id=founding_source_id) try: - equipment = Equipment.objects.get( - equipment_type=equipment_type, - serial_number = serial_number, - ) + equipment = Equipment.objects.get(equipment_type=equipment_type, serial_number=serial_number) - message = 'Already exist an equipment of equipment type ' - message += equipment_type.name - message += ' and serial number ' - message += equipment.serial_number + if int(equipment_id) != equipment.id: + message = f'''Could not update. Already exist an equipment of \ + equipment type {equipment_type.name} and serial \ + number {equipment.serial_number}''' - response = {'message': message} - - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - except ObjectDoesNotExist: - try: - equipment = Equipment.objects.get(id=equipment_id) + response = {'message': message} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + else: now = datetime.datetime.now() equipment.updated_at = now equipment.equipment_type = equipment_type equipment.manufacturer = manufacturer + equipment.founding_source = founding_source equipment.model = model equipment.serial_number = serial_number - equipment.acquired = acquired - equipment.first_deployed = first_deployed + equipment.acquisition_date = acquisition_date + equipment.first_deploy_date = first_deploy_date + equipment.last_calibration_date = last_calibration_date + equipment.next_calibration_date = next_calibration_date + equipment.decomission_date = decomission_date equipment.save() - except ObjectDoesNotExist: - message = "Object not found" - response = {'message': message} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + except ObjectDoesNotExist: + message = "Object not found" + response = {'message': message} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) response = {} return JsonResponse(response, status=status.HTTP_200_OK) From 84676bce59bcd749cd24e9bd43ede237b37c1434 Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Tue, 20 Jun 2023 18:32:29 -0300 Subject: [PATCH 03/21] Fixing typos --- ...024_equipment_equipmenttype_foundingsource_manufacturer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/wx/migrations/0024_equipment_equipmenttype_foundingsource_manufacturer.py b/api/wx/migrations/0024_equipment_equipmenttype_foundingsource_manufacturer.py index 3f37d2f..a798242 100644 --- a/api/wx/migrations/0024_equipment_equipmenttype_foundingsource_manufacturer.py +++ b/api/wx/migrations/0024_equipment_equipmenttype_foundingsource_manufacturer.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.13 on 2023-06-20 14:33 +# Generated by Django 3.2.13 on 2023-06-20 21:31 import ckeditor.fields from django.db import migrations, models @@ -63,7 +63,7 @@ class Migration(migrations.Migration): ('first_deploy_date', models.DateField(blank=True, null=True)), ('last_calibration_date', models.DateField(blank=True, null=True)), ('next_calibration_date', models.DateField(blank=True, null=True)), - ('decomission_date', models.DateField(blank=True, null=True)), + ('decommission_date', models.DateField(blank=True, null=True)), ('equipment_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.equipmenttype')), ('founding_source', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.foundingsource')), ('manufacturer', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.manufacturer')), From 7f6756c04cd300754106e705023b790801830941 Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Tue, 20 Jun 2023 18:40:45 -0300 Subject: [PATCH 04/21] Fixing typos --- api/wx/models.py | 2 +- .../equipment_inventory.html | 20 +++++++++---------- api/wx/views.py | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/api/wx/models.py b/api/wx/models.py index 922485d..fe5f4e7 100644 --- a/api/wx/models.py +++ b/api/wx/models.py @@ -1407,7 +1407,7 @@ class Equipment(BaseModel): first_deploy_date = models.DateField(blank=True, null=True) last_calibration_date = models.DateField(blank=True, null=True) next_calibration_date = models.DateField(blank=True, null=True) - decomission_date = models.DateField(blank=True, null=True) + decommission_date = models.DateField(blank=True, null=True) class Meta: unique_together = ("equipment_type", "serial_number") diff --git a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html index 2a388d5..3ae2fd9 100644 --- a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html +++ b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html @@ -149,14 +149,14 @@ @@ -366,8 +366,8 @@ last_calibration_date_menu: false, next_calibration_date: moment().format('YYYY-MM-DD'), next_calibration_date_menu: false, - decomission_date: moment().format('YYYY-MM-DD'), - decomission_date_menu: false, + decommission_date: moment().format('YYYY-MM-DD'), + decommission_date_menu: false, }, headers: [ { text: 'Equipment Type', align: 'left', value: 'equipment_type' }, @@ -409,8 +409,8 @@ last_calibration_date_menu: false, next_calibration_date: moment().format('YYYY-MM-DD'), next_calibration_date_menu: false, - decomission_date: moment().format('YYYY-MM-DD'), - decomission_date_menu: false, + decommission_date: moment().format('YYYY-MM-DD'), + decommission_date_menu: false, }; } } @@ -444,7 +444,7 @@ 'first_deploy_date': this.equipment.first_deploy_date, 'last_calibration_date': this.equipment.last_calibration_date, 'next_calibration_date': this.equipment.next_calibration_date, - 'decomission_date': this.equipment.decomission_date, + 'decommission_date': this.equipment.decommission_date, }, }) .then( @@ -476,7 +476,7 @@ 'first_deploy_date': this.equipment.first_deploy_date, 'last_calibration_date': this.equipment.last_calibration_date, 'next_calibration_date': this.equipment.next_calibration_date, - 'decomission_date': this.decomission_date + 'decommission_date': this.decommission_date }, }) .then( diff --git a/api/wx/views.py b/api/wx/views.py index 098c95a..9906306 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -4020,7 +4020,7 @@ def get_equipment_inventory_data(request): 'first_deploy_date': equipment.first_deploy_date, 'last_calibration_date': equipment.last_calibration_date, 'next_calibration_date': equipment.next_calibration_date, - 'decomission_date': equipment.decomission_date, + 'decommission_date': equipment.decommission_date, 'last_deployed': '---', 'location': 'Office', 'condition': '---', @@ -4047,7 +4047,7 @@ def create_equipment(request): first_deploy_date = request.GET.get('first_deploy_date', None) last_calibration_date = request.GET.get('last_calibration_date', None) next_calibration_date = request.GET.get('next_calibration_date', None) - decomission_date = request.GET.get('decomission_date', None) + decommission_date = request.GET.get('decommission_date', None) equipment_type = EquipmentType.objects.get(id=equipment_type_id) manufacturer = Manufacturer.objects.get(id=manufacturer_id) @@ -4083,7 +4083,7 @@ def create_equipment(request): first_deploy_date = first_deploy_date, last_calibration_date = last_calibration_date, next_calibration_date = next_calibration_date, - decomission_date = decomission_date, + decommission_date = decommission_date, ) response = {'equipment_id': equipment.id} @@ -4103,7 +4103,7 @@ def update_equipment(request): first_deploy_date = request.GET.get('first_deploy_date', None) last_calibration_date = request.GET.get('last_calibration_date', None) next_calibration_date = request.GET.get('next_calibration_date', None) - decomission_date = request.GET.get('decomission_date', None) + decommission_date = request.GET.get('decommission_date', None) equipment_type = EquipmentType.objects.get(id=equipment_type_id) manufacturer = Manufacturer.objects.get(id=manufacturer_id) @@ -4133,7 +4133,7 @@ def update_equipment(request): equipment.first_deploy_date = first_deploy_date equipment.last_calibration_date = last_calibration_date equipment.next_calibration_date = next_calibration_date - equipment.decomission_date = decomission_date + equipment.decommission_date = decommission_date equipment.save() response = {} From c12ae6bd5cf89792b233dddbb9fb38e576c91354 Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Tue, 20 Jun 2023 18:52:59 -0300 Subject: [PATCH 05/21] Fix edit equipment_type bug --- api/wx/views.py | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/api/wx/views.py b/api/wx/views.py index 9906306..c48e349 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -4113,31 +4113,36 @@ def update_equipment(request): equipment = Equipment.objects.get(equipment_type=equipment_type, serial_number=serial_number) if int(equipment_id) != equipment.id: - message = f'''Could not update. Already exist an equipment of \ + message = f"Could not update. Already exist an equipment of \ equipment type {equipment_type.name} and serial \ - number {equipment.serial_number}''' + number {equipment.serial_number}" response = {'message': message} return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - else: - now = datetime.datetime.now() - - equipment.updated_at = now - equipment.equipment_type = equipment_type - equipment.manufacturer = manufacturer - equipment.founding_source = founding_source - equipment.model = model - equipment.serial_number = serial_number - equipment.acquisition_date = acquisition_date - equipment.first_deploy_date = first_deploy_date - equipment.last_calibration_date = last_calibration_date - equipment.next_calibration_date = next_calibration_date - equipment.decommission_date = decommission_date - equipment.save() - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) + except ObjectDoesNotExist + pass + + try: + equipment = Equipment.objects.get(id=equipment_id) + + now = datetime.datetime.now() + + equipment.updated_at = now + equipment.equipment_type = equipment_type + equipment.manufacturer = manufacturer + equipment.founding_source = founding_source + equipment.model = model + equipment.serial_number = serial_number + equipment.acquisition_date = acquisition_date + equipment.first_deploy_date = first_deploy_date + equipment.last_calibration_date = last_calibration_date + equipment.next_calibration_date = next_calibration_date + equipment.decommission_date = decommission_date + equipment.save() + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) except ObjectDoesNotExist: message = "Object not found" From c35e382dcc0444de254cf875f6aedc6d1acec334 Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Tue, 20 Jun 2023 19:03:53 -0300 Subject: [PATCH 06/21] Add validation to equipment inventory --- .../equipment_inventory.html | 26 +++++++++++++++++-- api/wx/views.py | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html index 3ae2fd9..e3cb6b0 100644 --- a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html +++ b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html @@ -222,10 +222,22 @@ - + Create Equipment - + Update Equipment @@ -336,6 +348,16 @@ el: "#app", vuetify: new Vuetify(), delimiters: ["[[", "]]"], + validations: { + equipment: { + equipment_type_id: { required }, + founding_source_id: { required }, + manufacturer_id: { required }, + model: { required }, + serial_number: { required }, + acquisition_date: { required }, + }, + }, data:{ search: '', loading: false, diff --git a/api/wx/views.py b/api/wx/views.py index c48e349..24f4be0 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -4120,7 +4120,7 @@ def update_equipment(request): response = {'message': message} return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - except ObjectDoesNotExist + except ObjectDoesNotExist: pass try: From 07495cccbab458a16b0d7d0f67aa2234894e5a6f Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Wed, 21 Jun 2023 20:09:17 -0300 Subject: [PATCH 07/21] Added new fields to equipments model and fixed some buugs --- api/wx/admin.py | 5 +- api/wx/migrations/0025_auto_20230621_1744.py | 42 ++++++ api/wx/models.py | 17 ++- .../equipment_inventory.html | 102 +++++++++++++-- api/wx/views.py | 120 +++++++++++++----- 5 files changed, 236 insertions(+), 50 deletions(-) create mode 100644 api/wx/migrations/0025_auto_20230621_1744.py diff --git a/api/wx/admin.py b/api/wx/admin.py index 9c96d8b..b461560 100644 --- a/api/wx/admin.py +++ b/api/wx/admin.py @@ -345,12 +345,13 @@ class ManufacturerAdmin(admin.ModelAdmin): class EquipmentTypeAdmin(admin.ModelAdmin): list_display = ("name",) -@admin.register(models.FoundingSource) -class FoundingSourceAdmin(admin.ModelAdmin): +@admin.register(models.FundingSource) +class FundingSourceAdmin(admin.ModelAdmin): list_display = ("name",) @admin.register(models.Equipment) class EquipmentAdmin(admin.ModelAdmin): list_display = ("equipment_type", "manufacturer", "model", "serial_number", "acquisition_date", "first_deploy_date", "last_calibration_date") + readonly_fields=('location',) diff --git a/api/wx/migrations/0025_auto_20230621_1744.py b/api/wx/migrations/0025_auto_20230621_1744.py new file mode 100644 index 0000000..74220ac --- /dev/null +++ b/api/wx/migrations/0025_auto_20230621_1744.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.13 on 2023-06-21 17:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wx', '0024_equipment_equipmenttype_foundingsource_manufacturer'), + ] + + operations = [ + migrations.RenameModel( + old_name='FoundingSource', + new_name='FundingSource', + ), + migrations.AlterModelOptions( + name='equipment', + options={'verbose_name': 'equipment', 'verbose_name_plural': 'equipment'}, + ), + migrations.RenameField( + model_name='equipment', + old_name='founding_source', + new_name='funding_source', + ), + migrations.AddField( + model_name='equipment', + name='condition', + field=models.CharField(choices=[('F', 'Fully Functional'), ('P', 'Partially Functional'), ('N', 'Not Functional')], max_length=1, null=True), + ), + migrations.AddField( + model_name='equipment', + name='last_deploy_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='equipment', + name='location', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='wx.station'), + ), + ] diff --git a/api/wx/models.py b/api/wx/models.py index fe5f4e7..2c0406c 100644 --- a/api/wx/models.py +++ b/api/wx/models.py @@ -1394,22 +1394,33 @@ class Meta: def __str__(self): return self.name -class FoundingSource(BaseModel): +class FundingSource(BaseModel): name = models.CharField(max_length=128) + def __str__(self): + return self.name + class Equipment(BaseModel): + class EquipmentCondition(models.TextChoices): + FULLY_FUNCTIONAL = 'F', gettext_lazy('Fully Functional') + PARTIALLY_FUNCTIONAL = 'P', gettext_lazy('Partially Functional') + NOT_FUNCTIONAL = 'N', gettext_lazy('Not Functional') + equipment_type = models.ForeignKey(EquipmentType, on_delete=models.DO_NOTHING) manufacturer = models.ForeignKey(Manufacturer, on_delete=models.DO_NOTHING) - founding_source = models.ForeignKey(FoundingSource, on_delete=models.DO_NOTHING) + funding_source = models.ForeignKey(FundingSource, on_delete=models.DO_NOTHING) model = models.CharField(max_length=64) serial_number = models.CharField(max_length=64) acquisition_date = models.DateField() first_deploy_date = models.DateField(blank=True, null=True) + last_deploy_date = models.DateField(blank=True, null=True) last_calibration_date = models.DateField(blank=True, null=True) next_calibration_date = models.DateField(blank=True, null=True) decommission_date = models.DateField(blank=True, null=True) + location = models.ForeignKey(Station, on_delete=models.DO_NOTHING, null=True) + condition = models.CharField(max_length=1, choices=EquipmentCondition.choices, null=True) class Meta: unique_together = ("equipment_type", "serial_number") verbose_name = "equipment" - verbose_name_plural = "equipments" \ No newline at end of file + verbose_name_plural = "equipment" \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html index e3cb6b0..c82da91 100644 --- a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html +++ b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html @@ -83,11 +83,11 @@ + + + + + + + + + + + + + + + + + + + Date: Mon, 10 Jul 2023 16:46:50 -0300 Subject: [PATCH 08/21] Multiple Equipment --- api/Dockerfile | 3 +- api/requirements.txt | 1 + api/tempestas_api/settings.py | 2 + api/wx/admin.py | 20 +- api/wx/migrations/0026_auto_20230705_1900.py | 88 + api/wx/migrations/0027_auto_20230707_2048.py | 24 + api/wx/models.py | 71 +- .../maintenance_reports.html | 4 +- .../wx/maintenance_reports/new_report.html | 575 +- .../equipment_inventory.html | 622 ++ .../maintenance_reports.html | 412 ++ .../maintenance_reports_old/new_report.html | 778 ++ .../maintenance_reports_old/view_report.html | 275 + api/wx/urls.py | 23 +- api/wx/views.py | 235 +- api/wx/views_old.py | 6291 +++++++++++++++++ 16 files changed, 9029 insertions(+), 395 deletions(-) create mode 100644 api/wx/migrations/0026_auto_20230705_1900.py create mode 100644 api/wx/migrations/0027_auto_20230707_2048.py create mode 100644 api/wx/templates/wx/maintenance_reports_old/equipment_inventory.html create mode 100644 api/wx/templates/wx/maintenance_reports_old/maintenance_reports.html create mode 100644 api/wx/templates/wx/maintenance_reports_old/new_report.html create mode 100644 api/wx/templates/wx/maintenance_reports_old/view_report.html create mode 100644 api/wx/views_old.py diff --git a/api/Dockerfile b/api/Dockerfile index 93e90ff..234b3c3 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -5,7 +5,8 @@ ENV PYTHONUNBUFFERED 1 ENV DJANGO_SETTINGS_MODULE tempestas_api.settings RUN apt-get update --fix-missing -RUN apt-get install -y g++ libgdal-dev libpq-dev libgeos-dev libproj-dev openjdk-17-jre vim wait-for-it +RUN apt-get install -y g++ libgdal-dev libpq-dev libgeos-dev libproj-dev vim wait-for-it +RUN apt-get install -y openjdk-17-jre RUN apt-get install -y postgresql-client RUN adduser --disabled-password --gecos '' surface && mkdir /surface && chown -R surface /surface diff --git a/api/requirements.txt b/api/requirements.txt index d83634b..229b184 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -31,3 +31,4 @@ requests==2.26.0 gunicorn==20.1.0 croniter==1.3.5 django-ckeditor==6.5.1 +django-simple-history==3.3.0 \ No newline at end of file diff --git a/api/tempestas_api/settings.py b/api/tempestas_api/settings.py index 3b9c19d..bdef4e4 100644 --- a/api/tempestas_api/settings.py +++ b/api/tempestas_api/settings.py @@ -48,6 +48,7 @@ 'rest_framework.authtoken', 'colorfield', 'import_export', + 'simple_history', ] CKEDITOR_CONFIGS = { @@ -74,6 +75,7 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'simple_history.middleware.HistoryRequestMiddleware', ] ROOT_URLCONF = 'tempestas_api.urls' diff --git a/api/wx/admin.py b/api/wx/admin.py index b461560..339c5b7 100644 --- a/api/wx/admin.py +++ b/api/wx/admin.py @@ -341,17 +341,23 @@ class TechnicianAdmin(admin.ModelAdmin): class ManufacturerAdmin(admin.ModelAdmin): list_display = ("name",) -@admin.register(models.EquipmentType) -class EquipmentTypeAdmin(admin.ModelAdmin): - list_display = ("name",) - @admin.register(models.FundingSource) class FundingSourceAdmin(admin.ModelAdmin): list_display = ("name",) +from simple_history.admin import SimpleHistoryAdmin + +@admin.register(models.EquipmentType) +class EquipmentTypeAdmin(admin.ModelAdmin): + list_display = ("name",) + @admin.register(models.Equipment) -class EquipmentAdmin(admin.ModelAdmin): +class EquipmentAdmin(SimpleHistoryAdmin): list_display = ("equipment_type", "manufacturer", "model", "serial_number", "acquisition_date", "first_deploy_date", "last_calibration_date") - readonly_fields=('location',) + readonly_fields=('location',) + +@admin.register(models.StationProfileEquipmentType) +class StationProfileEquipmentTypeAdmin(admin.ModelAdmin): + list_display = ('profile', 'equipment_type', 'equipment_type_order', 'equipment_order') + - diff --git a/api/wx/migrations/0026_auto_20230705_1900.py b/api/wx/migrations/0026_auto_20230705_1900.py new file mode 100644 index 0000000..6428703 --- /dev/null +++ b/api/wx/migrations/0026_auto_20230705_1900.py @@ -0,0 +1,88 @@ +# Generated by Django 3.2.13 on 2023-07-05 19:00 + +import ckeditor.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('wx', '0025_auto_20230621_1744'), + ] + + operations = [ + migrations.RenameField( + model_name='equipment', + old_name='condition', + new_name='classification', + ), + migrations.CreateModel( + name='StationProfileEquipmentType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('equipment_type_order', models.IntegerField()), + ('equipment_order', models.IntegerField()), + ('equipment_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.equipmenttype')), + ('profile', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.stationprofile')), + ], + options={ + 'unique_together': {('profile', 'equipment_type', 'equipment_type_order', 'equipment_order')}, + }, + ), + migrations.CreateModel( + name='HistoricalEquipment', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('created_at', models.DateTimeField(blank=True, editable=False)), + ('updated_at', models.DateTimeField(blank=True, editable=False)), + ('model', models.CharField(max_length=64)), + ('serial_number', models.CharField(max_length=64)), + ('acquisition_date', models.DateField()), + ('first_deploy_date', models.DateField(blank=True, null=True)), + ('last_deploy_date', models.DateField(blank=True, null=True)), + ('last_calibration_date', models.DateField(blank=True, null=True)), + ('next_calibration_date', models.DateField(blank=True, null=True)), + ('decommission_date', models.DateField(blank=True, null=True)), + ('classification', models.CharField(choices=[('F', 'Fully Functional'), ('P', 'Partially Functional'), ('N', 'Not Functional')], max_length=1, null=True)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('equipment_type', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.equipmenttype')), + ('funding_source', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.fundingsource')), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.station')), + ('manufacturer', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.manufacturer')), + ], + options={ + 'verbose_name': 'historical equipment', + 'verbose_name_plural': 'historical equipment', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='MaintenanceReportEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('condition', ckeditor.fields.RichTextField()), + ('classification', models.CharField(choices=[('F', 'Fully Functional'), ('P', 'Partially Functional'), ('N', 'Not Functional')], default='F', max_length=1)), + ('current_equipment', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='current_equipment', to='wx.equipment')), + ('maintenance_report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wx.maintenancereport')), + ('old_equipment', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='old_equipment', to='wx.equipment')), + ('profile_equipment', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.stationprofileequipmenttype')), + ], + options={ + 'unique_together': {('maintenance_report', 'profile_equipment')}, + }, + ), + ] diff --git a/api/wx/migrations/0027_auto_20230707_2048.py b/api/wx/migrations/0027_auto_20230707_2048.py new file mode 100644 index 0000000..94d4315 --- /dev/null +++ b/api/wx/migrations/0027_auto_20230707_2048.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.13 on 2023-07-07 20:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wx', '0026_auto_20230705_1900'), + ] + + operations = [ + migrations.AlterField( + model_name='maintenancereportequipment', + name='classification', + field=models.CharField(choices=[('F', 'Fully Functional'), ('P', 'Partially Functional'), ('N', 'Not Functional')], default='F', max_length=1, null=True), + ), + migrations.AlterField( + model_name='maintenancereportequipment', + name='old_equipment', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='old_equipment', to='wx.equipment'), + ), + ] diff --git a/api/wx/models.py b/api/wx/models.py index 2c0406c..4f612aa 100644 --- a/api/wx/models.py +++ b/api/wx/models.py @@ -18,6 +18,8 @@ from timescale.db.models.fields import TimescaleDateTimeField from timescale.db.models.managers import TimescaleManager +from django.db.models import Q + class BaseModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) @@ -1298,6 +1300,9 @@ def no_future(value): if value > today: raise ValidationError('Visit date cannot be in the future.') +################################################################ +################################################################ + class StationComponent(BaseModel): name = models.CharField(max_length=64) description = models.CharField(max_length=256) @@ -1320,6 +1325,10 @@ class Meta: unique_together = ('profile', 'station_component') unique_together = ('profile', 'presentation_order') +################################################################ +################################################################ + + class MaintenanceReport(BaseModel): class Status(models.TextChoices): APPROVED = 'A', gettext_lazy('Approved') @@ -1375,6 +1384,9 @@ class ComponentClassification(models.TextChoices): class Meta: unique_together = ('maintenance_report', 'station_component') +################################################################ +################################################################ + class Manufacturer(BaseModel): name = models.CharField(max_length=64) @@ -1384,7 +1396,6 @@ def __str__(self): class EquipmentType(BaseModel): name = models.CharField(max_length=64) description = models.CharField(max_length=256) - # https://stackoverflow.com/questions/7946861/how-can-i-add-a-wsywyg-editor-to-django-admin report_template = RichTextField(blank=True, null=True) class Meta: @@ -1400,8 +1411,11 @@ class FundingSource(BaseModel): def __str__(self): return self.name + +from simple_history.models import HistoricalRecords + class Equipment(BaseModel): - class EquipmentCondition(models.TextChoices): + class EquipmentClassification(models.TextChoices): FULLY_FUNCTIONAL = 'F', gettext_lazy('Fully Functional') PARTIALLY_FUNCTIONAL = 'P', gettext_lazy('Partially Functional') NOT_FUNCTIONAL = 'N', gettext_lazy('Not Functional') @@ -1418,9 +1432,56 @@ class EquipmentCondition(models.TextChoices): next_calibration_date = models.DateField(blank=True, null=True) decommission_date = models.DateField(blank=True, null=True) location = models.ForeignKey(Station, on_delete=models.DO_NOTHING, null=True) - condition = models.CharField(max_length=1, choices=EquipmentCondition.choices, null=True) + classification = models.CharField(max_length=1, choices=EquipmentClassification.choices, null=True) + history = HistoricalRecords() class Meta: - unique_together = ("equipment_type", "serial_number") + unique_together = ("equipment_type", "serial_number") verbose_name = "equipment" - verbose_name_plural = "equipment" \ No newline at end of file + verbose_name_plural = "equipment" + + def __str__(self): + return ' '.join((self.equipment_type.name, self.model, self.serial_number)) + + +class StationProfileEquipmentType(BaseModel): + profile = models.ForeignKey(StationProfile, on_delete=models.DO_NOTHING) + equipment_type = models.ForeignKey(EquipmentType, on_delete=models.DO_NOTHING) + equipment_type_order = models.IntegerField() #Main + equipment_order = models.IntegerField() #Sub + + + def save(self, *args, **kwargs): + # For each profile, equiptment_type is one to one with equiptment_type_order: + if StationProfileEquipmentType.objects.filter( + profile=self.profile, + equipment_type=self.equipment_type, + ).exclude(equipment_type_order=self.equipment_type_order).exists(): + raise ValidationError("A StationProfileEquipmentType with the same profile and equipment_type, already exists with a different equipment_type_order.") + if StationProfileEquipmentType.objects.filter( + profile=self.profile, + equipment_type_order=self.equipment_type_order + ).exclude(equipment_type=self.equipment_type).exists(): + raise ValidationError("A StationProfileEquipmentType with the same profile and equipment_type_order, already exists with a different equipment_type.") + + super().save(*args, **kwargs) + + class Meta: + unique_together = ('profile', 'equipment_type', 'equipment_type_order', 'equipment_order') + +class MaintenanceReportEquipment(BaseModel): + class EquipmentClassification(models.TextChoices): + FULLY_FUNCTIONAL = 'F', gettext_lazy('Fully Functional') + PARTIALLY_FUNCTIONAL = 'P', gettext_lazy('Partially Functional') + NOT_FUNCTIONAL = 'N', gettext_lazy('Not Functional') + + profile_equipment = models.ForeignKey(StationProfileEquipmentType, on_delete=models.DO_NOTHING) + maintenance_report = models.ForeignKey(MaintenanceReport, on_delete=models.CASCADE) + old_equipment = models.ForeignKey(Equipment, on_delete=models.DO_NOTHING, related_name='old_equipment', null=True) + current_equipment = models.ForeignKey(Equipment, on_delete=models.DO_NOTHING, related_name='current_equipment') + condition = RichTextField() + classification = models.CharField(max_length=1, choices=EquipmentClassification.choices, default=EquipmentClassification.FULLY_FUNCTIONAL, null=True) + + class Meta: + unique_together = ('maintenance_report', 'current_equipment') + unique_together = ('maintenance_report', 'profile_equipment') diff --git a/api/wx/templates/wx/maintenance_reports/maintenance_reports.html b/api/wx/templates/wx/maintenance_reports/maintenance_reports.html index ee858ae..0507e9d 100644 --- a/api/wx/templates/wx/maintenance_reports/maintenance_reports.html +++ b/api/wx/templates/wx/maintenance_reports/maintenance_reports.html @@ -172,7 +172,7 @@
    Search Form
    - Are you sure that you really want to delete this report? + Do you want to delete this report? @@ -187,7 +187,7 @@
    Search Form
    - Are you sure that you really want to approve this report? + Do you want to approve this report? diff --git a/api/wx/templates/wx/maintenance_reports/new_report.html b/api/wx/templates/wx/maintenance_reports/new_report.html index ab2f6dc..56fce25 100644 --- a/api/wx/templates/wx/maintenance_reports/new_report.html +++ b/api/wx/templates/wx/maintenance_reports/new_report.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} {% block content %} +{% extends "base.html" %} {% block content %}
    @@ -6,6 +6,22 @@ + + + + Add New Equipment + + + + Create Equipment + + + Cancel + + + + +
    Maintenance Reports - New Report

    @@ -18,7 +34,7 @@
    Select the station and add initial data to create a report
    v-model="filter.isAutomatic" @change="invertValue(filter.isAutomatic)" label="Automatic" - :disabled="maintenance_report.id" + :disabled="disableForm" > @@ -27,7 +43,7 @@
    Select the station and add initial data to create a report
    item-text="label" item-value="id" v-model="maintenance_report.station_id" - :disabled="maintenance_report.id" + :disabled="disableForm" label="Station" required hint="*Required" @@ -49,7 +65,7 @@
    Select the station and add initial data to create a report
    v-model="maintenance_report.visit_date" label="Date of visit" v-on="on" - :disabled="maintenance_report.id" + :disabled="disableForm" required persistent-hint prepend-icon="event" @@ -64,13 +80,13 @@
    Select the station and add initial data to create a report
    - + Select the station and add initial data to create a report item-text="name" item-value="id" v-model="maintenance_report.responsible_technician_id" - :disabled="maintenance_report.id" + :disabled="disableForm" label="Responsible technician" required hint="*Required" @@ -107,7 +123,7 @@
    Select the station and add initial data to create a report
    v-model="maintenance_report.initial_time" label="Initial time" v-on="on" - :disabled="maintenance_report.id" + :disabled="disableForm" required persistent-hint prepend-icon="access_time" @@ -117,7 +133,7 @@
    Select the station and add initial data to create a report
    v-model="maintenance_report.initial_time" > -
    +
    @@ -136,7 +152,6 @@
    Select the station and add initial data to create a report
    >CREATE REPORT
    - @@ -149,276 +164,219 @@
    Select the station and add initial data to create a report
    > - - - - - Reported Created by [[maintenance_report.responsible_technician.name]] at [[maintenance_report.visit_date]] [[maintenance_report.initial_time]] - - - - Station information - -
    - - Station Name: [[maintenance_report.station_information.station_name]] - Station Host Name: [[maintenance_report.station_information.station_host_name]] - Station ID: [[maintenance_report.station_information.station_ID]] - WIGOS ID: [[maintenance_report.station_information.wigos_ID]] - - - - Station Type: [[maintenance_report.station_information.station_type]] - Latitude: [[maintenance_report.station_information.latitude]] - Longitude: [[maintenance_report.station_information.longitude]] - Station Profile: [[maintenance_report.station_information.station_profile]] - - - - Elevation: [[maintenance_report.station_information.elevation]] - District: [[maintenance_report.station_information.district]] - Watershed: [[maintenance_report.station_information.watershed]] - - - - - Transmission Type: [[maintenance_report.station_information.transmission_type]] - Transmission ID: [[maintenance_report.station_information.transmission_ID]] - Transmission Interval: [[maintenance_report.station_information.transmission_interval]] - Measurement Interval: [[maintenance_report.station_information.measurement_interval]] - - - - Data of first Operation: [[maintenance_report.station_information.data_of_first_operation]] - Date of Relocation: [[maintenance_report.station_information.data_of_relocation]] - - - -
    - - - Describe the station on arrival - - - - - - - - Save and Continue - - - - - - - - Evaluate Condition of [[component.component_name]] + + + +
    @@ -439,7 +397,7 @@
    Select the station and add initial data to create a report
    visit_type_id: { required }, responsible_technician_id: { required }, }, - }, + }, data (){ let ckeditor_toolbar = [ ['Bold', 'Italic', 'Font'], @@ -447,7 +405,7 @@
    Select the station and add initial data to create a report
    ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock', 'Indent', 'Outdent'], ['HorizontalRule', 'BulletedList'], ['Blockquote', 'Source', 'Link', 'Unlink', 'Image', 'Table', 'Print'] - ]; + ]; return { ckeditor_configs: { station_on_arrival_conditions: { @@ -485,10 +443,11 @@
    Select the station and add initial data to create a report
    extraAllowedContent : 'img(*){*}[*]', language: 'en', }, - }, + }, + dialog_new: false, e1: 1, steps: 1, - radioGroup: null, + radioGroup: null, request_error: false, request_error_message: "", loading: false, @@ -509,7 +468,7 @@
    Select the station and add initial data to create a report
    visit_date_menu: false, initial_time: moment().format('HH:mm'), initial_time_menu: false, - component_list: null, + equipment_types: null, contacts: null, other_technician_1: null, other_technician_2: null, @@ -544,10 +503,16 @@
    Select the station and add initial data to create a report
    { id: {{ technician.id }}, name: "{{ technician.name }}"}, {% endfor %} - ], + ], } }, computed: { + disableForm () { + if(this.maintenance_report.id){ + return true + } + return false + }, stationList () { let filteredStations = this.stations; @@ -556,7 +521,7 @@
    Select the station and add initial data to create a report
    this.maintenance_report.station_id = null; return filteredStations; - } + } }, watch: { steps (val) { @@ -566,6 +531,9 @@
    Select the station and add initial data to create a report
    }, }, methods: { + viewMaintenanceReport(id){ + return `/wx/maintenance_report/${id}/view/${0}/` + }, notFutureDate(){ var today = moment().format('YYYY-MM-DD'); var now = moment().format('HH:mm'); @@ -594,9 +562,6 @@
    Select the station and add initial data to create a report
    return this.technicianList.filter((item) => !arr.includes(item.id)) - }, - viewMaintenanceReport(id){ - return `/wx/maintenance_report/${id}/view/${0}/` }, nextStep(n) { if (n === this.steps+3) { @@ -604,7 +569,7 @@
    Select the station and add initial data to create a report
    } else { this.e1 = n + 1 } - }, + }, getInitialData(id) { this.loading = true; axios.get(`/wx/maintenance_report/${id}/get/`).then( @@ -625,13 +590,16 @@
    Select the station and add initial data to create a report
    this.maintenance_report.other_technician_2 = res.data['other_technician_2']; this.maintenance_report.other_technician_3 = res.data['other_technician_3']; this.maintenance_report.contacts = res.data['contacts']; - this.maintenance_report.component_list = res.data['components']; + this.maintenance_report.equipment_types = res.data['equipment_types']; this.maintenance_report.data_logger_file_name = res.data['data_logger_file_name']; this.steps = res.data['steps']; this.loading = false; } ); - }, + }, + getAvailableProfileEquipments(equipment_type){ + + } createMaintenanceReport(item) { this.loading = true; axios.post(`/wx/maintenance_report/create/`,{ @@ -653,6 +621,15 @@
    Select the station and add initial data to create a report
    .finally(() => { this.loading = false; }); + }, + notFutureDate(){ + var today = moment().format('YYYY-MM-DD'); + var now = moment().format('HH:mm'); + + if (this.maintenance_report.visit_date === today){ + return this.maintenance_report.initial_time > now; + } + return this.maintenance_report.visit_date > today; }, updateMaintenanceReportConditions(item){ this.loading = true; @@ -664,17 +641,6 @@
    Select the station and add initial data to create a report
    ); this.loading = false; }, - updateMaintenanceReportComponent(item, component){ - this.loading = true; - axios.put(`/wx/maintenance_report/${item.id}/update/component/${component.component_id}/`,{ - "component_classification": component.component_classification, - "component_condition": component.condition, - }) - .then( - res => {} - ); - this.loading = false; - }, updateMaintenanceReportContacts(item){ this.loading = true; axios.put(`/wx/maintenance_report/${item.id}/update/contacts/`,{ @@ -722,7 +688,7 @@
    Select the station and add initial data to create a report
    this.updateMaintenanceReportDataLogger(this.maintenance_report) }); this.loading = false; - }, + }, }, mounted() { {% if maintenance_report_id %} @@ -732,47 +698,10 @@
    Select the station and add initial data to create a report
    axios.defaults.xsrfCookieName = "csrftoken"; }, }); +
    {% endblock %} \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports_old/equipment_inventory.html b/api/wx/templates/wx/maintenance_reports_old/equipment_inventory.html new file mode 100644 index 0000000..c82da91 --- /dev/null +++ b/api/wx/templates/wx/maintenance_reports_old/equipment_inventory.html @@ -0,0 +1,622 @@ +{% extends "base.html" %} {% block content %} {% load static %} +
    +
    + + + + [[ request_error_message ]] + + + + + + + + + + Creating a new equipment + + + Updating equipment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create Equipment + + + Update Equipment + + + Cancel + + + + + + + + + Do you want to delete this equipment? + + + + Equipment Type: [[equipment.equipment_type]] + + + Manufacturer: [[equipment.manufacturer]] + + + Model: [[equipment.model]] + + + Serial Number: [[equipment.serial_number]] + + + + + Delete Equipment + + + Cancel + + + + + + + +
    + Equipment Inventory
    +
    +
    + + + + + + + + {% if perms.wx.add_equipment %} + + New Equipment + + {% endif %} + + + + + + + + + + +
    +
    +
    +
    +{% endblock %} {% block localjavascript %} + + +{% endblock %} \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports_old/maintenance_reports.html b/api/wx/templates/wx/maintenance_reports_old/maintenance_reports.html new file mode 100644 index 0000000..ee858ae --- /dev/null +++ b/api/wx/templates/wx/maintenance_reports_old/maintenance_reports.html @@ -0,0 +1,412 @@ +{% extends "base.html" %} {% block content %} {% load static %} +
    +
    + + + +
    + Maintenance Reports
    +
    + +
    Search Form
    + + + + + + + + + + + + + + + + + + + Submit + + + +
    + + +
    Search Results

    + Dates: [[start_date]] to [[end_date]] +
    + + + + + + + + + + + Are you sure that you really want to delete this report? + + + + Delete Report + + + Cancel + + + + + + + + Are you sure that you really want to approve this report? + + + + Approve Report + + + Cancel + + + + +
    +
    +
    +
    + + {% endblock %} {% block localjavascript %} + + + +
    +{% endblock %} \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports_old/new_report.html b/api/wx/templates/wx/maintenance_reports_old/new_report.html new file mode 100644 index 0000000..ab2f6dc --- /dev/null +++ b/api/wx/templates/wx/maintenance_reports_old/new_report.html @@ -0,0 +1,778 @@ +{% extends "base.html" %} {% block content %} + + +
    +
    + + + +
    + Maintenance Reports - New Report

    +
    + +
    Select the station and add initial data to create a report
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Visit date and initial time can not be in the future. + + + CREATE REPORT + + + + + + [[ request_error_message ]] + + + + +
    +
    + + + + Reported Created by [[maintenance_report.responsible_technician.name]] at [[maintenance_report.visit_date]] [[maintenance_report.initial_time]] + + + + Station information + +
    + + Station Name: [[maintenance_report.station_information.station_name]] + Station Host Name: [[maintenance_report.station_information.station_host_name]] + Station ID: [[maintenance_report.station_information.station_ID]] + WIGOS ID: [[maintenance_report.station_information.wigos_ID]] + + + + Station Type: [[maintenance_report.station_information.station_type]] + Latitude: [[maintenance_report.station_information.latitude]] + Longitude: [[maintenance_report.station_information.longitude]] + Station Profile: [[maintenance_report.station_information.station_profile]] + + + + Elevation: [[maintenance_report.station_information.elevation]] + District: [[maintenance_report.station_information.district]] + Watershed: [[maintenance_report.station_information.watershed]] + + + + + Transmission Type: [[maintenance_report.station_information.transmission_type]] + Transmission ID: [[maintenance_report.station_information.transmission_ID]] + Transmission Interval: [[maintenance_report.station_information.transmission_interval]] + Measurement Interval: [[maintenance_report.station_information.measurement_interval]] + + + + Data of first Operation: [[maintenance_report.station_information.data_of_first_operation]] + Date of Relocation: [[maintenance_report.station_information.data_of_relocation]] + + + +
    +
    + + + + Describe the station on arrival + + + + + + + + Save and Continue + + + + + + + + Evaluate Condition of [[component.component_name]] + + + + + + + + + + + Save and Continue + + + + + + + + Contacts + + + + + + + + Save and Continue + + + + + + + Summary of the visit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Data Logger File + + + + + + + + End time must be after initial time. + + + Next Visit must be in the future. + + + + + + + Preview + + + Save Draft + Publish + + + + +
    +
    +
    + + {% endblock %} {% block localjavascript %} + + + +
    +{% endblock %} \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports_old/view_report.html b/api/wx/templates/wx/maintenance_reports_old/view_report.html new file mode 100644 index 0000000..cea1e7f --- /dev/null +++ b/api/wx/templates/wx/maintenance_reports_old/view_report.html @@ -0,0 +1,275 @@ + +{% load static %} + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + + +
    + Manual Weather Station Maintenance Report +

    + {{station_information.station_name}} +
    +
    + + + Visit Summary Information + +
    + + + + Report #: + {{visit_summary_information.report_number}} + Responsible Technicians: + {{visit_summary_information.responsible_technician}} + + + + + Date of Visit: + {{visit_summary_information.date_of_visit}} + Date of Next Visit + {{visit_summary_information.date_of_next_visit}} + + + + + Start Time: + {{visit_summary_information.start_time}} + Other Technicians: + {{visit_summary_information.other_technicians}} + + + + + End Time: + {{visit_summary_information.end_time}} + Type of Visit: + {{visit_summary_information.type_of_visit}} + + + + + Description of Station Upon Arrival: + {{visit_summary_information.station_on_arrival_conditions | safe}} + + + + + Summary of Work Done at Station: + {{visit_summary_information.current_visit_summary | safe}} + + + + + Work To Be Done On Next Visit: + {{visit_summary_information.next_visit_summary | safe}} + + + + + Station Information + +
    + + + + Station Name: + {{station_information.station_name}} + Station Host Name: + {{station_information.station_host_name}} + + + + + Sttion ID: + {{station_information.station_ID}} + WIGOS ID: + {{station_information.wigos_ID}} + + + + + Sttion Type: + {{station_information.station_type}} + Sttion Profile: + {{station_information.station_profile}} + + + + + Latitude: + {{station_information.latitude}} + Elevation (m): + {{station_information.elevation}} + + + + + Longitude: + {{station_information.longitude}} + District: + {{station_information.district}} + + + + + Transmission Type: + {{station_information.transmission_type}} + Transmission ID: + {{station_information.transmission_ID}} + + + + + Transmission Interval: + {{station_information.transmission_interval}} + Measurement Interval: + {{station_information.measurement_interval}} + + + + + Date of First Operation: + {{station_information.data_of_first_operation}} + Date(s) of Relocation: + {{station_information.data_of_relocation}} + + + + + Contact Information + +
    + + + + {{ contact_information | safe }} + + + + + Equipment Record + +
    + + + + + + + [[equipment_record.component_name]] + + + + + + + Component Condition + + + + + + + + + + Component Classification + + + + + + + + + + + + + + + + Close Preview + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/api/wx/urls.py b/api/wx/urls.py index 5779b41..4e6c6bc 100644 --- a/api/wx/urls.py +++ b/api/wx/urls.py @@ -109,6 +109,17 @@ path('wx/quality_control/persist_threshold/get/', views.get_persist_threshold), path('wx/quality_control/persist_threshold/update/', views.update_persist_threshold), path('wx/quality_control/persist_threshold/delete/', views.delete_persist_threshold), + path('wx/products/wave_data/', login_required(views.get_wave_data_analysis), name='wave-data'), + path('wx/products/wave_data/get/', login_required(views.get_wave_data), name="get-wave-data"), + path('wx/stations/stations_monitoring/', views.stationsmonitoring_form, name="stations-monitoring"), + path('wx/stations/stations_monitoring/get/', views.get_stationsmonitoring_map_data), + path('wx/stations/stations_monitoring/get//', views.get_stationsmonitoring_station_data), + path('wx/stations/stations_monitoring/get///', views.get_stationsmonitoring_chart_data), + path('wx/maintenance_reports/equipment_inventory/', views.get_equipment_inventory, name="equipment-inventory"), + path('wx/maintenance_reports/equipment_inventory/get/', views.get_equipment_inventory_data), + path('wx/maintenance_reports/equipment_inventory/create/', views.create_equipment), + path('wx/maintenance_reports/equipment_inventory/delete/', views.delete_equipment), + path('wx/maintenance_reports/equipment_inventory/update/', views.update_equipment), path('wx/maintenance_report/', login_required(views.get_maintenance_reports), name='maintenance-reports'), path('wx/maintenance_report/get_reports/', login_required(views.get_maintenance_report_list)), path('wx/maintenance_report/new_report/', login_required(views.get_maintenance_report_form), name='new-maintenance-report'), @@ -123,17 +134,7 @@ path('wx/maintenance_report//delete/', login_required(views.delete_maintenance_report)), path('wx/maintenance_report//approve/', login_required(views.approve_maintenance_report)), path('wx/maintenance_report//view//', login_required(views.get_maintenance_report_view), name='view-maintenance-report'), - path('wx/products/wave_data/', login_required(views.get_wave_data_analysis), name='wave-data'), - path('wx/products/wave_data/get/', login_required(views.get_wave_data), name="get-wave-data"), - path('wx/stations/stations_monitoring/', views.stationsmonitoring_form, name="stations-monitoring"), - path('wx/stations/stations_monitoring/get/', views.get_stationsmonitoring_map_data), - path('wx/stations/stations_monitoring/get//', views.get_stationsmonitoring_station_data), - path('wx/stations/stations_monitoring/get///', views.get_stationsmonitoring_chart_data), - path('wx/maintenance_reports/equipment_inventory/', views.get_equipment_inventory, name="equipment-inventory"), - path('wx/maintenance_reports/equipment_inventory/get/', views.get_equipment_inventory_data), - path('wx/maintenance_reports/equipment_inventory/create/', views.create_equipment), - path('wx/maintenance_reports/equipment_inventory/delete/', views.delete_equipment), - path('wx/maintenance_reports/equipment_inventory/update/', views.update_equipment), + path('wx/maintenance_reports/get_available_profile_equipments///', login_required(views.get_maintenance_report_view), name='view-maintenance-report'), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/api/wx/views.py b/api/wx/views.py index cd5cff5..b9d8a7c 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -4074,6 +4074,30 @@ def get_equipment_inventory_data(request): } return JsonResponse(response, status=status.HTTP_200_OK) +from wx.models import StationProfileEquipmentType, MaintenanceReportEquipment + +def is_equipment_full(station, equipment_type): + if station.profile_id is None: + message = f"The station {station.name} - {station.code} is not associtated with any station profile." + return True, message + else: + station_profile = StationProfile.objects.get(id=station.profile_id) + try: + stationprofile_equipmenttypes = StationProfileEquipmentType.objects.filter(profile_id=station_profile.id, equipment_type_id=equipment_type.id) + max_equipments = max(entry.equipment_order for entry in stationprofile_equipmenttypes) + + equipments = Equipments.objects.filter(equipment_type_id=equipment_type.id, location=station.id) + if equipments.count() < max_equipments: + return False, '' + else: + message = f"The number of {equipment_type.name}s in {station.name} - {station.code} is {equipments.count()}. \ + The maximum number setted is {StationProfileEquipmentType.cardinality}." + return True, message + except ObjectDoesNotExist: + message = f"The station profile, {station_profile.name}, of the location \ + is not associtated with the equipment type {equipment_type}. \ + Please create a proper 'Station profile equipment types' object." + return True, message @require_http_methods(["POST"]) def create_equipment(request): @@ -4115,6 +4139,12 @@ def create_equipment(request): return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) except ObjectDoesNotExist: + if location: + is_full, message = is_equipment_full(location, equipment_type) + if is_full: + response = {'message': message} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + now = datetime.datetime.now() equipment = Equipment.objects.create( @@ -4181,6 +4211,12 @@ def update_equipment(request): try: equipment = Equipment.objects.get(id=equipment_id) + if location and equipment_type != equipment.equipment_type: + is_full, message = is_equipment_full(location, equipment_type) + if is_full: + response = {'message': message} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + now = datetime.datetime.now() equipment.updated_at = now @@ -4224,6 +4260,11 @@ def delete_equipment(request): return JsonResponse(response, status=status.HTTP_200_OK) +################################################################ +################################################################ + +############################# List ############################# + @require_http_methods(["GET"]) def get_maintenance_reports(request): # Maintenance report page template = loader.get_template('wx/maintenance_reports/maintenance_reports.html') @@ -4314,6 +4355,7 @@ def approve_maintenance_report(request, id): response={} return JsonResponse(response, status=status.HTTP_200_OK) +############################# View ############################# @require_http_methods(["GET"]) def get_maintenance_report_view(request, id, source): # Maintenance report view @@ -4400,6 +4442,26 @@ def get_maintenance_report_view(request, id, source): # Maintenance report view return HttpResponse(template.render(context, request)) +############################# New ############################## + +def get_ckeditor_config(): + ckeditor_config = { + 'toolbar': [ + ['Bold', 'Italic', 'Font'], + ['Format', 'Styles', 'TextColor', 'BGColor', 'RemoveFormat'], + ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock', 'Indent', 'Outdent'], + ['HorizontalRule', 'BulletedList'], + ['Blockquote', 'Source', 'Link', 'Unlink', 'Image', 'Table', 'Print'] + ], + 'removeButtons': 'Image', + 'extraAllowedContent' : 'img(*){*}[*]', + 'editorplaceholder': 'Description of station upon arribal:', + 'language': 'en', + } + return ckeditor_config + + + def get_maintenance_report_obj(maintenance_report): station = Station.objects.get(id=maintenance_report.station_id) station_profile = StationProfile.objects.get(id=station.profile_id) @@ -4429,6 +4491,72 @@ def get_station_contacts(station_id): return None +################################################## + + +@require_http_methods(["GET"]) # Create maintenance report from sratch +def get_available_profile_equipments(request, maintenance_report_id, equipment_type_id): + maintenance_report = MaintenanceReport.objects.get(id=maintenance_report_id) + equipment_type = EquipmentType.objects.get(id=equipment_type_id) + + station = Station.objects.get(id=maintenance_report.station_id) + station_profile = StationProfile.objects.get(id=station.profile_id) + + maintenance_report_equipments = MaintenanceReportEquipment.objects.filter(maintenance_report=maintenance_report) + used_ids = [entry.profile_equipment_id for entry in maintenance_report_equipments] + + profile_equipments = StationProfileEquipmentType.objects.filter(profile=station_profile.id, equipment_type=equipment_type.id) + profile_equipments = station_profile_equipment_type.exclude(id__in=used_ids) + + return profile_equipments + +def create_new_maintenance_report_equipment(maintenance_report, profile_equipment, equipment): + equipment_type = EquipmentType.objects.get(id=equipment.equipment_type_id) + available_profile_equipments = get_available_profile_equipments(maintenance_report, equipment_type) + + if not profile_equipment in available_profile_equipments: + print('Error profile_equipment is not availabe') + else: + MaintenanceReportEquipment.objects.create( + profile_equipment = profile_equipment, + maintenance_report = maintenance_report, + old_equipment = None, + current_equipment = equipment, + condition = equipment_type.report_template, + classification = equipment.classification, + ) + +################################################## + +def get_last_maintenance_report(maintenance_report): + station_id = maintenance_report.station_id + visit_date = maintenance_report.visit_date + + last_maintenance_report = MaintenanceReport.objects.filter( + station_id=station_id, + visit_date__lt=visit_date).latest('visit_date') + + return last_maintenance_report + +def copy_last_maintenance_report_equipments(maintenance_report): + last_maintenance_report = get_last_maintenance_report(maintenance_report) + + if last_maintenance_report: + last_maintenance_report_equipments = MaintenanceReportEquipment.objects.filter(maintenance_report=last_maintenance_report) + for maintenance_report_equipment in last_maintenance_report_equipments: + equipment = Equipment.objects.get(id=maintenance_report_equipment.current_equipment_id) + equipment_type = EquipmentType.objects.get(id=equipment.equipment_type_id) + + created_object = MaintenanceReportEquipment.objects.create( + created_at = now, + updated_at = now, + profile_equipment = maintenance_report_equipment.profile_equipment, + maintenance_report = maintenance_report, + old_equipment = equipment, + current_equipment = equipment, + condition = equipment_type.report_template, + classification = equipment.condition, + ) @require_http_methods(["POST"]) # Create maintenance report from sratch def create_maintenance_report(request): @@ -4436,65 +4564,40 @@ def create_maintenance_report(request): form_data = json.loads(request.body.decode()) + # Hard delete the maintenance report if its status is soft delete. try: - maintenance_report = MaintenanceReport.objects.get(station_id = form_data['station_id'], visit_date = form_data['visit_date']) - + maintenance_report = MaintenanceReport.objects.get(station_id=form_data['station_id'], visit_date=form_data['visit_date']) if maintenance_report.status=="-": maintenance_report.delete() except ObjectDoesNotExist: pass + + # Check if maintenance report already exists or create. try: maintenance_report = MaintenanceReport.objects.get(station_id = form_data['station_id'], visit_date = form_data['visit_date']) response = {"message": "Maintenance report already exist for chosen station and date."} return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - except ObjectDoesNotExist: + except ObjectDoesNotExist: maintenance_report = MaintenanceReport.objects.create( - created_at = now, - updated_at = now, - station_id = form_data['station_id'], - responsible_technician_id = form_data['responsible_technician_id'], - visit_type_id = form_data['visit_type_id'], - visit_date = form_data['visit_date'], - initial_time = form_data['initial_time'], - contacts = get_station_contacts(form_data['station_id']), + created_at = now, + updated_at = now, + station_id = form_data['station_id'], + responsible_technician_id = form_data['responsible_technician_id'], + visit_type_id = form_data['visit_type_id'], + visit_date = form_data['visit_date'], + initial_time = form_data['initial_time'], + contacts = get_station_contacts(form_data['station_id']), ) - station = Station.objects.get(pk=maintenance_report.station_id) - station_profile_component_list = StationProfileComponent.objects.filter(profile_id = station.profile_id) - - for station_profile_component in station_profile_component_list: - station_component = StationComponent.objects.get(id=station_profile_component.station_component_id) - - maintenance_report_station_component = MaintenanceReportStationComponent.objects.create( - maintenance_report_id = maintenance_report.id, - station_component_id = station_component.id, - condition = station_component.report_template, - ) + copy_last_maintenance_report_equipments(maintenance_report) response={"maintenance_report_id": maintenance_report.id} return JsonResponse(response, status=status.HTTP_200_OK) -def get_ckeditor_config(): - ckeditor_config = { - 'toolbar': [ - ['Bold', 'Italic', 'Font'], - ['Format', 'Styles', 'TextColor', 'BGColor', 'RemoveFormat'], - ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock', 'Indent', 'Outdent'], - ['HorizontalRule', 'BulletedList'], - ['Blockquote', 'Source', 'Link', 'Unlink', 'Image', 'Table', 'Print'] - ], - 'removeButtons': 'Image', - 'extraAllowedContent' : 'img(*){*}[*]', - 'editorplaceholder': 'Description of station upon arribal:', - 'language': 'en', - } - return ckeditor_config - def get_component_list(maintenance_report): station, station_profile, technician, visit_type = get_maintenance_report_obj(maintenance_report) @@ -4524,7 +4627,40 @@ def get_component_list(maintenance_report): return maintenance_report_station_component_list -@require_http_methods(["GET"]) +def get_station_equipment_types(station): + profile_equipments = StationProfileEquipmentType.objects.filter(profile=station.profile_id).distinct('equipment_type') + + equipment_type_ids = [profile_equipment.equipment_type_id for profile_equipment in profile_equipments] + equipment_types = EquipmentType.objects.filter(id__in=equipment_type_ids) + # return equipment_type + + equipment_type_list = [] + + for equipment_type in equipment_types: + is_full, msg = is_equipment_full(station, equipment_type) + dictionary = {'key':equipment_type.id, + 'id':equipment_type.id, + 'name': equipment_type.name + 'is_full': is_full} + + equipment_type_list.append(equipment_type) + + return equipment_type_list + +# def get_equipment_type_steps(station): +# profile_equipments = StationProfileEquipmentType.objects.filter(profile=station.profile_id).distinct('equipment_type') + +# equipment_type_ids = [profile_equipment.equipment_type_id for profile_equipment in profile_equipments] +# equipment_types = EquipmentType.objects.filter(id__in=equipment_type_ids) + +# steps = [{ 'title': equipment_type.name, +# 'complete': False, +# 'editable': True } for equipment_type in equipment_types] + +# return steps + + +@require_http_methods(["GET"]) # Ok def get_maintenance_report(request, id): maintenance_report = MaintenanceReport.objects.get(id=id) @@ -4550,6 +4686,8 @@ def get_maintenance_report(request, id): "data_of_relocation": station.relocation_date, } + + response["station_id"] = station.id response["responsible_technician"] = technician.name response["responsible_technician_id"] = maintenance_report.responsible_technician_id @@ -4567,8 +4705,9 @@ def get_maintenance_report(request, id): response["other_technician_3"] = maintenance_report.other_technician_3_id response['contacts'] = maintenance_report.contacts - response['components'] = get_component_list(maintenance_report) - response['steps'] = len(response['components']) + # response['equipment_types'] = equipment_types + response['equipment_types'] = get_station_equipment_types(station) + response['steps'] = len(response['equipment_types']) if maintenance_report.data_logger_file_name is None: response['data_logger_file_name'] = "Upload latest data logger program" @@ -4577,8 +4716,9 @@ def get_maintenance_report(request, id): return JsonResponse(response, status=status.HTTP_200_OK) +### -@require_http_methods(["PUT"]) +@require_http_methods(["PUT"]) # Ok def update_maintenance_report_condition(request, id): now = datetime.datetime.now() @@ -4615,7 +4755,7 @@ def update_maintenance_report_component(request, id, component_id): return JsonResponse(response, status=status.HTTP_200_OK) -@require_http_methods(["PUT"]) +@require_http_methods(["PUT"]) # Ok def update_maintenance_report_contacts(request, id): now = datetime.datetime.now() @@ -4633,7 +4773,7 @@ def update_maintenance_report_contacts(request, id): return JsonResponse(response, status=status.HTTP_200_OK) -@require_http_methods(["POST"]) +@require_http_methods(["POST"]) # Ok def update_maintenance_report_datalogger(request, id): # print(request.FILES) if 'data_logger_file' in request.FILES: @@ -4657,7 +4797,7 @@ def update_maintenance_report_datalogger(request, id): return JsonResponse(response, status=status.HTTP_206_PARTIAL_CONTENT) -@require_http_methods(["PUT"]) +@require_http_methods(["PUT"]) # Ok def update_maintenance_report_summary(request, id): now = datetime.datetime.now() @@ -4697,6 +4837,9 @@ def update_maintenance_report_summary(request, id): return JsonResponse(response, status=status.HTTP_200_OK) +################################################################ +################################################################ + class SpatialAnalysisView(LoginRequiredMixin, TemplateView): template_name = "wx/spatial_analysis.html" diff --git a/api/wx/views_old.py b/api/wx/views_old.py new file mode 100644 index 0000000..5d2947e --- /dev/null +++ b/api/wx/views_old.py @@ -0,0 +1,6291 @@ +import datetime +import io +import json +import logging +import os +import random +import uuid +from datetime import datetime as datetime_constructor +from datetime import timezone + +import matplotlib + +matplotlib.use("Agg") +import cv2 +import matplotlib.pyplot as plt +import pandas as pd +import psycopg2 +import pytz +from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.messages.views import SuccessMessageMixin +from django.core.cache import cache +from django.core.exceptions import ObjectDoesNotExist +from django.db import connection +from django.http import HttpResponse, JsonResponse +from django.template import loader +from django.urls import reverse +from django.views.decorators.csrf import csrf_exempt +from django.views.generic.base import TemplateView +from django.views.generic.detail import DetailView +from django.views.generic.edit import CreateView, UpdateView, DeleteView +from django.views.generic.list import ListView +from geopandas import geopandas +from material import * +from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas +from matplotlib.transforms import Bbox +from metpy.interpolate import interpolate_to_grid +from pandas import json_normalize +from rest_framework import viewsets, status, generics, views +from rest_framework.decorators import api_view, permission_classes +from rest_framework.parsers import FileUploadParser +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from slugify import slugify + +from tempestas_api import settings +from wx import serializers, tasks +from wx.decoders import insert_raw_data_pgia +from wx.decoders.hobo import read_file as read_file_hobo +from wx.decoders.toa5 import read_file +from wx.forms import StationForm +from wx.models import AdministrativeRegion, StationFile, Decoder, QualityFlag, DataFile, DataFileStation, \ + DataFileVariable, StationImage, WMOStationType, WMORegion, WMOProgram, StationCommunication +from wx.models import Country, Unit, Station, Variable, DataSource, StationVariable, \ + StationProfile, Document, Watershed, Interval +from wx.utils import get_altitude, get_watershed, get_district, get_interpolation_image, parse_float_value, \ + parse_int_value +from .utils import get_raw_data, get_station_raw_data +from wx.models import MaintenanceReport, MaintenanceReportStationComponent, StationProfileComponent, StationComponent, VisitType, Technician +from django.views.decorators.http import require_http_methods +from base64 import b64encode + +from wx.models import QualityFlag +import time +from wx.models import HighFrequencyData, MeasurementVariable +from wx.tasks import fft_decompose +import math +import numpy as np + +logger = logging.getLogger('surface.urls') + +# CONSTANT to be used in datetime to milliseconds conversion +EPOCH = datetime_constructor(1970, 1, 1, tzinfo=timezone.utc) + + +@csrf_exempt +def ScheduleDataExport(request): + if request.method != 'POST': + return HttpResponse(status=405) + + json_body = json.loads(request.body) + station_ids = json_body['stations'] # array with station ids + data_source = json_body[ + 'source'] # one of raw_data, hourly_summary, daily_summary, monthly_summary or yearly_summary + start_date = json_body['start_datetime'] # in format %Y-%m-%d %H:%M:%S + end_date = json_body['end_datetime'] # in format %Y-%m-%d %H:%M:%S + variable_ids = json_body['variables'] # list of obj in format {id: Int, agg: Str} + + data_interval_seconds = None + if data_source == 'raw_data' and 'data_interval' in json_body: # a number with the data interval in seconds. Only required for raw_data + data_interval_seconds = json_body['data_interval'] + elif data_source == 'raw_data': + data_interval_seconds = 300 + + created_data_file_ids = [] + start_date_utc = pytz.UTC.localize(datetime.datetime.strptime(start_date, '%Y-%m-%d %H:%M:%S')) + end_date_utc = pytz.UTC.localize(datetime.datetime.strptime(end_date, '%Y-%m-%d %H:%M:%S')) + + if start_date_utc > end_date_utc: + message = 'The initial date must be greater than final date.' + return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) + + days_interval = (end_date_utc - start_date_utc).days + + data_source_dict = { + "raw_data": "Raw Data", + "hourly_summary": "Hourly Summary", + "daily_summary": "Daily Summary", + "monthly_summary": "Monthly Summary", + "yearly_summary": "Yearly Summary", + } + + data_source_description = data_source_dict[data_source] + + prepared_by = None + if request.user.first_name and request.user.last_name: + prepared_by = f'{request.user.first_name} {request.user.last_name}' + else: + prepared_by = request.user.username + + for station_id in station_ids: + newfile = DataFile.objects.create(ready=False, initial_date=start_date_utc, final_date=end_date_utc, + source=data_source_description, prepared_by=prepared_by, + interval_in_seconds=data_interval_seconds) + DataFileStation.objects.create(datafile=newfile, station_id=station_id) + + for variable_id in variable_ids: + variable = Variable.objects.get(pk=variable_id) + DataFileVariable.objects.create(datafile=newfile, variable=variable) + + tasks.export_data.delay(station_id, data_source, start_date, end_date, variable_ids, newfile.id) + created_data_file_ids.append(newfile.id) + + return HttpResponse(created_data_file_ids, status=status.HTTP_200_OK) + + +@api_view(('GET',)) +def DataExportFiles(request): + files = [] + for df in DataFile.objects.all().order_by('-created_at').values()[:100:1]: + if df['ready'] and df['ready_at']: + file_status = {'text': "Ready", 'value': 1} + elif df['ready_at']: + file_status = {'text': "Error", 'value': 2} + else: + file_status = {'text': "Processing", 'value': 0} + + current_station_name = None + try: + current_data_file = DataFileStation.objects.get(datafile_id=df['id']) + current_station = Station.objects.get(pk=current_data_file.station_id) + current_station_name = current_station.name + except ObjectDoesNotExist: + current_station_name = "Station not found" + + f = { + 'id': df['id'], + 'request_date': df['created_at'], + 'ready_date': df['ready_at'], + 'station': current_station_name, + 'variables': [], + 'status': file_status, + 'initial_date': df['initial_date'], + 'final_date': df['final_date'], + 'source': {'text': df['source'], + 'value': 0 if df['source'] == 'Raw data' else (1 if df['source'] == 'Hourly summary' else 2)}, + 'lines': df['lines'], + 'prepared_by': df['prepared_by'] + } + if f['ready_date'] is not None: + f['ready_date'] = f['ready_date'] + for fv in DataFileVariable.objects.filter(datafile_id=df['id']).values(): + f['variables'].append(Variable.objects.filter(pk=fv['variable_id']).values()[0]['name']) + files.append(f) + + return Response(files, status=status.HTTP_200_OK) + + +def DownloadDataFile(request): + file_id = request.GET.get('id', None) + file_path = os.path.join('/data', 'exported_data', str(file_id) + '.csv') + if os.path.exists(file_path): + with open(file_path, 'rb') as fh: + response = HttpResponse(fh.read(), content_type="text/csv") + response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path) + return response + return JsonResponse({}, status=status.HTTP_404_NOT_FOUND) + + +def DeleteDataFile(request): + file_id = request.GET.get('id', None) + df = DataFile.objects.get(pk=file_id) + DataFileStation.objects.filter(datafile=df).delete() + DataFileVariable.objects.filter(datafile=df).delete() + df.delete() + file_path = os.path.join('/data', 'exported_data', str(file_id) + '.csv') + if os.path.exists(file_path): + os.remove(file_path) + return JsonResponse({}, status=status.HTTP_200_OK) + + +def GetInterpolationData(request): + start_datetime = request.GET.get('start_datetime', None) + end_datetime = request.GET.get('end_datetime', None) + variable_id = request.GET.get('variable_id', None) + agg = request.GET.get('agg', "instant") + source = request.GET.get('source', "raw_data") + quality_flags = request.GET.get('quality_flags', None) + + where_query = "" + if source == "raw_data": + dt_query = "datetime" + value_query = "measured" + source_query = "raw_data" + if quality_flags: + try: + [int(qf) for qf in quality_flags.split(',')] + except ValueError: + return JsonResponse({"message": "Invalid quality_flags value."}, status=status.HTTP_400_BAD_REQUEST) + where_query = f" measured != {settings.MISSING_VALUE} AND quality_flag IN ({quality_flags}) AND " + else: + where_query = f" measured != {settings.MISSING_VALUE} AND " + else: + with connection.cursor() as cursor: + cursor.execute(""" + SELECT sampling_operation_id + FROM wx_variable + WHERE id=%(variable_id)s; + """, + params={'variable_id': variable_id} + ) + sampling_operation = cursor.fetchone()[0] + + if sampling_operation in [6, 7]: + value_query = "sum_value" + elif sampling_operation == 3: + value_query = "min_value" + elif sampling_operation == 4: + value_query = "max_value" + else: + value_query = "avg_value" + + if source == "hourly": + dt_query = "datetime" + source_query = "hourly_summary" + + elif source == "daily": + dt_query = "day" + source_query = "daily_summary" + + elif source == "monthly": + dt_query = "date" + source_query = "monthly_summary" + + elif source == "yearly": + dt_query = "date" + source_query = "yearly_summary" + + if agg == "instant": + where_query += "variable_id=%(variable_id)s AND " + dt_query + "=%(datetime)s" + params = {'datetime': start_datetime, 'variable_id': variable_id} + else: + where_query += "variable_id=%(variable_id)s AND " + dt_query + " >= %(start_datetime)s AND " + dt_query + " <= %(end_datetime)s" + params = {'start_datetime': start_datetime, 'end_datetime': end_datetime, 'variable_id': variable_id} + + with connection.cursor() as cursor: + cursor.execute(""" + SELECT a.station_id,b.name,b.code,b.latitude,b.longitude,a.""" + value_query + """ as measured + FROM """ + source_query + """ a INNER JOIN wx_station b ON a.station_id=b.id + WHERE """ + where_query + ";", + params=params + ) + climate_data = {} + # if agg == "instant": + raw_data = cursor.fetchall() + climate_data['data'] = [] + for item in raw_data: + climate_data['data'].append({ + 'station_id': item[0], + 'name': item[1], + 'code': item[2], + 'latitude': item[3], + 'longitude': item[4], + 'measured': item[5], + }) + + if agg != "instant" and len(raw_data) > 0: + columns = ['station_id', 'name', 'code', 'latitude', 'longitude', 'measured'] + df_climate = json_normalize([ + dict(zip(columns, row)) + for row in raw_data + ]) + + climate_data['data'] = json.loads( + df_climate.groupby(['station_id', 'name', 'code', 'longitude', 'latitude']).agg( + agg).reset_index().sort_values('name').to_json(orient="records")) + + return JsonResponse(climate_data) + + +def GetInterpolationImage(request): + start_datetime = request.GET.get('start_datetime', None) + end_datetime = request.GET.get('end_datetime', None) + variable_id = request.GET.get('variable_id', None) + cmap = request.GET.get('cmap', 'Spectral_r') + hres = request.GET.get('hres', 0.01) + minimum_neighbors = request.GET.get('minimum_neighbors', 1) + search_radius = request.GET.get('search_radius', 0.7) + agg = request.GET.get('agg', "instant") + source = request.GET.get('source', "raw_data") + vmin = request.GET.get('vmin', 0) + vmax = request.GET.get('vmax', 30) + quality_flags = request.GET.get('quality_flags', None) + + stations_df = pd.read_sql_query(""" + SELECT id,name,alias_name,code,latitude,longitude + FROM wx_station + WHERE longitude!=0; + """, + con=connection + ) + stations = geopandas.GeoDataFrame( + stations_df, geometry=geopandas.points_from_xy(stations_df.longitude, stations_df.latitude)) + stations.crs = 'epsg:4326' + + stands_llat = settings.SPATIAL_ANALYSIS_INITIAL_LATITUDE + stands_llon = settings.SPATIAL_ANALYSIS_INITIAL_LONGITUDE + stands_ulat = settings.SPATIAL_ANALYSIS_FINAL_LATITUDE + stands_ulon = settings.SPATIAL_ANALYSIS_FINAL_LONGITUDE + + where_query = "" + if source == "raw_data": + dt_query = "datetime" + value_query = "measured" + source_query = "raw_data" + if quality_flags: + try: + [int(qf) for qf in quality_flags.split(',')] + except ValueError: + return JsonResponse({"message": "Invalid quality_flags value."}, status=status.HTTP_400_BAD_REQUEST) + where_query = f" measured != {settings.MISSING_VALUE} AND quality_flag IN ({quality_flags}) AND " + else: + where_query = f" measured != {settings.MISSING_VALUE} AND " + else: + with connection.cursor() as cursor: + cursor.execute(""" + SELECT sampling_operation_id + FROM wx_variable + WHERE id=%(variable_id)s; + """, + params={'variable_id': variable_id} + ) + sampling_operation = cursor.fetchone()[0] + + if sampling_operation in [6, 7]: + value_query = "sum_value" + elif sampling_operation == 3: + value_query = "min_value" + elif sampling_operation == 4: + value_query = "max_value" + else: + value_query = "avg_value" + + if source == "hourly": + dt_query = "datetime" + source_query = "hourly_summary" + + elif source == "daily": + dt_query = "day" + source_query = "daily_summary" + + elif source == "monthly": + dt_query = "date" + source_query = "monthly_summary" + + elif source == "yearly": + dt_query = "date" + source_query = "yearly_summary" + + if agg == "instant": + where_query += "variable_id=%(variable_id)s AND " + dt_query + "=%(datetime)s" + params = {'datetime': start_datetime, 'variable_id': variable_id} + else: + where_query += "variable_id=%(variable_id)s AND " + dt_query + " >= %(start_datetime)s AND " + dt_query + " <= %(end_datetime)s" + params = {'start_datetime': start_datetime, 'end_datetime': end_datetime, 'variable_id': variable_id} + + climate_data = pd.read_sql_query( + "SELECT station_id,variable_id," + dt_query + "," + value_query + """ + FROM """ + source_query + """ + WHERE """ + where_query + ";", + params=params, + con=connection + ) + + if len(climate_data) == 0: + with open("/surface/static/images/no-interpolated-data.png", "rb") as f: + img_data = f.read() + + return HttpResponse(img_data, content_type="image/jpeg") + + df_merged = pd.merge(left=climate_data, right=stations, how='left', left_on='station_id', right_on='id') + df_climate = df_merged[["station_id", dt_query, "longitude", "latitude", value_query]] + + if agg != "instant": + df_climate = df_climate.groupby(['station_id', 'longitude', 'latitude']).agg(agg).reset_index() + + gx, gy, img = interpolate_to_grid( + df_climate["longitude"], + df_climate["latitude"], + df_climate[value_query], + interp_type='cressman', + minimum_neighbors=int(minimum_neighbors), + hres=float(hres), + search_radius=float(search_radius), + boundary_coords={'west': stands_llon, 'east': stands_ulon, 'south': stands_llat, 'north': stands_ulat} + ) + + fig = plt.figure(frameon=False) + ax = plt.Axes(fig, [0., 0., 1., 1.]) + ax.set_axis_off() + fig.add_axes(ax) + ax.imshow(img, origin='lower', cmap=cmap, vmin=vmin, vmax=vmax) + fname = str(uuid.uuid4()) + fig.savefig("/surface/static/images/" + fname + ".png", dpi='figure', format='png', transparent=True, + bbox_inches=Bbox.from_bounds(2, 0, 2.333, 4.013)) + image1 = cv2.imread("/surface/static/images/" + fname + ".png", cv2.IMREAD_UNCHANGED) + image2 = cv2.imread(settings.SPATIAL_ANALYSIS_SHAPE_FILE_PATH, cv2.IMREAD_UNCHANGED) + image1 = cv2.resize(image1, dsize=(image2.shape[1], image2.shape[0])) + for i in range(image1.shape[0]): + for j in range(image1.shape[1]): + image1[i][j][3] = image2[i][j][3] + cv2.imwrite("/surface/static/images/" + fname + "-output.png", image1) + + with open("/surface/static/images/" + fname + "-output.png", "rb") as f: + img_data = f.read() + + os.remove("/surface/static/images/" + fname + ".png") + os.remove("/surface/static/images/" + fname + "-output.png") + + return HttpResponse(img_data, content_type="image/jpeg") + + +def GetColorMapBar(request): + start_datetime_req = request.GET.get('start_datetime', '') + end_datetime_req = request.GET.get('end_datetime', '') + variable_id = request.GET.get('variable_id', '') + cmap = request.GET.get('cmap', 'Spectral_r') + agg = request.GET.get('agg', "instant") + vmin = request.GET.get('vmin', 0) + vmax = request.GET.get('vmax', 30) + + try: + start_datetime = pytz.UTC.localize(datetime.datetime.strptime(start_datetime_req, '%Y-%m-%dT%H:%M:%S.%fZ')) + end_datetime = pytz.UTC.localize(datetime.datetime.strptime(end_datetime_req, '%Y-%m-%dT%H:%M:%S.%fZ')) + + start_datetime = start_datetime.astimezone(pytz.timezone(settings.TIMEZONE_NAME)) + end_datetime = end_datetime.astimezone(pytz.timezone(settings.TIMEZONE_NAME)) + except ValueError: + try: + start_datetime = pytz.UTC.localize(datetime.datetime.strptime(start_datetime_req, '%Y-%m-%d')) + end_datetime = pytz.UTC.localize(datetime.datetime.strptime(end_datetime_req, '%Y-%m-%d')) + + except ValueError: + return JsonResponse({"message": "Invalid date format"}, status=status.HTTP_400_BAD_REQUEST) + + with connection.cursor() as cursor: + cursor.execute(""" + SELECT a.name,b.symbol + FROM wx_variable a INNER JOIN wx_unit b ON a.unit_id=b.id + WHERE a.id=%(variable_id)s; + """, + params={'variable_id': variable_id} + ) + variable = cursor.fetchone() + + fig = plt.figure(figsize=(9, 1.5)) + plt.imshow([[2]], origin='lower', cmap=cmap, vmin=vmin, vmax=vmax) + plt.gca().set_visible(False) + cax = plt.axes([0.1, 0.2, 0.8, 0.2]) + title = variable[0] + ' (' + variable[1] + ') - ' + start_datetime.strftime("%d/%m/%Y %H:%M:%S") + if agg != 'instant': + title += ' to ' + end_datetime.strftime("%d/%m/%Y %H:%M:%S") + title += ' (' + agg + ')' + cax.set_title(title) + plt.colorbar(orientation='horizontal', cax=cax) + + FigureCanvas(fig) + buf = io.BytesIO() + plt.savefig(buf, dpi='figure', format='png', transparent=True, bbox_inches='tight') + plt.close(fig) + response = HttpResponse(buf.getvalue(), content_type='image/png') + + return response + + +@csrf_exempt +def InterpolatePostData(request): + if request.method != 'POST': + return HttpResponse(status=405) + + stands_llat = settings.SPATIAL_ANALYSIS_INITIAL_LATITUDE + stands_llon = settings.SPATIAL_ANALYSIS_INITIAL_LONGITUDE + stands_ulat = settings.SPATIAL_ANALYSIS_FINAL_LATITUDE + stands_ulon = settings.SPATIAL_ANALYSIS_FINAL_LONGITUDE + + json_body = json.loads(request.body) + parameters = json_body['parameters'] + vmin = json_body['vmin'] + vmax = json_body['vmax'] + df_climate = json_normalize(json_body['data']) + try: + df_climate = df_climate[["station_id", "longitude", "latitude", "measured"]] + except KeyError: + return HttpResponse("no-interpolated-data.png") + + gx, gy, img = interpolate_to_grid( + df_climate["longitude"], + df_climate["latitude"], + df_climate["measured"], + interp_type='cressman', + minimum_neighbors=int(parameters["minimum_neighbors"]), + hres=float(parameters["hres"]), + search_radius=float(parameters["search_radius"]), + boundary_coords={'west': stands_llon, 'east': stands_ulon, 'south': stands_llat, 'north': stands_ulat} + ) + + fig = plt.figure(frameon=False) + ax = plt.Axes(fig, [0., 0., 1., 1.]) + ax.set_axis_off() + fig.add_axes(ax) + ax.imshow(img, origin='lower', cmap=parameters["cmap"]["value"], vmin=vmin, vmax=vmax) + for filename in os.listdir('/surface/static/images'): + try: + if datetime.datetime.now() - datetime.datetime.strptime(filename.split('_')[0], + "%Y-%m-%dT%H:%M:%SZ") > datetime.timedelta( + minutes=5): + os.remove('/surface/static/images/' + filename) + except ValueError: + continue + fname = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ_") + str(uuid.uuid4()) + fig.savefig("/surface/static/images/" + fname + ".png", dpi='figure', format='png', transparent=True, + bbox_inches=Bbox.from_bounds(2, 0, 2.333, 4.013)) + image1 = cv2.imread("/surface/static/images/" + fname + ".png", cv2.IMREAD_UNCHANGED) + image2 = cv2.imread('/surface/static/images/blz_shape.png', cv2.IMREAD_UNCHANGED) + image1 = cv2.resize(image1, dsize=(image2.shape[1], image2.shape[0])) + for i in range(image1.shape[0]): + for j in range(image1.shape[1]): + image1[i][j][3] = image2[i][j][3] + cv2.imwrite("/surface/static/images/" + fname + "-output.png", image1) + + return HttpResponse(fname + "-output.png") + + +@permission_classes([IsAuthenticated]) +def GetImage(request): + image = request.GET.get('image', None) + try: + with open("/surface/static/images/" + image, "rb") as f: + return HttpResponse(f.read(), content_type="image/jpeg") + except IOError: + red = Image.new('RGBA', (1, 1), (255, 0, 0, 0)) + response = HttpResponse(content_type="image/jpeg") + red.save(response, "JPEG") + return response + + +from django.db.models.functions import Cast +from django.db.models import IntegerField + + +@permission_classes([IsAuthenticated]) +def DailyFormView(request): + template = loader.get_template('wx/daily_form.html') + + station_list = Station.objects.filter(is_automatic=False, is_active=True) + station_list = station_list.values('id', 'name', 'code') + + # for station in station_list: + # station['code'] = int(station['code']) + + context = {'station_list': station_list} + + return HttpResponse(template.render(context, request)) + + +class SynopCaptureView(LoginRequiredMixin, TemplateView): + template_name = "wx/synopcapture.html" + + +@permission_classes([IsAuthenticated]) +def DataCaptureView(request): + template = loader.get_template('wx/data_capture.html') + return HttpResponse(template.render({}, request)) + + +class DataExportView(LoginRequiredMixin, TemplateView): + template_name = "wx/data_export.html" + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + context['station_list'] = Station.objects.select_related('profile').all() + context['variable_list'] = Variable.objects.select_related('unit').all() + + context['station_profile_list'] = StationProfile.objects.all() + context['station_watershed_list'] = Watershed.objects.all() + context['station_district_list'] = AdministrativeRegion.objects.all() + + interval_list = Interval.objects.filter(seconds__lte=3600).order_by('seconds') + context['interval_list'] = interval_list + + return self.render_to_response(context) + + +class CountryViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = Country.objects.all().order_by("name") + serializer_class = serializers.CountrySerializer + + +class UnitViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = Unit.objects.all().order_by("name") + serializer_class = serializers.UnitSerializer + + +class DataSourceViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = DataSource.objects.all().order_by("name") + serializer_class = serializers.DataSourceSerializer + + +class VariableViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = Variable.objects.all().order_by("name") + serializer_class = serializers.VariableSerializer + + +class StationMetadataViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = Station.objects.all() + serializer_class = serializers.StationMetadataSerializer + + def get_serializer_class(self): + if self.request.method in ['GET']: + return serializers.StationSerializerRead + return serializers.StationMetadataSerializer + + +class StationViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = Station.objects.all() + + # queryset = Station.objects.all().select_related("country").order_by("name") + # def put(self, request, *args, **kwargs): + # station_object = Station.objects.get() + # data = request.data + + # station_object.save() + # serializer = serializers.StationSerializerWrite + + + def get_serializer_class(self): + if self.request.method in ['GET']: + return serializers.StationSerializerRead + + return serializers.StationSerializerWrite + + +class StationSimpleViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = Station.objects.all() + serializer_class = serializers.StationSimpleSerializer + + +class StationVariableViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = StationVariable.objects.all().order_by("variable") + serializer_class = serializers.StationVariableSerializer + + def get_queryset(self): + queryset = StationVariable.objects.all() + + station_id = self.request.query_params.get('station_id', None) + + if station_id is not None: + queryset = queryset.filter(station__id=station_id) + + return queryset + + +class StationProfileViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = StationProfile.objects.all().order_by("name") + serializer_class = serializers.StationProfileSerializer + + + +class DocumentViewSet(views.APIView): + permission_classes = (IsAuthenticated,) + parser_class = (FileUploadParser,) + queryset = Document.objects.all() + serializer_class = serializers.DocumentSerializer + available_decoders = {'HOBO': read_file_hobo, 'TOA5': read_file} + decoders = Decoder.objects.all().exclude(name='DCP TEXT').exclude(name='NESA') + + def put(self, request, format=None): + selected_decoder = 'TOA5' + + if 'decoder' in request.data.keys(): + selected_decoder = request.data['decoder'] + + serializer = serializers.DocumentSerializer(data=request.data) + + if serializer.is_valid(): + document = serializer.save() + self.available_decoders[selected_decoder].delay(document.file.path) + return Response({"message": "FIle uploaded successfully!"}, status=status.HTTP_201_CREATED) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def post(self, request, format=None): + return self.put(request, format=None) + + +class AdministrativeRegionViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = AdministrativeRegion.objects.all().order_by("name") + serializer_class = serializers.AdministrativeRegionSerializer + + +@api_view(['GET']) +def station_telemetry_data(request, date): + mock = { + 'temperature': { + 'min': 10, + 'max': 13, + 'avg': 16 + }, + 'relativeHumidity': { + 'min': 10, + 'max': 13, + 'avg': 16 + }, + 'precipitation': { + 'current': 123 + }, + 'windDirection': { + 'current': 'SW' + }, + 'windSpeed': { + 'current': 11, + 'max': 12 + }, + 'windGust': { + 'current': 12, + 'max': 11 + }, + 'solarRadiation': { + 'current': 12 + }, + 'atmosphericPressure': { + 'current': 11 + } + } + + data = { + 'latest': mock, + 'last24': mock, + 'current': mock, + } + + return Response(data) + + +def raw_data_list(request): + search_type = request.GET.get('search_type', None) + search_value = request.GET.get('search_value', None) + search_value2 = request.GET.get('search_value2', None) + search_date_start = request.GET.get( + 'search_date_start', + default=(datetime.datetime.now() - datetime.timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%SZ') + ) + search_date_end = request.GET.get( + 'search_date_end', + default=datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') + ) + sql_string = "" + + response = { + 'results': [], + 'messages': [], + } + + try: + start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%dT%H:%M:%SZ') + end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%dT%H:%M:%SZ') + + except ValueError: + message = 'Invalid date format. Expected YYYY-MM-DDTHH:MI:SSZ' + return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) + + delta = end_date - start_date + + if delta.days > 8: # Restrict queries to max seven days + message = 'Interval between start date and end date is greater than one week.' + return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) + + if search_type in ['station', 'stationvariable']: + try: + station = Station.objects.get(code=search_value) + except ObjectDoesNotExist: + station = Station.objects.get(pk=search_value) + finally: + search_value = station.id + + if search_type is not None and search_type == 'variable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + c.name, + c.symbol, + a.measured, + a.datetime, + q.symbol as quality_flag, + b.variable_type, + a.code + FROM raw_data a + JOIN wx_variable b ON a.variable_id=b.id + LEFT JOIN wx_unit c ON b.unit_id=c.id + JOIN wx_qualityflag q ON a.quality_flag=q.id + WHERE b.id = %s + AND datetime >= %s + AND datetime <= %s + """ + + if search_type is not None and search_type == 'station': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + c.name, + c.symbol, + a.measured, + a.datetime, + q.symbol as quality_flag, + b.variable_type, + a.code + FROM raw_data a + JOIN wx_variable b ON a.variable_id=b.id + LEFT JOIN wx_unit c ON b.unit_id=c.id + JOIN wx_qualityflag q ON a.quality_flag=q.id + WHERE station_id=%s AND datetime >= %s AND datetime <= %s""" + + if search_type is not None and search_type == 'stationvariable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + c.name, + c.symbol, + a.measured, + a.datetime, + q.symbol as quality_flag, + b.variable_type, + a.code + FROM raw_data a + JOIN wx_variable b ON a.variable_id=b.id + LEFT JOIN wx_unit c ON b.unit_id=c.id + JOIN wx_qualityflag q ON a.quality_flag=q.id + WHERE station_id=%s AND variable_id=%s AND datetime >= %s AND datetime <= %s""" + + if sql_string: + sql_string += " ORDER BY datetime" + + with connection.cursor() as cursor: + + if search_type is not None and search_type == 'stationvariable': + + cursor.execute(sql_string, [search_value, search_value2, search_date_start, search_date_end]) + + else: + + cursor.execute(sql_string, [search_value, search_date_start, search_date_end]) + + rows = cursor.fetchall() + + for row in rows: + + if row[9] is not None and row[9].lower() == 'code': + value = row[10] + else: + value = round(row[6], 2) + + obj = { + 'station': row[0], + 'date': row[7], + 'value': value, + 'variable': { + 'symbol': row[2], + 'name': row[3], + 'unit_name': row[4], + 'unit_symbol': row[5] + } + } + + response['results'].append(obj) + + if response['results']: + return JsonResponse(response, status=status.HTTP_200_OK) + + return JsonResponse(data={"message": "No data found."}, status=status.HTTP_404_NOT_FOUND) + + +def hourly_summary_list(request): + search_type = request.GET.get('search_type', None) + search_value = request.GET.get('search_value', None) + search_value2 = request.GET.get('search_value2', None) + search_date_start = request.GET.get( + 'search_date_start', + default=(datetime.datetime.now() - datetime.timedelta(days=30)).strftime('%Y-%m-%dT%H:%M:%SZ') + ) + search_date_end = request.GET.get( + 'search_date_end', + default=datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') + ) + sql_string = "" + + response = { + 'results': [], + 'messages': [], + } + + try: + start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%dT%H:%M:%SZ') + end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%dT%H:%M:%SZ') + + except ValueError: + message = 'Invalid date format. Expected YYYY-MM-DDTHH:MI:SSZ' + return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) + + delta = end_date - start_date + + if delta.days > 32: # Restrict queries to max 31 days + message = 'Interval between start date and end date is greater than one month.' + return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) + + if search_type in ['station', 'stationvariable']: + try: + station = Station.objects.get(code=search_value) + except ObjectDoesNotExist: + station = Station.objects.get(pk=search_value) + finally: + search_value = station.id + + if search_type is not None and search_type == 'variable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + num_records, + datetime as data + FROM hourly_summary a + INNER JOIN wx_variable b ON a.variable_id=b.id + INNER JOIN wx_unit c ON b.unit_id=c.id + WHERE b.id=%s AND datetime >= %s AND datetime <= %s""" + + if search_type is not None and search_type == 'station': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + num_records, + datetime as data + FROM hourly_summary a + INNER JOIN wx_variable b ON a.variable_id=b.id + INNER JOIN wx_unit c ON b.unit_id=c.id + WHERE station_id=%s AND datetime >= %s AND datetime <= %s""" + + if search_type is not None and search_type == 'stationvariable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + num_records, + datetime as data + FROM hourly_summary a + INNER JOIN wx_variable b ON a.variable_id=b.id + INNER JOIN wx_unit c ON b.unit_id=c.id + WHERE station_id=%s AND variable_id=%s AND datetime >= %s AND datetime <= %s""" + + if sql_string: + sql_string += " ORDER BY datetime" + + with connection.cursor() as cursor: + + if search_type is not None and search_type == 'stationvariable': + + cursor.execute(sql_string, [search_value, search_value2, search_date_start, search_date_end]) + + else: + + cursor.execute(sql_string, [search_value, search_date_start, search_date_end]) + + rows = cursor.fetchall() + + for row in rows: + + value = None + + if row[4] in [1, 2]: + value = row[9] + + elif row[4] == 3: + value = row[7] + + elif row[4] == 4: + value = row[8] + + elif row[4] == 6: + value = row[10] + + else: + value = row[10] + + if value is None: + + print('variable {} does not have supported sampling operation {}'.format(row[1], row[4])) + + else: + + obj = { + 'station': row[0], + 'date': row[12], + 'value': round(value, 2), + 'min': round(row[7], 2), + 'max': round(row[8], 2), + 'avg': round(row[9], 2), + 'sum': round(row[10], 2), + 'count': round(row[11], 2), + 'variable': { + 'symbol': row[2], + 'name': row[3], + 'unit_name': row[5], + 'unit_symbol': row[6], + } + } + + response['results'].append(obj) + + if response['results']: + return JsonResponse(response, status=status.HTTP_200_OK) + + return JsonResponse(data=response) + + +def daily_summary_list(request): + search_type = request.GET.get('search_type', None) + search_value = request.GET.get('search_value', None) + search_value2 = request.GET.get('search_value2', None) + search_date_start = request.GET.get( + 'search_date_start', + default=(datetime.datetime.now() - datetime.timedelta(days=365)).strftime('%Y-%m-%d') + ) + search_date_end = request.GET.get( + 'search_date_end', + default=datetime.datetime.now().strftime('%Y-%m-%d') + ) + sql_string = "" + + response = { + 'results': [], + 'messages': [], + } + + try: + start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%d') + end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%d') + + except ValueError: + message = 'Invalid date format. Expected YYYY-MM-DD' + return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) + + delta = end_date - start_date + + if delta.days > 400: # Restrict queries to max 400 days + message = 'Interval between start date and end date is greater than 13 months.' + return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) + + if search_type in ['station', 'stationvariable']: + try: + station = Station.objects.get(code=search_value) + except ObjectDoesNotExist: + station = Station.objects.get(pk=search_value) + finally: + search_value = station.id + + if search_type is not None and search_type == 'variable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + day, + num_records + FROM daily_summary a + INNER JOIN wx_variable b ON a.variable_id=b.id + INNER JOIN wx_unit c ON b.unit_id=c.id + WHERE b.id = %s + AND day >= %s + AND day <= %s""" + + if search_type is not None and search_type == 'station': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + day, + num_records + FROM daily_summary a + INNER JOIN wx_variable b ON a.variable_id=b.id + INNER JOIN wx_unit c ON b.unit_id=c.id + WHERE station_id=%s AND day >= %s AND day <= %s""" + + if search_type is not None and search_type == 'stationvariable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + day, + num_records + FROM daily_summary a + INNER JOIN wx_variable b ON a.variable_id=b.id + INNER JOIN wx_unit c ON b.unit_id=c.id + WHERE station_id=%s AND variable_id=%s AND day >= %s AND day <= %s""" + + if sql_string: + sql_string += " ORDER BY day" + + with connection.cursor() as cursor: + + if search_type is not None and search_type == 'stationvariable': + cursor.execute(sql_string, [search_value, search_value2, search_date_start, search_date_end]) + else: + cursor.execute(sql_string, [search_value, search_date_start, search_date_end]) + + rows = cursor.fetchall() + + for row in rows: + + value = None + + if row[4] in [1, 2]: + value = row[9] + + elif row[4] == 3: + value = row[7] + + elif row[4] == 4: + value = row[8] + + elif row[4] == 6: + value = row[10] + + else: + value = row[10] + + if value is not None: + + obj = { + 'station': row[0], + 'date': row[11], + 'value': round(value, 2), + 'min': round(row[7], 2), + 'max': round(row[8], 2), + 'avg': round(row[9], 2), + 'total': round(row[10], 2), + 'count': row[12], + 'variable': { + 'symbol': row[2], + 'name': row[3], + 'unit_name': row[5], + 'unit_symbol': row[6], + } + } + + response['results'].append(obj) + + else: + JsonResponse(data={ + "message": 'variable {} does not have supported sampling operation {}'.format(row[1], row[4])}, + status=status.HTTP_400_BAD_REQUEST) + + if response['results']: + return JsonResponse(response, status=status.HTTP_200_OK) + + return JsonResponse(data=response) + + +def monthly_summary_list(request): + search_type = request.GET.get('search_type', None) + search_value = request.GET.get('search_value', None) + search_value2 = request.GET.get('search_value2', None) + search_date_start = request.GET.get( + 'search_date_start', + default=(datetime.datetime.now() - datetime.timedelta(days=365)).strftime('%Y-%m-%d') + ) + search_date_end = request.GET.get( + 'search_date_end', + default=datetime.datetime.now().strftime('%Y-%m-%d') + ) + + sql_string = "" + + response = { + 'count': -999, + 'next': None, + 'previous': None, + 'results': [] + } + + try: + start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%d') + end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%d') + + except ValueError: + message = 'Invalid date format. Expected YYYY-MM-DD' + return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) + + if search_type in ['station', 'stationvariable']: + try: + station = Station.objects.get(code=search_value) + except ObjectDoesNotExist: + station = Station.objects.get(pk=search_value) + finally: + search_value = station.id + + if search_type is not None and search_type == 'variable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + date::date, + num_records + FROM monthly_summary a + JOIN wx_variable b ON a.variable_id=b.id + JOIN wx_unit c ON b.unit_id=c.id + WHERE b.id = %s + AND date >= %s + AND date <= %s + """ + + if search_type is not None and search_type == 'station': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + date::date, + num_records + FROM monthly_summary a + JOIN wx_variable b ON a.variable_id=b.id + JOIN wx_unit c ON b.unit_id=c.id + WHERE station_id = %s + AND date >= %s AND date <= %s + """ + + if search_type is not None and search_type == 'stationvariable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + date::date, + num_records + FROM monthly_summary a + JOIN wx_variable b ON a.variable_id=b.id + JOIN wx_unit c ON b.unit_id=c.id + WHERE station_id = %s + AND variable_id = %s + AND date >= %s AND date <= %s + """ + + if sql_string: + sql_string += " ORDER BY month" + + with connection.cursor() as cursor: + + if search_type is not None and search_type == 'stationvariable': + cursor.execute(sql_string, [search_value, search_value2, start_date, end_date]) + else: + cursor.execute(sql_string, [search_value, start_date, end_date]) + + rows = cursor.fetchall() + + for row in rows: + + value = None + + if row[4] in [1, 2]: + value = row[9] + + elif row[4] == 3: + value = row[7] + + elif row[4] == 4: + value = row[8] + + elif row[4] == 6: + value = row[10] + + else: + value = row[10] + + if value is not None: + + obj = { + 'station': row[0], + 'date': row[11], + 'value': round(value, 2), + 'min': round(row[7], 2), + 'max': round(row[8], 2), + 'avg': round(row[9], 2), + 'total': round(row[10], 2), + 'count': row[12], + 'variable': { + 'symbol': row[2], + 'name': row[3], + 'unit_name': row[5], + 'unit_symbol': row[6], + } + } + + response['results'].append(obj) + + else: + JsonResponse(data={ + "message": 'variable {} does not have supported sampling operation {}'.format(row[1], row[4])}, + status=status.HTTP_400_BAD_REQUEST) + + if response['results']: + return JsonResponse(response, status=status.HTTP_200_OK) + + return JsonResponse(data=response) + + +def yearly_summary_list(request): + search_type = request.GET.get('search_type', None) + search_value = request.GET.get('search_value', None) + search_value2 = request.GET.get('search_value2', None) + search_date_start = request.GET.get( + 'search_date_start', + default=(datetime.datetime.now() - datetime.timedelta(days=365)).strftime('%Y-%m-%d') + ) + search_date_end = request.GET.get( + 'search_date_end', + default=datetime.datetime.now().strftime('%Y-%m-%d') + ) + + sql_string = "" + + response = { + 'count': -999, + 'next': None, + 'previous': None, + 'results': [] + } + + try: + start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%d') + end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%d') + + except ValueError: + message = 'Invalid date format. Expected YYYY-MM-DD' + return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) + + if search_type in ['station', 'stationvariable']: + try: + station = Station.objects.get(code=search_value) + except ObjectDoesNotExist: + station = Station.objects.get(pk=search_value) + finally: + search_value = station.id + + if search_type is not None and search_type == 'variable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + date::date, + num_records + FROM yearly_summary a + JOIN wx_variable b ON a.variable_id=b.id + JOIN wx_unit c ON b.unit_id=c.id + WHERE b.id = %s + AND date >= %s + AND date <= %s + """ + + if search_type is not None and search_type == 'station': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + date::date, + num_records + FROM yearly_summary a + JOIN wx_variable b ON a.variable_id=b.id + JOIN wx_unit c ON b.unit_id=c.id + WHERE station_id = %s + AND date >= %s AND date <= %s + """ + + if search_type is not None and search_type == 'stationvariable': + sql_string = """ + SELECT station_id, + variable_id, + b.symbol, + b.name, + b.sampling_operation_id, + c.name, + c.symbol, + min_value, + max_value, + avg_value, + sum_value, + date::date, + num_records + FROM yearly_summary a + JOIN wx_variable b ON a.variable_id=b.id + JOIN wx_unit c ON b.unit_id=c.id + WHERE station_id = %s + AND variable_id = %s + AND date >= %s AND date <= %s + """ + + if sql_string: + sql_string += " ORDER BY year" + + with connection.cursor() as cursor: + + if search_type is not None and search_type == 'stationvariable': + cursor.execute(sql_string, [search_value, search_value2, start_date, end_date]) + else: + cursor.execute(sql_string, [search_value, start_date, end_date]) + + rows = cursor.fetchall() + + for row in rows: + + value = None + + if row[4] in [1, 2]: + value = row[9] + + elif row[4] == 3: + value = row[7] + + elif row[4] == 4: + value = row[8] + + elif row[4] == 6: + value = row[10] + + else: + value = row[10] + + if value is not None: + + obj = { + 'station': row[0], + 'date': row[11], + 'value': round(value, 2), + 'min': round(row[7], 2), + 'max': round(row[8], 2), + 'avg': round(row[9], 2), + 'total': round(row[10], 2), + 'count': row[12], + 'variable': { + 'symbol': row[2], + 'name': row[3], + 'unit_name': row[5], + 'unit_symbol': row[6], + } + } + + response['results'].append(obj) + + else: + JsonResponse(data={ + "message": 'variable {} does not have supported sampling operation {}'.format(row[1], row[4])}, + status=status.HTTP_400_BAD_REQUEST) + + if response['results']: + return JsonResponse(response, status=status.HTTP_200_OK) + + return JsonResponse(data=response) + + +@api_view(['GET']) +def station_geo_features(request, lon, lat): + longitude = float(lon) + latitude = float(lat) + + altitude = get_altitude(longitude, latitude) + + watershed = get_watershed(longitude, latitude) + + district = get_district(longitude, latitude) + + data = { + 'elevation': altitude, + 'watershed': watershed, + 'country': 'Belize', + 'administrative_region': district, + 'longitude': longitude, + 'latitude': latitude, + } + + return Response(data) + + +def get_last24_data(station_id): + result = [] + max_date = None + + query = """ + SELECT + last24h.datetime, + var.name, + var.symbol, + var.sampling_operation_id, + unit.name, + unit.symbol, + last24h.min_value, + last24h.max_value, + last24h.avg_value, + last24h.sum_value, + last24h.latest_value + FROM + last24h_summary last24h + INNER JOIN + wx_variable var ON last24h.variable_id=var.id + INNER JOIN + wx_unit unit ON var.unit_id=unit.id + WHERE + last24h.station_id=%s + ORDER BY var.name""" + + with connection.cursor() as cursor: + + cursor.execute(query, [station_id]) + + rows = cursor.fetchall() + + for row in rows: + + value = None + + if row[3] == 1: + value = row[10] + + elif row[3] == 2: + value = row[8] + + elif row[3] == 3: + value = row[6] + + elif row[3] == 4: + value = row[7] + + elif row[3] == 6: + value = row[9] + + if value is None: + print('variable {} does not have supported sampling operation {}'.format(row[1], row[3])) + + obj = { + 'value': value, + 'variable': { + 'name': row[1], + 'symbol': row[2], + 'unit_name': row[4], + 'unit_symbol': row[5] + } + } + result.append(obj) + + max_date = cache.get('last24h_summary_last_run', None) + + return result, max_date + + +def get_latest_data(station_id): + result = [] + max_date = None + + query = """ + SELECT CASE WHEN var.variable_type ilike 'code' THEN latest.last_data_code ELSE latest.last_data_value::varchar END as value, + latest.last_data_datetime, + var.name, + var.symbol, + unit.name, + unit.symbol + FROM wx_stationvariable latest + INNER JOIN wx_variable var ON latest.variable_id=var.id + LEFT JOIN wx_unit unit ON var.unit_id=unit.id + WHERE latest.station_id=%s + AND latest.last_data_value is not null + AND latest.last_data_datetime = ( SELECT MAX(most_recent.last_data_datetime) + FROM wx_stationvariable most_recent + WHERE most_recent.station_id=latest.station_id + AND most_recent.last_data_value is not null) + ORDER BY var.name + """ + + with connection.cursor() as cursor: + + cursor.execute(query, [station_id]) + + rows = cursor.fetchall() + + for row in rows: + obj = { + 'value': row[0], + 'variable': { + 'name': row[2], + 'symbol': row[3], + 'unit_name': row[4], + 'unit_symbol': row[5] + } + } + result.append(obj) + + if rows: + max_date = rows[-1][1] + + return result, max_date + + +def get_current_data(station_id): + result = [] + max_date = None + parameter_timezone = pytz.timezone(settings.TIMEZONE_NAME) + today = datetime.datetime.now().astimezone(parameter_timezone).date() + + query = """ + SELECT current.day, + var.name, + var.symbol, + var.sampling_operation_id, + unit.name, + unit.symbol, + current.min_value, + current.max_value, + current.avg_value, + current.sum_value + FROM daily_summary current + INNER JOIN wx_variable var ON current.variable_id=var.id + INNER JOIN wx_unit unit ON var.unit_id=unit.id + WHERE current.station_id=%s and current.day=%s + ORDER BY current.day, var.name + """ + + with connection.cursor() as cursor: + + cursor.execute(query, [station_id, today]) + + rows = cursor.fetchall() + + for row in rows: + + value = None + + if row[3] in (1, 2): + value = row[8] + + elif row[3] == 3: + value = row[6] + + elif row[3] == 4: + value = row[7] + + elif row[3] == 6: + value = row[9] + + if value is None: + print('variable {} does not have supported sampling operation {}'.format(row[1], row[3])) + + obj = { + 'value': value, + 'variable': { + 'name': row[1], + 'symbol': row[2], + 'unit_name': row[4], + 'unit_symbol': row[5] + } + } + result.append(obj) + + max_date = cache.get('daily_summary_last_run', None) + + return result, max_date + + +@api_view(['GET']) +def livedata(request, code): + try: + station = Station.objects.get(code=code) + except ObjectDoesNotExist as e: + station = Station.objects.get(pk=code) + finally: + id = station.id + + past24h_data, past24h_max_date = get_last24_data(station_id=id) + latest_data, latest_max_date = get_latest_data(station_id=id) + current_data, current_max_date = get_current_data(station_id=id) + + station_data = serializers.StationSerializerRead(station).data + + return Response( + { + 'station': station_data, + 'station_name': station.name, + 'station_id': station.id, + 'past24h': past24h_data, + 'past24h_last_update': past24h_max_date, + 'latest': latest_data, + 'latest_last_update': latest_max_date, + 'currentday': current_data, + 'currentday_last_update': current_max_date, + } + , status=status.HTTP_200_OK) + + +class WatershedList(generics.ListAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = serializers.WatershedSerializer + queryset = Watershed.objects.all().order_by("watershed") + + +class StationCommunicationList(generics.ListAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = serializers.StationCommunicationSerializer + queryset = StationCommunication.objects.all() + + +class DecoderList(generics.ListAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = serializers.DecoderSerializer + queryset = Decoder.objects.all().order_by("name") + + +class QualityFlagList(viewsets.ReadOnlyModelViewSet): + permission_classes = (IsAuthenticated,) + serializer_class = serializers.QualityFlagSerializer + queryset = QualityFlag.objects.all().order_by("name") + + +def qc_list(request): + if request.method == 'GET': + station_id = request.GET.get('station_id', None) + variable_id = request.GET.get('variable_id', None) + start_date = request.GET.get('start_date', None) + end_date = request.GET.get('end_date', None) + + if station_id is None: + JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if variable_id is None: + JsonResponse(data={"message": "'variable_id' parameter cannot be null."}, + status=status.HTTP_400_BAD_REQUEST) + + sql_string = "" + where_parameters = [station_id, variable_id] + + response = { + 'count': -999, + 'next': None, + 'previous': None, + 'results': [] + } + + sql_string = """SELECT value.datetime + ,value.measured + ,value.consisted + ,value.quality_flag + ,value.manual_flag + ,value.station_id + ,value.variable_id + ,value.remarks + ,value.ml_flag + FROM raw_data as value + WHERE value.station_id=%s + AND value.variable_id=%s + """ + + if start_date is not None and end_date is not None: + where_parameters.append(start_date) + where_parameters.append(end_date) + sql_string += " AND value.datetime >= %s AND value.datetime <= %s" + + elif start_date is not None: + where_parameters.append(start_date) + sql_string += " AND value.datetime >= %s" + + elif end_date is not None: + where_parameters.append(end_date) + sql_string += " AND %s >= value.datetime " + + sql_string += " ORDER BY value.datetime " + with connection.cursor() as cursor: + + cursor.execute(sql_string, where_parameters) + rows = cursor.fetchall() + + for row in rows: + obj = { + 'datetime': row[0], + 'measured': row[1], + 'consisted': row[2], + 'automatic_flag': row[3], + 'manual_flag': row[4], + 'station_id': row[5], + 'variable_id': row[6], + 'remarks': row[7], + 'ml_flag': row[8], + } + + response['results'].append(obj) + + return JsonResponse(response) + + if request.method == 'PATCH': + station_id = request.GET.get('station_id', None) + variable_id = request.GET.get('variable_id', None) + req_datetime = request.GET.get('datetime', None) + + if station_id is None: + JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if variable_id is None: + JsonResponse(data={"message": "'variable_id' parameter cannot be null."}, + status=status.HTTP_400_BAD_REQUEST) + + if req_datetime is None: + JsonResponse(data={"message": "'datetime' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + try: + station_id = int(station_id) + variable_id = int(variable_id) + req_datetime = datetime.datetime.strptime(req_datetime, '%Y-%m-%dT%H:%M:%SZ') + except ValueError: + JsonResponse(data={"message": "Invalid parameter type."}, status=status.HTTP_400_BAD_REQUEST) + + body = json.loads(request.body.decode('utf-8')) + query_parameters = [] + sql_columns_to_update = [] + + if 'manual_flag' in body: + try: + query_parameters.append(parse_int_value(body['manual_flag'])) + sql_columns_to_update.append(' manual_flag=%s ') + except ValueError: + return JsonResponse({'message': 'Wrong manual flag value type.'}, status=status.HTTP_400_BAD_REQUEST) + + if 'consisted' in body: + try: + query_parameters.append(parse_float_value(body['consisted'])) + sql_columns_to_update.append(' consisted=%s ') + except ValueError: + return JsonResponse({'message': 'Wrong consisted value type. Please inform a float value.'}, + status=status.HTTP_400_BAD_REQUEST) + + if 'remarks' in body: + try: + query_parameters.append(body['remarks']) + sql_columns_to_update.append(' remarks=%s ') + except ValueError: + return JsonResponse({'message': 'Wrong remarks value type. Please inform a text value.'}, + status=status.HTTP_400_BAD_REQUEST) + + if not sql_columns_to_update: + JsonResponse(data={"message": "You must send 'manual_flag', 'consisted' or 'remarks' data to update."}, + status=status.HTTP_400_BAD_REQUEST) + + if query_parameters: + query_parameters.append(req_datetime) + query_parameters.append(station_id) + query_parameters.append(variable_id) + sql_query = f"UPDATE raw_data SET {', '.join(sql_columns_to_update)} WHERE datetime=%s AND station_id=%s AND variable_id=%s" + + station = Station.objects.get(pk=station_id) + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + cursor.execute(sql_query, query_parameters) + + now = datetime.datetime.now() + cursor.execute(""" + INSERT INTO wx_hourlysummarytask (station_id, datetime, updated_at, created_at) + VALUES (%s, %s, %s, %s) + ON CONFLICT DO NOTHING + """, (station_id, req_datetime, now, now)) + + station_timezone = pytz.UTC + if station.utc_offset_minutes is not None: + station_timezone = pytz.FixedOffset(station.utc_offset_minutes) + + date = req_datetime.astimezone(station_timezone).date() + cursor.execute(""" + INSERT INTO wx_dailysummarytask (station_id, date, updated_at, created_at) + VALUES (%s, %s, %s, %s) + ON CONFLICT DO NOTHING + """, (station_id, date, now, now)) + + cursor.execute(sql_query, query_parameters) + + conn.commit() + + return JsonResponse({}, status=status.HTTP_200_OK) + return JsonResponse({'message': "There is no 'manual_flag' or 'consisted' fields in the json request."}, + status=status.HTTP_400_BAD_REQUEST) + + return JsonResponse({'message': 'Only the GET and PATCH methods is allowed.'}, status=status.HTTP_400_BAD_REQUEST) + + +@csrf_exempt +def interpolate_endpoint(request): + request_date = request.GET.get('request_date', None) + variable_id = request.GET.get('variable_id', None) + data_type = request.GET.get('data_type', None) + + sql_string = '' + where_parameters = [variable_id, request_date] + + if data_type == 'daily': + sql_string = """ + SELECT station.latitude + ,station.longitude + ,daily.avg_value + FROM daily_summary as daily + INNER JOIN wx_station as station ON daily.station_id=station.id + WHERE daily.variable_id=%s and daily.day=%s + ORDER BY daily.day + """ + + elif data_type == 'hourly': + + sql_string = """ + SELECT station.latitude + ,station.longitude + ,hourly.avg_value + FROM hourly_summary as hourly + INNER JOIN wx_station as station ON hourly.station_id=station.id + WHERE hourly.variable_id=%s and hourly.datetime=%s + ORDER BY hourly.datetime + """ + + elif data_type == 'monthly': + sql_string = """ + SELECT station.latitude + ,station.longitude + ,monthly.avg_value + FROM monthly_summary as monthly + INNER JOIN wx_station as station ON monthly.station_id=station.id + WHERE monthly.variable_id=%s and to_date(concat(lpad(monthly.year::varchar(4),4,'0'), lpad(monthly.month::varchar(2),2,'0'), '01'), 'YYYYMMDD')=date_trunc('month',TIMESTAMP %s) + ORDER BY monthly.year, monthly.month + """ + + if sql_string and where_parameters: + query_result = [] + with connection.cursor() as cursor: + cursor.execute(sql_string, where_parameters) + rows = cursor.fetchall() + + for row in rows: + obj = { + 'station__latitude': row[0], + 'station__longitude': row[1], + 'value': row[2], + } + + query_result.append(obj) + + if not query_result: + return JsonResponse({'message': 'No data found.'}, status=status.HTTP_400_BAD_REQUEST) + + return HttpResponse(get_interpolation_image(query_result), content_type="image/jpeg") + + return JsonResponse({'message': 'Missing parameters.'}, status=status.HTTP_400_BAD_REQUEST) + + +@csrf_exempt +def capture_forms_values_get(request): + request_month = request.GET.get('request_month', None) + station_id = request.GET.get('station_id', None) + variable_id = request.GET.get('variable_id', None) + + sql_string = """ + select to_char(date.day, 'DD') + ,to_char(date.day, 'HHAM') + ,values.measured + ,%s + ,%s + ,date.day + from ( select generate_series(to_date(%s, 'YYYY-MM') + interval '6 hours', (to_date(%s, 'YYYY-MM') + interval '1 month -1 second'), '12 hours') as day ) date + LEFT JOIN raw_data as values ON date.day=values.datetime and values.station_id=%s and values.variable_id=%s + ORDER BY date.day + """ + where_parameters = [station_id, variable_id, request_month, request_month, station_id, variable_id] + + with connection.cursor() as cursor: + cursor.execute(sql_string, where_parameters) + rows = cursor.fetchall() + + days = rows + + days = {} + for row in rows: + obj = { + 'value': row[2], + 'station_id': row[3], + 'variable_id': row[4], + 'datetime': row[5], + } + + if row[0] not in days.keys(): + days[row[0]] = {} + + days[row[0]][row[1]] = obj + + full_list = [] + for day in days.keys(): + line = {} + for obj in days[day]: + line[obj] = days[day][obj] + full_list.append(line) + + if not days: + return JsonResponse({'message': 'No data found.'}, status=status.HTTP_400_BAD_REQUEST) + + return JsonResponse({'next': None, 'results': full_list}, safe=False, status=status.HTTP_200_OK) + + return JsonResponse({'message': 'Missing parameters.'}, status=status.HTTP_400_BAD_REQUEST) + + +''' +@csrf_exempt +def capture_forms_values_patch(request): + if request.method == 'PATCH': + + body = json.loads(request.body.decode('utf-8')) + + conn = psycopg2.connect(settings.SURFACE_CONNECTION_STRING) + with conn.cursor() as cursor: + cursor.executemany( + """ + INSERT INTO raw_data(datetime, station_id, variable_id, measured) VALUES(%(datetime)s, %(station_id)s, %(variable_id)s, %(value)s::double precision) + ON CONFLICT (datetime, station_id, variable_id) + DO UPDATE + SET measured = %(value)s + """, body) + + conn.commit() + conn.close() + + return JsonResponse({}, status=status.HTTP_200_OK) + return JsonResponse({'message':'Only the GET and PATCH methods is allowed.'}, status=status.HTTP_400_BAD_REQUEST) +''' + + +@csrf_exempt +def capture_forms_values_patch(request): + error_flag = False + if request.method == 'PATCH': + + body = json.loads(request.body.decode('utf-8')) + + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + for rec in body: + try: + if not rec['value']: + cursor.execute( + """ DELETE FROM raw_data WHERE datetime=%(datetime)s and station_id=%(station_id)s and variable_id=%(variable_id)s """, + rec) + else: + valor = float(rec['value']) + + cursor.execute( + """ + INSERT INTO raw_data(datetime, station_id, variable_id, measured) VALUES(%(datetime)s, %(station_id)s, %(variable_id)s, %(value)s::double precision) + ON CONFLICT (datetime, station_id, variable_id) + DO UPDATE + SET measured = %(value)s + """, rec) + except (ValueError, psycopg2.errors.InvalidTextRepresentation): + error_flag = True + + conn.commit() + + if error_flag: + return JsonResponse({'message': 'Some data was bad formated, please certify that the input is numeric.'}, + status=status.HTTP_200_OK) + + return JsonResponse({}, status=status.HTTP_200_OK) + return JsonResponse({'message': 'Only the GET and PATCH methods is allowed.'}, status=status.HTTP_400_BAD_REQUEST) + + +class StationListView(LoginRequiredMixin, ListView): + model = Station + + +class StationDetailView(LoginRequiredMixin, DetailView): + model = Station + + +class StationCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView): + model = Station + + success_message = "%(name)s was created successfully" + form_class = StationForm + + layout = Layout( + Fieldset('Editing station', + Row('latitude', 'longitude'), + Row('name'), + Row('is_active', 'is_automatic'), + Row('alias_name'), + Row('code', 'profile'), + Row('wmo', 'organization'), + Row('wigos', 'observer'), + Row('begin_date', 'data_source'), + Row('end_date', 'communication_type') + ), + Fieldset('Other information', + Row('elevation', 'watershed'), + Row('country', 'region'), + Row('utc_offset_minutes', 'local_land_use'), + Row('soil_type', 'station_details'), + Row('site_description', 'alternative_names') + ), + # Fieldset('Hydrology information', + # Row('hydrology_station_type', 'ground_water_province'), + # Row('existing_gauges', 'flow_direction_at_station'), + # Row('flow_direction_above_station', 'flow_direction_below_station'), + # Row('bank_full_stage', 'bridge_level'), + # Row('temporary_benchmark', 'mean_sea_level'), + # Row('river_code', 'river_course'), + # Row('catchment_area_station', 'river_origin'), + # Row('easting', 'northing'), + # Row('river_outlet', 'river_length'), + # Row('z', 'land_surface_elevation'), + # Row('top_casing_land_surface', 'casing_diameter'), + # Row('screen_length', 'depth_midpoint'), + # Row('casing_type', 'datum'), + # Row('zone') + # ) + ) + + # passing required context for watershed and region autocomplete fields + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context['watersheds'] = Watershed.objects.all() + context['regions'] = AdministrativeRegion.objects.all() + + return context + + + +class StationUpdate(LoginRequiredMixin, SuccessMessageMixin, UpdateView): + model = Station + success_message = "%(name)s was updated successfully" + form_class = StationForm + + layout = Layout( + Fieldset('Editing station', + Row('latitude', 'longitude'), + Row('name', 'is_active'), + Row('alias_name', 'is_automatic'), + Row('code', 'profile'), + Row('wmo', 'organization'), + Row('wigos', 'observer'), + Row('begin_date', 'data_source'), + Row('end_date', 'communication_type') + ), + Fieldset('Other information', + Row('elevation', 'watershed'), + Row('country', 'region'), + Row('utc_offset_minutes', 'local_land_use'), + Row('soil_type', 'station_details'), + Row('site_description', 'alternative_names') + ), + Fieldset('Hydrology information', + Row('hydrology_station_type', 'ground_water_province'), + Row('existing_gauges', 'flow_direction_at_station'), + Row('flow_direction_above_station', 'flow_direction_below_station'), + Row('bank_full_stage', 'bridge_level'), + Row('temporary_benchmark', 'mean_sea_level'), + Row('river_code', 'river_course'), + Row('catchment_area_station', 'river_origin'), + Row('easting', 'northing'), + Row('river_outlet', 'river_length'), + Row('z', 'land_surface_elevation'), + Row('top_casing_land_surface', 'casing_diameter'), + Row('screen_length', 'depth_midpoint'), + Row('casing_type', 'datum'), + Row('zone') + ) + + ) + + +@api_view(['POST']) +def pgia_update(request): + try: + hours_dict = request.data['table'] + now_utc = datetime.datetime.now().astimezone(pytz.UTC) + datetime.timedelta( + hours=settings.PGIA_REPORT_HOURS_AHEAD_TIME) + + pgia = Station.objects.get(id=4) + datetime_offset = pytz.FixedOffset(pgia.utc_offset_minutes) + + day = datetime.datetime.strptime(request.data['date'], '%Y-%m-%d') + station_id = pgia.id + seconds = 3600 + + records_list = [] + for hour, hour_data in hours_dict.items(): + data_datetime = day.replace(hour=int(hour)) + data_datetime = datetime_offset.localize(data_datetime) + if data_datetime <= now_utc: + if hour_data: + if 'action' in hour_data.keys(): + hour_data.pop('action') + + if 'remarks' in hour_data.keys(): + remarks = hour_data.pop('remarks') + else: + remarks = None + + if 'observer' in hour_data.keys(): + observer = hour_data.pop('observer') + else: + observer = None + + for variable_id, measurement in hour_data.items(): + if measurement is None: + measurement_value = settings.MISSING_VALUE + measurement_code = settings.MISSING_VALUE_CODE + + try: + measurement_value = float(measurement) + measurement_code = measurement + except Exception: + measurement_value = settings.MISSING_VALUE + measurement_code = settings.MISSING_VALUE_CODE + records_list.append(( + station_id, variable_id, seconds, data_datetime, measurement_value, 1, None, + None, None, None, None, None, None, None, False, remarks, observer, + measurement_code)) + + insert_raw_data_pgia.insert(raw_data_list=records_list, date=day, station_id=station_id, + override_data_on_conflict=True, utc_offset_minutes=pgia.utc_offset_minutes) + except Exception as e: + logger.error(repr(e)) + return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + return HttpResponse(status=status.HTTP_200_OK) + + +@api_view(['GET']) +def pgia_load(request): + try: + date = datetime.datetime.strptime(request.GET['date'], '%Y-%m-%d') + except ValueError as e: + logger.error(repr(e)) + return HttpResponse(status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + logger.error(repr(e)) + return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + pgia = Station.objects.get(id=4) + datetime_offset = pytz.FixedOffset(pgia.utc_offset_minutes) + request_datetime = datetime_offset.localize(date) + + start_datetime = request_datetime + end_datetime = request_datetime + datetime.timedelta(days=1) + + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + cursor.execute( + f""" + SELECT (datetime + interval '{pgia.utc_offset_minutes} minutes') at time zone 'utc', + variable_id, + CASE WHEN var.variable_type ilike 'code' THEN code ELSE measured::varchar END as value, + remarks, + observer + FROM raw_data + JOIN wx_variable var ON raw_data.variable_id=var.id + WHERE station_id = %(station_id)s + AND datetime >= %(start_date)s + AND datetime < %(end_date)s + """, + { + 'start_date': start_datetime, + 'end_date': end_datetime, + 'station_id': pgia.id + }) + + response = cursor.fetchall() + + return JsonResponse(response, status=status.HTTP_200_OK, safe=False) + + +@api_view(['GET']) +def MonthlyFormLoad(request): + try: + start_date = datetime.datetime.strptime(request.GET['date'], '%Y-%m') + station_id = int(request.GET['station']) + except ValueError as e: + return JsonResponse({}, status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return JsonResponse({}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + station = Station.objects.get(id=station_id) + datetime_offset = pytz.FixedOffset(station.utc_offset_minutes) + end_date = start_date.replace(month=start_date.month + 1) - datetime.timedelta(days=1) + + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + cursor.execute( + f""" + SELECT (datetime + interval '{station.utc_offset_minutes} minutes') at time zone 'utc' + ,variable_id + ,measured + FROM raw_data + WHERE station_id = %(station_id)s + AND datetime >= %(start_date)s + AND datetime <= %(end_date)s + """, + { + 'start_date': start_date, + 'end_date': end_date, + 'station_id': station_id + }) + + response = cursor.fetchall() + + conn.commit() + + return JsonResponse(response, status=status.HTTP_200_OK, safe=False) + + +@api_view(['POST']) +def MonthlyFormUpdate(request): + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + records_list = [] + + station_id = int(request.data['station']) + station = Station.objects.get(id=station_id) + first_day = datetime.datetime(year=int(request.data['date']['year']), month=int(request.data['date']['month']), + day=1) + + now_utc = datetime.datetime.now().astimezone(pytz.UTC) + datetime_offset = pytz.FixedOffset(station.utc_offset_minutes) + + days_in_month = (first_day.replace(month=first_day.month + 1) - datetime.timedelta(days=1)).day + + for day in range(0, days_in_month): + data = request.data['table'][day] + data_datetime = first_day.replace(day=day + 1) + data_datetime = datetime_offset.localize(data_datetime) + + if data_datetime <= now_utc: + for variable_id, value in data.items(): + if value is None: + value = settings.MISSING_VALUE + + records_list.append(( + station_id, variable_id, 86400, data_datetime, value, 1, None, None, None, None, + None, None, None, None, False, None, None, None)) + + insert_raw_data_pgia.insert(raw_data_list=records_list, date=first_day, station_id=station_id, + override_data_on_conflict=True, utc_offset_minutes=station.utc_offset_minutes) + return JsonResponse({}, status=status.HTTP_200_OK) + + +class StationDelete(LoginRequiredMixin, DeleteView): + model = Station + fields = ['code', 'name', 'profile', ] + + def get_success_url(self): + return reverse('stations-list') + + +class StationFileList(LoginRequiredMixin, ListView): + model = StationFile + + def get_queryset(self): + queryset = StationFile.objects.filter(station__id=self.kwargs.get('pk')) + return queryset + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + station = Station.objects.get(pk=self.kwargs.get( + 'pk')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters + context['station'] = station + return context + + +class StationFileCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView): + model = StationFile + # fields = "__all__" + fields = ('name', 'file') + success_message = "%(name)s was created successfully" + layout = Layout( + Fieldset('Add file to station', + Row('name'), + Row('file') + ) + ) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + station = Station.objects.get(pk=self.kwargs.get( + 'pk')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters + context['station'] = station + return context + + def form_valid(self, form): + f = form.save(commit=False) + station = Station.objects.get(pk=self.kwargs.get('pk')) + f.station = station + f.save() + return super(StationFileCreate, self).form_valid(form) + + def get_success_url(self): + return reverse('stationfiles-list', kwargs={'pk': self.kwargs.get('pk')}) + + +class StationFileDelete(LoginRequiredMixin, DeleteView): + model = StationFile + success_message = "%(name)s was deleted successfully" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + station = Station.objects.get(pk=self.kwargs.get( + 'pk_station')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters + context['station'] = station + return context + + def get_success_url(self): + return reverse('stationfiles-list', kwargs={'pk': self.kwargs.get('pk_station')}) + + +class StationVariableListView(LoginRequiredMixin, ListView): + model = StationVariable + + def get_queryset(self): + queryset = StationVariable.objects.filter(station__id=self.kwargs.get('pk')) + return queryset + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + station = Station.objects.get(pk=self.kwargs.get( + 'pk')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters + context['station'] = station + return context + + +class StationVariableCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): + model = StationVariable + fields = ('variable',) + success_message = "%(variable)s was created successfully" + layout = Layout( + Fieldset('Add variable to station', + Row('variable') + ) + ) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + station = Station.objects.get(pk=self.kwargs.get( + 'pk')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters + context['station'] = station + return context + + def form_valid(self, form): + f = form.save(commit=False) + station = Station.objects.get(pk=self.kwargs.get('pk')) + f.station = station + f.save() + return super(StationVariableCreateView, self).form_valid(form) + + def get_success_url(self): + return reverse('stationvariable-list', kwargs={'pk': self.kwargs.get('pk')}) + + +class StationVariableDeleteView(LoginRequiredMixin, DeleteView): + model = StationVariable + success_message = "%(action)s was deleted successfully" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + station = Station.objects.get(pk=self.kwargs.get( + 'pk_station')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters + context['station'] = station + return context + + def get_success_url(self): + return reverse('stationvariable-list', kwargs={'pk': self.kwargs.get('pk_station')}) + + +def station_report_data(request): + station = request.GET.get('station', None) + initial_datetime = request.GET.get('initial_datetime', None) + final_datetime = request.GET.get('final_datetime', None) + source = request.GET.get('source', None) + + if station and initial_datetime and final_datetime and source: + if int(source) == 0: # Raw data + dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'raw_data') + elif int(source) == 1: # Hourly data + dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'hourly_summary') + elif int(source) == 2: # Daily data + dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'daily_summary') + elif int(source) == 3: # Monthly data + dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'monthly_summary') + elif int(source) == 4: # Yearly data + dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'yearly_summary') + else: + return JsonResponse({}, status=status.HTTP_404_NOT_FOUND) + + charts = { + + } + + for element_name, element_data in dataset['results'].items(): + + chart = { + 'chart': { + 'type': 'pie', + 'zoomType': 'xy' + }, + 'title': {'text': element_name}, + 'xAxis': { + 'type': 'datetime', + 'dateTimeLabelFormats': { + 'month': '%e. %b', + 'year': '%b' + }, + 'title': { + 'text': 'Date' + } + }, + 'yAxis': [], + 'exporting': { + 'showTable': True + }, + 'series': [] + } + + opposite = False + y_axis_unit_dict = {} + for variable_name, variable_data in element_data.items(): + + current_unit = variable_data['unit'] + if current_unit not in y_axis_unit_dict.keys(): + chart['yAxis'].append({ + 'labels': { + 'format': '{value} ' + variable_data['unit'] + }, + 'title': { + 'text': None + }, + 'opposite': opposite + }) + y_axis_unit_dict[current_unit] = len(chart['yAxis']) - 1 + opposite = not opposite + + current_y_axis_index = y_axis_unit_dict[current_unit] + data = [] + for record in variable_data['data']: + if int(source) == 0: + data.append({ + 'x': record['date'], + 'y': record['value'], + 'quality_flag': record['quality_flag'], + 'flag_color': record['flag_color'] + }) + elif int(source) == 3: + data.append({ + 'x': record['date'], + 'y': record['value'] + }) + + chart['xAxis'] = { + 'type': 'datetime', + 'labels': { + 'format': '{value:%Y-%b}' + }, + 'title': { + 'text': 'Y' + } + } + elif int(source) == 4: + data.append({ + 'x': record['date'], + 'y': record['value'] + }) + + chart['xAxis'] = { + 'type': 'datetime', + 'labels': { + 'format': '{value:%Y-%b}' + }, + 'title': { + 'text': 'Reference' + } + } + else: + data.append({ + 'x': record['date'], + 'y': record['value'] + }) + + chart['series'].append({ + 'name': variable_name, + 'color': variable_data['color'], + 'type': variable_data['default_representation'], + 'unit': variable_data['unit'], + 'data': data, + 'yAxis': current_y_axis_index + }) + chart['chart']['type'] = variable_data['default_representation'], + + charts[slugify(element_name)] = chart + + return JsonResponse(charts) + else: + return JsonResponse({}, status=status.HTTP_400_BAD_REQUEST) + + +def variable_report_data(request): + variable_ids_list = request.GET.get('variable_ids', None) + initial_datetime = request.GET.get('initial_datetime', None) + final_datetime = request.GET.get('final_datetime', None) + source = request.GET.get('source', None) + station_id_list = request.GET.get('station_ids', None) + + if variable_ids_list and initial_datetime and final_datetime and source and station_id_list: + variable_ids_list = tuple(json.loads(variable_ids_list)) + station_id_list = tuple(json.loads(station_id_list)) + + if int(source) == 0: # Raw data + dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, + station_id_list, 'raw_data') + elif int(source) == 1: # Hourly data + dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, + station_id_list, 'hourly_summary') + elif int(source) == 2: # Daily data + dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, + station_id_list, 'daily_summary') + elif int(source) == 3: # Monthly data + dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, + station_id_list, 'monthly_summary') + elif int(source) == 4: # Yearly data + dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, + station_id_list, 'yearly_summary') + else: + return JsonResponse({}, status=status.HTTP_404_NOT_FOUND) + + charts = { + + } + + for element_name, element_data in dataset['results'].items(): + + chart = { + 'chart': { + 'type': 'pie', + 'zoomType': 'xy' + }, + 'title': {'text': element_name}, + 'xAxis': { + 'type': 'datetime', + 'dateTimeLabelFormats': { + 'month': '%e. %b', + 'year': '%b' + }, + 'title': { + 'text': 'Date' + } + }, + 'yAxis': [], + 'exporting': { + 'showTable': True + }, + 'series': [] + } + + opposite = False + y_axis_unit_dict = {} + for variable_name, variable_data in element_data.items(): + + current_unit = variable_data['unit'] + if current_unit not in y_axis_unit_dict.keys(): + chart['yAxis'].append({ + 'labels': { + 'format': '{value} ' + variable_data['unit'] + }, + 'title': { + 'text': None + }, + 'opposite': opposite + }) + y_axis_unit_dict[current_unit] = len(chart['yAxis']) - 1 + opposite = not opposite + + current_y_axis_index = y_axis_unit_dict[current_unit] + data = [] + for record in variable_data['data']: + if int(source) == 0: + data.append({ + 'x': record['date'], + 'y': record['value'], + 'quality_flag': record['quality_flag'], + 'flag_color': record['flag_color'] + }) + elif int(source) == 3: + data.append({ + 'x': record['date'], + 'y': record['value'] + }) + + chart['xAxis'] = { + 'type': 'datetime', + 'labels': { + 'format': '{value:%Y-%b}' + }, + 'title': { + 'text': 'Y' + } + } + elif int(source) == 4: + data.append({ + 'x': record['date'], + 'y': record['value'] + }) + + chart['xAxis'] = { + 'type': 'datetime', + 'labels': { + 'format': '{value:%Y-%b}' + }, + 'title': { + 'text': 'Reference' + } + } + else: + data.append({ + 'x': record['date'], + 'y': record['value'] + }) + + chart['series'].append({ + 'name': variable_name, + 'color': "#" + ''.join([random.choice('0123456789ABCDEF') for j in range(6)]), + 'type': variable_data['default_representation'], + 'unit': variable_data['unit'], + 'data': data, + 'yAxis': current_y_axis_index + }) + chart['chart']['type'] = variable_data['default_representation'], + + charts[slugify(element_name)] = chart + + return JsonResponse(charts) + else: + return JsonResponse({}, status=status.HTTP_400_BAD_REQUEST) + + +class StationReportView(LoginRequiredMixin, TemplateView): + template_name = "wx/products/station_report.html" + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + context['station_id'] = request.GET.get('station_id', 'null') + + station_list = Station.objects.all() + context['station_list'] = station_list + + # interval_list = Interval.objects.filter(seconds__lte=3600).order_by('seconds') + # context['interval_list'] = interval_list + + quality_flag_query = QualityFlag.objects.all() + quality_flag_colors = {} + for quality_flag in quality_flag_query: + quality_flag_colors[quality_flag.name.replace(' ', '_')] = quality_flag.color + context['quality_flag_colors'] = quality_flag_colors + + selected_station = station_list.first() + if selected_station is not None: + selected_station_id = selected_station.id + station_variable_list = StationVariable.objects.filter(station__id=selected_station_id) + else: + station_variable_list = [] + + context['station_variable_list'] = station_variable_list + + return self.render_to_response(context) + + +class VariableReportView(LoginRequiredMixin, TemplateView): + template_name = "wx/products/variable_report.html" + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + + quality_flag_query = QualityFlag.objects.all() + quality_flag_colors = {} + for quality_flag in quality_flag_query: + quality_flag_colors[quality_flag.name.replace(' ', '_')] = quality_flag.color + + context['quality_flag_colors'] = quality_flag_colors + context['variable_list'] = Variable.objects.all() + context['station_list'] = Station.objects.all() + + return self.render_to_response(context) + + +class ProductReportView(LoginRequiredMixin, TemplateView): + template_name = "wx/products/report.html" + + +class ProductCompareView(LoginRequiredMixin, TemplateView): + template_name = 'wx/products/compare.html' + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + context['station_list'] = Station.objects.select_related('profile').all() + context['variable_list'] = Variable.objects.select_related('unit').all() + + context['station_profile_list'] = StationProfile.objects.all() + context['station_watershed_list'] = Watershed.objects.all() + context['station_district_list'] = AdministrativeRegion.objects.all() + + return self.render_to_response(context) + + +class QualityControlView(LoginRequiredMixin, TemplateView): + template_name = 'wx/quality_control/validation.html' + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + context['station_list'] = Station.objects.select_related('profile').all() + + context['station_profile_list'] = StationProfile.objects.all() + context['station_watershed_list'] = Watershed.objects.all() + context['station_district_list'] = AdministrativeRegion.objects.all() + + return self.render_to_response(context) + + +@csrf_exempt +def get_yearly_average(request): + begin_date = request.GET.get('begin_date', None) + end_date = request.GET.get('end_date', None) + station_id = request.GET.get('station_id', None) + variable_id = request.GET.get('variable_id', None) + + sql_string = """ + select avg(val.measured) as average, extract ('year' from month)::varchar as year, extract ('month' from month)::varchar as month, round(avg(val.measured)::decimal, 2) + from generate_series(date_trunc('year', date %s),date_trunc('year', date %s) + interval '1 year' - interval '1 day', interval '1 month') month + left outer join raw_data as val on date_trunc('month', val.datetime) = date_trunc('month', month) and val.station_id = %s and val.variable_id = %s + group by month + order by month + """ + where_parameters = [begin_date, end_date, station_id, variable_id] + + with connection.cursor() as cursor: + cursor.execute(sql_string, where_parameters) + rows = cursor.fetchall() + + years = {} + for row in rows: + if row[1] not in years.keys(): + years[row[1]] = {} + + if row[2] not in years[row[1]].keys(): + years[row[1]][row[2]] = row[3] + + years[row[1]][row[2]] = row[3] + + if not years: + return JsonResponse({'message': 'No data found.'}, status=status.HTTP_400_BAD_REQUEST) + + return JsonResponse({'next': None, 'results': years}, safe=False, status=status.HTTP_200_OK) + + return JsonResponse({'message': 'Missing parameters.'}, status=status.HTTP_400_BAD_REQUEST) + + +class YearlyAverageReport(LoginRequiredMixin, TemplateView): + template_name = 'wx/reports/yearly_average.html' + + +class StationVariableStationViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + queryset = StationVariable.objects.values('station__id', 'station__name', 'station__code').distinct('station__id') + serializer_class = serializers.ReducedStationSerializer + + def get_queryset(self): + queryset = StationVariable.objects.values('station__id', 'station__name', 'station__code').distinct( + 'station__id') + + variable_id = self.request.query_params.get('variable_id', None) + + if variable_id is not None: + queryset = queryset.filter(variable__id=variable_id) + + return queryset + +def last24_summary_list(request): + search_type = request.GET.get('search_type', None) + search_value = request.GET.get('search_value', None) + + response = { + 'results': [], + 'messages': [], + } + + if search_type is not None and search_type == 'variable': + query = """ + SELECT last24h.datetime, + var.name, + var.symbol, + var.sampling_operation_id, + unit.name, + unit.symbol, + last24h.min_value, + last24h.max_value, + last24h.avg_value, + last24h.sum_value, + last24h.latest_value, + last24h.station_id, + last24h.num_records + FROM last24h_summary last24h + JOIN wx_variable var ON last24h.variable_id=var.id + JOIN wx_unit unit ON var.unit_id=unit.id + WHERE last24h.variable_id=%s + """ + + with connection.cursor() as cursor: + + cursor.execute(query, [search_value]) + rows = cursor.fetchall() + + for row in rows: + + value = None + + if row[3] == 1: + value = row[10] + + elif row[3] == 2: + value = row[8] + + elif row[3] == 3: + value = row[6] + + elif row[3] == 4: + value = row[7] + + elif row[3] == 6: + value = row[9] + + if value is None: + print('variable {} does not have supported sampling operation {}'.format(row[1], row[3])) + + obj = { + 'station': row[11], + 'value': value, + 'min': round(row[6], 2), + 'max': round(row[7], 2), + 'avg': round(row[8], 2), + 'sum': round(row[9], 2), + 'count': row[12], + 'variable': { + 'name': row[1], + 'symbol': row[2], + 'unit_name': row[4], + 'unit_symbol': row[5] + } + } + response['results'].append(obj) + + if response['results']: + return JsonResponse(response, status=status.HTTP_200_OK) + + return JsonResponse(data={"message": "No data found."}, status=status.HTTP_404_NOT_FOUND) + + +def query_stationsmonintoring_chart(station_id, variable_id, data_type, datetime_picked): + station = Station.objects.get(id=station_id) + variable = Variable.objects.get(id=variable_id) + + date_start = str((datetime_picked - datetime.timedelta(days=6)).date()) + date_end = str(datetime_picked.date()) + + if data_type=='Communication': + query = """ + WITH + date_range AS ( + SELECT GENERATE_SERIES(%s::DATE - '6 day'::INTERVAL, %s::DATE, '1 day')::DATE AS date + ), + hs AS ( + SELECT + datetime::date AS date, + COUNT(DISTINCT EXTRACT(hour FROM datetime)) AS amount + FROM + hourly_summary + WHERE + datetime >= %s::DATE - '7 day'::INTERVAL AND datetime < %s::DATE + '1 day'::INTERVAL + AND station_id = %s + AND variable_id = %s + GROUP BY 1 + ) + SELECT + date_range.date, + COALESCE(hs.amount, 0) AS amount, + COALESCE(( + SELECT color FROM wx_qualityflag + WHERE + CASE + WHEN COALESCE(hs.amount, 0) >= 20 THEN name = 'Good' + WHEN COALESCE(hs.amount, 0) >= 8 AND COALESCE(hs.amount, 0) <= 19 THEN name = 'Suspicious' + WHEN COALESCE(hs.amount, 0) >= 1 AND COALESCE(hs.amount, 0) <= 7 THEN name = 'Bad' + ELSE name = 'Not checked' + END + ), '') AS color + FROM + date_range + LEFT JOIN hs ON date_range.date = hs.date + ORDER BY date_range.date; + """ + + with connection.cursor() as cursor: + cursor.execute(query, (datetime_picked, datetime_picked, datetime_picked, datetime_picked, station_id, variable_id,)) + results = cursor.fetchall() + + chart_options = { + 'chart': { + 'type': 'column' + }, + 'title': { + 'text': " ".join(['Delay Data Track -',date_start,'to',date_end]) + }, + 'subtitle': { + 'text': " ".join([station.name, station.code, '-', variable.name]) + }, + 'xAxis': { + 'categories': [r[0] for r in results] + }, + 'yAxis': { + 'title': None, + 'categories': [str(i)+'h' for i in range(25)], + 'tickInterval': 2, + 'min': 0, + 'max': 24, + }, + 'series': [ + { + 'name': 'Max comunication', + 'data': [{'y': r[1], 'color': r[2]} for r in results], + 'showInLegend': False + } + ], + 'plotOptions': { + 'column': { + 'minPointLength': 10, + 'pointPadding': 0.01, + 'groupPadding': 0.05 + } + } + } + + elif data_type=='Quality Control': + flags = { + 'good': QualityFlag.objects.get(name='Good').color, + 'suspicious': QualityFlag.objects.get(name='Suspicious').color, + 'bad': QualityFlag.objects.get(name='Bad').color, + 'not_checked': QualityFlag.objects.get(name='Not checked').color, + } + + query = """ + WITH + date_range AS ( + SELECT GENERATE_SERIES(%s::DATE - '6 day'::INTERVAL, %s::DATE, '1 day')::DATE AS date + ), + hs AS( + SELECT + rd.datetime::DATE AS date + ,EXTRACT(hour FROM rd.datetime) AS hour + ,CASE + WHEN COUNT(CASE WHEN name='Bad' THEN 1 END) > 0 THEN('Bad') + WHEN COUNT(CASE WHEN name='Suspicious' THEN 1 END) > 0 THEN('Suspicious') + WHEN COUNT(CASE WHEN name='Good' THEN 1 END) > 0 THEN('Good') + ELSE ('Not checked') + END AS quality_flag + FROM raw_data AS rd + LEFT JOIN wx_qualityflag qf ON rd.quality_flag = qf.id + WHERE + datetime >= %s::DATE - '7 day'::INTERVAL AND datetime < %s::DATE + '1 day'::INTERVAL + AND rd.station_id = %s + AND rd.variable_id = %s + GROUP BY 1,2 + ORDER BY 1,2 + ) + SELECT + date_range.date + ,COUNT(CASE WHEN hs.quality_flag='Good' THEN 1 END) AS good + ,COUNT(CASE WHEN hs.quality_flag='Suspicious' THEN 1 END) AS suspicious + ,COUNT(CASE WHEN hs.quality_flag='Bad' THEN 1 END) AS bad + ,COUNT(CASE WHEN hs.quality_flag='Not checked' THEN 1 END) AS not_checked + FROM date_range + LEFT JOIN hs ON date_range.date = hs.date + GROUP BY 1 + ORDER BY 1; + """ + + with connection.cursor() as cursor: + cursor.execute(query, (datetime_picked, datetime_picked, datetime_picked, datetime_picked, station_id, variable_id,)) + results = cursor.fetchall() + + series = [] + for i, flag in enumerate(flags): + data = [r[i+1] for r in results] + series.append({'name': flag.capitalize(), 'data': data, 'color': flags[flag]}) + + chart_options = { + 'chart': { + 'type': 'column' + }, + 'title': { + 'text': " ".join(['Amount of Flags - ',date_start,'to',date_end]) + }, + 'subtitle': { + 'text': " ".join([station.name, station.code, '-', variable.name]) + }, + 'xAxis': { + 'categories': [r[0] for r in results] + }, + 'yAxis': { + 'title': None, + 'categories': [str(i)+'h' for i in range(25)], + 'tickInterval': 2, + 'min': 0, + 'max': 24, + }, + 'series': series, + 'plotOptions': { + 'column': { + 'minPointLength': 10, + 'pointPadding': 0.01, + 'groupPadding': 0.05 + } + } + } + + return chart_options + + +@require_http_methods(["GET"]) +def get_stationsmonitoring_chart_data(request, station_id, variable_id): + time_type = request.GET.get('time_type', 'Last 24h') + data_type = request.GET.get('data_type', 'Communication') + date_picked = request.GET.get('date_picked', None) + + if time_type=='Last 24h': + datetime_picked = datetime.datetime.now() + else: + datetime_picked = datetime.datetime.strptime(date_picked, '%Y-%m-%d') + + # Fix a date to test + # datetime_picked = datetime.datetime.strptime('2023-01-01', '%Y-%m-%d') + + chart_data = query_stationsmonintoring_chart(station_id, variable_id, data_type, datetime_picked) + + response = { + "chartOptions": chart_data + } + + return JsonResponse(response, status=status.HTTP_200_OK) + + +def get_station_lastupdate(station_id): + stationvariables = StationVariable.objects.filter(station_id=station_id) + + last_data_datetimes = [sv.last_data_datetime for sv in stationvariables if sv.last_data_datetime is not None] + + if last_data_datetimes: + lastupdate = max(last_data_datetimes) + lastupdate = lastupdate.strftime("%Y-%m-%d %H:%M") + else: + lastupdate = None + + return lastupdate + + +def query_stationsmonitoring_station(data_type, time_type, date_picked, station_id): + if time_type=='Last 24h': + datetime_picked = datetime.datetime.now() + else: + datetime_picked = datetime.datetime.strptime(date_picked, '%Y-%m-%d') + + + station_data = [] + + if data_type=='Communication': + query = """ + WITH hs AS ( + SELECT + station_id, + variable_id, + COUNT(DISTINCT EXTRACT(hour FROM datetime)) AS number_hours + FROM + hourly_summary + WHERE + datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL AND station_id = %s + GROUP BY 1, 2 + ) + SELECT + v.id, + v.name, + hs.number_hours, + ls.latest_value, + u.symbol, + CASE + WHEN hs.number_hours >= 20 THEN ( + SELECT color FROM wx_qualityflag WHERE name = 'Good' + ) + WHEN hs.number_hours >= 8 AND hs.number_hours <= 19 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' + ) + WHEN hs.number_hours >= 1 AND hs.number_hours <= 7 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Bad' + ) + ELSE ( + SELECT color FROM wx_qualityflag WHERE name = 'Not checked' + ) + END AS color + FROM + wx_stationvariable sv + LEFT JOIN hs ON sv.station_id = hs.station_id AND sv.variable_id = hs.variable_id + LEFT JOIN last24h_summary ls ON sv.station_id = ls.station_id AND sv.variable_id = ls.variable_id + LEFT JOIN wx_variable v ON sv.variable_id = v.id + LEFT JOIN wx_unit u ON v.unit_id = u.id + WHERE + sv.station_id = %s + ORDER BY 1; + """ + + with connection.cursor() as cursor: + cursor.execute(query, (datetime_picked, datetime_picked, station_id, station_id)) + results = cursor.fetchall() + + station_data = [{'id': r[0], + 'name': r[1], + 'amount': r[2] if r[2] is not None else 0, + 'latestvalue': " ".join([str(r[3]), r[4]]) if r[3] is not None else '---', + 'color': r[5]} for r in results] + + elif data_type=='Quality Control': + query = """ + WITH h AS( + SELECT + rd.station_id + ,rd.variable_id + ,EXTRACT(hour FROM rd.datetime) AS hour + ,CASE + WHEN COUNT(CASE WHEN name='Bad' THEN 1 END) > 0 THEN('Bad') + WHEN COUNT(CASE WHEN name='Suspicious' THEN 1 END) > 0 THEN('Suspicious') + WHEN COUNT(CASE WHEN name='Good' THEN 1 END) > 0 THEN('Good') + ELSE ('Not checked') + END AS quality_flag + FROM raw_data AS rd + LEFT JOIN wx_qualityflag qf ON rd.quality_flag = qf.id + WHERE + datetime <= %s + AND datetime >= %s - '24 hour'::INTERVAL + AND rd.station_id = %s + GROUP BY 1,2,3 + ORDER BY 1,2,3 + ) + SELECT + v.id + ,v.name + ,COUNT(CASE WHEN h.quality_flag='Good' THEN 1 END) AS good + ,COUNT(CASE WHEN h.quality_flag='Suspicious' THEN 1 END) AS suspicious + ,COUNT(CASE WHEN h.quality_flag='Bad' THEN 1 END) AS bad + ,COUNT(CASE WHEN h.quality_flag='Not checked' THEN 1 END) AS not_checked + FROM wx_stationvariable AS sv + LEFT JOIN wx_variable AS v ON sv.variable_id = v.id + LEFT JOIN h ON sv.station_id = h.station_id AND sv.variable_id = h.variable_id + WHERE sv.station_id = %s + GROUP BY 1,2 + ORDER BY 1,2 + """ + + with connection.cursor() as cursor: + cursor.execute(query, (datetime_picked, datetime_picked, station_id, station_id)) + results = cursor.fetchall() + + station_data = [{'id': r[0], + 'name': r[1], + 'good': r[2], + 'suspicious': r[3], + 'bad': r[4], + 'not_checked': r[5]} for r in results] + + return station_data + + +@require_http_methods(["GET"]) +def get_stationsmonitoring_station_data(request, id): + data_type = request.GET.get('data_type', 'Communication') + time_type = request.GET.get('time_type', 'Last 24h') + date_picked = request.GET.get('date_picked', None) + + response = { + 'lastupdate': get_station_lastupdate(id), + 'station_data': query_stationsmonitoring_station(data_type, time_type, date_picked, id), + } + + return JsonResponse(response, status=status.HTTP_200_OK) + + +def query_stationsmonitoring_map(data_type, time_type, date_picked): + if time_type=='Last 24h': + datetime_picked = datetime.datetime.now() + else: + datetime_picked = datetime.datetime.strptime(date_picked, '%Y-%m-%d') + + results = [] + + if time_type=='Last 24h': + if data_type=='Communication': + query = """ + WITH hs AS ( + SELECT + station_id + ,variable_id + ,COUNT(DISTINCT EXTRACT(hour FROM datetime)) AS number_hours + FROM + hourly_summary + WHERE + datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL + GROUP BY 1, 2 + ) + SELECT + s.id + ,s.name + ,s.code + ,s.latitude + ,s.longitude + ,CASE + WHEN MAX(number_hours) >= 20 THEN ( + SELECT color FROM wx_qualityflag WHERE name = 'Good' + ) + WHEN MAX(number_hours) >= 8 AND MAX(number_hours) <= 19 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' + ) + WHEN MAX(number_hours) >= 1 AND MAX(number_hours) <= 7 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Bad' + ) + ELSE ( + SELECT color FROM wx_qualityflag WHERE name = 'Not checked' + ) + END AS color + FROM wx_station AS s + LEFT JOIN wx_stationvariable AS sv ON s.id = sv.station_id + LEFT JOIN hs ON sv.station_id = hs.station_id AND sv.variable_id = hs.variable_id + WHERE s.is_active + GROUP BY 1, 2, 3, 4, 5; + """ + elif data_type=='Quality Control': + query = """ + WITH qf AS ( + SELECT + station_id + ,CASE + WHEN COUNT(CASE WHEN name='Bad' THEN 1 END) > 0 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Bad' + ) + WHEN COUNT(CASE WHEN name='Suspicious' THEN 1 END) > 0 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' + ) + WHEN COUNT(CASE WHEN name='Good' THEN 1 END) > 0 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Good' + ) + ELSE ( + SELECT color FROM wx_qualityflag WHERE name = 'Not checked' + ) + END AS color + FROM + raw_data AS rd + LEFT JOIN wx_qualityflag AS qf ON rd.quality_flag = qf.id + WHERE + datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL + GROUP BY 1 + ) + SELECT + s.id + ,s.name + ,s.code + ,s.latitude + ,s.longitude + ,COALESCE(qf.color, (SELECT color FROM wx_qualityflag WHERE name = 'Not checked')) AS color + FROM wx_station AS s + LEFT JOIN qf ON s.id = qf.station_id + WHERE s.is_active + """ + + if data_type in ['Communication', 'Quality Control']: + with connection.cursor() as cursor: + cursor.execute(query, (datetime_picked, datetime_picked, )) + results = cursor.fetchall() + else: + if data_type=='Communication': + query = """ + WITH hs AS ( + SELECT + station_id + ,variable_id + ,COUNT(DISTINCT EXTRACT(hour FROM datetime)) AS number_hours + FROM + hourly_summary + WHERE + datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL + GROUP BY 1, 2 + ) + SELECT + s.id + ,s.name + ,s.code + ,s.latitude + ,s.longitude + ,CASE + WHEN MAX(number_hours) >= 20 THEN ( + SELECT color FROM wx_qualityflag WHERE name = 'Good' + ) + WHEN MAX(number_hours) >= 8 AND MAX(number_hours) <= 19 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' + ) + WHEN MAX(number_hours) >= 1 AND MAX(number_hours) <= 7 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Bad' + ) + ELSE ( + SELECT color FROM wx_qualityflag WHERE name = 'Not checked' + ) + END AS color + FROM wx_station AS s + LEFT JOIN wx_stationvariable AS sv ON s.id = sv.station_id + LEFT JOIN hs ON sv.station_id = hs.station_id AND sv.variable_id = hs.variable_id + WHERE s.begin_date <= %s AND (s.end_date IS NULL OR s.end_date >= %s) + GROUP BY 1, 2, 3, 4, 5; + """ + elif data_type=='Quality Control': + query = """ + WITH qf AS ( + SELECT + station_id + ,CASE + WHEN COUNT(CASE WHEN name='Bad' THEN 1 END) > 0 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Bad' + ) + WHEN COUNT(CASE WHEN name='Suspicious' THEN 1 END) > 0 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' + ) + WHEN COUNT(CASE WHEN name='Good' THEN 1 END) > 0 THEN( + SELECT color FROM wx_qualityflag WHERE name = 'Good' + ) + ELSE ( + SELECT color FROM wx_qualityflag WHERE name = 'Not checked' + ) + END AS color + FROM + raw_data AS rd + LEFT JOIN wx_qualityflag AS qf ON rd.quality_flag = qf.id + WHERE + datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL + GROUP BY 1 + ) + SELECT + s.id + ,s.name + ,s.code + ,s.latitude + ,s.longitude + ,COALESCE(qf.color, (SELECT color FROM wx_qualityflag WHERE name = 'Not checked')) AS color + FROM wx_station AS s + LEFT JOIN qf ON s.id = qf.station_id + WHERE s.begin_date <= %s AND (s.end_date IS NULL OR s.end_date >= %s) + """ + + if data_type in ['Communication', 'Quality Control']: + with connection.cursor() as cursor: + cursor.execute(query, (datetime_picked, datetime_picked, datetime_picked, datetime_picked, )) + results = cursor.fetchall() + + return results + + +@require_http_methods(["GET"]) +def get_stationsmonitoring_map_data(request): + time_type = request.GET.get('time_type', 'Last 24h') + data_type = request.GET.get('data_type', 'Communication') + date_picked = request.GET.get('date_picked', None) + + results = query_stationsmonitoring_map(data_type, time_type, date_picked) + + response = { + 'stations': [{'id': r[0], + 'name': r[1], + 'code': r[2], + 'position': [r[3], r[4]], + 'color': r[5]} for r in results ], + } + + return JsonResponse(response, status=status.HTTP_200_OK) + + +def stationsmonitoring_form(request): + template = loader.get_template('wx/stations/stations_monitoring.html') + + flags = { + 'good': QualityFlag.objects.get(name='Good').color, + 'suspicious': QualityFlag.objects.get(name='Suspicious').color, + 'bad': QualityFlag.objects.get(name='Bad').color, + 'not_checked': QualityFlag.objects.get(name='Not checked').color, + } + + context = {'flags': flags} + + return HttpResponse(template.render(context, request)) + + +class ComingSoonView(LoginRequiredMixin, TemplateView): + template_name = "coming-soon.html" + + +def get_wave_data_analysis(request): + template = loader.get_template('wx/products/wave_data.html') + + + variable = Variable.objects.get(name="Sea Level") # Sea Level + station_ids = HighFrequencyData.objects.filter(variable_id=variable.id).values('station_id').distinct() + + station_list = Station.objects.filter(id__in=station_ids) + + context = {'station_list': station_list} + + return HttpResponse(template.render(context, request)) + +def format_wave_data_var(variable_id, data): + variable = Variable.objects.get(id=variable_id) + measurement_variable = MeasurementVariable.objects.get(id=variable.measurement_variable_id) + unit = Unit.objects.get(id=variable.unit_id) + + formated_data = [] + for entry in data: + if type(entry) is not dict: + entry = entry.__dict__ + + formated_entry = { + "station": entry['station_id'], + "date": entry['datetime'].timestamp()*1000, + "measurementvariable": measurement_variable.name, + "value": entry['measured'], + "quality_flag": "Not checked", + "flag_color": "#FFFFFF", + } + formated_data.append(formated_entry) + + final_data = { + "color": variable.color, + "default_representation": variable.default_representation, + "data": formated_data, + "unit": unit.symbol, + } + return final_data + +def get_wave_components(data_slice, component_number): + wave_list = fft_decompose(data_slice) + wave_list.sort(key=lambda W: abs(W.height), reverse=True) + wave_components = wave_list[:component_number] + return wave_components + +def get_wave_component_ref_variables(i): + SYSTEM_COMPONENT_NUMBER = 5 # Number of wave components in the system + + ref_number = i % SYSTEM_COMPONENT_NUMBER + ref_number += 1 + + amp_ref_name = 'Wave Component ' + str(ref_number) + ' Amplitude' + amp_ref = Variable.objects.get(name=amp_ref_name) + + frq_ref_name = 'Wave Component ' + str(ref_number) + ' Frequency' + frq_ref = Variable.objects.get(name=frq_ref_name) + + pha_ref_name = 'Wave Component ' + str(ref_number) + ' Phase' + pha_ref = Variable.objects.get(name=pha_ref_name) + + return amp_ref, frq_ref, pha_ref + +def get_wave_component_name_and_symbol(i, component_type): + if component_type=='Amplitude': + name = 'Wave Component ' + str(i) + ' Amplitude' + symbol = 'WV'+str(i)+'AMP' + elif component_type=='Frequency': + name = 'Wave Component ' + str(i) + ' Frequency' + symbol = 'WV'+str(i)+'FRQ' + elif component_type=='Phase': + name = 'Wave Component ' + str(i) + ' Phase' + symbol = 'WV'+str(i)+'PHA' + else: + name = 'Component Type Error' + symbol = 'Component Type Error' + + return name, symbol + +def create_aggregated_data(component_number): + wv_amp_mv = MeasurementVariable.objects.get(name='Wave Amplitude') + wv_frq_mv = MeasurementVariable.objects.get(name='Wave Frequency') + wv_pha_mv = MeasurementVariable.objects.get(name='Wave Phase') + sl_mv = MeasurementVariable.objects.get(name='Sea Level') + + sl_min = Variable.objects.get(name = 'Sea Level [MIN]') + sl_max = Variable.objects.get(name = 'Sea Level [MAX]') + sl_avg = Variable.objects.get(name = 'Sea Level [AVG]') + sl_std = Variable.objects.get(name = 'Sea Level [STDV]') + sl_swh = Variable.objects.get(name = 'Significant Wave Height') + + sl_variables = [sl_min, sl_max, sl_avg, sl_std, sl_swh] + + + aggregated_data = { + wv_amp_mv.name: {}, + wv_frq_mv.name: {}, + wv_pha_mv.name: {}, + sl_mv.name: {} + } + + for sl_variable in sl_variables: + aggregated_data[sl_mv.name][sl_variable.name] = { + 'ref_variable_id': sl_variable.id, + 'symbol': sl_variable.symbol, + 'data': [] + } + + for i in range(component_number): + amp_ref, frq_ref, pha_ref = get_wave_component_ref_variables(i) + + amp_name, amp_symbol = get_wave_component_name_and_symbol(i+1, 'Amplitude') + frq_name, frq_symbol = get_wave_component_name_and_symbol(i+1, 'Frequency') + pha_name, pha_symbol = get_wave_component_name_and_symbol(i+1, 'Phase') + + aggregated_data[wv_amp_mv.name][amp_name] = { + 'ref_variable_id': amp_ref.id, + 'symbol': amp_symbol, + 'data': [] + } + aggregated_data[wv_frq_mv.name][frq_name] = { + 'ref_variable_id': frq_ref.id, + 'symbol': frq_symbol, + 'data': [] + } + aggregated_data[wv_pha_mv.name][pha_name] = { + 'ref_variable_id': pha_ref.id, + 'symbol': pha_symbol, + 'data': [] + } + + return aggregated_data + +def append_in_aggregated_data(aggregated_data, datetime, station_id, mv_name, var_name, value): + entry = { + 'measured': value, + 'datetime': datetime, + 'station_id': station_id, + } + + aggregated_data[mv_name][var_name]['data'].append(entry) + + return aggregated_data + +def get_wave_aggregated_data(station_id, data, initial_datetime, range_interval, calc_interval, component_number): + wv_amp_mv = MeasurementVariable.objects.get(name='Wave Amplitude') + wv_frq_mv = MeasurementVariable.objects.get(name='Wave Frequency') + wv_pha_mv = MeasurementVariable.objects.get(name='Wave Phase') + sl_mv = MeasurementVariable.objects.get(name='Sea Level') + + sl_min = Variable.objects.get(name = 'Sea Level [MIN]') + sl_max = Variable.objects.get(name = 'Sea Level [MAX]') + sl_avg = Variable.objects.get(name = 'Sea Level [AVG]') + sl_std = Variable.objects.get(name = 'Sea Level [STDV]') + sl_swh = Variable.objects.get(name = 'Significant Wave Height') + + aggregated_data = create_aggregated_data(component_number) + + for i in range(math.floor(range_interval/calc_interval)): + ini_datetime_slc = initial_datetime+datetime.timedelta(minutes=i*calc_interval) + end_datetime_slc = initial_datetime+datetime.timedelta(minutes=(i+1)*calc_interval) + + data_slice = [entry.measured for entry in data if ini_datetime_slc < entry.datetime <= end_datetime_slc] + + if len(data_slice) > 0: + aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_min.name, np.min(data_slice)) + aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_max.name, np.max(data_slice)) + aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_avg.name, np.mean(data_slice)) + aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_std.name, np.std(data_slice)) + aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_swh.name, 4*np.std(data_slice)) + + wave_components = get_wave_components(data_slice, component_number) + for j, wave_component in enumerate(wave_components): + amp_name, amp_symbol = get_wave_component_name_and_symbol(j+1, 'Amplitude') + frq_name, frq_symbol = get_wave_component_name_and_symbol(j+1, 'Frequency') + pha_name, pha_symbol = get_wave_component_name_and_symbol(j+1, 'Phase') + + amp_value = wave_component.height + frq_value = wave_component.frequency + pha_value = math.degrees(wave_component.phase_rad) % 360 + + aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, wv_amp_mv.name, amp_name, amp_value) + aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, wv_frq_mv.name, frq_name, frq_value) + aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, wv_pha_mv.name, pha_name, pha_value) + + return aggregated_data + +def add_wave_aggregated_data(dataset, aggregated_data): + for mv_name in aggregated_data.keys(): + dataset['results'][mv_name] = {} + for var_name in aggregated_data[mv_name].keys(): + variable_id = aggregated_data[mv_name][var_name]['ref_variable_id'] + variable_data = aggregated_data[mv_name][var_name]['data'] + variable_symbol = aggregated_data[mv_name][var_name]['symbol'] + + dataset['results'][mv_name][variable_symbol] = format_wave_data_var(variable_id, variable_data) + + return dataset + +def create_wave_dataset(station_id, sea_data, initial_datetime, range_interval, calc_interval, component_number): + sea_level = Variable.objects.get(name='Sea Level') + sea_level_mv = MeasurementVariable.objects.get(name='Sea Level') + + dataset = { + "results": { + sea_level_mv.name+' Raw': { + sea_level.symbol: format_wave_data_var(sea_level.id, sea_data), + } + }, + "messages": [], + } + + wave_component_data = get_wave_aggregated_data(station_id, + sea_data, + initial_datetime, + range_interval, + calc_interval, + component_number) + + + dataset = add_wave_aggregated_data(dataset, wave_component_data) + + return dataset + +def create_wave_chart(dataset): + charts = {} + + for element_name, element_data in dataset['results'].items(): + + chart = { + 'chart': { + 'type': 'pie', + 'zoomType': 'xy' + }, + 'title': {'text': element_name}, + 'xAxis': { + 'type': 'datetime', + 'dateTimeLabelFormats': { + 'month': '%e. %b', + 'year': '%b' + }, + 'title': { + 'text': 'Date' + } + }, + 'yAxis': [], + 'exporting': { + 'showTable': True + }, + 'series': [] + } + + opposite = False + y_axis_unit_dict = {} + for variable_name, variable_data in element_data.items(): + current_unit = variable_data['unit'] + if current_unit not in y_axis_unit_dict.keys(): + chart['yAxis'].append({ + 'labels': { + 'format': '{value} ' + variable_data['unit'] + }, + 'title': { + 'text': None + }, + 'opposite': opposite + }) + y_axis_unit_dict[current_unit] = len(chart['yAxis']) - 1 + opposite = not opposite + + current_y_axis_index = y_axis_unit_dict[current_unit] + data = [] + for record in variable_data['data']: + data.append({ + 'x': record['date'], + 'y': record['value'], + }) + + chart['series'].append({ + 'name': variable_name, + 'color': variable_data['color'], + 'type': variable_data['default_representation'], + 'unit': variable_data['unit'], + 'data': data, + 'yAxis': current_y_axis_index + }) + chart['chart']['type'] = variable_data['default_representation'], + + charts[slugify(element_name)] = chart + + return charts + +@require_http_methods(["GET"]) +def get_wave_data(request): + station_id = request.GET.get('station_id', None) + initial_date = request.GET.get('initial_date', None) + initial_time = request.GET.get('initial_time', None) + range_interval = request.GET.get('range_interval', None) + calc_interval = request.GET.get('calc_interval', None) + component_number = request.GET.get('component_number', None) + + tz_client = request.GET.get('tz_client', None) + tz_settings = pytz.timezone(settings.TIMEZONE_NAME) + + initial_datetime_str = initial_date+' '+initial_time + initial_datetime = datetime_constructor.strptime(initial_datetime_str, '%Y-%m-%d %H:%M') + initial_datetime = pytz.timezone(tz_client).localize(initial_datetime) + initial_datetime = initial_datetime.astimezone(tz_settings) + + range_intervals = {'30min': 30, "1h": 60, "3h": 180,} + + if range_interval in range_intervals.keys(): + range_interval = range_intervals[range_interval] + else: + response = {"message": "Not valid interval."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + calc_intervals = {'1min': 1, '5min': 5, '10min': 10, '15min': 15,} + + if calc_interval in calc_intervals.keys(): + calc_interval = calc_intervals[calc_interval] + else: + response = {"message": "Not valid calc interval."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + station_id = int(station_id) + component_number = int(component_number) + + final_datetime = initial_datetime + datetime.timedelta(minutes=range_interval) + + variable = Variable.objects.get(name="Sea Level") # Sea Level + sea_data = HighFrequencyData.objects.filter(variable_id=variable.id, + station_id=station_id, + datetime__gt=initial_datetime, + datetime__lte=final_datetime).order_by('datetime') + + dataset = {"results": {}, "messages": []} + + if len(sea_data) > 0: + dataset = create_wave_dataset(station_id, sea_data, initial_datetime, + range_interval, calc_interval, component_number) + + charts = create_wave_chart(dataset) + + return JsonResponse(charts) + + +from wx.models import Equipment, EquipmentType, Manufacturer, FundingSource +from django.core.serializers import serialize + + +@require_http_methods(["GET"]) +def get_equipment_inventory(request): + template = loader.get_template('wx/maintenance_reports/equipment_inventory.html') + context = {} + + return HttpResponse(template.render(context, request)) + + +def get_value(variable): + if variable is None: + return '---' + return variable + +def equipment_condition(condition): + if condition == 'F': + return 'Fully Functional' + elif condition == 'P': + return 'Partially Functional' + elif condition == 'N': + return 'Not Functional' + return None + +@require_http_methods(["GET"]) +def get_equipment_inventory_data(request): + equipment_types = EquipmentType.objects.all() + manufacturers = Manufacturer.objects.all() + equipments = Equipment.objects.all() + funding_sources = FundingSource.objects.all() + stations = Station.objects.all() + + equipment_list = [] + for equipment in equipments: + try: + equipment_type = equipment_types.get(id=equipment.equipment_type_id) + funding_source = funding_sources.get(id=equipment.funding_source_id) + manufacturer = manufacturers.get(id=equipment.manufacturer_id) + + station = None + if equipment.location_id: + station = stations.get(id=equipment.location_id) + + equipment_dict = { + 'equipment_id': equipment.id, + 'equipment_type': equipment_type.name, + 'equipment_type_id': equipment_type.id, + 'funding_source': funding_source.name, + 'funding_source_id': funding_source.id, + 'manufacturer': manufacturer.name, + 'manufacturer_id': manufacturer.id, + 'model': equipment.model, + 'serial_number': equipment.serial_number, + 'acquisition_date': equipment.acquisition_date, + 'first_deploy_date': equipment.first_deploy_date, + 'last_calibration_date': equipment.last_calibration_date, + 'next_calibration_date': equipment.next_calibration_date, + 'decommission_date': equipment.decommission_date, + 'last_deploy_date': equipment.last_deploy_date, + 'location': f"{station.name} - {station.code}" if station else 'Office', + 'location_id': station.id if station else None, + 'condition': equipment_condition(equipment.condition), + 'condition_id': equipment.condition, + } + equipment_list.append(equipment_dict) + except ObjectDoesNotExist: + pass + + equipment_conditions = [ + {'name': 'Fully Functional', 'id': 'F'}, + {'name': 'Partially Functional', 'id': 'P'}, + {'name': 'Not Functional', 'id': 'N'}, + ] + + station_list = [{'name': f"{station.name} - {station.code}", + 'id': station.id} for station in stations] + + response = { + 'equipments': equipment_list, + 'equipment_types': list(equipment_types.values()), + 'manufacturers': list(manufacturers.values()), + 'funding_sources': list(funding_sources.values()), + 'stations': station_list, + 'equipment_conditions': equipment_conditions, + } + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def create_equipment(request): + equipment_type_id = request.GET.get('equipment_type', None) + manufacturer_id = request.GET.get('manufacturer', None) + funding_source_id = request.GET.get('funding_source', None) + model = request.GET.get('model', None) + serial_number = request.GET.get('serial_number', None) + acquisition_date = request.GET.get('acquisition_date', None) + first_deploy_date = request.GET.get('first_deploy_date', None) + last_calibration_date = request.GET.get('last_calibration_date', None) + next_calibration_date = request.GET.get('next_calibration_date', None) + decommission_date = request.GET.get('decommission_date', None) + location_id = request.GET.get('location', None) + condition_id = request.GET.get('condition', None) + last_deploy_date = request.GET.get('last_deploy_date', None) + + equipment_type = EquipmentType.objects.get(id=equipment_type_id) + manufacturer = Manufacturer.objects.get(id=manufacturer_id) + funding_source = FundingSource.objects.get(id=funding_source_id) + + location = None + if location_id: + location = Station.objects.get(id=location_id) + + try: + equipment = Equipment.objects.get( + equipment_type=equipment_type, + serial_number = serial_number, + ) + + message = 'Already exist an equipment of equipment type ' + message += equipment_type.name + message += ' and serial number ' + message += equipment.serial_number + + response = {'message': message} + + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + except ObjectDoesNotExist: + now = datetime.datetime.now() + + equipment = Equipment.objects.create( + created_at = now, + updated_at = now, + equipment_type = equipment_type, + manufacturer = manufacturer, + funding_source = funding_source, + model = model, + serial_number = serial_number, + acquisition_date = acquisition_date, + first_deploy_date = first_deploy_date, + last_calibration_date = last_calibration_date, + next_calibration_date = next_calibration_date, + decommission_date = decommission_date, + location = location, + condition = condition, + last_deploy_date = last_deploy_date, + ) + + response = {'equipment_id': equipment.id} + + return JsonResponse(response, status=status.HTTP_200_OK) + +@require_http_methods(["POST"]) +def update_equipment(request): + equipment_id = request.GET.get('equipment_id', None) + equipment_type_id = request.GET.get('equipment_type', None) + manufacturer_id = request.GET.get('manufacturer', None) + funding_source_id = request.GET.get('funding_source', None) + model = request.GET.get('model', None) + serial_number = request.GET.get('serial_number', None) + acquisition_date = request.GET.get('acquisition_date', None) + first_deploy_date = request.GET.get('first_deploy_date', None) + last_calibration_date = request.GET.get('last_calibration_date', None) + next_calibration_date = request.GET.get('next_calibration_date', None) + decommission_date = request.GET.get('decommission_date', None) + location_id = request.GET.get('location', None) + condition = request.GET.get('condition', None) + last_deploy_date = request.GET.get('last_deploy_date', None) + + equipment_type = EquipmentType.objects.get(id=equipment_type_id) + manufacturer = Manufacturer.objects.get(id=manufacturer_id) + funding_source = FundingSource.objects.get(id=funding_source_id) + + location = None + if location_id: + location = Station.objects.get(id=location_id) + + try: + equipment = Equipment.objects.get(equipment_type=equipment_type, serial_number=serial_number) + + if int(equipment_id) != equipment.id: + message = f"Could not update. Already exist an equipment of \ + equipment type {equipment_type.name} and serial \ + number {equipment.serial_number}" + + response = {'message': message} + + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + except ObjectDoesNotExist: + pass + + try: + equipment = Equipment.objects.get(id=equipment_id) + + now = datetime.datetime.now() + + equipment.updated_at = now + equipment.equipment_type = equipment_type + equipment.manufacturer = manufacturer + equipment.funding_source = funding_source + equipment.model = model + equipment.serial_number = serial_number + equipment.acquisition_date = acquisition_date + equipment.first_deploy_date = first_deploy_date + equipment.last_calibration_date = last_calibration_date + equipment.next_calibration_date = next_calibration_date + equipment.decommission_date = decommission_date + equipment.location = location + equipment.condition = condition + equipment.last_deploy_date = last_deploy_date + equipment.save() + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + except ObjectDoesNotExist: + message = "Object not found" + response = {'message': message} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def delete_equipment(request): + equipment_id = request.GET.get('equipment_id', None) + try: + equipment = Equipment.objects.get(id=equipment_id) + equipment.delete() + except ObjectDoesNotExist: + pass + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +################################################################ +################################################################ + +############################# List ############################# + +@require_http_methods(["GET"]) +def get_maintenance_reports(request): # Maintenance report page + template = loader.get_template('wx/maintenance_reports/maintenance_reports.html') + context = {} + return HttpResponse(template.render(context, request)) + + +@require_http_methods(["PUT"]) +def get_maintenance_report_list(request): + form_data = json.loads(request.body.decode()) + + maintenance_reports = MaintenanceReport.objects.filter(visit_date__gte = form_data['start_date'], visit_date__lte = form_data['end_date']) + + response = {} + response['maintenance_report_list'] = [] + + for maintenance_report in maintenance_reports: + if maintenance_report.status != '-': + station, station_profile, technician, visit_type = get_maintenance_report_obj(maintenance_report) + + if station.is_automatic == form_data['is_automatic']: + if maintenance_report.status == 'A': + maintenance_report_status = 'Approved' + elif maintenance_report.status == 'P': + maintenance_report_status = 'Published' + else: + maintenance_report_status = 'Draft' + + maintenance_report_object = { + 'maintenance_report_id': maintenance_report.id, + 'station_name': station.name, + 'station_profile': station_profile.name, + 'station_type': 'Automatic' if station.is_automatic else 'Manual', + 'visit_date': maintenance_report.visit_date, + 'next_visit_date': maintenance_report.next_visit_date, + 'technician': technician.name, + 'type_of_visit': visit_type.name, + 'status': maintenance_report_status, + } + + response['maintenance_report_list'].append(maintenance_report_object) + + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["GET"]) # Update maintenance report from existing report +def update_maintenance_report(request, id): + maintenance_report = MaintenanceReport.objects.get(id=id) + if maintenance_report.status == 'A': + response={'message': "Approved reports can not be editable."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + elif maintenance_report.status == '-': + response={'message': "This report is deleated."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + template = loader.get_template('wx/maintenance_reports/new_report.html') + + context = {} + context['station_list'] = Station.objects.select_related('profile').all() + context['visit_type_list'] = VisitType.objects.all() + context['technician_list'] = Technician.objects.all() + + context['maintenance_report_id'] = id + + return HttpResponse(template.render(context, request)) + + +@require_http_methods(["PUT"]) +def delete_maintenance_report(request, id): + now = datetime.datetime.now() + + maintenance_report = MaintenanceReport.objects.get(id=id) + maintenance_report.status = '-' + maintenance_report.save() + + response={} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["PUT"]) +def approve_maintenance_report(request, id): + now = datetime.datetime.now() + + maintenance_report = MaintenanceReport.objects.get(id=id) + maintenance_report.status = 'A' + maintenance_report.save() + + response={} + return JsonResponse(response, status=status.HTTP_200_OK) + +############################# View ############################# + +@require_http_methods(["GET"]) +def get_maintenance_report_view(request, id, source): # Maintenance report view + template = loader.get_template('wx/maintenance_reports/view_report.html') + + maintenance_report = MaintenanceReport.objects.get(id=id) + + station = Station.objects.get(pk=maintenance_report.station_id) + profile = StationProfile.objects.get(pk=station.profile_id) + responsible_technician = Technician.objects.get(pk=maintenance_report.responsible_technician_id) + visit_type = VisitType.objects.get(pk=maintenance_report.visit_type_id) + maintenance_report_station_components = MaintenanceReportStationComponent.objects.filter(maintenance_report_id=maintenance_report.id) + + # maintenance_report_station_component_list = [] + # for maintenance_report_station_component in maintenance_report_station_components: + # dictionary = {'condition': maintenance_report_station_component.condition, + # 'component_classification': maintenance_report_station_component.component_classification, + # } + # maintenance_report_station_component_list.append(dictionary) + + maintenance_report_station_component_list = get_component_list(maintenance_report) + + + other_technicians_ids = [maintenance_report.other_technician_1_id, + maintenance_report.other_technician_2_id, + maintenance_report.other_technician_3_id] + other_technicians = [] + for other_technician_id in other_technicians_ids: + if other_technician_id: + other_technician = Technician.objects.get(id=other_technician_id) + other_technicians.append(other_technician.name) + + other_technicians = ", ".join(other_technicians) + + + context = {} + + if source == 0: + context['source'] = 'edit' + else: + context['source'] = 'list' + + context['visit_summary_information'] = { + "report_number": maintenance_report.id, + "responsible_technician": responsible_technician.name, + "date_of_visit": maintenance_report.visit_date, + "date_of_next_visit": maintenance_report.next_visit_date, + "start_time": maintenance_report.initial_time, + "other_technicians": other_technicians, + "end_time": maintenance_report.end_time, + "type_of_visit": visit_type.name, + "station_on_arrival_conditions": maintenance_report.station_on_arrival_conditions, + "current_visit_summary": maintenance_report.current_visit_summary, + "next_visit_summary": maintenance_report.next_visit_summary, + "maintenance_report_status": maintenance_report.status, + } + + context['station_information'] = { + "station_name": station.name, + "station_host_name": "---", + "station_ID": station.code, + "wigos_ID": station.wigos, + "station_type": 'Automatic' if station.is_automatic else 'Manual', + "station_profile": profile.name, + "latitude": station.latitude, + "elevation": station.elevation, + "longitude": station.longitude, + "district": station.region, + "transmission_type": "---", + "transmission_ID": "---", + "transmission_interval": "---", + "measurement_interval": "---", + "data_of_first_operation": station.begin_date, + "data_of_relocation": station.relocation_date, + } + + context['contact_information'] = maintenance_report.contacts + + context['equipment_records'] = maintenance_report_station_component_list + + # JSON + # return JsonResponse(context, status=status.HTTP_200_OK) + + return HttpResponse(template.render(context, request)) + + +############################# New ############################## + +def get_maintenance_report_obj(maintenance_report): + station = Station.objects.get(id=maintenance_report.station_id) + station_profile = StationProfile.objects.get(id=station.profile_id) + technician = Technician.objects.get(id=maintenance_report.responsible_technician_id) + visit_type = VisitType.objects.get(id=maintenance_report.visit_type_id) + + return station, station_profile, technician, visit_type + + +def get_maintenance_report_form(request): # New maintenance report form page + template = loader.get_template('wx/maintenance_reports/new_report.html') + + context = {} + context['station_list'] = Station.objects.select_related('profile').all() + context['visit_type_list'] = VisitType.objects.all() + context['technician_list'] = Technician.objects.all() + + return HttpResponse(template.render(context, request)) + + +def get_station_contacts(station_id): + maintenance_report_list = MaintenanceReport.objects.filter(station_id=station_id).order_by('visit_date') + + for maintenance_report in maintenance_report_list: + if maintenance_report.contacts != '': + return maintenance_report.contacts + + return None + + +@require_http_methods(["POST"]) # Create maintenance report from sratch +def create_maintenance_report(request): + now = datetime.datetime.now() + + form_data = json.loads(request.body.decode()) + + try: + maintenance_report = MaintenanceReport.objects.get(station_id = form_data['station_id'], visit_date = form_data['visit_date']) + + if maintenance_report.status=="-": + maintenance_report.delete() + except ObjectDoesNotExist: + pass + + try: + maintenance_report = MaintenanceReport.objects.get(station_id = form_data['station_id'], visit_date = form_data['visit_date']) + + response = {"message": "Maintenance report already exist for chosen station and date."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + except ObjectDoesNotExist: + maintenance_report = MaintenanceReport.objects.create( + created_at = now, + updated_at = now, + station_id = form_data['station_id'], + responsible_technician_id = form_data['responsible_technician_id'], + visit_type_id = form_data['visit_type_id'], + visit_date = form_data['visit_date'], + initial_time = form_data['initial_time'], + contacts = get_station_contacts(form_data['station_id']), + ) + + station = Station.objects.get(pk=maintenance_report.station_id) + station_profile_component_list = StationProfileComponent.objects.filter(profile_id = station.profile_id) + + for station_profile_component in station_profile_component_list: + station_component = StationComponent.objects.get(id=station_profile_component.station_component_id) + + maintenance_report_station_component = MaintenanceReportStationComponent.objects.create( + maintenance_report_id = maintenance_report.id, + station_component_id = station_component.id, + condition = station_component.report_template, + ) + + response={"maintenance_report_id": maintenance_report.id} + + return JsonResponse(response, status=status.HTTP_200_OK) + + +def get_ckeditor_config(): + ckeditor_config = { + 'toolbar': [ + ['Bold', 'Italic', 'Font'], + ['Format', 'Styles', 'TextColor', 'BGColor', 'RemoveFormat'], + ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock', 'Indent', 'Outdent'], + ['HorizontalRule', 'BulletedList'], + ['Blockquote', 'Source', 'Link', 'Unlink', 'Image', 'Table', 'Print'] + ], + 'removeButtons': 'Image', + 'extraAllowedContent' : 'img(*){*}[*]', + 'editorplaceholder': 'Description of station upon arribal:', + 'language': 'en', + } + return ckeditor_config + +def get_component_list(maintenance_report): + station, station_profile, technician, visit_type = get_maintenance_report_obj(maintenance_report) + + if station.profile_id is not None: + profile = StationProfile.objects.get(pk=station.profile_id) + station_profile_component_list = StationProfileComponent.objects.filter(profile_id=station.profile_id) + + maintenance_report_station_component_list = [] + + for station_profile_component in station_profile_component_list: + station_component = StationComponent.objects.get(id=station_profile_component.station_component_id) + maintenance_report_station_component = MaintenanceReportStationComponent.objects.get(maintenance_report_id=maintenance_report.id, + station_component_id=station_component.id) + + # Need one ckeditor config per text editor, otherwise it could store data in incorrect variables. + dictionary = {'component_id': station_component.id, + 'presentation_order': station_profile_component.presentation_order, + 'component_name': station_component.name, + 'condition': maintenance_report_station_component.condition, + 'component_classification': maintenance_report_station_component.component_classification, + 'ckeditor_config': get_ckeditor_config()} + + maintenance_report_station_component_list.append(dictionary) + + maintenance_report_station_component_list = sorted(maintenance_report_station_component_list, key=lambda d: d['presentation_order']) + + return maintenance_report_station_component_list + + +@require_http_methods(["GET"]) +def get_maintenance_report(request, id): + maintenance_report = MaintenanceReport.objects.get(id=id) + + station, station_profile, technician, visit_type = get_maintenance_report_obj(maintenance_report) + + response = {} + response['station_information'] = { + "station_name": station.name, + "station_host_name": "---", + "station_ID": station.code, + "wigos_ID": station.wigos, + "station_type": 'Automatic' if station.is_automatic else 'Manual', + "station_profile": station_profile.name, + "latitude": station.latitude, + "elevation": station.elevation, + "longitude": station.longitude, + "district": station.region, + "transmission_type": "---", + "transmission_ID": "---", + "transmission_interval": "---", + "measurement_interval": "---", + "data_of_first_operation": station.begin_date, + "data_of_relocation": station.relocation_date, + } + + response["station_id"] = station.id + response["responsible_technician"] = technician.name + response["responsible_technician_id"] = maintenance_report.responsible_technician_id + response["visit_date"] = maintenance_report.visit_date + response["next_visit_date"] = maintenance_report.next_visit_date + response["initial_time"] = maintenance_report.initial_time + response["end_time"] = maintenance_report.end_time + response["visit_type"] = visit_type.name + response["visit_type_id"] = visit_type.id + response["station_on_arrival_conditions"] = maintenance_report.station_on_arrival_conditions + response["current_visit_summary"] = maintenance_report.current_visit_summary + response["next_visit_summary"] = maintenance_report.next_visit_summary + response["other_technician_1"] = maintenance_report.other_technician_1_id + response["other_technician_2"] = maintenance_report.other_technician_2_id + response["other_technician_3"] = maintenance_report.other_technician_3_id + + response['contacts'] = maintenance_report.contacts + response['components'] = get_component_list(maintenance_report) + response['steps'] = len(response['components']) + + if maintenance_report.data_logger_file_name is None: + response['data_logger_file_name'] = "Upload latest data logger program" + else: + response['data_logger_file_name'] = maintenance_report.data_logger_file_name + + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["PUT"]) +def update_maintenance_report_condition(request, id): + now = datetime.datetime.now() + + maintenance_report = MaintenanceReport.objects.get(id=id) + + form_data = json.loads(request.body.decode()) + + maintenance_report.station_on_arrival_conditions = form_data['conditions'] + + maintenance_report.updated_at = now + maintenance_report.save() + + response={} + + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["PUT"]) +def update_maintenance_report_component(request, id, component_id): + now = datetime.datetime.now() + + maintenance_report_station_component = MaintenanceReportStationComponent.objects.get(maintenance_report_id=id, station_component_id=component_id) + + form_data = json.loads(request.body.decode()) + + maintenance_report_station_component.component_classification=form_data['component_classification'] + maintenance_report_station_component.condition=form_data['component_condition'] + + maintenance_report_station_component.updated_at = now + maintenance_report_station_component.save() + + response={} + + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["PUT"]) +def update_maintenance_report_contacts(request, id): + now = datetime.datetime.now() + + maintenance_report = MaintenanceReport.objects.get(id=id) + + form_data = json.loads(request.body.decode()) + + maintenance_report.contacts = form_data['contacts'] + + maintenance_report.updated_at = now + maintenance_report.save() + + response={} + + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def update_maintenance_report_datalogger(request, id): + # print(request.FILES) + if 'data_logger_file' in request.FILES: + maintenance_report = MaintenanceReport.objects.get(id=id) + + data_logger_file = request.FILES['data_logger_file'].file + data_logger_file_name = str(request.FILES['data_logger_file']) + data_logger_file_content = b64encode(data_logger_file.read()).decode('utf-8') + + maintenance_report.data_logger_file = data_logger_file_content + maintenance_report.data_logger_file_name = data_logger_file_name + maintenance_report.updated_at = datetime.datetime.now() + maintenance_report.save() + + response = {'data_logger_file_name', data_logger_file_name} + + return JsonResponse(response, status=status.HTTP_200_OK) + + # print("Data logger file not uploaded.") + response={'message': "Data logger file not uploaded."} + return JsonResponse(response, status=status.HTTP_206_PARTIAL_CONTENT) + + +@require_http_methods(["PUT"]) +def update_maintenance_report_summary(request, id): + now = datetime.datetime.now() + + maintenance_report = MaintenanceReport.objects.get(id=id) + + form_data = json.loads(request.body.decode()) + + if form_data['other_technician_1']: + other_technician_1 = Technician.objects.get(id=form_data['other_technician_1']) + else: + other_technician_1 = None + + if form_data['other_technician_2']: + other_technician_2 = Technician.objects.get(id=form_data['other_technician_2']) + else: + other_technician_2 = None + + if form_data['other_technician_3']: + other_technician_3 = Technician.objects.get(id=form_data['other_technician_3']) + else: + other_technician_3 = None + + maintenance_report.other_technician_1 = other_technician_1 + maintenance_report.other_technician_2 = other_technician_2 + maintenance_report.other_technician_3 = other_technician_3 + maintenance_report.next_visit_date = form_data['next_visit_date'] + maintenance_report.end_time = form_data['end_time'] + maintenance_report.current_visit_summary = form_data['current_visit_summary'] + maintenance_report.next_visit_summary = form_data['next_visit_summary'] + maintenance_report.status = form_data['status'] + + maintenance_report.updated_at = now + maintenance_report.save() + + response={} + + return JsonResponse(response, status=status.HTTP_200_OK) + + +################################################################ +################################################################ + +class SpatialAnalysisView(LoginRequiredMixin, TemplateView): + template_name = "wx/spatial_analysis.html" + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + context['quality_flags'] = QualityFlag.objects.all() + + return self.render_to_response(context) + + +@api_view(['GET']) +def raw_data_last_24h(request, station_id): + station = Station.objects.get(pk=station_id) + response_dict = {} + + query = f""" + WITH data AS ( + SELECT to_char((datetime + interval '{station.utc_offset_minutes} minutes') at time zone 'utc', 'YYYY-MM-DD HH24:MI:SS') as datetime, + measured, + variable_id + FROM raw_data + WHERE station_id=%s + AND datetime >= now() - '1 day'::interval + AND measured != {settings.MISSING_VALUE}) + SELECT datetime, + var.name, + var.symbol, + unit.name, + unit.symbol, + measured + FROM data + INNER JOIN wx_variable var ON variable_id=var.id + LEFT JOIN wx_unit unit ON var.unit_id=unit.id + ORDER BY datetime, var.name + """ + + with connection.cursor() as cursor: + + cursor.execute(query, [station.id]) + + rows = cursor.fetchall() + + for row in rows: + + if row[0] not in response_dict.keys(): + response_dict[row[0]] = [] + + obj = { + 'value': row[5], + 'variable': { + 'name': row[1], + 'symbol': row[2], + 'unit_name': row[3], + 'unit_symbol': row[4] + } + } + response_dict[row[0]].append(obj) + + return JsonResponse(response_dict) + + +class StationsMapView(LoginRequiredMixin, TemplateView): + template_name = "wx/station_map.html" + + +class StationMetadataView(LoginRequiredMixin, TemplateView): + template_name = "wx/station_metadata.html" + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + context['station_id'] = kwargs.get('pk', 'null') + print('kwargs', context['station_id']) + + wmo_station_type_list = WMOStationType.objects.all() + context['wmo_station_type_list'] = wmo_station_type_list + + wmo_region_list = WMORegion.objects.all() + context['wmo_region_list'] = wmo_region_list + + wmo_program_list = WMOProgram.objects.all() + context['wmo_program_list'] = wmo_program_list + + station_profile_list = StationProfile.objects.all() + context['station_profile_list'] = station_profile_list + + station_communication_list = StationCommunication.objects.all() + context['station_communication_list'] = station_communication_list + + return self.render_to_response(context) + + +@api_view(['GET']) +def latest_data(request, variable_id): + result = [] + + query = """ + SELECT CASE WHEN var.variable_type ilike 'code' THEN latest.last_data_code ELSE latest.last_data_value::varchar END as value, + sta.longitude, + sta.latitude, + unit.symbol + FROM wx_stationvariable latest + INNER JOIN wx_variable var ON latest.variable_id=var.id + INNER JOIN wx_station sta ON latest.station_id=sta.id + LEFT JOIN wx_unit unit ON var.unit_id=unit.id + WHERE latest.variable_id=%s + AND latest.last_data_value is not null + AND latest.last_data_datetime = ( SELECT MAX(most_recent.last_data_datetime) + FROM wx_stationvariable most_recent + WHERE most_recent.station_id=latest.station_id + AND most_recent.last_data_value is not null) + """ + + with connection.cursor() as cursor: + cursor.execute(query, [variable_id]) + + rows = cursor.fetchall() + + for row in rows: + obj = { + 'value': row[0], + 'longitude': row[1], + 'latitude': row[2], + 'symbol': row[3], + } + + result.append(obj) + + return Response(result, status=status.HTTP_200_OK) + + +class StationImageViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + parser_class = (FileUploadParser,) + queryset = StationImage.objects.all() + serializer_class = serializers.StationImageSerializer + + def get_queryset(self): + station_id = self.request.query_params.get('station_id', None) + + queryset = StationImage.objects.all() + + if station_id is not None: + queryset = queryset.filter(station__id=station_id) + + return queryset + + +class StationFileViewSet(viewsets.ModelViewSet): + permission_classes = (IsAuthenticated,) + parser_class = (FileUploadParser,) + queryset = StationFile.objects.all() + serializer_class = serializers.StationFileSerializer + + def get_queryset(self): + station_id = self.request.query_params.get('station_id', None) + + queryset = StationFile.objects.all() + + if station_id is not None: + queryset = queryset.filter(station__id=station_id) + + return queryset + + +class ExtremesMeansView(LoginRequiredMixin, TemplateView): + template_name = 'wx/products/extremes_means.html' + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + context['station_list'] = Station.objects.values('id', 'name', 'code') + + return self.render_to_response(context) + + +def get_months(): + months = { + 1: 'January', + 2: 'February', + 3: 'March', + 4: 'April', + 5: 'May', + 6: 'June', + 7: 'July', + 8: 'August', + 9: 'September', + 10: 'October', + 11: 'November', + 12: 'December', + } + + return months + + +def get_interval_in_seconds(interval_id): + if interval_id is None: + return None + interval = Interval.objects.get(id=int(interval_id)) + return interval.seconds + + +@require_http_methods(["POST"]) +def update_reference_station(request): + station_id = request.GET.get('station_id', None) + new_reference_station_id = request.GET.get('new_reference_station_id', None) + + station = Station.objects.get(id=station_id) + station.reference_station_id = new_reference_station_id + station.save() + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def update_global_threshold(request): + qc_method = request.GET.get('qc_method', None) + is_automatic = request.GET.get('is_automatic', None) + variable_name = request.GET.get('variable_name', None) + variable = Variable.objects.get(name=variable_name) + + is_automatic = is_automatic == "true" + + if qc_method=='range': + range_min = request.GET.get('range_min', None) + range_max = request.GET.get('range_max', None) + + if is_automatic: + variable.range_min_hourly = range_min + variable.range_max_hourly = range_max + else: + variable.range_min = range_min + variable.range_max = range_max + + elif qc_method=='step': + step = request.GET.get('step', None) + + if is_automatic: + variable.step_hourly = step + else: + variable.step = step + + elif qc_method=='persist': + minimum_variance = request.GET.get('minimum_variance', None) + window = request.GET.get('window', None) + + if is_automatic: + variable.persistence_hourly = minimum_variance + variable.persistence_window_hourly = window + else: + variable.persistence = minimum_variance + variable.persistence_window = window + + variable.save() + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +from wx.models import QcRangeThreshold + +@api_view(['GET', 'POST', 'PATCH', 'DELETE']) +def range_threshold_view(request): # For synop and daily data captures + if request.method == 'GET': + + station_id = request.GET.get('station_id', None) + variable_id_list = request.GET.get('variable_id_list', None) + month = request.GET.get('month', None) + + variable_query_statement = "" + month_query_statement = "" + query_parameters = {"station_id": station_id, } + + if station_id is None: + JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if variable_id_list is not None: + variable_id_list = tuple(json.loads(variable_id_list)) + variable_query_statement = "AND variable_id IN %(variable_id_list)s" + query_parameters['variable_id_list'] = variable_id_list + + if month is not None: + month_query_statement = "AND month = %(month)s" + query_parameters['month'] = month + + get_range_threshold_query = f""" + SELECT variable.id + ,variable.name + ,range_threshold.station_id + ,range_threshold.range_min + ,range_threshold.range_max + ,range_threshold.interval + ,range_threshold.month + ,TO_CHAR(TO_DATE(range_threshold.month::text, 'MM'), 'Month') + ,range_threshold.id + FROM wx_qcrangethreshold range_threshold + JOIN wx_variable variable on range_threshold.variable_id = variable.id + WHERE station_id = %(station_id)s + {variable_query_statement} + {month_query_statement} + ORDER BY variable.name, range_threshold.month + """ + + result = [] + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + cursor.execute(get_range_threshold_query, query_parameters) + + rows = cursor.fetchall() + for row in rows: + obj = { + 'variable': { + 'id': row[0], + 'name': row[1] + }, + 'station_id': row[2], + 'range_min': row[3], + 'range_max': row[4], + 'interval': row[5], + 'month': row[6], + 'month_desc': row[7], + 'id': row[8], + + } + result.append(obj) + + return Response(result, status=status.HTTP_200_OK) + + + elif request.method == 'POST': + + station_id = request.data['station_id'] + variable_id = request.data['variable_id'] + month = request.data['month'] + interval = request.data['interval'] + range_min = request.data['range_min'] + range_max = request.data['range_max'] + + if station_id is None: + JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if variable_id is None: + JsonResponse(data={"message": "'variable_id' parameter cannot be null."}, + status=status.HTTP_400_BAD_REQUEST) + + if month is None: + JsonResponse(data={"message": "'month' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if interval is None: + JsonResponse(data={"message": "'interval' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if range_min is None: + JsonResponse(data={"message": "'range_min' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if range_max is None: + JsonResponse(data={"message": "'range_max' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + post_range_threshold_query = f""" + INSERT INTO wx_qcrangethreshold (created_at, updated_at, range_min, range_max, station_id, variable_id, interval, month) + VALUES (now(), now(), %(range_min)s, %(range_max)s , %(station_id)s, %(variable_id)s, %(interval)s, %(month)s) + """ + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + try: + cursor.execute(post_range_threshold_query, + {'station_id': station_id, 'variable_id': variable_id, 'month': month, + 'interval': interval, 'range_min': range_min, 'range_max': range_max, }) + except psycopg2.errors.UniqueViolation: + conn.rollback() + return JsonResponse(data={"message": "Threshold already exists"}, + status=status.HTTP_400_BAD_REQUEST) + + conn.commit() + return Response(status=status.HTTP_200_OK) + + + elif request.method == 'PATCH': + range_threshold_id = request.GET.get('id', None) + month = request.data['month'] + interval = request.data['interval'] + range_min = request.data['range_min'] + range_max = request.data['range_max'] + + if range_threshold_id is None: + JsonResponse(data={"message": "'id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if month is None: + JsonResponse(data={"message": "'month' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if interval is None: + JsonResponse(data={"message": "'interval' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if range_min is None: + JsonResponse(data={"message": "'range_min' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if range_max is None: + JsonResponse(data={"message": "'range_max' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + patch_range_threshold_query = f""" + UPDATE wx_qcrangethreshold + SET month = %(month)s + ,interval = %(interval)s + ,range_min = %(range_min)s + ,range_max = %(range_max)s + WHERE id = %(range_threshold_id)s + """ + + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + try: + cursor.execute(patch_range_threshold_query, + {'range_threshold_id': range_threshold_id, 'month': month, 'interval': interval, + 'range_min': range_min, 'range_max': range_max, }) + except psycopg2.errors.UniqueViolation: + conn.rollback() + return JsonResponse(data={"message": "Threshold already exists"}, + status=status.HTTP_400_BAD_REQUEST) + + conn.commit() + return Response(status=status.HTTP_200_OK) + + + elif request.method == 'DELETE': + range_threshold_id = request.GET.get('id', None) + + if range_threshold_id is None: + JsonResponse(data={"message": "'range_threshold_id' parameter cannot be null."}, + status=status.HTTP_400_BAD_REQUEST) + + delete_range_threshold_query = f""" DELETE FROM wx_qcrangethreshold WHERE id = %(range_threshold_id)s """ + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + try: + cursor.execute(delete_range_threshold_query, {'range_threshold_id': range_threshold_id}) + except: + conn.rollback() + return JsonResponse(data={"message": "Error on delete threshold"}, + status=status.HTTP_400_BAD_REQUEST) + + conn.commit() + return Response(status=status.HTTP_200_OK) + + return Response([], status=status.HTTP_200_OK) + + +def get_range_threshold_form(request): + template = loader.get_template('wx/quality_control/range_threshold.html') + + context = {} + context['station_list'] = Station.objects.select_related('profile').all() + context['station_profile_list'] = StationProfile.objects.all() + context['station_watershed_list'] = Watershed.objects.all() + context['station_district_list'] = AdministrativeRegion.objects.all() + context['interval_list'] = Interval.objects.filter(seconds__gt=1).order_by('seconds') + + return HttpResponse(template.render(context, request)) + + +def get_range_threshold_list(station_id, variable_id, interval, is_reference=False): + threshold_list = [] + months = get_months() + for month_id in sorted(months.keys()): + month = months[month_id] + try: + threshold = QcRangeThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=interval, month=month_id) + threshold_entry = { + 'month': month_id, + 'min': str(threshold.range_min) if threshold.range_min is not None else '---', + 'max': str(threshold.range_max) if threshold.range_max is not None else '---', + } + except ObjectDoesNotExist: + if is_reference: + try: + threshold = QcRangeThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=None, month=month_id) + threshold_entry = { + 'month': month_id, + 'min': str(threshold.range_min)+'*' if threshold.range_min is not None else '---', + 'max': str(threshold.range_max)+'*' if threshold.range_max is not None else '---', + } + except ObjectDoesNotExist: + threshold_entry = { + 'month': month_id, + 'min': '---', + 'max': '---', + } + else: + threshold_entry = { + 'month': month_id, + 'min': '---', + 'max': '---', + } + threshold_list.append(threshold_entry) + return threshold_list + + +def get_range_threshold_in_list(threshold_list, month_id): + if threshold_list: + for threshold_entry in threshold_list: + if threshold_entry['month'] == month_id: + return threshold_entry + + threshold_entry = {'month': month_id, 'min': '---', 'max': '---'} + return threshold_entry + + +def format_range_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable_name): + months = get_months() + + formated_thresholds = [] + for month_id in sorted(months.keys()): + month_name = months[month_id] + + custom_entry = get_range_threshold_in_list(custom_thresholds, month_id) + reference_entry = get_range_threshold_in_list(reference_thresholds, month_id) + + formated_threshold = { + 'variable_name': variable_name, + 'month_name': month_name, + 'global':{ + 'children':{ + 'g_min': global_thresholds['min'], + 'g_max': global_thresholds['max'], + } + }, + 'reference':{ + 'children':{ + 'r_min': reference_entry['min'], + 'r_max': reference_entry['max'], + } + }, + 'custom':{ + 'children':{ + 'c_min': custom_entry['min'], + 'c_max': custom_entry['max'], + } + } + } + formated_thresholds.append(formated_threshold) + + return formated_thresholds + + +@require_http_methods(["GET"]) +def get_range_threshold(request): + station_id = request.GET.get('station_id', None) + if station_id is None: + response = {'message': "Field Station can not be empty."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + + variable_ids = request.GET.get('variable_ids', None) + if variable_ids is None: + response = {'message': "Field Variables can not be empty."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + interval_id = request.GET.get('interval_id', None) + # if interval_id is None: + # response = {'message': "Field Measurement Interval can not be empty."} + # return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + station = Station.objects.get(id=int(station_id)) + variable_ids = [int(variable_id) for variable_id in variable_ids.split(",")] + interval_seconds = get_interval_in_seconds(interval_id) + + reference_station_id = station.reference_station_id + if reference_station_id: + reference_station = Station.objects.get(id=station.reference_station_id) + reference_station_name = reference_station.name+' - '+reference_station.code + else: + reference_station = None + reference_station_name = None + + data = { + 'reference_station_id': reference_station_id, + 'reference_station_name': reference_station_name, + 'variable_data': {}, + } + + for variable_id in variable_ids: + variable = Variable.objects.get(id=variable_id) + + custom_thresholds = get_range_threshold_list(station.id, variable.id, interval_seconds, is_reference=False) + + if reference_station: + reference_thresholds = get_range_threshold_list(reference_station.id, variable.id, interval_seconds, is_reference=True) + else: + reference_thresholds = None + + global_thresholds = {} + if station.is_automatic: + global_thresholds['min'] = variable.range_min_hourly + global_thresholds['max'] = variable.range_max_hourly + else: + global_thresholds['min'] = variable.range_min + global_thresholds['max'] = variable.range_max + + global_thresholds['min'] = '---' if global_thresholds['min'] is None else str(global_thresholds['min']) + global_thresholds['max'] = '---' if global_thresholds['max'] is None else str(global_thresholds['max']) + + formated_thresholds = format_range_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable.name) + + data['variable_data'][variable.name] = formated_thresholds; + + response = {'data': data} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def update_range_threshold(request): + months = get_months() + months_ids = {v: k for k, v in months.items()} + + new_min = request.GET.get('new_min', None) + new_max = request.GET.get('new_max', None) + interval_id = request.GET.get('interval_id', None) + station_id = request.GET.get('station_id', None) + variable_name = request.GET.get('variable_name', None) + month_name = request.GET.get('month_name', None) + + + station = Station.objects.get(id=station_id) + variable = Variable.objects.get(name=variable_name) + month_id = months_ids[month_name] + interval_seconds = get_interval_in_seconds(interval_id) + + qcrangethreshold, created = QcRangeThreshold.objects.get_or_create(station_id=station.id, variable_id=variable.id, month=month_id, interval=interval_seconds) + + qcrangethreshold.range_min = new_min + qcrangethreshold.range_max = new_max + + qcrangethreshold.save() + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def delete_range_threshold(request): + months = get_months() + months_ids = {v: k for k, v in months.items()} + + interval_id = request.GET.get('interval_id', None) + station_id = request.GET.get('station_id', None) + variable_name = request.GET.get('variable_name', None) + month_name = request.GET.get('month_name', None) + + station = Station.objects.get(id=station_id) + variable = Variable.objects.get(name=variable_name) + month_id = months_ids[month_name] + interval_seconds = get_interval_in_seconds(interval_id) + + try: + qcrangethreshold = QcRangeThreshold.objects.get(station_id=station.id, variable_id=variable.id, month=month_id, interval=interval_seconds) + qcrangethreshold.delete() + except ObjectDoesNotExist: + pass + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +from wx.models import QcStepThreshold + + +def get_step_threshold_form(request): + template = loader.get_template('wx/quality_control/step_threshold.html') + + context = {} + context['station_list'] = Station.objects.select_related('profile').all() + context['station_profile_list'] = StationProfile.objects.all() + context['station_watershed_list'] = Watershed.objects.all() + context['station_district_list'] = AdministrativeRegion.objects.all() + context['interval_list'] = Interval.objects.filter(seconds__gt=1).order_by('seconds') + + return HttpResponse(template.render(context, request)) + + +def get_step_threshold_entry(station_id, variable_id, interval, is_reference=False): + try: + threshold = QcStepThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=interval) + threshold_entry = { + 'min': str(threshold.step_min) if threshold.step_min is not None else '---', + 'max': str(threshold.step_max) if threshold.step_max is not None else '---', + } + except ObjectDoesNotExist: + if is_reference: + try: + threshold = QcStepThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=None) + threshold_entry = { + 'min': str(threshold.step_min)+'*' if threshold.step_min is not None else '---', + 'max': str(threshold.step_max)+'*' if threshold.step_max is not None else '---', + } + except ObjectDoesNotExist: + threshold_entry = { + 'min': '---', + 'max': '---', + } + else: + threshold_entry = { + 'min': '---', + 'max': '---', + } + + return threshold_entry + + +def format_step_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable_name): + formated_threshold = { + 'variable_name': variable_name, + 'global':{ + 'children':{ + 'g_min': global_thresholds['min'], + 'g_max': global_thresholds['max'], + } + }, + 'reference':{ + 'children':{ + 'r_min': reference_thresholds['min'], + 'r_max': reference_thresholds['max'], + } + }, + 'custom':{ + 'children':{ + 'c_min': custom_thresholds['min'], + 'c_max': custom_thresholds['max'], + } + } + } + return [formated_threshold] + + +@require_http_methods(["GET"]) +def get_step_threshold(request): + station_id = request.GET.get('station_id', None) + if station_id is None: + response = {'message': "Field Station can not be empty."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + variable_ids = request.GET.get('variable_ids', None) + if variable_ids is None: + response = {'message': "Field Variables can not be empty."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + interval_id = request.GET.get('interval_id', None) + # if interval_id is None: + # response = {'message': "Field Measurement Interval can not be empty."} + # return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + station = Station.objects.get(id=int(station_id)) + variable_ids = [int(variable_id) for variable_id in variable_ids.split(",")] + interval_seconds = get_interval_in_seconds(interval_id) + + reference_station_id = station.reference_station_id + if reference_station_id: + reference_station = Station.objects.get(id=station.reference_station_id) + reference_station_name = reference_station.name+' - '+reference_station.code + else: + reference_station = None + reference_station_name = None + + data = { + 'reference_station_id': reference_station_id, + 'reference_station_name': reference_station_name, + 'variable_data': {}, + } + + for variable_id in variable_ids: + variable = Variable.objects.get(id=variable_id) + + custom_thresholds = get_step_threshold_entry(station.id, variable.id, interval_seconds, is_reference=False) + + if reference_station: + reference_thresholds = get_step_threshold_entry(reference_station.id, variable.id, interval_seconds, is_reference=True) + else: + reference_thresholds = {'min': '---', 'max': '---'} + + global_thresholds = {} + if station.is_automatic: + global_thresholds['min'] = -variable.step_hourly if variable.step_hourly else variable.step_hourly + global_thresholds['max'] = variable.step_hourly + else: + global_thresholds['min'] = -variable.step if variable.step else variable.step + global_thresholds['max'] = variable.step + + global_thresholds['min'] = '---' if global_thresholds['min'] is None else str(global_thresholds['min']) + global_thresholds['max'] = '---' if global_thresholds['max'] is None else str(global_thresholds['max']) + + formated_thresholds = format_step_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable.name) + + data['variable_data'][variable.name] = formated_thresholds; + + response = {'data': data} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def update_step_threshold(request): + new_min = request.GET.get('new_min', None) + new_max = request.GET.get('new_max', None) + interval_id = request.GET.get('interval_id', None) + station_id = request.GET.get('station_id', None) + variable_name = request.GET.get('variable_name', None) + + station = Station.objects.get(id=station_id) + variable = Variable.objects.get(name=variable_name) + interval_seconds = get_interval_in_seconds(interval_id) + + qcstepthreshold, created = QcStepThreshold.objects.get_or_create(station_id=station.id, variable_id=variable.id, interval=interval_seconds) + + qcstepthreshold.step_min = new_min + qcstepthreshold.step_max = new_max + + qcstepthreshold.save() + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def delete_step_threshold(request): + interval_id = request.GET.get('interval_id', None) + station_id = request.GET.get('station_id', None) + variable_name = request.GET.get('variable_name', None) + + station = Station.objects.get(id=station_id) + variable = Variable.objects.get(name=variable_name) + interval_seconds = get_interval_in_seconds(interval_id) + + try: + qcstepthreshold = QcStepThreshold.objects.get(station_id=station.id, variable_id=variable.id, interval=interval_seconds) + qcstepthreshold.delete() + except ObjectDoesNotExist: + pass + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +from wx.models import QcPersistThreshold + + +def get_persist_threshold_form(request): + template = loader.get_template('wx/quality_control/persist_threshold.html') + + context = {} + context['station_list'] = Station.objects.select_related('profile').all() + context['station_profile_list'] = StationProfile.objects.all() + context['station_watershed_list'] = Watershed.objects.all() + context['station_district_list'] = AdministrativeRegion.objects.all() + context['interval_list'] = Interval.objects.filter(seconds__gt=1).order_by('seconds') + + return HttpResponse(template.render(context, request)) + + +def get_persist_threshold_entry(station_id, variable_id, interval, is_reference=False): + try: + threshold = QcPersistThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=interval) + threshold_entry = { + 'var': str(threshold.minimum_variance) if threshold.minimum_variance is not None else '---', + 'win': str(threshold.window) if threshold.window is not None else '---', + } + except ObjectDoesNotExist: + if is_reference: + try: + threshold = QcPersistThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=None) + threshold_entry = { + 'var': str(threshold.minimum_variance)+'*' if threshold.minimum_variance is not None else '---', + 'win': str(threshold.window)+'*' if threshold.window is not None else '---', + } + except ObjectDoesNotExist: + threshold_entry = { + 'var': '---', + 'win': '---', + } + else: + threshold_entry = { + 'var': '---', + 'win': '---', + } + + return threshold_entry + + +def format_persist_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable_name): + formated_threshold = { + 'variable_name': variable_name, + 'global':{ + 'children':{ + 'g_var': global_thresholds['var'], + 'g_win': global_thresholds['win'], + } + }, + 'reference':{ + 'children':{ + 'r_var': reference_thresholds['var'], + 'r_win': reference_thresholds['win'], + } + }, + 'custom':{ + 'children':{ + 'c_var': custom_thresholds['var'], + 'c_win': custom_thresholds['win'], + } + } + } + return [formated_threshold] + + +@require_http_methods(["GET"]) +def get_persist_threshold(request): + station_id = request.GET.get('station_id', None) + if station_id is None: + response = {'message': "Field Station can not be empty."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + variable_ids = request.GET.get('variable_ids', None) + if variable_ids is None: + response = {'message': "Field Variables can not be empty."} + return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + interval_id = request.GET.get('interval_id', None) + # if interval_id is None: + # response = {'message': "Field Measurement Interval can not be empty."} + # return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) + + station = Station.objects.get(id=int(station_id)) + variable_ids = [int(variable_id) for variable_id in variable_ids.split(",")] + interval_seconds = get_interval_in_seconds(interval_id) + + reference_station_id = station.reference_station_id + if reference_station_id: + reference_station = Station.objects.get(id=station.reference_station_id) + reference_station_name = reference_station.name+' - '+reference_station.code + else: + reference_station = None + reference_station_name = None + + data = { + 'reference_station_id': reference_station_id, + 'reference_station_name': reference_station_name, + 'variable_data': {}, + } + + for variable_id in variable_ids: + variable = Variable.objects.get(id=variable_id) + + custom_thresholds = get_persist_threshold_entry(station.id, variable.id, interval_seconds, is_reference=False) + + if reference_station: + reference_thresholds = get_persist_threshold_entry(reference_station.id, variable.id, interval_seconds, is_reference=True) + else: + reference_thresholds = {'var': '---', 'win': '---'} + + global_thresholds = {} + if station.is_automatic: + global_thresholds['var'] = variable.persistence_hourly + global_thresholds['win'] = variable.persistence_window_hourly + else: + global_thresholds['var'] = variable.persistence + global_thresholds['win'] = variable.persistence_window + + global_thresholds['var'] = '---' if global_thresholds['var'] is None else str(global_thresholds['var']) + global_thresholds['win'] = '---' if global_thresholds['win'] is None else str(global_thresholds['win']) + + formated_thresholds = format_persist_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable.name) + + data['variable_data'][variable.name] = formated_thresholds; + + response = {'data': data} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def update_persist_threshold(request): + new_var = request.GET.get('new_var', None) + new_win = request.GET.get('new_win', None) + station_id = request.GET.get('station_id', None) + variable_name = request.GET.get('variable_name', None) + interval_id = request.GET.get('interval_id', None) + + station = Station.objects.get(id=station_id) + variable = Variable.objects.get(name=variable_name) + interval_seconds = get_interval_in_seconds(interval_id) + + try: + qcpersistthreshold = QcPersistThreshold.objects.get(station_id=station.id, variable_id=variable.id, interval=interval_seconds) + except ObjectDoesNotExist: + qcpersistthreshold = QcPersistThreshold.objects.create(station_id=station.id, variable_id=variable.id, interval=interval_seconds, minimum_variance=new_var, window=new_win) + + qcpersistthreshold.save() + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@require_http_methods(["POST"]) +def delete_persist_threshold(request): + station_id = request.GET.get('station_id', None) + variable_name = request.GET.get('variable_name', None) + interval_id = request.GET.get('interval_id', None) + + station = Station.objects.get(id=station_id) + variable = Variable.objects.get(name=variable_name) + interval_seconds = get_interval_in_seconds(interval_id) + + try: + qcpersistthreshold = QcPersistThreshold.objects.get(station_id=station.id, variable_id=variable.id, interval=interval_seconds) + qcpersistthreshold.delete() + except ObjectDoesNotExist: + pass + + response = {} + return JsonResponse(response, status=status.HTTP_200_OK) + + +@api_view(['GET']) +def daily_means_data_view(request): + station_id = request.GET.get('station_id', None) + month = request.GET.get('month', None) + variable_id_list = request.GET.get('variable_id_list', None) + begin_year = request.GET.get('begin_year', None) + end_year = request.GET.get('end_year', None) + filter_year_query = "" + filter_year_query_avg = "" + period = "All years" + + if station_id is None: + JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if month is None: + JsonResponse(data={"message": "'month' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) + + if variable_id_list is not None: + variable_id_list = json.loads(variable_id_list) + else: + JsonResponse(data={"message": "'variable_id_list' parameter cannot be null."}, + status=status.HTTP_400_BAD_REQUEST) + + if begin_year is not None and end_year is not None: + try: + filter_year_query = "AND EXTRACT(YEAR from day) >= %(begin_year)s AND EXTRACT(YEAR from day) <= %(end_year)s" + period = f"{begin_year} - {end_year}" + + begin_year = int(begin_year) + end_year = int(end_year) + except ValueError: + JsonResponse(data={"message": "Invalid 'begin_year' or 'end_year' parameters."}, + status=status.HTTP_400_BAD_REQUEST) + + month = int(month) + res = {} + variable_dict = {} + variable_symbol_dict = {} + for variable_id in variable_id_list: + query_params_dict = {"station_id": station_id, "month": month, "variable_id": variable_id, + "begin_year": begin_year, "end_year": end_year} + variable = Variable.objects.get(pk=variable_id) + + variable_symbol_dict[variable.symbol] = variable.name + aggregation_type = variable.sampling_operation.name if variable.sampling_operation is not None else None + data_dict = {} + summary_dict = {} + colspan = 1 + headers = ['Average'] + columns = ['value'] + + if aggregation_type == 'Accumulation': + colspan = 3 + headers = ['Greatest', 'Year', 'Years'] + columns = ['agg_value', 'year', 'years'] + + with connection.cursor() as cursor: + cursor.execute(f""" + SELECT data.day + ,data.sum_value + ,data.year + ,data.years + FROM (SELECT EXTRACT(DAY from day) as day + ,sum_value + ,EXTRACT(YEAR from day) as year + ,RANK () OVER (PARTITION BY EXTRACT(DAY from day) ORDER BY sum_value DESC) as rank + ,count(1) OVER (PARTITION BY EXTRACT(DAY from day)) as years + FROM daily_summary + WHERE station_id = %(station_id)s + AND EXTRACT(MONTH from day) = %(month)s + AND variable_id = %(variable_id)s + {filter_year_query}) data + WHERE data.rank = 1 + """, query_params_dict) + rows = cursor.fetchall() + + if len(rows) > 0: + df = pd.DataFrame(data=rows, columns=("day", "sum_value", "year", "years")) + for index, row in df.iterrows(): + data_dict[int(row['day'])] = {"agg_value": round(row['sum_value'], 2), "year": row['year'], + "years": row['years']} + + max_row = df.loc[df["sum_value"].idxmax()] + summary_dict = {"agg_value": round(max_row['sum_value'], 2), "year": max_row['year']} + + + elif aggregation_type == 'Maximum': + colspan = 4 + headers = ['Average', 'Extreme', 'Year', 'Years'] + columns = ['value', 'agg_value', 'year', 'years'] + + with connection.cursor() as cursor: + cursor.execute(f""" + SELECT data.day + ,summary.value + ,data.max_value + ,data.year + ,data.years + FROM (SELECT EXTRACT(DAY from day) as day + ,max_value + ,EXTRACT(YEAR from day) as year + ,RANK () OVER (PARTITION BY EXTRACT(DAY from day) ORDER BY max_value DESC) as rank + ,count(1) OVER (PARTITION BY EXTRACT(DAY from day)) as years + FROM daily_summary + WHERE station_id = %(station_id)s + AND EXTRACT(MONTH from day) = %(month)s + AND variable_id = %(variable_id)s + {filter_year_query}) data, + (SELECT EXTRACT(DAY from day) as day + ,avg(max_value) as value + FROM daily_summary + WHERE station_id = %(station_id)s + AND EXTRACT(MONTH from day) = %(month)s + AND variable_id = %(variable_id)s + {filter_year_query} + GROUP BY 1) summary + WHERE data.rank = 1 + AND summary.day = data.day + """, query_params_dict) + rows = cursor.fetchall() + + if len(rows) > 0: + df = pd.DataFrame(data=rows, columns=("day", "value", "max_value", "year", "years")) + for index, row in df.iterrows(): + data_dict[int(row['day'])] = {"value": round(row['value'], 2), + "agg_value": round(row['max_value'], 2), "year": row['year'], + "years": row['years']} + + max_row = df.loc[df["max_value"].idxmax()] + summary_dict = {"value": round(df["value"].mean(), 2), "agg_value": round(max_row['max_value'], 2), + "year": max_row['year']} + + elif aggregation_type == 'Minimum': + headers = ['Average', 'Extreme', 'Year', 'Years'] + columns = ['value', 'agg_value', 'year', 'years'] + colspan = 4 + with connection.cursor() as cursor: + cursor.execute(f""" + SELECT data.day + ,summary.value + ,data.min_value + ,data.year + ,data.years + FROM (SELECT EXTRACT(DAY from day) as day + ,min_value + ,EXTRACT(YEAR from day) as year + ,RANK () OVER (PARTITION BY EXTRACT(DAY from day) ORDER BY min_value ASC) as rank + ,count(1) OVER (PARTITION BY EXTRACT(DAY from day)) as years + FROM daily_summary + WHERE station_id = %(station_id)s + AND EXTRACT(MONTH from day) = %(month)s + AND variable_id = %(variable_id)s + {filter_year_query}) data, + (SELECT EXTRACT(DAY from day) as day + ,avg(min_value) as value + FROM daily_summary + WHERE station_id = %(station_id)s + AND EXTRACT(MONTH from day) = %(month)s + AND variable_id = %(variable_id)s + {filter_year_query} + GROUP BY 1) summary + WHERE data.rank = 1 + AND summary.day = data.day + """, query_params_dict) + rows = cursor.fetchall() + + if len(rows) > 0: + df = pd.DataFrame(data=rows, columns=("day", "value", "min_value", "year", "years")) + for index, row in df.iterrows(): + data_dict[int(row['day'])] = {"value": round(row['value'], 2), + "agg_value": round(row['min_value'], 2), "year": row['year'], + "years": row['years']} + + max_row = df.loc[df["min_value"].idxmin()] + summary_dict = {"value": round(df["value"].mean(), 2), "agg_value": round(max_row['min_value'], 2), + "year": max_row['year']} + + else: + with connection.cursor() as cursor: + cursor.execute(f""" + SELECT EXTRACT(DAY from day) + ,avg(avg_value) + FROM daily_summary data + WHERE station_id = %(station_id)s + AND EXTRACT(MONTH from day) = %(month)s + AND variable_id = %(variable_id)s + {filter_year_query} + GROUP BY 1 + """, query_params_dict) + rows = cursor.fetchall() + + if len(rows) > 0: + for row in rows: + day = int(row[0]) + value = round(row[1], 2) + data_dict[day] = {"value": value} + + df = pd.DataFrame(data=rows, columns=("day", "value")) + summary_dict = {"value": round(df["value"].mean(), 2)} + + variable_dict[variable_id] = { + 'metadata': { + "name": variable.symbol, + "id": variable.id, + "unit": variable.unit.name if variable.unit is not None else "", + "unit_symbol": variable.unit.symbol if variable.unit is not None else "", + "aggregation": aggregation_type, + "colspan": colspan, + "headers": headers, + "columns": columns + }, + 'data': data_dict, + 'summary': summary_dict, + } + + res['variables'] = variable_dict + station = Station.objects.get(pk=station_id) + res['station'] = { + "name": station.name, + "district": station.region, + "latitude": station.latitude, + "longitude": station.longitude, + "elevation": station.elevation, + "variables": variable_symbol_dict, + } + + res['params'] = { + "month": month, + "period": period, + } + + return JsonResponse(res, status=status.HTTP_200_OK) + + +class DataInventoryView(LoginRequiredMixin, TemplateView): + template_name = "wx/data_inventory.html" + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + + context['variable_list'] = Variable.objects.all() + + return self.render_to_response(context) + + +@api_view(['GET']) +def get_data_inventory(request): + start_year = request.GET.get('start_date', None) + end_year = request.GET.get('end_date', None) + is_automatic = request.GET.get('is_automatic', None) + + result = [] + + query = """ + SELECT EXTRACT('YEAR' from station_data.datetime) + ,station.id + ,station.name + ,station.code + ,station.is_automatic + ,station.begin_date + ,station.watershed + ,TRUNC(AVG(station_data.record_count_percentage)::numeric, 2) + FROM wx_stationdataminimuminterval AS station_data + JOIN wx_station AS station ON station.id = station_data.station_id + WHERE EXTRACT('YEAR' from station_data.datetime) >= %(start_year)s + AND EXTRACT('YEAR' from station_data.datetime) < %(end_year)s + AND station.is_automatic = %(is_automatic)s + GROUP BY 1, station.id + ORDER BY station.watershed, station.name + """ + + with connection.cursor() as cursor: + cursor.execute(query, {"start_year": start_year, "end_year": end_year, "is_automatic": is_automatic}) + rows = cursor.fetchall() + + for row in rows: + obj = { + 'year': row[0], + 'station': { + 'id': row[1], + 'name': row[2], + 'code': row[3], + 'is_automatic': row[4], + 'begin_date': row[5], + 'watershed': row[6], + }, + 'percentage': row[7], + } + result.append(obj) + + return Response(result, status=status.HTTP_200_OK) + + +@api_view(['GET']) +def get_data_inventory_by_station(request): + start_year = request.GET.get('start_date', None) + end_year = request.GET.get('end_date', None) + station_id: list = request.GET.get('station_id', None) + record_limit = request.GET.get('record_limit', None) + + if station_id is None or len(station_id) == 0: + return JsonResponse({"message": "\"station_id\" must not be null"}, status=status.HTTP_400_BAD_REQUEST) + + record_limit_lexical = "" + if record_limit is not None: + record_limit_lexical = f"LIMIT {record_limit}" + + result = [] + query = f""" + WITH variable AS ( + SELECT variable.id, variable.name + FROM wx_variable AS variable + JOIN wx_stationvariable AS station_variable ON station_variable.variable_id = variable.id + WHERE station_variable.station_id = %(station_id)s + ORDER BY variable.name + {record_limit_lexical} + ) + SELECT EXTRACT('YEAR' from station_data.datetime) + ,limited_variable.id + ,limited_variable.name + ,TRUNC(AVG(station_data.record_count_percentage)::numeric, 2) + FROM wx_stationdataminimuminterval AS station_data + JOIN variable AS limited_variable ON limited_variable.id = station_data.variable_id + JOIN wx_station station ON station_data.station_id = station.id + WHERE EXTRACT('YEAR' from station_data.datetime) >= %(start_year)s + AND EXTRACT('YEAR' from station_data.datetime) < %(end_year)s + AND station_data.station_id = %(station_id)s + GROUP BY 1, limited_variable.id, limited_variable.name + ORDER BY 1, limited_variable.name + """ + + with connection.cursor() as cursor: + cursor.execute(query, {"start_year": start_year, "end_year": end_year, "station_id": station_id}) + rows = cursor.fetchall() + + for row in rows: + obj = { + 'year': row[0], + 'variable': { + 'id': row[1], + 'name': row[2], + }, + 'percentage': row[3], + } + result.append(obj) + + return Response(result, status=status.HTTP_200_OK) + +@api_view(['GET']) +def get_station_variable_month_data_inventory(request): + year = request.GET.get('year', None) + station_id = request.GET.get('station_id', None) + + if station_id is None: + return JsonResponse({"message": "Invalid request. Station id must be provided"}, + status=status.HTTP_400_BAD_REQUEST) + + result = [] + query = """ + SELECT EXTRACT('MONTH' FROM station_data.datetime) AS month + ,variable.id + ,variable.name + ,measurementvariable.name + ,TRUNC(AVG(station_data.record_count_percentage)::numeric, 2) + FROM wx_stationdataminimuminterval AS station_data + JOIN wx_variable variable ON station_data.variable_id=variable.id + LEFT JOIN wx_measurementvariable measurementvariable ON measurementvariable.id = variable.measurement_variable_id + WHERE EXTRACT('YEAR' from station_data.datetime) = %(year)s + AND station_data.station_id = %(station_id)s + GROUP BY 1, variable.id, variable.name, measurementvariable.name + """ + + with connection.cursor() as cursor: + cursor.execute(query, {"year": year, "station_id": station_id}) + rows = cursor.fetchall() + + for row in rows: + obj = { + 'month': row[0], + 'variable': { + 'id': row[1], + 'name': row[2], + 'measurement_variable_name': row[3] if row[3] is None else row[3].lower().replace(' ', '-'), + }, + 'percentage': row[4], + } + result.append(obj) + + return Response(result, status=status.HTTP_200_OK) + + +@api_view(['GET']) +def get_station_variable_day_data_inventory(request): + year = request.GET.get('year', None) + month = request.GET.get('month', None) + station_id = request.GET.get('station_id', None) + variable_id = request.GET.get('variable_id', None) + + if station_id is None: + return JsonResponse({"message": "Invalid request. Station id must be provided"}, + status=status.HTTP_400_BAD_REQUEST) + + query = """ + WITH data AS ( + SELECT EXTRACT('DAY' FROM station_data.datetime) AS day + ,EXTRACT('DOW' FROM station_data.datetime) AS dow + ,TRUNC(station_data.record_count_percentage::numeric, 2) as percentage + ,station_data.record_count + ,station_data.ideal_record_count + ,(select COUNT(1) from raw_data rd where rd.station_id = station_data.station_id and rd.variable_id = station_data.variable_id and rd.datetime between station_data.datetime and station_data.datetime + '1 DAY'::interval and coalesce(rd.manual_flag, rd.quality_flag) in (1, 4)) qc_passed_amount + ,(select COUNT(1) from raw_data rd where rd.station_id = station_data.station_id and rd.variable_id = station_data.variable_id and rd.datetime between station_data.datetime and station_data.datetime + '1 DAY'::interval) qc_amount + FROM wx_stationdataminimuminterval AS station_data + WHERE EXTRACT('YEAR' from station_data.datetime) = %(year)s + AND EXTRACT('MONTH' from station_data.datetime) = %(month)s + AND station_data.station_id = %(station_id)s + AND station_data.variable_id = %(variable_id)s + ORDER BY station_data.datetime) + SELECT available_days.custom_day + ,data.dow + ,COALESCE(data.percentage, 0) AS percentage + ,COALESCE(data.record_count, 0) AS record_count + ,COALESCE(data.ideal_record_count, 0) AS ideal_record_count + ,case when data.qc_amount = 0 then 0 + else TRUNC((data.qc_passed_amount / data.qc_amount::numeric) * 100, 2) end as qc_passed_percentage + FROM (SELECT custom_day FROM unnest( ARRAY[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31] ) AS custom_day) AS available_days + LEFT JOIN data ON data.day = available_days.custom_day; + + """ + + days = [] + day_with_data = None + with connection.cursor() as cursor: + cursor.execute(query, {"year": year, "station_id": station_id, "month": month, "variable_id": variable_id}) + rows = cursor.fetchall() + + for row in rows: + obj = { + 'day': row[0], + 'dow': row[1], + 'percentage': row[2], + 'record_count': row[3], + 'ideal_record_count': row[4], + 'qc_passed_percentage': row[5], + } + + if row[1] is not None and day_with_data is None: + day_with_data = obj + days.append(obj) + + if day_with_data is None: + return JsonResponse({"message": "No data found"}, + status=status.HTTP_404_NOT_FOUND) + + for day in days: + current_day = day.get('day', None) + current_dow = day.get('dow', None) + if current_dow is None: + day_with_data_day = day_with_data.get('day', None) + day_with_data_dow = day_with_data.get('dow', None) + day_difference = current_day - day_with_data_day + day["dow"] = (day_difference + day_with_data_dow) % 7 + + return Response(days, status=status.HTTP_200_OK) + + + +@api_view(['POST']) +def delete_pgia_hourly_capture_row(request): + request_date = request.data['date'] + hour = request.data['hour'] + station_id = request.data['station_id'] + variable_id_list = request.data['variable_ids'] + + try: + request_date = datetime.datetime.strptime(request_date, '%Y-%m-%d') + except ValueError: + return JsonResponse({"message": "Invalid date format. The expected date format is 'YYYY-MM-DD'"}, + status=status.HTTP_400_BAD_REQUEST) + + if station_id is None: + return JsonResponse({"message": "Invalid request. Station id must be provided"}, + status=status.HTTP_400_BAD_REQUEST) + + if hour is None: + return JsonResponse({"message": "Invalid request. Hour must be provided"}, status=status.HTTP_400_BAD_REQUEST) + + if variable_id_list is None: + return JsonResponse({"message": "Invalid request. Variable ids must be provided"}, + status=status.HTTP_400_BAD_REQUEST) + + variable_id_list = tuple(variable_id_list) + station = Station.objects.get(id=station_id) + datetime_offset = pytz.FixedOffset(station.utc_offset_minutes) + current_datetime = datetime_offset.localize(request_date.replace(hour=hour)) + + result = [] + delete_query = """ + DELETE FROM raw_data + WHERE station_id = %(station_id)s + AND variable_id IN %(variable_id_list)s + AND datetime = %(current_datetime)s + """ + + get_last_updated_datetime_query = """ + SELECT max(last_data_datetime) + FROM wx_stationvariable + WHERE station_id = %(station_id)s + AND variable_id IN %(variable_id_list)s + ORDER BY 1 DESC + """ + + update_last_updated_datetime_query = """ + WITH rd as ( + SELECT station_id + ,variable_id + ,measured + ,code + ,datetime + ,RANK() OVER (PARTITION BY station_id, variable_id ORDER BY datetime DESC) datetime_rank + FROM raw_data + WHERE station_id = %(station_id)s + AND variable_id IN %(variable_id_list)s) + UPDATE wx_stationvariable sv + SET last_data_datetime = rd.datetime + ,last_data_value = rd.measured + ,last_data_code = rd.code + FROM rd + WHERE sv.station_id = rd.station_id + AND sv.variable_id = rd.variable_id + AND rd.datetime_rank = 1 + """ + + create_daily_summary_task_query = """ + INSERT INTO wx_dailysummarytask (station_id, date, created_at, updated_at) + VALUES (%(station_id)s, %(current_datetime)s, now(), now()) + ON CONFLICT DO NOTHING + """ + + create_hourly_summary_task_query = """ + INSERT INTO wx_hourlysummarytask (station_id, datetime, created_at, updated_at) + VALUES (%(station_id)s, %(current_datetime)s, now(), now()) + ON CONFLICT DO NOTHING + """ + + with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + cursor.execute(delete_query, {"station_id": station_id, "variable_id_list": variable_id_list, + "current_datetime": current_datetime}) + + cursor.execute(create_daily_summary_task_query, + {"station_id": station_id, "current_datetime": request_date}) + cursor.execute(create_hourly_summary_task_query, + {"station_id": station_id, "current_datetime": current_datetime}) + + cursor.execute(get_last_updated_datetime_query, + {"station_id": station_id, "variable_id_list": variable_id_list}) + + last_data_datetime_row = cursor.fetchone() + if last_data_datetime_row is not None: + last_data_datetime = last_data_datetime_row[0] + + if last_data_datetime == current_datetime: + cursor.execute(update_last_updated_datetime_query, + {"station_id": station_id, "variable_id_list": variable_id_list}) + conn.commit() + + return Response(result, status=status.HTTP_200_OK) \ No newline at end of file From 7d9e2e13c32dd6c04f2d4cf4367f4dad8bfb5fb0 Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Tue, 11 Jul 2023 07:54:08 -0300 Subject: [PATCH 09/21] Fix compile error --- api/wx/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/wx/views.py b/api/wx/views.py index b9d8a7c..2fdf510 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -4640,7 +4640,7 @@ def get_station_equipment_types(station): is_full, msg = is_equipment_full(station, equipment_type) dictionary = {'key':equipment_type.id, 'id':equipment_type.id, - 'name': equipment_type.name + 'name': equipment_type.name, 'is_full': is_full} equipment_type_list.append(equipment_type) From 235d71c968185415c7b0ee7a79aa613a5b32ac3c Mon Sep 17 00:00:00 2001 From: "Abner F. B. Costa" <43327209+AbnerBissolli@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:45:44 -0300 Subject: [PATCH 10/21] Equipment history (#119) * Building Bug Fix * Added equipment history --- api/Dockerfile | 3 +- api/requirements.txt | 1 + api/tempestas_api/settings.py | 2 + api/wx/admin.py | 11 ++-- api/wx/migrations/0026_auto_20230711_1034.py | 55 ++++++++++++++++++++ api/wx/models.py | 14 +++-- 6 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 api/wx/migrations/0026_auto_20230711_1034.py diff --git a/api/Dockerfile b/api/Dockerfile index 93e90ff..234b3c3 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -5,7 +5,8 @@ ENV PYTHONUNBUFFERED 1 ENV DJANGO_SETTINGS_MODULE tempestas_api.settings RUN apt-get update --fix-missing -RUN apt-get install -y g++ libgdal-dev libpq-dev libgeos-dev libproj-dev openjdk-17-jre vim wait-for-it +RUN apt-get install -y g++ libgdal-dev libpq-dev libgeos-dev libproj-dev vim wait-for-it +RUN apt-get install -y openjdk-17-jre RUN apt-get install -y postgresql-client RUN adduser --disabled-password --gecos '' surface && mkdir /surface && chown -R surface /surface diff --git a/api/requirements.txt b/api/requirements.txt index d83634b..229b184 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -31,3 +31,4 @@ requests==2.26.0 gunicorn==20.1.0 croniter==1.3.5 django-ckeditor==6.5.1 +django-simple-history==3.3.0 \ No newline at end of file diff --git a/api/tempestas_api/settings.py b/api/tempestas_api/settings.py index 3b9c19d..bdef4e4 100644 --- a/api/tempestas_api/settings.py +++ b/api/tempestas_api/settings.py @@ -48,6 +48,7 @@ 'rest_framework.authtoken', 'colorfield', 'import_export', + 'simple_history', ] CKEDITOR_CONFIGS = { @@ -74,6 +75,7 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'simple_history.middleware.HistoryRequestMiddleware', ] ROOT_URLCONF = 'tempestas_api.urls' diff --git a/api/wx/admin.py b/api/wx/admin.py index b461560..7af3752 100644 --- a/api/wx/admin.py +++ b/api/wx/admin.py @@ -341,16 +341,19 @@ class TechnicianAdmin(admin.ModelAdmin): class ManufacturerAdmin(admin.ModelAdmin): list_display = ("name",) -@admin.register(models.EquipmentType) -class EquipmentTypeAdmin(admin.ModelAdmin): - list_display = ("name",) @admin.register(models.FundingSource) class FundingSourceAdmin(admin.ModelAdmin): list_display = ("name",) +from simple_history.admin import SimpleHistoryAdmin + +@admin.register(models.EquipmentType) +class EquipmentTypeAdmin(admin.ModelAdmin): + list_display = ("name",) + @admin.register(models.Equipment) -class EquipmentAdmin(admin.ModelAdmin): +class EquipmentAdmin(SimpleHistoryAdmin): list_display = ("equipment_type", "manufacturer", "model", "serial_number", "acquisition_date", "first_deploy_date", "last_calibration_date") readonly_fields=('location',) diff --git a/api/wx/migrations/0026_auto_20230711_1034.py b/api/wx/migrations/0026_auto_20230711_1034.py new file mode 100644 index 0000000..db2c00a --- /dev/null +++ b/api/wx/migrations/0026_auto_20230711_1034.py @@ -0,0 +1,55 @@ +# Generated by Django 3.2.13 on 2023-07-11 10:34 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('wx', '0025_auto_20230621_1744'), + ] + + operations = [ + migrations.RenameField( + model_name='equipment', + old_name='condition', + new_name='classification', + ), + migrations.CreateModel( + name='HistoricalEquipment', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('created_at', models.DateTimeField(blank=True, editable=False)), + ('updated_at', models.DateTimeField(blank=True, editable=False)), + ('model', models.CharField(max_length=64)), + ('serial_number', models.CharField(max_length=64)), + ('acquisition_date', models.DateField()), + ('first_deploy_date', models.DateField(blank=True, null=True)), + ('last_deploy_date', models.DateField(blank=True, null=True)), + ('last_calibration_date', models.DateField(blank=True, null=True)), + ('next_calibration_date', models.DateField(blank=True, null=True)), + ('decommission_date', models.DateField(blank=True, null=True)), + ('classification', models.CharField(choices=[('F', 'Fully Functional'), ('P', 'Partially Functional'), ('N', 'Not Functional')], max_length=1, null=True)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('equipment_type', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.equipmenttype')), + ('funding_source', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.fundingsource')), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.station')), + ('manufacturer', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.manufacturer')), + ], + options={ + 'verbose_name': 'historical equipment', + 'verbose_name_plural': 'historical equipment', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/api/wx/models.py b/api/wx/models.py index 2c0406c..d5dfecf 100644 --- a/api/wx/models.py +++ b/api/wx/models.py @@ -1400,8 +1400,10 @@ class FundingSource(BaseModel): def __str__(self): return self.name +from simple_history.models import HistoricalRecords + class Equipment(BaseModel): - class EquipmentCondition(models.TextChoices): + class EquipmentClassification(models.TextChoices): FULLY_FUNCTIONAL = 'F', gettext_lazy('Fully Functional') PARTIALLY_FUNCTIONAL = 'P', gettext_lazy('Partially Functional') NOT_FUNCTIONAL = 'N', gettext_lazy('Not Functional') @@ -1418,9 +1420,13 @@ class EquipmentCondition(models.TextChoices): next_calibration_date = models.DateField(blank=True, null=True) decommission_date = models.DateField(blank=True, null=True) location = models.ForeignKey(Station, on_delete=models.DO_NOTHING, null=True) - condition = models.CharField(max_length=1, choices=EquipmentCondition.choices, null=True) + classification = models.CharField(max_length=1, choices=EquipmentClassification.choices, null=True) + history = HistoricalRecords() class Meta: - unique_together = ("equipment_type", "serial_number") + unique_together = ("equipment_type", "serial_number") verbose_name = "equipment" - verbose_name_plural = "equipment" \ No newline at end of file + verbose_name_plural = "equipment" + + def __str__(self): + return ' '.join((self.equipment_type.name, self.model, self.serial_number)) \ No newline at end of file From 80d8667692771e6278b227464426b2b73ef65dbf Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Tue, 11 Jul 2023 14:54:42 -0300 Subject: [PATCH 11/21] Equipment inventory bug fixes from last update --- .../equipment_inventory.html | 48 +++++++++---------- api/wx/views.py | 24 +++++----- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html index c82da91..aa6345f 100644 --- a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html +++ b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html @@ -246,11 +246,11 @@
    @@ -258,7 +258,7 @@ - + @@ -422,7 +422,7 @@ manufacturers: [], funding_sources: [], stations: [], - equipment_conditions: [], + equipment_classifications: [], is_new_equipment: true, equipment: { equipment_id: null, @@ -436,19 +436,19 @@ serial_number: null, acquisition_date: moment().format('YYYY-MM-DD'), acquisition_date_menu: false, - first_deploy_date: moment().format('YYYY-MM-DD'), + first_deploy_date: null, first_deploy_date_menu: false, - last_calibration_date: moment().format('YYYY-MM-DD'), + last_calibration_date: null, last_calibration_date_menu: false, - next_calibration_date: moment().format('YYYY-MM-DD'), + next_calibration_date: null, next_calibration_date_menu: false, - decommission_date: moment().format('YYYY-MM-DD'), + decommission_date: null, decommission_date_menu: false, location: null, location_id: null, - condition: null, - condition_id: null, - last_deploy_date: moment().format('YYYY-MM-DD'), + classification: null, + classification_id: null, + last_deploy_date: null, last_deploy_date_menu: false }, headers: [ @@ -460,7 +460,7 @@ { text: 'First Deployed', align: 'center', value: 'first_deploy_date' }, { text: 'Last Deployed', align: 'center', value: 'last_deploy_date' }, { text: 'Location', align: 'center', value: 'location' }, - { text: 'Condition', align: 'center', value: 'condition' }, + { text: 'Classification', align: 'center', value: 'classification' }, { text: 'Actions', align: 'center', value: 'actions', sortable: false }, ], equipments: [], @@ -485,19 +485,19 @@ serial_number: null, acquisition_date: moment().format('YYYY-MM-DD'), acquisition_date_menu: false, - first_deploy_date: moment().format('YYYY-MM-DD'), + first_deploy_date: null, first_deploy_date_menu: false, - last_calibration_date: moment().format('YYYY-MM-DD'), + last_calibration_date: null, last_calibration_date_menu: false, - next_calibration_date: moment().format('YYYY-MM-DD'), + next_calibration_date: null, next_calibration_date_menu: false, - decommission_date: moment().format('YYYY-MM-DD'), + decommission_date: null, decommission_date_menu: false, location: null, location_id: null, - condition: null, - condition_id: null, - last_deploy_date: moment().format('YYYY-MM-DD'), + classification: null, + classification_id: null, + last_deploy_date: null, last_deploy_date_menu: false }; } @@ -516,7 +516,7 @@ this.manufacturers = res.data['manufacturers']; this.funding_sources = res.data['funding_sources']; this.stations = res.data['stations']; - this.equipment_conditions = res.data['equipment_conditions']; + this.equipment_classifications = res.data['equipment_classifications']; }); }, createEquipment(){ @@ -536,7 +536,7 @@ 'next_calibration_date': this.equipment.next_calibration_date, 'decommission_date': this.equipment.decommission_date, 'location': this.equipment.location_id, - 'condition': this.equipment.condition_id, + 'classification': this.equipment.classification_id, 'last_deploy_date': this.equipment.last_deploy_date, }, }) @@ -571,7 +571,7 @@ 'next_calibration_date': this.equipment.next_calibration_date, 'decommission_date': this.equipment.decommission_date, 'location': this.equipment.location_id, - 'condition': this.equipment.condition_id, + 'classification': this.equipment.classification_id, 'last_deploy_date': this.equipment.last_deploy_date, }, }) diff --git a/api/wx/views.py b/api/wx/views.py index cd5cff5..8bcbf8e 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -4002,12 +4002,12 @@ def get_value(variable): return '---' return variable -def equipment_condition(condition): - if condition == 'F': +def equipment_classification(classification): + if classification == 'F': return 'Fully Functional' - elif condition == 'P': + elif classification == 'P': return 'Partially Functional' - elif condition == 'N': + elif classification == 'N': return 'Not Functional' return None @@ -4048,14 +4048,14 @@ def get_equipment_inventory_data(request): 'last_deploy_date': equipment.last_deploy_date, 'location': f"{station.name} - {station.code}" if station else 'Office', 'location_id': station.id if station else None, - 'condition': equipment_condition(equipment.condition), - 'condition_id': equipment.condition, + 'classification': equipment_classification(equipment.classification), + 'classification_id': equipment.classification, } equipment_list.append(equipment_dict) except ObjectDoesNotExist: pass - equipment_conditions = [ + equipment_classifications = [ {'name': 'Fully Functional', 'id': 'F'}, {'name': 'Partially Functional', 'id': 'P'}, {'name': 'Not Functional', 'id': 'N'}, @@ -4070,7 +4070,7 @@ def get_equipment_inventory_data(request): 'manufacturers': list(manufacturers.values()), 'funding_sources': list(funding_sources.values()), 'stations': station_list, - 'equipment_conditions': equipment_conditions, + 'equipment_classifications': equipment_classifications, } return JsonResponse(response, status=status.HTTP_200_OK) @@ -4088,7 +4088,7 @@ def create_equipment(request): next_calibration_date = request.GET.get('next_calibration_date', None) decommission_date = request.GET.get('decommission_date', None) location_id = request.GET.get('location', None) - condition_id = request.GET.get('condition', None) + classification = request.GET.get('classification', None) last_deploy_date = request.GET.get('last_deploy_date', None) equipment_type = EquipmentType.objects.get(id=equipment_type_id) @@ -4131,7 +4131,7 @@ def create_equipment(request): next_calibration_date = next_calibration_date, decommission_date = decommission_date, location = location, - condition = condition, + classification = classification, last_deploy_date = last_deploy_date, ) @@ -4153,7 +4153,7 @@ def update_equipment(request): next_calibration_date = request.GET.get('next_calibration_date', None) decommission_date = request.GET.get('decommission_date', None) location_id = request.GET.get('location', None) - condition = request.GET.get('condition', None) + classification = request.GET.get('classification', None) last_deploy_date = request.GET.get('last_deploy_date', None) equipment_type = EquipmentType.objects.get(id=equipment_type_id) @@ -4195,7 +4195,7 @@ def update_equipment(request): equipment.next_calibration_date = next_calibration_date equipment.decommission_date = decommission_date equipment.location = location - equipment.condition = condition + equipment.classification = classification equipment.last_deploy_date = last_deploy_date equipment.save() From 1cdaf999543f3a220218552ec70ac72283b72040 Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Wed, 12 Jul 2023 09:18:36 -0300 Subject: [PATCH 12/21] Revert "Merge branch 'maintenance-reports-refactor' into dev" This reverts commit 6a3dd411cffd7e303ac2e15121b9d53ba18327a1, reversing changes made to 80d8667692771e6278b227464426b2b73ef65dbf. Changes to be committed: modified: api/wx/admin.py deleted: api/wx/migrations/0026_auto_20230705_1900.py deleted: api/wx/migrations/0027_auto_20230707_2048.py modified: api/wx/models.py modified: api/wx/templates/wx/maintenance_reports/maintenance_reports.html modified: api/wx/templates/wx/maintenance_reports/new_report.html deleted: api/wx/templates/wx/maintenance_reports_old/equipment_inventory.html deleted: api/wx/templates/wx/maintenance_reports_old/maintenance_reports.html deleted: api/wx/templates/wx/maintenance_reports_old/new_report.html deleted: api/wx/templates/wx/maintenance_reports_old/view_report.html modified: api/wx/urls.py modified: api/wx/views.py deleted: api/wx/views_old.py --- api/wx/admin.py | 9 +- api/wx/migrations/0026_auto_20230705_1900.py | 88 - api/wx/migrations/0027_auto_20230707_2048.py | 24 - api/wx/models.py | 59 +- .../maintenance_reports.html | 4 +- .../wx/maintenance_reports/new_report.html | 575 +- .../equipment_inventory.html | 622 -- .../maintenance_reports.html | 412 -- .../maintenance_reports_old/new_report.html | 778 -- .../maintenance_reports_old/view_report.html | 275 - api/wx/urls.py | 23 +- api/wx/views.py | 235 +- api/wx/views_old.py | 6291 ----------------- 13 files changed, 388 insertions(+), 9007 deletions(-) delete mode 100644 api/wx/migrations/0026_auto_20230705_1900.py delete mode 100644 api/wx/migrations/0027_auto_20230707_2048.py delete mode 100644 api/wx/templates/wx/maintenance_reports_old/equipment_inventory.html delete mode 100644 api/wx/templates/wx/maintenance_reports_old/maintenance_reports.html delete mode 100644 api/wx/templates/wx/maintenance_reports_old/new_report.html delete mode 100644 api/wx/templates/wx/maintenance_reports_old/view_report.html delete mode 100644 api/wx/views_old.py diff --git a/api/wx/admin.py b/api/wx/admin.py index 339c5b7..7af3752 100644 --- a/api/wx/admin.py +++ b/api/wx/admin.py @@ -341,6 +341,7 @@ class TechnicianAdmin(admin.ModelAdmin): class ManufacturerAdmin(admin.ModelAdmin): list_display = ("name",) + @admin.register(models.FundingSource) class FundingSourceAdmin(admin.ModelAdmin): list_display = ("name",) @@ -354,10 +355,6 @@ class EquipmentTypeAdmin(admin.ModelAdmin): @admin.register(models.Equipment) class EquipmentAdmin(SimpleHistoryAdmin): list_display = ("equipment_type", "manufacturer", "model", "serial_number", "acquisition_date", "first_deploy_date", "last_calibration_date") - readonly_fields=('location',) - -@admin.register(models.StationProfileEquipmentType) -class StationProfileEquipmentTypeAdmin(admin.ModelAdmin): - list_display = ('profile', 'equipment_type', 'equipment_type_order', 'equipment_order') - + readonly_fields=('location',) + diff --git a/api/wx/migrations/0026_auto_20230705_1900.py b/api/wx/migrations/0026_auto_20230705_1900.py deleted file mode 100644 index 6428703..0000000 --- a/api/wx/migrations/0026_auto_20230705_1900.py +++ /dev/null @@ -1,88 +0,0 @@ -# Generated by Django 3.2.13 on 2023-07-05 19:00 - -import ckeditor.fields -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import simple_history.models - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('wx', '0025_auto_20230621_1744'), - ] - - operations = [ - migrations.RenameField( - model_name='equipment', - old_name='condition', - new_name='classification', - ), - migrations.CreateModel( - name='StationProfileEquipmentType', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('equipment_type_order', models.IntegerField()), - ('equipment_order', models.IntegerField()), - ('equipment_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.equipmenttype')), - ('profile', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.stationprofile')), - ], - options={ - 'unique_together': {('profile', 'equipment_type', 'equipment_type_order', 'equipment_order')}, - }, - ), - migrations.CreateModel( - name='HistoricalEquipment', - fields=[ - ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), - ('created_at', models.DateTimeField(blank=True, editable=False)), - ('updated_at', models.DateTimeField(blank=True, editable=False)), - ('model', models.CharField(max_length=64)), - ('serial_number', models.CharField(max_length=64)), - ('acquisition_date', models.DateField()), - ('first_deploy_date', models.DateField(blank=True, null=True)), - ('last_deploy_date', models.DateField(blank=True, null=True)), - ('last_calibration_date', models.DateField(blank=True, null=True)), - ('next_calibration_date', models.DateField(blank=True, null=True)), - ('decommission_date', models.DateField(blank=True, null=True)), - ('classification', models.CharField(choices=[('F', 'Fully Functional'), ('P', 'Partially Functional'), ('N', 'Not Functional')], max_length=1, null=True)), - ('history_id', models.AutoField(primary_key=True, serialize=False)), - ('history_date', models.DateTimeField(db_index=True)), - ('history_change_reason', models.CharField(max_length=100, null=True)), - ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), - ('equipment_type', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.equipmenttype')), - ('funding_source', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.fundingsource')), - ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), - ('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.station')), - ('manufacturer', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='wx.manufacturer')), - ], - options={ - 'verbose_name': 'historical equipment', - 'verbose_name_plural': 'historical equipment', - 'ordering': ('-history_date', '-history_id'), - 'get_latest_by': ('history_date', 'history_id'), - }, - bases=(simple_history.models.HistoricalChanges, models.Model), - ), - migrations.CreateModel( - name='MaintenanceReportEquipment', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('condition', ckeditor.fields.RichTextField()), - ('classification', models.CharField(choices=[('F', 'Fully Functional'), ('P', 'Partially Functional'), ('N', 'Not Functional')], default='F', max_length=1)), - ('current_equipment', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='current_equipment', to='wx.equipment')), - ('maintenance_report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wx.maintenancereport')), - ('old_equipment', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='old_equipment', to='wx.equipment')), - ('profile_equipment', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.stationprofileequipmenttype')), - ], - options={ - 'unique_together': {('maintenance_report', 'profile_equipment')}, - }, - ), - ] diff --git a/api/wx/migrations/0027_auto_20230707_2048.py b/api/wx/migrations/0027_auto_20230707_2048.py deleted file mode 100644 index 94d4315..0000000 --- a/api/wx/migrations/0027_auto_20230707_2048.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.13 on 2023-07-07 20:48 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('wx', '0026_auto_20230705_1900'), - ] - - operations = [ - migrations.AlterField( - model_name='maintenancereportequipment', - name='classification', - field=models.CharField(choices=[('F', 'Fully Functional'), ('P', 'Partially Functional'), ('N', 'Not Functional')], default='F', max_length=1, null=True), - ), - migrations.AlterField( - model_name='maintenancereportequipment', - name='old_equipment', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='old_equipment', to='wx.equipment'), - ), - ] diff --git a/api/wx/models.py b/api/wx/models.py index 1599724..d5dfecf 100644 --- a/api/wx/models.py +++ b/api/wx/models.py @@ -18,8 +18,6 @@ from timescale.db.models.fields import TimescaleDateTimeField from timescale.db.models.managers import TimescaleManager -from django.db.models import Q - class BaseModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) @@ -1300,9 +1298,6 @@ def no_future(value): if value > today: raise ValidationError('Visit date cannot be in the future.') -################################################################ -################################################################ - class StationComponent(BaseModel): name = models.CharField(max_length=64) description = models.CharField(max_length=256) @@ -1325,10 +1320,6 @@ class Meta: unique_together = ('profile', 'station_component') unique_together = ('profile', 'presentation_order') -################################################################ -################################################################ - - class MaintenanceReport(BaseModel): class Status(models.TextChoices): APPROVED = 'A', gettext_lazy('Approved') @@ -1384,9 +1375,6 @@ class ComponentClassification(models.TextChoices): class Meta: unique_together = ('maintenance_report', 'station_component') -################################################################ -################################################################ - class Manufacturer(BaseModel): name = models.CharField(max_length=64) @@ -1396,6 +1384,7 @@ def __str__(self): class EquipmentType(BaseModel): name = models.CharField(max_length=64) description = models.CharField(max_length=256) + # https://stackoverflow.com/questions/7946861/how-can-i-add-a-wsywyg-editor-to-django-admin report_template = RichTextField(blank=True, null=True) class Meta: @@ -1438,48 +1427,6 @@ class Meta: unique_together = ("equipment_type", "serial_number") verbose_name = "equipment" verbose_name_plural = "equipment" - - def __str__(self): - return ' '.join((self.equipment_type.name, self.model, self.serial_number)) - -class StationProfileEquipmentType(BaseModel): - profile = models.ForeignKey(StationProfile, on_delete=models.DO_NOTHING) - equipment_type = models.ForeignKey(EquipmentType, on_delete=models.DO_NOTHING) - equipment_type_order = models.IntegerField() #Main - equipment_order = models.IntegerField() #Sub - - - def save(self, *args, **kwargs): - # For each profile, equiptment_type is one to one with equiptment_type_order: - if StationProfileEquipmentType.objects.filter( - profile=self.profile, - equipment_type=self.equipment_type, - ).exclude(equipment_type_order=self.equipment_type_order).exists(): - raise ValidationError("A StationProfileEquipmentType with the same profile and equipment_type, already exists with a different equipment_type_order.") - if StationProfileEquipmentType.objects.filter( - profile=self.profile, - equipment_type_order=self.equipment_type_order - ).exclude(equipment_type=self.equipment_type).exists(): - raise ValidationError("A StationProfileEquipmentType with the same profile and equipment_type_order, already exists with a different equipment_type.") - - super().save(*args, **kwargs) - - class Meta: - unique_together = ('profile', 'equipment_type', 'equipment_type_order', 'equipment_order') -class MaintenanceReportEquipment(BaseModel): - class EquipmentClassification(models.TextChoices): - FULLY_FUNCTIONAL = 'F', gettext_lazy('Fully Functional') - PARTIALLY_FUNCTIONAL = 'P', gettext_lazy('Partially Functional') - NOT_FUNCTIONAL = 'N', gettext_lazy('Not Functional') - - profile_equipment = models.ForeignKey(StationProfileEquipmentType, on_delete=models.DO_NOTHING) - maintenance_report = models.ForeignKey(MaintenanceReport, on_delete=models.CASCADE) - old_equipment = models.ForeignKey(Equipment, on_delete=models.DO_NOTHING, related_name='old_equipment', null=True) - current_equipment = models.ForeignKey(Equipment, on_delete=models.DO_NOTHING, related_name='current_equipment') - condition = RichTextField() - classification = models.CharField(max_length=1, choices=EquipmentClassification.choices, default=EquipmentClassification.FULLY_FUNCTIONAL, null=True) - - class Meta: - unique_together = ('maintenance_report', 'current_equipment') - unique_together = ('maintenance_report', 'profile_equipment') + def __str__(self): + return ' '.join((self.equipment_type.name, self.model, self.serial_number)) \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports/maintenance_reports.html b/api/wx/templates/wx/maintenance_reports/maintenance_reports.html index 0507e9d..ee858ae 100644 --- a/api/wx/templates/wx/maintenance_reports/maintenance_reports.html +++ b/api/wx/templates/wx/maintenance_reports/maintenance_reports.html @@ -172,7 +172,7 @@
    Search Form
    - Do you want to delete this report? + Are you sure that you really want to delete this report? @@ -187,7 +187,7 @@
    Search Form
    - Do you want to approve this report? + Are you sure that you really want to approve this report? diff --git a/api/wx/templates/wx/maintenance_reports/new_report.html b/api/wx/templates/wx/maintenance_reports/new_report.html index 56fce25..ab2f6dc 100644 --- a/api/wx/templates/wx/maintenance_reports/new_report.html +++ b/api/wx/templates/wx/maintenance_reports/new_report.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} {% block content %} +{% extends "base.html" %} {% block content %}
    @@ -6,22 +6,6 @@ - - - - Add New Equipment - - - - Create Equipment - - - Cancel - - - - -
    Maintenance Reports - New Report

    @@ -34,7 +18,7 @@
    Select the station and add initial data to create a report
    v-model="filter.isAutomatic" @change="invertValue(filter.isAutomatic)" label="Automatic" - :disabled="disableForm" + :disabled="maintenance_report.id" > @@ -43,7 +27,7 @@
    Select the station and add initial data to create a report
    item-text="label" item-value="id" v-model="maintenance_report.station_id" - :disabled="disableForm" + :disabled="maintenance_report.id" label="Station" required hint="*Required" @@ -65,7 +49,7 @@
    Select the station and add initial data to create a report
    v-model="maintenance_report.visit_date" label="Date of visit" v-on="on" - :disabled="disableForm" + :disabled="maintenance_report.id" required persistent-hint prepend-icon="event" @@ -80,13 +64,13 @@
    Select the station and add initial data to create a report
    - + Select the station and add initial data to create a report item-text="name" item-value="id" v-model="maintenance_report.responsible_technician_id" - :disabled="disableForm" + :disabled="maintenance_report.id" label="Responsible technician" required hint="*Required" @@ -123,7 +107,7 @@
    Select the station and add initial data to create a report
    v-model="maintenance_report.initial_time" label="Initial time" v-on="on" - :disabled="disableForm" + :disabled="maintenance_report.id" required persistent-hint prepend-icon="access_time" @@ -133,7 +117,7 @@
    Select the station and add initial data to create a report
    v-model="maintenance_report.initial_time" > -
    +
    @@ -152,6 +136,7 @@
    Select the station and add initial data to create a report
    >CREATE REPORT
    + @@ -164,219 +149,276 @@
    Select the station and add initial data to create a report
    > - + + + + + Reported Created by [[maintenance_report.responsible_technician.name]] at [[maintenance_report.visit_date]] [[maintenance_report.initial_time]] + + + + Station information + +
    + + Station Name: [[maintenance_report.station_information.station_name]] + Station Host Name: [[maintenance_report.station_information.station_host_name]] + Station ID: [[maintenance_report.station_information.station_ID]] + WIGOS ID: [[maintenance_report.station_information.wigos_ID]] + - - - - + Save Draft + Publish + + + +
    @@ -397,7 +439,7 @@
    Select the station and add initial data to create a report
    visit_type_id: { required }, responsible_technician_id: { required }, }, - }, + }, data (){ let ckeditor_toolbar = [ ['Bold', 'Italic', 'Font'], @@ -405,7 +447,7 @@
    Select the station and add initial data to create a report
    ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock', 'Indent', 'Outdent'], ['HorizontalRule', 'BulletedList'], ['Blockquote', 'Source', 'Link', 'Unlink', 'Image', 'Table', 'Print'] - ]; + ]; return { ckeditor_configs: { station_on_arrival_conditions: { @@ -443,11 +485,10 @@
    Select the station and add initial data to create a report
    extraAllowedContent : 'img(*){*}[*]', language: 'en', }, - }, - dialog_new: false, + }, e1: 1, steps: 1, - radioGroup: null, + radioGroup: null, request_error: false, request_error_message: "", loading: false, @@ -468,7 +509,7 @@
    Select the station and add initial data to create a report
    visit_date_menu: false, initial_time: moment().format('HH:mm'), initial_time_menu: false, - equipment_types: null, + component_list: null, contacts: null, other_technician_1: null, other_technician_2: null, @@ -503,16 +544,10 @@
    Select the station and add initial data to create a report
    { id: {{ technician.id }}, name: "{{ technician.name }}"}, {% endfor %} - ], + ], } }, computed: { - disableForm () { - if(this.maintenance_report.id){ - return true - } - return false - }, stationList () { let filteredStations = this.stations; @@ -521,7 +556,7 @@
    Select the station and add initial data to create a report
    this.maintenance_report.station_id = null; return filteredStations; - } + } }, watch: { steps (val) { @@ -531,9 +566,6 @@
    Select the station and add initial data to create a report
    }, }, methods: { - viewMaintenanceReport(id){ - return `/wx/maintenance_report/${id}/view/${0}/` - }, notFutureDate(){ var today = moment().format('YYYY-MM-DD'); var now = moment().format('HH:mm'); @@ -562,6 +594,9 @@
    Select the station and add initial data to create a report
    return this.technicianList.filter((item) => !arr.includes(item.id)) + }, + viewMaintenanceReport(id){ + return `/wx/maintenance_report/${id}/view/${0}/` }, nextStep(n) { if (n === this.steps+3) { @@ -569,7 +604,7 @@
    Select the station and add initial data to create a report
    } else { this.e1 = n + 1 } - }, + }, getInitialData(id) { this.loading = true; axios.get(`/wx/maintenance_report/${id}/get/`).then( @@ -590,16 +625,13 @@
    Select the station and add initial data to create a report
    this.maintenance_report.other_technician_2 = res.data['other_technician_2']; this.maintenance_report.other_technician_3 = res.data['other_technician_3']; this.maintenance_report.contacts = res.data['contacts']; - this.maintenance_report.equipment_types = res.data['equipment_types']; + this.maintenance_report.component_list = res.data['components']; this.maintenance_report.data_logger_file_name = res.data['data_logger_file_name']; this.steps = res.data['steps']; this.loading = false; } ); - }, - getAvailableProfileEquipments(equipment_type){ - - } + }, createMaintenanceReport(item) { this.loading = true; axios.post(`/wx/maintenance_report/create/`,{ @@ -621,15 +653,6 @@
    Select the station and add initial data to create a report
    .finally(() => { this.loading = false; }); - }, - notFutureDate(){ - var today = moment().format('YYYY-MM-DD'); - var now = moment().format('HH:mm'); - - if (this.maintenance_report.visit_date === today){ - return this.maintenance_report.initial_time > now; - } - return this.maintenance_report.visit_date > today; }, updateMaintenanceReportConditions(item){ this.loading = true; @@ -641,6 +664,17 @@
    Select the station and add initial data to create a report
    ); this.loading = false; }, + updateMaintenanceReportComponent(item, component){ + this.loading = true; + axios.put(`/wx/maintenance_report/${item.id}/update/component/${component.component_id}/`,{ + "component_classification": component.component_classification, + "component_condition": component.condition, + }) + .then( + res => {} + ); + this.loading = false; + }, updateMaintenanceReportContacts(item){ this.loading = true; axios.put(`/wx/maintenance_report/${item.id}/update/contacts/`,{ @@ -688,7 +722,7 @@
    Select the station and add initial data to create a report
    this.updateMaintenanceReportDataLogger(this.maintenance_report) }); this.loading = false; - }, + }, }, mounted() { {% if maintenance_report_id %} @@ -698,10 +732,47 @@
    Select the station and add initial data to create a report
    axios.defaults.xsrfCookieName = "csrftoken"; }, }); -
    {% endblock %} \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports_old/equipment_inventory.html b/api/wx/templates/wx/maintenance_reports_old/equipment_inventory.html deleted file mode 100644 index c82da91..0000000 --- a/api/wx/templates/wx/maintenance_reports_old/equipment_inventory.html +++ /dev/null @@ -1,622 +0,0 @@ -{% extends "base.html" %} {% block content %} {% load static %} -
    -
    - - - - [[ request_error_message ]] - - - - - - - - - - Creating a new equipment - - - Updating equipment - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create Equipment - - - Update Equipment - - - Cancel - - - - - - - - - Do you want to delete this equipment? - - - - Equipment Type: [[equipment.equipment_type]] - - - Manufacturer: [[equipment.manufacturer]] - - - Model: [[equipment.model]] - - - Serial Number: [[equipment.serial_number]] - - - - - Delete Equipment - - - Cancel - - - - - - - -
    - Equipment Inventory
    -
    -
    - - - - - - - - {% if perms.wx.add_equipment %} - - New Equipment - - {% endif %} - - - - - - - - - - -
    -
    -
    -
    -{% endblock %} {% block localjavascript %} - - -{% endblock %} \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports_old/maintenance_reports.html b/api/wx/templates/wx/maintenance_reports_old/maintenance_reports.html deleted file mode 100644 index ee858ae..0000000 --- a/api/wx/templates/wx/maintenance_reports_old/maintenance_reports.html +++ /dev/null @@ -1,412 +0,0 @@ -{% extends "base.html" %} {% block content %} {% load static %} -
    -
    - - - -
    - Maintenance Reports
    -
    - -
    Search Form
    - - - - - - - - - - - - - - - - - - - Submit - - - -
    - - -
    Search Results

    - Dates: [[start_date]] to [[end_date]] -
    - - - - - - - - - - - Are you sure that you really want to delete this report? - - - - Delete Report - - - Cancel - - - - - - - - Are you sure that you really want to approve this report? - - - - Approve Report - - - Cancel - - - - -
    -
    -
    -
    - - {% endblock %} {% block localjavascript %} - - - -
    -{% endblock %} \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports_old/new_report.html b/api/wx/templates/wx/maintenance_reports_old/new_report.html deleted file mode 100644 index ab2f6dc..0000000 --- a/api/wx/templates/wx/maintenance_reports_old/new_report.html +++ /dev/null @@ -1,778 +0,0 @@ -{% extends "base.html" %} {% block content %} - - -
    -
    - - - -
    - Maintenance Reports - New Report

    -
    - -
    Select the station and add initial data to create a report
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Visit date and initial time can not be in the future. - - - CREATE REPORT - - - - - - [[ request_error_message ]] - - - - -
    -
    - - - - Reported Created by [[maintenance_report.responsible_technician.name]] at [[maintenance_report.visit_date]] [[maintenance_report.initial_time]] - - - - Station information - -
    - - Station Name: [[maintenance_report.station_information.station_name]] - Station Host Name: [[maintenance_report.station_information.station_host_name]] - Station ID: [[maintenance_report.station_information.station_ID]] - WIGOS ID: [[maintenance_report.station_information.wigos_ID]] - - - - Station Type: [[maintenance_report.station_information.station_type]] - Latitude: [[maintenance_report.station_information.latitude]] - Longitude: [[maintenance_report.station_information.longitude]] - Station Profile: [[maintenance_report.station_information.station_profile]] - - - - Elevation: [[maintenance_report.station_information.elevation]] - District: [[maintenance_report.station_information.district]] - Watershed: [[maintenance_report.station_information.watershed]] - - - - - Transmission Type: [[maintenance_report.station_information.transmission_type]] - Transmission ID: [[maintenance_report.station_information.transmission_ID]] - Transmission Interval: [[maintenance_report.station_information.transmission_interval]] - Measurement Interval: [[maintenance_report.station_information.measurement_interval]] - - - - Data of first Operation: [[maintenance_report.station_information.data_of_first_operation]] - Date of Relocation: [[maintenance_report.station_information.data_of_relocation]] - - - -
    -
    - - - - Describe the station on arrival - - - - - - - - Save and Continue - - - - - - - - Evaluate Condition of [[component.component_name]] - - - - - - - - - - - Save and Continue - - - - - - - - Contacts - - - - - - - - Save and Continue - - - - - - - Summary of the visit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Data Logger File - - - - - - - - End time must be after initial time. - - - Next Visit must be in the future. - - - - - - - Preview - - - Save Draft - Publish - - - - -
    -
    -
    - - {% endblock %} {% block localjavascript %} - - - -
    -{% endblock %} \ No newline at end of file diff --git a/api/wx/templates/wx/maintenance_reports_old/view_report.html b/api/wx/templates/wx/maintenance_reports_old/view_report.html deleted file mode 100644 index cea1e7f..0000000 --- a/api/wx/templates/wx/maintenance_reports_old/view_report.html +++ /dev/null @@ -1,275 +0,0 @@ - -{% load static %} - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - -
    - - -
    - Manual Weather Station Maintenance Report -

    - {{station_information.station_name}} -
    -
    - - - Visit Summary Information - -
    - - - - Report #: - {{visit_summary_information.report_number}} - Responsible Technicians: - {{visit_summary_information.responsible_technician}} - - - - - Date of Visit: - {{visit_summary_information.date_of_visit}} - Date of Next Visit - {{visit_summary_information.date_of_next_visit}} - - - - - Start Time: - {{visit_summary_information.start_time}} - Other Technicians: - {{visit_summary_information.other_technicians}} - - - - - End Time: - {{visit_summary_information.end_time}} - Type of Visit: - {{visit_summary_information.type_of_visit}} - - - - - Description of Station Upon Arrival: - {{visit_summary_information.station_on_arrival_conditions | safe}} - - - - - Summary of Work Done at Station: - {{visit_summary_information.current_visit_summary | safe}} - - - - - Work To Be Done On Next Visit: - {{visit_summary_information.next_visit_summary | safe}} - - - - - Station Information - -
    - - - - Station Name: - {{station_information.station_name}} - Station Host Name: - {{station_information.station_host_name}} - - - - - Sttion ID: - {{station_information.station_ID}} - WIGOS ID: - {{station_information.wigos_ID}} - - - - - Sttion Type: - {{station_information.station_type}} - Sttion Profile: - {{station_information.station_profile}} - - - - - Latitude: - {{station_information.latitude}} - Elevation (m): - {{station_information.elevation}} - - - - - Longitude: - {{station_information.longitude}} - District: - {{station_information.district}} - - - - - Transmission Type: - {{station_information.transmission_type}} - Transmission ID: - {{station_information.transmission_ID}} - - - - - Transmission Interval: - {{station_information.transmission_interval}} - Measurement Interval: - {{station_information.measurement_interval}} - - - - - Date of First Operation: - {{station_information.data_of_first_operation}} - Date(s) of Relocation: - {{station_information.data_of_relocation}} - - - - - Contact Information - -
    - - - - {{ contact_information | safe }} - - - - - Equipment Record - -
    - - - - - - - [[equipment_record.component_name]] - - - - - - - Component Condition - - - - - - - - - - Component Classification - - - - - - - - - - - - - - - - Close Preview - - -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/api/wx/urls.py b/api/wx/urls.py index 4e6c6bc..5779b41 100644 --- a/api/wx/urls.py +++ b/api/wx/urls.py @@ -109,17 +109,6 @@ path('wx/quality_control/persist_threshold/get/', views.get_persist_threshold), path('wx/quality_control/persist_threshold/update/', views.update_persist_threshold), path('wx/quality_control/persist_threshold/delete/', views.delete_persist_threshold), - path('wx/products/wave_data/', login_required(views.get_wave_data_analysis), name='wave-data'), - path('wx/products/wave_data/get/', login_required(views.get_wave_data), name="get-wave-data"), - path('wx/stations/stations_monitoring/', views.stationsmonitoring_form, name="stations-monitoring"), - path('wx/stations/stations_monitoring/get/', views.get_stationsmonitoring_map_data), - path('wx/stations/stations_monitoring/get//', views.get_stationsmonitoring_station_data), - path('wx/stations/stations_monitoring/get///', views.get_stationsmonitoring_chart_data), - path('wx/maintenance_reports/equipment_inventory/', views.get_equipment_inventory, name="equipment-inventory"), - path('wx/maintenance_reports/equipment_inventory/get/', views.get_equipment_inventory_data), - path('wx/maintenance_reports/equipment_inventory/create/', views.create_equipment), - path('wx/maintenance_reports/equipment_inventory/delete/', views.delete_equipment), - path('wx/maintenance_reports/equipment_inventory/update/', views.update_equipment), path('wx/maintenance_report/', login_required(views.get_maintenance_reports), name='maintenance-reports'), path('wx/maintenance_report/get_reports/', login_required(views.get_maintenance_report_list)), path('wx/maintenance_report/new_report/', login_required(views.get_maintenance_report_form), name='new-maintenance-report'), @@ -134,7 +123,17 @@ path('wx/maintenance_report//delete/', login_required(views.delete_maintenance_report)), path('wx/maintenance_report//approve/', login_required(views.approve_maintenance_report)), path('wx/maintenance_report//view//', login_required(views.get_maintenance_report_view), name='view-maintenance-report'), - path('wx/maintenance_reports/get_available_profile_equipments///', login_required(views.get_maintenance_report_view), name='view-maintenance-report'), + path('wx/products/wave_data/', login_required(views.get_wave_data_analysis), name='wave-data'), + path('wx/products/wave_data/get/', login_required(views.get_wave_data), name="get-wave-data"), + path('wx/stations/stations_monitoring/', views.stationsmonitoring_form, name="stations-monitoring"), + path('wx/stations/stations_monitoring/get/', views.get_stationsmonitoring_map_data), + path('wx/stations/stations_monitoring/get//', views.get_stationsmonitoring_station_data), + path('wx/stations/stations_monitoring/get///', views.get_stationsmonitoring_chart_data), + path('wx/maintenance_reports/equipment_inventory/', views.get_equipment_inventory, name="equipment-inventory"), + path('wx/maintenance_reports/equipment_inventory/get/', views.get_equipment_inventory_data), + path('wx/maintenance_reports/equipment_inventory/create/', views.create_equipment), + path('wx/maintenance_reports/equipment_inventory/delete/', views.delete_equipment), + path('wx/maintenance_reports/equipment_inventory/update/', views.update_equipment), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/api/wx/views.py b/api/wx/views.py index 7e4223d..8bcbf8e 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -4074,30 +4074,6 @@ def get_equipment_inventory_data(request): } return JsonResponse(response, status=status.HTTP_200_OK) -from wx.models import StationProfileEquipmentType, MaintenanceReportEquipment - -def is_equipment_full(station, equipment_type): - if station.profile_id is None: - message = f"The station {station.name} - {station.code} is not associtated with any station profile." - return True, message - else: - station_profile = StationProfile.objects.get(id=station.profile_id) - try: - stationprofile_equipmenttypes = StationProfileEquipmentType.objects.filter(profile_id=station_profile.id, equipment_type_id=equipment_type.id) - max_equipments = max(entry.equipment_order for entry in stationprofile_equipmenttypes) - - equipments = Equipments.objects.filter(equipment_type_id=equipment_type.id, location=station.id) - if equipments.count() < max_equipments: - return False, '' - else: - message = f"The number of {equipment_type.name}s in {station.name} - {station.code} is {equipments.count()}. \ - The maximum number setted is {StationProfileEquipmentType.cardinality}." - return True, message - except ObjectDoesNotExist: - message = f"The station profile, {station_profile.name}, of the location \ - is not associtated with the equipment type {equipment_type}. \ - Please create a proper 'Station profile equipment types' object." - return True, message @require_http_methods(["POST"]) def create_equipment(request): @@ -4139,12 +4115,6 @@ def create_equipment(request): return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) except ObjectDoesNotExist: - if location: - is_full, message = is_equipment_full(location, equipment_type) - if is_full: - response = {'message': message} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - now = datetime.datetime.now() equipment = Equipment.objects.create( @@ -4211,12 +4181,6 @@ def update_equipment(request): try: equipment = Equipment.objects.get(id=equipment_id) - if location and equipment_type != equipment.equipment_type: - is_full, message = is_equipment_full(location, equipment_type) - if is_full: - response = {'message': message} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - now = datetime.datetime.now() equipment.updated_at = now @@ -4260,11 +4224,6 @@ def delete_equipment(request): return JsonResponse(response, status=status.HTTP_200_OK) -################################################################ -################################################################ - -############################# List ############################# - @require_http_methods(["GET"]) def get_maintenance_reports(request): # Maintenance report page template = loader.get_template('wx/maintenance_reports/maintenance_reports.html') @@ -4355,7 +4314,6 @@ def approve_maintenance_report(request, id): response={} return JsonResponse(response, status=status.HTTP_200_OK) -############################# View ############################# @require_http_methods(["GET"]) def get_maintenance_report_view(request, id, source): # Maintenance report view @@ -4442,26 +4400,6 @@ def get_maintenance_report_view(request, id, source): # Maintenance report view return HttpResponse(template.render(context, request)) -############################# New ############################## - -def get_ckeditor_config(): - ckeditor_config = { - 'toolbar': [ - ['Bold', 'Italic', 'Font'], - ['Format', 'Styles', 'TextColor', 'BGColor', 'RemoveFormat'], - ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock', 'Indent', 'Outdent'], - ['HorizontalRule', 'BulletedList'], - ['Blockquote', 'Source', 'Link', 'Unlink', 'Image', 'Table', 'Print'] - ], - 'removeButtons': 'Image', - 'extraAllowedContent' : 'img(*){*}[*]', - 'editorplaceholder': 'Description of station upon arribal:', - 'language': 'en', - } - return ckeditor_config - - - def get_maintenance_report_obj(maintenance_report): station = Station.objects.get(id=maintenance_report.station_id) station_profile = StationProfile.objects.get(id=station.profile_id) @@ -4491,72 +4429,6 @@ def get_station_contacts(station_id): return None -################################################## - - -@require_http_methods(["GET"]) # Create maintenance report from sratch -def get_available_profile_equipments(request, maintenance_report_id, equipment_type_id): - maintenance_report = MaintenanceReport.objects.get(id=maintenance_report_id) - equipment_type = EquipmentType.objects.get(id=equipment_type_id) - - station = Station.objects.get(id=maintenance_report.station_id) - station_profile = StationProfile.objects.get(id=station.profile_id) - - maintenance_report_equipments = MaintenanceReportEquipment.objects.filter(maintenance_report=maintenance_report) - used_ids = [entry.profile_equipment_id for entry in maintenance_report_equipments] - - profile_equipments = StationProfileEquipmentType.objects.filter(profile=station_profile.id, equipment_type=equipment_type.id) - profile_equipments = station_profile_equipment_type.exclude(id__in=used_ids) - - return profile_equipments - -def create_new_maintenance_report_equipment(maintenance_report, profile_equipment, equipment): - equipment_type = EquipmentType.objects.get(id=equipment.equipment_type_id) - available_profile_equipments = get_available_profile_equipments(maintenance_report, equipment_type) - - if not profile_equipment in available_profile_equipments: - print('Error profile_equipment is not availabe') - else: - MaintenanceReportEquipment.objects.create( - profile_equipment = profile_equipment, - maintenance_report = maintenance_report, - old_equipment = None, - current_equipment = equipment, - condition = equipment_type.report_template, - classification = equipment.classification, - ) - -################################################## - -def get_last_maintenance_report(maintenance_report): - station_id = maintenance_report.station_id - visit_date = maintenance_report.visit_date - - last_maintenance_report = MaintenanceReport.objects.filter( - station_id=station_id, - visit_date__lt=visit_date).latest('visit_date') - - return last_maintenance_report - -def copy_last_maintenance_report_equipments(maintenance_report): - last_maintenance_report = get_last_maintenance_report(maintenance_report) - - if last_maintenance_report: - last_maintenance_report_equipments = MaintenanceReportEquipment.objects.filter(maintenance_report=last_maintenance_report) - for maintenance_report_equipment in last_maintenance_report_equipments: - equipment = Equipment.objects.get(id=maintenance_report_equipment.current_equipment_id) - equipment_type = EquipmentType.objects.get(id=equipment.equipment_type_id) - - created_object = MaintenanceReportEquipment.objects.create( - created_at = now, - updated_at = now, - profile_equipment = maintenance_report_equipment.profile_equipment, - maintenance_report = maintenance_report, - old_equipment = equipment, - current_equipment = equipment, - condition = equipment_type.report_template, - classification = equipment.condition, - ) @require_http_methods(["POST"]) # Create maintenance report from sratch def create_maintenance_report(request): @@ -4564,40 +4436,65 @@ def create_maintenance_report(request): form_data = json.loads(request.body.decode()) - # Hard delete the maintenance report if its status is soft delete. try: - maintenance_report = MaintenanceReport.objects.get(station_id=form_data['station_id'], visit_date=form_data['visit_date']) + maintenance_report = MaintenanceReport.objects.get(station_id = form_data['station_id'], visit_date = form_data['visit_date']) + if maintenance_report.status=="-": maintenance_report.delete() except ObjectDoesNotExist: pass - - # Check if maintenance report already exists or create. try: maintenance_report = MaintenanceReport.objects.get(station_id = form_data['station_id'], visit_date = form_data['visit_date']) response = {"message": "Maintenance report already exist for chosen station and date."} return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - except ObjectDoesNotExist: + + except ObjectDoesNotExist: maintenance_report = MaintenanceReport.objects.create( - created_at = now, - updated_at = now, - station_id = form_data['station_id'], - responsible_technician_id = form_data['responsible_technician_id'], - visit_type_id = form_data['visit_type_id'], - visit_date = form_data['visit_date'], - initial_time = form_data['initial_time'], - contacts = get_station_contacts(form_data['station_id']), + created_at = now, + updated_at = now, + station_id = form_data['station_id'], + responsible_technician_id = form_data['responsible_technician_id'], + visit_type_id = form_data['visit_type_id'], + visit_date = form_data['visit_date'], + initial_time = form_data['initial_time'], + contacts = get_station_contacts(form_data['station_id']), ) - copy_last_maintenance_report_equipments(maintenance_report) + station = Station.objects.get(pk=maintenance_report.station_id) + station_profile_component_list = StationProfileComponent.objects.filter(profile_id = station.profile_id) + + for station_profile_component in station_profile_component_list: + station_component = StationComponent.objects.get(id=station_profile_component.station_component_id) + + maintenance_report_station_component = MaintenanceReportStationComponent.objects.create( + maintenance_report_id = maintenance_report.id, + station_component_id = station_component.id, + condition = station_component.report_template, + ) response={"maintenance_report_id": maintenance_report.id} return JsonResponse(response, status=status.HTTP_200_OK) +def get_ckeditor_config(): + ckeditor_config = { + 'toolbar': [ + ['Bold', 'Italic', 'Font'], + ['Format', 'Styles', 'TextColor', 'BGColor', 'RemoveFormat'], + ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock', 'Indent', 'Outdent'], + ['HorizontalRule', 'BulletedList'], + ['Blockquote', 'Source', 'Link', 'Unlink', 'Image', 'Table', 'Print'] + ], + 'removeButtons': 'Image', + 'extraAllowedContent' : 'img(*){*}[*]', + 'editorplaceholder': 'Description of station upon arribal:', + 'language': 'en', + } + return ckeditor_config + def get_component_list(maintenance_report): station, station_profile, technician, visit_type = get_maintenance_report_obj(maintenance_report) @@ -4627,40 +4524,7 @@ def get_component_list(maintenance_report): return maintenance_report_station_component_list -def get_station_equipment_types(station): - profile_equipments = StationProfileEquipmentType.objects.filter(profile=station.profile_id).distinct('equipment_type') - - equipment_type_ids = [profile_equipment.equipment_type_id for profile_equipment in profile_equipments] - equipment_types = EquipmentType.objects.filter(id__in=equipment_type_ids) - # return equipment_type - - equipment_type_list = [] - - for equipment_type in equipment_types: - is_full, msg = is_equipment_full(station, equipment_type) - dictionary = {'key':equipment_type.id, - 'id':equipment_type.id, - 'name': equipment_type.name, - 'is_full': is_full} - - equipment_type_list.append(equipment_type) - - return equipment_type_list - -# def get_equipment_type_steps(station): -# profile_equipments = StationProfileEquipmentType.objects.filter(profile=station.profile_id).distinct('equipment_type') - -# equipment_type_ids = [profile_equipment.equipment_type_id for profile_equipment in profile_equipments] -# equipment_types = EquipmentType.objects.filter(id__in=equipment_type_ids) - -# steps = [{ 'title': equipment_type.name, -# 'complete': False, -# 'editable': True } for equipment_type in equipment_types] - -# return steps - - -@require_http_methods(["GET"]) # Ok +@require_http_methods(["GET"]) def get_maintenance_report(request, id): maintenance_report = MaintenanceReport.objects.get(id=id) @@ -4686,8 +4550,6 @@ def get_maintenance_report(request, id): "data_of_relocation": station.relocation_date, } - - response["station_id"] = station.id response["responsible_technician"] = technician.name response["responsible_technician_id"] = maintenance_report.responsible_technician_id @@ -4705,9 +4567,8 @@ def get_maintenance_report(request, id): response["other_technician_3"] = maintenance_report.other_technician_3_id response['contacts'] = maintenance_report.contacts - # response['equipment_types'] = equipment_types - response['equipment_types'] = get_station_equipment_types(station) - response['steps'] = len(response['equipment_types']) + response['components'] = get_component_list(maintenance_report) + response['steps'] = len(response['components']) if maintenance_report.data_logger_file_name is None: response['data_logger_file_name'] = "Upload latest data logger program" @@ -4716,9 +4577,8 @@ def get_maintenance_report(request, id): return JsonResponse(response, status=status.HTTP_200_OK) -### -@require_http_methods(["PUT"]) # Ok +@require_http_methods(["PUT"]) def update_maintenance_report_condition(request, id): now = datetime.datetime.now() @@ -4755,7 +4615,7 @@ def update_maintenance_report_component(request, id, component_id): return JsonResponse(response, status=status.HTTP_200_OK) -@require_http_methods(["PUT"]) # Ok +@require_http_methods(["PUT"]) def update_maintenance_report_contacts(request, id): now = datetime.datetime.now() @@ -4773,7 +4633,7 @@ def update_maintenance_report_contacts(request, id): return JsonResponse(response, status=status.HTTP_200_OK) -@require_http_methods(["POST"]) # Ok +@require_http_methods(["POST"]) def update_maintenance_report_datalogger(request, id): # print(request.FILES) if 'data_logger_file' in request.FILES: @@ -4797,7 +4657,7 @@ def update_maintenance_report_datalogger(request, id): return JsonResponse(response, status=status.HTTP_206_PARTIAL_CONTENT) -@require_http_methods(["PUT"]) # Ok +@require_http_methods(["PUT"]) def update_maintenance_report_summary(request, id): now = datetime.datetime.now() @@ -4837,9 +4697,6 @@ def update_maintenance_report_summary(request, id): return JsonResponse(response, status=status.HTTP_200_OK) -################################################################ -################################################################ - class SpatialAnalysisView(LoginRequiredMixin, TemplateView): template_name = "wx/spatial_analysis.html" diff --git a/api/wx/views_old.py b/api/wx/views_old.py deleted file mode 100644 index 5d2947e..0000000 --- a/api/wx/views_old.py +++ /dev/null @@ -1,6291 +0,0 @@ -import datetime -import io -import json -import logging -import os -import random -import uuid -from datetime import datetime as datetime_constructor -from datetime import timezone - -import matplotlib - -matplotlib.use("Agg") -import cv2 -import matplotlib.pyplot as plt -import pandas as pd -import psycopg2 -import pytz -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.messages.views import SuccessMessageMixin -from django.core.cache import cache -from django.core.exceptions import ObjectDoesNotExist -from django.db import connection -from django.http import HttpResponse, JsonResponse -from django.template import loader -from django.urls import reverse -from django.views.decorators.csrf import csrf_exempt -from django.views.generic.base import TemplateView -from django.views.generic.detail import DetailView -from django.views.generic.edit import CreateView, UpdateView, DeleteView -from django.views.generic.list import ListView -from geopandas import geopandas -from material import * -from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas -from matplotlib.transforms import Bbox -from metpy.interpolate import interpolate_to_grid -from pandas import json_normalize -from rest_framework import viewsets, status, generics, views -from rest_framework.decorators import api_view, permission_classes -from rest_framework.parsers import FileUploadParser -from rest_framework.permissions import IsAuthenticated -from rest_framework.response import Response -from slugify import slugify - -from tempestas_api import settings -from wx import serializers, tasks -from wx.decoders import insert_raw_data_pgia -from wx.decoders.hobo import read_file as read_file_hobo -from wx.decoders.toa5 import read_file -from wx.forms import StationForm -from wx.models import AdministrativeRegion, StationFile, Decoder, QualityFlag, DataFile, DataFileStation, \ - DataFileVariable, StationImage, WMOStationType, WMORegion, WMOProgram, StationCommunication -from wx.models import Country, Unit, Station, Variable, DataSource, StationVariable, \ - StationProfile, Document, Watershed, Interval -from wx.utils import get_altitude, get_watershed, get_district, get_interpolation_image, parse_float_value, \ - parse_int_value -from .utils import get_raw_data, get_station_raw_data -from wx.models import MaintenanceReport, MaintenanceReportStationComponent, StationProfileComponent, StationComponent, VisitType, Technician -from django.views.decorators.http import require_http_methods -from base64 import b64encode - -from wx.models import QualityFlag -import time -from wx.models import HighFrequencyData, MeasurementVariable -from wx.tasks import fft_decompose -import math -import numpy as np - -logger = logging.getLogger('surface.urls') - -# CONSTANT to be used in datetime to milliseconds conversion -EPOCH = datetime_constructor(1970, 1, 1, tzinfo=timezone.utc) - - -@csrf_exempt -def ScheduleDataExport(request): - if request.method != 'POST': - return HttpResponse(status=405) - - json_body = json.loads(request.body) - station_ids = json_body['stations'] # array with station ids - data_source = json_body[ - 'source'] # one of raw_data, hourly_summary, daily_summary, monthly_summary or yearly_summary - start_date = json_body['start_datetime'] # in format %Y-%m-%d %H:%M:%S - end_date = json_body['end_datetime'] # in format %Y-%m-%d %H:%M:%S - variable_ids = json_body['variables'] # list of obj in format {id: Int, agg: Str} - - data_interval_seconds = None - if data_source == 'raw_data' and 'data_interval' in json_body: # a number with the data interval in seconds. Only required for raw_data - data_interval_seconds = json_body['data_interval'] - elif data_source == 'raw_data': - data_interval_seconds = 300 - - created_data_file_ids = [] - start_date_utc = pytz.UTC.localize(datetime.datetime.strptime(start_date, '%Y-%m-%d %H:%M:%S')) - end_date_utc = pytz.UTC.localize(datetime.datetime.strptime(end_date, '%Y-%m-%d %H:%M:%S')) - - if start_date_utc > end_date_utc: - message = 'The initial date must be greater than final date.' - return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) - - days_interval = (end_date_utc - start_date_utc).days - - data_source_dict = { - "raw_data": "Raw Data", - "hourly_summary": "Hourly Summary", - "daily_summary": "Daily Summary", - "monthly_summary": "Monthly Summary", - "yearly_summary": "Yearly Summary", - } - - data_source_description = data_source_dict[data_source] - - prepared_by = None - if request.user.first_name and request.user.last_name: - prepared_by = f'{request.user.first_name} {request.user.last_name}' - else: - prepared_by = request.user.username - - for station_id in station_ids: - newfile = DataFile.objects.create(ready=False, initial_date=start_date_utc, final_date=end_date_utc, - source=data_source_description, prepared_by=prepared_by, - interval_in_seconds=data_interval_seconds) - DataFileStation.objects.create(datafile=newfile, station_id=station_id) - - for variable_id in variable_ids: - variable = Variable.objects.get(pk=variable_id) - DataFileVariable.objects.create(datafile=newfile, variable=variable) - - tasks.export_data.delay(station_id, data_source, start_date, end_date, variable_ids, newfile.id) - created_data_file_ids.append(newfile.id) - - return HttpResponse(created_data_file_ids, status=status.HTTP_200_OK) - - -@api_view(('GET',)) -def DataExportFiles(request): - files = [] - for df in DataFile.objects.all().order_by('-created_at').values()[:100:1]: - if df['ready'] and df['ready_at']: - file_status = {'text': "Ready", 'value': 1} - elif df['ready_at']: - file_status = {'text': "Error", 'value': 2} - else: - file_status = {'text': "Processing", 'value': 0} - - current_station_name = None - try: - current_data_file = DataFileStation.objects.get(datafile_id=df['id']) - current_station = Station.objects.get(pk=current_data_file.station_id) - current_station_name = current_station.name - except ObjectDoesNotExist: - current_station_name = "Station not found" - - f = { - 'id': df['id'], - 'request_date': df['created_at'], - 'ready_date': df['ready_at'], - 'station': current_station_name, - 'variables': [], - 'status': file_status, - 'initial_date': df['initial_date'], - 'final_date': df['final_date'], - 'source': {'text': df['source'], - 'value': 0 if df['source'] == 'Raw data' else (1 if df['source'] == 'Hourly summary' else 2)}, - 'lines': df['lines'], - 'prepared_by': df['prepared_by'] - } - if f['ready_date'] is not None: - f['ready_date'] = f['ready_date'] - for fv in DataFileVariable.objects.filter(datafile_id=df['id']).values(): - f['variables'].append(Variable.objects.filter(pk=fv['variable_id']).values()[0]['name']) - files.append(f) - - return Response(files, status=status.HTTP_200_OK) - - -def DownloadDataFile(request): - file_id = request.GET.get('id', None) - file_path = os.path.join('/data', 'exported_data', str(file_id) + '.csv') - if os.path.exists(file_path): - with open(file_path, 'rb') as fh: - response = HttpResponse(fh.read(), content_type="text/csv") - response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path) - return response - return JsonResponse({}, status=status.HTTP_404_NOT_FOUND) - - -def DeleteDataFile(request): - file_id = request.GET.get('id', None) - df = DataFile.objects.get(pk=file_id) - DataFileStation.objects.filter(datafile=df).delete() - DataFileVariable.objects.filter(datafile=df).delete() - df.delete() - file_path = os.path.join('/data', 'exported_data', str(file_id) + '.csv') - if os.path.exists(file_path): - os.remove(file_path) - return JsonResponse({}, status=status.HTTP_200_OK) - - -def GetInterpolationData(request): - start_datetime = request.GET.get('start_datetime', None) - end_datetime = request.GET.get('end_datetime', None) - variable_id = request.GET.get('variable_id', None) - agg = request.GET.get('agg', "instant") - source = request.GET.get('source', "raw_data") - quality_flags = request.GET.get('quality_flags', None) - - where_query = "" - if source == "raw_data": - dt_query = "datetime" - value_query = "measured" - source_query = "raw_data" - if quality_flags: - try: - [int(qf) for qf in quality_flags.split(',')] - except ValueError: - return JsonResponse({"message": "Invalid quality_flags value."}, status=status.HTTP_400_BAD_REQUEST) - where_query = f" measured != {settings.MISSING_VALUE} AND quality_flag IN ({quality_flags}) AND " - else: - where_query = f" measured != {settings.MISSING_VALUE} AND " - else: - with connection.cursor() as cursor: - cursor.execute(""" - SELECT sampling_operation_id - FROM wx_variable - WHERE id=%(variable_id)s; - """, - params={'variable_id': variable_id} - ) - sampling_operation = cursor.fetchone()[0] - - if sampling_operation in [6, 7]: - value_query = "sum_value" - elif sampling_operation == 3: - value_query = "min_value" - elif sampling_operation == 4: - value_query = "max_value" - else: - value_query = "avg_value" - - if source == "hourly": - dt_query = "datetime" - source_query = "hourly_summary" - - elif source == "daily": - dt_query = "day" - source_query = "daily_summary" - - elif source == "monthly": - dt_query = "date" - source_query = "monthly_summary" - - elif source == "yearly": - dt_query = "date" - source_query = "yearly_summary" - - if agg == "instant": - where_query += "variable_id=%(variable_id)s AND " + dt_query + "=%(datetime)s" - params = {'datetime': start_datetime, 'variable_id': variable_id} - else: - where_query += "variable_id=%(variable_id)s AND " + dt_query + " >= %(start_datetime)s AND " + dt_query + " <= %(end_datetime)s" - params = {'start_datetime': start_datetime, 'end_datetime': end_datetime, 'variable_id': variable_id} - - with connection.cursor() as cursor: - cursor.execute(""" - SELECT a.station_id,b.name,b.code,b.latitude,b.longitude,a.""" + value_query + """ as measured - FROM """ + source_query + """ a INNER JOIN wx_station b ON a.station_id=b.id - WHERE """ + where_query + ";", - params=params - ) - climate_data = {} - # if agg == "instant": - raw_data = cursor.fetchall() - climate_data['data'] = [] - for item in raw_data: - climate_data['data'].append({ - 'station_id': item[0], - 'name': item[1], - 'code': item[2], - 'latitude': item[3], - 'longitude': item[4], - 'measured': item[5], - }) - - if agg != "instant" and len(raw_data) > 0: - columns = ['station_id', 'name', 'code', 'latitude', 'longitude', 'measured'] - df_climate = json_normalize([ - dict(zip(columns, row)) - for row in raw_data - ]) - - climate_data['data'] = json.loads( - df_climate.groupby(['station_id', 'name', 'code', 'longitude', 'latitude']).agg( - agg).reset_index().sort_values('name').to_json(orient="records")) - - return JsonResponse(climate_data) - - -def GetInterpolationImage(request): - start_datetime = request.GET.get('start_datetime', None) - end_datetime = request.GET.get('end_datetime', None) - variable_id = request.GET.get('variable_id', None) - cmap = request.GET.get('cmap', 'Spectral_r') - hres = request.GET.get('hres', 0.01) - minimum_neighbors = request.GET.get('minimum_neighbors', 1) - search_radius = request.GET.get('search_radius', 0.7) - agg = request.GET.get('agg', "instant") - source = request.GET.get('source', "raw_data") - vmin = request.GET.get('vmin', 0) - vmax = request.GET.get('vmax', 30) - quality_flags = request.GET.get('quality_flags', None) - - stations_df = pd.read_sql_query(""" - SELECT id,name,alias_name,code,latitude,longitude - FROM wx_station - WHERE longitude!=0; - """, - con=connection - ) - stations = geopandas.GeoDataFrame( - stations_df, geometry=geopandas.points_from_xy(stations_df.longitude, stations_df.latitude)) - stations.crs = 'epsg:4326' - - stands_llat = settings.SPATIAL_ANALYSIS_INITIAL_LATITUDE - stands_llon = settings.SPATIAL_ANALYSIS_INITIAL_LONGITUDE - stands_ulat = settings.SPATIAL_ANALYSIS_FINAL_LATITUDE - stands_ulon = settings.SPATIAL_ANALYSIS_FINAL_LONGITUDE - - where_query = "" - if source == "raw_data": - dt_query = "datetime" - value_query = "measured" - source_query = "raw_data" - if quality_flags: - try: - [int(qf) for qf in quality_flags.split(',')] - except ValueError: - return JsonResponse({"message": "Invalid quality_flags value."}, status=status.HTTP_400_BAD_REQUEST) - where_query = f" measured != {settings.MISSING_VALUE} AND quality_flag IN ({quality_flags}) AND " - else: - where_query = f" measured != {settings.MISSING_VALUE} AND " - else: - with connection.cursor() as cursor: - cursor.execute(""" - SELECT sampling_operation_id - FROM wx_variable - WHERE id=%(variable_id)s; - """, - params={'variable_id': variable_id} - ) - sampling_operation = cursor.fetchone()[0] - - if sampling_operation in [6, 7]: - value_query = "sum_value" - elif sampling_operation == 3: - value_query = "min_value" - elif sampling_operation == 4: - value_query = "max_value" - else: - value_query = "avg_value" - - if source == "hourly": - dt_query = "datetime" - source_query = "hourly_summary" - - elif source == "daily": - dt_query = "day" - source_query = "daily_summary" - - elif source == "monthly": - dt_query = "date" - source_query = "monthly_summary" - - elif source == "yearly": - dt_query = "date" - source_query = "yearly_summary" - - if agg == "instant": - where_query += "variable_id=%(variable_id)s AND " + dt_query + "=%(datetime)s" - params = {'datetime': start_datetime, 'variable_id': variable_id} - else: - where_query += "variable_id=%(variable_id)s AND " + dt_query + " >= %(start_datetime)s AND " + dt_query + " <= %(end_datetime)s" - params = {'start_datetime': start_datetime, 'end_datetime': end_datetime, 'variable_id': variable_id} - - climate_data = pd.read_sql_query( - "SELECT station_id,variable_id," + dt_query + "," + value_query + """ - FROM """ + source_query + """ - WHERE """ + where_query + ";", - params=params, - con=connection - ) - - if len(climate_data) == 0: - with open("/surface/static/images/no-interpolated-data.png", "rb") as f: - img_data = f.read() - - return HttpResponse(img_data, content_type="image/jpeg") - - df_merged = pd.merge(left=climate_data, right=stations, how='left', left_on='station_id', right_on='id') - df_climate = df_merged[["station_id", dt_query, "longitude", "latitude", value_query]] - - if agg != "instant": - df_climate = df_climate.groupby(['station_id', 'longitude', 'latitude']).agg(agg).reset_index() - - gx, gy, img = interpolate_to_grid( - df_climate["longitude"], - df_climate["latitude"], - df_climate[value_query], - interp_type='cressman', - minimum_neighbors=int(minimum_neighbors), - hres=float(hres), - search_radius=float(search_radius), - boundary_coords={'west': stands_llon, 'east': stands_ulon, 'south': stands_llat, 'north': stands_ulat} - ) - - fig = plt.figure(frameon=False) - ax = plt.Axes(fig, [0., 0., 1., 1.]) - ax.set_axis_off() - fig.add_axes(ax) - ax.imshow(img, origin='lower', cmap=cmap, vmin=vmin, vmax=vmax) - fname = str(uuid.uuid4()) - fig.savefig("/surface/static/images/" + fname + ".png", dpi='figure', format='png', transparent=True, - bbox_inches=Bbox.from_bounds(2, 0, 2.333, 4.013)) - image1 = cv2.imread("/surface/static/images/" + fname + ".png", cv2.IMREAD_UNCHANGED) - image2 = cv2.imread(settings.SPATIAL_ANALYSIS_SHAPE_FILE_PATH, cv2.IMREAD_UNCHANGED) - image1 = cv2.resize(image1, dsize=(image2.shape[1], image2.shape[0])) - for i in range(image1.shape[0]): - for j in range(image1.shape[1]): - image1[i][j][3] = image2[i][j][3] - cv2.imwrite("/surface/static/images/" + fname + "-output.png", image1) - - with open("/surface/static/images/" + fname + "-output.png", "rb") as f: - img_data = f.read() - - os.remove("/surface/static/images/" + fname + ".png") - os.remove("/surface/static/images/" + fname + "-output.png") - - return HttpResponse(img_data, content_type="image/jpeg") - - -def GetColorMapBar(request): - start_datetime_req = request.GET.get('start_datetime', '') - end_datetime_req = request.GET.get('end_datetime', '') - variable_id = request.GET.get('variable_id', '') - cmap = request.GET.get('cmap', 'Spectral_r') - agg = request.GET.get('agg', "instant") - vmin = request.GET.get('vmin', 0) - vmax = request.GET.get('vmax', 30) - - try: - start_datetime = pytz.UTC.localize(datetime.datetime.strptime(start_datetime_req, '%Y-%m-%dT%H:%M:%S.%fZ')) - end_datetime = pytz.UTC.localize(datetime.datetime.strptime(end_datetime_req, '%Y-%m-%dT%H:%M:%S.%fZ')) - - start_datetime = start_datetime.astimezone(pytz.timezone(settings.TIMEZONE_NAME)) - end_datetime = end_datetime.astimezone(pytz.timezone(settings.TIMEZONE_NAME)) - except ValueError: - try: - start_datetime = pytz.UTC.localize(datetime.datetime.strptime(start_datetime_req, '%Y-%m-%d')) - end_datetime = pytz.UTC.localize(datetime.datetime.strptime(end_datetime_req, '%Y-%m-%d')) - - except ValueError: - return JsonResponse({"message": "Invalid date format"}, status=status.HTTP_400_BAD_REQUEST) - - with connection.cursor() as cursor: - cursor.execute(""" - SELECT a.name,b.symbol - FROM wx_variable a INNER JOIN wx_unit b ON a.unit_id=b.id - WHERE a.id=%(variable_id)s; - """, - params={'variable_id': variable_id} - ) - variable = cursor.fetchone() - - fig = plt.figure(figsize=(9, 1.5)) - plt.imshow([[2]], origin='lower', cmap=cmap, vmin=vmin, vmax=vmax) - plt.gca().set_visible(False) - cax = plt.axes([0.1, 0.2, 0.8, 0.2]) - title = variable[0] + ' (' + variable[1] + ') - ' + start_datetime.strftime("%d/%m/%Y %H:%M:%S") - if agg != 'instant': - title += ' to ' + end_datetime.strftime("%d/%m/%Y %H:%M:%S") - title += ' (' + agg + ')' - cax.set_title(title) - plt.colorbar(orientation='horizontal', cax=cax) - - FigureCanvas(fig) - buf = io.BytesIO() - plt.savefig(buf, dpi='figure', format='png', transparent=True, bbox_inches='tight') - plt.close(fig) - response = HttpResponse(buf.getvalue(), content_type='image/png') - - return response - - -@csrf_exempt -def InterpolatePostData(request): - if request.method != 'POST': - return HttpResponse(status=405) - - stands_llat = settings.SPATIAL_ANALYSIS_INITIAL_LATITUDE - stands_llon = settings.SPATIAL_ANALYSIS_INITIAL_LONGITUDE - stands_ulat = settings.SPATIAL_ANALYSIS_FINAL_LATITUDE - stands_ulon = settings.SPATIAL_ANALYSIS_FINAL_LONGITUDE - - json_body = json.loads(request.body) - parameters = json_body['parameters'] - vmin = json_body['vmin'] - vmax = json_body['vmax'] - df_climate = json_normalize(json_body['data']) - try: - df_climate = df_climate[["station_id", "longitude", "latitude", "measured"]] - except KeyError: - return HttpResponse("no-interpolated-data.png") - - gx, gy, img = interpolate_to_grid( - df_climate["longitude"], - df_climate["latitude"], - df_climate["measured"], - interp_type='cressman', - minimum_neighbors=int(parameters["minimum_neighbors"]), - hres=float(parameters["hres"]), - search_radius=float(parameters["search_radius"]), - boundary_coords={'west': stands_llon, 'east': stands_ulon, 'south': stands_llat, 'north': stands_ulat} - ) - - fig = plt.figure(frameon=False) - ax = plt.Axes(fig, [0., 0., 1., 1.]) - ax.set_axis_off() - fig.add_axes(ax) - ax.imshow(img, origin='lower', cmap=parameters["cmap"]["value"], vmin=vmin, vmax=vmax) - for filename in os.listdir('/surface/static/images'): - try: - if datetime.datetime.now() - datetime.datetime.strptime(filename.split('_')[0], - "%Y-%m-%dT%H:%M:%SZ") > datetime.timedelta( - minutes=5): - os.remove('/surface/static/images/' + filename) - except ValueError: - continue - fname = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ_") + str(uuid.uuid4()) - fig.savefig("/surface/static/images/" + fname + ".png", dpi='figure', format='png', transparent=True, - bbox_inches=Bbox.from_bounds(2, 0, 2.333, 4.013)) - image1 = cv2.imread("/surface/static/images/" + fname + ".png", cv2.IMREAD_UNCHANGED) - image2 = cv2.imread('/surface/static/images/blz_shape.png', cv2.IMREAD_UNCHANGED) - image1 = cv2.resize(image1, dsize=(image2.shape[1], image2.shape[0])) - for i in range(image1.shape[0]): - for j in range(image1.shape[1]): - image1[i][j][3] = image2[i][j][3] - cv2.imwrite("/surface/static/images/" + fname + "-output.png", image1) - - return HttpResponse(fname + "-output.png") - - -@permission_classes([IsAuthenticated]) -def GetImage(request): - image = request.GET.get('image', None) - try: - with open("/surface/static/images/" + image, "rb") as f: - return HttpResponse(f.read(), content_type="image/jpeg") - except IOError: - red = Image.new('RGBA', (1, 1), (255, 0, 0, 0)) - response = HttpResponse(content_type="image/jpeg") - red.save(response, "JPEG") - return response - - -from django.db.models.functions import Cast -from django.db.models import IntegerField - - -@permission_classes([IsAuthenticated]) -def DailyFormView(request): - template = loader.get_template('wx/daily_form.html') - - station_list = Station.objects.filter(is_automatic=False, is_active=True) - station_list = station_list.values('id', 'name', 'code') - - # for station in station_list: - # station['code'] = int(station['code']) - - context = {'station_list': station_list} - - return HttpResponse(template.render(context, request)) - - -class SynopCaptureView(LoginRequiredMixin, TemplateView): - template_name = "wx/synopcapture.html" - - -@permission_classes([IsAuthenticated]) -def DataCaptureView(request): - template = loader.get_template('wx/data_capture.html') - return HttpResponse(template.render({}, request)) - - -class DataExportView(LoginRequiredMixin, TemplateView): - template_name = "wx/data_export.html" - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - context['station_list'] = Station.objects.select_related('profile').all() - context['variable_list'] = Variable.objects.select_related('unit').all() - - context['station_profile_list'] = StationProfile.objects.all() - context['station_watershed_list'] = Watershed.objects.all() - context['station_district_list'] = AdministrativeRegion.objects.all() - - interval_list = Interval.objects.filter(seconds__lte=3600).order_by('seconds') - context['interval_list'] = interval_list - - return self.render_to_response(context) - - -class CountryViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = Country.objects.all().order_by("name") - serializer_class = serializers.CountrySerializer - - -class UnitViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = Unit.objects.all().order_by("name") - serializer_class = serializers.UnitSerializer - - -class DataSourceViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = DataSource.objects.all().order_by("name") - serializer_class = serializers.DataSourceSerializer - - -class VariableViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = Variable.objects.all().order_by("name") - serializer_class = serializers.VariableSerializer - - -class StationMetadataViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = Station.objects.all() - serializer_class = serializers.StationMetadataSerializer - - def get_serializer_class(self): - if self.request.method in ['GET']: - return serializers.StationSerializerRead - return serializers.StationMetadataSerializer - - -class StationViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = Station.objects.all() - - # queryset = Station.objects.all().select_related("country").order_by("name") - # def put(self, request, *args, **kwargs): - # station_object = Station.objects.get() - # data = request.data - - # station_object.save() - # serializer = serializers.StationSerializerWrite - - - def get_serializer_class(self): - if self.request.method in ['GET']: - return serializers.StationSerializerRead - - return serializers.StationSerializerWrite - - -class StationSimpleViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = Station.objects.all() - serializer_class = serializers.StationSimpleSerializer - - -class StationVariableViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = StationVariable.objects.all().order_by("variable") - serializer_class = serializers.StationVariableSerializer - - def get_queryset(self): - queryset = StationVariable.objects.all() - - station_id = self.request.query_params.get('station_id', None) - - if station_id is not None: - queryset = queryset.filter(station__id=station_id) - - return queryset - - -class StationProfileViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = StationProfile.objects.all().order_by("name") - serializer_class = serializers.StationProfileSerializer - - - -class DocumentViewSet(views.APIView): - permission_classes = (IsAuthenticated,) - parser_class = (FileUploadParser,) - queryset = Document.objects.all() - serializer_class = serializers.DocumentSerializer - available_decoders = {'HOBO': read_file_hobo, 'TOA5': read_file} - decoders = Decoder.objects.all().exclude(name='DCP TEXT').exclude(name='NESA') - - def put(self, request, format=None): - selected_decoder = 'TOA5' - - if 'decoder' in request.data.keys(): - selected_decoder = request.data['decoder'] - - serializer = serializers.DocumentSerializer(data=request.data) - - if serializer.is_valid(): - document = serializer.save() - self.available_decoders[selected_decoder].delay(document.file.path) - return Response({"message": "FIle uploaded successfully!"}, status=status.HTTP_201_CREATED) - - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def post(self, request, format=None): - return self.put(request, format=None) - - -class AdministrativeRegionViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = AdministrativeRegion.objects.all().order_by("name") - serializer_class = serializers.AdministrativeRegionSerializer - - -@api_view(['GET']) -def station_telemetry_data(request, date): - mock = { - 'temperature': { - 'min': 10, - 'max': 13, - 'avg': 16 - }, - 'relativeHumidity': { - 'min': 10, - 'max': 13, - 'avg': 16 - }, - 'precipitation': { - 'current': 123 - }, - 'windDirection': { - 'current': 'SW' - }, - 'windSpeed': { - 'current': 11, - 'max': 12 - }, - 'windGust': { - 'current': 12, - 'max': 11 - }, - 'solarRadiation': { - 'current': 12 - }, - 'atmosphericPressure': { - 'current': 11 - } - } - - data = { - 'latest': mock, - 'last24': mock, - 'current': mock, - } - - return Response(data) - - -def raw_data_list(request): - search_type = request.GET.get('search_type', None) - search_value = request.GET.get('search_value', None) - search_value2 = request.GET.get('search_value2', None) - search_date_start = request.GET.get( - 'search_date_start', - default=(datetime.datetime.now() - datetime.timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%SZ') - ) - search_date_end = request.GET.get( - 'search_date_end', - default=datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') - ) - sql_string = "" - - response = { - 'results': [], - 'messages': [], - } - - try: - start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%dT%H:%M:%SZ') - end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%dT%H:%M:%SZ') - - except ValueError: - message = 'Invalid date format. Expected YYYY-MM-DDTHH:MI:SSZ' - return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) - - delta = end_date - start_date - - if delta.days > 8: # Restrict queries to max seven days - message = 'Interval between start date and end date is greater than one week.' - return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) - - if search_type in ['station', 'stationvariable']: - try: - station = Station.objects.get(code=search_value) - except ObjectDoesNotExist: - station = Station.objects.get(pk=search_value) - finally: - search_value = station.id - - if search_type is not None and search_type == 'variable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - c.name, - c.symbol, - a.measured, - a.datetime, - q.symbol as quality_flag, - b.variable_type, - a.code - FROM raw_data a - JOIN wx_variable b ON a.variable_id=b.id - LEFT JOIN wx_unit c ON b.unit_id=c.id - JOIN wx_qualityflag q ON a.quality_flag=q.id - WHERE b.id = %s - AND datetime >= %s - AND datetime <= %s - """ - - if search_type is not None and search_type == 'station': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - c.name, - c.symbol, - a.measured, - a.datetime, - q.symbol as quality_flag, - b.variable_type, - a.code - FROM raw_data a - JOIN wx_variable b ON a.variable_id=b.id - LEFT JOIN wx_unit c ON b.unit_id=c.id - JOIN wx_qualityflag q ON a.quality_flag=q.id - WHERE station_id=%s AND datetime >= %s AND datetime <= %s""" - - if search_type is not None and search_type == 'stationvariable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - c.name, - c.symbol, - a.measured, - a.datetime, - q.symbol as quality_flag, - b.variable_type, - a.code - FROM raw_data a - JOIN wx_variable b ON a.variable_id=b.id - LEFT JOIN wx_unit c ON b.unit_id=c.id - JOIN wx_qualityflag q ON a.quality_flag=q.id - WHERE station_id=%s AND variable_id=%s AND datetime >= %s AND datetime <= %s""" - - if sql_string: - sql_string += " ORDER BY datetime" - - with connection.cursor() as cursor: - - if search_type is not None and search_type == 'stationvariable': - - cursor.execute(sql_string, [search_value, search_value2, search_date_start, search_date_end]) - - else: - - cursor.execute(sql_string, [search_value, search_date_start, search_date_end]) - - rows = cursor.fetchall() - - for row in rows: - - if row[9] is not None and row[9].lower() == 'code': - value = row[10] - else: - value = round(row[6], 2) - - obj = { - 'station': row[0], - 'date': row[7], - 'value': value, - 'variable': { - 'symbol': row[2], - 'name': row[3], - 'unit_name': row[4], - 'unit_symbol': row[5] - } - } - - response['results'].append(obj) - - if response['results']: - return JsonResponse(response, status=status.HTTP_200_OK) - - return JsonResponse(data={"message": "No data found."}, status=status.HTTP_404_NOT_FOUND) - - -def hourly_summary_list(request): - search_type = request.GET.get('search_type', None) - search_value = request.GET.get('search_value', None) - search_value2 = request.GET.get('search_value2', None) - search_date_start = request.GET.get( - 'search_date_start', - default=(datetime.datetime.now() - datetime.timedelta(days=30)).strftime('%Y-%m-%dT%H:%M:%SZ') - ) - search_date_end = request.GET.get( - 'search_date_end', - default=datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') - ) - sql_string = "" - - response = { - 'results': [], - 'messages': [], - } - - try: - start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%dT%H:%M:%SZ') - end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%dT%H:%M:%SZ') - - except ValueError: - message = 'Invalid date format. Expected YYYY-MM-DDTHH:MI:SSZ' - return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) - - delta = end_date - start_date - - if delta.days > 32: # Restrict queries to max 31 days - message = 'Interval between start date and end date is greater than one month.' - return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) - - if search_type in ['station', 'stationvariable']: - try: - station = Station.objects.get(code=search_value) - except ObjectDoesNotExist: - station = Station.objects.get(pk=search_value) - finally: - search_value = station.id - - if search_type is not None and search_type == 'variable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - num_records, - datetime as data - FROM hourly_summary a - INNER JOIN wx_variable b ON a.variable_id=b.id - INNER JOIN wx_unit c ON b.unit_id=c.id - WHERE b.id=%s AND datetime >= %s AND datetime <= %s""" - - if search_type is not None and search_type == 'station': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - num_records, - datetime as data - FROM hourly_summary a - INNER JOIN wx_variable b ON a.variable_id=b.id - INNER JOIN wx_unit c ON b.unit_id=c.id - WHERE station_id=%s AND datetime >= %s AND datetime <= %s""" - - if search_type is not None and search_type == 'stationvariable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - num_records, - datetime as data - FROM hourly_summary a - INNER JOIN wx_variable b ON a.variable_id=b.id - INNER JOIN wx_unit c ON b.unit_id=c.id - WHERE station_id=%s AND variable_id=%s AND datetime >= %s AND datetime <= %s""" - - if sql_string: - sql_string += " ORDER BY datetime" - - with connection.cursor() as cursor: - - if search_type is not None and search_type == 'stationvariable': - - cursor.execute(sql_string, [search_value, search_value2, search_date_start, search_date_end]) - - else: - - cursor.execute(sql_string, [search_value, search_date_start, search_date_end]) - - rows = cursor.fetchall() - - for row in rows: - - value = None - - if row[4] in [1, 2]: - value = row[9] - - elif row[4] == 3: - value = row[7] - - elif row[4] == 4: - value = row[8] - - elif row[4] == 6: - value = row[10] - - else: - value = row[10] - - if value is None: - - print('variable {} does not have supported sampling operation {}'.format(row[1], row[4])) - - else: - - obj = { - 'station': row[0], - 'date': row[12], - 'value': round(value, 2), - 'min': round(row[7], 2), - 'max': round(row[8], 2), - 'avg': round(row[9], 2), - 'sum': round(row[10], 2), - 'count': round(row[11], 2), - 'variable': { - 'symbol': row[2], - 'name': row[3], - 'unit_name': row[5], - 'unit_symbol': row[6], - } - } - - response['results'].append(obj) - - if response['results']: - return JsonResponse(response, status=status.HTTP_200_OK) - - return JsonResponse(data=response) - - -def daily_summary_list(request): - search_type = request.GET.get('search_type', None) - search_value = request.GET.get('search_value', None) - search_value2 = request.GET.get('search_value2', None) - search_date_start = request.GET.get( - 'search_date_start', - default=(datetime.datetime.now() - datetime.timedelta(days=365)).strftime('%Y-%m-%d') - ) - search_date_end = request.GET.get( - 'search_date_end', - default=datetime.datetime.now().strftime('%Y-%m-%d') - ) - sql_string = "" - - response = { - 'results': [], - 'messages': [], - } - - try: - start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%d') - end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%d') - - except ValueError: - message = 'Invalid date format. Expected YYYY-MM-DD' - return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) - - delta = end_date - start_date - - if delta.days > 400: # Restrict queries to max 400 days - message = 'Interval between start date and end date is greater than 13 months.' - return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) - - if search_type in ['station', 'stationvariable']: - try: - station = Station.objects.get(code=search_value) - except ObjectDoesNotExist: - station = Station.objects.get(pk=search_value) - finally: - search_value = station.id - - if search_type is not None and search_type == 'variable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - day, - num_records - FROM daily_summary a - INNER JOIN wx_variable b ON a.variable_id=b.id - INNER JOIN wx_unit c ON b.unit_id=c.id - WHERE b.id = %s - AND day >= %s - AND day <= %s""" - - if search_type is not None and search_type == 'station': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - day, - num_records - FROM daily_summary a - INNER JOIN wx_variable b ON a.variable_id=b.id - INNER JOIN wx_unit c ON b.unit_id=c.id - WHERE station_id=%s AND day >= %s AND day <= %s""" - - if search_type is not None and search_type == 'stationvariable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - day, - num_records - FROM daily_summary a - INNER JOIN wx_variable b ON a.variable_id=b.id - INNER JOIN wx_unit c ON b.unit_id=c.id - WHERE station_id=%s AND variable_id=%s AND day >= %s AND day <= %s""" - - if sql_string: - sql_string += " ORDER BY day" - - with connection.cursor() as cursor: - - if search_type is not None and search_type == 'stationvariable': - cursor.execute(sql_string, [search_value, search_value2, search_date_start, search_date_end]) - else: - cursor.execute(sql_string, [search_value, search_date_start, search_date_end]) - - rows = cursor.fetchall() - - for row in rows: - - value = None - - if row[4] in [1, 2]: - value = row[9] - - elif row[4] == 3: - value = row[7] - - elif row[4] == 4: - value = row[8] - - elif row[4] == 6: - value = row[10] - - else: - value = row[10] - - if value is not None: - - obj = { - 'station': row[0], - 'date': row[11], - 'value': round(value, 2), - 'min': round(row[7], 2), - 'max': round(row[8], 2), - 'avg': round(row[9], 2), - 'total': round(row[10], 2), - 'count': row[12], - 'variable': { - 'symbol': row[2], - 'name': row[3], - 'unit_name': row[5], - 'unit_symbol': row[6], - } - } - - response['results'].append(obj) - - else: - JsonResponse(data={ - "message": 'variable {} does not have supported sampling operation {}'.format(row[1], row[4])}, - status=status.HTTP_400_BAD_REQUEST) - - if response['results']: - return JsonResponse(response, status=status.HTTP_200_OK) - - return JsonResponse(data=response) - - -def monthly_summary_list(request): - search_type = request.GET.get('search_type', None) - search_value = request.GET.get('search_value', None) - search_value2 = request.GET.get('search_value2', None) - search_date_start = request.GET.get( - 'search_date_start', - default=(datetime.datetime.now() - datetime.timedelta(days=365)).strftime('%Y-%m-%d') - ) - search_date_end = request.GET.get( - 'search_date_end', - default=datetime.datetime.now().strftime('%Y-%m-%d') - ) - - sql_string = "" - - response = { - 'count': -999, - 'next': None, - 'previous': None, - 'results': [] - } - - try: - start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%d') - end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%d') - - except ValueError: - message = 'Invalid date format. Expected YYYY-MM-DD' - return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) - - if search_type in ['station', 'stationvariable']: - try: - station = Station.objects.get(code=search_value) - except ObjectDoesNotExist: - station = Station.objects.get(pk=search_value) - finally: - search_value = station.id - - if search_type is not None and search_type == 'variable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - date::date, - num_records - FROM monthly_summary a - JOIN wx_variable b ON a.variable_id=b.id - JOIN wx_unit c ON b.unit_id=c.id - WHERE b.id = %s - AND date >= %s - AND date <= %s - """ - - if search_type is not None and search_type == 'station': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - date::date, - num_records - FROM monthly_summary a - JOIN wx_variable b ON a.variable_id=b.id - JOIN wx_unit c ON b.unit_id=c.id - WHERE station_id = %s - AND date >= %s AND date <= %s - """ - - if search_type is not None and search_type == 'stationvariable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - date::date, - num_records - FROM monthly_summary a - JOIN wx_variable b ON a.variable_id=b.id - JOIN wx_unit c ON b.unit_id=c.id - WHERE station_id = %s - AND variable_id = %s - AND date >= %s AND date <= %s - """ - - if sql_string: - sql_string += " ORDER BY month" - - with connection.cursor() as cursor: - - if search_type is not None and search_type == 'stationvariable': - cursor.execute(sql_string, [search_value, search_value2, start_date, end_date]) - else: - cursor.execute(sql_string, [search_value, start_date, end_date]) - - rows = cursor.fetchall() - - for row in rows: - - value = None - - if row[4] in [1, 2]: - value = row[9] - - elif row[4] == 3: - value = row[7] - - elif row[4] == 4: - value = row[8] - - elif row[4] == 6: - value = row[10] - - else: - value = row[10] - - if value is not None: - - obj = { - 'station': row[0], - 'date': row[11], - 'value': round(value, 2), - 'min': round(row[7], 2), - 'max': round(row[8], 2), - 'avg': round(row[9], 2), - 'total': round(row[10], 2), - 'count': row[12], - 'variable': { - 'symbol': row[2], - 'name': row[3], - 'unit_name': row[5], - 'unit_symbol': row[6], - } - } - - response['results'].append(obj) - - else: - JsonResponse(data={ - "message": 'variable {} does not have supported sampling operation {}'.format(row[1], row[4])}, - status=status.HTTP_400_BAD_REQUEST) - - if response['results']: - return JsonResponse(response, status=status.HTTP_200_OK) - - return JsonResponse(data=response) - - -def yearly_summary_list(request): - search_type = request.GET.get('search_type', None) - search_value = request.GET.get('search_value', None) - search_value2 = request.GET.get('search_value2', None) - search_date_start = request.GET.get( - 'search_date_start', - default=(datetime.datetime.now() - datetime.timedelta(days=365)).strftime('%Y-%m-%d') - ) - search_date_end = request.GET.get( - 'search_date_end', - default=datetime.datetime.now().strftime('%Y-%m-%d') - ) - - sql_string = "" - - response = { - 'count': -999, - 'next': None, - 'previous': None, - 'results': [] - } - - try: - start_date = datetime.datetime.strptime(search_date_start, '%Y-%m-%d') - end_date = datetime.datetime.strptime(search_date_end, '%Y-%m-%d') - - except ValueError: - message = 'Invalid date format. Expected YYYY-MM-DD' - return JsonResponse(data={"message": message}, status=status.HTTP_400_BAD_REQUEST) - - if search_type in ['station', 'stationvariable']: - try: - station = Station.objects.get(code=search_value) - except ObjectDoesNotExist: - station = Station.objects.get(pk=search_value) - finally: - search_value = station.id - - if search_type is not None and search_type == 'variable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - date::date, - num_records - FROM yearly_summary a - JOIN wx_variable b ON a.variable_id=b.id - JOIN wx_unit c ON b.unit_id=c.id - WHERE b.id = %s - AND date >= %s - AND date <= %s - """ - - if search_type is not None and search_type == 'station': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - date::date, - num_records - FROM yearly_summary a - JOIN wx_variable b ON a.variable_id=b.id - JOIN wx_unit c ON b.unit_id=c.id - WHERE station_id = %s - AND date >= %s AND date <= %s - """ - - if search_type is not None and search_type == 'stationvariable': - sql_string = """ - SELECT station_id, - variable_id, - b.symbol, - b.name, - b.sampling_operation_id, - c.name, - c.symbol, - min_value, - max_value, - avg_value, - sum_value, - date::date, - num_records - FROM yearly_summary a - JOIN wx_variable b ON a.variable_id=b.id - JOIN wx_unit c ON b.unit_id=c.id - WHERE station_id = %s - AND variable_id = %s - AND date >= %s AND date <= %s - """ - - if sql_string: - sql_string += " ORDER BY year" - - with connection.cursor() as cursor: - - if search_type is not None and search_type == 'stationvariable': - cursor.execute(sql_string, [search_value, search_value2, start_date, end_date]) - else: - cursor.execute(sql_string, [search_value, start_date, end_date]) - - rows = cursor.fetchall() - - for row in rows: - - value = None - - if row[4] in [1, 2]: - value = row[9] - - elif row[4] == 3: - value = row[7] - - elif row[4] == 4: - value = row[8] - - elif row[4] == 6: - value = row[10] - - else: - value = row[10] - - if value is not None: - - obj = { - 'station': row[0], - 'date': row[11], - 'value': round(value, 2), - 'min': round(row[7], 2), - 'max': round(row[8], 2), - 'avg': round(row[9], 2), - 'total': round(row[10], 2), - 'count': row[12], - 'variable': { - 'symbol': row[2], - 'name': row[3], - 'unit_name': row[5], - 'unit_symbol': row[6], - } - } - - response['results'].append(obj) - - else: - JsonResponse(data={ - "message": 'variable {} does not have supported sampling operation {}'.format(row[1], row[4])}, - status=status.HTTP_400_BAD_REQUEST) - - if response['results']: - return JsonResponse(response, status=status.HTTP_200_OK) - - return JsonResponse(data=response) - - -@api_view(['GET']) -def station_geo_features(request, lon, lat): - longitude = float(lon) - latitude = float(lat) - - altitude = get_altitude(longitude, latitude) - - watershed = get_watershed(longitude, latitude) - - district = get_district(longitude, latitude) - - data = { - 'elevation': altitude, - 'watershed': watershed, - 'country': 'Belize', - 'administrative_region': district, - 'longitude': longitude, - 'latitude': latitude, - } - - return Response(data) - - -def get_last24_data(station_id): - result = [] - max_date = None - - query = """ - SELECT - last24h.datetime, - var.name, - var.symbol, - var.sampling_operation_id, - unit.name, - unit.symbol, - last24h.min_value, - last24h.max_value, - last24h.avg_value, - last24h.sum_value, - last24h.latest_value - FROM - last24h_summary last24h - INNER JOIN - wx_variable var ON last24h.variable_id=var.id - INNER JOIN - wx_unit unit ON var.unit_id=unit.id - WHERE - last24h.station_id=%s - ORDER BY var.name""" - - with connection.cursor() as cursor: - - cursor.execute(query, [station_id]) - - rows = cursor.fetchall() - - for row in rows: - - value = None - - if row[3] == 1: - value = row[10] - - elif row[3] == 2: - value = row[8] - - elif row[3] == 3: - value = row[6] - - elif row[3] == 4: - value = row[7] - - elif row[3] == 6: - value = row[9] - - if value is None: - print('variable {} does not have supported sampling operation {}'.format(row[1], row[3])) - - obj = { - 'value': value, - 'variable': { - 'name': row[1], - 'symbol': row[2], - 'unit_name': row[4], - 'unit_symbol': row[5] - } - } - result.append(obj) - - max_date = cache.get('last24h_summary_last_run', None) - - return result, max_date - - -def get_latest_data(station_id): - result = [] - max_date = None - - query = """ - SELECT CASE WHEN var.variable_type ilike 'code' THEN latest.last_data_code ELSE latest.last_data_value::varchar END as value, - latest.last_data_datetime, - var.name, - var.symbol, - unit.name, - unit.symbol - FROM wx_stationvariable latest - INNER JOIN wx_variable var ON latest.variable_id=var.id - LEFT JOIN wx_unit unit ON var.unit_id=unit.id - WHERE latest.station_id=%s - AND latest.last_data_value is not null - AND latest.last_data_datetime = ( SELECT MAX(most_recent.last_data_datetime) - FROM wx_stationvariable most_recent - WHERE most_recent.station_id=latest.station_id - AND most_recent.last_data_value is not null) - ORDER BY var.name - """ - - with connection.cursor() as cursor: - - cursor.execute(query, [station_id]) - - rows = cursor.fetchall() - - for row in rows: - obj = { - 'value': row[0], - 'variable': { - 'name': row[2], - 'symbol': row[3], - 'unit_name': row[4], - 'unit_symbol': row[5] - } - } - result.append(obj) - - if rows: - max_date = rows[-1][1] - - return result, max_date - - -def get_current_data(station_id): - result = [] - max_date = None - parameter_timezone = pytz.timezone(settings.TIMEZONE_NAME) - today = datetime.datetime.now().astimezone(parameter_timezone).date() - - query = """ - SELECT current.day, - var.name, - var.symbol, - var.sampling_operation_id, - unit.name, - unit.symbol, - current.min_value, - current.max_value, - current.avg_value, - current.sum_value - FROM daily_summary current - INNER JOIN wx_variable var ON current.variable_id=var.id - INNER JOIN wx_unit unit ON var.unit_id=unit.id - WHERE current.station_id=%s and current.day=%s - ORDER BY current.day, var.name - """ - - with connection.cursor() as cursor: - - cursor.execute(query, [station_id, today]) - - rows = cursor.fetchall() - - for row in rows: - - value = None - - if row[3] in (1, 2): - value = row[8] - - elif row[3] == 3: - value = row[6] - - elif row[3] == 4: - value = row[7] - - elif row[3] == 6: - value = row[9] - - if value is None: - print('variable {} does not have supported sampling operation {}'.format(row[1], row[3])) - - obj = { - 'value': value, - 'variable': { - 'name': row[1], - 'symbol': row[2], - 'unit_name': row[4], - 'unit_symbol': row[5] - } - } - result.append(obj) - - max_date = cache.get('daily_summary_last_run', None) - - return result, max_date - - -@api_view(['GET']) -def livedata(request, code): - try: - station = Station.objects.get(code=code) - except ObjectDoesNotExist as e: - station = Station.objects.get(pk=code) - finally: - id = station.id - - past24h_data, past24h_max_date = get_last24_data(station_id=id) - latest_data, latest_max_date = get_latest_data(station_id=id) - current_data, current_max_date = get_current_data(station_id=id) - - station_data = serializers.StationSerializerRead(station).data - - return Response( - { - 'station': station_data, - 'station_name': station.name, - 'station_id': station.id, - 'past24h': past24h_data, - 'past24h_last_update': past24h_max_date, - 'latest': latest_data, - 'latest_last_update': latest_max_date, - 'currentday': current_data, - 'currentday_last_update': current_max_date, - } - , status=status.HTTP_200_OK) - - -class WatershedList(generics.ListAPIView): - permission_classes = (IsAuthenticated,) - serializer_class = serializers.WatershedSerializer - queryset = Watershed.objects.all().order_by("watershed") - - -class StationCommunicationList(generics.ListAPIView): - permission_classes = (IsAuthenticated,) - serializer_class = serializers.StationCommunicationSerializer - queryset = StationCommunication.objects.all() - - -class DecoderList(generics.ListAPIView): - permission_classes = (IsAuthenticated,) - serializer_class = serializers.DecoderSerializer - queryset = Decoder.objects.all().order_by("name") - - -class QualityFlagList(viewsets.ReadOnlyModelViewSet): - permission_classes = (IsAuthenticated,) - serializer_class = serializers.QualityFlagSerializer - queryset = QualityFlag.objects.all().order_by("name") - - -def qc_list(request): - if request.method == 'GET': - station_id = request.GET.get('station_id', None) - variable_id = request.GET.get('variable_id', None) - start_date = request.GET.get('start_date', None) - end_date = request.GET.get('end_date', None) - - if station_id is None: - JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if variable_id is None: - JsonResponse(data={"message": "'variable_id' parameter cannot be null."}, - status=status.HTTP_400_BAD_REQUEST) - - sql_string = "" - where_parameters = [station_id, variable_id] - - response = { - 'count': -999, - 'next': None, - 'previous': None, - 'results': [] - } - - sql_string = """SELECT value.datetime - ,value.measured - ,value.consisted - ,value.quality_flag - ,value.manual_flag - ,value.station_id - ,value.variable_id - ,value.remarks - ,value.ml_flag - FROM raw_data as value - WHERE value.station_id=%s - AND value.variable_id=%s - """ - - if start_date is not None and end_date is not None: - where_parameters.append(start_date) - where_parameters.append(end_date) - sql_string += " AND value.datetime >= %s AND value.datetime <= %s" - - elif start_date is not None: - where_parameters.append(start_date) - sql_string += " AND value.datetime >= %s" - - elif end_date is not None: - where_parameters.append(end_date) - sql_string += " AND %s >= value.datetime " - - sql_string += " ORDER BY value.datetime " - with connection.cursor() as cursor: - - cursor.execute(sql_string, where_parameters) - rows = cursor.fetchall() - - for row in rows: - obj = { - 'datetime': row[0], - 'measured': row[1], - 'consisted': row[2], - 'automatic_flag': row[3], - 'manual_flag': row[4], - 'station_id': row[5], - 'variable_id': row[6], - 'remarks': row[7], - 'ml_flag': row[8], - } - - response['results'].append(obj) - - return JsonResponse(response) - - if request.method == 'PATCH': - station_id = request.GET.get('station_id', None) - variable_id = request.GET.get('variable_id', None) - req_datetime = request.GET.get('datetime', None) - - if station_id is None: - JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if variable_id is None: - JsonResponse(data={"message": "'variable_id' parameter cannot be null."}, - status=status.HTTP_400_BAD_REQUEST) - - if req_datetime is None: - JsonResponse(data={"message": "'datetime' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - try: - station_id = int(station_id) - variable_id = int(variable_id) - req_datetime = datetime.datetime.strptime(req_datetime, '%Y-%m-%dT%H:%M:%SZ') - except ValueError: - JsonResponse(data={"message": "Invalid parameter type."}, status=status.HTTP_400_BAD_REQUEST) - - body = json.loads(request.body.decode('utf-8')) - query_parameters = [] - sql_columns_to_update = [] - - if 'manual_flag' in body: - try: - query_parameters.append(parse_int_value(body['manual_flag'])) - sql_columns_to_update.append(' manual_flag=%s ') - except ValueError: - return JsonResponse({'message': 'Wrong manual flag value type.'}, status=status.HTTP_400_BAD_REQUEST) - - if 'consisted' in body: - try: - query_parameters.append(parse_float_value(body['consisted'])) - sql_columns_to_update.append(' consisted=%s ') - except ValueError: - return JsonResponse({'message': 'Wrong consisted value type. Please inform a float value.'}, - status=status.HTTP_400_BAD_REQUEST) - - if 'remarks' in body: - try: - query_parameters.append(body['remarks']) - sql_columns_to_update.append(' remarks=%s ') - except ValueError: - return JsonResponse({'message': 'Wrong remarks value type. Please inform a text value.'}, - status=status.HTTP_400_BAD_REQUEST) - - if not sql_columns_to_update: - JsonResponse(data={"message": "You must send 'manual_flag', 'consisted' or 'remarks' data to update."}, - status=status.HTTP_400_BAD_REQUEST) - - if query_parameters: - query_parameters.append(req_datetime) - query_parameters.append(station_id) - query_parameters.append(variable_id) - sql_query = f"UPDATE raw_data SET {', '.join(sql_columns_to_update)} WHERE datetime=%s AND station_id=%s AND variable_id=%s" - - station = Station.objects.get(pk=station_id) - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - cursor.execute(sql_query, query_parameters) - - now = datetime.datetime.now() - cursor.execute(""" - INSERT INTO wx_hourlysummarytask (station_id, datetime, updated_at, created_at) - VALUES (%s, %s, %s, %s) - ON CONFLICT DO NOTHING - """, (station_id, req_datetime, now, now)) - - station_timezone = pytz.UTC - if station.utc_offset_minutes is not None: - station_timezone = pytz.FixedOffset(station.utc_offset_minutes) - - date = req_datetime.astimezone(station_timezone).date() - cursor.execute(""" - INSERT INTO wx_dailysummarytask (station_id, date, updated_at, created_at) - VALUES (%s, %s, %s, %s) - ON CONFLICT DO NOTHING - """, (station_id, date, now, now)) - - cursor.execute(sql_query, query_parameters) - - conn.commit() - - return JsonResponse({}, status=status.HTTP_200_OK) - return JsonResponse({'message': "There is no 'manual_flag' or 'consisted' fields in the json request."}, - status=status.HTTP_400_BAD_REQUEST) - - return JsonResponse({'message': 'Only the GET and PATCH methods is allowed.'}, status=status.HTTP_400_BAD_REQUEST) - - -@csrf_exempt -def interpolate_endpoint(request): - request_date = request.GET.get('request_date', None) - variable_id = request.GET.get('variable_id', None) - data_type = request.GET.get('data_type', None) - - sql_string = '' - where_parameters = [variable_id, request_date] - - if data_type == 'daily': - sql_string = """ - SELECT station.latitude - ,station.longitude - ,daily.avg_value - FROM daily_summary as daily - INNER JOIN wx_station as station ON daily.station_id=station.id - WHERE daily.variable_id=%s and daily.day=%s - ORDER BY daily.day - """ - - elif data_type == 'hourly': - - sql_string = """ - SELECT station.latitude - ,station.longitude - ,hourly.avg_value - FROM hourly_summary as hourly - INNER JOIN wx_station as station ON hourly.station_id=station.id - WHERE hourly.variable_id=%s and hourly.datetime=%s - ORDER BY hourly.datetime - """ - - elif data_type == 'monthly': - sql_string = """ - SELECT station.latitude - ,station.longitude - ,monthly.avg_value - FROM monthly_summary as monthly - INNER JOIN wx_station as station ON monthly.station_id=station.id - WHERE monthly.variable_id=%s and to_date(concat(lpad(monthly.year::varchar(4),4,'0'), lpad(monthly.month::varchar(2),2,'0'), '01'), 'YYYYMMDD')=date_trunc('month',TIMESTAMP %s) - ORDER BY monthly.year, monthly.month - """ - - if sql_string and where_parameters: - query_result = [] - with connection.cursor() as cursor: - cursor.execute(sql_string, where_parameters) - rows = cursor.fetchall() - - for row in rows: - obj = { - 'station__latitude': row[0], - 'station__longitude': row[1], - 'value': row[2], - } - - query_result.append(obj) - - if not query_result: - return JsonResponse({'message': 'No data found.'}, status=status.HTTP_400_BAD_REQUEST) - - return HttpResponse(get_interpolation_image(query_result), content_type="image/jpeg") - - return JsonResponse({'message': 'Missing parameters.'}, status=status.HTTP_400_BAD_REQUEST) - - -@csrf_exempt -def capture_forms_values_get(request): - request_month = request.GET.get('request_month', None) - station_id = request.GET.get('station_id', None) - variable_id = request.GET.get('variable_id', None) - - sql_string = """ - select to_char(date.day, 'DD') - ,to_char(date.day, 'HHAM') - ,values.measured - ,%s - ,%s - ,date.day - from ( select generate_series(to_date(%s, 'YYYY-MM') + interval '6 hours', (to_date(%s, 'YYYY-MM') + interval '1 month -1 second'), '12 hours') as day ) date - LEFT JOIN raw_data as values ON date.day=values.datetime and values.station_id=%s and values.variable_id=%s - ORDER BY date.day - """ - where_parameters = [station_id, variable_id, request_month, request_month, station_id, variable_id] - - with connection.cursor() as cursor: - cursor.execute(sql_string, where_parameters) - rows = cursor.fetchall() - - days = rows - - days = {} - for row in rows: - obj = { - 'value': row[2], - 'station_id': row[3], - 'variable_id': row[4], - 'datetime': row[5], - } - - if row[0] not in days.keys(): - days[row[0]] = {} - - days[row[0]][row[1]] = obj - - full_list = [] - for day in days.keys(): - line = {} - for obj in days[day]: - line[obj] = days[day][obj] - full_list.append(line) - - if not days: - return JsonResponse({'message': 'No data found.'}, status=status.HTTP_400_BAD_REQUEST) - - return JsonResponse({'next': None, 'results': full_list}, safe=False, status=status.HTTP_200_OK) - - return JsonResponse({'message': 'Missing parameters.'}, status=status.HTTP_400_BAD_REQUEST) - - -''' -@csrf_exempt -def capture_forms_values_patch(request): - if request.method == 'PATCH': - - body = json.loads(request.body.decode('utf-8')) - - conn = psycopg2.connect(settings.SURFACE_CONNECTION_STRING) - with conn.cursor() as cursor: - cursor.executemany( - """ - INSERT INTO raw_data(datetime, station_id, variable_id, measured) VALUES(%(datetime)s, %(station_id)s, %(variable_id)s, %(value)s::double precision) - ON CONFLICT (datetime, station_id, variable_id) - DO UPDATE - SET measured = %(value)s - """, body) - - conn.commit() - conn.close() - - return JsonResponse({}, status=status.HTTP_200_OK) - return JsonResponse({'message':'Only the GET and PATCH methods is allowed.'}, status=status.HTTP_400_BAD_REQUEST) -''' - - -@csrf_exempt -def capture_forms_values_patch(request): - error_flag = False - if request.method == 'PATCH': - - body = json.loads(request.body.decode('utf-8')) - - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - for rec in body: - try: - if not rec['value']: - cursor.execute( - """ DELETE FROM raw_data WHERE datetime=%(datetime)s and station_id=%(station_id)s and variable_id=%(variable_id)s """, - rec) - else: - valor = float(rec['value']) - - cursor.execute( - """ - INSERT INTO raw_data(datetime, station_id, variable_id, measured) VALUES(%(datetime)s, %(station_id)s, %(variable_id)s, %(value)s::double precision) - ON CONFLICT (datetime, station_id, variable_id) - DO UPDATE - SET measured = %(value)s - """, rec) - except (ValueError, psycopg2.errors.InvalidTextRepresentation): - error_flag = True - - conn.commit() - - if error_flag: - return JsonResponse({'message': 'Some data was bad formated, please certify that the input is numeric.'}, - status=status.HTTP_200_OK) - - return JsonResponse({}, status=status.HTTP_200_OK) - return JsonResponse({'message': 'Only the GET and PATCH methods is allowed.'}, status=status.HTTP_400_BAD_REQUEST) - - -class StationListView(LoginRequiredMixin, ListView): - model = Station - - -class StationDetailView(LoginRequiredMixin, DetailView): - model = Station - - -class StationCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView): - model = Station - - success_message = "%(name)s was created successfully" - form_class = StationForm - - layout = Layout( - Fieldset('Editing station', - Row('latitude', 'longitude'), - Row('name'), - Row('is_active', 'is_automatic'), - Row('alias_name'), - Row('code', 'profile'), - Row('wmo', 'organization'), - Row('wigos', 'observer'), - Row('begin_date', 'data_source'), - Row('end_date', 'communication_type') - ), - Fieldset('Other information', - Row('elevation', 'watershed'), - Row('country', 'region'), - Row('utc_offset_minutes', 'local_land_use'), - Row('soil_type', 'station_details'), - Row('site_description', 'alternative_names') - ), - # Fieldset('Hydrology information', - # Row('hydrology_station_type', 'ground_water_province'), - # Row('existing_gauges', 'flow_direction_at_station'), - # Row('flow_direction_above_station', 'flow_direction_below_station'), - # Row('bank_full_stage', 'bridge_level'), - # Row('temporary_benchmark', 'mean_sea_level'), - # Row('river_code', 'river_course'), - # Row('catchment_area_station', 'river_origin'), - # Row('easting', 'northing'), - # Row('river_outlet', 'river_length'), - # Row('z', 'land_surface_elevation'), - # Row('top_casing_land_surface', 'casing_diameter'), - # Row('screen_length', 'depth_midpoint'), - # Row('casing_type', 'datum'), - # Row('zone') - # ) - ) - - # passing required context for watershed and region autocomplete fields - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - - context['watersheds'] = Watershed.objects.all() - context['regions'] = AdministrativeRegion.objects.all() - - return context - - - -class StationUpdate(LoginRequiredMixin, SuccessMessageMixin, UpdateView): - model = Station - success_message = "%(name)s was updated successfully" - form_class = StationForm - - layout = Layout( - Fieldset('Editing station', - Row('latitude', 'longitude'), - Row('name', 'is_active'), - Row('alias_name', 'is_automatic'), - Row('code', 'profile'), - Row('wmo', 'organization'), - Row('wigos', 'observer'), - Row('begin_date', 'data_source'), - Row('end_date', 'communication_type') - ), - Fieldset('Other information', - Row('elevation', 'watershed'), - Row('country', 'region'), - Row('utc_offset_minutes', 'local_land_use'), - Row('soil_type', 'station_details'), - Row('site_description', 'alternative_names') - ), - Fieldset('Hydrology information', - Row('hydrology_station_type', 'ground_water_province'), - Row('existing_gauges', 'flow_direction_at_station'), - Row('flow_direction_above_station', 'flow_direction_below_station'), - Row('bank_full_stage', 'bridge_level'), - Row('temporary_benchmark', 'mean_sea_level'), - Row('river_code', 'river_course'), - Row('catchment_area_station', 'river_origin'), - Row('easting', 'northing'), - Row('river_outlet', 'river_length'), - Row('z', 'land_surface_elevation'), - Row('top_casing_land_surface', 'casing_diameter'), - Row('screen_length', 'depth_midpoint'), - Row('casing_type', 'datum'), - Row('zone') - ) - - ) - - -@api_view(['POST']) -def pgia_update(request): - try: - hours_dict = request.data['table'] - now_utc = datetime.datetime.now().astimezone(pytz.UTC) + datetime.timedelta( - hours=settings.PGIA_REPORT_HOURS_AHEAD_TIME) - - pgia = Station.objects.get(id=4) - datetime_offset = pytz.FixedOffset(pgia.utc_offset_minutes) - - day = datetime.datetime.strptime(request.data['date'], '%Y-%m-%d') - station_id = pgia.id - seconds = 3600 - - records_list = [] - for hour, hour_data in hours_dict.items(): - data_datetime = day.replace(hour=int(hour)) - data_datetime = datetime_offset.localize(data_datetime) - if data_datetime <= now_utc: - if hour_data: - if 'action' in hour_data.keys(): - hour_data.pop('action') - - if 'remarks' in hour_data.keys(): - remarks = hour_data.pop('remarks') - else: - remarks = None - - if 'observer' in hour_data.keys(): - observer = hour_data.pop('observer') - else: - observer = None - - for variable_id, measurement in hour_data.items(): - if measurement is None: - measurement_value = settings.MISSING_VALUE - measurement_code = settings.MISSING_VALUE_CODE - - try: - measurement_value = float(measurement) - measurement_code = measurement - except Exception: - measurement_value = settings.MISSING_VALUE - measurement_code = settings.MISSING_VALUE_CODE - records_list.append(( - station_id, variable_id, seconds, data_datetime, measurement_value, 1, None, - None, None, None, None, None, None, None, False, remarks, observer, - measurement_code)) - - insert_raw_data_pgia.insert(raw_data_list=records_list, date=day, station_id=station_id, - override_data_on_conflict=True, utc_offset_minutes=pgia.utc_offset_minutes) - except Exception as e: - logger.error(repr(e)) - return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - return HttpResponse(status=status.HTTP_200_OK) - - -@api_view(['GET']) -def pgia_load(request): - try: - date = datetime.datetime.strptime(request.GET['date'], '%Y-%m-%d') - except ValueError as e: - logger.error(repr(e)) - return HttpResponse(status=status.HTTP_400_BAD_REQUEST) - except Exception as e: - logger.error(repr(e)) - return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - pgia = Station.objects.get(id=4) - datetime_offset = pytz.FixedOffset(pgia.utc_offset_minutes) - request_datetime = datetime_offset.localize(date) - - start_datetime = request_datetime - end_datetime = request_datetime + datetime.timedelta(days=1) - - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - cursor.execute( - f""" - SELECT (datetime + interval '{pgia.utc_offset_minutes} minutes') at time zone 'utc', - variable_id, - CASE WHEN var.variable_type ilike 'code' THEN code ELSE measured::varchar END as value, - remarks, - observer - FROM raw_data - JOIN wx_variable var ON raw_data.variable_id=var.id - WHERE station_id = %(station_id)s - AND datetime >= %(start_date)s - AND datetime < %(end_date)s - """, - { - 'start_date': start_datetime, - 'end_date': end_datetime, - 'station_id': pgia.id - }) - - response = cursor.fetchall() - - return JsonResponse(response, status=status.HTTP_200_OK, safe=False) - - -@api_view(['GET']) -def MonthlyFormLoad(request): - try: - start_date = datetime.datetime.strptime(request.GET['date'], '%Y-%m') - station_id = int(request.GET['station']) - except ValueError as e: - return JsonResponse({}, status=status.HTTP_400_BAD_REQUEST) - except Exception as e: - return JsonResponse({}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - station = Station.objects.get(id=station_id) - datetime_offset = pytz.FixedOffset(station.utc_offset_minutes) - end_date = start_date.replace(month=start_date.month + 1) - datetime.timedelta(days=1) - - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - cursor.execute( - f""" - SELECT (datetime + interval '{station.utc_offset_minutes} minutes') at time zone 'utc' - ,variable_id - ,measured - FROM raw_data - WHERE station_id = %(station_id)s - AND datetime >= %(start_date)s - AND datetime <= %(end_date)s - """, - { - 'start_date': start_date, - 'end_date': end_date, - 'station_id': station_id - }) - - response = cursor.fetchall() - - conn.commit() - - return JsonResponse(response, status=status.HTTP_200_OK, safe=False) - - -@api_view(['POST']) -def MonthlyFormUpdate(request): - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - records_list = [] - - station_id = int(request.data['station']) - station = Station.objects.get(id=station_id) - first_day = datetime.datetime(year=int(request.data['date']['year']), month=int(request.data['date']['month']), - day=1) - - now_utc = datetime.datetime.now().astimezone(pytz.UTC) - datetime_offset = pytz.FixedOffset(station.utc_offset_minutes) - - days_in_month = (first_day.replace(month=first_day.month + 1) - datetime.timedelta(days=1)).day - - for day in range(0, days_in_month): - data = request.data['table'][day] - data_datetime = first_day.replace(day=day + 1) - data_datetime = datetime_offset.localize(data_datetime) - - if data_datetime <= now_utc: - for variable_id, value in data.items(): - if value is None: - value = settings.MISSING_VALUE - - records_list.append(( - station_id, variable_id, 86400, data_datetime, value, 1, None, None, None, None, - None, None, None, None, False, None, None, None)) - - insert_raw_data_pgia.insert(raw_data_list=records_list, date=first_day, station_id=station_id, - override_data_on_conflict=True, utc_offset_minutes=station.utc_offset_minutes) - return JsonResponse({}, status=status.HTTP_200_OK) - - -class StationDelete(LoginRequiredMixin, DeleteView): - model = Station - fields = ['code', 'name', 'profile', ] - - def get_success_url(self): - return reverse('stations-list') - - -class StationFileList(LoginRequiredMixin, ListView): - model = StationFile - - def get_queryset(self): - queryset = StationFile.objects.filter(station__id=self.kwargs.get('pk')) - return queryset - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - station = Station.objects.get(pk=self.kwargs.get( - 'pk')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters - context['station'] = station - return context - - -class StationFileCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView): - model = StationFile - # fields = "__all__" - fields = ('name', 'file') - success_message = "%(name)s was created successfully" - layout = Layout( - Fieldset('Add file to station', - Row('name'), - Row('file') - ) - ) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - station = Station.objects.get(pk=self.kwargs.get( - 'pk')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters - context['station'] = station - return context - - def form_valid(self, form): - f = form.save(commit=False) - station = Station.objects.get(pk=self.kwargs.get('pk')) - f.station = station - f.save() - return super(StationFileCreate, self).form_valid(form) - - def get_success_url(self): - return reverse('stationfiles-list', kwargs={'pk': self.kwargs.get('pk')}) - - -class StationFileDelete(LoginRequiredMixin, DeleteView): - model = StationFile - success_message = "%(name)s was deleted successfully" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - station = Station.objects.get(pk=self.kwargs.get( - 'pk_station')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters - context['station'] = station - return context - - def get_success_url(self): - return reverse('stationfiles-list', kwargs={'pk': self.kwargs.get('pk_station')}) - - -class StationVariableListView(LoginRequiredMixin, ListView): - model = StationVariable - - def get_queryset(self): - queryset = StationVariable.objects.filter(station__id=self.kwargs.get('pk')) - return queryset - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - station = Station.objects.get(pk=self.kwargs.get( - 'pk')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters - context['station'] = station - return context - - -class StationVariableCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): - model = StationVariable - fields = ('variable',) - success_message = "%(variable)s was created successfully" - layout = Layout( - Fieldset('Add variable to station', - Row('variable') - ) - ) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - station = Station.objects.get(pk=self.kwargs.get( - 'pk')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters - context['station'] = station - return context - - def form_valid(self, form): - f = form.save(commit=False) - station = Station.objects.get(pk=self.kwargs.get('pk')) - f.station = station - f.save() - return super(StationVariableCreateView, self).form_valid(form) - - def get_success_url(self): - return reverse('stationvariable-list', kwargs={'pk': self.kwargs.get('pk')}) - - -class StationVariableDeleteView(LoginRequiredMixin, DeleteView): - model = StationVariable - success_message = "%(action)s was deleted successfully" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - station = Station.objects.get(pk=self.kwargs.get( - 'pk_station')) # the self.kwargs is different from **kwargs, and gives access to the named url parameters - context['station'] = station - return context - - def get_success_url(self): - return reverse('stationvariable-list', kwargs={'pk': self.kwargs.get('pk_station')}) - - -def station_report_data(request): - station = request.GET.get('station', None) - initial_datetime = request.GET.get('initial_datetime', None) - final_datetime = request.GET.get('final_datetime', None) - source = request.GET.get('source', None) - - if station and initial_datetime and final_datetime and source: - if int(source) == 0: # Raw data - dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'raw_data') - elif int(source) == 1: # Hourly data - dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'hourly_summary') - elif int(source) == 2: # Daily data - dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'daily_summary') - elif int(source) == 3: # Monthly data - dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'monthly_summary') - elif int(source) == 4: # Yearly data - dataset = get_raw_data('station', station, None, initial_datetime, final_datetime, 'yearly_summary') - else: - return JsonResponse({}, status=status.HTTP_404_NOT_FOUND) - - charts = { - - } - - for element_name, element_data in dataset['results'].items(): - - chart = { - 'chart': { - 'type': 'pie', - 'zoomType': 'xy' - }, - 'title': {'text': element_name}, - 'xAxis': { - 'type': 'datetime', - 'dateTimeLabelFormats': { - 'month': '%e. %b', - 'year': '%b' - }, - 'title': { - 'text': 'Date' - } - }, - 'yAxis': [], - 'exporting': { - 'showTable': True - }, - 'series': [] - } - - opposite = False - y_axis_unit_dict = {} - for variable_name, variable_data in element_data.items(): - - current_unit = variable_data['unit'] - if current_unit not in y_axis_unit_dict.keys(): - chart['yAxis'].append({ - 'labels': { - 'format': '{value} ' + variable_data['unit'] - }, - 'title': { - 'text': None - }, - 'opposite': opposite - }) - y_axis_unit_dict[current_unit] = len(chart['yAxis']) - 1 - opposite = not opposite - - current_y_axis_index = y_axis_unit_dict[current_unit] - data = [] - for record in variable_data['data']: - if int(source) == 0: - data.append({ - 'x': record['date'], - 'y': record['value'], - 'quality_flag': record['quality_flag'], - 'flag_color': record['flag_color'] - }) - elif int(source) == 3: - data.append({ - 'x': record['date'], - 'y': record['value'] - }) - - chart['xAxis'] = { - 'type': 'datetime', - 'labels': { - 'format': '{value:%Y-%b}' - }, - 'title': { - 'text': 'Y' - } - } - elif int(source) == 4: - data.append({ - 'x': record['date'], - 'y': record['value'] - }) - - chart['xAxis'] = { - 'type': 'datetime', - 'labels': { - 'format': '{value:%Y-%b}' - }, - 'title': { - 'text': 'Reference' - } - } - else: - data.append({ - 'x': record['date'], - 'y': record['value'] - }) - - chart['series'].append({ - 'name': variable_name, - 'color': variable_data['color'], - 'type': variable_data['default_representation'], - 'unit': variable_data['unit'], - 'data': data, - 'yAxis': current_y_axis_index - }) - chart['chart']['type'] = variable_data['default_representation'], - - charts[slugify(element_name)] = chart - - return JsonResponse(charts) - else: - return JsonResponse({}, status=status.HTTP_400_BAD_REQUEST) - - -def variable_report_data(request): - variable_ids_list = request.GET.get('variable_ids', None) - initial_datetime = request.GET.get('initial_datetime', None) - final_datetime = request.GET.get('final_datetime', None) - source = request.GET.get('source', None) - station_id_list = request.GET.get('station_ids', None) - - if variable_ids_list and initial_datetime and final_datetime and source and station_id_list: - variable_ids_list = tuple(json.loads(variable_ids_list)) - station_id_list = tuple(json.loads(station_id_list)) - - if int(source) == 0: # Raw data - dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, - station_id_list, 'raw_data') - elif int(source) == 1: # Hourly data - dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, - station_id_list, 'hourly_summary') - elif int(source) == 2: # Daily data - dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, - station_id_list, 'daily_summary') - elif int(source) == 3: # Monthly data - dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, - station_id_list, 'monthly_summary') - elif int(source) == 4: # Yearly data - dataset = get_station_raw_data('variable', variable_ids_list, None, initial_datetime, final_datetime, - station_id_list, 'yearly_summary') - else: - return JsonResponse({}, status=status.HTTP_404_NOT_FOUND) - - charts = { - - } - - for element_name, element_data in dataset['results'].items(): - - chart = { - 'chart': { - 'type': 'pie', - 'zoomType': 'xy' - }, - 'title': {'text': element_name}, - 'xAxis': { - 'type': 'datetime', - 'dateTimeLabelFormats': { - 'month': '%e. %b', - 'year': '%b' - }, - 'title': { - 'text': 'Date' - } - }, - 'yAxis': [], - 'exporting': { - 'showTable': True - }, - 'series': [] - } - - opposite = False - y_axis_unit_dict = {} - for variable_name, variable_data in element_data.items(): - - current_unit = variable_data['unit'] - if current_unit not in y_axis_unit_dict.keys(): - chart['yAxis'].append({ - 'labels': { - 'format': '{value} ' + variable_data['unit'] - }, - 'title': { - 'text': None - }, - 'opposite': opposite - }) - y_axis_unit_dict[current_unit] = len(chart['yAxis']) - 1 - opposite = not opposite - - current_y_axis_index = y_axis_unit_dict[current_unit] - data = [] - for record in variable_data['data']: - if int(source) == 0: - data.append({ - 'x': record['date'], - 'y': record['value'], - 'quality_flag': record['quality_flag'], - 'flag_color': record['flag_color'] - }) - elif int(source) == 3: - data.append({ - 'x': record['date'], - 'y': record['value'] - }) - - chart['xAxis'] = { - 'type': 'datetime', - 'labels': { - 'format': '{value:%Y-%b}' - }, - 'title': { - 'text': 'Y' - } - } - elif int(source) == 4: - data.append({ - 'x': record['date'], - 'y': record['value'] - }) - - chart['xAxis'] = { - 'type': 'datetime', - 'labels': { - 'format': '{value:%Y-%b}' - }, - 'title': { - 'text': 'Reference' - } - } - else: - data.append({ - 'x': record['date'], - 'y': record['value'] - }) - - chart['series'].append({ - 'name': variable_name, - 'color': "#" + ''.join([random.choice('0123456789ABCDEF') for j in range(6)]), - 'type': variable_data['default_representation'], - 'unit': variable_data['unit'], - 'data': data, - 'yAxis': current_y_axis_index - }) - chart['chart']['type'] = variable_data['default_representation'], - - charts[slugify(element_name)] = chart - - return JsonResponse(charts) - else: - return JsonResponse({}, status=status.HTTP_400_BAD_REQUEST) - - -class StationReportView(LoginRequiredMixin, TemplateView): - template_name = "wx/products/station_report.html" - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - context['station_id'] = request.GET.get('station_id', 'null') - - station_list = Station.objects.all() - context['station_list'] = station_list - - # interval_list = Interval.objects.filter(seconds__lte=3600).order_by('seconds') - # context['interval_list'] = interval_list - - quality_flag_query = QualityFlag.objects.all() - quality_flag_colors = {} - for quality_flag in quality_flag_query: - quality_flag_colors[quality_flag.name.replace(' ', '_')] = quality_flag.color - context['quality_flag_colors'] = quality_flag_colors - - selected_station = station_list.first() - if selected_station is not None: - selected_station_id = selected_station.id - station_variable_list = StationVariable.objects.filter(station__id=selected_station_id) - else: - station_variable_list = [] - - context['station_variable_list'] = station_variable_list - - return self.render_to_response(context) - - -class VariableReportView(LoginRequiredMixin, TemplateView): - template_name = "wx/products/variable_report.html" - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - - quality_flag_query = QualityFlag.objects.all() - quality_flag_colors = {} - for quality_flag in quality_flag_query: - quality_flag_colors[quality_flag.name.replace(' ', '_')] = quality_flag.color - - context['quality_flag_colors'] = quality_flag_colors - context['variable_list'] = Variable.objects.all() - context['station_list'] = Station.objects.all() - - return self.render_to_response(context) - - -class ProductReportView(LoginRequiredMixin, TemplateView): - template_name = "wx/products/report.html" - - -class ProductCompareView(LoginRequiredMixin, TemplateView): - template_name = 'wx/products/compare.html' - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - context['station_list'] = Station.objects.select_related('profile').all() - context['variable_list'] = Variable.objects.select_related('unit').all() - - context['station_profile_list'] = StationProfile.objects.all() - context['station_watershed_list'] = Watershed.objects.all() - context['station_district_list'] = AdministrativeRegion.objects.all() - - return self.render_to_response(context) - - -class QualityControlView(LoginRequiredMixin, TemplateView): - template_name = 'wx/quality_control/validation.html' - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - context['station_list'] = Station.objects.select_related('profile').all() - - context['station_profile_list'] = StationProfile.objects.all() - context['station_watershed_list'] = Watershed.objects.all() - context['station_district_list'] = AdministrativeRegion.objects.all() - - return self.render_to_response(context) - - -@csrf_exempt -def get_yearly_average(request): - begin_date = request.GET.get('begin_date', None) - end_date = request.GET.get('end_date', None) - station_id = request.GET.get('station_id', None) - variable_id = request.GET.get('variable_id', None) - - sql_string = """ - select avg(val.measured) as average, extract ('year' from month)::varchar as year, extract ('month' from month)::varchar as month, round(avg(val.measured)::decimal, 2) - from generate_series(date_trunc('year', date %s),date_trunc('year', date %s) + interval '1 year' - interval '1 day', interval '1 month') month - left outer join raw_data as val on date_trunc('month', val.datetime) = date_trunc('month', month) and val.station_id = %s and val.variable_id = %s - group by month - order by month - """ - where_parameters = [begin_date, end_date, station_id, variable_id] - - with connection.cursor() as cursor: - cursor.execute(sql_string, where_parameters) - rows = cursor.fetchall() - - years = {} - for row in rows: - if row[1] not in years.keys(): - years[row[1]] = {} - - if row[2] not in years[row[1]].keys(): - years[row[1]][row[2]] = row[3] - - years[row[1]][row[2]] = row[3] - - if not years: - return JsonResponse({'message': 'No data found.'}, status=status.HTTP_400_BAD_REQUEST) - - return JsonResponse({'next': None, 'results': years}, safe=False, status=status.HTTP_200_OK) - - return JsonResponse({'message': 'Missing parameters.'}, status=status.HTTP_400_BAD_REQUEST) - - -class YearlyAverageReport(LoginRequiredMixin, TemplateView): - template_name = 'wx/reports/yearly_average.html' - - -class StationVariableStationViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - queryset = StationVariable.objects.values('station__id', 'station__name', 'station__code').distinct('station__id') - serializer_class = serializers.ReducedStationSerializer - - def get_queryset(self): - queryset = StationVariable.objects.values('station__id', 'station__name', 'station__code').distinct( - 'station__id') - - variable_id = self.request.query_params.get('variable_id', None) - - if variable_id is not None: - queryset = queryset.filter(variable__id=variable_id) - - return queryset - -def last24_summary_list(request): - search_type = request.GET.get('search_type', None) - search_value = request.GET.get('search_value', None) - - response = { - 'results': [], - 'messages': [], - } - - if search_type is not None and search_type == 'variable': - query = """ - SELECT last24h.datetime, - var.name, - var.symbol, - var.sampling_operation_id, - unit.name, - unit.symbol, - last24h.min_value, - last24h.max_value, - last24h.avg_value, - last24h.sum_value, - last24h.latest_value, - last24h.station_id, - last24h.num_records - FROM last24h_summary last24h - JOIN wx_variable var ON last24h.variable_id=var.id - JOIN wx_unit unit ON var.unit_id=unit.id - WHERE last24h.variable_id=%s - """ - - with connection.cursor() as cursor: - - cursor.execute(query, [search_value]) - rows = cursor.fetchall() - - for row in rows: - - value = None - - if row[3] == 1: - value = row[10] - - elif row[3] == 2: - value = row[8] - - elif row[3] == 3: - value = row[6] - - elif row[3] == 4: - value = row[7] - - elif row[3] == 6: - value = row[9] - - if value is None: - print('variable {} does not have supported sampling operation {}'.format(row[1], row[3])) - - obj = { - 'station': row[11], - 'value': value, - 'min': round(row[6], 2), - 'max': round(row[7], 2), - 'avg': round(row[8], 2), - 'sum': round(row[9], 2), - 'count': row[12], - 'variable': { - 'name': row[1], - 'symbol': row[2], - 'unit_name': row[4], - 'unit_symbol': row[5] - } - } - response['results'].append(obj) - - if response['results']: - return JsonResponse(response, status=status.HTTP_200_OK) - - return JsonResponse(data={"message": "No data found."}, status=status.HTTP_404_NOT_FOUND) - - -def query_stationsmonintoring_chart(station_id, variable_id, data_type, datetime_picked): - station = Station.objects.get(id=station_id) - variable = Variable.objects.get(id=variable_id) - - date_start = str((datetime_picked - datetime.timedelta(days=6)).date()) - date_end = str(datetime_picked.date()) - - if data_type=='Communication': - query = """ - WITH - date_range AS ( - SELECT GENERATE_SERIES(%s::DATE - '6 day'::INTERVAL, %s::DATE, '1 day')::DATE AS date - ), - hs AS ( - SELECT - datetime::date AS date, - COUNT(DISTINCT EXTRACT(hour FROM datetime)) AS amount - FROM - hourly_summary - WHERE - datetime >= %s::DATE - '7 day'::INTERVAL AND datetime < %s::DATE + '1 day'::INTERVAL - AND station_id = %s - AND variable_id = %s - GROUP BY 1 - ) - SELECT - date_range.date, - COALESCE(hs.amount, 0) AS amount, - COALESCE(( - SELECT color FROM wx_qualityflag - WHERE - CASE - WHEN COALESCE(hs.amount, 0) >= 20 THEN name = 'Good' - WHEN COALESCE(hs.amount, 0) >= 8 AND COALESCE(hs.amount, 0) <= 19 THEN name = 'Suspicious' - WHEN COALESCE(hs.amount, 0) >= 1 AND COALESCE(hs.amount, 0) <= 7 THEN name = 'Bad' - ELSE name = 'Not checked' - END - ), '') AS color - FROM - date_range - LEFT JOIN hs ON date_range.date = hs.date - ORDER BY date_range.date; - """ - - with connection.cursor() as cursor: - cursor.execute(query, (datetime_picked, datetime_picked, datetime_picked, datetime_picked, station_id, variable_id,)) - results = cursor.fetchall() - - chart_options = { - 'chart': { - 'type': 'column' - }, - 'title': { - 'text': " ".join(['Delay Data Track -',date_start,'to',date_end]) - }, - 'subtitle': { - 'text': " ".join([station.name, station.code, '-', variable.name]) - }, - 'xAxis': { - 'categories': [r[0] for r in results] - }, - 'yAxis': { - 'title': None, - 'categories': [str(i)+'h' for i in range(25)], - 'tickInterval': 2, - 'min': 0, - 'max': 24, - }, - 'series': [ - { - 'name': 'Max comunication', - 'data': [{'y': r[1], 'color': r[2]} for r in results], - 'showInLegend': False - } - ], - 'plotOptions': { - 'column': { - 'minPointLength': 10, - 'pointPadding': 0.01, - 'groupPadding': 0.05 - } - } - } - - elif data_type=='Quality Control': - flags = { - 'good': QualityFlag.objects.get(name='Good').color, - 'suspicious': QualityFlag.objects.get(name='Suspicious').color, - 'bad': QualityFlag.objects.get(name='Bad').color, - 'not_checked': QualityFlag.objects.get(name='Not checked').color, - } - - query = """ - WITH - date_range AS ( - SELECT GENERATE_SERIES(%s::DATE - '6 day'::INTERVAL, %s::DATE, '1 day')::DATE AS date - ), - hs AS( - SELECT - rd.datetime::DATE AS date - ,EXTRACT(hour FROM rd.datetime) AS hour - ,CASE - WHEN COUNT(CASE WHEN name='Bad' THEN 1 END) > 0 THEN('Bad') - WHEN COUNT(CASE WHEN name='Suspicious' THEN 1 END) > 0 THEN('Suspicious') - WHEN COUNT(CASE WHEN name='Good' THEN 1 END) > 0 THEN('Good') - ELSE ('Not checked') - END AS quality_flag - FROM raw_data AS rd - LEFT JOIN wx_qualityflag qf ON rd.quality_flag = qf.id - WHERE - datetime >= %s::DATE - '7 day'::INTERVAL AND datetime < %s::DATE + '1 day'::INTERVAL - AND rd.station_id = %s - AND rd.variable_id = %s - GROUP BY 1,2 - ORDER BY 1,2 - ) - SELECT - date_range.date - ,COUNT(CASE WHEN hs.quality_flag='Good' THEN 1 END) AS good - ,COUNT(CASE WHEN hs.quality_flag='Suspicious' THEN 1 END) AS suspicious - ,COUNT(CASE WHEN hs.quality_flag='Bad' THEN 1 END) AS bad - ,COUNT(CASE WHEN hs.quality_flag='Not checked' THEN 1 END) AS not_checked - FROM date_range - LEFT JOIN hs ON date_range.date = hs.date - GROUP BY 1 - ORDER BY 1; - """ - - with connection.cursor() as cursor: - cursor.execute(query, (datetime_picked, datetime_picked, datetime_picked, datetime_picked, station_id, variable_id,)) - results = cursor.fetchall() - - series = [] - for i, flag in enumerate(flags): - data = [r[i+1] for r in results] - series.append({'name': flag.capitalize(), 'data': data, 'color': flags[flag]}) - - chart_options = { - 'chart': { - 'type': 'column' - }, - 'title': { - 'text': " ".join(['Amount of Flags - ',date_start,'to',date_end]) - }, - 'subtitle': { - 'text': " ".join([station.name, station.code, '-', variable.name]) - }, - 'xAxis': { - 'categories': [r[0] for r in results] - }, - 'yAxis': { - 'title': None, - 'categories': [str(i)+'h' for i in range(25)], - 'tickInterval': 2, - 'min': 0, - 'max': 24, - }, - 'series': series, - 'plotOptions': { - 'column': { - 'minPointLength': 10, - 'pointPadding': 0.01, - 'groupPadding': 0.05 - } - } - } - - return chart_options - - -@require_http_methods(["GET"]) -def get_stationsmonitoring_chart_data(request, station_id, variable_id): - time_type = request.GET.get('time_type', 'Last 24h') - data_type = request.GET.get('data_type', 'Communication') - date_picked = request.GET.get('date_picked', None) - - if time_type=='Last 24h': - datetime_picked = datetime.datetime.now() - else: - datetime_picked = datetime.datetime.strptime(date_picked, '%Y-%m-%d') - - # Fix a date to test - # datetime_picked = datetime.datetime.strptime('2023-01-01', '%Y-%m-%d') - - chart_data = query_stationsmonintoring_chart(station_id, variable_id, data_type, datetime_picked) - - response = { - "chartOptions": chart_data - } - - return JsonResponse(response, status=status.HTTP_200_OK) - - -def get_station_lastupdate(station_id): - stationvariables = StationVariable.objects.filter(station_id=station_id) - - last_data_datetimes = [sv.last_data_datetime for sv in stationvariables if sv.last_data_datetime is not None] - - if last_data_datetimes: - lastupdate = max(last_data_datetimes) - lastupdate = lastupdate.strftime("%Y-%m-%d %H:%M") - else: - lastupdate = None - - return lastupdate - - -def query_stationsmonitoring_station(data_type, time_type, date_picked, station_id): - if time_type=='Last 24h': - datetime_picked = datetime.datetime.now() - else: - datetime_picked = datetime.datetime.strptime(date_picked, '%Y-%m-%d') - - - station_data = [] - - if data_type=='Communication': - query = """ - WITH hs AS ( - SELECT - station_id, - variable_id, - COUNT(DISTINCT EXTRACT(hour FROM datetime)) AS number_hours - FROM - hourly_summary - WHERE - datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL AND station_id = %s - GROUP BY 1, 2 - ) - SELECT - v.id, - v.name, - hs.number_hours, - ls.latest_value, - u.symbol, - CASE - WHEN hs.number_hours >= 20 THEN ( - SELECT color FROM wx_qualityflag WHERE name = 'Good' - ) - WHEN hs.number_hours >= 8 AND hs.number_hours <= 19 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' - ) - WHEN hs.number_hours >= 1 AND hs.number_hours <= 7 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Bad' - ) - ELSE ( - SELECT color FROM wx_qualityflag WHERE name = 'Not checked' - ) - END AS color - FROM - wx_stationvariable sv - LEFT JOIN hs ON sv.station_id = hs.station_id AND sv.variable_id = hs.variable_id - LEFT JOIN last24h_summary ls ON sv.station_id = ls.station_id AND sv.variable_id = ls.variable_id - LEFT JOIN wx_variable v ON sv.variable_id = v.id - LEFT JOIN wx_unit u ON v.unit_id = u.id - WHERE - sv.station_id = %s - ORDER BY 1; - """ - - with connection.cursor() as cursor: - cursor.execute(query, (datetime_picked, datetime_picked, station_id, station_id)) - results = cursor.fetchall() - - station_data = [{'id': r[0], - 'name': r[1], - 'amount': r[2] if r[2] is not None else 0, - 'latestvalue': " ".join([str(r[3]), r[4]]) if r[3] is not None else '---', - 'color': r[5]} for r in results] - - elif data_type=='Quality Control': - query = """ - WITH h AS( - SELECT - rd.station_id - ,rd.variable_id - ,EXTRACT(hour FROM rd.datetime) AS hour - ,CASE - WHEN COUNT(CASE WHEN name='Bad' THEN 1 END) > 0 THEN('Bad') - WHEN COUNT(CASE WHEN name='Suspicious' THEN 1 END) > 0 THEN('Suspicious') - WHEN COUNT(CASE WHEN name='Good' THEN 1 END) > 0 THEN('Good') - ELSE ('Not checked') - END AS quality_flag - FROM raw_data AS rd - LEFT JOIN wx_qualityflag qf ON rd.quality_flag = qf.id - WHERE - datetime <= %s - AND datetime >= %s - '24 hour'::INTERVAL - AND rd.station_id = %s - GROUP BY 1,2,3 - ORDER BY 1,2,3 - ) - SELECT - v.id - ,v.name - ,COUNT(CASE WHEN h.quality_flag='Good' THEN 1 END) AS good - ,COUNT(CASE WHEN h.quality_flag='Suspicious' THEN 1 END) AS suspicious - ,COUNT(CASE WHEN h.quality_flag='Bad' THEN 1 END) AS bad - ,COUNT(CASE WHEN h.quality_flag='Not checked' THEN 1 END) AS not_checked - FROM wx_stationvariable AS sv - LEFT JOIN wx_variable AS v ON sv.variable_id = v.id - LEFT JOIN h ON sv.station_id = h.station_id AND sv.variable_id = h.variable_id - WHERE sv.station_id = %s - GROUP BY 1,2 - ORDER BY 1,2 - """ - - with connection.cursor() as cursor: - cursor.execute(query, (datetime_picked, datetime_picked, station_id, station_id)) - results = cursor.fetchall() - - station_data = [{'id': r[0], - 'name': r[1], - 'good': r[2], - 'suspicious': r[3], - 'bad': r[4], - 'not_checked': r[5]} for r in results] - - return station_data - - -@require_http_methods(["GET"]) -def get_stationsmonitoring_station_data(request, id): - data_type = request.GET.get('data_type', 'Communication') - time_type = request.GET.get('time_type', 'Last 24h') - date_picked = request.GET.get('date_picked', None) - - response = { - 'lastupdate': get_station_lastupdate(id), - 'station_data': query_stationsmonitoring_station(data_type, time_type, date_picked, id), - } - - return JsonResponse(response, status=status.HTTP_200_OK) - - -def query_stationsmonitoring_map(data_type, time_type, date_picked): - if time_type=='Last 24h': - datetime_picked = datetime.datetime.now() - else: - datetime_picked = datetime.datetime.strptime(date_picked, '%Y-%m-%d') - - results = [] - - if time_type=='Last 24h': - if data_type=='Communication': - query = """ - WITH hs AS ( - SELECT - station_id - ,variable_id - ,COUNT(DISTINCT EXTRACT(hour FROM datetime)) AS number_hours - FROM - hourly_summary - WHERE - datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL - GROUP BY 1, 2 - ) - SELECT - s.id - ,s.name - ,s.code - ,s.latitude - ,s.longitude - ,CASE - WHEN MAX(number_hours) >= 20 THEN ( - SELECT color FROM wx_qualityflag WHERE name = 'Good' - ) - WHEN MAX(number_hours) >= 8 AND MAX(number_hours) <= 19 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' - ) - WHEN MAX(number_hours) >= 1 AND MAX(number_hours) <= 7 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Bad' - ) - ELSE ( - SELECT color FROM wx_qualityflag WHERE name = 'Not checked' - ) - END AS color - FROM wx_station AS s - LEFT JOIN wx_stationvariable AS sv ON s.id = sv.station_id - LEFT JOIN hs ON sv.station_id = hs.station_id AND sv.variable_id = hs.variable_id - WHERE s.is_active - GROUP BY 1, 2, 3, 4, 5; - """ - elif data_type=='Quality Control': - query = """ - WITH qf AS ( - SELECT - station_id - ,CASE - WHEN COUNT(CASE WHEN name='Bad' THEN 1 END) > 0 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Bad' - ) - WHEN COUNT(CASE WHEN name='Suspicious' THEN 1 END) > 0 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' - ) - WHEN COUNT(CASE WHEN name='Good' THEN 1 END) > 0 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Good' - ) - ELSE ( - SELECT color FROM wx_qualityflag WHERE name = 'Not checked' - ) - END AS color - FROM - raw_data AS rd - LEFT JOIN wx_qualityflag AS qf ON rd.quality_flag = qf.id - WHERE - datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL - GROUP BY 1 - ) - SELECT - s.id - ,s.name - ,s.code - ,s.latitude - ,s.longitude - ,COALESCE(qf.color, (SELECT color FROM wx_qualityflag WHERE name = 'Not checked')) AS color - FROM wx_station AS s - LEFT JOIN qf ON s.id = qf.station_id - WHERE s.is_active - """ - - if data_type in ['Communication', 'Quality Control']: - with connection.cursor() as cursor: - cursor.execute(query, (datetime_picked, datetime_picked, )) - results = cursor.fetchall() - else: - if data_type=='Communication': - query = """ - WITH hs AS ( - SELECT - station_id - ,variable_id - ,COUNT(DISTINCT EXTRACT(hour FROM datetime)) AS number_hours - FROM - hourly_summary - WHERE - datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL - GROUP BY 1, 2 - ) - SELECT - s.id - ,s.name - ,s.code - ,s.latitude - ,s.longitude - ,CASE - WHEN MAX(number_hours) >= 20 THEN ( - SELECT color FROM wx_qualityflag WHERE name = 'Good' - ) - WHEN MAX(number_hours) >= 8 AND MAX(number_hours) <= 19 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' - ) - WHEN MAX(number_hours) >= 1 AND MAX(number_hours) <= 7 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Bad' - ) - ELSE ( - SELECT color FROM wx_qualityflag WHERE name = 'Not checked' - ) - END AS color - FROM wx_station AS s - LEFT JOIN wx_stationvariable AS sv ON s.id = sv.station_id - LEFT JOIN hs ON sv.station_id = hs.station_id AND sv.variable_id = hs.variable_id - WHERE s.begin_date <= %s AND (s.end_date IS NULL OR s.end_date >= %s) - GROUP BY 1, 2, 3, 4, 5; - """ - elif data_type=='Quality Control': - query = """ - WITH qf AS ( - SELECT - station_id - ,CASE - WHEN COUNT(CASE WHEN name='Bad' THEN 1 END) > 0 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Bad' - ) - WHEN COUNT(CASE WHEN name='Suspicious' THEN 1 END) > 0 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Suspicious' - ) - WHEN COUNT(CASE WHEN name='Good' THEN 1 END) > 0 THEN( - SELECT color FROM wx_qualityflag WHERE name = 'Good' - ) - ELSE ( - SELECT color FROM wx_qualityflag WHERE name = 'Not checked' - ) - END AS color - FROM - raw_data AS rd - LEFT JOIN wx_qualityflag AS qf ON rd.quality_flag = qf.id - WHERE - datetime <= %s AND datetime >= %s - '24 hour'::INTERVAL - GROUP BY 1 - ) - SELECT - s.id - ,s.name - ,s.code - ,s.latitude - ,s.longitude - ,COALESCE(qf.color, (SELECT color FROM wx_qualityflag WHERE name = 'Not checked')) AS color - FROM wx_station AS s - LEFT JOIN qf ON s.id = qf.station_id - WHERE s.begin_date <= %s AND (s.end_date IS NULL OR s.end_date >= %s) - """ - - if data_type in ['Communication', 'Quality Control']: - with connection.cursor() as cursor: - cursor.execute(query, (datetime_picked, datetime_picked, datetime_picked, datetime_picked, )) - results = cursor.fetchall() - - return results - - -@require_http_methods(["GET"]) -def get_stationsmonitoring_map_data(request): - time_type = request.GET.get('time_type', 'Last 24h') - data_type = request.GET.get('data_type', 'Communication') - date_picked = request.GET.get('date_picked', None) - - results = query_stationsmonitoring_map(data_type, time_type, date_picked) - - response = { - 'stations': [{'id': r[0], - 'name': r[1], - 'code': r[2], - 'position': [r[3], r[4]], - 'color': r[5]} for r in results ], - } - - return JsonResponse(response, status=status.HTTP_200_OK) - - -def stationsmonitoring_form(request): - template = loader.get_template('wx/stations/stations_monitoring.html') - - flags = { - 'good': QualityFlag.objects.get(name='Good').color, - 'suspicious': QualityFlag.objects.get(name='Suspicious').color, - 'bad': QualityFlag.objects.get(name='Bad').color, - 'not_checked': QualityFlag.objects.get(name='Not checked').color, - } - - context = {'flags': flags} - - return HttpResponse(template.render(context, request)) - - -class ComingSoonView(LoginRequiredMixin, TemplateView): - template_name = "coming-soon.html" - - -def get_wave_data_analysis(request): - template = loader.get_template('wx/products/wave_data.html') - - - variable = Variable.objects.get(name="Sea Level") # Sea Level - station_ids = HighFrequencyData.objects.filter(variable_id=variable.id).values('station_id').distinct() - - station_list = Station.objects.filter(id__in=station_ids) - - context = {'station_list': station_list} - - return HttpResponse(template.render(context, request)) - -def format_wave_data_var(variable_id, data): - variable = Variable.objects.get(id=variable_id) - measurement_variable = MeasurementVariable.objects.get(id=variable.measurement_variable_id) - unit = Unit.objects.get(id=variable.unit_id) - - formated_data = [] - for entry in data: - if type(entry) is not dict: - entry = entry.__dict__ - - formated_entry = { - "station": entry['station_id'], - "date": entry['datetime'].timestamp()*1000, - "measurementvariable": measurement_variable.name, - "value": entry['measured'], - "quality_flag": "Not checked", - "flag_color": "#FFFFFF", - } - formated_data.append(formated_entry) - - final_data = { - "color": variable.color, - "default_representation": variable.default_representation, - "data": formated_data, - "unit": unit.symbol, - } - return final_data - -def get_wave_components(data_slice, component_number): - wave_list = fft_decompose(data_slice) - wave_list.sort(key=lambda W: abs(W.height), reverse=True) - wave_components = wave_list[:component_number] - return wave_components - -def get_wave_component_ref_variables(i): - SYSTEM_COMPONENT_NUMBER = 5 # Number of wave components in the system - - ref_number = i % SYSTEM_COMPONENT_NUMBER - ref_number += 1 - - amp_ref_name = 'Wave Component ' + str(ref_number) + ' Amplitude' - amp_ref = Variable.objects.get(name=amp_ref_name) - - frq_ref_name = 'Wave Component ' + str(ref_number) + ' Frequency' - frq_ref = Variable.objects.get(name=frq_ref_name) - - pha_ref_name = 'Wave Component ' + str(ref_number) + ' Phase' - pha_ref = Variable.objects.get(name=pha_ref_name) - - return amp_ref, frq_ref, pha_ref - -def get_wave_component_name_and_symbol(i, component_type): - if component_type=='Amplitude': - name = 'Wave Component ' + str(i) + ' Amplitude' - symbol = 'WV'+str(i)+'AMP' - elif component_type=='Frequency': - name = 'Wave Component ' + str(i) + ' Frequency' - symbol = 'WV'+str(i)+'FRQ' - elif component_type=='Phase': - name = 'Wave Component ' + str(i) + ' Phase' - symbol = 'WV'+str(i)+'PHA' - else: - name = 'Component Type Error' - symbol = 'Component Type Error' - - return name, symbol - -def create_aggregated_data(component_number): - wv_amp_mv = MeasurementVariable.objects.get(name='Wave Amplitude') - wv_frq_mv = MeasurementVariable.objects.get(name='Wave Frequency') - wv_pha_mv = MeasurementVariable.objects.get(name='Wave Phase') - sl_mv = MeasurementVariable.objects.get(name='Sea Level') - - sl_min = Variable.objects.get(name = 'Sea Level [MIN]') - sl_max = Variable.objects.get(name = 'Sea Level [MAX]') - sl_avg = Variable.objects.get(name = 'Sea Level [AVG]') - sl_std = Variable.objects.get(name = 'Sea Level [STDV]') - sl_swh = Variable.objects.get(name = 'Significant Wave Height') - - sl_variables = [sl_min, sl_max, sl_avg, sl_std, sl_swh] - - - aggregated_data = { - wv_amp_mv.name: {}, - wv_frq_mv.name: {}, - wv_pha_mv.name: {}, - sl_mv.name: {} - } - - for sl_variable in sl_variables: - aggregated_data[sl_mv.name][sl_variable.name] = { - 'ref_variable_id': sl_variable.id, - 'symbol': sl_variable.symbol, - 'data': [] - } - - for i in range(component_number): - amp_ref, frq_ref, pha_ref = get_wave_component_ref_variables(i) - - amp_name, amp_symbol = get_wave_component_name_and_symbol(i+1, 'Amplitude') - frq_name, frq_symbol = get_wave_component_name_and_symbol(i+1, 'Frequency') - pha_name, pha_symbol = get_wave_component_name_and_symbol(i+1, 'Phase') - - aggregated_data[wv_amp_mv.name][amp_name] = { - 'ref_variable_id': amp_ref.id, - 'symbol': amp_symbol, - 'data': [] - } - aggregated_data[wv_frq_mv.name][frq_name] = { - 'ref_variable_id': frq_ref.id, - 'symbol': frq_symbol, - 'data': [] - } - aggregated_data[wv_pha_mv.name][pha_name] = { - 'ref_variable_id': pha_ref.id, - 'symbol': pha_symbol, - 'data': [] - } - - return aggregated_data - -def append_in_aggregated_data(aggregated_data, datetime, station_id, mv_name, var_name, value): - entry = { - 'measured': value, - 'datetime': datetime, - 'station_id': station_id, - } - - aggregated_data[mv_name][var_name]['data'].append(entry) - - return aggregated_data - -def get_wave_aggregated_data(station_id, data, initial_datetime, range_interval, calc_interval, component_number): - wv_amp_mv = MeasurementVariable.objects.get(name='Wave Amplitude') - wv_frq_mv = MeasurementVariable.objects.get(name='Wave Frequency') - wv_pha_mv = MeasurementVariable.objects.get(name='Wave Phase') - sl_mv = MeasurementVariable.objects.get(name='Sea Level') - - sl_min = Variable.objects.get(name = 'Sea Level [MIN]') - sl_max = Variable.objects.get(name = 'Sea Level [MAX]') - sl_avg = Variable.objects.get(name = 'Sea Level [AVG]') - sl_std = Variable.objects.get(name = 'Sea Level [STDV]') - sl_swh = Variable.objects.get(name = 'Significant Wave Height') - - aggregated_data = create_aggregated_data(component_number) - - for i in range(math.floor(range_interval/calc_interval)): - ini_datetime_slc = initial_datetime+datetime.timedelta(minutes=i*calc_interval) - end_datetime_slc = initial_datetime+datetime.timedelta(minutes=(i+1)*calc_interval) - - data_slice = [entry.measured for entry in data if ini_datetime_slc < entry.datetime <= end_datetime_slc] - - if len(data_slice) > 0: - aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_min.name, np.min(data_slice)) - aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_max.name, np.max(data_slice)) - aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_avg.name, np.mean(data_slice)) - aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_std.name, np.std(data_slice)) - aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, sl_mv.name, sl_swh.name, 4*np.std(data_slice)) - - wave_components = get_wave_components(data_slice, component_number) - for j, wave_component in enumerate(wave_components): - amp_name, amp_symbol = get_wave_component_name_and_symbol(j+1, 'Amplitude') - frq_name, frq_symbol = get_wave_component_name_and_symbol(j+1, 'Frequency') - pha_name, pha_symbol = get_wave_component_name_and_symbol(j+1, 'Phase') - - amp_value = wave_component.height - frq_value = wave_component.frequency - pha_value = math.degrees(wave_component.phase_rad) % 360 - - aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, wv_amp_mv.name, amp_name, amp_value) - aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, wv_frq_mv.name, frq_name, frq_value) - aggregated_data = append_in_aggregated_data(aggregated_data, end_datetime_slc, station_id, wv_pha_mv.name, pha_name, pha_value) - - return aggregated_data - -def add_wave_aggregated_data(dataset, aggregated_data): - for mv_name in aggregated_data.keys(): - dataset['results'][mv_name] = {} - for var_name in aggregated_data[mv_name].keys(): - variable_id = aggregated_data[mv_name][var_name]['ref_variable_id'] - variable_data = aggregated_data[mv_name][var_name]['data'] - variable_symbol = aggregated_data[mv_name][var_name]['symbol'] - - dataset['results'][mv_name][variable_symbol] = format_wave_data_var(variable_id, variable_data) - - return dataset - -def create_wave_dataset(station_id, sea_data, initial_datetime, range_interval, calc_interval, component_number): - sea_level = Variable.objects.get(name='Sea Level') - sea_level_mv = MeasurementVariable.objects.get(name='Sea Level') - - dataset = { - "results": { - sea_level_mv.name+' Raw': { - sea_level.symbol: format_wave_data_var(sea_level.id, sea_data), - } - }, - "messages": [], - } - - wave_component_data = get_wave_aggregated_data(station_id, - sea_data, - initial_datetime, - range_interval, - calc_interval, - component_number) - - - dataset = add_wave_aggregated_data(dataset, wave_component_data) - - return dataset - -def create_wave_chart(dataset): - charts = {} - - for element_name, element_data in dataset['results'].items(): - - chart = { - 'chart': { - 'type': 'pie', - 'zoomType': 'xy' - }, - 'title': {'text': element_name}, - 'xAxis': { - 'type': 'datetime', - 'dateTimeLabelFormats': { - 'month': '%e. %b', - 'year': '%b' - }, - 'title': { - 'text': 'Date' - } - }, - 'yAxis': [], - 'exporting': { - 'showTable': True - }, - 'series': [] - } - - opposite = False - y_axis_unit_dict = {} - for variable_name, variable_data in element_data.items(): - current_unit = variable_data['unit'] - if current_unit not in y_axis_unit_dict.keys(): - chart['yAxis'].append({ - 'labels': { - 'format': '{value} ' + variable_data['unit'] - }, - 'title': { - 'text': None - }, - 'opposite': opposite - }) - y_axis_unit_dict[current_unit] = len(chart['yAxis']) - 1 - opposite = not opposite - - current_y_axis_index = y_axis_unit_dict[current_unit] - data = [] - for record in variable_data['data']: - data.append({ - 'x': record['date'], - 'y': record['value'], - }) - - chart['series'].append({ - 'name': variable_name, - 'color': variable_data['color'], - 'type': variable_data['default_representation'], - 'unit': variable_data['unit'], - 'data': data, - 'yAxis': current_y_axis_index - }) - chart['chart']['type'] = variable_data['default_representation'], - - charts[slugify(element_name)] = chart - - return charts - -@require_http_methods(["GET"]) -def get_wave_data(request): - station_id = request.GET.get('station_id', None) - initial_date = request.GET.get('initial_date', None) - initial_time = request.GET.get('initial_time', None) - range_interval = request.GET.get('range_interval', None) - calc_interval = request.GET.get('calc_interval', None) - component_number = request.GET.get('component_number', None) - - tz_client = request.GET.get('tz_client', None) - tz_settings = pytz.timezone(settings.TIMEZONE_NAME) - - initial_datetime_str = initial_date+' '+initial_time - initial_datetime = datetime_constructor.strptime(initial_datetime_str, '%Y-%m-%d %H:%M') - initial_datetime = pytz.timezone(tz_client).localize(initial_datetime) - initial_datetime = initial_datetime.astimezone(tz_settings) - - range_intervals = {'30min': 30, "1h": 60, "3h": 180,} - - if range_interval in range_intervals.keys(): - range_interval = range_intervals[range_interval] - else: - response = {"message": "Not valid interval."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - calc_intervals = {'1min': 1, '5min': 5, '10min': 10, '15min': 15,} - - if calc_interval in calc_intervals.keys(): - calc_interval = calc_intervals[calc_interval] - else: - response = {"message": "Not valid calc interval."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - station_id = int(station_id) - component_number = int(component_number) - - final_datetime = initial_datetime + datetime.timedelta(minutes=range_interval) - - variable = Variable.objects.get(name="Sea Level") # Sea Level - sea_data = HighFrequencyData.objects.filter(variable_id=variable.id, - station_id=station_id, - datetime__gt=initial_datetime, - datetime__lte=final_datetime).order_by('datetime') - - dataset = {"results": {}, "messages": []} - - if len(sea_data) > 0: - dataset = create_wave_dataset(station_id, sea_data, initial_datetime, - range_interval, calc_interval, component_number) - - charts = create_wave_chart(dataset) - - return JsonResponse(charts) - - -from wx.models import Equipment, EquipmentType, Manufacturer, FundingSource -from django.core.serializers import serialize - - -@require_http_methods(["GET"]) -def get_equipment_inventory(request): - template = loader.get_template('wx/maintenance_reports/equipment_inventory.html') - context = {} - - return HttpResponse(template.render(context, request)) - - -def get_value(variable): - if variable is None: - return '---' - return variable - -def equipment_condition(condition): - if condition == 'F': - return 'Fully Functional' - elif condition == 'P': - return 'Partially Functional' - elif condition == 'N': - return 'Not Functional' - return None - -@require_http_methods(["GET"]) -def get_equipment_inventory_data(request): - equipment_types = EquipmentType.objects.all() - manufacturers = Manufacturer.objects.all() - equipments = Equipment.objects.all() - funding_sources = FundingSource.objects.all() - stations = Station.objects.all() - - equipment_list = [] - for equipment in equipments: - try: - equipment_type = equipment_types.get(id=equipment.equipment_type_id) - funding_source = funding_sources.get(id=equipment.funding_source_id) - manufacturer = manufacturers.get(id=equipment.manufacturer_id) - - station = None - if equipment.location_id: - station = stations.get(id=equipment.location_id) - - equipment_dict = { - 'equipment_id': equipment.id, - 'equipment_type': equipment_type.name, - 'equipment_type_id': equipment_type.id, - 'funding_source': funding_source.name, - 'funding_source_id': funding_source.id, - 'manufacturer': manufacturer.name, - 'manufacturer_id': manufacturer.id, - 'model': equipment.model, - 'serial_number': equipment.serial_number, - 'acquisition_date': equipment.acquisition_date, - 'first_deploy_date': equipment.first_deploy_date, - 'last_calibration_date': equipment.last_calibration_date, - 'next_calibration_date': equipment.next_calibration_date, - 'decommission_date': equipment.decommission_date, - 'last_deploy_date': equipment.last_deploy_date, - 'location': f"{station.name} - {station.code}" if station else 'Office', - 'location_id': station.id if station else None, - 'condition': equipment_condition(equipment.condition), - 'condition_id': equipment.condition, - } - equipment_list.append(equipment_dict) - except ObjectDoesNotExist: - pass - - equipment_conditions = [ - {'name': 'Fully Functional', 'id': 'F'}, - {'name': 'Partially Functional', 'id': 'P'}, - {'name': 'Not Functional', 'id': 'N'}, - ] - - station_list = [{'name': f"{station.name} - {station.code}", - 'id': station.id} for station in stations] - - response = { - 'equipments': equipment_list, - 'equipment_types': list(equipment_types.values()), - 'manufacturers': list(manufacturers.values()), - 'funding_sources': list(funding_sources.values()), - 'stations': station_list, - 'equipment_conditions': equipment_conditions, - } - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def create_equipment(request): - equipment_type_id = request.GET.get('equipment_type', None) - manufacturer_id = request.GET.get('manufacturer', None) - funding_source_id = request.GET.get('funding_source', None) - model = request.GET.get('model', None) - serial_number = request.GET.get('serial_number', None) - acquisition_date = request.GET.get('acquisition_date', None) - first_deploy_date = request.GET.get('first_deploy_date', None) - last_calibration_date = request.GET.get('last_calibration_date', None) - next_calibration_date = request.GET.get('next_calibration_date', None) - decommission_date = request.GET.get('decommission_date', None) - location_id = request.GET.get('location', None) - condition_id = request.GET.get('condition', None) - last_deploy_date = request.GET.get('last_deploy_date', None) - - equipment_type = EquipmentType.objects.get(id=equipment_type_id) - manufacturer = Manufacturer.objects.get(id=manufacturer_id) - funding_source = FundingSource.objects.get(id=funding_source_id) - - location = None - if location_id: - location = Station.objects.get(id=location_id) - - try: - equipment = Equipment.objects.get( - equipment_type=equipment_type, - serial_number = serial_number, - ) - - message = 'Already exist an equipment of equipment type ' - message += equipment_type.name - message += ' and serial number ' - message += equipment.serial_number - - response = {'message': message} - - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - except ObjectDoesNotExist: - now = datetime.datetime.now() - - equipment = Equipment.objects.create( - created_at = now, - updated_at = now, - equipment_type = equipment_type, - manufacturer = manufacturer, - funding_source = funding_source, - model = model, - serial_number = serial_number, - acquisition_date = acquisition_date, - first_deploy_date = first_deploy_date, - last_calibration_date = last_calibration_date, - next_calibration_date = next_calibration_date, - decommission_date = decommission_date, - location = location, - condition = condition, - last_deploy_date = last_deploy_date, - ) - - response = {'equipment_id': equipment.id} - - return JsonResponse(response, status=status.HTTP_200_OK) - -@require_http_methods(["POST"]) -def update_equipment(request): - equipment_id = request.GET.get('equipment_id', None) - equipment_type_id = request.GET.get('equipment_type', None) - manufacturer_id = request.GET.get('manufacturer', None) - funding_source_id = request.GET.get('funding_source', None) - model = request.GET.get('model', None) - serial_number = request.GET.get('serial_number', None) - acquisition_date = request.GET.get('acquisition_date', None) - first_deploy_date = request.GET.get('first_deploy_date', None) - last_calibration_date = request.GET.get('last_calibration_date', None) - next_calibration_date = request.GET.get('next_calibration_date', None) - decommission_date = request.GET.get('decommission_date', None) - location_id = request.GET.get('location', None) - condition = request.GET.get('condition', None) - last_deploy_date = request.GET.get('last_deploy_date', None) - - equipment_type = EquipmentType.objects.get(id=equipment_type_id) - manufacturer = Manufacturer.objects.get(id=manufacturer_id) - funding_source = FundingSource.objects.get(id=funding_source_id) - - location = None - if location_id: - location = Station.objects.get(id=location_id) - - try: - equipment = Equipment.objects.get(equipment_type=equipment_type, serial_number=serial_number) - - if int(equipment_id) != equipment.id: - message = f"Could not update. Already exist an equipment of \ - equipment type {equipment_type.name} and serial \ - number {equipment.serial_number}" - - response = {'message': message} - - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - except ObjectDoesNotExist: - pass - - try: - equipment = Equipment.objects.get(id=equipment_id) - - now = datetime.datetime.now() - - equipment.updated_at = now - equipment.equipment_type = equipment_type - equipment.manufacturer = manufacturer - equipment.funding_source = funding_source - equipment.model = model - equipment.serial_number = serial_number - equipment.acquisition_date = acquisition_date - equipment.first_deploy_date = first_deploy_date - equipment.last_calibration_date = last_calibration_date - equipment.next_calibration_date = next_calibration_date - equipment.decommission_date = decommission_date - equipment.location = location - equipment.condition = condition - equipment.last_deploy_date = last_deploy_date - equipment.save() - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - except ObjectDoesNotExist: - message = "Object not found" - response = {'message': message} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def delete_equipment(request): - equipment_id = request.GET.get('equipment_id', None) - try: - equipment = Equipment.objects.get(id=equipment_id) - equipment.delete() - except ObjectDoesNotExist: - pass - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -################################################################ -################################################################ - -############################# List ############################# - -@require_http_methods(["GET"]) -def get_maintenance_reports(request): # Maintenance report page - template = loader.get_template('wx/maintenance_reports/maintenance_reports.html') - context = {} - return HttpResponse(template.render(context, request)) - - -@require_http_methods(["PUT"]) -def get_maintenance_report_list(request): - form_data = json.loads(request.body.decode()) - - maintenance_reports = MaintenanceReport.objects.filter(visit_date__gte = form_data['start_date'], visit_date__lte = form_data['end_date']) - - response = {} - response['maintenance_report_list'] = [] - - for maintenance_report in maintenance_reports: - if maintenance_report.status != '-': - station, station_profile, technician, visit_type = get_maintenance_report_obj(maintenance_report) - - if station.is_automatic == form_data['is_automatic']: - if maintenance_report.status == 'A': - maintenance_report_status = 'Approved' - elif maintenance_report.status == 'P': - maintenance_report_status = 'Published' - else: - maintenance_report_status = 'Draft' - - maintenance_report_object = { - 'maintenance_report_id': maintenance_report.id, - 'station_name': station.name, - 'station_profile': station_profile.name, - 'station_type': 'Automatic' if station.is_automatic else 'Manual', - 'visit_date': maintenance_report.visit_date, - 'next_visit_date': maintenance_report.next_visit_date, - 'technician': technician.name, - 'type_of_visit': visit_type.name, - 'status': maintenance_report_status, - } - - response['maintenance_report_list'].append(maintenance_report_object) - - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["GET"]) # Update maintenance report from existing report -def update_maintenance_report(request, id): - maintenance_report = MaintenanceReport.objects.get(id=id) - if maintenance_report.status == 'A': - response={'message': "Approved reports can not be editable."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - elif maintenance_report.status == '-': - response={'message': "This report is deleated."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - template = loader.get_template('wx/maintenance_reports/new_report.html') - - context = {} - context['station_list'] = Station.objects.select_related('profile').all() - context['visit_type_list'] = VisitType.objects.all() - context['technician_list'] = Technician.objects.all() - - context['maintenance_report_id'] = id - - return HttpResponse(template.render(context, request)) - - -@require_http_methods(["PUT"]) -def delete_maintenance_report(request, id): - now = datetime.datetime.now() - - maintenance_report = MaintenanceReport.objects.get(id=id) - maintenance_report.status = '-' - maintenance_report.save() - - response={} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["PUT"]) -def approve_maintenance_report(request, id): - now = datetime.datetime.now() - - maintenance_report = MaintenanceReport.objects.get(id=id) - maintenance_report.status = 'A' - maintenance_report.save() - - response={} - return JsonResponse(response, status=status.HTTP_200_OK) - -############################# View ############################# - -@require_http_methods(["GET"]) -def get_maintenance_report_view(request, id, source): # Maintenance report view - template = loader.get_template('wx/maintenance_reports/view_report.html') - - maintenance_report = MaintenanceReport.objects.get(id=id) - - station = Station.objects.get(pk=maintenance_report.station_id) - profile = StationProfile.objects.get(pk=station.profile_id) - responsible_technician = Technician.objects.get(pk=maintenance_report.responsible_technician_id) - visit_type = VisitType.objects.get(pk=maintenance_report.visit_type_id) - maintenance_report_station_components = MaintenanceReportStationComponent.objects.filter(maintenance_report_id=maintenance_report.id) - - # maintenance_report_station_component_list = [] - # for maintenance_report_station_component in maintenance_report_station_components: - # dictionary = {'condition': maintenance_report_station_component.condition, - # 'component_classification': maintenance_report_station_component.component_classification, - # } - # maintenance_report_station_component_list.append(dictionary) - - maintenance_report_station_component_list = get_component_list(maintenance_report) - - - other_technicians_ids = [maintenance_report.other_technician_1_id, - maintenance_report.other_technician_2_id, - maintenance_report.other_technician_3_id] - other_technicians = [] - for other_technician_id in other_technicians_ids: - if other_technician_id: - other_technician = Technician.objects.get(id=other_technician_id) - other_technicians.append(other_technician.name) - - other_technicians = ", ".join(other_technicians) - - - context = {} - - if source == 0: - context['source'] = 'edit' - else: - context['source'] = 'list' - - context['visit_summary_information'] = { - "report_number": maintenance_report.id, - "responsible_technician": responsible_technician.name, - "date_of_visit": maintenance_report.visit_date, - "date_of_next_visit": maintenance_report.next_visit_date, - "start_time": maintenance_report.initial_time, - "other_technicians": other_technicians, - "end_time": maintenance_report.end_time, - "type_of_visit": visit_type.name, - "station_on_arrival_conditions": maintenance_report.station_on_arrival_conditions, - "current_visit_summary": maintenance_report.current_visit_summary, - "next_visit_summary": maintenance_report.next_visit_summary, - "maintenance_report_status": maintenance_report.status, - } - - context['station_information'] = { - "station_name": station.name, - "station_host_name": "---", - "station_ID": station.code, - "wigos_ID": station.wigos, - "station_type": 'Automatic' if station.is_automatic else 'Manual', - "station_profile": profile.name, - "latitude": station.latitude, - "elevation": station.elevation, - "longitude": station.longitude, - "district": station.region, - "transmission_type": "---", - "transmission_ID": "---", - "transmission_interval": "---", - "measurement_interval": "---", - "data_of_first_operation": station.begin_date, - "data_of_relocation": station.relocation_date, - } - - context['contact_information'] = maintenance_report.contacts - - context['equipment_records'] = maintenance_report_station_component_list - - # JSON - # return JsonResponse(context, status=status.HTTP_200_OK) - - return HttpResponse(template.render(context, request)) - - -############################# New ############################## - -def get_maintenance_report_obj(maintenance_report): - station = Station.objects.get(id=maintenance_report.station_id) - station_profile = StationProfile.objects.get(id=station.profile_id) - technician = Technician.objects.get(id=maintenance_report.responsible_technician_id) - visit_type = VisitType.objects.get(id=maintenance_report.visit_type_id) - - return station, station_profile, technician, visit_type - - -def get_maintenance_report_form(request): # New maintenance report form page - template = loader.get_template('wx/maintenance_reports/new_report.html') - - context = {} - context['station_list'] = Station.objects.select_related('profile').all() - context['visit_type_list'] = VisitType.objects.all() - context['technician_list'] = Technician.objects.all() - - return HttpResponse(template.render(context, request)) - - -def get_station_contacts(station_id): - maintenance_report_list = MaintenanceReport.objects.filter(station_id=station_id).order_by('visit_date') - - for maintenance_report in maintenance_report_list: - if maintenance_report.contacts != '': - return maintenance_report.contacts - - return None - - -@require_http_methods(["POST"]) # Create maintenance report from sratch -def create_maintenance_report(request): - now = datetime.datetime.now() - - form_data = json.loads(request.body.decode()) - - try: - maintenance_report = MaintenanceReport.objects.get(station_id = form_data['station_id'], visit_date = form_data['visit_date']) - - if maintenance_report.status=="-": - maintenance_report.delete() - except ObjectDoesNotExist: - pass - - try: - maintenance_report = MaintenanceReport.objects.get(station_id = form_data['station_id'], visit_date = form_data['visit_date']) - - response = {"message": "Maintenance report already exist for chosen station and date."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - except ObjectDoesNotExist: - maintenance_report = MaintenanceReport.objects.create( - created_at = now, - updated_at = now, - station_id = form_data['station_id'], - responsible_technician_id = form_data['responsible_technician_id'], - visit_type_id = form_data['visit_type_id'], - visit_date = form_data['visit_date'], - initial_time = form_data['initial_time'], - contacts = get_station_contacts(form_data['station_id']), - ) - - station = Station.objects.get(pk=maintenance_report.station_id) - station_profile_component_list = StationProfileComponent.objects.filter(profile_id = station.profile_id) - - for station_profile_component in station_profile_component_list: - station_component = StationComponent.objects.get(id=station_profile_component.station_component_id) - - maintenance_report_station_component = MaintenanceReportStationComponent.objects.create( - maintenance_report_id = maintenance_report.id, - station_component_id = station_component.id, - condition = station_component.report_template, - ) - - response={"maintenance_report_id": maintenance_report.id} - - return JsonResponse(response, status=status.HTTP_200_OK) - - -def get_ckeditor_config(): - ckeditor_config = { - 'toolbar': [ - ['Bold', 'Italic', 'Font'], - ['Format', 'Styles', 'TextColor', 'BGColor', 'RemoveFormat'], - ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock', 'Indent', 'Outdent'], - ['HorizontalRule', 'BulletedList'], - ['Blockquote', 'Source', 'Link', 'Unlink', 'Image', 'Table', 'Print'] - ], - 'removeButtons': 'Image', - 'extraAllowedContent' : 'img(*){*}[*]', - 'editorplaceholder': 'Description of station upon arribal:', - 'language': 'en', - } - return ckeditor_config - -def get_component_list(maintenance_report): - station, station_profile, technician, visit_type = get_maintenance_report_obj(maintenance_report) - - if station.profile_id is not None: - profile = StationProfile.objects.get(pk=station.profile_id) - station_profile_component_list = StationProfileComponent.objects.filter(profile_id=station.profile_id) - - maintenance_report_station_component_list = [] - - for station_profile_component in station_profile_component_list: - station_component = StationComponent.objects.get(id=station_profile_component.station_component_id) - maintenance_report_station_component = MaintenanceReportStationComponent.objects.get(maintenance_report_id=maintenance_report.id, - station_component_id=station_component.id) - - # Need one ckeditor config per text editor, otherwise it could store data in incorrect variables. - dictionary = {'component_id': station_component.id, - 'presentation_order': station_profile_component.presentation_order, - 'component_name': station_component.name, - 'condition': maintenance_report_station_component.condition, - 'component_classification': maintenance_report_station_component.component_classification, - 'ckeditor_config': get_ckeditor_config()} - - maintenance_report_station_component_list.append(dictionary) - - maintenance_report_station_component_list = sorted(maintenance_report_station_component_list, key=lambda d: d['presentation_order']) - - return maintenance_report_station_component_list - - -@require_http_methods(["GET"]) -def get_maintenance_report(request, id): - maintenance_report = MaintenanceReport.objects.get(id=id) - - station, station_profile, technician, visit_type = get_maintenance_report_obj(maintenance_report) - - response = {} - response['station_information'] = { - "station_name": station.name, - "station_host_name": "---", - "station_ID": station.code, - "wigos_ID": station.wigos, - "station_type": 'Automatic' if station.is_automatic else 'Manual', - "station_profile": station_profile.name, - "latitude": station.latitude, - "elevation": station.elevation, - "longitude": station.longitude, - "district": station.region, - "transmission_type": "---", - "transmission_ID": "---", - "transmission_interval": "---", - "measurement_interval": "---", - "data_of_first_operation": station.begin_date, - "data_of_relocation": station.relocation_date, - } - - response["station_id"] = station.id - response["responsible_technician"] = technician.name - response["responsible_technician_id"] = maintenance_report.responsible_technician_id - response["visit_date"] = maintenance_report.visit_date - response["next_visit_date"] = maintenance_report.next_visit_date - response["initial_time"] = maintenance_report.initial_time - response["end_time"] = maintenance_report.end_time - response["visit_type"] = visit_type.name - response["visit_type_id"] = visit_type.id - response["station_on_arrival_conditions"] = maintenance_report.station_on_arrival_conditions - response["current_visit_summary"] = maintenance_report.current_visit_summary - response["next_visit_summary"] = maintenance_report.next_visit_summary - response["other_technician_1"] = maintenance_report.other_technician_1_id - response["other_technician_2"] = maintenance_report.other_technician_2_id - response["other_technician_3"] = maintenance_report.other_technician_3_id - - response['contacts'] = maintenance_report.contacts - response['components'] = get_component_list(maintenance_report) - response['steps'] = len(response['components']) - - if maintenance_report.data_logger_file_name is None: - response['data_logger_file_name'] = "Upload latest data logger program" - else: - response['data_logger_file_name'] = maintenance_report.data_logger_file_name - - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["PUT"]) -def update_maintenance_report_condition(request, id): - now = datetime.datetime.now() - - maintenance_report = MaintenanceReport.objects.get(id=id) - - form_data = json.loads(request.body.decode()) - - maintenance_report.station_on_arrival_conditions = form_data['conditions'] - - maintenance_report.updated_at = now - maintenance_report.save() - - response={} - - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["PUT"]) -def update_maintenance_report_component(request, id, component_id): - now = datetime.datetime.now() - - maintenance_report_station_component = MaintenanceReportStationComponent.objects.get(maintenance_report_id=id, station_component_id=component_id) - - form_data = json.loads(request.body.decode()) - - maintenance_report_station_component.component_classification=form_data['component_classification'] - maintenance_report_station_component.condition=form_data['component_condition'] - - maintenance_report_station_component.updated_at = now - maintenance_report_station_component.save() - - response={} - - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["PUT"]) -def update_maintenance_report_contacts(request, id): - now = datetime.datetime.now() - - maintenance_report = MaintenanceReport.objects.get(id=id) - - form_data = json.loads(request.body.decode()) - - maintenance_report.contacts = form_data['contacts'] - - maintenance_report.updated_at = now - maintenance_report.save() - - response={} - - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def update_maintenance_report_datalogger(request, id): - # print(request.FILES) - if 'data_logger_file' in request.FILES: - maintenance_report = MaintenanceReport.objects.get(id=id) - - data_logger_file = request.FILES['data_logger_file'].file - data_logger_file_name = str(request.FILES['data_logger_file']) - data_logger_file_content = b64encode(data_logger_file.read()).decode('utf-8') - - maintenance_report.data_logger_file = data_logger_file_content - maintenance_report.data_logger_file_name = data_logger_file_name - maintenance_report.updated_at = datetime.datetime.now() - maintenance_report.save() - - response = {'data_logger_file_name', data_logger_file_name} - - return JsonResponse(response, status=status.HTTP_200_OK) - - # print("Data logger file not uploaded.") - response={'message': "Data logger file not uploaded."} - return JsonResponse(response, status=status.HTTP_206_PARTIAL_CONTENT) - - -@require_http_methods(["PUT"]) -def update_maintenance_report_summary(request, id): - now = datetime.datetime.now() - - maintenance_report = MaintenanceReport.objects.get(id=id) - - form_data = json.loads(request.body.decode()) - - if form_data['other_technician_1']: - other_technician_1 = Technician.objects.get(id=form_data['other_technician_1']) - else: - other_technician_1 = None - - if form_data['other_technician_2']: - other_technician_2 = Technician.objects.get(id=form_data['other_technician_2']) - else: - other_technician_2 = None - - if form_data['other_technician_3']: - other_technician_3 = Technician.objects.get(id=form_data['other_technician_3']) - else: - other_technician_3 = None - - maintenance_report.other_technician_1 = other_technician_1 - maintenance_report.other_technician_2 = other_technician_2 - maintenance_report.other_technician_3 = other_technician_3 - maintenance_report.next_visit_date = form_data['next_visit_date'] - maintenance_report.end_time = form_data['end_time'] - maintenance_report.current_visit_summary = form_data['current_visit_summary'] - maintenance_report.next_visit_summary = form_data['next_visit_summary'] - maintenance_report.status = form_data['status'] - - maintenance_report.updated_at = now - maintenance_report.save() - - response={} - - return JsonResponse(response, status=status.HTTP_200_OK) - - -################################################################ -################################################################ - -class SpatialAnalysisView(LoginRequiredMixin, TemplateView): - template_name = "wx/spatial_analysis.html" - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - context['quality_flags'] = QualityFlag.objects.all() - - return self.render_to_response(context) - - -@api_view(['GET']) -def raw_data_last_24h(request, station_id): - station = Station.objects.get(pk=station_id) - response_dict = {} - - query = f""" - WITH data AS ( - SELECT to_char((datetime + interval '{station.utc_offset_minutes} minutes') at time zone 'utc', 'YYYY-MM-DD HH24:MI:SS') as datetime, - measured, - variable_id - FROM raw_data - WHERE station_id=%s - AND datetime >= now() - '1 day'::interval - AND measured != {settings.MISSING_VALUE}) - SELECT datetime, - var.name, - var.symbol, - unit.name, - unit.symbol, - measured - FROM data - INNER JOIN wx_variable var ON variable_id=var.id - LEFT JOIN wx_unit unit ON var.unit_id=unit.id - ORDER BY datetime, var.name - """ - - with connection.cursor() as cursor: - - cursor.execute(query, [station.id]) - - rows = cursor.fetchall() - - for row in rows: - - if row[0] not in response_dict.keys(): - response_dict[row[0]] = [] - - obj = { - 'value': row[5], - 'variable': { - 'name': row[1], - 'symbol': row[2], - 'unit_name': row[3], - 'unit_symbol': row[4] - } - } - response_dict[row[0]].append(obj) - - return JsonResponse(response_dict) - - -class StationsMapView(LoginRequiredMixin, TemplateView): - template_name = "wx/station_map.html" - - -class StationMetadataView(LoginRequiredMixin, TemplateView): - template_name = "wx/station_metadata.html" - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - context['station_id'] = kwargs.get('pk', 'null') - print('kwargs', context['station_id']) - - wmo_station_type_list = WMOStationType.objects.all() - context['wmo_station_type_list'] = wmo_station_type_list - - wmo_region_list = WMORegion.objects.all() - context['wmo_region_list'] = wmo_region_list - - wmo_program_list = WMOProgram.objects.all() - context['wmo_program_list'] = wmo_program_list - - station_profile_list = StationProfile.objects.all() - context['station_profile_list'] = station_profile_list - - station_communication_list = StationCommunication.objects.all() - context['station_communication_list'] = station_communication_list - - return self.render_to_response(context) - - -@api_view(['GET']) -def latest_data(request, variable_id): - result = [] - - query = """ - SELECT CASE WHEN var.variable_type ilike 'code' THEN latest.last_data_code ELSE latest.last_data_value::varchar END as value, - sta.longitude, - sta.latitude, - unit.symbol - FROM wx_stationvariable latest - INNER JOIN wx_variable var ON latest.variable_id=var.id - INNER JOIN wx_station sta ON latest.station_id=sta.id - LEFT JOIN wx_unit unit ON var.unit_id=unit.id - WHERE latest.variable_id=%s - AND latest.last_data_value is not null - AND latest.last_data_datetime = ( SELECT MAX(most_recent.last_data_datetime) - FROM wx_stationvariable most_recent - WHERE most_recent.station_id=latest.station_id - AND most_recent.last_data_value is not null) - """ - - with connection.cursor() as cursor: - cursor.execute(query, [variable_id]) - - rows = cursor.fetchall() - - for row in rows: - obj = { - 'value': row[0], - 'longitude': row[1], - 'latitude': row[2], - 'symbol': row[3], - } - - result.append(obj) - - return Response(result, status=status.HTTP_200_OK) - - -class StationImageViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - parser_class = (FileUploadParser,) - queryset = StationImage.objects.all() - serializer_class = serializers.StationImageSerializer - - def get_queryset(self): - station_id = self.request.query_params.get('station_id', None) - - queryset = StationImage.objects.all() - - if station_id is not None: - queryset = queryset.filter(station__id=station_id) - - return queryset - - -class StationFileViewSet(viewsets.ModelViewSet): - permission_classes = (IsAuthenticated,) - parser_class = (FileUploadParser,) - queryset = StationFile.objects.all() - serializer_class = serializers.StationFileSerializer - - def get_queryset(self): - station_id = self.request.query_params.get('station_id', None) - - queryset = StationFile.objects.all() - - if station_id is not None: - queryset = queryset.filter(station__id=station_id) - - return queryset - - -class ExtremesMeansView(LoginRequiredMixin, TemplateView): - template_name = 'wx/products/extremes_means.html' - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - context['station_list'] = Station.objects.values('id', 'name', 'code') - - return self.render_to_response(context) - - -def get_months(): - months = { - 1: 'January', - 2: 'February', - 3: 'March', - 4: 'April', - 5: 'May', - 6: 'June', - 7: 'July', - 8: 'August', - 9: 'September', - 10: 'October', - 11: 'November', - 12: 'December', - } - - return months - - -def get_interval_in_seconds(interval_id): - if interval_id is None: - return None - interval = Interval.objects.get(id=int(interval_id)) - return interval.seconds - - -@require_http_methods(["POST"]) -def update_reference_station(request): - station_id = request.GET.get('station_id', None) - new_reference_station_id = request.GET.get('new_reference_station_id', None) - - station = Station.objects.get(id=station_id) - station.reference_station_id = new_reference_station_id - station.save() - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def update_global_threshold(request): - qc_method = request.GET.get('qc_method', None) - is_automatic = request.GET.get('is_automatic', None) - variable_name = request.GET.get('variable_name', None) - variable = Variable.objects.get(name=variable_name) - - is_automatic = is_automatic == "true" - - if qc_method=='range': - range_min = request.GET.get('range_min', None) - range_max = request.GET.get('range_max', None) - - if is_automatic: - variable.range_min_hourly = range_min - variable.range_max_hourly = range_max - else: - variable.range_min = range_min - variable.range_max = range_max - - elif qc_method=='step': - step = request.GET.get('step', None) - - if is_automatic: - variable.step_hourly = step - else: - variable.step = step - - elif qc_method=='persist': - minimum_variance = request.GET.get('minimum_variance', None) - window = request.GET.get('window', None) - - if is_automatic: - variable.persistence_hourly = minimum_variance - variable.persistence_window_hourly = window - else: - variable.persistence = minimum_variance - variable.persistence_window = window - - variable.save() - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -from wx.models import QcRangeThreshold - -@api_view(['GET', 'POST', 'PATCH', 'DELETE']) -def range_threshold_view(request): # For synop and daily data captures - if request.method == 'GET': - - station_id = request.GET.get('station_id', None) - variable_id_list = request.GET.get('variable_id_list', None) - month = request.GET.get('month', None) - - variable_query_statement = "" - month_query_statement = "" - query_parameters = {"station_id": station_id, } - - if station_id is None: - JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if variable_id_list is not None: - variable_id_list = tuple(json.loads(variable_id_list)) - variable_query_statement = "AND variable_id IN %(variable_id_list)s" - query_parameters['variable_id_list'] = variable_id_list - - if month is not None: - month_query_statement = "AND month = %(month)s" - query_parameters['month'] = month - - get_range_threshold_query = f""" - SELECT variable.id - ,variable.name - ,range_threshold.station_id - ,range_threshold.range_min - ,range_threshold.range_max - ,range_threshold.interval - ,range_threshold.month - ,TO_CHAR(TO_DATE(range_threshold.month::text, 'MM'), 'Month') - ,range_threshold.id - FROM wx_qcrangethreshold range_threshold - JOIN wx_variable variable on range_threshold.variable_id = variable.id - WHERE station_id = %(station_id)s - {variable_query_statement} - {month_query_statement} - ORDER BY variable.name, range_threshold.month - """ - - result = [] - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - cursor.execute(get_range_threshold_query, query_parameters) - - rows = cursor.fetchall() - for row in rows: - obj = { - 'variable': { - 'id': row[0], - 'name': row[1] - }, - 'station_id': row[2], - 'range_min': row[3], - 'range_max': row[4], - 'interval': row[5], - 'month': row[6], - 'month_desc': row[7], - 'id': row[8], - - } - result.append(obj) - - return Response(result, status=status.HTTP_200_OK) - - - elif request.method == 'POST': - - station_id = request.data['station_id'] - variable_id = request.data['variable_id'] - month = request.data['month'] - interval = request.data['interval'] - range_min = request.data['range_min'] - range_max = request.data['range_max'] - - if station_id is None: - JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if variable_id is None: - JsonResponse(data={"message": "'variable_id' parameter cannot be null."}, - status=status.HTTP_400_BAD_REQUEST) - - if month is None: - JsonResponse(data={"message": "'month' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if interval is None: - JsonResponse(data={"message": "'interval' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if range_min is None: - JsonResponse(data={"message": "'range_min' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if range_max is None: - JsonResponse(data={"message": "'range_max' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - post_range_threshold_query = f""" - INSERT INTO wx_qcrangethreshold (created_at, updated_at, range_min, range_max, station_id, variable_id, interval, month) - VALUES (now(), now(), %(range_min)s, %(range_max)s , %(station_id)s, %(variable_id)s, %(interval)s, %(month)s) - """ - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - try: - cursor.execute(post_range_threshold_query, - {'station_id': station_id, 'variable_id': variable_id, 'month': month, - 'interval': interval, 'range_min': range_min, 'range_max': range_max, }) - except psycopg2.errors.UniqueViolation: - conn.rollback() - return JsonResponse(data={"message": "Threshold already exists"}, - status=status.HTTP_400_BAD_REQUEST) - - conn.commit() - return Response(status=status.HTTP_200_OK) - - - elif request.method == 'PATCH': - range_threshold_id = request.GET.get('id', None) - month = request.data['month'] - interval = request.data['interval'] - range_min = request.data['range_min'] - range_max = request.data['range_max'] - - if range_threshold_id is None: - JsonResponse(data={"message": "'id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if month is None: - JsonResponse(data={"message": "'month' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if interval is None: - JsonResponse(data={"message": "'interval' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if range_min is None: - JsonResponse(data={"message": "'range_min' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if range_max is None: - JsonResponse(data={"message": "'range_max' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - patch_range_threshold_query = f""" - UPDATE wx_qcrangethreshold - SET month = %(month)s - ,interval = %(interval)s - ,range_min = %(range_min)s - ,range_max = %(range_max)s - WHERE id = %(range_threshold_id)s - """ - - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - try: - cursor.execute(patch_range_threshold_query, - {'range_threshold_id': range_threshold_id, 'month': month, 'interval': interval, - 'range_min': range_min, 'range_max': range_max, }) - except psycopg2.errors.UniqueViolation: - conn.rollback() - return JsonResponse(data={"message": "Threshold already exists"}, - status=status.HTTP_400_BAD_REQUEST) - - conn.commit() - return Response(status=status.HTTP_200_OK) - - - elif request.method == 'DELETE': - range_threshold_id = request.GET.get('id', None) - - if range_threshold_id is None: - JsonResponse(data={"message": "'range_threshold_id' parameter cannot be null."}, - status=status.HTTP_400_BAD_REQUEST) - - delete_range_threshold_query = f""" DELETE FROM wx_qcrangethreshold WHERE id = %(range_threshold_id)s """ - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - try: - cursor.execute(delete_range_threshold_query, {'range_threshold_id': range_threshold_id}) - except: - conn.rollback() - return JsonResponse(data={"message": "Error on delete threshold"}, - status=status.HTTP_400_BAD_REQUEST) - - conn.commit() - return Response(status=status.HTTP_200_OK) - - return Response([], status=status.HTTP_200_OK) - - -def get_range_threshold_form(request): - template = loader.get_template('wx/quality_control/range_threshold.html') - - context = {} - context['station_list'] = Station.objects.select_related('profile').all() - context['station_profile_list'] = StationProfile.objects.all() - context['station_watershed_list'] = Watershed.objects.all() - context['station_district_list'] = AdministrativeRegion.objects.all() - context['interval_list'] = Interval.objects.filter(seconds__gt=1).order_by('seconds') - - return HttpResponse(template.render(context, request)) - - -def get_range_threshold_list(station_id, variable_id, interval, is_reference=False): - threshold_list = [] - months = get_months() - for month_id in sorted(months.keys()): - month = months[month_id] - try: - threshold = QcRangeThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=interval, month=month_id) - threshold_entry = { - 'month': month_id, - 'min': str(threshold.range_min) if threshold.range_min is not None else '---', - 'max': str(threshold.range_max) if threshold.range_max is not None else '---', - } - except ObjectDoesNotExist: - if is_reference: - try: - threshold = QcRangeThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=None, month=month_id) - threshold_entry = { - 'month': month_id, - 'min': str(threshold.range_min)+'*' if threshold.range_min is not None else '---', - 'max': str(threshold.range_max)+'*' if threshold.range_max is not None else '---', - } - except ObjectDoesNotExist: - threshold_entry = { - 'month': month_id, - 'min': '---', - 'max': '---', - } - else: - threshold_entry = { - 'month': month_id, - 'min': '---', - 'max': '---', - } - threshold_list.append(threshold_entry) - return threshold_list - - -def get_range_threshold_in_list(threshold_list, month_id): - if threshold_list: - for threshold_entry in threshold_list: - if threshold_entry['month'] == month_id: - return threshold_entry - - threshold_entry = {'month': month_id, 'min': '---', 'max': '---'} - return threshold_entry - - -def format_range_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable_name): - months = get_months() - - formated_thresholds = [] - for month_id in sorted(months.keys()): - month_name = months[month_id] - - custom_entry = get_range_threshold_in_list(custom_thresholds, month_id) - reference_entry = get_range_threshold_in_list(reference_thresholds, month_id) - - formated_threshold = { - 'variable_name': variable_name, - 'month_name': month_name, - 'global':{ - 'children':{ - 'g_min': global_thresholds['min'], - 'g_max': global_thresholds['max'], - } - }, - 'reference':{ - 'children':{ - 'r_min': reference_entry['min'], - 'r_max': reference_entry['max'], - } - }, - 'custom':{ - 'children':{ - 'c_min': custom_entry['min'], - 'c_max': custom_entry['max'], - } - } - } - formated_thresholds.append(formated_threshold) - - return formated_thresholds - - -@require_http_methods(["GET"]) -def get_range_threshold(request): - station_id = request.GET.get('station_id', None) - if station_id is None: - response = {'message': "Field Station can not be empty."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - - variable_ids = request.GET.get('variable_ids', None) - if variable_ids is None: - response = {'message': "Field Variables can not be empty."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - interval_id = request.GET.get('interval_id', None) - # if interval_id is None: - # response = {'message': "Field Measurement Interval can not be empty."} - # return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - station = Station.objects.get(id=int(station_id)) - variable_ids = [int(variable_id) for variable_id in variable_ids.split(",")] - interval_seconds = get_interval_in_seconds(interval_id) - - reference_station_id = station.reference_station_id - if reference_station_id: - reference_station = Station.objects.get(id=station.reference_station_id) - reference_station_name = reference_station.name+' - '+reference_station.code - else: - reference_station = None - reference_station_name = None - - data = { - 'reference_station_id': reference_station_id, - 'reference_station_name': reference_station_name, - 'variable_data': {}, - } - - for variable_id in variable_ids: - variable = Variable.objects.get(id=variable_id) - - custom_thresholds = get_range_threshold_list(station.id, variable.id, interval_seconds, is_reference=False) - - if reference_station: - reference_thresholds = get_range_threshold_list(reference_station.id, variable.id, interval_seconds, is_reference=True) - else: - reference_thresholds = None - - global_thresholds = {} - if station.is_automatic: - global_thresholds['min'] = variable.range_min_hourly - global_thresholds['max'] = variable.range_max_hourly - else: - global_thresholds['min'] = variable.range_min - global_thresholds['max'] = variable.range_max - - global_thresholds['min'] = '---' if global_thresholds['min'] is None else str(global_thresholds['min']) - global_thresholds['max'] = '---' if global_thresholds['max'] is None else str(global_thresholds['max']) - - formated_thresholds = format_range_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable.name) - - data['variable_data'][variable.name] = formated_thresholds; - - response = {'data': data} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def update_range_threshold(request): - months = get_months() - months_ids = {v: k for k, v in months.items()} - - new_min = request.GET.get('new_min', None) - new_max = request.GET.get('new_max', None) - interval_id = request.GET.get('interval_id', None) - station_id = request.GET.get('station_id', None) - variable_name = request.GET.get('variable_name', None) - month_name = request.GET.get('month_name', None) - - - station = Station.objects.get(id=station_id) - variable = Variable.objects.get(name=variable_name) - month_id = months_ids[month_name] - interval_seconds = get_interval_in_seconds(interval_id) - - qcrangethreshold, created = QcRangeThreshold.objects.get_or_create(station_id=station.id, variable_id=variable.id, month=month_id, interval=interval_seconds) - - qcrangethreshold.range_min = new_min - qcrangethreshold.range_max = new_max - - qcrangethreshold.save() - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def delete_range_threshold(request): - months = get_months() - months_ids = {v: k for k, v in months.items()} - - interval_id = request.GET.get('interval_id', None) - station_id = request.GET.get('station_id', None) - variable_name = request.GET.get('variable_name', None) - month_name = request.GET.get('month_name', None) - - station = Station.objects.get(id=station_id) - variable = Variable.objects.get(name=variable_name) - month_id = months_ids[month_name] - interval_seconds = get_interval_in_seconds(interval_id) - - try: - qcrangethreshold = QcRangeThreshold.objects.get(station_id=station.id, variable_id=variable.id, month=month_id, interval=interval_seconds) - qcrangethreshold.delete() - except ObjectDoesNotExist: - pass - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -from wx.models import QcStepThreshold - - -def get_step_threshold_form(request): - template = loader.get_template('wx/quality_control/step_threshold.html') - - context = {} - context['station_list'] = Station.objects.select_related('profile').all() - context['station_profile_list'] = StationProfile.objects.all() - context['station_watershed_list'] = Watershed.objects.all() - context['station_district_list'] = AdministrativeRegion.objects.all() - context['interval_list'] = Interval.objects.filter(seconds__gt=1).order_by('seconds') - - return HttpResponse(template.render(context, request)) - - -def get_step_threshold_entry(station_id, variable_id, interval, is_reference=False): - try: - threshold = QcStepThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=interval) - threshold_entry = { - 'min': str(threshold.step_min) if threshold.step_min is not None else '---', - 'max': str(threshold.step_max) if threshold.step_max is not None else '---', - } - except ObjectDoesNotExist: - if is_reference: - try: - threshold = QcStepThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=None) - threshold_entry = { - 'min': str(threshold.step_min)+'*' if threshold.step_min is not None else '---', - 'max': str(threshold.step_max)+'*' if threshold.step_max is not None else '---', - } - except ObjectDoesNotExist: - threshold_entry = { - 'min': '---', - 'max': '---', - } - else: - threshold_entry = { - 'min': '---', - 'max': '---', - } - - return threshold_entry - - -def format_step_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable_name): - formated_threshold = { - 'variable_name': variable_name, - 'global':{ - 'children':{ - 'g_min': global_thresholds['min'], - 'g_max': global_thresholds['max'], - } - }, - 'reference':{ - 'children':{ - 'r_min': reference_thresholds['min'], - 'r_max': reference_thresholds['max'], - } - }, - 'custom':{ - 'children':{ - 'c_min': custom_thresholds['min'], - 'c_max': custom_thresholds['max'], - } - } - } - return [formated_threshold] - - -@require_http_methods(["GET"]) -def get_step_threshold(request): - station_id = request.GET.get('station_id', None) - if station_id is None: - response = {'message': "Field Station can not be empty."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - variable_ids = request.GET.get('variable_ids', None) - if variable_ids is None: - response = {'message': "Field Variables can not be empty."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - interval_id = request.GET.get('interval_id', None) - # if interval_id is None: - # response = {'message': "Field Measurement Interval can not be empty."} - # return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - station = Station.objects.get(id=int(station_id)) - variable_ids = [int(variable_id) for variable_id in variable_ids.split(",")] - interval_seconds = get_interval_in_seconds(interval_id) - - reference_station_id = station.reference_station_id - if reference_station_id: - reference_station = Station.objects.get(id=station.reference_station_id) - reference_station_name = reference_station.name+' - '+reference_station.code - else: - reference_station = None - reference_station_name = None - - data = { - 'reference_station_id': reference_station_id, - 'reference_station_name': reference_station_name, - 'variable_data': {}, - } - - for variable_id in variable_ids: - variable = Variable.objects.get(id=variable_id) - - custom_thresholds = get_step_threshold_entry(station.id, variable.id, interval_seconds, is_reference=False) - - if reference_station: - reference_thresholds = get_step_threshold_entry(reference_station.id, variable.id, interval_seconds, is_reference=True) - else: - reference_thresholds = {'min': '---', 'max': '---'} - - global_thresholds = {} - if station.is_automatic: - global_thresholds['min'] = -variable.step_hourly if variable.step_hourly else variable.step_hourly - global_thresholds['max'] = variable.step_hourly - else: - global_thresholds['min'] = -variable.step if variable.step else variable.step - global_thresholds['max'] = variable.step - - global_thresholds['min'] = '---' if global_thresholds['min'] is None else str(global_thresholds['min']) - global_thresholds['max'] = '---' if global_thresholds['max'] is None else str(global_thresholds['max']) - - formated_thresholds = format_step_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable.name) - - data['variable_data'][variable.name] = formated_thresholds; - - response = {'data': data} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def update_step_threshold(request): - new_min = request.GET.get('new_min', None) - new_max = request.GET.get('new_max', None) - interval_id = request.GET.get('interval_id', None) - station_id = request.GET.get('station_id', None) - variable_name = request.GET.get('variable_name', None) - - station = Station.objects.get(id=station_id) - variable = Variable.objects.get(name=variable_name) - interval_seconds = get_interval_in_seconds(interval_id) - - qcstepthreshold, created = QcStepThreshold.objects.get_or_create(station_id=station.id, variable_id=variable.id, interval=interval_seconds) - - qcstepthreshold.step_min = new_min - qcstepthreshold.step_max = new_max - - qcstepthreshold.save() - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def delete_step_threshold(request): - interval_id = request.GET.get('interval_id', None) - station_id = request.GET.get('station_id', None) - variable_name = request.GET.get('variable_name', None) - - station = Station.objects.get(id=station_id) - variable = Variable.objects.get(name=variable_name) - interval_seconds = get_interval_in_seconds(interval_id) - - try: - qcstepthreshold = QcStepThreshold.objects.get(station_id=station.id, variable_id=variable.id, interval=interval_seconds) - qcstepthreshold.delete() - except ObjectDoesNotExist: - pass - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -from wx.models import QcPersistThreshold - - -def get_persist_threshold_form(request): - template = loader.get_template('wx/quality_control/persist_threshold.html') - - context = {} - context['station_list'] = Station.objects.select_related('profile').all() - context['station_profile_list'] = StationProfile.objects.all() - context['station_watershed_list'] = Watershed.objects.all() - context['station_district_list'] = AdministrativeRegion.objects.all() - context['interval_list'] = Interval.objects.filter(seconds__gt=1).order_by('seconds') - - return HttpResponse(template.render(context, request)) - - -def get_persist_threshold_entry(station_id, variable_id, interval, is_reference=False): - try: - threshold = QcPersistThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=interval) - threshold_entry = { - 'var': str(threshold.minimum_variance) if threshold.minimum_variance is not None else '---', - 'win': str(threshold.window) if threshold.window is not None else '---', - } - except ObjectDoesNotExist: - if is_reference: - try: - threshold = QcPersistThreshold.objects.get(station_id=station_id, variable_id=variable_id, interval=None) - threshold_entry = { - 'var': str(threshold.minimum_variance)+'*' if threshold.minimum_variance is not None else '---', - 'win': str(threshold.window)+'*' if threshold.window is not None else '---', - } - except ObjectDoesNotExist: - threshold_entry = { - 'var': '---', - 'win': '---', - } - else: - threshold_entry = { - 'var': '---', - 'win': '---', - } - - return threshold_entry - - -def format_persist_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable_name): - formated_threshold = { - 'variable_name': variable_name, - 'global':{ - 'children':{ - 'g_var': global_thresholds['var'], - 'g_win': global_thresholds['win'], - } - }, - 'reference':{ - 'children':{ - 'r_var': reference_thresholds['var'], - 'r_win': reference_thresholds['win'], - } - }, - 'custom':{ - 'children':{ - 'c_var': custom_thresholds['var'], - 'c_win': custom_thresholds['win'], - } - } - } - return [formated_threshold] - - -@require_http_methods(["GET"]) -def get_persist_threshold(request): - station_id = request.GET.get('station_id', None) - if station_id is None: - response = {'message': "Field Station can not be empty."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - variable_ids = request.GET.get('variable_ids', None) - if variable_ids is None: - response = {'message': "Field Variables can not be empty."} - return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - interval_id = request.GET.get('interval_id', None) - # if interval_id is None: - # response = {'message': "Field Measurement Interval can not be empty."} - # return JsonResponse(response, status=status.HTTP_400_BAD_REQUEST) - - station = Station.objects.get(id=int(station_id)) - variable_ids = [int(variable_id) for variable_id in variable_ids.split(",")] - interval_seconds = get_interval_in_seconds(interval_id) - - reference_station_id = station.reference_station_id - if reference_station_id: - reference_station = Station.objects.get(id=station.reference_station_id) - reference_station_name = reference_station.name+' - '+reference_station.code - else: - reference_station = None - reference_station_name = None - - data = { - 'reference_station_id': reference_station_id, - 'reference_station_name': reference_station_name, - 'variable_data': {}, - } - - for variable_id in variable_ids: - variable = Variable.objects.get(id=variable_id) - - custom_thresholds = get_persist_threshold_entry(station.id, variable.id, interval_seconds, is_reference=False) - - if reference_station: - reference_thresholds = get_persist_threshold_entry(reference_station.id, variable.id, interval_seconds, is_reference=True) - else: - reference_thresholds = {'var': '---', 'win': '---'} - - global_thresholds = {} - if station.is_automatic: - global_thresholds['var'] = variable.persistence_hourly - global_thresholds['win'] = variable.persistence_window_hourly - else: - global_thresholds['var'] = variable.persistence - global_thresholds['win'] = variable.persistence_window - - global_thresholds['var'] = '---' if global_thresholds['var'] is None else str(global_thresholds['var']) - global_thresholds['win'] = '---' if global_thresholds['win'] is None else str(global_thresholds['win']) - - formated_thresholds = format_persist_thresholds(global_thresholds, reference_thresholds, custom_thresholds, variable.name) - - data['variable_data'][variable.name] = formated_thresholds; - - response = {'data': data} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def update_persist_threshold(request): - new_var = request.GET.get('new_var', None) - new_win = request.GET.get('new_win', None) - station_id = request.GET.get('station_id', None) - variable_name = request.GET.get('variable_name', None) - interval_id = request.GET.get('interval_id', None) - - station = Station.objects.get(id=station_id) - variable = Variable.objects.get(name=variable_name) - interval_seconds = get_interval_in_seconds(interval_id) - - try: - qcpersistthreshold = QcPersistThreshold.objects.get(station_id=station.id, variable_id=variable.id, interval=interval_seconds) - except ObjectDoesNotExist: - qcpersistthreshold = QcPersistThreshold.objects.create(station_id=station.id, variable_id=variable.id, interval=interval_seconds, minimum_variance=new_var, window=new_win) - - qcpersistthreshold.save() - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@require_http_methods(["POST"]) -def delete_persist_threshold(request): - station_id = request.GET.get('station_id', None) - variable_name = request.GET.get('variable_name', None) - interval_id = request.GET.get('interval_id', None) - - station = Station.objects.get(id=station_id) - variable = Variable.objects.get(name=variable_name) - interval_seconds = get_interval_in_seconds(interval_id) - - try: - qcpersistthreshold = QcPersistThreshold.objects.get(station_id=station.id, variable_id=variable.id, interval=interval_seconds) - qcpersistthreshold.delete() - except ObjectDoesNotExist: - pass - - response = {} - return JsonResponse(response, status=status.HTTP_200_OK) - - -@api_view(['GET']) -def daily_means_data_view(request): - station_id = request.GET.get('station_id', None) - month = request.GET.get('month', None) - variable_id_list = request.GET.get('variable_id_list', None) - begin_year = request.GET.get('begin_year', None) - end_year = request.GET.get('end_year', None) - filter_year_query = "" - filter_year_query_avg = "" - period = "All years" - - if station_id is None: - JsonResponse(data={"message": "'station_id' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if month is None: - JsonResponse(data={"message": "'month' parameter cannot be null."}, status=status.HTTP_400_BAD_REQUEST) - - if variable_id_list is not None: - variable_id_list = json.loads(variable_id_list) - else: - JsonResponse(data={"message": "'variable_id_list' parameter cannot be null."}, - status=status.HTTP_400_BAD_REQUEST) - - if begin_year is not None and end_year is not None: - try: - filter_year_query = "AND EXTRACT(YEAR from day) >= %(begin_year)s AND EXTRACT(YEAR from day) <= %(end_year)s" - period = f"{begin_year} - {end_year}" - - begin_year = int(begin_year) - end_year = int(end_year) - except ValueError: - JsonResponse(data={"message": "Invalid 'begin_year' or 'end_year' parameters."}, - status=status.HTTP_400_BAD_REQUEST) - - month = int(month) - res = {} - variable_dict = {} - variable_symbol_dict = {} - for variable_id in variable_id_list: - query_params_dict = {"station_id": station_id, "month": month, "variable_id": variable_id, - "begin_year": begin_year, "end_year": end_year} - variable = Variable.objects.get(pk=variable_id) - - variable_symbol_dict[variable.symbol] = variable.name - aggregation_type = variable.sampling_operation.name if variable.sampling_operation is not None else None - data_dict = {} - summary_dict = {} - colspan = 1 - headers = ['Average'] - columns = ['value'] - - if aggregation_type == 'Accumulation': - colspan = 3 - headers = ['Greatest', 'Year', 'Years'] - columns = ['agg_value', 'year', 'years'] - - with connection.cursor() as cursor: - cursor.execute(f""" - SELECT data.day - ,data.sum_value - ,data.year - ,data.years - FROM (SELECT EXTRACT(DAY from day) as day - ,sum_value - ,EXTRACT(YEAR from day) as year - ,RANK () OVER (PARTITION BY EXTRACT(DAY from day) ORDER BY sum_value DESC) as rank - ,count(1) OVER (PARTITION BY EXTRACT(DAY from day)) as years - FROM daily_summary - WHERE station_id = %(station_id)s - AND EXTRACT(MONTH from day) = %(month)s - AND variable_id = %(variable_id)s - {filter_year_query}) data - WHERE data.rank = 1 - """, query_params_dict) - rows = cursor.fetchall() - - if len(rows) > 0: - df = pd.DataFrame(data=rows, columns=("day", "sum_value", "year", "years")) - for index, row in df.iterrows(): - data_dict[int(row['day'])] = {"agg_value": round(row['sum_value'], 2), "year": row['year'], - "years": row['years']} - - max_row = df.loc[df["sum_value"].idxmax()] - summary_dict = {"agg_value": round(max_row['sum_value'], 2), "year": max_row['year']} - - - elif aggregation_type == 'Maximum': - colspan = 4 - headers = ['Average', 'Extreme', 'Year', 'Years'] - columns = ['value', 'agg_value', 'year', 'years'] - - with connection.cursor() as cursor: - cursor.execute(f""" - SELECT data.day - ,summary.value - ,data.max_value - ,data.year - ,data.years - FROM (SELECT EXTRACT(DAY from day) as day - ,max_value - ,EXTRACT(YEAR from day) as year - ,RANK () OVER (PARTITION BY EXTRACT(DAY from day) ORDER BY max_value DESC) as rank - ,count(1) OVER (PARTITION BY EXTRACT(DAY from day)) as years - FROM daily_summary - WHERE station_id = %(station_id)s - AND EXTRACT(MONTH from day) = %(month)s - AND variable_id = %(variable_id)s - {filter_year_query}) data, - (SELECT EXTRACT(DAY from day) as day - ,avg(max_value) as value - FROM daily_summary - WHERE station_id = %(station_id)s - AND EXTRACT(MONTH from day) = %(month)s - AND variable_id = %(variable_id)s - {filter_year_query} - GROUP BY 1) summary - WHERE data.rank = 1 - AND summary.day = data.day - """, query_params_dict) - rows = cursor.fetchall() - - if len(rows) > 0: - df = pd.DataFrame(data=rows, columns=("day", "value", "max_value", "year", "years")) - for index, row in df.iterrows(): - data_dict[int(row['day'])] = {"value": round(row['value'], 2), - "agg_value": round(row['max_value'], 2), "year": row['year'], - "years": row['years']} - - max_row = df.loc[df["max_value"].idxmax()] - summary_dict = {"value": round(df["value"].mean(), 2), "agg_value": round(max_row['max_value'], 2), - "year": max_row['year']} - - elif aggregation_type == 'Minimum': - headers = ['Average', 'Extreme', 'Year', 'Years'] - columns = ['value', 'agg_value', 'year', 'years'] - colspan = 4 - with connection.cursor() as cursor: - cursor.execute(f""" - SELECT data.day - ,summary.value - ,data.min_value - ,data.year - ,data.years - FROM (SELECT EXTRACT(DAY from day) as day - ,min_value - ,EXTRACT(YEAR from day) as year - ,RANK () OVER (PARTITION BY EXTRACT(DAY from day) ORDER BY min_value ASC) as rank - ,count(1) OVER (PARTITION BY EXTRACT(DAY from day)) as years - FROM daily_summary - WHERE station_id = %(station_id)s - AND EXTRACT(MONTH from day) = %(month)s - AND variable_id = %(variable_id)s - {filter_year_query}) data, - (SELECT EXTRACT(DAY from day) as day - ,avg(min_value) as value - FROM daily_summary - WHERE station_id = %(station_id)s - AND EXTRACT(MONTH from day) = %(month)s - AND variable_id = %(variable_id)s - {filter_year_query} - GROUP BY 1) summary - WHERE data.rank = 1 - AND summary.day = data.day - """, query_params_dict) - rows = cursor.fetchall() - - if len(rows) > 0: - df = pd.DataFrame(data=rows, columns=("day", "value", "min_value", "year", "years")) - for index, row in df.iterrows(): - data_dict[int(row['day'])] = {"value": round(row['value'], 2), - "agg_value": round(row['min_value'], 2), "year": row['year'], - "years": row['years']} - - max_row = df.loc[df["min_value"].idxmin()] - summary_dict = {"value": round(df["value"].mean(), 2), "agg_value": round(max_row['min_value'], 2), - "year": max_row['year']} - - else: - with connection.cursor() as cursor: - cursor.execute(f""" - SELECT EXTRACT(DAY from day) - ,avg(avg_value) - FROM daily_summary data - WHERE station_id = %(station_id)s - AND EXTRACT(MONTH from day) = %(month)s - AND variable_id = %(variable_id)s - {filter_year_query} - GROUP BY 1 - """, query_params_dict) - rows = cursor.fetchall() - - if len(rows) > 0: - for row in rows: - day = int(row[0]) - value = round(row[1], 2) - data_dict[day] = {"value": value} - - df = pd.DataFrame(data=rows, columns=("day", "value")) - summary_dict = {"value": round(df["value"].mean(), 2)} - - variable_dict[variable_id] = { - 'metadata': { - "name": variable.symbol, - "id": variable.id, - "unit": variable.unit.name if variable.unit is not None else "", - "unit_symbol": variable.unit.symbol if variable.unit is not None else "", - "aggregation": aggregation_type, - "colspan": colspan, - "headers": headers, - "columns": columns - }, - 'data': data_dict, - 'summary': summary_dict, - } - - res['variables'] = variable_dict - station = Station.objects.get(pk=station_id) - res['station'] = { - "name": station.name, - "district": station.region, - "latitude": station.latitude, - "longitude": station.longitude, - "elevation": station.elevation, - "variables": variable_symbol_dict, - } - - res['params'] = { - "month": month, - "period": period, - } - - return JsonResponse(res, status=status.HTTP_200_OK) - - -class DataInventoryView(LoginRequiredMixin, TemplateView): - template_name = "wx/data_inventory.html" - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - - context['variable_list'] = Variable.objects.all() - - return self.render_to_response(context) - - -@api_view(['GET']) -def get_data_inventory(request): - start_year = request.GET.get('start_date', None) - end_year = request.GET.get('end_date', None) - is_automatic = request.GET.get('is_automatic', None) - - result = [] - - query = """ - SELECT EXTRACT('YEAR' from station_data.datetime) - ,station.id - ,station.name - ,station.code - ,station.is_automatic - ,station.begin_date - ,station.watershed - ,TRUNC(AVG(station_data.record_count_percentage)::numeric, 2) - FROM wx_stationdataminimuminterval AS station_data - JOIN wx_station AS station ON station.id = station_data.station_id - WHERE EXTRACT('YEAR' from station_data.datetime) >= %(start_year)s - AND EXTRACT('YEAR' from station_data.datetime) < %(end_year)s - AND station.is_automatic = %(is_automatic)s - GROUP BY 1, station.id - ORDER BY station.watershed, station.name - """ - - with connection.cursor() as cursor: - cursor.execute(query, {"start_year": start_year, "end_year": end_year, "is_automatic": is_automatic}) - rows = cursor.fetchall() - - for row in rows: - obj = { - 'year': row[0], - 'station': { - 'id': row[1], - 'name': row[2], - 'code': row[3], - 'is_automatic': row[4], - 'begin_date': row[5], - 'watershed': row[6], - }, - 'percentage': row[7], - } - result.append(obj) - - return Response(result, status=status.HTTP_200_OK) - - -@api_view(['GET']) -def get_data_inventory_by_station(request): - start_year = request.GET.get('start_date', None) - end_year = request.GET.get('end_date', None) - station_id: list = request.GET.get('station_id', None) - record_limit = request.GET.get('record_limit', None) - - if station_id is None or len(station_id) == 0: - return JsonResponse({"message": "\"station_id\" must not be null"}, status=status.HTTP_400_BAD_REQUEST) - - record_limit_lexical = "" - if record_limit is not None: - record_limit_lexical = f"LIMIT {record_limit}" - - result = [] - query = f""" - WITH variable AS ( - SELECT variable.id, variable.name - FROM wx_variable AS variable - JOIN wx_stationvariable AS station_variable ON station_variable.variable_id = variable.id - WHERE station_variable.station_id = %(station_id)s - ORDER BY variable.name - {record_limit_lexical} - ) - SELECT EXTRACT('YEAR' from station_data.datetime) - ,limited_variable.id - ,limited_variable.name - ,TRUNC(AVG(station_data.record_count_percentage)::numeric, 2) - FROM wx_stationdataminimuminterval AS station_data - JOIN variable AS limited_variable ON limited_variable.id = station_data.variable_id - JOIN wx_station station ON station_data.station_id = station.id - WHERE EXTRACT('YEAR' from station_data.datetime) >= %(start_year)s - AND EXTRACT('YEAR' from station_data.datetime) < %(end_year)s - AND station_data.station_id = %(station_id)s - GROUP BY 1, limited_variable.id, limited_variable.name - ORDER BY 1, limited_variable.name - """ - - with connection.cursor() as cursor: - cursor.execute(query, {"start_year": start_year, "end_year": end_year, "station_id": station_id}) - rows = cursor.fetchall() - - for row in rows: - obj = { - 'year': row[0], - 'variable': { - 'id': row[1], - 'name': row[2], - }, - 'percentage': row[3], - } - result.append(obj) - - return Response(result, status=status.HTTP_200_OK) - -@api_view(['GET']) -def get_station_variable_month_data_inventory(request): - year = request.GET.get('year', None) - station_id = request.GET.get('station_id', None) - - if station_id is None: - return JsonResponse({"message": "Invalid request. Station id must be provided"}, - status=status.HTTP_400_BAD_REQUEST) - - result = [] - query = """ - SELECT EXTRACT('MONTH' FROM station_data.datetime) AS month - ,variable.id - ,variable.name - ,measurementvariable.name - ,TRUNC(AVG(station_data.record_count_percentage)::numeric, 2) - FROM wx_stationdataminimuminterval AS station_data - JOIN wx_variable variable ON station_data.variable_id=variable.id - LEFT JOIN wx_measurementvariable measurementvariable ON measurementvariable.id = variable.measurement_variable_id - WHERE EXTRACT('YEAR' from station_data.datetime) = %(year)s - AND station_data.station_id = %(station_id)s - GROUP BY 1, variable.id, variable.name, measurementvariable.name - """ - - with connection.cursor() as cursor: - cursor.execute(query, {"year": year, "station_id": station_id}) - rows = cursor.fetchall() - - for row in rows: - obj = { - 'month': row[0], - 'variable': { - 'id': row[1], - 'name': row[2], - 'measurement_variable_name': row[3] if row[3] is None else row[3].lower().replace(' ', '-'), - }, - 'percentage': row[4], - } - result.append(obj) - - return Response(result, status=status.HTTP_200_OK) - - -@api_view(['GET']) -def get_station_variable_day_data_inventory(request): - year = request.GET.get('year', None) - month = request.GET.get('month', None) - station_id = request.GET.get('station_id', None) - variable_id = request.GET.get('variable_id', None) - - if station_id is None: - return JsonResponse({"message": "Invalid request. Station id must be provided"}, - status=status.HTTP_400_BAD_REQUEST) - - query = """ - WITH data AS ( - SELECT EXTRACT('DAY' FROM station_data.datetime) AS day - ,EXTRACT('DOW' FROM station_data.datetime) AS dow - ,TRUNC(station_data.record_count_percentage::numeric, 2) as percentage - ,station_data.record_count - ,station_data.ideal_record_count - ,(select COUNT(1) from raw_data rd where rd.station_id = station_data.station_id and rd.variable_id = station_data.variable_id and rd.datetime between station_data.datetime and station_data.datetime + '1 DAY'::interval and coalesce(rd.manual_flag, rd.quality_flag) in (1, 4)) qc_passed_amount - ,(select COUNT(1) from raw_data rd where rd.station_id = station_data.station_id and rd.variable_id = station_data.variable_id and rd.datetime between station_data.datetime and station_data.datetime + '1 DAY'::interval) qc_amount - FROM wx_stationdataminimuminterval AS station_data - WHERE EXTRACT('YEAR' from station_data.datetime) = %(year)s - AND EXTRACT('MONTH' from station_data.datetime) = %(month)s - AND station_data.station_id = %(station_id)s - AND station_data.variable_id = %(variable_id)s - ORDER BY station_data.datetime) - SELECT available_days.custom_day - ,data.dow - ,COALESCE(data.percentage, 0) AS percentage - ,COALESCE(data.record_count, 0) AS record_count - ,COALESCE(data.ideal_record_count, 0) AS ideal_record_count - ,case when data.qc_amount = 0 then 0 - else TRUNC((data.qc_passed_amount / data.qc_amount::numeric) * 100, 2) end as qc_passed_percentage - FROM (SELECT custom_day FROM unnest( ARRAY[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31] ) AS custom_day) AS available_days - LEFT JOIN data ON data.day = available_days.custom_day; - - """ - - days = [] - day_with_data = None - with connection.cursor() as cursor: - cursor.execute(query, {"year": year, "station_id": station_id, "month": month, "variable_id": variable_id}) - rows = cursor.fetchall() - - for row in rows: - obj = { - 'day': row[0], - 'dow': row[1], - 'percentage': row[2], - 'record_count': row[3], - 'ideal_record_count': row[4], - 'qc_passed_percentage': row[5], - } - - if row[1] is not None and day_with_data is None: - day_with_data = obj - days.append(obj) - - if day_with_data is None: - return JsonResponse({"message": "No data found"}, - status=status.HTTP_404_NOT_FOUND) - - for day in days: - current_day = day.get('day', None) - current_dow = day.get('dow', None) - if current_dow is None: - day_with_data_day = day_with_data.get('day', None) - day_with_data_dow = day_with_data.get('dow', None) - day_difference = current_day - day_with_data_day - day["dow"] = (day_difference + day_with_data_dow) % 7 - - return Response(days, status=status.HTTP_200_OK) - - - -@api_view(['POST']) -def delete_pgia_hourly_capture_row(request): - request_date = request.data['date'] - hour = request.data['hour'] - station_id = request.data['station_id'] - variable_id_list = request.data['variable_ids'] - - try: - request_date = datetime.datetime.strptime(request_date, '%Y-%m-%d') - except ValueError: - return JsonResponse({"message": "Invalid date format. The expected date format is 'YYYY-MM-DD'"}, - status=status.HTTP_400_BAD_REQUEST) - - if station_id is None: - return JsonResponse({"message": "Invalid request. Station id must be provided"}, - status=status.HTTP_400_BAD_REQUEST) - - if hour is None: - return JsonResponse({"message": "Invalid request. Hour must be provided"}, status=status.HTTP_400_BAD_REQUEST) - - if variable_id_list is None: - return JsonResponse({"message": "Invalid request. Variable ids must be provided"}, - status=status.HTTP_400_BAD_REQUEST) - - variable_id_list = tuple(variable_id_list) - station = Station.objects.get(id=station_id) - datetime_offset = pytz.FixedOffset(station.utc_offset_minutes) - current_datetime = datetime_offset.localize(request_date.replace(hour=hour)) - - result = [] - delete_query = """ - DELETE FROM raw_data - WHERE station_id = %(station_id)s - AND variable_id IN %(variable_id_list)s - AND datetime = %(current_datetime)s - """ - - get_last_updated_datetime_query = """ - SELECT max(last_data_datetime) - FROM wx_stationvariable - WHERE station_id = %(station_id)s - AND variable_id IN %(variable_id_list)s - ORDER BY 1 DESC - """ - - update_last_updated_datetime_query = """ - WITH rd as ( - SELECT station_id - ,variable_id - ,measured - ,code - ,datetime - ,RANK() OVER (PARTITION BY station_id, variable_id ORDER BY datetime DESC) datetime_rank - FROM raw_data - WHERE station_id = %(station_id)s - AND variable_id IN %(variable_id_list)s) - UPDATE wx_stationvariable sv - SET last_data_datetime = rd.datetime - ,last_data_value = rd.measured - ,last_data_code = rd.code - FROM rd - WHERE sv.station_id = rd.station_id - AND sv.variable_id = rd.variable_id - AND rd.datetime_rank = 1 - """ - - create_daily_summary_task_query = """ - INSERT INTO wx_dailysummarytask (station_id, date, created_at, updated_at) - VALUES (%(station_id)s, %(current_datetime)s, now(), now()) - ON CONFLICT DO NOTHING - """ - - create_hourly_summary_task_query = """ - INSERT INTO wx_hourlysummarytask (station_id, datetime, created_at, updated_at) - VALUES (%(station_id)s, %(current_datetime)s, now(), now()) - ON CONFLICT DO NOTHING - """ - - with psycopg2.connect(settings.SURFACE_CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - cursor.execute(delete_query, {"station_id": station_id, "variable_id_list": variable_id_list, - "current_datetime": current_datetime}) - - cursor.execute(create_daily_summary_task_query, - {"station_id": station_id, "current_datetime": request_date}) - cursor.execute(create_hourly_summary_task_query, - {"station_id": station_id, "current_datetime": current_datetime}) - - cursor.execute(get_last_updated_datetime_query, - {"station_id": station_id, "variable_id_list": variable_id_list}) - - last_data_datetime_row = cursor.fetchone() - if last_data_datetime_row is not None: - last_data_datetime = last_data_datetime_row[0] - - if last_data_datetime == current_datetime: - cursor.execute(update_last_updated_datetime_query, - {"station_id": station_id, "variable_id_list": variable_id_list}) - conn.commit() - - return Response(result, status=status.HTTP_200_OK) \ No newline at end of file From 232a2e5a2a8af4ff5add24f9e37720d0e3704afc Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Wed, 12 Jul 2023 11:20:33 -0300 Subject: [PATCH 13/21] Improved equipment history and removed delete option from equipment inventory. --- api/wx/admin.py | 17 +++++++++++++++++ .../equipment_inventory.html | 18 ++---------------- api/wx/urls.py | 2 +- api/wx/views.py | 6 +++--- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/api/wx/admin.py b/api/wx/admin.py index 7af3752..f4b1d45 100644 --- a/api/wx/admin.py +++ b/api/wx/admin.py @@ -354,7 +354,24 @@ class EquipmentTypeAdmin(admin.ModelAdmin): @admin.register(models.Equipment) class EquipmentAdmin(SimpleHistoryAdmin): + def changed_fields(self, obj): + if obj.prev_record: + delta = obj.diff_against(obj.prev_record) + return delta.changed_fields + return None + + def list_changes(self, obj): + fields = "" + if obj.prev_record: + delta = obj.diff_against(obj.prev_record) + + for change in delta.changes: + fields += str("{} changed from {} to {} .
    ".format(change.field, change.old, change.new)) + return format_html(fields) + return None + list_display = ("equipment_type", "manufacturer", "model", "serial_number", "acquisition_date", "first_deploy_date", "last_calibration_date") readonly_fields=('location',) + history_list_display = ["changed_fields","list_changes"] diff --git a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html index aa6345f..1a2dac3 100644 --- a/api/wx/templates/wx/maintenance_reports/equipment_inventory.html +++ b/api/wx/templates/wx/maintenance_reports/equipment_inventory.html @@ -257,20 +257,6 @@
    - - @@ -376,14 +362,14 @@ {% endif %} - {% if perms.wx.delete_equipment %} + diff --git a/api/wx/urls.py b/api/wx/urls.py index 5779b41..3c5f66a 100644 --- a/api/wx/urls.py +++ b/api/wx/urls.py @@ -132,7 +132,7 @@ path('wx/maintenance_reports/equipment_inventory/', views.get_equipment_inventory, name="equipment-inventory"), path('wx/maintenance_reports/equipment_inventory/get/', views.get_equipment_inventory_data), path('wx/maintenance_reports/equipment_inventory/create/', views.create_equipment), - path('wx/maintenance_reports/equipment_inventory/delete/', views.delete_equipment), + # path('wx/maintenance_reports/equipment_inventory/delete/', views.delete_equipment), path('wx/maintenance_reports/equipment_inventory/update/', views.update_equipment), ] diff --git a/api/wx/views.py b/api/wx/views.py index 8bcbf8e..c7b882d 100644 --- a/api/wx/views.py +++ b/api/wx/views.py @@ -4015,7 +4015,7 @@ def equipment_classification(classification): def get_equipment_inventory_data(request): equipment_types = EquipmentType.objects.all() manufacturers = Manufacturer.objects.all() - equipments = Equipment.objects.all() + equipments = Equipment.objects.all().order_by('equipment_type', 'serial_number') funding_sources = FundingSource.objects.all() stations = Station.objects.all() @@ -4130,7 +4130,7 @@ def create_equipment(request): last_calibration_date = last_calibration_date, next_calibration_date = next_calibration_date, decommission_date = decommission_date, - location = location, + # location = location, classification = classification, last_deploy_date = last_deploy_date, ) @@ -4194,7 +4194,7 @@ def update_equipment(request): equipment.last_calibration_date = last_calibration_date equipment.next_calibration_date = next_calibration_date equipment.decommission_date = decommission_date - equipment.location = location + # equipment.location = location equipment.classification = classification equipment.last_deploy_date = last_deploy_date equipment.save() From b17b9c3720911cf9f961b5faada6170ebc884b93 Mon Sep 17 00:00:00 2001 From: AbnerBissolli Date: Tue, 25 Jul 2023 01:09:17 -0300 Subject: [PATCH 14/21] Added maintenance reports creation and update for the new model. --- api/templates/base.html | 2 +- api/wx/admin.py | 19 +- api/wx/migrations/0027_auto_20230725_0352.py | 86 ++ api/wx/models.py | 161 ++- .../maintenance_reports.html | 14 +- .../wx/maintenance_reports/new_report.html | 996 ++++++++++++------ api/wx/urls.py | 4 +- api/wx/views.py | 490 ++++++--- 8 files changed, 1177 insertions(+), 595 deletions(-) create mode 100644 api/wx/migrations/0027_auto_20230725_0352.py diff --git a/api/templates/base.html b/api/templates/base.html index c4047dc..26192a6 100644 --- a/api/templates/base.html +++ b/api/templates/base.html @@ -572,7 +572,7 @@ diff --git a/api/wx/admin.py b/api/wx/admin.py index f4b1d45..e8c5b64 100644 --- a/api/wx/admin.py +++ b/api/wx/admin.py @@ -3,7 +3,7 @@ from import_export.admin import ExportMixin, ImportMixin from wx import models, forms - +from simple_history.utils import update_change_reason @admin.register(models.AdministrativeRegion) class AdministrativeRegionAdmin(admin.ModelAdmin): @@ -320,14 +320,6 @@ class ElementDecoder(admin.ModelAdmin): search_fields = ("element_name", "variable__name", "decoder__name") list_display = ("element_name", "variable_id", "decoder") -# admin.site.register(models.StationComponent) -# class StationComponentAdmin(admin.ModelAdmin): -# list_display = ('name', 'description') - -# @admin.register(models.StationProfileComponent) -# class StationProfileComponentAdmin(admin.ModelAdmin): -# list_display = ('profile', 'presentation_order', 'station_component') - @admin.register(models.VisitType) class VisitTypeAdmin(admin.ModelAdmin): list_display = ("name",) @@ -371,7 +363,14 @@ def list_changes(self, obj): return None list_display = ("equipment_type", "manufacturer", "model", "serial_number", "acquisition_date", "first_deploy_date", "last_calibration_date") - readonly_fields=('location',) history_list_display = ["changed_fields","list_changes"] + def save_model(self, request, obj, form, change): + super().save_model(request, obj, form, change) + update_change_reason(obj, 'Source of change: Admin page') + +@admin.register(models.StationProfileEquipmentType) +class StationProfileEquipmentTypeAdmin(admin.ModelAdmin): + list_display = ("station_profile", "equipment_type", "equipment_type_order") + diff --git a/api/wx/migrations/0027_auto_20230725_0352.py b/api/wx/migrations/0027_auto_20230725_0352.py new file mode 100644 index 0000000..08bb5b7 --- /dev/null +++ b/api/wx/migrations/0027_auto_20230725_0352.py @@ -0,0 +1,86 @@ +# Generated by Django 3.2.13 on 2023-07-25 03:52 + +import ckeditor.fields +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wx', '0026_auto_20230711_1034'), + ] + + operations = [ + migrations.CreateModel( + name='MaintenanceReportEquipment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('equipment_order', models.CharField(choices=[('P', 'Primary Equipment'), ('S', 'Secondary Equipment')], default='F', max_length=1)), + ('condition', ckeditor.fields.RichTextField()), + ('classification', models.CharField(choices=[('F', 'Fully Functional'), ('P', 'Partially Functional'), ('N', 'Not Functional')], default='F', max_length=1, null=True)), + ('equipment_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.equipmenttype')), + ('maintenance_report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wx.maintenancereport')), + ], + ), + migrations.CreateModel( + name='StationProfileEquipmentType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('equipment_type_order', models.IntegerField(validators=[django.core.validators.MinValueValidator(1)])), + ('equipment_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.equipmenttype')), + ('station_profile', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wx.stationprofile')), + ], + options={ + 'unique_together': {('station_profile', 'equipment_type'), ('station_profile', 'equipment_type_order')}, + }, + ), + migrations.AlterUniqueTogether( + name='stationprofilecomponent', + unique_together=None, + ), + migrations.RemoveField( + model_name='stationprofilecomponent', + name='profile', + ), + migrations.RemoveField( + model_name='stationprofilecomponent', + name='station_component', + ), + migrations.RemoveField( + model_name='equipment', + name='location', + ), + migrations.RemoveField( + model_name='historicalequipment', + name='location', + ), + migrations.DeleteModel( + name='MaintenanceReportStationComponent', + ), + migrations.DeleteModel( + name='StationComponent', + ), + migrations.DeleteModel( + name='StationProfileComponent', + ), + migrations.AddField( + model_name='maintenancereportequipment', + name='new_equipment', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='new_equipment', to='wx.equipment'), + ), + migrations.AddField( + model_name='maintenancereportequipment', + name='old_equipment', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='old_equipment', to='wx.equipment'), + ), + migrations.AlterUniqueTogether( + name='maintenancereportequipment', + unique_together={('maintenance_report', 'equipment_type', 'equipment_order'), ('maintenance_report', 'new_equipment')}, + ), + ] diff --git a/api/wx/models.py b/api/wx/models.py index d5dfecf..8ba226e 100644 --- a/api/wx/models.py +++ b/api/wx/models.py @@ -17,7 +17,10 @@ from timescale.db.models.models import TimescaleModel from timescale.db.models.fields import TimescaleDateTimeField from timescale.db.models.managers import TimescaleManager - +from simple_history.models import HistoricalRecords +from django.utils.translation import gettext_lazy # Enumaretor +from datetime import date +from ckeditor.fields import RichTextField class BaseModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) @@ -1276,49 +1279,77 @@ class HFSummaryTask(BaseModel): class Meta: unique_together = ('station', 'variable', 'start_datetime', 'end_datetime') -class VisitType(BaseModel): - name = models.CharField(max_length=64, unique=True, blank=False, null=False) - description = models.CharField(max_length=256, blank=True, null=True) +class Manufacturer(BaseModel): + name = models.CharField(max_length=64) -class Technician(BaseModel): # Singular - name = models.CharField(max_length=64, unique=True, blank=False, null=False) + def __str__(self): + return self.name + -################################################################################################ -# https://github.com/jazzband/django-tinymce -# from tinymce import models as tinymce_models -from ckeditor.fields import RichTextField +class FundingSource(BaseModel): + name = models.CharField(max_length=128) -# https://stackoverflow.com/questions/54802616/how-to-use-enums-as-a-choice-field-in-django-model -from django.utils.translation import gettext_lazy # Enumaretor -from datetime import date + def __str__(self): + return self.name -def no_future(value): - today = date.today() - if value > today: - raise ValidationError('Visit date cannot be in the future.') -class StationComponent(BaseModel): +class EquipmentType(BaseModel): name = models.CharField(max_length=64) description = models.CharField(max_length=256) - # https://stackoverflow.com/questions/7946861/how-can-i-add-a-wsywyg-editor-to-django-admin report_template = RichTextField(blank=True, null=True) class Meta: - verbose_name = "station component" - verbose_name_plural = "station components" + verbose_name = "equipment type" + verbose_name_plural = "equipment types" def __str__(self): - return self.name + return self.name -class StationProfileComponent(BaseModel): - profile = models.ForeignKey(StationProfile, on_delete=models.DO_NOTHING) - presentation_order = models.IntegerField() - station_component = models.ForeignKey(StationComponent, on_delete=models.DO_NOTHING) + +class Equipment(BaseModel): + class EquipmentClassification(models.TextChoices): + FULLY_FUNCTIONAL = 'F', gettext_lazy('Fully Functional') + PARTIALLY_FUNCTIONAL = 'P', gettext_lazy('Partially Functional') + NOT_FUNCTIONAL = 'N', gettext_lazy('Not Functional') + + equipment_type = models.ForeignKey(EquipmentType, on_delete=models.DO_NOTHING) + manufacturer = models.ForeignKey(Manufacturer, on_delete=models.DO_NOTHING) + funding_source = models.ForeignKey(FundingSource, on_delete=models.DO_NOTHING) + model = models.CharField(max_length=64) + serial_number = models.CharField(max_length=64) + acquisition_date = models.DateField() + first_deploy_date = models.DateField(blank=True, null=True) + last_deploy_date = models.DateField(blank=True, null=True) + last_calibration_date = models.DateField(blank=True, null=True) + next_calibration_date = models.DateField(blank=True, null=True) + decommission_date = models.DateField(blank=True, null=True) + classification = models.CharField(max_length=1, choices=EquipmentClassification.choices, null=True) + history = HistoricalRecords() class Meta: - unique_together = ('profile', 'station_component') - unique_together = ('profile', 'presentation_order') + unique_together = ("equipment_type", "serial_number") + verbose_name = "equipment" + verbose_name_plural = "equipment" + + def __str__(self): + return ' '.join((self.equipment_type.name, self.model, self.serial_number)) + + +class VisitType(BaseModel): + name = models.CharField(max_length=64, unique=True, blank=False, null=False) + description = models.CharField(max_length=256, blank=True, null=True) + + +class Technician(BaseModel): # Singular + name = models.CharField(max_length=64, unique=True, blank=False, null=False) + + +def no_future(value): + today = date.today() + if value > today: + raise ValidationError('Visit date cannot be in the future.') + class MaintenanceReport(BaseModel): class Status(models.TextChoices): @@ -1360,73 +1391,33 @@ class Status(models.TextChoices): class Meta: unique_together = ('station', 'visit_date') -class MaintenanceReportStationComponent(BaseModel): - class ComponentClassification(models.TextChoices): - FULLY_FUNCTIONAL = 'F', gettext_lazy('Fully Functional') - PARTIALLY_FUNCTIONAL = 'P', gettext_lazy('Partially Functional') - NOT_FUNCTIONAL = 'N', gettext_lazy('Not Functional') - maintenance_report = models.ForeignKey(MaintenanceReport, on_delete=models.CASCADE) - station_component = models.ForeignKey(StationComponent, on_delete=models.DO_NOTHING) - # https://stackoverflow.com/questions/7946861/how-can-i-add-a-wsywyg-editor-to-django-admin - condition = RichTextField() - component_classification = models.CharField(max_length=1, choices=ComponentClassification.choices, default=ComponentClassification.FULLY_FUNCTIONAL) +class StationProfileEquipmentType(BaseModel): + station_profile = models.ForeignKey(StationProfile, on_delete=models.DO_NOTHING) + equipment_type = models.ForeignKey(EquipmentType, on_delete=models.DO_NOTHING) + equipment_type_order = models.IntegerField(validators=[MinValueValidator(1)]) class Meta: - unique_together = ('maintenance_report', 'station_component') + unique_together = (('station_profile', 'equipment_type'), ('station_profile', 'equipment_type_order')) -class Manufacturer(BaseModel): - name = models.CharField(max_length=64) - - def __str__(self): - return self.name -class EquipmentType(BaseModel): - name = models.CharField(max_length=64) - description = models.CharField(max_length=256) - # https://stackoverflow.com/questions/7946861/how-can-i-add-a-wsywyg-editor-to-django-admin - report_template = RichTextField(blank=True, null=True) - - class Meta: - verbose_name = "equipment type" - verbose_name_plural = "equipment types" - - def __str__(self): - return self.name - -class FundingSource(BaseModel): - name = models.CharField(max_length=128) - - def __str__(self): - return self.name - -from simple_history.models import HistoricalRecords - -class Equipment(BaseModel): +class MaintenanceReportEquipment(BaseModel): class EquipmentClassification(models.TextChoices): FULLY_FUNCTIONAL = 'F', gettext_lazy('Fully Functional') PARTIALLY_FUNCTIONAL = 'P', gettext_lazy('Partially Functional') NOT_FUNCTIONAL = 'N', gettext_lazy('Not Functional') + class EquipmentOrder(models.TextChoices): + PRIMARY_EQUIPMENT = 'P', gettext_lazy('Primary Equipment') + SECONDARY_EQUIPMENT = 'S', gettext_lazy('Secondary Equipment') + + maintenance_report = models.ForeignKey(MaintenanceReport, on_delete=models.CASCADE) equipment_type = models.ForeignKey(EquipmentType, on_delete=models.DO_NOTHING) - manufacturer = models.ForeignKey(Manufacturer, on_delete=models.DO_NOTHING) - funding_source = models.ForeignKey(FundingSource, on_delete=models.DO_NOTHING) - model = models.CharField(max_length=64) - serial_number = models.CharField(max_length=64) - acquisition_date = models.DateField() - first_deploy_date = models.DateField(blank=True, null=True) - last_deploy_date = models.DateField(blank=True, null=True) - last_calibration_date = models.DateField(blank=True, null=True) - next_calibration_date = models.DateField(blank=True, null=True) - decommission_date = models.DateField(blank=True, null=True) - location = models.ForeignKey(Station, on_delete=models.DO_NOTHING, null=True) - classification = models.CharField(max_length=1, choices=EquipmentClassification.choices, null=True) - history = HistoricalRecords() + equipment_order = models.CharField(max_length=1, choices=EquipmentOrder.choices, default=EquipmentClassification.FULLY_FUNCTIONAL) + old_equipment = models.ForeignKey(Equipment, on_delete=models.DO_NOTHING, related_name='old_equipment', null=True) + new_equipment = models.ForeignKey(Equipment, on_delete=models.DO_NOTHING, related_name='new_equipment', null=True) + condition = RichTextField() + classification = models.CharField(max_length=1, choices=EquipmentClassification.choices, default=EquipmentClassification.FULLY_FUNCTIONAL, null=True) class Meta: - unique_together = ("equipment_type", "serial_number") - verbose_name = "equipment" - verbose_name_plural = "equipment" - - def __str__(self): - return ' '.join((self.equipment_type.name, self.model, self.serial_number)) \ No newline at end of file + unique_together = (('maintenance_report', 'new_equipment'), ('maintenance_report', 'equipment_type', 'equipment_order')) diff --git a/api/wx/templates/wx/maintenance_reports/maintenance_reports.html b/api/wx/templates/wx/maintenance_reports/maintenance_reports.html index ee858ae..fd42d40 100644 --- a/api/wx/templates/wx/maintenance_reports/maintenance_reports.html +++ b/api/wx/templates/wx/maintenance_reports/maintenance_reports.html @@ -131,16 +131,16 @@
    Search Form
    Download Maintenance Report - +