Skip to content

Commit

Permalink
feat: updated qaqc filters to use custom filtering and nullchecks
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Feb 2, 2024
1 parent 4668c27 commit c58750a
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 65 deletions.
64 changes: 64 additions & 0 deletions app/backend/wells/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from django import forms
from django.core.exceptions import FieldDoesNotExist
from django.db import connection
from django.contrib.gis.db import models
from django.http import HttpRequest, QueryDict
from django.contrib.gis.geos import GEOSException, Polygon, GEOSGeometry, Point
from django.contrib.gis.gdal import GDALException
Expand Down Expand Up @@ -828,3 +829,66 @@ def filter_queryset(self, request, queryset, view):
return queryset.order_by(*ordering)

return queryset


class WellQaQcFilterBackend(filters.DjangoFilterBackend):
"""
Custom well list filtering logic for the QaQc Dashboard.
allows additional 'filter_group' params.
"""
def filter_queryset(self, request, queryset, view):
try:
filter_groups = request.query_params.getlist('filter_group', [])
for group in filter_groups:
try:
group_params = json.loads(group)
except ValueError as exc:
raise ValidationError({
'filter_group': 'Error parsing JSON data: {}'.format(exc),
})

if not group_params:
continue

q_objects = Q()
for field, value in group_params.items():
if field == 'well_tag_number':
queryset = queryset.filter(well_tag_number__icontains=value)

elif field == 'identification_plate_number':
queryset = queryset.filter(identification_plate_number__icontains=value)

elif field == 'person_responsible_name' and value == 'null':
q_objects |= (Q(person_responsible__isnull=True) | Q(person_responsible__first_name__isnull=True) |
Q(person_responsible__first_name='') | Q(person_responsible__first_name=' '))

elif field == 'company_of_person_responsible_name' and value == 'null':
q_objects |= (Q(company_of_person_responsible__isnull=True) |
Q(company_of_person_responsible__name__isnull=True) |
Q(company_of_person_responsible__name='') | Q(company_of_person_responsible__name=' '))

# Directly handle special cases for fields like latitude and longitude
elif field in ['latitude', 'longitude']:
if value == 'null':
q_objects &= Q(**{'geom__isnull': True})

elif value == 'null':
# Check if the field exists in the model
try:
field_obj = queryset.model._meta.get_field(field)
except models.FieldDoesNotExist:
continue

# Now handling CharField, including checks for empty strings and spaces
if isinstance(field_obj, models.CharField):
q_objects &= (Q(**{f'{field}__isnull': True}) | Q(**{f'{field}': ''}) | Q(**{f'{field}': ' '}))
else:
# For other field types, just check for null
q_objects &= Q(**{f'{field}__isnull': True})

# Apply the combined Q object filters to the queryset
queryset = queryset.filter(q_objects)

except Exception as e:
print(e)
return queryset
8 changes: 6 additions & 2 deletions app/backend/wells/serializers_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,12 +506,16 @@ class Meta:
class RecordComplianceSerializer(serializers.ModelSerializer):
work_start_date = serializers.DateField(read_only=True)
work_end_date = serializers.DateField(read_only=True)
company_of_person_responsible_name = serializers.ReadOnlyField(
source='company_of_person_responsible.name')
person_responsible_name = serializers.ReadOnlyField(source='person_responsible.name')

class Meta:
model = Well
fields = [
'well_tag_number',
'identification_plate_number',
'intended_water_use',
'well_class',
'latitude',
'longitude',
Expand All @@ -523,8 +527,8 @@ class Meta:
'well_status',
'work_start_date',
'work_end_date',
'person_responsible',
'company_of_person_responsible',
'person_responsible_name',
'company_of_person_responsible_name',
'create_date',
'create_user',
'natural_resource_region',
Expand Down
53 changes: 26 additions & 27 deletions app/backend/wells/views_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
WellListFilterBackend,
WellListOrderingFilter,
GeometryFilterBackend,
RadiusFilterBackend
RadiusFilterBackend,
WellQaQcFilterBackend
)
from wells.models import Well, WellAttachment, \
WELL_STATUS_CODE_ALTERATION, WELL_STATUS_CODE_CONSTRUCTION, WELL_STATUS_CODE_DECOMMISSION
Expand Down Expand Up @@ -592,8 +593,8 @@ class MislocatedWellsListView(ListAPIView):
pagination_class = APILimitOffsetPagination

# Allow searching on name fields, names of related companies, etc.
filter_backends = (WellListFilterBackend, BoundingBoxFilterBackend,
filters.SearchFilter, WellListOrderingFilter, GeometryFilterBackend)
filter_backends = (WellQaQcFilterBackend, filters.SearchFilter)

ordering = ('well_tag_number',)

def get_queryset(self):
Expand All @@ -616,8 +617,7 @@ class RecordComplianceListView(ListAPIView):
pagination_class = APILimitOffsetPagination

