How to get or create user profile in GAE? - google-app-engine

I have a Profile model:
class Profile(db.Model):
user = db.UserProperty(auto_current_user=True)
bio = db.StringProperty()
I'd like to display the user's existing bio in this view. If the user has no profile yet, I'd like to create it. Here's what I have so far, which doesn't work yet:
class BioPage(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
if user:
profile = Profile.get_or_insert(user=user) #This line is wrong
profile.bio = "No bio entered yet."
profile.save()
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('Hello, ' + user.nickname() + '<br/>Bio: ' + profile.bio)
else:
self.redirect(users.create_login_url(self.request.uri))
How do I fix the incorrect line above? I know that get_or_insert() should take a key name, but I can't figure out what that would be.
(Should the user field in Profile even be a db.UserProperty?)

You have to pass the key_name to get_or_insert(), in this case, like so:
profile = Profile.get_or_insert(key_name=user.email())
Note that since the user property is auto-populated because of the auto_current_user=True you don't need to pass it to the get_or_insert() call. In your case you don't need to pass anything but the key name.

You probably don't want to use db.UserProperty, for reasons explained here. In summary, if a user changes his/her email address, your (old) stored 'User' will not compare equal to the currently-logged-in (new) 'User'.
Instead, store the user.user_id() as either a StringProperty on your Profile model (as shown on the page I referenced above), or as the key (key_name) of your Profile model. An example of the latter is here.

Related

Is it possible to set two fields as indexes on an entity in ndb?

I am new to ndb and gae and have a problem coming up with a good solution setting indexes.
Let say we have a user model like this:
class User(ndb.Model):
name = ndb.StringProperty()
email = ndb.StringProperty(required = True)
fb_id = ndb.StringProperty()
Upon login if I was going to check against the email address with a query, I believe this would be quite slow and inefficient. Possibly it has to do a full table scan.
q = User.query(User.email == EMAIL)
user = q.fetch(1)
I believe it would be much faster, if User models were saved with the email as their key.
user = user(id=EMAIL)
user.put()
That way I could retrieve them like this a lot faster (so I believe)
key = ndb.Key('User', EMAIL)
user = key.get()
So far if I am wrong please correct me. But after implementing this I realized there is a chance that facebook users would change their email address, that way upon a new oauth2.0 connection their new email can't be recognized in the system and they will be created as a new user. Hence maybe I should use a different approach:
Using the social-media-provider-id (unique for all provider users)
and
provider-name (in rare case that two twitter and facebook users share
the same provider-id)
However in order to achieve this, I needed to set two indexes, which I believe is not possible.
So what could I do? Shall I concatenate both fields as a single key and index on that?
e.g. the new idea would be:
class User(ndb.Model):
name = ndb.StringProperty()
email = ndb.StringProperty(required = True)
provider_id = ndb.StringProperty()
provider_type = ndb.StringProperty()
saving:
provider_id = 1234
provider_type = fb
user = user(id=provider_id + provider_type)
user.put()
retrieval:
provider_id = 1234
provider_type = fb
key = ndb.Key('User', provider_id + provider_type)
user = key.get()
This way we don't care any more if the user changes the email address on his social media.
Is this idea sound?
Thanks,
UPDATE
Tim's solution sounded so far the cleanest and likely also the fastest to me. But I came across a problem.
class AuthProvider(polymodel.PolyModel):
user_key = ndb.KeyProperty(kind=User)
active = ndb.BooleanProperty(default=True)
date_created = ndb.DateTimeProperty(auto_now_add=True)
#property
def user(self):
return self.user_key.get()
class FacebookLogin(AuthProvider):
pass
View.py: Within facebook_callback method
provider = ndb.Key('FacebookLogin', fb_id).get()
# Problem is right here. provider is always None. Only if I used the PolyModel like this:
# ndb.Key('AuthProvider', fb_id).get()
#But this defeats the whole purpose of having different sub classes as different providers.
#Maybe I am using the key handeling wrong?
if provider:
user = provider.user
else:
provider = FacebookLogin(id=fb_id)
if not user:
user = User()
user_key = user.put()
provider.user_key = user_key
provider.put()
return user
One slight variation on your approach which could allow a more flexible model will be to create a separate entity for the provider_id, provider_type, as the key or any other auth scheme you come up with
This entity then holds a reference (key) of the actual user details.
You can then
do a direct get() for the auth details, then get() the actual user details.
The auth details can be changed without actually rewriting/rekeying the user details
You can support multiple auth schemes for a single user.
I use this approach for an application that has > 2000 users, most use a custom auth scheme (app specific userid/passwd) or google account.
e.g
class AuthLogin(ndb.Polymodel):
user_key = ndb.KeyProperty(kind=User)
status = ndb.StringProperty() # maybe you need to disable a particular login with out deleting it.
date_created = ndb.DatetimeProperty(auto_now_add=True)
#property
def user(self):
return self.user_key.get()
class FacebookLogin(AuthLogin):
# some additional facebook properties
class TwitterLogin(AuthLogin):
# Some additional twitter specific properties
etc...
By using PolyModel as the base class you can do a AuthLogin.query().filter(AuthLogin.user_key == user.key) and get all auth types defined for that user as they all share the same base class AuthLogin. You need this otherwise you would have to query in turn for each supported auth type, as you can not do a kindless query without an ancestor, and in this case we can't use the User as the ancestor becuase then we couldn't do a simple get() to from the login id.
However some things to note, all subclasses of AuthLogin will share the same kind in the key "AuthLogin" so you still need to concatenate the auth_provider and auth_type for the keys id so that you can ensure you have unique keys. E.g.
dev~fish-and-lily> from google.appengine.ext.ndb.polymodel import PolyModel
dev~fish-and-lily> class X(PolyModel):
... pass
...
dev~fish-and-lily> class Y(X):
... pass
...
dev~fish-and-lily> class Z(X):
... pass
...
dev~fish-and-lily> y = Y(id="abc")
dev~fish-and-lily> y.put()
Key('X', 'abc')
dev~fish-and-lily> z = Z(id="abc")
dev~fish-and-lily> z.put()
Key('X', 'abc')
dev~fish-and-lily> y.key.get()
Z(key=Key('X', 'abc'), class_=[u'X', u'Z'])
dev~fish-and-lily> z.key.get()
Z(key=Key('X', 'abc'), class_=[u'X', u'Z'])
This is the problem you ran into. By adding the provider type as part of the key you now get distinct keys.
dev~fish-and-lily> z = Z(id="Zabc")
dev~fish-and-lily> z.put()
Key('X', 'Zabc')
dev~fish-and-lily> y = Y(id="Yabc")
dev~fish-and-lily> y.put()
Key('X', 'Yabc')
dev~fish-and-lily> y.key.get()
Y(key=Key('X', 'Yabc'), class_=[u'X', u'Y'])
dev~fish-and-lily> z.key.get()
Z(key=Key('X', 'Zabc'), class_=[u'X', u'Z'])
dev~fish-and-lily>
I don't believe this is any less convenient a model for you.
Does all that make sense ;-)
While #Greg's answer seems OK, I think it's actually a bad idea to associate an external type/id as a key for your entity, because this solution doesn't scale very well.
What if you would like to implement your own username/password at one point?
What if the user going to delete their Facebook account?
What if the same user wants to sign in with a Twitter account as well?
What if the user has more than one Facebook accounts?
So the idea of having the type/id as key looks weak. A better solution would be to have a field for every type to store only the id. For example facebook_id, twitter_id, google_id etc, then query on these fields to retrieve the actual user. This will happen during sign-in and signup process so it's not that often. Of course you will have to add some logic to add another provider for an already existed user or merge users if the same user signed in with a different provider.
Still the last solution won't work if you want to support multiple sign-ins from the same provider. In order to achieve that you will have to create another model that will store only the external providers/ids and associate them with your user model.
As an example of the second solution you could check my gae-init project where I'm storing the 3 different providers in the User model and working on them in the auth.py module. Again this solution doesn't not scale very well with more providers and doesn't support multiple IDs from the same provider.
Concatenating the user-type with their ID is sensible.
You can save on your read and write costs by not duplicating the type and ID as properties though - when you need to use them, just split the ID back up. (Doing this will be simpler if you include a separator between the parts, '%s|%s' % (provider_type, provider_id) for example)
If you want to use a single model, you can do something like:
class User(ndb.Model):
name = ndb.StringProperty()
email = ndb.StringProperty(required = True)
providers = ndb.KeyProperty(repeated=True)
auser = User(id="auser", name="A user", email="auser#example.com")
auser.providers = [
ndb.Key("ProviderName", "fb", "ProviderId", 123),
ndb.Key("ProviderName", "tw", "ProviderId", 123)
]
auser.put()
To query for a specific FB login, you simple do:
fbkey = ndb.Key("ProviderName", "fb", "ProviderId", 123)
for entry in User.query(User.providers==fbkey):
# Do something with the entry
As ndb does not provide an easy way to create a unique constraint, you could use the _pre_put_hook to ensure that providers is unique.

