app.py 6.9 KB
Newer Older
1
from flask import Flask, redirect, jsonify, abort, request, url_for, make_response
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 with_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__)

Zach Perkins's avatar
Zach Perkins committed
12
# Endpoints:
13
14


Dylan Jones's avatar
app.py    
Dylan Jones committed
15
16
@app.route('/')
def index():
Dylan Jones's avatar
no slug    
Dylan Jones committed
17
18
19
20
21
22
23
24
25
26
27
28
    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')
29
30
31
32
33
34
35
36
37
38
39
@with_session
def test_data(session):
    session.query(Point).delete()
    session.query(Field).delete()
    session.query(Category).delete()
    # Water Fountain, the class.
    wf = Category()
    wf.name = "Water Fountain"
    wf.icon = "https://karel.pw/water.png"
    session.add(wf)
    session.commit()
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    # 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:*"
    session.add(bd)
    session.commit()
    # 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
    session.add(rd)
    session.commit()

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    # 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
    session.add(cd)
    session.add(fl)
    session.commit()
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

    # 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
        }
    }
    session.add(jc)

    # A water fountain inside the JC
86
87
88
89
90
91
    fn = Point()
    fn.name = None
    fn.lat = 38.829791
    fn.lon = -77.307043
    # fn.category_id = wf.id
    fn.category = wf
92
    fn.parent = jc
93
94
95
96
97
98
99
    fn.attributes = {
        "coldness": {
            "num_reviews": 32,
            "average_rating": 0.5
        },
        "bottle_filler": {
            "value": True
Dylan Jones's avatar
Dylan Jones committed
100
        }
101
    }
102

103
104
105
    session.add(fn)
    session.commit()
    return redirect('/')
Dylan Jones's avatar
Dylan Jones committed
106
107
108


@app.route('/category/<id>')
109
110
@with_session
def get_category(session, id):
Zach Perkins's avatar
Zach Perkins committed
111
    return get_resource(session, Category, id)
Dylan Jones's avatar
Dylan Jones committed
112
113


114
115
@app.route('/category/<id>/children')
@with_session
116
def get_category_children(data, session, id):
117
118
119
120
121
    data = dict(request.args)
    data['parent_id'] = id
    return search_resource(session, Point, data)


122
@app.route('/point', methods=['GET'])
123
@use_args({'parent_id': fields.Int(), 'category_id': fields.Int(required=True)})
124
@with_session
125
126
def search_point(session, args):
    return search_resource(session, Point, args)
127
128
129


@app.route('/point', methods=['POST'])
130
131
132
133
134
@use_args(PointSchema)
@with_session
def create_point(session, args):
    args['category'] = session.query(Category).get(args.pop('category_id'))
    return create_resource(session, Point, args, 'get_point')
135
136
   

137
@app.route('/point/<id>', methods=['GET'])
138
139
@with_session
def get_point(session, id):
140
141
142
143
144
145
146
147
148
149
150
151
152
    return get_resource(session, Point, id)


@app.route('/point/<id>', methods=['DELETE'])
@with_session
def del_point(session, id):
    return delete_resource(session, Point, id)


@app.route('/point/<id>', methods=['PUT'])
@with_session
def edit_point(session, id):
    return edit_resource(session, Point, id, request.get_json())
Dylan Jones's avatar
app.py    
Dylan Jones committed
153

Zach Perkins's avatar
Zach Perkins committed
154

155
@app.route('/point/<id>/children', methods=['GET'])
Zach Perkins's avatar
Zach Perkins committed
156
@with_session
157
158
159
160
def get_point_children(session, id):
    data = dict(request.args)
    data['parent_id'] = id
    return search_resource(session, Point, data)
Zach Perkins's avatar
Zach Perkins committed
161

Zach Perkins's avatar
Zach Perkins committed
162

Zach Perkins's avatar
Zach Perkins committed
163
164
165
166
167
168
169
# Helper functions:
# TODO: Add helper functions for data validation


def create_resource(session, model_cls, data, get_function):
    '''
    Create the resource specified by the given model class and initialized with the data
Zach Perkins's avatar
Zach Perkins committed
170
    dict, returning an appropriate JSON response. 
Zach Perkins's avatar
Zach Perkins committed
171
172
173
174
175
176

    :param session: The sqlalchemy session
    :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
177
178
179
180
181
182
183
184
185
186
187
188
    '''
    resource = model_cls(**data)
    session.add(resource)
    session.commit()

    response = make_response(jsonify(resource.as_json()), 201)
    response.headers['Location'] = url_for(get_function, id=resource.id)
    return response


def get_resource(session, model_cls, id):
    '''
Zach Perkins's avatar
Zach Perkins committed
189
190
191
192
193
194
    Get a single resource of the specified model class by its ID.
    
    :param session: The sqlalchemy session
    :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
195
196
197
198
199
200
201
202
203
    '''
    resource = session.query(model_cls).get(id)
    return make_response(jsonify(resource.as_json()), 200)


def edit_resource(session, model_cls, id, data):
    '''
    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
204
205
206
207
208
209

    :param session: The sqlalchemy session
    :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
210
211
212
213
214
215
216
217
218
219
220
221
222
    '''
    resource = session.query(model_cls).get(id)
    for attr in data:
        setattr(resource, attr, data[attr])
    session.commit()

    return make_response(jsonify(resource.as_json()), 200)


def delete_resource(session, model_cls, id):
    '''
    Delete the resource of the specified model class and id and return the 
    appropriate response.
Zach Perkins's avatar
Zach Perkins committed
223
224
225
226
227

    :param session: The sqlalchemy session
    :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
228
229
230
231
232
233
234
235
236
237
238
239
    '''
    resource = session.query(model_cls).get(id)
    session.delete(resource)
    session.commit()

    return make_response('', 204)


def search_resource(session, model_cls, data):
    '''
    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
240
241
242
243
244
245
    response. Does not perform validation on search parameters.

    :param session: The sqlalchemy session
    :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
246
247
248
249
250
251
252
253
    '''
    query = session.query(model_cls).filter_by(**data)
    results = list(map(lambda m: m.as_json(), query.limit(100).all()))

    response = make_response(jsonify(results), 200)
    return response


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