Skip to content

Commit

Permalink
Merge pull request #33420 from dimagi/ze/create-gps-capture-page
Browse files Browse the repository at this point in the history
Create GPS capture page
  • Loading branch information
zandre-eng authored Sep 13, 2023
2 parents 8cc8ef9 + 95cfd32 commit ed47f49
Show file tree
Hide file tree
Showing 18 changed files with 817 additions and 44 deletions.
4 changes: 2 additions & 2 deletions corehq/apps/data_dictionary/tests/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from corehq.apps.data_dictionary.models import CaseProperty, CasePropertyGroup, CasePropertyAllowedValue, CaseType
from corehq.apps.domain.shortcuts import create_domain
from corehq.apps.geospatial.const import GEO_POINT_CASE_PROPERTY
from corehq.apps.geospatial.const import GPS_POINT_CASE_PROPERTY
from corehq.apps.users.models import WebUser, HqPermissions
from corehq.apps.users.models_role import UserRole

Expand Down Expand Up @@ -390,6 +390,6 @@ def test_get_json_success(self, *args):
"properties": [],
}
],
"geo_case_property": GEO_POINT_CASE_PROPERTY,
"geo_case_property": GPS_POINT_CASE_PROPERTY,
}
self.assertEqual(response.json(), expected_response)
1 change: 1 addition & 0 deletions corehq/apps/es/es_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ def builtin_filters(self):
filters.doc_id,
filters.nested,
filters.regexp,
filters.wildcard,
]

def __getattr__(self, attr):
Expand Down
4 changes: 4 additions & 0 deletions corehq/apps/es/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def prefix(field, value):
return {"prefix": {field: value}}


def wildcard(field, value):
return {"wildcard": {field: value}}


