Dynamically Choosing Which Properties to Write to the Datastore

35 views
Skip to first unread message

David Kamenetz

unread,
Jan 23, 2009, 7:48:56 PM1/23/09
to Google App Engine
Has anyone tried to dynamically select which properties they want to
write to an entity on appengine? For example:

I have a web form with 5 fields, and any given user will fill out some
subset of those fields. I POST only the fields with data to the server
(e.g. Fields 1,2,4). On the server side, how do I elegantly write only
properties 1,2, and 4? The Model class has a function that returns a
dictionary of property names (Model.properties()), but how would I use
it to select property names?

In SQL, I would build an INSERT or UPDATE statement by matching the
fields POSTed against the Model.properties() dictionary. I read trunk/
google/appengine/ext/db/init.py which seemed to confirm that there is
no way to refer to the properties as a group. Am I approaching this
the wrong way? Anyone know of a workaround?

kang

unread,
Jan 24, 2009, 7:33:41 AM1/24/09
to google-a...@googlegroups.com
You can get the form data through

self.request.get("name")

and give the object proper property.

for example, in the html

<form action="/submit" method="post">
<input name="a">
<input name="b">
<input type="submit" value="Post"> 
</form>

in the server side, you write,

class Submit(webapp.RequestHandler):    
    def post(self):
        a = self.request.POST.get('a')
        b = self.request.POST.get('b')
        object = Object()
        object.a=a
        object.b=b
        object.put()
--
Stay hungry,Stay foolish.

David Kamenetz

unread,
Jan 24, 2009, 11:39:05 AM1/24/09
to Google App Engine
Hmm...I'm not sure how to use your suggestion. Maybe if I make my
question more specific it will help.

My html looks something like this:

<form method="POST" action="." enctype="multipart/form-data">
Title: <input type="text" name="title"/>
Text: <input type="text" name="txt"/>
Image: <input type="file" name="imgfile"/>

<input type="submit"/>
</form>

If I were writing every field, every time, my python (Django) would
look something like this:

if request.POST:
formitem = TestModel()
formitem.title = request.POST.get('title', '')
formtem.txt = request.POST.get('txt', '')
if request.FILES['imgfile']:
formitem.img = request.FILES['imgfile'].read()

formitem.put()

However, if the user only enters/changes, say, the txt field on the
form I only POST the txt field to the server. I don't send all the
fields. My POST only has data for txt. If I use the code above on an
existing entity, it will erase the title property (formitem.title).

Is there an elegant way to write only the txt element to the txt
property? If we were working with SQL, we could do something roughly
like this:

dctPOST = request.POST
strSQL1 = "INSERT INTO testmodel ("
strSQL2 = "VALUES ("
for k, v in dctPOST.iteritems():
strSQL1 += ", %s" % (k)
strSQL2 += ", %s" % (v)

strSQL = strSQL1 + ") " + strSQL2 + ")"

Then execute the SQL. On other RDBMS platforms, there would probably
be a collection of fields that we could reference like this: table
(strFieldName)=value.

Basically, I want write access to model properties by name or some
other dictionary-like reference (Model.properties() is read only). Am
I approaching this problem the wrong way on appengine, or is this a
limitation?

Thanks,
David

Bill

unread,
Jan 24, 2009, 7:11:51 PM1/24/09
to Google App Engine
Hi David,

On Jan 24, 8:39 am, David Kamenetz <kamene...@yahoo.ca> wrote:
> However, if the user only enters/changes, say, the txt field on the
> form I only POST the txt field to the server. I don't send all the
> fields. My POST only has data for txt. If I use the code above on an
> existing entity, it will erase the title property (formitem.title).
>
> Is there an elegant way to write only the txt element to the txt
> property?

As far as I know, you can't write only one property when replacing an
existing entity, but maybe there's something that can be done at the
lowest levels just before going to the protocol buffer. (See
datastore.py and datastore_pb.py in the SDK where entity dicts are
being passed.)

I use POSTs to create new entities and PUTs to modify existing ones or
store a new entity in a known url. With the PUTs, if there's an
existing entity, you need to read it in and then selectively modify
key/values depending on what's been passed from your form.

So creating new entities through POST is simple. Just set whatever
property you want to set on model initialization, and all others are
not stored.

You can automate the whole process by creating a Model you'll inherit
from (I call mine SerializableModel), and create a method that
iterates through all model properties, calls request.get() on them,
and if the get isn't None (i.e., there's a value passed in from your
form), you add that key/value to your entity dict. You later pass the
entity dict as an initializer into your Model constructor.

For example:

def get_entity_dict(model_class, get_func):
entity_dict = {}
for prop_name, prop_class in model_class.properties().iteritems():
value = get_func(prop_name)
if value:
entity_dict[prop_name] = model_class.deserialize
(prop_class, value)
return entity_dict

So in the above, get_func is set to:

get_func = lambda x : some_handler.request.get(x, default_value=None)

We only set key/value pairs in entity_dict for properties that are set
in form.

The model_class.deserialize() is a routine that takes strings from the
form and converts them into appropriate datastore objects.

So in your handler you'd have something like this in simplified form:

def post(self):
get_func = lambda x : some_handler.request.get(x,
default_value=None)
props = get_entity_dict(MyModel, get_func)
obj = MyModel(**props)
obj.put()

For the PUT case, you read the entity first, set the entity_dict to
the current entity values, and then do the above.

I might open source the model system I've created that does all this
stuff.

-Bill


David Kamenetz

unread,
Jan 24, 2009, 10:32:49 PM1/24/09
to Google App Engine
Thanks Bill, that was helpful. I had been browsing datastore.py in the
SDK, but I wasn't quite sure how to use it. Your solution gave me a
lot of ideas.

Regards,
David

David Kamenetz

unread,
Jan 26, 2009, 12:28:02 AM1/26/09
to Google App Engine
I finally got this working.

My python module looked something like this:

from google.appengine.ext.db import Key
from google.appengine.api.datastore import Get, Put

def edit_item(request, db_id):

objKey = Key(str(db_id))

if request.method == 'POST':
objEntity = Get(objKey)
for k, v in request.POST.iteritems():
objEntity[k]=v
Put(objEntity)
return HttpResponseRedirect('/')

query = TestModel.get(objKey)
return render_to_response('edit.html', ({'modify_data': query,}))

My HTML looked something like this:

<form method="POST" action="." enctype="multipart/form-data">
Title: <input type="text" name="title"
value="{{modify_data.field1}}"/>
Text: <input type="text" name="txt" value="{{modify_data.field2}}"/>

<input type="submit"/>
</form>

Many thanks to Bill!

kang

unread,
Jan 26, 2009, 12:34:46 AM1/26/09
to google-a...@googlegroups.com
now I get your point...good example
--
Stay hungry,Stay foolish.

boson

unread,
Jan 26, 2009, 2:33:43 PM1/26/09
to Google App Engine
Consider wrapping the get/change/put in a transaction:
http://code.google.com/appengine/docs/python/datastore/transactions.html
Reply all
Reply to author
Forward
0 new messages