diff --git a/Dockerfile b/Dockerfile
index 4c464fdf..d250560a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -19,7 +19,7 @@ RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
#
# Read more on Dockerfile best practices at the source:
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices
-RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client nodejs
+RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client nodejs npm
# Inside the container, create an app directory and switch into it
RUN mkdir /app
diff --git a/parserator_web/static/js/index.js b/parserator_web/static/js/index.js
index 492674cc..07bacd96 100644
--- a/parserator_web/static/js/index.js
+++ b/parserator_web/static/js/index.js
@@ -1,2 +1,97 @@
-/* TODO: Flesh this out to connect the form to the API and render results
- in the #address-results div. */
+const $form = $(".form");
+const $addressInput = $("#address");
+const $addressResults = $("#address-results");
+const $parseType = $("#parse-type");
+const $resultsTableBody = $("tbody");
+const $errorMessage = $("
")
+ .attr("id", "error-message")
+ .addClass("text-danger d-inline-block mx-4");
+const DEFAULT_ERROR_MESSAGE = "Could not parse address";
+const API_URL = "/api/parse/"
+
+$form.on("submit", (evt) => {
+ evt.preventDefault();
+ handleSubmit();
+});
+
+/** handleSubmit:
+ * Handles user form input;
+ * calls functions to fetch parsed data and display results/errors
+ */
+async function handleSubmit() {
+ const input = $addressInput.val();
+
+ try {
+ const parsedAddressData = await fetchParsedAddressData(input);
+ renderResults(
+ parsedAddressData.address_components,
+ parsedAddressData.address_type
+ );
+ } catch (e) {
+ renderErrorMessage(e.message);
+ }
+}
+
+/** fetchParsedAddressData:
+ * fetches parsed address data from API
+ * @param {String} address
+ * @returns {Object} parsed address data, from JSON response
+ * @throws Error on bad request
+ */
+async function fetchParsedAddressData(address) {
+ const params = new URLSearchParams({ address });
+ const response = await fetch(`${API_URL}?${params}`);
+ const parsedAddressData = await response.json();
+
+ if (response.status != 200) {
+ throw new Error(parsedAddressData.error || DEFAULT_ERROR_MESSAGE);
+ }
+
+ return parsedAddressData;
+}
+
+/** renderResults:
+ * Displays parsed address components and information in UI
+ * @param {Object} addressComponents tagged address components
+ * @param {String} addressType
+ */
+function renderResults(addressComponents, addressType) {
+ clearResultsDisplay();
+
+ $parseType.text(addressType);
+ fillResultsTableBody(addressComponents);
+}
+
+/** renderErrorMessage:
+ * Displays an error message in UI
+ * @param {String} message error message
+ */
+function renderErrorMessage(message) {
+ $errorMessage.text(message);
+ $form.append($errorMessage);
+}
+
+/** fillResultsTableBody:
+ * builds displayed table of address components in UI
+ * @param {Object} addressComponents like {component: tag, ...}
+ */
+function fillResultsTableBody(addressComponents) {
+ for (const addressPart in addressComponents) {
+ const $row = $("
");
+ const tag = addressComponents[addressPart];
+ $row.append(
+ $("| ").text(addressPart),
+ $(" | ").text(tag)
+ );
+ $resultsTableBody.append($row);
+ }
+}
+
+/** clearResultsDisplay:
+ * resets UI, in order to display new results
+ */
+function clearResultsDisplay() {
+ $errorMessage.remove();
+ $resultsTableBody.empty();
+ $addressResults.removeClass("d-none");
+}
diff --git a/parserator_web/templates/parserator_web/base.html b/parserator_web/templates/parserator_web/base.html
index 8de4f844..0d532a4b 100644
--- a/parserator_web/templates/parserator_web/base.html
+++ b/parserator_web/templates/parserator_web/base.html
@@ -81,7 +81,15 @@
/>
- ... and you!
+ ... and
+
+ Noah Appelbaum!
+
diff --git a/parserator_web/templates/parserator_web/index.html b/parserator_web/templates/parserator_web/index.html
index a72d9c80..b70c738a 100644
--- a/parserator_web/templates/parserator_web/index.html
+++ b/parserator_web/templates/parserator_web/index.html
@@ -17,8 +17,7 @@ U.S. addres
-
-
+
Parsing results
Address type:
diff --git a/parserator_web/views.py b/parserator_web/views.py
index 0be3f4a9..01bf7454 100644
--- a/parserator_web/views.py
+++ b/parserator_web/views.py
@@ -1,9 +1,11 @@
import usaddress
+from usaddress import RepeatedLabelError
from django.views.generic import TemplateView
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer
from rest_framework.exceptions import ParseError
+from rest_framework import status
class Home(TemplateView):
@@ -14,11 +16,44 @@ class AddressParse(APIView):
renderer_classes = [JSONRenderer]
def get(self, request):
- # TODO: Flesh out this method to parse an address string using the
- # parse() method and return the parsed components to the frontend.
- return Response({})
+ """
+ API endpoint to parse a U.S. address
+ expects a string "address" query
+ responds with JSON, like:
+ {
+ "input_string": "123 main street"
+ "address_components": {"AddressNumber": "123", ...},
+ "addres_type": "Street Address"
+ }
+ """
+ form_data = request.query_params
+ if ("address" not in form_data):
+ raise ParseError
+
+ input_string = form_data["address"]
+
+ try:
+ (address_components, address_type) = self.parse(input_string)
+ response_object = {}
+ response_object["input_string"] = input_string
+ response_object["address_components"] = address_components
+ response_object["address_type"] = address_type
+
+ return Response(response_object)
+
+ except RepeatedLabelError:
+ error_message = "Repeated Label Error; check that address is correct"
+ return Response(
+ {"error": error_message}, status=status.HTTP_400_BAD_REQUEST
+ )
+ # TODO: Add except clauses for any other errors raised by usaddress
def parse(self, address):
- # TODO: Implement this method to return the parsed components of a
- # given address using usaddress: https://github.com/datamade/usaddress
+ """
+ Parses a United States address string into its component parts,
+ using usaddress: https://github.com/datamade/usaddress.
+
+ -> address_components: OrderedDict, address_type: str
+ """
+ (address_components, address_type) = usaddress.tag(address)
return address_components, address_type
diff --git a/tests/test_views.py b/tests/test_views.py
index bfd5d0b7..e2476dba 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -1,15 +1,43 @@
-import pytest
+import json
+
+parse_url = "/api/parse/"
def test_api_parse_succeeds(client):
- # TODO: Finish this test. Send a request to the API and confirm that the
- # data comes back in the appropriate format.
address_string = '123 main st chicago il'
- pytest.fail()
+ response = client.get(parse_url, {"address": address_string})
+
+ assert response.status_code == 200
+
+ parsed_data = json.loads(response.content)
+ assert parsed_data == {
+ "input_string": "123 main st chicago il",
+ "address_components": {
+ "AddressNumber": "123",
+ "StreetName": "main",
+ "StreetNamePostType": "st",
+ "PlaceName": "chicago",
+ "StateName": "il"
+ },
+ "address_type": "Street Address"
+ }
def test_api_parse_raises_error(client):
- # TODO: Finish this test. The address_string below will raise a
- # RepeatedLabelError, so ParseAddress.parse() will not be able to parse it.
address_string = '123 main st chicago il 123 main st'
- pytest.fail()
+ response = client.get(parse_url, {"address": address_string})
+
+ assert response.status_code == 400
+
+
+def test_api_parse_raises_error_on_missing_address_string(client):
+ response = client.get(parse_url, {})
+
+ assert response.status_code == 400
+
+
+def test_api_parse_succeeds_on_empty_string(client):
+ address_string = ""
+ response = client.get(parse_url, {"address": address_string})
+
+ assert response.status_code == 200
|