app.py 6.98 KB
Newer Older
1
from flask import Flask, redirect, jsonify, abort, request, url_for, make_response, g
2 3
from webargs.flaskparser import use_args
from webargs import fields
Dylan Jones's avatar
no slug  
Dylan Jones committed
4

5
from where.model import Session, Point, Category, Field
Dylan Jones's avatar
Dylan Jones committed
6
from where.model.field_types import FieldType
Dylan Jones's avatar
app.py  
Dylan Jones committed
7

8 9
from where.validation import PointSchema, CategorySchema, FieldSchema

Dylan Jones's avatar
app.py  
Dylan Jones committed
10 11
app = Flask(__name__)

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

@app.before_request
def create_local_db_session():
    g.db_session = Session()
    

@app.after_request
def destroy_local_db_session(resp):
    try:
        g.db_session.commit()
    except BaseException:
        g.db_session.rollback()
        raise
    finally:
        Session.remove()
        return resp
28 29


Dylan Jones's avatar
app.py  
Dylan Jones committed
30 31
@app.route('/')
def index():
Zach Perkins's avatar
Zach Perkins committed
32
    print(PointSchema().Meta.model)
Dylan Jones's avatar
no slug  
Dylan Jones committed
33 34 35 36 37 38 39 40 41 42 43 44
    return """
<head>
</head>
<body>
    <h1>W H E R E</h1>
    <p>This is the WHERE project.</p>
    <a href='/test_data'>Click here to nuke the database and make it all be test data.</a>
</body>
    """


@app.route('/test_data')
45 46 47 48
def test_data():
    g.db_session.query(Point).delete()
    g.db_session.query(Field).delete()
    g.db_session.query(Category).delete()
49 50 51 52
    # Water Fountain, the class.
    wf = Category()
    wf.name = "Water Fountain"
    wf.icon = "https://karel.pw/water.png"
53 54
    g.db_session.add(wf)
    g.db_session.commit()
55 56 57 58
    # Building
    bd = Category()
    bd.name = "Building"
    bd.icon = "https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/basket-building-news-photo-1572015168.jpg?resize=980:*"
59 60
    g.db_session.add(bd)
    g.db_session.commit()
61 62 63 64 65 66
    # Radius (Really the simplest metric we can have for building size)
    rd = Field()
    rd.name = "Radius"
    rd.slug = "radius"
    rd.type = FieldType.FLOAT
    rd.category_id = bd.id
67 68
    g.db_session.add(rd)
    g.db_session.commit()
69

70 71 72 73 74 75 76 77 78 79 80 81
    # coldness
    cd = Field()
    cd.name = "Coldness"
    cd.slug = "coldness"
    cd.type = FieldType.RATING
    cd.category_id = wf.id
    # filler
    fl = Field()
    fl.slug = "bottle_filler"
    fl.name = "Has Bottle Filler"
    fl.type = FieldType.BOOLEAN
    fl.category_id = wf.id
82 83 84
    g.db_session.add(cd)
    g.db_session.add(fl)
    g.db_session.commit()
85 86 87 88 89 90 91 92 93 94 95 96 97

    # The johnson center
    jc = Point()
    jc.category = bd
    jc.name = "Johnson Center"
    jc.lat = 38
    jc.lon = -77
    jc.parent = None
    jc.attributes = {
        "radius": {
            "value": 2.0
        }
    }
98
    g.db_session.add(jc)
99 100

    # A water fountain inside the JC
101 102 103 104 105 106
    fn = Point()
    fn.name = None
    fn.lat = 38.829791
    fn.lon = -77.307043
    # fn.category_id = wf.id
    fn.category = wf
107
    fn.parent = jc
108 109 110 111 112 113 114
    fn.attributes = {
        "coldness": {
            "num_reviews": 32,
            "average_rating": 0.5
        },
        "bottle_filler": {
            "value": True
Dylan Jones's avatar
Dylan Jones committed
115
        }
116
    }
117

118 119
    g.db_session.add(fn)
    g.db_session.commit()
