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.
Related
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.
I'd like to get the user id in the db automatically when the user fill a form, but I don't know what I have to do. I'm working on that for all day long, I tried to do it by an hidden input but I'm not it's the solution.
This is my code:
class ImputationForm(forms.Form):
user = forms.ModelChoiceField(queryset=User.objects.all(), widget=forms.HiddenInput())
morning = forms.BooleanField( required=False)
afternoon = forms.BooleanField(required=False)
workpackage = forms.ModelChoiceField(
queryset=Workpackage.objects.all()
)
class ImputationFormModel(ModelForm):
class Meta:
model = Imputation
def imputation(request):
if request.method == 'POST': # If the form has been submitted...
form = ImputationForm(request.POST) # A form bound to the POST data
f = ImputationFormModel(request.POST)
if form.is_valid(): # All validation rules pass
user = request.user.id
morning = form.cleaned_data['morning']
afternoon = form.cleaned_data['afternoon']
workpackage = form.cleaned_data['workpackage']
f.save()
return render_to_response( 'no/form.html', {'form' : form, }, context_instance=RequestContext(request))
else:
form = ImputationForm()
return render_to_response( 'no/form.html', {'form' : form, }, context_instance=RequestContext(request))
Great question. Let's first start by identifying the purpose of various lines of code of your application and see if we can make sense of it. I see that you've got two forms right now which seems to reference the same kinds of data, which is a bit redundant. We'll want to avoid redundancy where possible, so let's start by perhaps cleaning up the code.
We can tell by the code you've shared that you're trying to:
Allow a user to submit a form with data regarding "morning", "afternoon", and "workpackage".
We're not checking whether or not you're editing an instance, so we can assume that upon successful form submission, a new Imputation record is created in the database with the data passed into it.
You're asking for a hidden input of a user value on the client side, which binds the submitted user ID to the form for adding a relationship with the user to the newly saved Imputation record.
Generally, you never want to allow hidden variables regarding sensitive or personal data that can be associated with a user on the client side. It's considered a huge security risk. A user may edit the hidden field using Firebug (or other DOM editing tools) and bind the user ID to a different user than which is intended.
With that being said, let's have a gander at the code now:
class ImputationForm(ModelForm):
class Meta:
model = Imputation
fields = [
'morning',
'afternoon',
'workpackage'
]
# assumed fields of morning, afternoon, workpackage
def imputation(request):
if request.method == 'POST': # If the form has been submitted...
form = ImputationForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
form.user = request.user
form.save()
return render_to_response( 'no/form.html', {'form' : form, }, context_instance=RequestContext(request))
else:
form = ImputationForm()
return render_to_response( 'no/form.html', {'form' : form, }, context_instance=RequestContext(request))
With this code, we're no longer asking the user what his ID is on the form, and we're no longer even asking for it in the form altogether. We're allowing Django to determine what the current user ID is and pass it into the model right before saving the form. You'll want to make sure that the user field is set to null=True, blank=True on the model to avoid an exception from being raised.
I hope this helps answer your question.
I found it I'm really a newbie in django sorry for disturbing you and thank you very much for your help I would have not change my way of thinking on this issue without you!!!!! here is what I changed:
def imputation(request):
if request.method == 'POST': # If the form has been submitted...
form = ImputationForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
#print("%s" % form.user)
print("%s" % form)
new = form.save(commit=False) #here
new.user = request.user #here
new.save() #here
Thanks again ;)
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.
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.
django nonrel's documentation states: "you have to manually write code for merging the results of multiple queries (JOINs, select_related(), etc.)".
Can someone point me to any snippets that manually add the related data? #nickjohnson has an excellent post showing how to do this with the straight AppEngine models, but I'm using django-nonrel.
For my particular use I'm trying to get the UserProfiles with their related User models. This should be just two simple queries, then match the data.
However, using django-nonrel, a new query gets fired off for each result in the queryset. How can I get access to the related items in a 'select_related' sort of way?
I've tried this, but it doesn't seem to work as I'd expect. Looking at the rpc stats, it still seems to be firing a query for each item displayed.
all_profiles = UserProfile.objects.all()
user_pks = set()
for profile in all_profiles:
user_pks.add(profile.user_id) # a way to access the pk without triggering the query
users = User.objects.filter(pk__in=user_pks)
for profile in all_profiles:
profile.user = get_matching_model(profile.user_id, users)
def get_matching_model(key, queryset):
"""Generator expression to get the next match for a given key"""
try:
return (model for model in queryset if model.pk == key).next()
except StopIteration:
return None
UPDATE:
Ick... I figured out what my issue was.
I was trying to improve the efficiency of the changelist_view in the django admin. It seemed that the select_related logic above was still producing additional queries for each row in the results set when a foreign key was in my 'display_list'. However, I traced it down to something different. The above logic does not produce multiple queries (but if you more closely mimic Nick Johnson's way it will look a lot prettier).
The issue is that in django.contrib.admin.views.main on line 117 inside the ChangeList method there is the following code: result_list = self.query_set._clone(). So, even though I was properly overriding the queryset in the admin and selecting the related stuff, this method was triggering a clone of the queryset which does NOT keep the attributes on the model that I had added for my 'select related', resulting in an even more inefficient page load than when I started.
Not sure what to do about it yet, but the code that selects related stuff is just fine.
I don't like answering my own question, but the answer might help others.
Here is my solution that will get related items on a queryset based entirely on Nick Johnson's solution linked above.
from collections import defaultdict
def get_with_related(queryset, *attrs):
"""
Adds related attributes to a queryset in a more efficient way
than simply triggering the new query on access at runtime.
attrs must be valid either foreign keys or one to one fields on the queryset model
"""
# Makes a list of the entity and related attribute to grab for all possibilities
fields = [(model, attr) for model in queryset for attr in attrs]
# we'll need to make one query for each related attribute because
# I don't know how to get everything at once. So, we make a list
# of the attribute to fetch and pks to fetch.
ref_keys = defaultdict(list)
for model, attr in fields:
ref_keys[attr].append(get_value_for_datastore(model, attr))
# now make the actual queries for each attribute and store the results
# in a dict of {pk: model} for easy matching later
ref_models = {}
for attr, pk_vals in ref_keys.items():
related_queryset = queryset.model._meta.get_field(attr).rel.to.objects.filter(pk__in=set(pk_vals))
ref_models[attr] = dict((x.pk, x) for x in related_queryset)
# Finally put related items on their models
for model, attr in fields:
setattr(model, attr, ref_models[attr].get(get_value_for_datastore(model, attr)))
return queryset
def get_value_for_datastore(model, attr):
"""
Django's foreign key fields all have attributes 'field_id' where
you can access the pk of the related field without grabbing the
actual value.
"""
return getattr(model, attr + '_id')
To be able to modify the queryset on the admin to make use of the select related we have to jump through a couple hoops. Here is what I've done. The only thing changed on the 'get_results' method of the 'AppEngineRelatedChangeList' is that I removed the self.query_set._clone() and just used self.query_set instead.
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('username', 'user', 'paid')
select_related_fields = ['user']
def get_changelist(self, request, **kwargs):
return AppEngineRelatedChangeList
class AppEngineRelatedChangeList(ChangeList):
def get_query_set(self):
qs = super(AppEngineRelatedChangeList, self).get_query_set()
related_fields = getattr(self.model_admin, 'select_related_fields', [])
return get_with_related(qs, *related_fields)
def get_results(self, request):
paginator = self.model_admin.get_paginator(request, self.query_set, self.list_per_page)
# Get the number of objects, with admin filters applied.
result_count = paginator.count
# Get the total number of objects, with no admin filters applied.
# Perform a slight optimization: Check to see whether any filters were
# given. If not, use paginator.hits to calculate the number of objects,
# because we've already done paginator.hits and the value is cached.
if not self.query_set.query.where:
full_result_count = result_count
else:
full_result_count = self.root_query_set.count()
can_show_all = result_count self.list_per_page
# Get the list of objects to display on this page.
if (self.show_all and can_show_all) or not multi_page:
result_list = self.query_set
else:
try:
result_list = paginator.page(self.page_num+1).object_list
except InvalidPage:
raise IncorrectLookupParameters
self.result_count = result_count
self.full_result_count = full_result_count
self.result_list = result_list
self.can_show_all = can_show_all
self.multi_page = multi_page
self.paginator = paginator