Example #1
0
    def get(self, request, owners=None, format=None):
        """
        Return sets of parcels grouped by owner
        """

        CODLogger.instance().log_api_call(name=__name__, msg=request.path)

        # TODO get cache working?
        # cache_key = 'ownership_groups'
        # cached_content = cache.get(cache_key, None)
        # if cached_content:
        #     return Response(cached_content)

        # hardcode the owners, for own - TODO fix this
        owners = [
            'DETROIT LAND BANK AUTHORITY', 'CITY OF DETROIT-P&DD',
            'MI LAND BANK FAST TRACK AUTH', 'HANTZ WOODLANDS LLC', 'TAXPAYER'
        ]

        # excecute the search
        parcels = ParcelMaster.objects.filter(ownername1__in=owners)

        content = [{
            "pnum": parcel.pnum,
            "address": parcel.propstreetcombined,
            "owner_name": parcel.ownername1
        } for parcel in parcels]

        # cache.set(cache_key, content, 60 * 60)

        return Response(content)
Example #2
0
def get_escrow_data(request, item_num, param=None):
    """
    Returns escrow data for the given item num.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    # Only allow certain servers to call this endpoint
    if security.block_client(request):  # pragma: no cover
        remote_addr = request.META.get('REMOTE_ADDR')
        SlackMsgHandler().send_admin_alert(
            "Address {} was blocked from subscribing waste alerts".format(
                remote_addr))
        return Response("Invalid caller ip or host name: " + remote_addr,
                        status=status.HTTP_403_FORBIDDEN)

    # Only call via https...
    if not request.is_secure():
        return Response({"error": "must be secure"},
                        status=status.HTTP_403_FORBIDDEN)

    balances = EscrowBalance.objects.filter(item_num=item_num)
    if not balances.exists():
        raise Http404("No escrow data found for item number " + str(item_num))

    return Response(balances.first().to_json())
Example #3
0
def get_auth_token(request):

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    if not request.is_secure():
        return Response({"error": "must be secure"},
                        status=status.HTTP_403_FORBIDDEN)

    if not request.body or request.body == b'{}':
        return Response({"required": ["email", "password"]},
                        status=status.HTTP_400_BAD_REQUEST)

    # Parse the data and get email and password
    data = json.loads(request.body.decode('utf-8'))
    if not data.get('email') or not data.get('password'):
        return Response({"required": ["email", "password"]},
                        status=status.HTTP_400_BAD_REQUEST)

    email = data['email']
    password = data['password']

    # Try to authenticate the user
    # Note: we are using email for username
    user = authenticate_user(email, password)
    if not user:
        return Response({"user not authorized": email},
                        status=status.HTTP_401_UNAUTHORIZED)

    # Authentication succeeded:  return an auth token
    token, created = Token.objects.using('photo_survey').get_or_create(
        user=user)
    return Response({"token": token.key}, status=status.HTTP_201_CREATED)
Example #4
0
def get_sales_property_address(request,
                               address=None,
                               years_back=None,
                               format=None):
    """
    Retrieve property info via address
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    # urls with dots are problematic: substitute underscores for dots in the url
    # (and replace underscores with dots here)
    address = address.replace('_', '.')

    # Search for parcels with the given parcel num
    # pnum = request.path_info.split('/')[2]
    results = Sales.objects.filter(addresscombined__contains=address)

    # filter recent-years only?
    if years_back != None:
        results = filter_years_back(results, years_back)

    # if no results found, return 404
    if len(results) == 0:
        raise Http404("Address " + address + " not found")

    return get_parcels(results)
