Skip to content

Commit

Permalink
Added User model and UserController
Browse files Browse the repository at this point in the history
  • Loading branch information
omo committed Nov 12, 2011
1 parent 3d930e2 commit 2bf326a
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 39 deletions.
17 changes: 15 additions & 2 deletions MEMO.txt
Expand Up @@ -8,8 +8,21 @@ TODO:
+ Add empty index page that ask user to login.
+ Add empty main page which required login user.
+ Add bootstrap stack
- Add empty js test page
- Add empty app js to main page
+ Add empty js test page
+ Add empty app js to main page

> Minimal Model and API:
+ Define User Model
> Define UserController
+ /r/<userid> for get
+ /r/me for get
- Define Card Model
> Define CardController
- /r/<userid>/card for list (no pagination)
- /r/<userid>/card/<cardid> for get/put/delete

> A simple CRUD for the Card model
> Exercise Rounds

> Someday:
- Logout
Expand Down
2 changes: 0 additions & 2 deletions app/app.yaml
Expand Up @@ -19,5 +19,3 @@ libraries:
version: latest
- name: jinja2
version: latest
#- name: MarkupSafe
# version: latest
111 changes: 109 additions & 2 deletions app/py/blbr/__init__.py
@@ -1,8 +1,65 @@

import functools
import webapp2
import json
from google.appengine.ext import db
from google.appengine.api import users

def require_login(**options):
if users.get_current_user():
return None
redirect = options.get('redirect')
if redirect:
return webapp2.redirect(users.create_login_url(redirect))
resp = webapp2.Response()
resp.status = '400 Bad Request'
return resp

def login_required(func, **deco_kwargs):
@functools.wraps(func)
def decorated_view(*args, **kwargs):
return require_login(**deco_kwargs) or func(*args, **kwargs)
return decorated_view

class User(db.Model):
def user_to_serializable(user):
return {
"nickname": user.nickname(),
"email": user.email(),
"user_id": user.user_id()
}


class ModelSerizable(object):

to_map = {
users.User: user_to_serializable
}

@staticmethod
def _build_property_name(list, base):
if issubclass(base, ModelSerizable):
list += base.list_property_names()
return list

@classmethod
def list_property_names(cls):
names = [k for k,v in cls.__dict__.items() if isinstance(v, db.Property)]
return reduce(cls._build_property_name, cls.__bases__, names)

@classmethod
def _build_serializable(cls, value, dict):
names = cls.list_property_names()
for name in names:
value = getattr(value, name)
to = ModelSerizable.to_map.get(value.__class__)
dict[name] = to and to(value) or value
return dict

def to_serializable(self):
return self._build_serializable(self, {"id": str(self.key()) })


class User(db.Model, ModelSerizable):
account = db.UserProperty(required=True)

@classmethod
Expand All @@ -11,4 +68,54 @@ def find_by_account(cls, account):

@classmethod
def ensure_by_account(cls, account):
return cls.find_by_account(account) or cls(account=account)
found = cls.find_by_account(account)
if found:
return found
created = cls(account=account)
created.put()
return created


class UserMapper(object):
def get_account(self):
return users.get_current_user()

def get(self, params):
if (1 != len(params)):
return users.create_login_url(self.url)

account = self.get_account()
key = params[0]
if (key == "me"):
return User.ensure_by_account(account)
try:
found = User.get(db.Key(key))
except db.BadKeyError:
return None
if not found or found.account != account:
return None
return found


class UserController(webapp2.RequestHandler):
url = '/r/([^/]+)'

@property
def mapper(self):
if not hasattr(self, '_mapper'):
self._mapper = UserMapper()
return self._mapper

@login_required
def get(self, *args):
found = self.mapper.get(args)
if not found:
self.response.status = 404
return
self.response.headers['Content-Type'] = 'text/json'
json.dump(found.to_serializable(), self.response.out)
return self.response


def to_application(handler_classes):
return webapp2.WSGIApplication([(p.url, p) for p in handler_classes])
101 changes: 89 additions & 12 deletions app/py/hello_test.py
@@ -1,5 +1,8 @@

import unittest
import os
import json
import webapp2

from google.appengine.api import memcache
from google.appengine.api import users
Expand All @@ -8,15 +11,21 @@

import blbr

class HelloTest(unittest.TestCase):

def test_hello(self):
pass
def round_serialization(obj):
return json.loads(json.dumps(obj))

class WSGITestHelper(object):

class UserTest(unittest.TestCase):
def __init__(self, application):
self.application = application

def setUp(self):
def get(self, url):
req = webapp2.Request.blank(url)
return req.get_response(self.application)

class TestBedHelper(object):
def __init__(self):
# First, create an instance of the Testbed class.
self.testbed = testbed.Testbed()
# Then activate the testbed, which prepares the service stubs for use.
Expand All @@ -25,19 +34,87 @@ def setUp(self):
self.testbed.init_user_stub()
self.testbed.init_datastore_v3_stub()
self.testbed.init_memcache_stub()
self.user_email = "alice@example.com"
os.environ['USER_EMAIL'] = self.user_email