setWhatId in salesforce using email template

i have to send an email to a user in salesforce using email template.this template contain merge field type of custom object.
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setTargetObjectId(user.get(0).id);
mail.setTargetObjectId(user.get(0).Id)
mail.setTemplateId(specifier.get(0).Template_id__c);
mail.saveAsActivity = false;
mail.setWhatId(custom_object.Id);
i read in documentation
If you specify a contact for the targetObjectId field, you can specify a whatId as well. This helps to further ensure that merge fields in the template contain the correct data. The value must be one of the following types:
Account
Asset
Campaign
Case
Contract
Opportunity
Order
Product
Solution
Custom
but if we are sending email to a user not to contact then how to assign a custom object for merge field type in custom objects as in the above code
This is a GIGANTIC whole in their email methods, and one that has annoyed me for years. Particularly given workflow email alerts seem to have no problem sending an email template for a user. Alas, you can't use setWhatId() if your target is a user. But you can vote for them to add that functionality,
I've worked around this I typically create a contact with the same name and email as the user, use it to send the email, and then delete it. This works well, although dealing with validation rules on the contact object can be a challenge. See their dev boards for a full discussion.
You can get the template and replace the merge fields as follows:
EmailTemplate template = [SELECT Id, Subject, HtmlValue, Body FROM EmailTemplate WHERE Name = 'Case Update'];
Case modifiedCase = [SELECT Account.Id, Account.Name, Owner.FirstName, Owner.LastName, CaseNumber, Subject, LastModifiedBy.FirstName, LastModifiedBy.LastName from Case where Id=:modifiedCaseId];
String subject = template.Subject;
subject = subject.replace('{!Case.Account}', modifiedCase.Account.Name);
subject = subject.replace('{!Case.CaseNumber}', modifiedCase.CaseNumber);
subject = subject.replace('{!Case.Subject}', modifiedCase.Subject);
String htmlBody = template.HtmlValue;
htmlBody = htmlBody.replace('{!Case.Account}', modifiedCase.Account.Name);
htmlBody = htmlBody.replace('{!Case.OwnerFullName}', ownerFullName);
...
Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
email.setSubject(subject);
email.setHtmlBody(htmlBody);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});
As far as no new fields are added in the template this will work fine. The admin can mess with the format of the email without the need for code changes.
Not sure this is possible to do, but it depends on the relationship between your custom object and your users that will be receiving the merged emails. Do you have a 1-to-1 relationship between User and CustomObject? If so, perhaps adding a reference to the single custom object instance that each user object references and then adding custom formula fields on your user object with CustomObject__r.CustomField__c would do the trick.
In a custom formula field on your User object:
TEXT(CustomObject__r.CustomField__c)
Then your template could be changed into a User template and the merge fields would be the formula fields that actually pointed to your custom object instance. But if you have some other relationship like 1-to-many or many-to-many between User and CustomObject__c, I think you're out of luck.

