Cleaner API
This commit is contained in:
parent
9424f81959
commit
b285c270aa
@ -13,6 +13,7 @@ import arrow
|
||||
from sqlalchemy import (
|
||||
Column, DateTime, Enum, Float, SmallInteger, String, Text
|
||||
)
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
from flatisfy.database.base import BASE
|
||||
from flatisfy.database.types import MagicJSON
|
||||
@ -96,6 +97,62 @@ class Flat(BASE):
|
||||
# Date for visit
|
||||
visit_date = Column(DateTime)
|
||||
|
||||
@validates('utilities')
|
||||
def validate_utilities(self, _, utilities):
|
||||
"""
|
||||
Utilities validation method
|
||||
"""
|
||||
if isinstance(utilities, FlatUtilities):
|
||||
return utilities
|
||||
|
||||
if utilities == "C.C.":
|
||||
return FlatUtilities.included
|
||||
elif utilities == "H.C.":
|
||||
return FlatUtilities.excluded
|
||||
else:
|
||||
return FlatUtilities.unknown
|
||||
|
||||
@validates("status")
|
||||
def validate_status(self, _, status):
|
||||
"""
|
||||
Status validation method
|
||||
"""
|
||||
if isinstance(status, FlatStatus):
|
||||
return status
|
||||
|
||||
try:
|
||||
return getattr(FlatStatus, status)
|
||||
except (AttributeError, TypeError):
|
||||
LOGGER.warn("Unkown flat status %s, ignoring it.",
|
||||
status)
|
||||
return self.status.default.arg
|
||||
|
||||
@validates("notation")
|
||||
def validate_status(self, _, notation):
|
||||
"""
|
||||
Notation validation method
|
||||
"""
|
||||
try:
|
||||
notation = int(notation)
|
||||
assert notation >= 0 and notation <= 5
|
||||
except (ValueError, AssertionError):
|
||||
raise ValueError('notation should be an integer between 0 and 5')
|
||||
return notation
|
||||
|
||||
@validates("date")
|
||||
def validate_date(self, _, date):
|
||||
"""
|
||||
Date validation method
|
||||
"""
|
||||
return arrow.get(date).naive
|
||||
|
||||
@validates("visit_date")
|
||||
def validate_visit_date(self, _, visit_date):
|
||||
"""
|
||||
Visit date validation method
|
||||
"""
|
||||
return arrow.get(visit_date).naive
|
||||
|
||||
@staticmethod
|
||||
def from_dict(flat_dict):
|
||||
"""
|
||||
@ -119,29 +176,6 @@ class Flat(BASE):
|
||||
)
|
||||
del flat_dict["flatisfy"]
|
||||
|
||||
# Handle utilities field
|
||||
if not isinstance(flat_dict["utilities"], FlatUtilities):
|
||||
if flat_dict["utilities"] == "C.C.":
|
||||
flat_dict["utilities"] = FlatUtilities.included
|
||||
elif flat_dict["utilities"] == "H.C.":
|
||||
flat_dict["utilities"] = FlatUtilities.excluded
|
||||
else:
|
||||
flat_dict["utilities"] = FlatUtilities.unknown
|
||||
|
||||
# Handle status field
|
||||
flat_status = flat_dict.get("status", "new")
|
||||
if not isinstance(flat_status, FlatStatus):
|
||||
try:
|
||||
flat_dict["status"] = getattr(FlatStatus, flat_status)
|
||||
except AttributeError:
|
||||
if "status" in flat_dict:
|
||||
del flat_dict["status"]
|
||||
LOGGER.warn("Unkown flat status %s, ignoring it.",
|
||||
flat_status)
|
||||
|
||||
# Handle date field
|
||||
flat_dict["date"] = arrow.get(flat_dict["date"]).naive
|
||||
|
||||
flat_object = Flat()
|
||||
# Using a __dict__.update() call to make it work even if there are
|
||||
# extra keys in flat_dict which are not valid kwargs for Flat model.
|
||||
@ -151,7 +185,6 @@ class Flat(BASE):
|
||||
def __repr__(self):
|
||||
return "<Flat(id=%s, urls=%s)>" % (self.id, self.urls)
|
||||
|
||||
|
||||
def json_api_repr(self):
|
||||
"""
|
||||
Return a dict representation of this flat object that is JSON
|
||||
|
@ -73,20 +73,11 @@ def get_app(config):
|
||||
api_routes.time_to_places_v1)
|
||||
|
||||
app.route("/api/v1/flats", "GET", api_routes.flats_v1)
|
||||
app.route("/api/v1/flats/status/:status", "GET",
|
||||
api_routes.flats_by_status_v1)
|
||||
app.route("/api/v1/flats/:flat_id", "GET", api_routes.flat_v1)
|
||||
app.route("/api/v1/flats/:flat_id", "PATCH",
|
||||
api_routes.update_flat_v1)
|
||||
|
||||
app.route("/api/v1/flat/:flat_id", "GET", api_routes.flat_v1)
|
||||
app.route("/api/v1/flat/:flat_id/status", "POST",
|
||||
api_routes.update_flat_status_v1)
|
||||
app.route("/api/v1/flat/:flat_id/notes", "POST",
|
||||
api_routes.update_flat_notes_v1)
|
||||
app.route("/api/v1/flat/:flat_id/notation", "POST",
|
||||
api_routes.update_flat_notation_v1)
|
||||
app.route("/api/v1/flat/:flat_id/visit_date", "POST",
|
||||
api_routes.update_flat_visit_date_v1)
|
||||
|
||||
app.route("/api/v1/visits.ics", "GET",
|
||||
app.route("/api/v1/ics/visits.ics", "GET",
|
||||
api_routes.ics_feed_v1)
|
||||
|
||||
app.route("/api/v1/search", "POST", api_routes.search_v1)
|
||||
|
@ -46,7 +46,7 @@ export const getFlats = function (callback) {
|
||||
|
||||
export const getFlat = function (flatId, callback) {
|
||||
fetch(
|
||||
'/api/v1/flat/' + encodeURIComponent(flatId),
|
||||
'/api/v1/flats/' + encodeURIComponent(flatId),
|
||||
{ credentials: 'same-origin' }
|
||||
)
|
||||
.then(function (response) {
|
||||
@ -61,10 +61,10 @@ export const getFlat = function (flatId, callback) {
|
||||
|
||||
export const updateFlatStatus = function (flatId, newStatus, callback) {
|
||||
fetch(
|
||||
'/api/v1/flat/' + encodeURIComponent(flatId) + '/status',
|
||||
'/api/v1/flats/' + encodeURIComponent(flatId),
|
||||
{
|
||||
credentials: 'same-origin',
|
||||
method: 'POST',
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
@ -79,10 +79,10 @@ export const updateFlatStatus = function (flatId, newStatus, callback) {
|
||||
|
||||
export const updateFlatNotes = function (flatId, newNotes, callback) {
|
||||
fetch(
|
||||
'/api/v1/flat/' + encodeURIComponent(flatId) + '/notes',
|
||||
'/api/v1/flats/' + encodeURIComponent(flatId),
|
||||
{
|
||||
credentials: 'same-origin',
|
||||
method: 'POST',
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
@ -97,10 +97,10 @@ export const updateFlatNotes = function (flatId, newNotes, callback) {
|
||||
|
||||
export const updateFlatNotation = function (flatId, newNotation, callback) {
|
||||
fetch(
|
||||
'/api/v1/flat/' + encodeURIComponent(flatId) + '/notation',
|
||||
'/api/v1/flats/' + encodeURIComponent(flatId),
|
||||
{
|
||||
credentials: 'same-origin',
|
||||
method: 'POST',
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
@ -115,10 +115,10 @@ export const updateFlatNotation = function (flatId, newNotation, callback) {
|
||||
|
||||
export const updateFlatVisitDate = function (flatId, newVisitDate, callback) {
|
||||
fetch(
|
||||
'/api/v1/flat/' + encodeURIComponent(flatId) + '/visit_date',
|
||||
'/api/v1/flats/' + encodeURIComponent(flatId),
|
||||
{
|
||||
credentials: 'same-origin',
|
||||
method: 'POST',
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
@ -8,8 +8,8 @@ from __future__ import (
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
|
||||
import arrow
|
||||
import bottle
|
||||
import vobject
|
||||
|
||||
@ -18,6 +18,12 @@ from flatisfy.models import flat as flat_model
|
||||
from flatisfy.models.postal_code import PostalCode
|
||||
|
||||
|
||||
def JSONError(error_code, error_str):
|
||||
bottle.response.status = error_code
|
||||
bottle.response.content_type = "application/json"
|
||||
return json.dumps(dict(error=error_str, status_code=error_code))
|
||||
|
||||
|
||||
def _serialize_flat(flat, config):
|
||||
"""
|
||||
Serialize a flat for JSON API.
|
||||
@ -65,7 +71,8 @@ def index_v1():
|
||||
"flats": "/api/v1/flats",
|
||||
"flat": "/api/v1/flat/:id",
|
||||
"search": "/api/v1/search",
|
||||
"time_to_places": "/api/v1/time_to/places"
|
||||
"ics": "/api/v1/ics/visits.ics",
|
||||
"time_to_places": "/api/v1/time_to_places"
|
||||
}
|
||||
|
||||
|
||||
@ -75,57 +82,43 @@ def flats_v1(config, db):
|
||||
|
||||
GET /api/v1/flats
|
||||
|
||||
.. note:: Filtering can be done through the ``filter`` GET param, according
|
||||
to JSON API spec (http://jsonapi.org/recommendations/#filtering).
|
||||
|
||||
:return: The available flats objects in a JSON ``data`` dict.
|
||||
"""
|
||||
try:
|
||||
db_query = db.query(flat_model.Flat)
|
||||
|
||||
# Handle filtering according to JSON API spec
|
||||
FILTER_RE = re.compile(r"filter\[([A-z0-9_]+)\]")
|
||||
filters = {}
|
||||
for param in bottle.request.query:
|
||||
filter_match = FILTER_RE.match(param)
|
||||
if not filter:
|
||||
continue
|
||||
field = filter_match.group(1)
|
||||
value = bottle.request.query[filter_match.group(0)]
|
||||
filters[field] = value
|
||||
db_query = db_query.filter_by(**filters)
|
||||
|
||||
# Build flat list
|
||||
flats = [
|
||||
_serialize_flat(flat, config)
|
||||
for flat in db.query(flat_model.Flat).all()
|
||||
for flat in db_query
|
||||
]
|
||||
|
||||
return {
|
||||
"data": flats
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
def flats_by_status_v1(status, config, db):
|
||||
"""
|
||||
API v1 flats route with a specific status:
|
||||
|
||||
GET /api/v1/flats/status/:status
|
||||
|
||||
:return: The matching flats objects in a JSON ``data`` dict.
|
||||
"""
|
||||
try:
|
||||
flats = [
|
||||
_serialize_flat(flat, config)
|
||||
for flat in (
|
||||
db.query(flat_model.Flat)
|
||||
.filter_by(status=getattr(flat_model.FlatStatus, status))
|
||||
.all()
|
||||
)
|
||||
]
|
||||
|
||||
return {
|
||||
"data": flats
|
||||
}
|
||||
except AttributeError:
|
||||
return bottle.HTTPError(400, "Invalid status provided.")
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
except Exception as exc:
|
||||
return JSONError(500, str(exc))
|
||||
|
||||
|
||||
def flat_v1(flat_id, config, db):
|
||||
"""
|
||||
API v1 flat route:
|
||||
|
||||
GET /api/v1/flat/:flat_id
|
||||
GET /api/v1/flats/:flat_id
|
||||
|
||||
:return: The flat object in a JSON ``data`` dict.
|
||||
"""
|
||||
@ -133,141 +126,50 @@ def flat_v1(flat_id, config, db):
|
||||
flat = db.query(flat_model.Flat).filter_by(id=flat_id).first()
|
||||
|
||||
if not flat:
|
||||
return bottle.HTTPError(404, "No flat with id {}.".format(flat_id))
|
||||
return JSONError(404, "No flat with id {}.".format(flat_id))
|
||||
|
||||
return {
|
||||
"data": _serialize_flat(flat, config)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
except Exception as exc:
|
||||
return JSONError(500, str(exc))
|
||||
|
||||
|
||||
def update_flat_status_v1(flat_id, config, db):
|
||||
def update_flat_v1(flat_id, config, db):
|
||||
"""
|
||||
API v1 route to update flat status:
|
||||
|
||||
POST /api/v1/flat/:flat_id/status
|
||||
Data: {
|
||||
"status": "NEW_STATUS"
|
||||
}
|
||||
|
||||
:return: The new flat object in a JSON ``data`` dict.
|
||||
"""
|
||||
try:
|
||||
flat = db.query(flat_model.Flat).filter_by(id=flat_id).first()
|
||||
if not flat:
|
||||
return bottle.HTTPError(404, "No flat with id {}.".format(flat_id))
|
||||
|
||||
try:
|
||||
flat.status = getattr(
|
||||
flat_model.FlatStatus, json.load(bottle.request.body)["status"]
|
||||
)
|
||||
except (AttributeError, ValueError, KeyError):
|
||||
return bottle.HTTPError(400, "Invalid status provided.")
|
||||
|
||||
return {
|
||||
"data": _serialize_flat(flat, config)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
def update_flat_notes_v1(flat_id, config, db):
|
||||
"""
|
||||
API v1 route to update flat notes:
|
||||
|
||||
POST /api/v1/flat/:flat_id/notes
|
||||
Data: {
|
||||
"notes": "NEW_NOTES"
|
||||
}
|
||||
|
||||
:return: The new flat object in a JSON ``data`` dict.
|
||||
"""
|
||||
try:
|
||||
flat = db.query(flat_model.Flat).filter_by(id=flat_id).first()
|
||||
if not flat:
|
||||
return bottle.HTTPError(404, "No flat with id {}.".format(flat_id))
|
||||
|
||||
try:
|
||||
flat.notes = json.load(bottle.request.body)["notes"]
|
||||
except (ValueError, KeyError):
|
||||
return bottle.HTTPError(400, "Invalid notes provided.")
|
||||
|
||||
return {
|
||||
"data": _serialize_flat(flat, config)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
def update_flat_notation_v1(flat_id, config, db):
|
||||
"""
|
||||
API v1 route to update flat notation:
|
||||
|
||||
POST /api/v1/flat/:flat_id/notation
|
||||
Data: {
|
||||
"notation": "NEW_NOTATION"
|
||||
}
|
||||
|
||||
:return: The new flat object in a JSON ``data`` dict.
|
||||
"""
|
||||
try:
|
||||
flat = db.query(flat_model.Flat).filter_by(id=flat_id).first()
|
||||
if not flat:
|
||||
return bottle.HTTPError(404, "No flat with id {}.".format(flat_id))
|
||||
|
||||
try:
|
||||
flat.notation = json.load(bottle.request.body)["notation"]
|
||||
assert flat.notation >= 0 and flat.notation <= 5
|
||||
except (AssertionError, ValueError, KeyError):
|
||||
return bottle.HTTPError(400, "Invalid notation provided.")
|
||||
|
||||
return {
|
||||
"data": _serialize_flat(flat, config)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
def update_flat_visit_date_v1(flat_id, config, db):
|
||||
"""
|
||||
API v1 route to update flat date of visit:
|
||||
|
||||
POST /api/v1/flat/:flat_id/visit_date
|
||||
PATCH /api/v1/flat/:flat_id
|
||||
Data: {
|
||||
"status": "NEW_STATUS",
|
||||
"visit_date": "ISO8601 DATETIME"
|
||||
}
|
||||
|
||||
.. note:: The keys in the data sent are same keys as in ``Flat`` model. You
|
||||
can provide any subset of them to update part of the flat infos.
|
||||
|
||||
:return: The new flat object in a JSON ``data`` dict.
|
||||
"""
|
||||
try:
|
||||
flat = db.query(flat_model.Flat).filter_by(id=flat_id).first()
|
||||
if not flat:
|
||||
return bottle.HTTPError(404, "No flat with id {}.".format(flat_id))
|
||||
return JSONError(404, "No flat with id {}.".format(flat_id))
|
||||
|
||||
try:
|
||||
visit_date = json.load(bottle.request.body)["visit_date"]
|
||||
if visit_date:
|
||||
visit_date = arrow.get(visit_date).naive
|
||||
flat.visit_date = visit_date
|
||||
except (arrow.parser.ParserError, ValueError, KeyError):
|
||||
return bottle.HTTPError(400, "Invalid visit date provided.")
|
||||
json_body = json.load(bottle.request.body)
|
||||
for k, v in json_body.items():
|
||||
setattr(flat, k, v)
|
||||
except ValueError as exc:
|
||||
return JSONError(
|
||||
400,
|
||||
"Invalid payload provided: {}.".format(str(exc))
|
||||
)
|
||||
|
||||
return {
|
||||
"data": _serialize_flat(flat, config)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
except Exception as exc:
|
||||
return JSONError(500, str(exc))
|
||||
|
||||
|
||||
def time_to_places_v1(config):
|
||||
@ -289,10 +191,8 @@ def time_to_places_v1(config):
|
||||
return {
|
||||
"data": places
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
except Exception as exc:
|
||||
return JSONError(500, str(exc))
|
||||
|
||||
|
||||
def search_v1(db, config):
|
||||
@ -310,7 +210,7 @@ def search_v1(db, config):
|
||||
try:
|
||||
query = json.load(bottle.request.body)["query"]
|
||||
except (ValueError, KeyError):
|
||||
return bottle.HTTPError(400, "Invalid query provided.")
|
||||
return JSONError(400, "Invalid query provided.")
|
||||
|
||||
flats_db_query = flat_model.Flat.search_query(db, query)
|
||||
flats = [
|
||||
@ -321,26 +221,24 @@ def search_v1(db, config):
|
||||
return {
|
||||
"data": flats
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
except Exception as exc:
|
||||
return JSONError(500, str(exc))
|
||||
|
||||
|
||||
def ics_feed_v1(config, db):
|
||||
"""
|
||||
API v1 ICS feed of visits route:
|
||||
|
||||
GET /api/v1/visits.ics
|
||||
GET /api/v1/ics/visits.ics
|
||||
|
||||
:return: The ICS feed for the visits.
|
||||
"""
|
||||
cal = vobject.iCalendar()
|
||||
try:
|
||||
flats_with_visits = db.query(flat_model.Flat).filter(
|
||||
flat_model.Flat.visit_date.isnot(None)
|
||||
).all()
|
||||
)
|
||||
|
||||
cal = vobject.iCalendar()
|
||||
for flat in flats_with_visits:
|
||||
vevent = cal.add('vevent')
|
||||
vevent.add('dtstart').value = flat.visit_date
|
||||
@ -360,7 +258,7 @@ def ics_feed_v1(config, db):
|
||||
description += '\n{}\n'.format(flat.notes)
|
||||
|
||||
vevent.add('description').value = description
|
||||
except:
|
||||
pass
|
||||
|
||||
return cal.serialize()
|
||||
except:
|
||||
return ''
|
||||
|
Loading…
Reference in New Issue
Block a user