Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 12

TODO API

# Get index
#!flask/bin/python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, World!"
if __name__ == '__main__':
app.run(debug=True)
===============================================================================
# Get tasks
#!flask/bin/python
from flask import Flask, jsonify
app = Flask(__name__)
tasks = [
{
'id': 1,
'title': u'Buy groceries',
'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
'done': False
},
{
'id': 2,
'title': u'Learn Python',
'description': u'Need to find a good Python tutorial on the web',
'done': False
}
]
@app.route('/todo/api/v1.0/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': tasks})
if __name__ == '__main__':
app.run(debug=True)
===============================================================================
# Get specific task
from flask import abort
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
task = [task for task in tasks if task['id'] == task_id]
if len(task) == 0:
abort(404)
return jsonify({'task': task[0]})
===============================================================================
# Error handler

from flask import make_response


@app.errorhandler(400)
def bad_request(error):
return make_response(jsonify({'error': 'Bad request'}), 400)
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
===============================================================================
# Add new task
from flask import request
@app.route('/todo/api/v1.0/tasks', methods=['POST'])
def create_task():
if not request.json or not 'title' in request.json:
abort(400)
task = {
'id': tasks[-1]['id'] + 1,
'title': request.json['title'],
'description': request.json.get('description', ""),
'done': False
}
tasks.append(task)
return jsonify({'task': task}), 201
===============================================================================
# Edit a task
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id):
task = [task for task in tasks if task['id'] == task_id]
if len(task) == 0:
abort(404)
if not request.json:
abort(400)
if 'title' in request.json and type(request.json['title']) != unicode:
abort(400)
if 'description' in request.json and type(request.json['description']) is no
t unicode:
abort(400)
if 'done' in request.json and type(request.json['done']) is not bool:
abort(400)
task[0]['title'] = request.json.get('title', task[0]['title'])
task[0]['description'] = request.json.get('description', task[0]['descriptio
n'])
task[0]['done'] = request.json.get('done', task[0]['done'])
return jsonify({'task': task[0]})
===============================================================================
# Delete a task
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
task = [task for task in tasks if task['id'] == task_id]
if len(task) == 0:
abort(404)
tasks.remove(task[0])

return jsonify({'result': True})


===============================================================================
# Return URI to control tasks
from flask import url_for
def make_public_task(task):
new_task = {}
for field in task:
if field == 'id':
new_task['uri'] = url_for('get_task', task_id=task['id'], _external=
True)
else:
new_task[field] = task[field]
return new_task
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id):
...
return jsonify({'task': make_public_task(task[0])})
@app.route('/todo/api/v1.0/tasks', methods=['POST'])
def create_task():
...
return jsonify({'task': make_public_task(task)}), 201
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
...
return jsonify({'task': make_public_task(task[0])})
@app.route('/todo/api/v1.0/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': [make_public_task(task) for task in tasks]})
===============================================================================
# Basic HTTPAuth
$ pip install flask-httpauth
from flask.ext.httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
@auth.get_password
def get_password(username):
if username == 'miguel':
return 'python'
return None
@auth.error_handler
def unauthorized():
return make_response(jsonify({'error': 'Unauthorized access'}), 401)
@app.route('/todo/api/v1.0/tasks', methods=['GET'])
@auth.login_required
def get_tasks():
return jsonify({'tasks': tasks})
===============================================================================

# Prevent browser shows authentication dialog (warning: violates the HTTP standa
rd)
@auth.error_handler
def unauthorized():
return make_response(jsonify({'error': 'Unauthorized access'}), 403)
===============================================================================
# Possible improvements
1.
2.
3.
4.

A real web service should be backed by a real database.


Handling multiple users.
Task list could take optional pagination arguments
Filtering task list by certain criteria

