I'm using NDB on GoogleAppEngine and I want to retrieve a instance Key or ID by passing an e-mail into the query.
My Model looks something like this:
class Users(ndb.Model):
user_name = ndb.StringProperty(required=True)
user_email = ndb.StringProperty(required=True)
user_password = ndb.StringProperty(required=True)
#classmethod
def get_password_by_email(cls, email):
return Users.query(Users.user_email == email).get(projection=[Users.key, Users.user_password])
When running the code, I get the following error:
BadProjectionError: Projecting on unknown property __key__
How can I get an instance ID or Key by querying users through an e-mail in AppEngine's NDB (e.g. Login process)?
Thanks!
A projection query will always include the key as well as the fields you specify, so if keys_only isn't sufficient, then:
return Users.query(Users.user_email == email).get(projection=[Users.password])
If you only need Key you can try keys-only query:
Users.query(Users.user_email == email).get(keys_only=True)
Related
I'm trying to see if the username variable in the post function matches the username in the accountsArchive entity.
I think the problem is that user.username isn't the proper way to reference the username entity. Also, the query above may have a problem. What's the proper way to see if the two usernames match?
Python
class accountsArchive(db.Model):
# The username entity
username = db.StringProperty(required = True)
password = db.TextProperty(required = True)
email = db.StringProperty(required = True)
dateJoined = db.DateTimeProperty(auto_now_add = True)
class loginPage(Handler):
def post(self):
# The username variable
username = self.request.get("username")
password = self.request.get("password")
# The query
user = db.GqlQuery("SELECT * FROM accountsArchive WHERE
user.username = :name", name=username)
# This is how I tried to check if the two usernames matched
if username == user.username:
# Do stuff
You have a number of problems in your code.
Firstly
user = db.GqlQuery("SELECT * FROM accountsArchive WHERE
user.username = :name", name=username)
Is incorrect - you should go back and reread the docs https://cloud.google.com/appengine/docs/python/datastore/gqlreference?hl=en
This query should be
user = db.GqlQuery("SELECT * FROM accountsArchive WHERE
username = :name", name=username)
Next.
The result of this line of code is an instance of GqlQuery class not a user or as you might expect a list of users. See https://cloud.google.com/appengine/docs/python/datastore/gqlqueryclass?hl=en
You now have to fetch the results and/or iterate through them.
For instance
for u in user.run():
if u.username == username:
# then do something
However you have a problem. There is nothing in this that would limit the system a single unique user. So if you get more than one user with the same username what will you do.
Some comments.
You could use the username as the key of the accountsArchive which means you just use a get rather than a query.
Secondly if you are new to appengine and don't have an existing base of code, start out using ndb instead.
I'm trying to develop row level access on google appengine datastore tables. So far I do have got a working example for regular ndb put(), get() and delete() operations using _hooks.
The class Acl shall be used by all the other tables. It's used as a structured property.
class Acl(EndpointsModel):
UNAUTHORIZED_ERROR = 'Invalid token.'
FORBIDDEN_ERROR = 'Permission denied.'
public = ndb.BooleanProperty()
readers = ndb.UserProperty(repeated=True)
writers = ndb.UserProperty(repeated=True)
owners = ndb.UserProperty(repeated=True)
#classmethod
def require_user(cls):
current_user = endpoints.get_current_user()
if current_user is None:
raise endpoints.UnauthorizedException(cls.UNAUTHORIZED_ERROR)
return current_user
#classmethod
def require_reader(cls, record):
if not record:
raise endpoints.NotFoundException(record.NOT_FOUND_ERROR)
current_user = cls.require_user()
if record.acl.public is not True or current_user not in record.acl.readers:
raise endpoints.ForbiddenException(cls.FORBIDDEN_ERROR)
I do want to protect access to the Location class. So I did add three hooks (_post_get_hook, _pre_put_hook and _pre_delete_hook) to the class.
class Location(EndpointsModel):
QUERY_FIELDS = ('state', 'limit', 'order', 'pageToken')
NOT_FOUND_ERROR = 'Location not found.'
description = ndb.TextProperty()
address = ndb.StringProperty()
acl = ndb.StructuredProperty(Acl)
#classmethod
def _post_get_hook(cls, key, future):
location = future.get_result()
Acl.require_reader(location)
def _pre_put_hook(self):
if self.key.id() is None:
current_user = Acl.require_user()
self.acl = Acl()
self.acl.readers.append(current_user)
self.acl.writers.append(current_user)
self.acl.owners.append(current_user)
else:
location = self.key.get()
Acl.require_writer(location)
This does work for all the create, read, update and delete operations, but it does not work for query.
#Location.query_method(user_required=True,
path='location', http_method='GET', name='location.query')
def location_query(self, query):
"""
Queries locations
"""
current_user = Acl.require_user()
query = query.filter(ndb.OR(Location.acl.readers == current_user, Location.acl.public == True))
return query
When I run a query against all locations I get the following error message:
BadArgumentError: _MultiQuery with cursors requires __key__ order
Now I've got some questions:
How do I fix the _MultiQuery issue?
Once fixed: Does this Acl implementation make sense? Are there out of the box alternatives? (I wanted to store the Acl on the record itself to be able to run a direct query, without having to get the keys first.)
Datastore doesn't support OR filters natively. Instead what NDB is doing behind the scenes is running two queries:
query.filter(Location.acl.readers == current_user)
query.filter(Location.acl.public == True)
It then merges the results of these two queries into a single result set. In order to properly merge results (in particular to eliminate duplicates when you have repeated properties), the query needs to be ordered by the key when continuing the query from an arbitrary position (using cursors).
In order to run the query successfully, you need to append a key order to the query before running it:
def location_query(self, query):
"""
Queries locations
"""
current_user = Acl.require_user()
query = query.filter(ndb.OR(Location.acl.readers == current_user,
Location.acl.public == True)
).order(Location.key)
return query
Unfortunately, your ACL implementation will not work for queries. In particular, _post_get_hook is not called for query results. There is a bug filed on the issue tracker about this.
I'm going in circles on getting the id of NDB Datastore.
I have setup the webapp2.RequestHandler to catch the email and get the ID. Basically my goal is to delete an entity, but if I pass the email address to get the ID of the entity, I'm stump, because it gives me results I was just getting. I used ID instead of key_name.
I tried finding the ID by querying via email, but it seems like using query does not have a method attribute to find the id.
def get(self,email):
user = users.get_current_user()
if user:
user_key = ndb.Key('UserPrefs',user.email())
contacts = Contact.query(Contact.email==email,ancestor=user_key)
self.response.write(contacts.id) # there is no attribute such as Contact.id
I tried to find the ID by getting the key, but when I displayed the key, it showed me whatever value I have in the email variable
def get(self,email):
user = users.get_current_user()
if user:
user_key = ndb.Key('UserPrefs',user.email())
contact_key = ndb.Key('Contact',email,parent=user_key)
self.response.write(contact_key.id())
Real Question: So, given that I do not have the ID, how do I find the correct ID inside an entity if I saved my entities via id and not key_name?
Here are the mixture of codes that I'm trying out.
def get(self,email):
user = users.get_current_user()
if user:
user_key = ndb.Key('UserPrefs',user.email())
contact_key = ndb.Key('Contact',email,parent=user_key)
contacts = Contact.query(Contact.email==email,ancestor=user_key)
contact = contacts.get()
contact_key.delete()
# self.response.write(contact.name) # this works
self.response.write(contact_key.id()) # this does not work because I do not have the entity id, and I'd like to get it blindfolded. Is there a way?
Here is my Model for Contact.
class Contact(ndb.Model):
name = ndb.StringProperty()
phone = ndb.StringProperty()
email = ndb.StringProperty()
dateCreated = ndb.DateTimeProperty(auto_now_add=True)
dateUpdated = ndb.DateTimeProperty(auto_now=True)
The docs state:
The identifier may be either a key "name" string assigned by the application or an integer numeric ID generated automatically by the Datastore.
Since you are defining the name property on your Contact class, this is used as the identifier. (You don't want that because in real world different users can have same names)
So if you want NDB to generate numeric IDs for your entities, rename the name property to something else, e.g. username.
Update: let's go step by step:
Problem with the first example is that you are trying to get id on the Query. Query class has no id property defined on it. You should call get() on it:
# get() or fetch() should be called on query to return some data
contacts = Contact.query(Contact.email==email,ancestor=user_key).get()
self.response.write(contacts.id) # there is no attribute such as Contact.id
Problem with the second piece of code is that you are just initialising a Key and providing email as id - the second param of constructor is the id and you are providing email as value. Hence you are getting the email out. Also, there is no database operation here.
Note: the identifiers, which are id, key, urlsafe, or value (for the query) should be passed from the HTTP Request by webapp2.RequestHandler from a parsed url or HTTP POST, GET, PUT, or DELETE.
If you do not have any identifiers or values passed from an HTTP request, it could be difficult to access the specific entity (or the record). So, it is important to take note to pass a form of identifier or value to access the specific entity (or the record in database terms).
So, you can do the following to get the id:
Access by value:
def get(self,email):
user = users.get_current_user()
if user:
user_key = ndb.Key('UserPrefs',user.email())
contacts = Contact.query(Contact.email==email,ancestor=user_key)
contact = contacts.get()
id = contact.key.id() # this access the entity id directly because you have the data.
self.response.write(id)
Access by urlsafe:
def get(self,urlString):
user = users.get_current_user()
if user:
contact_key = ndb.Key(urlsafe=urlString) #urlString refers to the key of contact
contact = contact_key.get()
id = contact.key.id() # this access the entity id directly because you have the data.
self.response.write(id)
Access by HTTP POST Request:
def post(self):
user = users.get_current_user()
if user:
user_key = ndb.Key('UserPrefs',user.email())
email = self.request.get('email')
contacts = Contact.query(Contact.email==email,ancestor=user_key)
contact = contacts.get()
id = contact.key.id() # this access the entity id directly because you have the data.
self.response.write(id)
I've got a simple User model, defined like so:
# models.py
from datetime import datetime
from myapp import db
class User(db.Model):
id = db.Column(db.Integer(), primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
date_updated = db.Column(db.DateTime())
def __init__(self, email, password, date_updated=None):
self.email = email
self.password = password
self.date_updated = datetime.utcnow()
When I create a new User object, my date_updated field gets set to the current time. What I'd like to do is make it so that whenever I save changes to my User object my date_updated field is set to the current time automatically.
I've scoured the documentation, but for the life of me I can't seem to find any references to this. I'm very new to SQLAlchemy, so I really have no prior experience to draw from.
Would love some feedback, thank you.
Just add server_default or default argument to the column fields:
created_on = db.Column(db.DateTime, server_default=db.func.now())
updated_on = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now())
I prefer the {created,updated}_on column names. ;)
SQLAlchemy docs about column insert/update defaults.
[Edit]: Updated code to use server_default arguments in the code.
[Edit 2]: Replaced onupdate with server_onupdate arguments.
date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(),
onupdate=db.func.current_timestamp())
How do I apply a GQL query such that it lists the user_name in the UserInformation model table of those who have logged in within the last 3 days?
I am presuming UserInformation is your own class, it is not part of any App Engine model that I know and you are using python.
You won't be able to return just a list of user_names, you will get a collection of UserInformation model instances.
Do you have a last login date property in your model? If yes, then the following should work.
three_days_ago = datetime.datetime.now() - datetime.timedelta(days = 3)
users = db.Query(UserInformation).filter("login_date >", three_days_ago).fetch(10)