Is it possible to override methids for db.Model in Google App Engine?
I want to declare beforeSave, afterSave methods, etc.. to create automatic tagging system.
I know there are hooks, but it seems to me a wrong way to solve this issue :)
Thanks!
Yes, it's possible to override these methods. Have a look at this blog post by Nick Johnson.The hooked model class looks this:
class HookedModel(db.Model):
def before_put(self):
pass
def after_put(self):
pass
def put(self, **kwargs):
self.before_put()
super(HookedModel, self).put(**kwargs)
self.after_put()
Read the blog to see how to handle the db.put() method too.
You might also be interested on "derived properties".
I posted an extension to jbochi's HookedModel class so that the before_put and after_put methods are correctly invoked when called from db.put() and the _async family of functions.
See AppEngine PreCall API hooks
I've attempted to improve upon the answer by #jbochi:
According to https://developers.google.com/appengine/docs/python/datastore/modelclass#Model_put , put() should return a Key, so the return value from the library should be passed through.
db.Model.save(), while deprecated, is (a) sometimes still used, (b) meant to be a synonym for put() and (c) apparently not called directly by put() - so should be handled manually.
Revised code:
class HookedModel(db.Model):
def before_put(self):
pass
def after_put(self):
pass
def put(self, **kwargs):
self.before_put()
result = super(HookedModel, self).put(**kwargs)
self.after_put()
return result
def save(self, **kwargs):
self.before_put()
result = super(HookedModel, self).save(**kwargs)
self.after_put()
return result
You should still read http://blog.notdot.net/2010/04/Pre--and-post--put-hooks-for-Datastore-models if you wish to use the monkeypatching, or Chris Farmiloe's answer for use of the async methods.
Related
So far I have used oauth2 to manage authentication using Google Accounts for my app, which gives me some data to complete a basic profile. The problem is that I now want to manage permissions to view and edit a lot of the content in the app, with different groups of people being able to view/edit different parts of the app.
I want some parts of my application to be accessed by users with permission for A, some for B, C, etc. The way I started doing this was using a decorator in the get and post method of each handler, like this:
class SomeHandler(Handler):
#validate_access
def get(self):
pass
#validate_access
def post(self):
pass
Where #validate_access executes the code in the function only if the user has permission for it, and returning an authorization error if not. This seemed to be a good solution a while back, but since there are many handlers I have to use that decorator everywhere, which is annoying and dangerous, since I may forget to put it in some functions.
Is there a way to put this validations in the initialization of the base handler, so that I don't have to use that decorator everywhere? I imagine something like this:
class BaseHandler(webapp2.RequestHandler):
def initialize(self, request, response):
super(Handler, self).initialize(request, response)
self.user = users.get_current_user()
employee = Employee.query(user_id=self.user.user_id).get()
if employee.auth_level > 3:
#See the content: Do whatever the "get" method of the corresponding handler does.
pass
else:
#Raise authorization error
pass
Or is there a better way to do this? (Sorry if it's a basic question, I've never done this before)
Yes, you can overwrite the webapp2 dispatch handler for this purpose. I used this method to enforce role based access control (RBAC).
Example code:
class BaseHandler(webapp2.RequestHandler):
""" webapp2 base handler """
def dispatch(self):
# UserAccess aborts if the user does not have permission to use a handler
UserAccess(self.request)
super(BaseHandler, self).dispatch()
....
class ExampleHandler(BaseHandler):
def get(self):
.....
I use a config file with the allowed roles for a handler. This file is also used to create the webapp2 routes and the dynamic user menu.
The Python source code for Djangular Demos gives examples of how to process the post request from a form that creates a Django object instance. But they don't show how to process the post request from a form which updates an existing object instance.
The code for updating an object seems rather complicated: my code is missing something crucial. Using my code I always get a form validation error: Object with this Name already exists.
I am using the Django generic UpdateView class and my model has a unique field called name.
My code:
from django.views.generic.edit import UpdateView
class MyForm(NgModelFormMixin, Bootstrap3FormMixin, NgModelForm):
scope_prefix='form_data'
form_name = 'my_form'
class Meta:
model = models.MyModel
fields = ['name','person']
class MyModelUpdate(UpdateView):
model = models.MyModel
form_class = MyForm
def post(self, request, **kwargs):
if request.is_ajax():
return self.ajax(request, **kwargs)
return super(MyModelUpdate, self).post(request, **kwargs)
# from the djangular combined_validation example
def ajax(self, request, **kwargs):
# tbd: need update-specific logic here: pass in instance
# parameter (object) or set it from pk. Base class post
# methods use self.get_object()
form = self.form_class(data=json.loads(request.body))
return JsonResponse({'errors': form.errors,
'success_url': force_text(self.success_url)})
What code do I need to get Django to load the instance identified by the pk argument and attach it to the form. That would be the default behavior when the request data comes from POST rather than ajax?
After trial and error experimentation I came up with the following new implementation for the view's ajax method. It passes my tests but feels clunky.
def ajax(self, request, **kwargs):
form = self.form_class(data=json.loads(request.body),
instance=self.get_object())
try:
form.save()
except:
# error is in form.errors
pass
return JsonResponse({'errors': form.errors,
'success_url': force_text(self.success_url)})
I have an entity that is used to store some global app settings. These settings can be edited via an admin HTML page, but very rarely change. I have only one instance of this entity (a singleton of sorts) and always refer to this instance when I need access to the settings.
Here's what it boils down to:
class Settings(ndb.Model):
SINGLETON_DATASTORE_KEY = 'SINGLETON'
#classmethod
def singleton(cls):
return cls.get_or_insert(cls.SINGLETON_DATASTORE_KEY)
foo = ndb.IntegerProperty(
default = 100,
verbose_name = "Some setting called 'foo'",
indexed = False)
#ndb.tasklet
def foo():
# Even though settings has already been fetched from memcache and
# should be available in NDB's in-context cache, the following call
# fetches it from memcache anyways. Why?
settings = Settings.singleton()
class SomeHandler(webapp2.RequestHandler):
#ndb.toplevel
def get(self):
settings = Settings.singleton()
# Do some stuff
yield foo()
self.response.write("The 'foo' setting value is %d" % settings.foo)
I was under the assumption that calling Settings.singleton() more than once per request handler would be pretty fast, as the first call would most probably retrieve the Settings entity from memcache (since the entity is seldom updated) and all subsequent calls within the same request handler would retrieve it from NDB's in-context cache. From the documentation:
The in-context cache persists only for the duration of a single incoming HTTP request and is "visible" only to the code that handles that request. It's fast; this cache lives in memory.
However, AppStat is showing that my Settings entity is being retrieved from memcache multiple times within the same request handler. I know this by looking at a request handler's detailed page in AppStat, expanding the call trace of each call to memcache.Get and looking at the memcahe key that is being reteived.
I am using a lot of tasklets in my request handlers, and I call Settings.singleton() from within the tasklets that need access to the settings. Could this be the reason why the Settings entity is being fetched from memcache again instead of from the in-context cache? If so, what are the exact rules that govern if/when an entity can be fetched from the in-context cache or not? I have not been able to find this information in the NDB documentation.
Update 2013/02/15: I am unable to reproduce this in a dummy test application. Test code is:
class Foo(ndb.Model):
prop_a = ndb.DateTimeProperty(auto_now_add = True)
def use_foo():
foo = Foo.get_or_insert('singleton')
logging.info("Function using foo: %r", foo.prop_a)
#ndb.tasklet
def use_foo_tasklet():
foo = Foo.get_or_insert('singleton')
logging.info("Function using foo: %r", foo.prop_a)
#ndb.tasklet
def use_foo_async_tasklet():
foo = yield Foo.get_or_insert_async('singleton')
logging.info("Function using foo: %r", foo.prop_a)
class FuncGetOrInsertHandler(webapp2.RequestHandler):
def get(self):
for i in xrange(10):
logging.info("Iteration %d", i)
use_foo()
class TaskletGetOrInsertHandler(webapp2.RequestHandler):
#ndb.toplevel
def get(self):
logging.info("Toplevel")
use_foo()
for i in xrange(10):
logging.info("Iteration %d", i)
use_foo_tasklet()
class AsyncTaskletGetOrInsertHandler(webapp2.RequestHandler):
#ndb.toplevel
def get(self):
logging.info("Toplevel")
use_foo()
for i in xrange(10):
logging.info("Iteration %d", i)
use_foo_async_tasklet()
Before running any of the test handlers, I make sure that the Foo entity with keyname singleton exists.
Contrary to what I am seeing in my production app, all of these request handlers show a single call to memcache.Get in Appstats.
Update 2013/02/21: I am finally able to reproduce this in a dummy test application. Test code is:
class ToplevelAsyncTaskletGetOrInsertHandler(webapp2.RequestHandler):
#ndb.toplevel
def get(self):
logging.info("Toplevel 1")
use_foo()
self._toplevel2()
#ndb.toplevel
def _toplevel2(self):
logging.info("Toplevel 2")
use_foo()
for i in xrange(10):
logging.info("Iteration %d", i)
use_foo_async_tasklet()
This handler does show 2 calls to memcache.Get in Appstats, just like my production code.
Indeed, in my production request handler codepath, I have a toplevel called by another toplevel. It seems like a toplevel creates a new ndb context.
Changing the nested toplevel to a synctasklet fixes the problem.
It seems like a toplevel creates a new ndb context.
Exactly, each handler with a toplevel decorator have its own context and therefore a separate cache. You can take a look to the code for toplevel in the link below, in the function documentation states that toplevel is "A sync tasklet that sets a fresh default Context".
https://code.google.com/p/googleappengine/source/browse/trunk/python/google/appengine/ext/ndb/tasklets.py#1033
Is there a way to handle requests from different geographic locations with a different WSGI handler? Specifically I want to allow all requests from one local (US) and redirect all others to a holding page i.e. something like
application_us = webapp2.WSGIApplication([
('/.*', MainHandler)
], debug=DEBUG)
application_elsewhere = webapp2.WSGIApplication([
('/.*', HoldingPageHandler)
], debug=DEBUG)
I'm aware of X-AppEngine-Country however I'm not quite sure how to put it to use in this case?
Okay, building the answer by Sebastian Kreft I figured it's probably easiest to throw this into a base handler of which every other handler is a subclass as follows.
class BaseHandler(webapp2.RequestHandler):
def __init__(self, *args, **kwargs):
super(BaseHandler, self).__init__(*args, **kwargs)
country = self.request.headers.get('X-AppEngine-Country')
if not country == "US" and not country == "" and not country == None: # The last two handle local development
self.redirect('international_holding_page')
logging.info(country)
This is more in keeping with DRY though I'm not certain it's the most efficient way of doing this.
What you need to do is to have only one app and in the handler redirect the user to a HoldingPageHandler if the country is not supported.
See Is it possible to use X-AppEngine-Country within an application. There they explain how to get the country
country = self.request.headers.get('X-AppEngine-Country')
So your handler would be something like this
class MainHandler(webapp2.RequestHandler):
def get(self):
country = self.request.headers.get('X-AppEngine-Country')
if country != "US":
self.redirect_to('hold') #assuming you have a route to hold
# your logic
Using Google App Engines webapp framework, is there any way to pass data to a base template?
To be specific, I just want a logout button to be visible if the user is logged on (using googles own authentication system).
I'm still learning so I'm not sure what parts are GAE specific and what parts are django specific; having to send the logged in user from every single request handler seems very un-DRY.
Arguments to base templates are passed the same way as any other template arguments, by being passed to template.render. I usually solve this by having a convenience method on my base handler that inserts common template arguments, like this:
class BaseHandler(webapp.RequestHandler):
def render_template(self, filename, template_args):
path = os.path.join(os.path.dirname(__file__), 'templates', filename)
template_args.update({
'user': users.get_current_user(),
# ...
})
class MyHandler(BaseHandler):
def get(self):
self.render_template('my.html', {'foo': 'bar'})
I think you are looking for something like login_required decorator in django. You can either try to use a complete django framework in GAE (I never tried) or it can be customized easily with decoration and add your own behavior. In your case, it will be a good idea to pass user's login status to template engine.
#the decorator
def login_checked(f):
def wrap(request, *args, **kwargs):
# get current user
user = get_current_user()
template_path, vars = f(request, *args, **kwargs)
vars['user']= user
template.render(template_path, vars)
return wrap
# usage
class MyPage(webapp.RequestHandler):
#login_checked # add a decoration
def get(self):
# your page
return "the_template_page_you_want", {"the value you want to pass to template": "xxx"}
Take a look at this example:
from google.appengine.api import users
class MyHandler(webapp.RequestHandler):
def get(self):
user = users.get_current_user()
if user:
greeting = ("Welcome, %s! (sign out)" %
(user.nickname(), users.create_logout_url("/")))
else:
greeting = ("Sign in or register." %
users.create_login_url("/"))
self.response.out.write("<html><body>%s</body></html>" % greeting)
Source: http://code.google.com/appengine/docs/python/users/loginurls.html