Update multiple fields on Google NDB entity - python

Working with Google App Engine for Python, I am trying to create and then update an ndb entity. To update a single property, you can just access the property using a dot, e.g.
post.body = body
But I would like to know if there is a simple way to update multiple fields within an ndb entity. The following code:
class Create(Handler):
def post(self):
## code to get params
post = Post(author = author,
title = title,
body = body)
post.put()
class Update(Handler):
def post(self, post_id):
post = post.get_by_id(int(post_id))
fields = ['author', 'title', 'body']
data = get_params(self.request, fields)
for field in fields:
post[field] = data[field]
post.put()
The "Create" handler works fine, but the "Update" handler results in:
TypeError: 'Post' object does not support item assignment
So it seems I would need to access the properties using a dot, but that is not going to work when I have a list of properties I want to access.
Can someone provide an alternative way to update multiple properties of an NDB entity after it has been created?

You should use setattr.
for field in fields:
setattr(post, field, data[field])
(Note that GAE objects do actually provide a hidden way of updating them via a dict, but you should use the public interface.)

You can use the populate method:
post.populate(**data)

Related

How to use different key name in MongoDB while working with Django?

I am working on developing a micro service using Django, where I use MongoDB as backend.
Folks who wrote the main code used java hence the data already present in the MongoDB collections follows camelcase pattern for e.g. firstName is the key name.
Now while working with python I like to name my variables and functions using snake casing like first_name and get_first_name().
What I want is that inside the code I want to refer first name as first_name everywhere but while saving/updating the first name in DB and while returning JSON response of the user data I want to return it as firstName?
Is there any way to achieve this behaviour? Please help me?
Use db_column as argument in your models field constructor as described here:
https://docs.djangoproject.com/en/4.0/ref/models/fields/#db-column
Edit: Regarding the API Output, you have two possibilities, either use a Serializer or the .values() function.
Serializer with django-restframework:
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila#example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
https://www.django-rest-framework.org/api-guide/serializers/
.values(): Blog.objects.values('id', 'name') where the arguments define the output key values, https://docs.djangoproject.com/en/4.0/ref/models/querysets/#values

Django/Tastypie - DELETE requests deleting everything

I have the following model
class Open(models.Model):
name=models.TextField()
opened=models.DateTimeField(auto_now_add=True)
user=models.ForeignKey(User)
and the following resources
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
class OpenResource(ModelResource):
user = fields.ForeignKey(UserResource,'user')
class Meta:
queryset = Open.objects.all()
resource_name = 'open'
I'm trying to delete an Open object from some User's open_set.
For posting, I use the following code (using Requests):
content={"name":file_path,
"user":"/api/v1/user/2/"}
requests.post(
url='http://localhost:8000/api/v1/open/',
data=json.dumps(content),
headers={'content-type':'application/json'},
)
which works perfectly and does exactly what I want.
However, when trying to use similar code for deleting:
content={"name":file_path,
"user":"/api/v1/user/2/"}
requests.delete(
url='http://localhost:8000/api/v1/open/',
data=json.dumps(content),
headers={'content-type':'application/json'},
)
it just deletes all the Open objects from that user (in this case, user with id=2), instead of deleting only the Open objects whose "name" is file_path and whose "user" is "/api/vi/user/2/"
What am I missing?
Distinction between list and detail.
The RESTful methods are split into two kinds:
detail (for GET, PUT and DELETE):
/api/v1/objects/1/
and list (for GET, PUT and DELETE):
/api/v1/objects/
POST and PATCH are bit different.
Means that DELETE /api/v1/objects/ will remove all objects.
To delete one object you have to provide path with id:
DELETE /api/v1/objects/1/
Link to documentation
How filtering works in Tastypie:
You cannot just add things to content and wish to be picked up by Tastypie. All not meant to be there information will be ignored by Tastypie.
If you want to filter your list use queryset parameters:
/api/v1/objects/?name=asdfasdf&user=2
And allow filtering of these:
from tastypie.constants import ALL, ALL_WITH_RELATIONS
class Open(models.Model):
name=models.TextField()
opened=models.DateTimeField(auto_now_add=True)
user=models.ForeignKey(User)
filtering = {'name': ALL, 'user': ALL_WITH_RELATIONS}
After these changes you will be able to delete set of objects:
DELETE /api/v1/objects/?name=asdfasdf&user=5
Link to documentation
Edit:
So your call will look like this:
import urllib
content={"name":file_path,
"user":"/api/v1/user/2/"} # If doesn't work change '/api/v1/user/2/' into 2 I am not sure about this
url = 'http://localhost:8000/api/v1/open/?' + urllib.urlencode(content)
requests.delete(
url=url,
data=None,
headers={'content-type':'application/json'},
)

