-
Hello, As I'm using betahaus.emaillogin[1] in my application I'm wanting to override the generation of the username as this is a little confusing for users as they expect to login with their email address but we're still asking them to enter a username. I've tried to override this as follows in my member schema: # Overriding the id field as we're using betahaus.emaillogin atapi.ComputedField('id', expression = "context.setId()", index=('membrane_tool/ZCTextIndex,lexicon_id=member_lexicon,index_type=Cosine Measure|TextIndex:brains', 'FieldIndex:brains'), widget=atapi.ComputedWidget(label='User name'), regfield=0, user_property=True, ), and I've setup the setId method on the member content type as follows (the getName method is an accessor for a StringField where users can enter their fullname): def setId(self, id=''): "Overriding the username generator" import pdb;pdb.set_trace() if self.getName(): if not getattr(self, 'subscribememberid', None): names = self.getName().split() if len(names) > 1: firstname = names[0] surname = names[-1] id = firstname[:1]+surname else: id = names[0] catalog = getToolByName(self.context, 'portal_catalog') existing = catalog(id=id, portal_type='Subscribemember') if len(existing) != 0: try: i = int(id[-1]) id = id+str(i+1) except: id = id+'1' return self.setId(id) else: setattr(self, 'subscribememberid', True) else: return getattr(self, 'subscribememberid') As you can see I just grab the first initial of the firstname and the surname and then check that no user exists for that username. When I run my functional test, though, it doesn't create the user and doesn't even enter the setId method (as you can see I've got a pdb in there). Its supposed to return 'You have been registered as a member.' but instead I get 'Changes saved.' as a status message. Any tips on how I *should* be implementing this? Thanks, Tim- Thread Outline:
-
- Re: Overriding the id/username generati ... by ra
- Re: Overriding the id/username generati ... by hedley
-
Tim Knapp wrote: > Hello, > > As I'm using betahaus.emaillogin[1] in my application I'm wanting to > override the generation of the username as this is a little confusing > for users as they expect to login with their email address but we're > still asking them to enter a username. > > I've tried to override this as follows in my member schema: > > # Overriding the id field as we're using betahaus.emaillogin > atapi.ComputedField('id', > expression = "context.setId()", > > index=('membrane_tool/ZCTextIndex,lexicon_id=member_lexicon,index_type=Cosine Measure|TextIndex:brains', > 'FieldIndex:brains'), > widget=atapi.ComputedWidget(label='User name'), > regfield=0, > user_property=True, > ), it's worth noting that, even though the default member schema still has these 'index' settings littered throughout, the AT schema is no longer the correct place to be setting up indexing behaviour. in fact, i don't think it will have any impact. the GenericSetup profile is where this should be done. > and I've setup the setId method on the member content type as follows > (the getName method is an accessor for a StringField where users can > enter their fullname): > > def setId(self, id=''): > "Overriding the username generator" > import pdb;pdb.set_trace() > if self.getName(): > if not getattr(self, 'subscribememberid', None): > names = self.getName().split() > if len(names) > 1: > firstname = names[0] > surname = names[-1] > id = firstname[:1]+surname > else: > id = names[0] > catalog = getToolByName(self.context, 'portal_catalog') > existing = catalog(id=id, portal_type='Subscribemember') > if len(existing) != 0: > try: > i = int(id[-1]) > id = id+str(i+1) > except: > id = id+'1' > return self.setId(id) > else: > setattr(self, 'subscribememberid', True) > else: > return getattr(self, 'subscribememberid') like ross, i'm not entirely sure how what you're doing here is all supposed to fit together. it's a bit confusing, too, b/c you're using 'setId' as the computed field expression, when setId is the default name for the id field mutator. i don't know why your approach isn't working, but i'd personally be a bit reluctant to change id to a ComputedField. i'd probably continue to have id as a regular StringField, but i'd hide it from the edit forms and would take steps to make sure that it was always set to the value that you wanted, probably by overriding the 'update' method on the member object. i'd also maybe override the 'setId' mutator to do a bit of sanity checking, to reject any attempts to set the id if it doesn't match the expected value (be sure to make allowances for the auto-generated temporary ids, though). -r-
Hello again, On Mon, 2009-06-01 at 11:09 -0700, Rob Miller wrote: <snip /> > > like ross, i'm not entirely sure how what you're doing here is all supposed to > fit together. it's a bit confusing, too, b/c you're using 'setId' as the > computed field expression, when setId is the default name for the id field > mutator. > > i don't know why your approach isn't working, but i'd personally be a bit > reluctant to change id to a ComputedField. i'd probably continue to have id > as a regular StringField, but i'd hide it from the edit forms and would take > steps to make sure that it was always set to the value that you wanted, > probably by overriding the 'update' method on the member object. i'd also > maybe override the 'setId' mutator to do a bit of sanity checking, to reject > any attempts to set the id if it doesn't match the expected value (be sure to > make allowances for the auto-generated temporary ids, though). Ok, I've been trying to implement this today and have been hitting my head against a brick wall. It should be a simple exercise to override the 'update' method within my customised-Remember type but 'oh no' it doesn't wanna work. My current Remember-classes code is here[1]. As you can see I've put a pdb statement in there but it never gets called. As you can also see I've overridden the validate_id method as this was causing it to fail too as it was expecting a id to be entered into the form. Any tips please? -A very frustrated Plonista (Tim) [1] http://duffyd.pastebin.com/f1d41cb88 > > -r > > > -- > Archive: http://www.openplans.org/projects/remember/lists/remember/archive/2009/06/1243879831442 > To unsubscribe send an email with subject "unsubscribe" to remember@.... Please contact remember-manager@... for questions. >
-
-
> As I'm using betahaus.emaillogin[1] in my application I'm wanting to > override the generation of the username as this is a little confusing > for users as they expect to login with their email address but we're > still asking them to enter a username. Hi I haven't been paying attention to this thread but since Ross asked others for help I'll try. Assuming you simply want to let users log in with their email addresses I can help. I do that exact thing with remember in a site, and I'll provide code. The key to everything is the correct use of getUserName instead of getId. There are a few methods in membrane and remember that need to be monkey patched. The monkey patching is *not* a hack - there are errors in these methods but I haven't had the time to jump in and fix them. Sorry Ross! If you subclass from member.py then you must override getUserName as such: def getUserName(self): """ Override """ return self.getEmail() If you do not have your own class then you'll have to monkey member.py (I don't think that method is ZCA aware yet). I attach the patches. Individual methods have comments explaining what has been done. Roche Compaan had a big hand in this. NB: 1. You must update your membrane_tool catalog once you decide that users are going to use email address as login 2. You must of course enforce unique email addresses in a validator. 3. Leave the username field on all forms since that is going to be the id of the member object. Change the help text for the username to indicate that it is a nickname. Hope this helps Hedley-
Hi Hedley, Thanks a lot for responding to my message. On Thu, 2009-06-04 at 10:32 +0200, Hedley Roos wrote: > > As I'm using betahaus.emaillogin[1] in my application I'm wanting to > > override the generation of the username as this is a little confusing > > for users as they expect to login with their email address but we're > > still asking them to enter a username. > > Hi > > I haven't been paying attention to this thread but since Ross asked > others for help I'll try. > > Assuming you simply want to let users log in with their email addresses > I can help. I do that exact thing with remember in a site, and I'll > provide code. The key to everything is the correct use of getUserName > instead of getId. There are a few methods in membrane and remember that > need to be monkey patched. The monkey patching is *not* a hack - there > are errors in these methods but I haven't had the time to jump in and > fix them. Sorry Ross! > > If you subclass from member.py then you must override getUserName as such: > > def getUserName(self): > """ > Override > """ > return self.getEmail() > > If you do not have your own class then you'll have to monkey member.py > (I don't think that method is ZCA aware yet). > > I attach the patches. Individual methods have comments explaining what > has been done. Roche Compaan had a big hand in this. > > NB: > 1. You must update your membrane_tool catalog once you decide that users > are going to use email address as login > 2. You must of course enforce unique email addresses in a validator. > 3. Leave the username field on all forms since that is going to be the > id of the member object. Change the help text for the username to > indicate that it is a nickname. betahaus.emaillogin works just fine with remember so I don't have a need to change the remember code in any way to allow users to login with their email address. My main need is to be able to remove the id field from the reg_form and instead autogenerate this as it is confusing users who are wondering why they need to enter a username as they will be logging on with their email address. Your suggestion re. changing the help text is a novel one and I'll see if I can sell this to the client, though. Thanks again, Tim > > Hope this helps > Hedley > > > > plain text document attachment (patch.py) > from Products.PlonePAS.tools.memberdata import MemberDataTool \ > as BaseTool > from Products.CMFCore.utils import getToolByName > from Products.membrane.tools.membrane import MembraneTool > from Products.membrane.plugins.usermanager import MembraneUserManager > from Products.remember.content.member import BaseMember > from Products.remember.tools.memberdata import MemberDataContainer > from Products.remember.config import ALLOWED_MEMBER_ID_PATTERN > > # Make call to getUserName instead of getId > def wrapUser(self, user): > """ > If possible, returns the Member object that corresponds to the > given User object. > """ > mbtool = getToolByName(self, 'membrane_tool') > mem = mbtool.getUserAuthProvider(user.getUserName()) > if mem is None: > return BaseTool.wrapUser(self, user) > return mem.__of__(self).__of__(user) > > # Make call to getId instead of getUserName > def register(self): > """ > perform any registration information necessary after a member is registered > """ > rtool = getToolByName(self, 'portal_registration') > site_props = getToolByName(self, 'portal_properties').site_properties > > # XXX unicode names break sending the email > unicode_name = self.getFullname() > self.setFullname(str(unicode_name)) > if site_props.validate_email or self.getMail_me(): > rtool.registeredNotify(self.getId()) > > self.setFullname(unicode_name) > > # Patch validate_id to getMemberById instead of mbtool.getUserAuthProvider > # since the latter does lookups on login, not id > def validate_id(self, id): > # we can't always trust the id argument, b/c the autogen'd > # id will be passed in if the reg form id field is blank > form = self.REQUEST.form > if form.has_key('id') and not form['id']: > return self.translate('Input is required but no input given.', > default='You did not enter a login name.'), > elif self.id and id != self.id: > # we only validate if we're changing the id > mtool = getToolByName(self, 'portal_membership') > if mtool.getMemberById(id) is not None or \ > not ALLOWED_MEMBER_ID_PATTERN.match(id) or \ > id == 'Anonymous User': > msg = "The login name you selected is already " + \ > "in use or is not valid. Please choose another." > return self.translate(msg, default=msg) > > > MemberDataContainer.wrapUser = wrapUser > BaseMember.register = register > BaseMember.validate_id = validate_id-
> betahaus.emaillogin works just fine with remember so I don't have a need > to change the remember code in any way to allow users to login with > their email address. My main need is to be able to remove the id field > from the reg_form and instead autogenerate this as it is confusing users > who are wondering why they need to enter a username as they will be > logging on with their email address. > > Your suggestion re. changing the help text is a novel one and I'll see > if I can sell this to the client, though. Hi Tim If you autogenerate an id I assume it is going to be computed from the email address. If that is the case then it means that users' email addresses can easily be retrieved from an URL, which is breach of privacy. That warning is usually enough to convince the client :) Hedley
-
On Thu, 2009-06-04 at 12:13 +0200, Hedley Roos wrote: > > betahaus.emaillogin works just fine with remember so I don't have a need > > to change the remember code in any way to allow users to login with > > their email address. My main need is to be able to remove the id field > > from the reg_form and instead autogenerate this as it is confusing users > > who are wondering why they need to enter a username as they will be > > logging on with their email address. > > > > Your suggestion re. changing the help text is a novel one and I'll see > > if I can sell this to the client, though. > > Hi Tim > > If you autogenerate an id I assume it is going to be computed from the > email address. If that is the case then it means that users' email > addresses can easily be retrieved from an URL, which is breach of > privacy. That warning is usually enough to convince the client :) Good thinking 99 ;) but unfortunately the client has asked me to autogenerate the usernames based on a sequential 4 digit number (betahaus.emaillogin just authenticates directly against the member email addresses) so can't use that argument unfortunately :( -Tim > > Hedley > > > -- > Archive: http://www.openplans.org/projects/remember/lists/remember/archive/2009/06/1244110430264 > To unsubscribe send an email with subject "unsubscribe" to remember@.... Please contact remember-manager@... for questions. >
-
> Good thinking 99 ;) > but unfortunately the client has asked me to autogenerate the usernames > based on a sequential 4 digit number (betahaus.emaillogin just > authenticates directly against the member email addresses) so can't use > that argument unfortunately :( > For your case I'll use a formlib add form. You don't have to write any UI! Roche came up with this neat approach. For this example the content type class is called AccountingFolder but you'll see the pattern. ZCML: <browser:page name="addAccountingFolder" <<< name must match the factory for="*" class=".adding.AccountingFolderAddForm" permission="zope.View" /> Class: class AccountingFolderAddForm(formbase.PageAddForm): """ """ form_fields = form.FormFields(IAccountingFolder) def create(self, data): title = data['title'] id = <your algorithm here> self.contentName = id folder = AccountingFolder(id, **data) folder.setTitle(title) return folder def nextURL(self): obj = self.context.get(self.contentName) return "%s/edit" % obj.absolute_url() I haven't used this myself but I've seen it in action and it is pretty damn cool :) BTW you'll need to modify the member schema. So you can use schemaextender or monkey it, but you'll need this: schema['id'].regfield = 0 schema['id'].display_autogenerated = 1 Hedley-
On Thu, 2009-06-04 at 12:33 +0200, Hedley Roos wrote: > > Good thinking 99 ;) > > but unfortunately the client has asked me to autogenerate the usernames > > based on a sequential 4 digit number (betahaus.emaillogin just > > authenticates directly against the member email addresses) so can't use > > that argument unfortunately :( > > > > For your case I'll use a formlib add form. You don't have to write any > UI! Roche came up with this neat approach. > > For this example the content type class is called AccountingFolder but > you'll see the pattern. > > ZCML: > > <browser:page > name="addAccountingFolder" <<< name must match the factory > for="*" > class=".adding.AccountingFolderAddForm" > permission="zope.View" > /> > > > Class: > > class AccountingFolderAddForm(formbase.PageAddForm): > """ > """ > > form_fields = form.FormFields(IAccountingFolder) > > def create(self, data): > title = data['title'] > id = <your algorithm here> > self.contentName = id > > folder = AccountingFolder(id, **data) > folder.setTitle(title) > return folder > > def nextURL(self): > obj = self.context.get(self.contentName) > return "%s/edit" % obj.absolute_url() > > I haven't used this myself but I've seen it in action and it is pretty > damn cool :) > > BTW you'll need to modify the member schema. So you can use > schemaextender or monkey it, but you'll need this: > > schema['id'].regfield = 0 > schema['id'].display_autogenerated = 1 Thank you very much! This looks very promising. I'll give it a go today and report back. Thanks again, Tim > > Hedley > > > -- > Archive: http://www.openplans.org/projects/remember/lists/remember/archive/2009/06/1244111662728 > To unsubscribe send an email with subject "unsubscribe" to remember@.... Please contact remember-manager@... for questions. >
-
Hello again, I've implemented these changes as per your suggestion below and put a pdb statement in the create method of the PageAddForm class but it doesn't even get there and just fails. Are you sure you implemented this for a remember-based class? Thanks again, Tim On Thu, 2009-06-04 at 12:33 +0200, Hedley Roos wrote: > > Good thinking 99 ;) > > but unfortunately the client has asked me to autogenerate the usernames > > based on a sequential 4 digit number (betahaus.emaillogin just > > authenticates directly against the member email addresses) so can't use > > that argument unfortunately :( > > > > For your case I'll use a formlib add form. You don't have to write any > UI! Roche came up with this neat approach. > > For this example the content type class is called AccountingFolder but > you'll see the pattern. > > ZCML: > > <browser:page > name="addAccountingFolder" <<< name must match the factory > for="*" > class=".adding.AccountingFolderAddForm" > permission="zope.View" > /> > > > Class: > > class AccountingFolderAddForm(formbase.PageAddForm): > """ > """ > > form_fields = form.FormFields(IAccountingFolder) > > def create(self, data): > title = data['title'] > id = <your algorithm here> > self.contentName = id > > folder = AccountingFolder(id, **data) > folder.setTitle(title) > return folder > > def nextURL(self): > obj = self.context.get(self.contentName) > return "%s/edit" % obj.absolute_url() > > I haven't used this myself but I've seen it in action and it is pretty > damn cool :) > > BTW you'll need to modify the member schema. So you can use > schemaextender or monkey it, but you'll need this: > > schema['id'].regfield = 0 > schema['id'].display_autogenerated = 1 > > Hedley > > > -- > Archive: http://www.openplans.org/projects/remember/lists/remember/archive/2009/06/1244111662728 > To unsubscribe send an email with subject "unsubscribe" to remember@.... Please contact remember-manager@... for questions. >
-
Tim Knapp wrote: > Hello again, > > I've implemented these changes as per your suggestion below and put a > pdb statement in the create method of the PageAddForm class but it > doesn't even get there and just fails. Are you sure you implemented this > for a remember-based class? > Hi Tim It was not implemented for a remember based class, but since member is basically a BaseObject subclass I assume it should work. Then again, assumption is the mother of all... you know what. I'll try it myself a bit later if I have time. Hedley
-
On Fri, 2009-06-05 at 09:32 +0200, Hedley Roos wrote: > Tim Knapp wrote: > > Hello again, > > > > I've implemented these changes as per your suggestion below and put a > > pdb statement in the create method of the PageAddForm class but it > > doesn't even get there and just fails. Are you sure you implemented this > > for a remember-based class? > > > > Hi Tim > > It was not implemented for a remember based class, but since member is > basically a BaseObject subclass I assume it should work. Then again, > assumption is the mother of all... you know what. > > I'll try it myself a bit later if I have time. Well after some more hunting through the code it appears that the creation of the remember objects is invoked via the createMember.cpy script, which creates a member object via portal_factory in the portal_memberdata tool, and returns reg_form.cpt (hence circumventing the browser view), and then eventually calls another script (do_register.cpy), which does: <code> new_context = getToolByName(context, 'portal_factory').doCreate(context, id) new_context.processForm() </code> so IOW it completely bypasses the formlib PageAddForm we created. Thanks, Tim > > Hedley > > > -- > Archive: http://www.openplans.org/projects/remember/lists/remember/archive/2009/06/1244187196967 > To unsubscribe send an email with subject "unsubscribe" to remember@.... Please contact remember-manager@... for questions. >
-
On Fri, 2009-06-05 at 20:14 +1200, Tim Knapp wrote: <snip /> > Well after some more hunting through the code it appears that the > creation of the remember objects is invoked via the createMember.cpy > script, which creates a member object via portal_factory in the > portal_memberdata tool, and returns reg_form.cpt (hence circumventing > the browser view), and then eventually calls another script > (do_register.cpy), which does: > > <code> > new_context = getToolByName(context, 'portal_factory').doCreate(context, > id) > new_context.processForm() > </code> > > so IOW it completely bypasses the formlib PageAddForm we created. After some 'more' hacking, I have got the portal_factory to create the object using my auto-generated id by doing the following: 1. Removing 'validate_registration' from reg_form.cpt.metadata (validators) - I think the better way would possibly be to ensure validate_id method in the member class is returning None (still need to confirm this). 2. Modifying do_register.cpy as follows[1]. Unfortunately it still errors as follows[2]. I can confirm that it has definitely renamed the object but it appears to be trying to traverse to the old id. Any ideas on how to get around this? Thanks again, Tim [1] http://duffyd.pastebin.com/f63609c7e [2] http://duffyd.pastebin.com/f540d2840 > > Thanks, > Tim > > > > > Hedley > > > > > > -- > > Archive: http://www.openplans.org/projects/remember/lists/remember/archive/2009/06/1244187196967 > > To unsubscribe send an email with subject "unsubscribe" to remember@.... Please contact remember-manager@... for questions. > > > > > > -- > Archive: http://www.openplans.org/projects/remember/lists/remember/archive/2009/06/1244189723212 > To unsubscribe send an email with subject "unsubscribe" to remember@.... Please contact remember-manager@... for questions. >
-
-
-
-
-
-
-
-
patch.py (text/plain) 2.5 kB