diff --git a/NEWS b/NEWS index 53605bd..8fd6bbb 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,8 @@ -1.0.4: November ??, 2007 +1.0.4: December ??, 2007 * When the web browser is resized, all notes are automatically resized as well. * Fixed note focusing in Safari. * Fixed note state detection (bold, italic, etc.) in Safari. + * Improved input validation. 1.0.3: November 28, 2007 * Updated logo, which is now an image and could be theoretically replaced for diff --git a/controller/Users.py b/controller/Users.py index e08ecfb..e68cc3b 100644 --- a/controller/Users.py +++ b/controller/Users.py @@ -717,7 +717,14 @@ class Users( object ): # record the sending of this invite email invite_id = self.__database.next_id( Invite, commit = False ) invite = Invite.create( invite_id, user_id, notebook_id, email_address, read_write, owner ) - self.__database.save( invite ) + self.__database.save( invite, commit = False ) + + # update any unredeemed invitations for this notebook already sent to the same email address + similar_invites = self.__database.select_many( Invite, invite.sql_load_similar() ) + for similar in similar_invites: + similar.read_write = read_write + similar.owner = owner + self.__database.save( similar, commit = False ) # create an email message with a unique invitation link notebook_name = notebook.name.strip().replace( "\n", " " ).replace( "\r", " " ) @@ -739,6 +746,8 @@ class Users( object ): server.sendmail( message[ u"from" ], [ email_address ], message.as_string() ) server.quit() + self.__database.commit() + if email_count == 1: return dict( message = u"An invitation has been sent." ) else: diff --git a/controller/test/Test_controller.py b/controller/test/Test_controller.py index 89a689c..b34946e 100644 --- a/controller/test/Test_controller.py +++ b/controller/test/Test_controller.py @@ -12,6 +12,7 @@ class Test_controller( object ): from model.User import User from model.Notebook import Notebook from model.Note import Note + from model.Invite import Invite # Since Stub_database isn't a real database and doesn't know SQL, replace some of the # SQL-returning methods in User, Note, and Notebook to return functions that manipulate data in @@ -214,6 +215,21 @@ class Test_controller( object ): Notebook.sql_count_notes = lambda self: \ lambda database: sql_count_notes( self, database ) + def sql_load_similar( self, database ): + invites = [] + + for ( object_id, obj_list ) in database.objects.items(): + obj = obj_list[ -1 ] + if isinstance( obj, Invite ) and obj.from_user_id == self.from_user_id and \ + obj.notebook_id == self.notebook_id and obj.email_address == self.email_address and \ + obj.redeemed_user_id is None and obj.object_id != self.object_id: + invites.append( obj ) + + return invites + + Invite.sql_load_similar = lambda self: \ + lambda database: sql_load_similar( self, database ) + def setUp( self ): from controller.Root import Root cherrypy.lowercase_api = True diff --git a/controller/test/Test_users.py b/controller/test/Test_users.py index 833b637..41a3ced 100644 --- a/controller/test/Test_users.py +++ b/controller/test/Test_users.py @@ -853,6 +853,60 @@ class Test_users( Test_controller ): assert self.notebooks[ 0 ].name in message assert self.INVITE_LINK_PATTERN.search( message ) + def test_send_invites_similar( self ): + Stub_smtp.reset() + smtplib.SMTP = Stub_smtp + self.login() + + self.user.rate_plan = 1 + self.database.save( self.user ) + + email_addresses_list = [ u"foo@example.com" ] + email_addresses = email_addresses_list[ 0 ] + + # first send an invite with read_write and owner set to False + self.http_post( "/users/send_invites", dict( + notebook_id = self.notebooks[ 0 ].object_id, + email_addresses = email_addresses, + read_write = False, + owner = False, + invite_button = u"send invites", + ), session_id = self.session_id ) + + matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message ) + invite_id1 = matches.group( 2 ) + assert invite_id1 + + # then send a similar invite to the same email address with read_write and owner set to True + self.http_post( "/users/send_invites", dict( + notebook_id = self.notebooks[ 0 ].object_id, + email_addresses = email_addresses, + read_write = True, + owner = True, + invite_button = u"send invites", + ), session_id = self.session_id ) + + matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message ) + invite_id2 = matches.group( 2 ) + assert invite_id2 + + # assert that both invites have the read_write / owner flags set to True now + invite1_list = self.database.objects.get( invite_id1 ) + assert invite1_list + assert len( invite1_list ) >= 2 + invite1 = invite1_list[ -1 ] + assert invite1 + assert invite1.read_write is True + assert invite1.owner is True + + invite2_list = self.database.objects.get( invite_id2 ) + assert invite2_list + assert len( invite2_list ) >= 1 + invite2 = invite2_list[ -1 ] + assert invite2 + assert invite2.read_write is True + assert invite2.owner is True + def test_send_invites_with_generic_from_address( self ): Stub_smtp.reset() smtplib.SMTP = Stub_smtp diff --git a/model/Invite.py b/model/Invite.py index 26a1604..dd4d86f 100644 --- a/model/Invite.py +++ b/model/Invite.py @@ -90,6 +90,22 @@ class Invite( Persistent ): quote( self.__email_address ), quote( self.__read_write and "t" or "f" ), quote( self.__owner and "t" or "f" ), quote( self.__redeemed_user_id ) ) + def sql_load_similar( self ): + # select unredeemed invitations with the same from_user_id, notebook_id, and email_address as this invitation + return "select id, revision, from_user_id, notebook_id, email_address, read_write, owner, redeemed_user_id from invite " + \ + "where from_user_id = %s and notebook_id = %s and email_address = %s and id != %s and redeemed_user_id is null;" % \ + ( quote( self.__from_user_id ), quote( self.__notebook_id ), quote( self.__email_address ), quote( self.object_id ) ) + + def __set_read_write( self, read_write ): + if read_write != self.__read_write: + self.update_revision() + self.__read_write = read_write + + def __set_owner( self, owner ): + if owner != self.__owner: + self.update_revision() + self.__owner = owner + def __set_redeemed_user_id( self, redeemed_user_id ): if redeemed_user_id != self.__redeemed_user_id: self.update_revision() @@ -98,6 +114,6 @@ class Invite( Persistent ): from_user_id = property( lambda self: self.__from_user_id ) notebook_id = property( lambda self: self.__notebook_id ) email_address = property( lambda self: self.__email_address ) - read_write = property( lambda self: self.__read_write ) - owner = property( lambda self: self.__owner ) + read_write = property( lambda self: self.__read_write, __set_read_write ) + owner = property( lambda self: self.__owner, __set_owner ) redeemed_user_id = property( lambda self: self.__redeemed_user_id, __set_redeemed_user_id )