def term(field, value):
"""
Filter docs by a field
Expand Down
38 changes: 38 additions & 0 deletions corehq/apps/es/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def builtin_filters(self):
is_active,
username,
metadata,
missing_or_empty_metadata_property,
] + super(UserES, self).builtin_filters

def show_inactive(self):
Expand Down Expand Up @@ -243,3 +244,40 @@ def metadata(key, value):
queries.match(field='user_data_es.value', search_string=value),
)
)


def _missing_metadata_property(property_name):
"""
A metadata property doesn't exist.
"""
return filters.NOT(
queries.nested(
'user_data_es',
filters.term(field='user_data_es.key', value=property_name),
)
)


def _missing_metadata_value(property_name):
"""
A metadata property exists but has an empty string value.
"""
return queries.nested(
'user_data_es',
filters.AND(
filters.term('user_data_es.key', property_name),
filters.NOT(
filters.wildcard(field='user_data_es.value', value='*')
)
)
)


def missing_or_empty_metadata_property(property_name):
"""
A metadata property doesn't exist, or does exist but has an empty string value.
"""
return filters.OR(
_missing_metadata_property(property_name),
_missing_metadata_value(property_name),
)
2 changes: 1 addition & 1 deletion corehq/apps/geospatial/const.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

GEO_POINT_CASE_PROPERTY = 'commcare_gps_point'
GPS_POINT_CASE_PROPERTY = 'gps_point'

# Modified version of https://geojson.org/schema/FeatureCollection.json
# Modification 1 - Added top-level name attribute
Expand Down
23 changes: 23 additions & 0 deletions corehq/apps/geospatial/migrations/0003_auto_20230908_0927.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.20 on 2023-09-08 09:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('geospatial', '0002_geoconfig'),
]

operations = [
migrations.AlterField(
model_name='geoconfig',
name='case_location_property_name',
field=models.CharField(default='gps_point', max_length=256),
),
migrations.AlterField(
model_name='geoconfig',
name='user_location_property_name',
field=models.CharField(default='gps_point', max_length=256),
),
]
6 changes: 3 additions & 3 deletions corehq/apps/geospatial/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from shapely.geometry import Point

from .exceptions import InvalidCoordinate, InvalidDistributionParam
from corehq.apps.geospatial.const import GEO_POINT_CASE_PROPERTY
from corehq.apps.geospatial.const import GPS_POINT_CASE_PROPERTY


@dataclass
Expand Down Expand Up @@ -143,5 +143,5 @@ class GeoConfig(models.Model):

domain = models.CharField(max_length=256, db_index=True, primary_key=True)
location_data_source = models.CharField(max_length=126, default=CUSTOM_USER_PROPERTY)
user_location_property_name = models.CharField(max_length=256, default=GEO_POINT_CASE_PROPERTY)
case_location_property_name = models.CharField(max_length=256, default=GEO_POINT_CASE_PROPERTY)
user_location_property_name = models.CharField(max_length=256, default=GPS_POINT_CASE_PROPERTY)
case_location_property_name = models.CharField(max_length=256, default=GPS_POINT_CASE_PROPERTY)
3 changes: 1 addition & 2 deletions corehq/apps/geospatial/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
from django.utils.translation import gettext as _
from jsonobject.exceptions import BadValueError


from corehq.apps.geospatial.dispatchers import CaseManagementMapDispatcher
from corehq.apps.reports.standard import ProjectReport
from corehq.apps.reports.standard.cases.basic import CaseListMixin
from corehq.apps.reports.standard.cases.data_sources import CaseDisplayES
from couchforms.geopoint import GeoPoint
from .const import GEO_POINT_CASE_PROPERTY
from .models import GeoPolygon
from .utils import get_geo_case_property


class CaseManagementMap(ProjectReport, CaseListMixin):
name = gettext_noop("Case Management Map")
slug = "case_management_map"
Expand Down
146 changes: 146 additions & 0 deletions corehq/apps/geospatial/static/geospatial/js/gps_capture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
hqDefine("geospatial/js/gps_capture",[
"jquery",
"knockout",
'underscore',
'hqwebapp/js/initial_page_data',
"hqwebapp/js/bootstrap3/components.ko", // for pagination
], function (
$,
ko,
_,
initialPageData
) {
'use strict';

var dataItemModel = function (options, dataType) {
options = options || {};
options = _.defaults(options, {
name: '',
id: '', // Can be case_id or user_id
lat: '',
lon: '',
});

var self = ko.mapping.fromJS(options);
self.url = ko.observable();
self.dataType = dataType;
if (self.dataType === 'user') {
self.url(initialPageData.reverse('edit_commcare_user', options.id));
} else {
self.url(initialPageData.reverse('case_data', options.id));
}
self.hasUnsavedChanges = ko.observable(false);
self.isLatValid = ko.observable(true);
self.isLonValid = ko.observable(true);

self.onMapCaptureStart = function () {
// TODO: Implement this function
};

self.onValueChanged = function () {
self.hasUnsavedChanges(true);

self.lat(self.lat().substr(0, 20));
self.lon(self.lon().substr(0, 20));

let latNum = parseFloat(self.lat());
let lonNum = parseFloat(self.lon());

const latValid = (!isNaN(latNum) && latNum >= -90 && latNum <= 90) || !self.lat().length;
self.isLatValid(latValid);
const lonValid = (!isNaN(lonNum) && lonNum >= -180 && lonNum <= 180) || !self.lon().length;
self.isLonValid(lonValid);
};

self.canSaveRow = ko.computed(function () {
const isValidInput = self.isLatValid() && self.isLonValid();
return self.lat().length && self.lon().length && self.hasUnsavedChanges() && isValidInput;
});

return self;
};

var dataItemListModel = function () {
var self = {};
self.dataItems = ko.observableArray([]); // Can be cases or users

self.itemsPerPage = ko.observable(5);
self.totalItems = ko.observable(0);
self.query = ko.observable('');

self.dataType = initialPageData.get('data_type');

self.showLoadingSpinner = ko.observable(true);
self.showPaginationSpinner = ko.observable(false);
self.hasError = ko.observable(false);
self.hasSubmissionError = ko.observable(false);
self.isSubmissionSuccess = ko.observable(false);
self.showTable = ko.computed(function () {
return !self.showLoadingSpinner() && !self.hasError();
});

self.goToPage = function (pageNumber) {
self.dataItems.removeAll();
self.hasError(false);
self.showPaginationSpinner(true);
self.showLoadingSpinner(true);
$.ajax({
method: 'GET',
url: initialPageData.reverse('get_paginated_cases_or_users_without_gps'),
data: {
page: pageNumber || 1,
limit: self.itemsPerPage(),
data_type: self.dataType,
query: self.query(),
},
success: function (data) {
self.dataItems(_.map(data.items, function (inData) {
return dataItemModel(inData, self.dataType);
}));
self.totalItems(data.total);

self.showLoadingSpinner(false);
self.showPaginationSpinner(false);

},
error: function () {
self.showLoadingSpinner(false);
self.showPaginationSpinner(false);
self.hasError(true);
},
});
};

self.onPaginationLoad = function () {
self.goToPage(1);
};

self.saveDataRow = function (dataItem) {
self.isSubmissionSuccess(false);
self.hasSubmissionError(false);
$.ajax({
method: 'POST',
url: initialPageData.reverse('gps_capture'),
data: JSON.stringify({
'data_type': self.dataType,
'data_item': ko.mapping.toJS(dataItem),
}),
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function () {
dataItem.hasUnsavedChanges(false);
self.isSubmissionSuccess(true);
},
error: function () {
self.hasSubmissionError(true);
},
});
};

return self;
};

$(function () {
$("#no-gps-list").koApplyBindings(dataItemListModel());
});
});
Loading

0 comments on commit ed47f49

Please sign in to comment.