diff --git a/requirements.in b/requirements.in index abc17b3..d2fd409 100644 --- a/requirements.in +++ b/requirements.in @@ -98,3 +98,6 @@ google-cloud-storage # Metrics reporting prometheus_client + +# Reverse Geocoding for validating lat/lng +reverse_geocoder \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d842826..cdd06fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -422,6 +422,34 @@ mypy-extensions==0.4.3 \ # via # black # typing-inspect +numpy==1.20.2 \ + --hash=sha256:2428b109306075d89d21135bdd6b785f132a1f5a3260c371cee1fae427e12727 \ + --hash=sha256:377751954da04d4a6950191b20539066b4e19e3b559d4695399c5e8e3e683bf6 \ + --hash=sha256:4703b9e937df83f5b6b7447ca5912b5f5f297aba45f91dbbbc63ff9278c7aa98 \ + --hash=sha256:471c0571d0895c68da309dacee4e95a0811d0a9f9f532a48dc1bea5f3b7ad2b7 \ + --hash=sha256:61d5b4cf73622e4d0c6b83408a16631b670fc045afd6540679aa35591a17fe6d \ + --hash=sha256:6c915ee7dba1071554e70a3664a839fbc033e1d6528199d4621eeaaa5487ccd2 \ + --hash=sha256:6e51e417d9ae2e7848314994e6fc3832c9d426abce9328cf7571eefceb43e6c9 \ + --hash=sha256:719656636c48be22c23641859ff2419b27b6bdf844b36a2447cb39caceb00935 \ + --hash=sha256:780ae5284cb770ade51d4b4a7dce4faa554eb1d88a56d0e8b9f35fca9b0270ff \ + --hash=sha256:878922bf5ad7550aa044aa9301d417e2d3ae50f0f577de92051d739ac6096cee \ + --hash=sha256:924dc3f83de20437de95a73516f36e09918e9c9c18d5eac520062c49191025fb \ + --hash=sha256:97ce8b8ace7d3b9288d88177e66ee75480fb79b9cf745e91ecfe65d91a856042 \ + --hash=sha256:9c0fab855ae790ca74b27e55240fe4f2a36a364a3f1ebcfd1fb5ac4088f1cec3 \ + --hash=sha256:9cab23439eb1ebfed1aaec9cd42b7dc50fc96d5cd3147da348d9161f0501ada5 \ + --hash=sha256:a8e6859913ec8eeef3dbe9aed3bf475347642d1cdd6217c30f28dee8903528e6 \ + --hash=sha256:aa046527c04688af680217fffac61eec2350ef3f3d7320c07fd33f5c6e7b4d5f \ + --hash=sha256:abc81829c4039e7e4c30f7897938fa5d4916a09c2c7eb9b244b7a35ddc9656f4 \ + --hash=sha256:bad70051de2c50b1a6259a6df1daaafe8c480ca98132da98976d8591c412e737 \ + --hash=sha256:c73a7975d77f15f7f68dacfb2bca3d3f479f158313642e8ea9058eea06637931 \ + --hash=sha256:d15007f857d6995db15195217afdbddfcd203dfaa0ba6878a2f580eaf810ecd6 \ + --hash=sha256:d76061ae5cab49b83a8cf3feacefc2053fac672728802ac137dd8c4123397677 \ + --hash=sha256:e8e4fbbb7e7634f263c5b0150a629342cc19b47c5eba8d1cd4363ab3455ab576 \ + --hash=sha256:e9459f40244bb02b2f14f6af0cd0732791d72232bbb0dc4bab57ef88e75f6935 \ + --hash=sha256:edb1f041a9146dcf02cd7df7187db46ab524b9af2515f392f337c7cbbf5b52cd + # via + # reverse-geocoder + # scipy oauthlib==3.1.0 \ --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea @@ -731,6 +759,9 @@ requests==2.25.1 \ # requests-mock # requests-oauthlib # social-auth-core +reverse-geocoder==1.5.1 \ + --hash=sha256:2a2e781b5f69376d922b78fe8978f1350c84fce0ddb07e02c834ecf98b57c75c + # via -r requirements.in rfc3986[idna2008]==1.4.0 \ --hash=sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d \ --hash=sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50 @@ -741,6 +772,27 @@ rsa==4.7.2 \ # via # google-auth # python-jose +scipy==1.6.2 \ + --hash=sha256:03f1fd3574d544456325dae502facdf5c9f81cbfe12808a5e67a737613b7ba8c \ + --hash=sha256:0c81ea1a95b4c9e0a8424cf9484b7b8fa7ef57169d7bcc0dfcfc23e3d7c81a12 \ + --hash=sha256:1fba8a214c89b995e3721670e66f7053da82e7e5d0fe6b31d8e4b19922a9315e \ + --hash=sha256:37f4c2fb904c0ba54163e03993ce3544c9c5cde104bcf90614f17d85bdfbb431 \ + --hash=sha256:50e5bcd9d45262725e652611bb104ac0919fd25ecb78c22f5282afabd0b2e189 \ + --hash=sha256:6ca1058cb5bd45388041a7c3c11c4b2bd58867ac9db71db912501df77be2c4a4 \ + --hash=sha256:77f7a057724545b7e097bfdca5c6006bed8580768cd6621bb1330aedf49afba5 \ + --hash=sha256:816951e73d253a41fa2fd5f956f8e8d9ac94148a9a2039e7db56994520582bf2 \ + --hash=sha256:96620240b393d155097618bcd6935d7578e85959e55e3105490bbbf2f594c7ad \ + --hash=sha256:993c86513272bc84c451349b10ee4376652ab21f312b0554fdee831d593b6c02 \ + --hash=sha256:adf7cee8e5c92b05f2252af498f77c7214a2296d009fc5478fc432c2f8fb953b \ + --hash=sha256:bc52d4d70863141bb7e2f8fd4d98e41d77375606cde50af65f1243ce2d7853e8 \ + --hash=sha256:c1d3f771c19af00e1a36f749bd0a0690cc64632783383bc68f77587358feb5a4 \ + --hash=sha256:d744657c27c128e357de2f0fd532c09c84cd6e4933e8232895a872e67059ac37 \ + --hash=sha256:e3e9742bad925c421d39e699daa8d396c57535582cba90017d17f926b61c1552 \ + --hash=sha256:e547f84cd52343ac2d56df0ab08d3e9cc202338e7d09fafe286d6c069ddacb31 \ + --hash=sha256:e89091e6a8e211269e23f049473b2fde0c0e5ae0dd5bd276c3fc91b97da83480 \ + --hash=sha256:e9da33e21c9bc1b92c20b5328adb13e5f193b924c9b969cd700c8908f315aa59 \ + --hash=sha256:ffdfb09315896c6e9ac739bb6e13a19255b698c24e6b28314426fd40a1180822 + # via reverse-geocoder sentry-sdk==1.0.0 \ --hash=sha256:71de00c9711926816f750bc0f57ef2abbcb1bfbdf5378c601df7ec978f44857a \ --hash=sha256:9221e985f425913204989d0e0e1cbb719e8b7fa10540f1bc509f660c06a34e66 diff --git a/vaccinate/core/models.py b/vaccinate/core/models.py index 97e881b..702c28e 100644 --- a/vaccinate/core/models.py +++ b/vaccinate/core/models.py @@ -3,7 +3,9 @@ import beeline import pytz +import reverse_geocoder from django.conf import settings +from django.core.exceptions import ValidationError from django.db import models from django.db.models import Max, Q from django.db.models.signals import m2m_changed @@ -422,6 +424,17 @@ def save(self, *args, **kwargs): if set_public_id_later: Location.objects.filter(pk=self.pk).update(public_id=self.pid) + def clean(self): + results = reverse_geocoder.search((self.latitude, self.longitude)) + if len(results) == 0: + raise ValidationError( + "Couldn't reverse lookup that latitude and longitude!" + ) + elif results[0]["admin1"] != self.state.name: + raise ValidationError( + f"That latitude and longitide are not in {self.state.name}!" + ) + class Reporter(models.Model): """ diff --git a/vaccinate/core/test_admin.py b/vaccinate/core/test_admin.py index 5d97a17..8c0c159 100644 --- a/vaccinate/core/test_admin.py +++ b/vaccinate/core/test_admin.py @@ -24,8 +24,8 @@ def test_admin_create_location_sets_public_id(admin_client): "name": "hello", "state": State.objects.get(abbreviation="OR").id, "location_type": "1", - "latitude": "0", - "longitude": "0", + "latitude": "45.518941976546756", + "longitude": "-122.70686202698961", "_save": "Save", }, )