From e13ceaf351d4b54dd2bc651d9f4385a8188b7418 Mon Sep 17 00:00:00 2001 From: Alexis Metaireau Date: Tue, 13 Sep 2011 18:15:07 +0200 Subject: [PATCH] REST API is now able to list stuff \o/ --- budget/api.py | 53 +++++++++++++++++++----------------------------- budget/models.py | 27 +++++++++++++++++++++++- budget/rest.py | 19 ++++++++++++++--- budget/web.py | 13 ++++++++++-- 4 files changed, 74 insertions(+), 38 deletions(-) diff --git a/budget/api.py b/budget/api.py index c307fdf..c50d668 100644 --- a/budget/api.py +++ b/budget/api.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- from flask import * -import werkzeug from models import db, Project, Person, Bill from utils import for_all_methods -from rest import RESTResource, need_auth # FIXME make it an ext +from rest import RESTResource, need_auth# FIXME make it an ext +from werkzeug import Response api = Blueprint("api", __name__, url_prefix="/api") @@ -33,7 +33,7 @@ class ProjectHandler(object): @need_auth(check_project, "project") def get(self, project): - return "get" + return project @need_auth(check_project, "project") def delete(self, project): @@ -47,7 +47,10 @@ class ProjectHandler(object): class MemberHandler(object): def get(self, project, member_id): - pass + member = Person.query.get(member_id) + if not member or member.project != project: + return Response('Not Found', status=404) + return member def list(self, project): return project.members @@ -59,25 +62,32 @@ class MemberHandler(object): pass def delete(self, project, member_id): - pass + if project.remove_member(member_id): + return Response('OK', status=200) class BillHandler(object): - def get(self, project, member_id): - pass + def get(self, project, bill_id): + bill = Bill.query.get(project, bill_id) + if not bill: + return Response('Not Found', status=404) + return bill def list(self, project): - pass + return project.get_bills().all() def add(self, project): pass - def update(self, project, member_id): + def update(self, project, bill_id): pass - def delete(self, project, member_id): - pass + def delete(self, project, bill_id): + bill = Bill.query.delete(project, bill_id) + if not bill: + return Response('Not Found', status=404) + return bill project_resource = RESTResource( @@ -102,24 +112,3 @@ bill_resource = RESTResource( app=api, handler=BillHandler(), authentifier=check_project) - -# projects: add, delete, edit, get -# GET /project/ → get -# PUT /project/ → add & edit -# DELETE /project/ → delete - -# project members: list, add, delete -# GET /project//members → list -# POST /project//members/ → add -# PUT /project//members/ → edit -# DELETE /project//members/ → delete - -# project bills: list, add, delete, edit, get -# GET /project//bills → list -# GET /project//bills/ → get -# DELETE /project//bills/ → delete -# POST /project//bills/ → add - - -# GET, PUT, DELETE: / : Get, update and delete -# GET, POST: / Add & List diff --git a/budget/models.py b/budget/models.py index 8d68746..e56ae4e 100644 --- a/budget/models.py +++ b/budget/models.py @@ -3,6 +3,8 @@ from collections import defaultdict from datetime import datetime from flaskext.sqlalchemy import SQLAlchemy +from sqlalchemy import orm + db = SQLAlchemy() # define models @@ -103,6 +105,29 @@ billowers = db.Table('billowers', ) class Bill(db.Model): + + class BillQuery(orm.query.Query): + + def get(self, project, id): + try: + return self.join(Person, Project)\ + .filter(Bill.payer_id == Person.id)\ + .filter(Person.project_id == Project.id)\ + .filter(Project.id == project.id)\ + .filter(Bill.id == id).one() + except orm.exc.NoResultFound: + return None + + def delete(self, project, id): + bill = self.get(project, id) + if bill: + db.session.delete(bill) + return bill + + query_class = BillQuery + + _to_serialize = ("id", "payer_id", "owers", "amount", "date", "what") + id = db.Column(db.Integer, primary_key=True) payer_id = db.Column(db.Integer, db.ForeignKey("person.id")) @@ -122,7 +147,6 @@ class Bill(db.Model): return "" % (self.amount, self.payer, ", ".join([o.name for o in self.owers])) - class Archive(db.Model): id = db.Column(db.Integer, primary_key=True) project_id = db.Column(db.Integer, db.ForeignKey("project.id")) @@ -138,3 +162,4 @@ class Archive(db.Model): def __repr__(self): return "" + diff --git a/budget/rest.py b/budget/rest.py index a61f02c..e698e21 100644 --- a/budget/rest.py +++ b/budget/rest.py @@ -1,5 +1,6 @@ import json from flask import request +import werkzeug class RESTResource(object): """Represents a REST resource, with the different HTTP verbs""" @@ -117,18 +118,28 @@ def need_auth(authentifier, name=None, remove_attr=True): return wrapped return wrapper - # serializers def serialize(func): + """If the object returned by the view is not already a Response, serialize + it using the ACCEPT header and return it. + """ def wrapped(*args, **kwargs): + # get the mimetype mime = request.accept_mimetypes.best_match(SERIALIZERS.keys()) - return SERIALIZERS.get(mime, "text/json")\ - .encode(func(*args, **kwargs)) + data = func(*args, **kwargs) + + if isinstance(data, werkzeug.Response): + return data + else: + # serialize it + return SERIALIZERS.get(mime, "text/json").encode(data) + return wrapped class JSONEncoder(json.JSONEncoder): + """Subclass of the default encoder to support custom objects""" def default(self, o): if hasattr(o, "_to_serialize"): # build up the object @@ -136,6 +147,8 @@ class JSONEncoder(json.JSONEncoder): for attr in o._to_serialize: data[attr] = getattr(o, attr) return data + elif hasattr(o, "isoformat"): + return o.isoformat() else: return json.JSONEncoder.default(self, o) diff --git a/budget/web.py b/budget/web.py index f72a686..61d67e5 100644 --- a/budget/web.py +++ b/budget/web.py @@ -2,6 +2,7 @@ from collections import defaultdict from flask import * from flaskext.mail import Mail, Message +import werkzeug # local modules from models import db, Project, Person, Bill @@ -239,7 +240,11 @@ def add_bill(): @main.route("//delete/") def delete_bill(bill_id): - bill = Bill.query.get_or_404(bill_id) + # fixme: everyone is able to delete a bill + bill = Bill.query.get(g.project, bill_id) + if not bill: + raise werkzeug.exceptions.NotFound() + db.session.delete(bill) db.session.commit() flash("The bill has been deleted") @@ -249,7 +254,11 @@ def delete_bill(bill_id): @main.route("//edit/", methods=["GET", "POST"]) def edit_bill(bill_id): - bill = Bill.query.get_or_404(bill_id) + # FIXME: Test this bill belongs to this project ! + bill = Bill.query.get(g.project, bill_id) + if not bill: + raise werkzeug.exceptions.NotFound() + form = get_billform_for(g.project, set_default=False) if request.method == 'POST' and form.validate(): form.save(bill)