===============================================================================
# Javascript client
For the base stack we do not really have much of a choice: The layout will be do
ne in HTML, styling will be done in CSS and scripting in Javascript. But these t
echnologies alone would make for a rough development experience. For example, wh
ile Javascript and CSS work in all browsers, implementations differ, many times
in subtle or obscure ways. There are three areas in which we would benefit from
higher level cross-browser frameworks:
1. Presentation and styling
2. REST request management
3. Templates and event handling
===============================================================================
# Presentation and styling
We will use Twitter Bootstrap, the most popular CSS/Javascript layout framework.
===============================================================================
# REST request management
The browsers Javascript interpreter provides an API for this called XMLHttpReque
st, but the actual implementation varies from browser to browser, so we would ne
ed to write browser specific code if we wanted to code against this API directly
. Lucky for us there are several cross-browser Javascript frameworks that hide t
hese little differences and provide a uniform API. Once again we will pick the l
eading framework in this category, jQuery, which not only provides a uniform Aja
x API but a large number of cross-browser helper functions.
===============================================================================
# Templates and event handling
Many application frameworks adopt a pattern called Model View Controller to writ
e maintainable applications that separate data from behavior. In MVC frameworks
models store the data, views render the data and controllers update views and mo
dels according to user actions. The separation between data, presentation and be
havior is very clear. There are some variations of MVC, like MVP (Model View Pre
senter) or MVVM (Model View ViewModel) that are popular as well.
AngularJS and Ember.js are the ones with the most features, in particular both h
ave two-way data binding, which allows you to associate Javascript variables to
elements in the HTML page so that when one changes the other updates automatical
ly. They both look pretty good, but the learning curve is steep, and their docum
entation is pretty bad.
Backbone is the oldest framework of the four. It has no automatic data binding,
instead you have to set up event handlers and write the code to perform the upda

tes inside them.


Knockout was a surprise. This is a framework that is smaller than the other thre
e, its main feature is two-way data binding. The documentation is excellent, ver
y detailed and complete. There is a fun interactive tutorial followed by referen
ce documentation and several examples, all integrated into an easy to navigate s
ite.
Based on the above analysis, I have decided to give Knockout a chance.
===============================================================================
# Basic page layout
static/index.html:
<!DOCTYPE html>
<html>
<head>
<title>ToDo API Client Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/boots
trap-combined.min.css" rel="stylesheet">
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.js"></script
>
<script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/boots
trap.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js"></sc
ript>
</head>
<body>
<div class="navbar">
<div class="navbar-inner">
<a class="brand" href="#">ToDo API Client Demo</a>
</div>
</div>
<div id="main" class="container">
Main content here!
</div>
<script type="text/javascript">
// application code here!
</script>
</body>
</html>
app.py:
...
app = Flask(__name__, static_url_path='')
...
@app.route('/')
def index():
return app.send_static_file('index.html')
...
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
===============================================================================
# Content area layout
<table class="table table-striped">

<tr><td style="width: 1px;"></td><td><b>Task</b></td><td><b>Options</b></td>


</tr>
<tr>
<td>
<span class="label label-success">Done</span>
</td>
<td><p><b>Task title</b></p><p>Task description</p></td>
<td>
<button class="btn">Edit</button>
<button class="btn">Delete</button>
<span><button class="btn">Mark In Progress</button></span>
</td>
</tr>
<tr>
<td>
<span class="label label-important">In Progress</span>
</td>
<td><p><b>Task title</b></p><p>Task description</p></td>
<td>
<button class="btn">Edit</button>
<button class="btn">Delete</button>
<span><button class="btn">Mark Done</button></span>
</td>
</tr>
</table>
<button class="btn">Add Task</button>
===============================================================================
#Two-way data binding from the HTML side
<table class="table table-striped">
<tr><td style="width: 1px;"></td><td><b>Task</b></td><td><b>Options</b></td>
</tr>
<!-- ko foreach: tasks -->
<tr>
<td>
<span data-bind="visible: done" class="label label-success">Done</sp
an>
<span data-bind="visible: !done()" class="label label-important">In
Progress</span>
</td>
<td><p><b data-bind="text: title"></b></p><p data-bind="text: descriptio
n"></p></td>
<td>
<button data-bind="click: $parent.beginEdit" class="btn">Edit</butto
n>
<button data-bind="click: $parent.remove" class="btn">Delete</button
>
<span data-bind="visible: done">
<button data-bind="click: $parent.markInProgress" class="btn">Ma
rk In Progress</button>
</span>
<span data-bind="visible: !done()">
<button data-bind="click: $parent.markDone" class="btn">Mark Don
e</button>
</span>
</td>
</tr>
<!-- /ko -->
</table>

