Skip to content

Commit

Permalink
Merge pull request #296 from mvexel/master
Browse files Browse the repository at this point in the history
API changes
  • Loading branch information
mvexel committed Jul 29, 2014
2 parents 15a272e + f404187 commit a40d82b
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 74 deletions.
129 changes: 104 additions & 25 deletions maproulette/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@
from flask import session, request, abort, url_for
from maproulette.helpers import get_random_task,\
get_challenge_or_404, get_task_or_404,\
require_signedin, osmerror, challenge_exists,\
require_signedin, osmerror, \
json_to_task, refine_with_user_area, user_area_is_defined,\
send_email, as_stats_dict
send_email, as_stats_dict, challenge_exists
from maproulette.models import Challenge, Task, Action, User, db
from geoalchemy2.functions import ST_Buffer
from geoalchemy2.shape import to_shape
from sqlalchemy import func
import geojson
import json
import markdown
import re


class ProtectedResource(Resource):
Expand Down Expand Up @@ -175,7 +176,10 @@ def put(self):
abort(400)
[session.pop(k, None) for k, v in payload.iteritems() if v is None]
for k, v in payload.iteritems():
if k not in me_fields.keys():
abort(400, 'you cannot set this key')
if v is not None:
app.logger.debug('setting {k} to {v}'.format(k=k, v=v))
session[k] = v
return {}

Expand Down Expand Up @@ -522,22 +526,48 @@ class AdminApiChallenge(Resource):

"""Admin challenge creation endpoint"""

def post(self, slug):
if challenge_exists(slug):
abort(409, 'This challenge already exists')
if not re.match("^[\w\d_-]+$", slug):
abort(400, 'slug should contain only a-z, A-Z, 0-9, _, -')
try:
payload = json.loads(request.data)
except Exception:
abort(400, "JSON bad")
if 'title' not in payload:
abort(400, "new challenge must have title")
c = Challenge(slug, payload.get('title'))
if 'title' in payload:
c.title = payload.get('title')
if 'geometry' in payload:
c.geometry = payload.get('geometry')
if 'description' in payload:
c.description = payload.get('description')
if 'blurb' in payload:
c.blurb = payload.get('blurb')
if 'help' in payload:
c.help = payload.get('help')
if 'instruction' in payload:
c.instruction = payload.get('instruction')
if 'active' in payload:
c.active = payload.get('active')
if 'difficulty' in payload:
c.difficulty = payload.get('difficulty')
db.session.add(c)
db.session.commit()
return {}, 201

def put(self, slug):
exists = challenge_exists(slug)
c = get_challenge_or_404(slug, abort_if_inactive=False)
if not re.match("^[\w\d_-]+$", slug):
abort(400, 'slug should contain only a-z, A-Z, 0-9, _, -')
try:
payload = json.loads(request.data)
except Exception:
abort(400, "JSON bad")
if not exists and 'title' not in payload:
abort(400, "No title")
return {}
if exists:
app.logger.debug('challenge existed, retrieving')
c = get_challenge_or_404(slug, abort_if_inactive=False)
if 'title' in payload:
c.title = payload.get('title')
else:
c = Challenge(slug, payload.get('title'))
if 'title' in payload:
c.title = payload.get('title')
if 'geometry' in payload:
c.geometry = payload.get('geometry')
if 'description' in payload:
Expand All @@ -552,10 +582,9 @@ def put(self, slug):
c.active = payload.get('active')
if 'difficulty' in payload:
c.difficulty = payload.get('difficulty')
merged_c = db.session.merge(c)
db.session.add(merged_c)
db.session.add(c)
db.session.commit()
return {}
return {}, 200

def delete(self, slug):
"""delete a challenge"""
Expand All @@ -581,16 +610,32 @@ class AdminApiUpdateTask(Resource):

"""Challenge Task Create / Update endpoint"""

def put(self, slug, identifier):
"""Create or update one task."""
def post(self, slug, identifier):
"""create one task."""

if not re.match("^[\w\d_-]+$", identifier):
abort(400, 'identifier should contain only a-z, A-Z, 0-9, _, -')

# Parse the posted data
t = json_to_task(slug, json.loads(request.data))
merged_t = db.session.merge(t)
db.session.add(merged_t)
t = json_to_task(
slug,
json.loads(request.data))
db.session.add(t)
db.session.commit()
return {}, 201

def put(self, slug, identifier):
"""update one task."""

# Parse the posted data
t = json_to_task(
slug,
json.loads(request.data),
task=get_task_or_404(slug, identifier))
db.session.add(t)
db.session.commit()
return {}, 200

def delete(self, slug, identifier):
"""Delete a task"""

Expand All @@ -606,7 +651,9 @@ class AdminApiUpdateTasks(Resource):

"""Bulk task create / update endpoint"""

def put(self, slug):
def post(self, slug):

"""bulk create"""