Expanding on django forms

I have a modelform, which has three required fields: user, network, and position. The position is pulled by request.POST, and the other two will be supplied outside of it. This is what I currently have:
form = StartForm(request.POST)
form.save()
Obviously, this form is not validating, because I haven't provided the user and network instances. How do I add this additional information to the form? Conceptually, I'm looking for something like this:
form = StartForm(request.POST + user_id=10, network_id=20)
form.save()
Can't you do something similar to: form = StartForm(position = request.POST, user_id = 10, network_id = 20)? You might have to print the value for request.POST since I think it's actually a list. So just find out what position is in the request.POST
There are two options there:
user and network fields are not allowed to come from request.POST. For example if user should be the currently logged in user which comes from request.user.
In that case you can do:
class StartForm(form.models.ModelForm):
class Meta:
model = MyModel
fields = ["position", ] # You don't include user and network to the form
form = StartForm(request.POST, instance=MyModel(user=user, network=network))
So you initialize the form with a model which has pre-filled fields.
user and network fields are allowed to come from request.POST. Then you do:
form = StartForm(request.POST, initial={"user": user, "network": network})
Note that in this case user and network fields may be overriden by values which come from request.POST.

Converting a User.user_id() to a User.email()

Is there a way to derive a user's email given his user_id?
You can build a relationship between the email and the user_id,and then retrieve it as needed. If the user is logged in you can easily access both properties separately.
http://code.google.com/intl/en/appengine/docs/python/users/userclass.html
However the user_id is not a hashed version of the email that can be reconstructed using some kind of algorithm.
It seems like it's not possible to derive an email from a user id. If you want to go from user_id to email, you must store both when the user is logged in, and then, when the user is not logged in, do a lookup to convert. Eg.
class Email(db.model):
'''keyed by user_id'''
email=db.EmailProperty()
def save_user():
u=users.get_current_user()
k=db.Key.from_path('Email',u.user_id())
e=Email.get(k)
if not e:
Email(key=k, email=u.email()).put()
def get_email_from_user_id(id):
'''No way to derive email without a lookup.'''
k=db.Key.from_path('Email',id)
return Email.get(k).email # raises exception if email is not found