<button data-bind="click: beginAdd" class="btn">Add Task</button>


===============================================================================
# Two-way data binding from the Javascript side
function TasksViewModel() {
var self = this;
self.tasks = ko.observableArray();
self.tasks([
{
title: ko.observable('title #1'),
description: ko.observable('description #1'),
done: ko.observable(false)
},
{
title: ko.observable('title #2'),
description: ko.observable('description #2'),
done: ko.observable(true)
}
]);
self.beginAdd = function() {
alert("Add");
}
self.beginEdit = function(task) {
alert("Edit: " + task.title());
}
self.remove = function(task) {
alert("Remove: " + task.title());
}
self.markInProgress = function(task) {
task.done(false);
}
self.markDone = function(task) {
task.done(true);
}
}
ko.applyBindings(new TasksViewModel(), $('#main')[0]);
===============================================================================
# Connecting to the REST server
function TasksViewModel() {
var self = this;
self.tasksURI = 'http://localhost:5000/todo/api/v1.0/tasks';
self.username = "pras";
self.password = "python";
self.tasks = ko.observableArray();
self.ajax = function(uri, method, data) {
var request = {
url: uri,
type: method,
contentType: "application/json",
accepts: "application/json",
cache: false,
dataType: 'json',
data: JSON.stringify(data),
beforeSend: function (xhr) {

xhr.setRequestHeader("Authorization",
"Basic " + btoa(self.username + ":" + self.password));
},
error: function(jqXHR) {
console.log("ajax error " + jqXHR.status);
}
};
return $.ajax(request);
}
self.beginAdd = function() {
alert("Add");
}
self.beginEdit = function(task) {
alert("Edit: " + task.title());
}
self.remove = function(task) {
alert("Remove: " + task.title());
}
self.markInProgress = function(task) {
task.done(false);
}
self.markDone = function(task) {
task.done(true);
}
self.ajax(self.tasksURI, 'GET').done(function(data) {
for (var i = 0; i < data.tasks.length; i++) {
self.tasks.push({
uri: ko.observable(data.tasks[i].uri),
title: ko.observable(data.tasks[i].title),
description: ko.observable(data.tasks[i].description),
done: ko.observable(data.tasks[i].done)
});
}
});
}
ko.applyBindings(new TasksViewModel(), $('#main')[0]);
===============================================================================
# Adding a new task
...
<div id="add" class="modal hide fade" tabindex="=1" role="dialog" aria-labelledb
y="addDialogLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="tr
ue"> </button>
<h3 id="addDialogLabel">Add Task</h3>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="inputTask">Task</label>
<div class="controls">
<input data-bind="value: title" type="text" id="inputTask" p
laceholder="Task title" style="width: 150px;">
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputDescription">Description<