# Allow searching on name fields, names of related companies, etc.
filter_backends = (WellListFilterBackend, BoundingBoxFilterBackend,
filters.SearchFilter, WellListOrderingFilter, GeometryFilterBackend)
filter_backends = (WellQaQcFilterBackend, filters.SearchFilter)
ordering = ('well_tag_number',)

def get_queryset(self):
Expand All @@ -628,26 +628,26 @@ def get_queryset(self):
queryset = get_annotated_well_queryset()

# Filtering for records missing any of the specified fields
missing_info_filter = (
Q(well_tag_number__isnull=True) |
Q(identification_plate_number__isnull=True) |
Q(well_class__isnull=True) |
Q(geom__isnull=True) | # for latitude and longitude
Q(finished_well_depth__isnull=True) |
Q(surface_seal_depth__isnull=True) |
Q(surface_seal_thickness__isnull=True) |
Q(aquifer_lithology__isnull=True) |
Q(well_status__isnull=True) |
Q(work_start_date__isnull=True) |
Q(work_end_date__isnull=True) |
Q(person_responsible__isnull=True) |
Q(company_of_person_responsible__isnull=True) |
Q(create_date__isnull=True) |
Q(create_user__isnull=True) |
Q(natural_resource_region__isnull=True)
)

queryset = queryset.filter(missing_info_filter)
# missing_info_filter = (
# Q(well_tag_number__isnull=True) |
# Q(identification_plate_number__isnull=True) |
# Q(well_class__isnull=True) |
# Q(geom__isnull=True) | # for latitude and longitude
# Q(finished_well_depth__isnull=True) |
# Q(surface_seal_depth__isnull=True) |
# Q(surface_seal_thickness__isnull=True) |
# Q(aquifer_lithology__isnull=True) |
# Q(well_status__isnull=True) |
# Q(work_start_date__isnull=True) |
# Q(work_end_date__isnull=True) |
# Q(person_responsible__isnull=True) |
# Q(company_of_person_responsible__isnull=True) |
# Q(create_date__isnull=True) |
# Q(create_user__isnull=True) |
# Q(natural_resource_region__isnull=True)
# )

# queryset = queryset.filter(missing_info_filter)

# Additional filtering based on query parameters
work_start_date = self.request.query_params.get('work_start_date')
Expand All @@ -670,8 +670,7 @@ class CrossReferencingListView(ListAPIView):
pagination_class = APILimitOffsetPagination

# Allow searching on name fields, names of related companies, etc.
filter_backends = (WellListFilterBackend, BoundingBoxFilterBackend,
filters.SearchFilter, WellListOrderingFilter, GeometryFilterBackend)
filter_backends = (WellQaQcFilterBackend, filters.SearchFilter)
ordering = ('well_tag_number',)

def get_queryset(self):
Expand Down
50 changes: 38 additions & 12 deletions app/frontend/src/qaqc/components/QaQcFilters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/
<template>
<b-form-row class="search-result-filter" :class="`search-result-filter-${type}`">
<b-col v-if="type === 'text' || type === 'number' || type === 'select' || type === 'radio'">
<b-col v-if="type === 'text' || type === 'number' || type === 'select' || type === 'radio' || type === 'nullcheck'">
<b-form-input
v-if="type === 'text'"
type="text"
Expand Down Expand Up @@ -45,6 +45,16 @@
:text-field="textField"
v-model="localValue[paramNames[0]]"
@keyup.enter.native="applyFilter()" />
<b-col v-else-if="type === 'nullcheck'">
<b-button
variant="button"
class="py-2 px-3"
:class="{'active-filter-border': isActive, 'active-filter': isActive}"
@click.prevent="toggleNullFilter">
Null Fields
<span class="fa fa-sm mb-1 pl-1" :class="{'fa-check': !isActive, 'fa-times': isActive}" :aria-label="isActive ? 'Clear' : 'Apply'" />
</b-button>
</b-col>
<b-form-invalid-feedback :id="`${id}InvalidFeedback`">
<div v-for="(error, index) in errors" :key="`${id}Input error ${index}`">
{{ error }}
Expand Down Expand Up @@ -73,7 +83,7 @@
v-model="localValue[paramNames[1]]"
@keyup.enter.native="applyFilter()" />
</b-col>
<b-col :sm="(type === 'text') ? 3 : 2">
<b-col v-if="type !== 'nullcheck'" :sm="(type === 'text') ? 3 : 2">
<b-button
variant="link"
class="py-2 px-0"
Expand All @@ -87,16 +97,6 @@
</template>