Example #5
0
    def delete(self, request, username, parcel_id, format=None):
        """
        Do a soft-delete on a user's favorite.
        """

        CODLogger.instance().log_api_call(name=__name__, msg=request.path)

        if not request.is_secure():
            return Response({"error": "must be secure"},
                            status=status.HTTP_403_FORBIDDEN)

        parcel_id = get_parcel_id(request.path, 5)

        users = User.objects.using('photo_survey').filter(username=username)
        if not users:
            return Response({"User not found": username},
                            status=status.HTTP_404_NOT_FOUND)

        favorites = Survey.objects.filter(
            survey_type__survey_template_id='bridging_neighborhoods').filter(
                user_id=users[0].id).filter(parcel__parcel_id=parcel_id)
        if not favorites:
            return Response({"favorite not found": parcel_id},
                            status=status.HTTP_404_NOT_FOUND)

        for favorite in favorites:
            favorite.status = 'deleted'
            favorite.save()

        return Response(status=status.HTTP_204_NO_CONTENT)
Example #6
0
def get_dte_active_connection(request, parcel_id, param=None):
    """
    Returns data cached for the given data source, updating the data whenever necessary.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    # Only allow certain servers to call this endpoint
    if security.block_client(request):  # pragma: no cover
        remote_addr = request.META.get('REMOTE_ADDR')
        SlackMsgHandler().send_admin_alert(
            "Address {} was blocked from subscribing waste alerts".format(
                remote_addr))
        return Response("Invalid caller ip or host name: " + remote_addr,
                        status=status.HTTP_403_FORBIDDEN)

    # Only call via https...
    if not request.is_secure():
        return Response({"error": "must be secure"},
                        status=status.HTTP_403_FORBIDDEN)

    parcel_id = get_parcel_id(request.path, 4)

    if not DTEActiveGasSite.objects.filter(parcel_id=parcel_id).exists():
        raise Http404("No parcel " + parcel_id +
                      " found with active connection")

    return Response({"active": True})
Example #7
0
def get_schedule_details(request,
                         waste_area_ids=None,
                         year=None,
                         month=None,
                         format=None):
    """
    List details to the waste collection schedule for a waste area
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    # Throw error if there is an unrecognized query param
    for param in request.query_params.keys():
        if param not in ['today']:
            return Response("Invalid param: " + param,
                            status=status.HTTP_400_BAD_REQUEST)

    # Allow caller to specify what 'today' is
    today = request.query_params.get('today')
    if not today:
        today = datetime.date.today()

    if type(today) is str:
        today = datetime.date(int(today[0:4]), int(today[4:6]),
                              int(today[6:8]))

    # get details that apply citywide
    citywide_details = ScheduleDetail.objects.filter(waste_area_ids__exact='')

    wa_ids = [int(wa_id) for wa_id in waste_area_ids.split(',')]

    wa_details = ScheduleDetail.objects.none()

    # get waste schedule details for each waste area requested
    for wa_id in wa_ids:
        wa_details = wa_details | ScheduleDetail.objects.filter(
            waste_area_ids__contains=wa_id)

    if month or year:
        citywide_details = util.filter_month(year, month, citywide_details)
        wa_details = util.filter_month(year, month, wa_details)

    # sort the different sets of results by 'normal_day'
    details = sorted(chain(citywide_details, wa_details),
                     key=attrgetter('sort_value'))

    # get next pickup for each route
    next_pickups = get_next_pickups(wa_ids, details, today)

    # build an array of json objects, one for each detail
    content = {
        'next_pickups': next_pickups,
        'details': [detail.json() for detail in details]
    }

    return Response(content)
Example #8
0
def get_sales_property_address_recent(request, address=None, format=None):
    """
    Retrieve property info via address, for recent years only
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    return get_sales_property_address(request._request,
                                      address=address,
                                      years_back=5,
                                      format=format)
Example #9
0
def get_sales_property_recent(request, pnum=None, format=None):
    """
    Retrieve property info via parcel id (aka 'pnum'), for recent years only
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    return get_sales_property(request._request,
                              pnum=pnum,
                              years_back=5,
                              format=format)
