app.py 6.74 KB
Newer Older
1
from flask import Flask, redirect, jsonify, abort, request, url_for, make_response
Dylan Jones's avatar
no slug    
Dylan Jones committed
2

3
from where.model import with_session, Point, Category, Field
Dylan Jones's avatar
Dylan Jones committed
4
from where.model.field_types import FieldType
Dylan Jones's avatar
app.py    
Dylan Jones committed
5

6
7
from where.validation import PointSchema, CategorySchema, FieldSchema

Dylan Jones's avatar
app.py    
Dylan Jones committed
8
9
app = Flask(__name__)

10

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


Dylan Jones's avatar
app.py    
Dylan Jones committed
14
15
@app.route('/')
def index():
Dylan Jones's avatar
no slug    
Dylan Jones committed
16
17
18
19
20
21
22
23
24
25
26
27
    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')
28
29
30
31
32
33
34
35
36
37
38
@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()
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    # 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()

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

    # 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
85
86
87
88
89
90
    fn = Point()
    fn.name = None
    fn.lat = 38.829791
    fn.lon = -77.307043
    # fn.category_id = wf.id
    fn.category = wf
91
    fn.parent = jc
92
93
94
95
96
97
98
    fn.attributes = {
        "coldness": {
            "num_reviews": 32,
            "average_rating": 0.5
        },
        "bottle_filler": {
            "value": True
Dylan Jones's avatar
Dylan Jones committed
99
        }
100
    }
101

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


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


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


121
@app.route('/point', methods=['GET'])
122
@with_session
123
124
125
126
127
128
129
130
131
132
def search_point(session):
    return search_resource(session, Point, dict(request.args))


@app.route('/point', methods=['POST'])
def create_point(session):
    data = request.get_json()
    data['category'] = session.query(Category).get(data.pop('category_id'))

    return create_resource(session, Point, data, 'get_point')
133
134
   

135
@app.route('/point/<id>', methods=['GET'])
136
137
@with_session
def get_point(session, id):
138
139
140
141
142
143
144
145
146
147
148
149
150
    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
151

Zach Perkins's avatar
Zach Perkins committed
152

153
@app.route('/point/<id>/children', methods=['GET'])
Zach Perkins's avatar
Zach Perkins committed
154
@with_session
155
156
157
158
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
159

Zach Perkins's avatar
Zach Perkins committed
160

Zach Perkins's avatar
Zach Perkins committed
161
162
163
164
165
166
167
# 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
168
    dict, returning an appropriate JSON response. 
Zach Perkins's avatar
Zach Perkins committed
169
170
171
172
173
174

    :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
175
176
177
178
179
180
181
182
183
184
185
186
    '''
    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
187
188
189
190
191
192
    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
193
194
195
196
197
198
199
200
201
    '''
    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
202
203
204
205
206
207

    :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
208
209
210
211
212
213
214
215
216
217
218
219
220
    '''
    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
221
222
223
224
225

    :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
226
227
228
229
230
231
232
233
234
235
236
237
    '''
    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
238
239
240
241
242
243
    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
244
245
246
247
248
249
250
251
    '''
    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
252
if __name__ == '__main__':
253
    app.run()