<script>
/**
* example usage in another component:
*
* <search-result-filter
* id="ownerNameFilter"
* v-model="ownerNameInput"
* placeholder="Type a name!"
* :errors="errors['ownerName']"/> // errors for individual fields must be an array e.g. ['Name already taken']
*
*/
export default {
props: {
id: { // an ID for the form group that will be used to generate IDs for the related components
Expand Down Expand Up @@ -159,6 +159,22 @@ export default {
return localValue
},
toggleNullFilter () {
// Check if the null filter is already applied
if (this.localValue[this.paramNames[0]] === 'null') {
// If yes, clear the filter
this.clearFilter()
} else {
// If no, set the null filter
this.applyNullFilter()
}
},
applyNullFilter () {
// Set the field to a special value that represents 'null'
this.$set(this.localValue, this.paramNames[0], 'null')
// Emit an update event (or directly apply the filter if handled within the component)
this.applyFilter()
},
applyFilter () {
if (this.hasLocalValue) {
this.$emit('input', this.localValue)
Expand All @@ -184,6 +200,16 @@ export default {

<style lang="scss">
.active-filter-border {
border: 5px solid #007bff;
}
.active-filter {
background-color: #007bff;
color: white;
border-color: #007bff;
}
.search-result-filter {
min-width: 8rem;
Expand Down
40 changes: 27 additions & 13 deletions app/frontend/src/qaqc/components/QaQcTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
<tr v-for="row in results" :key="row.well_tag_number" @mousedown="searchResultsRowClicked(row)">
<td v-for="column in columns" :key="column.id" class="data">
<template v-if="column.param === 'well_tag_number'">
<router-link :to="{ name: 'wells-detail', params: {id: row.well_tag_number} }">{{ row.well_tag_number }}</router-link>
<a :href="`/wells-detail/${row.well_tag_number}`" target="_blank">{{ row.well_tag_number }}</a>
</template>
<template v-else-if="column.param === 'street_address'">
{{ row | streetAddressFormat }}
Expand Down Expand Up @@ -240,23 +240,24 @@ export default {
'wellTagNumber': 'WTN',
'identificationPlateNumber': 'WIDP',
'wellClass': 'Class of well',
'latitude': 'Lat',
'longitude': 'Lon',
'finishedWellDepth': 'Finished well depth (feet)',
'diameter': 'Casing Diameter (inches)',
'surfaceSealDepth': 'Seal Depth (feet)',
'surfaceSealThickness': 'Seal Thickness (inches)',
'aquiferLithology': 'Lithology',
'wellStatus': 'Work Type',
'dateOfWork': 'Work Start Date',
'personResponsible': 'Person Responsible',
'orgResponsible': 'Company that did the work',
'intendedWaterUse': 'Intended Water Use',
'latitudeNull': 'Lat',
'longitudeNull': 'Lon',
'finishedWellDepthNull': 'Finished well depth (feet)',
'diameterNull': 'Casing Diameter (inches)',
'surfaceSealDepthNull': 'Seal Depth (feet)',
'surfaceSealThicknessNull': 'Seal Thickness (inches)',
'aquiferLithologyNull': 'Lithology',
'wellStatusNull': 'Work Type',
'dateOfWorkNull': 'Work Start Date',
'personResponsibleNull': 'Person Responsible',
'orgResponsibleNull': 'Company that did the work',
'createDate': 'Created Date',
'createUser': 'Created By',
'updateDate': 'Updated Date',
'updateUser': 'Updated By',
'internalOfficeComments': 'Internal Office Comments',
'internalComments': 'Notes',
'internalComments': 'Internal Comments',
'geocodeDistance': 'Geocode Distance',
'distanceToPid': 'Distance to Matching PID',
'scoreAddress': 'Score Address',
Expand Down Expand Up @@ -399,6 +400,19 @@ export default {
}
}
#qaqcTable thead {
position: sticky;
top: 0;
background-color: white; /* or any color that matches your design */
z-index: 1; /* This ensures the header stays on top of other content */
}
.table-responsive {
/* Ensure the container allows the sticky positioning to work */
overflow-y: auto;
height: 400px; /* Adjust the height as needed */
}
/* Spinner styles — these can be removed when moving to bootstrap 4.3 */
$spinner-width: 2rem !default;
Expand Down
23 changes: 12 additions & 11 deletions app/frontend/src/qaqc/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,18 @@ export const RECORD_COMPLIANCE_COLUMNS = [
'wellTagNumber',
'identificationPlateNumber',
'wellClass',
'latitude',
'longitude',
'finishedWellDepth',
'diameter',
'surfaceSealDepth',
'surfaceSealThickness',
'aquiferLithology',
'wellStatus',
'dateOfWork',
'personResponsible',
'orgResponsible',
'intendedWaterUse',
'latitudeNull',
'longitudeNull',
'finishedWellDepthNull',
'diameterNull',
'surfaceSealDepthNull',
'surfaceSealThicknessNull',
'aquiferLithologyNull',
'wellStatusNull',
'dateOfWorkNull',
'personResponsibleNull',
'orgResponsibleNull',
'createDate',
'createUser',
'naturalResourceRegion',
Expand Down
Loading

0 comments on commit c58750a

Please sign in to comment.