Example #10
0
def get_survey(request, survey_id):
    """
    Get photos and survey data for the given parcel.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    surveys = Survey.objects.filter(id=int(survey_id)).exclude(
        status='deleted')
    if not surveys:
        return Response({"survey not found": survey_id},
                        status=status.HTTP_404_NOT_FOUND)

    return Response(get_survey_data(surveys[0]))
Example #11
0
def get_survey_count(request, parcel_id):
    """
    Get number of surveys that exist currently for the given parcel.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    parcel_id = get_parcel_id(request.path, 3)

    count = Survey.objects.filter(parcel__parcel_id=parcel_id).exclude(
        status='deleted').count()
    content = {"count": count}

    return Response(content)
Example #12
0
def get_status_summary(request):
    """
    Returns general info:  number of parcels that currently have surveys, etc.
    TODO:  add any other metadata here?
    e.g., add in this, but for residential / commercial:
    num_parcels_total = ParcelMaster.objects.count()
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    num_parcels_surveyed = Survey.objects.values(
        'parcel_id').distinct().count()

    return Response({"num_parcels_surveyed": num_parcels_surveyed})
Example #13
0
def get_image(request, id, format=None):
    """
    Retrieve an image from the assessors database.
    """

    # REVIEW TODO add frank's suggestions

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    sketches = Sketch.objects.filter(id=int(id))
    if not sketches:
        raise Http404("Sketch id " + id + " not found")

    sketch = sketches.first()

    return HttpResponse(sketch.imageData, content_type="image/jpg")
Example #14
0
def get_latest_survey(request, parcel_id):
    """
    Returns latest survey data for the parcel.
    TODO return latest survey that has a 'good' status.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    parcel_id = get_parcel_id(request.path, 4)

    survey = Survey.objects.filter(parcel__parcel_id=parcel_id).exclude(
        status='deleted').last()

    content = get_survey_data(survey) if survey else {}

    return Response(content)
Example #15
0
def get_status(request):
    """
    Returns all parcels that have already been surveyed.  Each parcel is paired with the number of
    times it has been surveyed.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    parcel_counts = ParcelMetadata.objects.all().annotate(
        count=Count('survey'))
    content = {
        parcel_count.parcel_id: parcel_count.count
        for parcel_count in parcel_counts
    }

    return Response(content)
Example #16
0
def get_ticket_info(request, ticket_id):
    """
    Returns list of all tickets for a given date.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    table_name = "[COURT36TICKETS].[SWEETSpower].[tblZTickets]"
    if settings.RUNNING_UNITTESTS:
        table_name = "tblZTickets"

    tickets = Tblztickets.objects.raw(
        "select * from {} where zticketid = {}".format(table_name, ticket_id))
    ticket_list = [ticket.zticketid for ticket in tickets]
    content = {"tickets": ticket_list}

    return Response(content)
Example #17
0
def get_surveyor_survey_count(request):
    """
    Returns number of surveys each surveyor has completed.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    user_info = {user.id: user for user in User.objects.using('photo_survey')}
    survey_counts = Survey.objects.values('user_id').annotate(
        count=Count('parcel_id', distinct=True)).order_by('-count')

    results = []
    for survey_count in survey_counts:
        user = user_info[int(survey_count['user_id'])]
        results.append({user.email: survey_count['count']})

    return Response(results)
Example #18
0
def confirm(request, client_id):
    """
    Parse subscription confirmation and send a simple response.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    client = get_existing_object(cl_type=MessengerClient,
                                 obj_id=client_id,
                                 cl_name="Client",
                                 required=True)

    msg_handler = get_messenger_msg_handler(client)

    # Make sure the call came from twilio and is valid
    msg_handler.validate(request)

    # Verify required fields are present
    if not request.data.get('From') or not request.data.get('Body'):
        return Response({"error": "From and body values are required"},
                        status=status.HTTP_400_BAD_REQUEST)

    # Clean up phone number
    phone_number = MsgHandler.get_fone_number(request)

    # Retrieve the subscriber
    subscribers = MessengerSubscriber.objects.filter(phone_number=phone_number,
                                                     messenger_clients=client)
    if not subscribers.exists():
        message = "Subscriber for phone_number {phone_number} for client {client_name} not found".format(
            phone_number=phone_number, client_name=client.name)
        raise NotFound(detail={"error": message})

    subscriber = subscribers.get()

    # Update the subscriber's status
    subscriber.change_status(activate=True)

    # Let the subscriber know their notifications were activated.
    # REVIEW:  make message configurable?
    msg_handler.send_text(phone_number=phone_number,
                          text="Your {} alerts have been activated".format(
                              client.name))

    return Response({"subscriber": str(subscriber), "message": phone_number})