120
    return redirect('/')
Dylan Jones's avatar
Dylan Jones committed
121 122


axiiom's avatar
axiiom committed
123
@app.route('/category/<int:id>')
124
def get_category(id):
Zach Perkins's avatar
Zach Perkins committed
125
    return get_resource(CategorySchema(), id)
Dylan Jones's avatar
Dylan Jones committed
126 127


axiiom's avatar
axiiom committed
128
@app.route('/category/<int:id>/children')
129
def get_category_children(data, id):
130 131
    data = dict(request.args)
    data['parent_id'] = id
Zach Perkins's avatar
Zach Perkins committed
132
    return search_resource(PointSchema(), data)
133 134


135
@app.route('/point', methods=['GET'])
136
@use_args({'parent_id': fields.Int(), 'category_id': fields.Int(required=True)})
137
def search_point(args):
Zach Perkins's avatar
Zach Perkins committed
138
    return search_resource(PointSchema(), args)
139 140 141


@app.route('/point', methods=['POST'])
142
@use_args(PointSchema)
143
def create_point(args):
Zach Perkins's avatar
Zach Perkins committed
144
    return create_resource(PointSchema(), args, 'get_point')
145 146
   

axiiom's avatar
axiiom committed
147
@app.route('/point/<int:id>', methods=['GET'])
148
def get_point(id):
Zach Perkins's avatar
Zach Perkins committed
149
    return get_resource(PointSchema(), id)
150 151


axiiom's avatar
axiiom committed
152
@app.route('/point/<int:id>', methods=['DELETE'])
153
def del_point(id):
Zach Perkins's avatar
Zach Perkins committed
154
    return delete_resource(PointSchema(), id)
155 156


Zach Perkins's avatar
Zach Perkins committed
157
#TODO: Validate this
axiiom's avatar
axiiom committed
158
@app.route('/point/<int:id>', methods=['PUT'])
159
def edit_point(id):
Zach Perkins's avatar
Zach Perkins committed
160
    return edit_resource(PointSchema(), id, request.get_json())
Dylan Jones's avatar
app.py  
Dylan Jones committed
161

Zach Perkins's avatar
Zach Perkins committed
162

axiiom's avatar
axiiom committed
163
@app.route('/point/<int:id>/children', methods=['GET'])
164
def get_point_children(id):
165 166
    data = dict(request.args)
    data['parent_id'] = id
Zach Perkins's avatar
Zach Perkins committed
167
    return search_resource(PointSchema(), data)
Zach Perkins's avatar
Zach Perkins committed
168

Zach Perkins's avatar
Zach Perkins committed
169

Zach Perkins's avatar
Zach Perkins committed
170 171 172
# Helper functions:


Zach Perkins's avatar
Zach Perkins committed
173
def create_resource(schema, data, get_function):
Zach Perkins's avatar
Zach Perkins committed
174 175
    '''
    Create the resource specified by the given model class and initialized with the data
Zach Perkins's avatar
Zach Perkins committed
176
    dict, returning an appropriate JSON response. 
Zach Perkins's avatar
Zach Perkins committed
177 178 179 180 181

    :param model_cls: The class of the model for this resource
    :param data: The initial data for this resource stored as a dictionary
    :param get_function: The name of the view function (as a string) that gets a single instance of this resource. This is used for the response Location header.
    :return: a Flask Response object
Zach Perkins's avatar
Zach Perkins committed
182
    '''
Zach Perkins's avatar
Zach Perkins committed
183
    resource = schema.Meta.model(**data)
184 185
    g.db_session.add(resource)
    g.db_session.commit()
Zach Perkins's avatar
Zach Perkins committed
186

Zach Perkins's avatar
Zach Perkins committed
187
    response = make_response(schema.dump(resource, many=False), 201)
Zach Perkins's avatar
Zach Perkins committed
188 189 190 191
    response.headers['Location'] = url_for(get_function, id=resource.id)
    return response


