app.py 6.65 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

app = Flask(__name__)

8

Zach Perkins's avatar
Zach Perkins committed
9
# Endpoints:
10
11


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

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

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

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


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


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@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)


@app.route('/point', methods=['GET', 'POST'])
@with_session
def handle_point(session):
    if request.method == 'GET':
        return search_resource(session, Point, dict(request.args))
    elif request.method == 'POST':
        data = request.get_json()
Zach Perkins's avatar
Zach Perkins committed
126
        data['category'] = session.query(Category).get(data.pop('category_id'))
127
128
129
130
131

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

@app.route('/point/<id>', methods=['GET', 'PUT', 'DELETE'])
132
133
@with_session
def get_point(session, id):
Zach Perkins's avatar
Zach Perkins committed
134
135
136
137
138
139
    if request.method == 'GET':
        return get_resource(session, Point, id)
    elif request.method == 'DELETE':
        return delete_resource(session, Point, id)
    elif request.method == 'PUT':
        return edit_resource(session, Point, id, request.get_json())
Dylan Jones's avatar
app.py    
Dylan Jones committed
140

Zach Perkins's avatar
Zach Perkins committed
141

142
@app.route('/point/<id>/children', methods=['GET'])
Zach Perkins's avatar
Zach Perkins committed
143
@with_session
144
145
146
147
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
148

Zach Perkins's avatar
Zach Perkins committed
149

Zach Perkins's avatar
Zach Perkins committed
150
151
152
153
154
155
156
# 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
157
158
159
160
161
162
163
    dict and return an appropriate JSON response. 

    :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
164
165
166
167
168
169
170
171
172
173
174
175
    '''
    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
176
177
178
179
180
181
    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
182
183
184
185
186
187
188
189
190
    '''
    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
191
192
193
194
195
196

    :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
197
198
199
200
201
202
203
204
205
206
207
208
209
    '''
    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
210
211
212
213
214

    :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
215
216
217
218
219
220
221
222
223
224
225
226
    '''
    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
227
228
229
230
231
232
    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
233
234
235
236
237
238
239
240
    '''
    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
241
if __name__ == '__main__':
242
    app.run()