Example #19
0
def get_parcel(request, pnum=None, format=None):
    """
    Return parcel data from the assessors dataset
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    # clean up the pnum
    pnum = get_parcel_id(request.path, 3)

    # excecute the search
    parcels = BSAPARCELDATA.objects.filter(PARCELNO=pnum)
    if not parcels:
        raise Http404("Parcel id " + pnum + " not found")

    content = parcels[0].to_json()
    content['field_descriptions'] = util.get_parcel_descriptions()

    return Response(content)
Example #20
0
def get_rental_cases(request, pnum=None, format=None):
    """
    Return rental unit cases from tidemark (oracle)
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    # TODO need massive cleanup for tidemark pnums
    # clean up the pnum
    # pnum = util.clean_pnum(pnum)

    # excecute the search
    casemains = CaseMain.objects.filter(
        prc_parcel_no__prc_parcel_no__exact=pnum)
    if not casemains.exists():
        raise Http404("Parcel id " + pnum + " not found")

    content = [casemain.to_json() for casemain in casemains]

    return Response(content)
Example #21
0
def get_images(request, pnum=None, format=None):
    """
    Retrieve all images for the given parcel.
    """

    # REVIEW TODO add frank's suggestions

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    # clean up the pnum
    pnum = get_parcel_id(request.path, 2)
    sketch_data = Sketch.objects.filter(pnum=pnum).order_by('-date')
    content = []

    # REVIEW (TODO) remove the call to move_files() ...

    for sketch in sketch_data:

        content.append(sketch.to_json())

    return Response(content)
Example #22
0
def subscribe_web(request, client_id):
    """
    Parse subscription request and text user request for confirmation.

{
    "phone_number": "2124831691",
    "address": "27 Montclair Rd",
    "lang": "en|es|ar|bn" (optional - default is "en")
}

    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    client = get_existing_object(cl_type=MessengerClient,
                                 obj_id=client_id,
                                 cl_name="Client",
                                 required=True)

    msg_handler = get_messenger_msg_handler(client)

    # Verify required fields are present
    if not request.data.get('phone_number') or not request.data.get('address'):
        return Response({"error": "Address and phone number are required"},
                        status=status.HTTP_400_BAD_REQUEST)

    # Clean up phone number
    phone_number_from = MsgHandler.get_fone_number(request, key='phone_number')

    # Clean up street address
    street_address = MsgHandler.get_address(request=request, key='address')

    lang = request.data.get("lang", "en")

    return subscriber_helper(phone_number_from=phone_number_from,
                             msg_handler=msg_handler,
                             client=client,
                             street_address=street_address,
                             lang=lang,
                             text_signup=False)
Example #23
0
def get_notifications(request, client_id=None):
    """
    Returns all notifications for a client.
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    if client_id:

        return Response(get_notifications_helper(client_id=client_id))

    else:

        response = []

        for client in MessengerClient.objects.all():

            response.append(
                get_notifications_helper(client_id=client.id,
                                         client_only=True))

        return Response(response)