# Get the posted data
data = json.loads(request.data)
Expand All @@ -615,12 +662,44 @@ def put(self, slug):
app.logger.debug('posting {number} tasks...'.format(number=len(data)))

if len(data) > app.config['MAX_TASKS_BULK_UPDATE']:
abort(400, 'more than 5000 tasks in bulk update')
abort(400, 'more than 5000 tasks in bulk create')

for task in data:
if not 'identifier' in task:
abort(400, 'task must have identifier')
if not re.match("^[\w\d_-]+$", task['identifier']):
abort(400, 'identifier should contain only a-z, A-Z, 0-9, _, -')
if not 'geometries' in task:
abort(400, 'new task must have geometries')
t = json_to_task(slug, task)
merged_t = db.session.merge(t)
db.session.add(merged_t)
db.session.add(t)

# commit all dirty tasks at once.
db.session.commit()
return {}, 200

def put(self, slug):

"""bulk update"""

# Get the posted data
data = json.loads(request.data)

# debug output number of tasks being posted
app.logger.debug('putting {number} tasks...'.format(number=len(data)))

if len(data) > app.config['MAX_TASKS_BULK_UPDATE']:
abort(400, 'more than 5000 tasks in bulk update')

for task in data:
if not 'identifier' in task:
abort(400, 'task must have identifier')
if not re.match("^[\w\d_-]+$", task['identifier']):
abort(400, 'identifier should contain only a-z, A-Z, 0-9, _, -')
t = json_to_task(slug,
task,
task=get_task_or_404(slug, task['identifier']))
db.session.add(t)

# commit all dirty tasks at once.
db.session.commit()
Expand Down
23 changes: 8 additions & 15 deletions maproulette/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,13 @@ def get_random_task(challenge):
return q.first() or None


def json_to_task(slug, data):
def json_to_task(slug, data, task=None):
"""Parse task json coming in through the admin api"""

task_geometries = []

# task json needs to have identifier
if not 'identifier' in data:
abort(400, 'no identifier')

# if the task is new, it needs to have geometry
if not 'geometries' in data:
if not task_exists(slug, data['identifier']):
abort(400, 'no geometries for new tasks')
else:
# extract the task geometries
# extract the task geometries
if 'geometries' in data:
geometries = data.pop('geometries')
# parse the geometries
for feature in geometries['features']:
Expand All @@ -159,15 +151,16 @@ def json_to_task(slug, data):
task_geometries.append(g)

# create the task
t = Task(slug, data['identifier'], task_geometries)
if task is None:
task = Task(slug, data['identifier'], task_geometries)

# check for instruction
if 'instruction' in data:
t.instruction = data['instruction']
task.instruction = data['instruction']
# check for status
if 'status' in data:
t.status = data['status']
return t
task.status = data['status']
return task


def get_envelope(geoms):
Expand Down
2 changes: 1 addition & 1 deletion maproulette/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<div id="user">
{%- if session.get('osm_token') -%}
<script type="text/javascript">MRManager.loggedIn = "{{ session.display_name }}";</script>
signed in as <a href="{{ url_for("me") }}">{{ session.display_name }}</a>. <a href="{{ url_for("logout") }}">sign out</a>
signed in as {{ session.display_name }}. <a href="{{ url_for("logout") }}">sign out</a>
{%- else -%}
<script type="text/javascript">MRManager.loggedIn = false;</script>
not signed in. <a href="{{ url_for("oauth_authorize") }}">sign in on OSM</a>
Expand Down
20 changes: 0 additions & 20 deletions maproulette/templates/me.html

This file was deleted.

7 changes: 0 additions & 7 deletions maproulette/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,3 @@ def logout():
if signed_in() or app.debug:
session.destroy()
return redirect('/')


@app.route('/me')
def me():
"""Display a page about me with some stats
and user settings."""
return render_template('me.html')
6 changes: 6 additions & 0 deletions profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from werkzeug.contrib.profiler import ProfilerMiddleware
from maproulette import app

app.config['PROFILE'] = True
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
app.run(debug=True)
19 changes: 13 additions & 6 deletions test/test.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import os
import maproulette
from maproulette import app
from maproulette.models import db
import unittest
import tempfile


class MapRouletteTestCase(unittest.TestCase):

def setUp(self):
self.db_fd, maproulette.app.config['DATABASE'] = tempfile.mkstemp()
maproulette.app.config['TESTING'] = True
self.app = maproulette.app.test_client()
maproulette.init_db()
self.db_fd, app.config['DATABASE'] = tempfile.mkstemp()
app.config['TESTING'] = True
self.app = app.test_client()
db.create_all()

def tearDown(self):
db.drop_all()
os.close(self.db_fd)
os.unlink(maproulette.app.config['DATABASE'])
os.unlink(app.config['DATABASE'])

def test_empty_db(self):
r = self.app.get('/api/challenges')
assert r.data.startswith('[]')


if __name__ == '__main__':
unittest.main()

0 comments on commit a40d82b

Please sign in to comment.