Zach Perkins's avatar
Zach Perkins committed
192
def get_resource(schema, id):
Zach Perkins's avatar
Zach Perkins committed
193
    '''
Zach Perkins's avatar
Zach Perkins committed
194 195 196 197 198
    Get a single resource of the specified model class by its ID.
    
    :param model_cls: The class of the model for this resource
    :param id: The id of this resource
    :return: a Flask Response object
Zach Perkins's avatar
Zach Perkins committed
199
    '''
Zach Perkins's avatar
Zach Perkins committed
200
    resource = g.db_session.query(schema.Meta.model).get(id)
axiiom's avatar
axiiom committed
201
    resp = (None, 404) if resource is None else \
Zach Perkins's avatar
Zach Perkins committed
202 203
        (schema.dump(resource, many=False), 200)
    return make_response(resp)
Zach Perkins's avatar
Zach Perkins committed
204 205


Zach Perkins's avatar
Zach Perkins committed
206
def edit_resource(schema, id, data):
Zach Perkins's avatar
Zach Perkins committed
207 208 209
    '''
    Modify the resource of the specified model class and id with the data from
    data. Does not perform data validation.
Zach Perkins's avatar
Zach Perkins committed
210 211 212 213 214

    :param model_cls: The class of the model for this resource
    :param id: The id of this resource
    :param data: The new data for this resource stored as a dictionary
    :return: a Flask Response object
Zach Perkins's avatar
Zach Perkins committed
215
    '''
Zach Perkins's avatar
Zach Perkins committed
216
    resource = g.db_session.query(schema.Meta.model).get(id)
Zach Perkins's avatar
Zach Perkins committed
217 218
    for attr in data:
        setattr(resource, attr, data[attr])
219
    g.db_session.commit()
Zach Perkins's avatar
Zach Perkins committed
220

Zach Perkins's avatar
Zach Perkins committed
221
    return make_response(schema.dump(resource), 200)
Zach Perkins's avatar
Zach Perkins committed
222 223


Zach Perkins's avatar
Zach Perkins committed
224
def delete_resource(schema, id):
Zach Perkins's avatar
Zach Perkins committed
225 226 227
    '''
    Delete the resource of the specified model class and id and return the 
    appropriate response.
Zach Perkins's avatar
Zach Perkins committed
228 229 230 231

    :param model_cls: The class of the model for this resource
    :param id: The id of this resource
    :return: a Flask Response object
Zach Perkins's avatar
Zach Perkins committed
232
    '''
Zach Perkins's avatar
Zach Perkins committed
233
    resource = g.db_session.query(schema.Meta.model).get(id)
234 235
    g.db_session.delete(resource)
    g.db_session.commit()
Zach Perkins's avatar
Zach Perkins committed
236 237 238 239

    return make_response('', 204)


Zach Perkins's avatar
Zach Perkins committed
240
def search_resource(schema, data):
Zach Perkins's avatar
Zach Perkins committed
241 242 243
    '''
    Search the database for a list of instances of the specified model class
    that have the attributes given in data and return the appropriate JSON
Zach Perkins's avatar
Zach Perkins committed
244 245 246 247 248
    response. Does not perform validation on search parameters.

    :param model_cls: The class of the model for this resource
    :param data: A dictionary containing search parameters
    :return: a Flask Response object
Zach Perkins's avatar
Zach Perkins committed
249
    '''
250 251

    # TODO: returns 404 when accessing children - i think it should just return an empty array
Zach Perkins's avatar
Zach Perkins committed
252
    query = g.db_session.query(schema.Meta.model).filter_by(**data)
axiiom's avatar
axiiom committed
253
    resp = (None, 404) if query.first() is None else \
Zach Perkins's avatar
Zach Perkins committed
254
        (schema.dump(query.all(), many=True), 200)
Zach Perkins's avatar
Zach Perkins committed
255

Zach Perkins's avatar
Zach Perkins committed
256
    return make_response(resp)
Zach Perkins's avatar
Zach Perkins committed
257 258


Dylan Jones's avatar
app.py  
Dylan Jones committed
259
if __name__ == '__main__':
260
    app.run()