Example #24
0
def add_notification_message(request, notification_id, message_id=None):
    """
    Adds or updates a message for a notification.

    {
        "lang": "es",
        "message": "<insert message here>"
    }
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    notification = get_existing_object(cl_type=MessengerNotification,
                                       obj_id=notification_id,
                                       cl_name="Notification",
                                       required=True)

    messenger_message = get_existing_object(cl_type=MessengerMessage,
                                            obj_id=message_id,
                                            cl_name="Message")

    if not request.data.get('lang') or not request.data.get('message'):
        return Response({"error": "lang and message are required"},
                        status=status.HTTP_400_BAD_REQUEST)

    lang = request.data['lang']
    message = request.data['message']

    if not messenger_message:
        messenger_message = MessengerMessage(
            messenger_notification=notification, lang=lang, message=message)
    else:
        messenger_message.lang = lang
        messenger_message.message = message

    messenger_message.save()

    return Response(messenger_message.to_json(),
                    status=status.HTTP_201_CREATED)
Example #25
0
def get_sales_property(request, pnum=None, years_back=None, format=None):
    """
    Retrieve property info via parcel id (aka 'pnum')
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    pnum = get_parcel_id(request.path, 2)

    # Search for parcels with the given parcel num
    # pnum = request.path_info.split('/')[2]
    results = Sales.objects.filter(pnum__iexact=pnum)

    # filter recent-years only?
    if years_back != None:
        results = filter_years_back(results, years_back)

    # if no results found, return 404
    if len(results) == 0:
        raise Http404("Parcel id " + pnum + " not found")

    return get_parcels(results)
Example #26
0
def get_metadata(request, parcel_id):
    """
    Get photos and survey data for the given parcel
    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    parcel_id = get_parcel_id(request.path, 2)

    # TODO possibly optimize the images on the disk?
    # https://hugogiraudel.com/2013/07/29/optimizing-with-bash/
    # e.g., jpegtran -progressive image_raw.jpg small.jpg

    images = []
    image_metadata = ImageMetadata.objects.filter(parcel__parcel_id=parcel_id)
    for img_meta in image_metadata:
        url = request.build_absolute_uri(
            location='/data/photo_survey/images/' + img_meta.image.file_path)
        images.append(url)

    surveys = [
        survey.id for survey in Survey.objects.filter(
            parcel__parcel_id=parcel_id).exclude(status='deleted')
    ]

    # Is this parcel publicly owned?
    # TODO: make sure the dataset for this eventually 'goes live' (currently we load it
    # whenever dexter slusarski gets a new csv with this content)
    public_property_data = PublicPropertyData.objects.filter(
        parcelno=parcel_id)
    publicly_owned = len(public_property_data) > 0

    return Response({
        "images": images,
        "surveys": surveys,
        "publicly_owned": publicly_owned
    })
Example #27
0
    def get(self, request, username, format=None):
        """
        Returns list of parcels (houses) a resident is interested in.
        """

        CODLogger.instance().log_api_call(name=__name__, msg=request.path)

        if not request.is_secure():
            return Response({"error": "must be secure"},
                            status=status.HTTP_403_FORBIDDEN)

        users = User.objects.using('photo_survey').filter(username=username)
        if not users:
            return Response({"User not found": username},
                            status=status.HTTP_404_NOT_FOUND)

        surveys = Survey.objects.filter(user_id=users[0].id).exclude(
            status='deleted')
        favorites = {
            survey.parcel.parcel_id: self.describe_favorite(survey)
            for survey in surveys
        }

        return Response({"favorites": favorites})
Example #28
0
def subscribe(request):
    """
    Parse subscription request and text user request for confirmation.