def tearDown(self):
def disable_current_user(self):
if os.environ.get('USER_EMAIL'):
del os.environ['USER_EMAIL']

def deactivate(self):
self.disable_current_user()
self.testbed.deactivate()


class SerializableTest(unittest.TestCase):
def setUp(self):
self.helper = TestBedHelper()

def tearDown(self):
self.helper.deactivate()

def test_hello(self):
account = users.User("alice@example.com")
new_user = blbr.User(account=account)
email = "bob@example.com"
new_user = blbr.User(account=users.User(email))
new_user.put()
existing_user = blbr.User.find_by_account(new_user.account)
rounded = round_serialization(existing_user.to_serializable())
self.assertIsNotNone(rounded['id'])
self.assertEquals(rounded['account']['email'], email)


class UserTest(unittest.TestCase, TestBedHelper):
def setUp(self):
self.helper = TestBedHelper()
self.web = WSGITestHelper(blbr.to_application([blbr.UserController]))
self.bob_email = "bob@example.com"

existing_user = blbr.User.find_by_account(account)
def tearDown(self):
self.helper.deactivate()

def test_list_property_names(self):
names = blbr.User.list_property_names()
self.assertEquals(names, ['account'])

def create_bob_user(self):
account = users.User(self.bob_email)
creating = blbr.User(account=account)
creating.put()
return creating

def test_find_by_account(self):
new_user = self.create_bob_user()
existing_user = blbr.User.find_by_account(new_user.account)
self.assertEquals(existing_user.account, new_user.account)

def test_ensure(self):
account = users.User("alice@example.com")
u = blbr.User.ensure_by_account(account)
u = blbr.User.ensure_by_account(users.User("bob@example.com"))
self.assertIsNotNone(u)
self.assertTrue(u.is_saved())

def test_web_get_me(self):
res = self.web.get('/r/me')
self.assertRegexpMatches(res.status, '200')
self.assertEquals(self.helper.user_email, json.loads(res.body)["account"]["email"])

def test_web_get_me(self):
alice = blbr.User.ensure_by_account(users.get_current_user())
res = self.web.get('/r/%s' % str(alice.key()))
self.assertRegexpMatches(res.status, '200')
self.assertEquals(self.helper.user_email, json.loads(res.body)["account"]["email"])

def test_web_get_non_owner(self):
bob = self.create_bob_user()
res = self.web.get('/r/%s' % str(bob.key()))
self.assertRegexpMatches(res.status, '404')

def test_web_get_notfound(self):
bob = self.create_bob_user()
res = self.web.get('/r/nonexistent')
self.assertRegexpMatches(res.status, '404')

def test_web_unauth(self):
self.helper.disable_current_user()
res = self.web.get('/r/me')
self.assertRegexpMatches(res.status, '400')

40 changes: 19 additions & 21 deletions app/py/theapp.py
Expand Up @@ -14,43 +14,41 @@

class TemplatePage(webapp2.RequestHandler):

LOGIN_REQUIRED = True
login_required = True

def make_context(self):
return {}

def get(self):
user = users.get_current_user()
if not user and self.LOGIN_REQUIRED:
#raise Exception("Hello")
self.response = webapp2.redirect(users.create_login_url(self.URL))
return self.response

template = jinja_environment.get_template(self.TEMPLATE_NAME)
requiring = self.login_required and blbr.require_login(redirect=self.url) or None
if requiring:
return requiring
template = jinja_environment.get_template(self.template_name)
self.response.headers['Content-Type'] = 'text/html'
self.response.out.write(template.render(self.make_context()))

return self.response

class IndexPage(TemplatePage):
URL = "/"
TEMPLATE_NAME = 'index.html'
LOGIN_REQUIRED = False
url = "/"
template_name = 'index.html'
login_required = False

def make_context(self):
return {'login_url': users.create_login_url(DashboardPage.URL)}
return {'login_url': users.create_login_url(DashboardPage.url)}


class DashboardPage(TemplatePage):
URL = '/dashboard'
TEMPLATE_NAME = 'dashboard.html'
url = '/dashboard'
template_name = 'dashboard.html'

class TestPage(TemplatePage):
URL = '/test'
LOGIN_REQUIRED = False
TEMPLATE_NAME = 'test.html'
url = '/test'
login_required = False
template_name = 'test.html'


page_classes = [IndexPage, DashboardPage, TestPage]
page_classes = [IndexPage, DashboardPage, TestPage,
blbr.UserController]

# The name |app| is given at 'app.cfg' file.
app = webapp2.WSGIApplication([(p.URL, p) for p in page_classes])
# Don't change the name |app|. It is given in the 'app.cfg' file.
app = blbr.to_application(page_classes)

0 comments on commit 2bf326a

Please sign in to comment.