Use a db.StringProperty() as unique identifier in Google App Engine

I just have a hunch about this. But if feels like I'm doing it the wrong way. What I want to do is to have a db.StringProperty() as a unique identifier. I have a simple db.Model, with property name and file. If I add another entry with the same "name" as one already in the db.Model I want to update this.
As of know I look it up with:
template = Templates.all().filter('name = ', name)
Check if it's one entry already:
if template.count() > 0:
Then add it or update it. But from what I've read .count() is every expensive in CPU usage.
Is there away to set the "name" property to be unique and the datastore will automatic update it or another better way to do this?
..fredrik
You can't make a property unique in the App Engine datastore. What you can do instead is to specify a key name for your model, which is guaranteed to be unique - see the docs for details.
I was having the same problem and came up with the following answer as the simplest one :
class Car(db.Model):
name = db.StringProperty(required=True)
def __init__(self,*args, **kwargs):
super(Car, self).__init__(*args, **kwargs)
loadingAnExistingCar = ("key" in kwargs.keys() or "key_name" in kwargs.keys())
if not loadingAnExistingCar:
self.__makeSureTheCarsNameIsUnique(kwargs['name'])
def __makeSureTheCarsNameIsUnique(self, name):
existingCarWithTheSameName = Car.GetByName(name)
if existingCarWithTheSameName:
raise UniqueConstraintValidationException("Car should be unique by name")
#staticmethod
def GetByName(name):
return Car.all().filter("name", name).get()
It's important to not that I first check if we are loading an existing entity first.
For the complete solution : http://nicholaslemay.blogspot.com/2010/07/app-engine-unique-constraint.html
You can just try to get your entity and edit it, and if not found create a new one:
template = Templates.gql('WHERE name = :1', name)
if template is None:
template = Templates()
# do your thing to set the entity's properties
template.put()
That way it will insert a new entry when it wasn't found, and if it was found it will update the existing entry with the changes you made (see documentation here).
An alternative solution is to create a model to store the unique values, and store it transationally using a combination of Model.property_name.value as key. Only if that value is created you save your actual model. This solution is described (with code) here:
http://squeeville.com/2009/01/30/add-a-unique-constraint-to-google-app-engine/
I agree with Nick. But, if you do ever want to check for model/entity existence based on a property, the get() method is handy:
template = Templates.all().filter('name = ', name).get()
if template is None:
# doesn't exist
else:
# exists
I wrote some code to do this. The idea for it is to be pretty easy to use. So you can do this:
if register_property_value('User', 'username', 'sexy_bbw_vixen'):
return 'Successfully registered sexy_bbw_vixen as your username!'
else:
return 'The username sexy_bbw_vixen is already in use.'
This is the code. There are a lot of comments, but its actually only a few lines:
# This entity type is a registry. It doesn't hold any data, but
# each entity is keyed to an Entity_type-Property_name-Property-value
# this allows for a transaction to 'register' a property value. It returns
# 'False' if the property value is already in use, and thus cannot be used
# again. Or 'True' if the property value was not in use and was successfully
# 'registered'
class M_Property_Value_Register(db.Expando):
pass
# This is the transaction. It returns 'False' if the value is already
# in use, or 'True' if the property value was successfully registered.
def _register_property_value_txn(in_key_name):
entity = M_Property_Value_Register.get_by_key_name(in_key_name)
if entity is not None:
return False
entity = M_Property_Value_Register(key_name=in_key_name)
entity.put()
return True
# This is the function that is called by your code, it constructs a key value
# from your Model-Property-Property-value trio and then runs a transaction
# that attempts to register the new property value. It returns 'True' if the
# value was successfully registered. Or 'False' if the value was already in use.
def register_property_value(model_name, property_name, property_value):
key_name = model_name + '_' + property_name + '_' + property_value
return db.run_in_transaction(_register_property_value_txn, key_name )

Resources