{
  'From': [
    '5005550007'
  ],
  'Body': [
    '7840 van dyke pl'
  ],
  'To': [
    '5005550006'
  ]
}

    """

    CODLogger.instance().log_api_call(name=__name__, msg=request.path)

    # # Only allow certain servers to call this endpoint
    # if cod_utils.security.block_client(request):
    #     remote_addr = request.META.get('REMOTE_ADDR')
    #     SlackMsgHandler().send_admin_alert("Address {} was blocked from subscribing waste alerts".format(remote_addr))
    #     return Response("Invalid caller ip or host name: " + remote_addr, status=status.HTTP_403_FORBIDDEN)

    # Verify required fields are present
    if not request.data.get('From') or not request.data.get('Body'):
        return Response({"error": "Address and phone_number are required"},
                        status=status.HTTP_400_BAD_REQUEST)

    # Clean up phone numbers
    phone_number_from = MsgHandler.get_fone_number(request, key='From')
    phone_number_to = MsgHandler.get_fone_number(request, key='To')

    # Make sure phone number is set up and valid.
    if not MessengerPhoneNumber.objects.filter(
            phone_number=phone_number_to).exists():
        return Response(
            {
                "error":
                "phone_number {phone_number_to} not found".format(
                    phone_number_to=phone_number_to)
            },
            status=status.HTTP_404_NOT_FOUND)

    # Figure out what messenger client this is and get the msg handler for this client.
    phone_number_to_object = MessengerPhoneNumber.objects.get(
        phone_number=phone_number_to)
    client = phone_number_to_object.messenger_client

    msg_handler = get_messenger_msg_handler(client)

    # Make sure the call came from twilio and is valid
    # Note: would be nice to validate earlier but we haven't yet been
    # able to construct our msg_handler until now...
    msg_handler.validate(request)

    # Clean up street address
    street_address = MsgHandler.get_address(request=request)

    return subscriber_helper(phone_number_from=phone_number_from,
                             msg_handler=msg_handler,
                             client=client,
                             street_address=street_address,
                             lang='en',
                             text_signup=True)
Example #29
0
    def update_or_create_from_dict(data):
        """
        Using dictionary of form data, update existing subscriber or create new one
        """

        if not data.get('phone_number') or not data.get('address'):
            return None, {"error": "address and phone_number are required"}

        phone_number = data['phone_number']
        street_address = data['address']

        if not re.fullmatch(r'[\d]{10}', phone_number):
            return None, {
                "error": "phone_number must be 9 digits, no punctuation"
            }

        waste_area_ids = data.get('waste_area_ids')
        if not waste_area_ids:
            # Parse address string and get result from AddressPoint geocoder
            location, address = util.geocode_address(
                street_address=street_address)
            if not location:
                invalid_addr_msg = 'Invalid waste reminder text signup: {} from {}'.format(
                    street_address, phone_number)

                CODLogger.instance().log_error(
                    name=__name__,
                    area="waste notifier signup by text",
                    msg=invalid_addr_msg)

                SlackMsgHandler().send_admin_alert(invalid_addr_msg)

                msg = "Unfortunately, address {} could not be located - please text the street address only, for example '1301 3rd ave'".format(
                    street_address)
                text_signup_number = settings.AUTO_LOADED_DATA[
                    "WASTE_REMINDER_TEXT_SIGNUP_NUMBERS"][0]
                get_dpw_msg_handler().send_text(
                    phone_number=phone_number,
                    phone_sender=text_signup_number,
                    text=msg)

                return None, {"error": "Address not found"}

            waste_area_ids = get_waste_area_ids(location=location)

        if type(waste_area_ids) == list:
            waste_area_ids = ''.join(
                [str(num) + ',' for num in waste_area_ids])

        # update existing subscriber or create new one
        subscriber = Subscriber.objects.none()
        previous = Subscriber.objects.filter(phone_number__exact=phone_number)
        if previous.exists():
            subscriber = previous[0]
            subscriber.phone_number = phone_number
            subscriber.waste_area_ids = waste_area_ids
        else:
            # try to create a subscriber with the posted data
            subscriber = Subscriber(phone_number=phone_number,
                                    waste_area_ids=waste_area_ids)

        # set service type
        if data.get("service_type"):
            subscriber.service_type = data['service_type'].replace('|', ',')

        # check for optional values
        for value in ['address', 'latitude', 'longitude']:
            if data.get(value):
                setattr(subscriber, value, data.get(value))

        # validate and save subscriber
        subscriber.clean()
        subscriber.save()

        return subscriber, None
Example #30
0
    def post(self, request, parcel_id, format=None):
        """
        Post results of a field survey
        """

        CODLogger.instance().log_api_call(name=__name__, msg=request.path)

        if not request.is_secure():
            return Response({"error": "must be secure"},
                            status=status.HTTP_403_FORBIDDEN)

        # TODO remove this once we get mod_wsgi passing the authorization header through properly
        auth_meta = request.META.get('HTTP_AUTHORIZATION')
        if auth_meta:
            print(auth_meta)
        else:
            print('saw no http auth meta')

        data = json.loads(request.body.decode('utf-8'))

        # What user is posting this?
        user = self.get_surveyor(request, data)

        if user == None:
            return Response({"error": "user not authorized"},
                            status=status.HTTP_401_UNAUTHORIZED)

        survey_template_id = self.get_survey_template_id(data)
        parcel_id = get_parcel_id(request.path, self.get_parcel_id_offset())
        answer_errors = {}

        # Is the parcel id valid?
        if not ParcelMaster.objects.filter(pnum__iexact=parcel_id).exists():
            return Response({"invalid parcel id": parcel_id},
                            status=status.HTTP_400_BAD_REQUEST)

        # What are our questions and answers?
        questions = SurveyQuestion.objects.filter(
            survey_type__survey_template_id=survey_template_id).order_by(
                'question_number')
        if not questions:
            return Response({"invalid survey": data['survey_id']},
                            status=status.HTTP_400_BAD_REQUEST)

        answers = {answer['question_id']: answer for answer in data['answers']}

        # Report any answers that did not match a question_id
        question_ids = {question.question_id for question in questions}
        orphaned_answers = {
            key
            for key in answers.keys() if key not in question_ids
        }
        if orphaned_answers:
            return Response({"invalid question ids": list(orphaned_answers)},
                            status=status.HTTP_400_BAD_REQUEST)

        # Validate each answer
        for question in questions:
            answer = answers.get(question.question_id)
            if answer and answer.get('answer'):
                if not question.is_valid(answer['answer']):
                    answer_errors[
                        question.question_id] = "question answer is invalid"
                elif question.answer_trigger and question.answer_trigger_action:
                    # TODO clean this up - add 'skip to question' feature
                    if re.fullmatch(
                            question.answer_trigger, answer['answer']
                    ) and question.answer_trigger_action == 'exit':
                        break
            elif SurveyorView.is_answer_required(question, answers):
                answer_errors[
                    question.question_id] = "question answer is required"

        # Report invalid content?
        if answer_errors:
            return Response(answer_errors, status=status.HTTP_400_BAD_REQUEST)

        parcel, created = ParcelMetadata.objects.get_or_create(
            parcel_id=parcel_id)

        # Create the survey
        survey_type = SurveyType.objects.get(
            survey_template_id=survey_template_id)
        survey = Survey(survey_type=survey_type,
                        user_id=str(user.id),
                        parcel=parcel,
                        common_name=data.get('common_name', ''),
                        note=data.get('note', ''),
                        status=data.get('status', ''),
                        image_url=data.get('image_url', ''))
        survey.save()

        # Save all the answers
        for survey_question in questions:
            answer = answers.get(survey_question.question_id)
            if answer and answer['answer']:
                survey_answer = SurveyAnswer(survey=survey,
                                             survey_question=survey_question,
                                             answer=answer['answer'],
                                             note=answer.get('answer', None))
                survey_answer.save()

        # Indicate number of surveys present for each parcel id in the request
        parcel_info = self.check_parcels(data.get('parcel_ids', []))

        return Response({
            "answers": answers,
            "parcel_survey_info": parcel_info
        },
                        status=status.HTTP_201_CREATED)