/label>
<div class="controls">
<input data-bind="value: description" type="text" id="inputD
escription" placeholder="Description" style="width: 300px;">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button data-bind="click: addTask" class="btn btn-primary">Add Task</but
ton>
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</butt
on>
</div>
</div>
...
function TasksViewModel() {
...
self.beginAdd = function() {
$('#add').modal('show');
}
self.add = function(task) {
self.ajax(self.tasksURI, 'POST', task).done(function(data){
self.tasks.push({
uri: ko.observable(data.task.uri),
title: ko.observable(data.task.title),
description: ko.observable(data.task.description),
done: ko.observable(data.task.done)
});
});
}
...
}
function AddTaskViewModel(){
var self = this;
self.title = ko.observable();
self.description = ko.observable();
self.addTask = function(){
$('#add').modal('hide');
tasksViewModel.add({
title: self.title(),
description: self.description()
});
self.title("");
self.description("");
}
}
var tasksViewModel = new TasksViewModel();
var addTaskViewModel = new AddTaskViewModel();
ko.applyBindings(tasksViewModel, $('#main')[0]);
ko.applyBindings(addTaskViewModel, $('#add')[0]);
===============================================================================
# Editing an existing task
...

<div id="edit" class="modal hide fade" tabindex="=1" role="dialog" aria-labelled


by="editDialogLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="tr
ue"> </button>
<h3 id="editDialogLabel">Add Task</h3>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="inputTask">Task</label>
<div class="controls">
<input data-bind="value: title" type="text" id="inputTask" p
laceholder="Task title" style="width: 150px;">
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputDescription">Description<
/label>
<div class="controls">
<input data-bind="value: description" type="text" id="inputD
escription" placeholder="Description" style="width: 300px;">
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input data-bind="checked: done" type="checkbox"> Done
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button data-bind="click:editTask" class="btn btn-primary">Update Task</
button>
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</butt
on>
</div>
</div>
...
function TasksViewModel() {
...
self.updateTask = function(task, newTask) {
var i = self.tasks.indexOf(task);
self.tasks()[i].uri(newTask.uri);
self.tasks()[i].title(newTask.title);
self.tasks()[i].description(newTask.description);
self.tasks()[i].done(newTask.done);
}
self.beginAdd = function() {
$('#add').modal('show');
}
...
self.beginEdit = function(task) {
editTaskViewModel.setTask(task);
$('#edit').modal('show');
}

self.edit = function(task, data) {


self.ajax(task.uri(), 'PUT', data).done(function(res) {
self.updateTask(task, res.task);
});
}
...
}
...
function EditTaskViewModel() {
var self = this;
self.title = ko.observable();
self.description = ko.observable();
self.done = ko.observable();
self.setTask = function(task) {
self.task = task;
self.title(task.title());
self.description(task.description());
self.done(task.done());
$('edit').modal('show');
}
self.editTask = function() {
$('#edit').modal('hide');
tasksViewModel.edit(self.task, {
title: self.title(),
description: self.description() ,
done: self.done()
});
}
}
var tasksViewModel = new TasksViewModel();
var addTaskViewModel = new AddTaskViewModel();
var editTaskViewModel = new EditTaskViewModel();
ko.applyBindings(tasksViewModel, $('#main')[0]);
ko.applyBindings(addTaskViewModel, $('#add')[0]);
ko.applyBindings(editTaskViewModel, $('#edit')[0]);
===============================================================================
# Delete a task
function TasksViewModel() {
...
self.remove = function(task) {
self.ajax(task.uri(), 'DELETE').done(function() {
self.tasks.remove(task);
})
}
...
}
===============================================================================
# Change task status
function TasksViewModel() {
...
self.markInProgress = function(task) {
self.ajax(task.uri(), 'PUT', { done: false }).done(function(res) {
self.updateTask(task, res.task);
});

}
self.markDone = function(task) {
self.ajax(task.uri(), 'PUT', { done: true }).done(function(res) {
self.updateTask(task, res.task);
});
}
...
}
===============================================================================
# Authentication
function TasksViewModel() {
...
self.username = "";
self.password = "";
...
}
function LoginViewModel() {
var self = this;
self.username = ko.observable();
self.password = ko.observable();
self.login = function() {
$('#login').modal('hide');
tasksViewModel.login(self.username(), self.password());
}
}

You might also like