POST List of Objects w/ endpoints-proto-datastore

tl;dr: is it possible, with endpoints-proto-datastore, to receive a list with objects from a POST and insert it in the db?
Following the samples, when building my API i didn't got how could i let the users POST a list of objects so that i could be more efficient about putting a bunch of data in the db using ndb.put_multi, for example.
From this comment here at endpoints_proto_datastore.ndb.model i imagine that it is not possible with how it is designed. Am i right or i am missing something?
Extending the sample provided by endpoints achieved the desired with:
class Greeting(messages.Message):
message = messages.StringField(1)
class GreetingCollection(messages.Message):
items = messages.MessageField(Greeting, 1, repeated=True)
# then inside the endpoints.api class
#endpoints.method(GreetingCollection, GreetingCollection,
path='hellogretting', http_method='POST',
name='greetings.postGreeting')
def greetings_post(self, request):
result = [item for item in request.items]
return GreetingCollection(items=result)
-- edit --
See the docs about POSTing into the datastore, your only issue is that your models aren't EndpointsModels. Instead define a datastore model for both your Greeting and GreetingCollection:
from endpoints_proto_datastore.ndb import EndpointsModel
class Greeting(EndpointsModel):
message = ndb.StringProperty()
class GreetingCollection(EndpointsModel):
items = ndb.StructuredProperty(Greeting, repeated=True)
Once you've done this, you can use
class MyApi(remote.Service):
# ...
#GreetingCollection.method(path='hellogretting', http_method='POST',
name='greetings.postGreeting')
def greetings_post(self, my_collection):
ndb.put_multi(my_collection.items)
return my_collection

Returning non-predefined fields via. API with Tastypie in Django

I am using Tastypie for non-ORM data source (Amazon Dynamodb). I have gone through the official documentation for non-ORM source and found the following code:
class MessageResource(Resource):
# Just like a Django ``Form`` or ``Model``, we're defining all the
# fields we're going to handle with the API here.
uuid = fields.CharField(attribute='uuid')
user_uuid = fields.CharField(attribute='user_uuid')
message = fields.CharField(attribute='message')
created = fields.IntegerField(attribute='created')
I am new to Tastypie and what I understand is that fields uuid, message, created.. which are returned by API are defined over here. Is there any way that I return those fields that are not defined here i.e. all those fields returned by the dictionary in obj_get_list or obj_get.
You can use the dehydrade method. Simply add a new key to bundle.data.
def dehydrate(self, bundle):
for item in bundle.obj.iteritems():
bundle.data["new_key"] = "new_value"
return bundle

Where's the primary key of the object I want to update in django using modelform?

I'm using modelform in django to insert and update objects in my database, but when I try to update I cannot see the primary key/id of the object being updated:
My model:
class Category(models.Model):
name = models.CharField(max_length=20, db_index = True)
and my form:
class CategoryForm(ModelForm):
class Meta:
model = Category
fields = ['name']
and in my template I got:
{% csrf_token %}
{{ category_form.as_p }}
In my view I do
cat = Category.objects.get(pk = cat_id)
data['category_form'] = CategoryForm(instance = cat)
and pass the data to my template, which renders the form ok, but the id of the object I about to update is nowhere in the html source. How can the code then now what object to update?
I feel stupid asking this since it should be pretty basic, but I've followed all the tutorials and looked thru the django docs, googled and search this site without luck.
Thanks in advance.
Where is cat_id coming from in your view? I guess you receive it in url, like so:
url( r'categories/(\d+)/edit/', your_view, {} ),
in urls.py somewhere. Now in your view you can read it from appropriate view function argument:
def your_view( request, cat_id ):
Now you can obtain object with proper id, which you do here:
cat = Category.objects.get(pk = cat_id)
...and instantiate ModelForm passing it cat object if you want to edit existing object, or don't pass it, if you want an empty form for object creation.
The explanation for this can be found in the django docs here: http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method
While trying to update already saved entity you must provide an instance parameter when you recreate the form. Otherwise django will try to insert a new entity.
foo_form = FooForm(request.POST, instance=foo)
The primary key is an attribute called "id" on your instance object "cat". The form itself, and in your example represented by "cat_id". The Model form should takes care to track the primary key - all you should need to do is pass the resulting "request.POST" data back into CategoryForm, valid the data with is_valid() and then save it.
i.e.
form_with_post = CategoryForm(request.POST)
if form_with_post.is_valid():
form_with_post.save()
else:
... return the form_with_post through the context to display the errors
The ID doesn't need to be in the HTML, because you've passed the instance into the form object when you instantiate it. Django simply updates that instance when you do form.save().

Categories