* Users.signup(), Users.login(), and Root.default() now support optional invite_id parameter.
* Modified Wiki.js to include invite_id parameter when necessary. * Increased storage quota (and price) for premium rate plan. * Added a note displayed when redeeming an invite, with links to signup and login.
This commit is contained in:
parent
8372b03373
commit
f00809955c
|
@ -41,7 +41,7 @@ settings = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "premium",
|
"name": "premium",
|
||||||
"storage_quota_bytes": 1000 * MEGABYTE,
|
"storage_quota_bytes": 2000 * MEGABYTE,
|
||||||
"notebook_collaboration": True,
|
"notebook_collaboration": True,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -45,10 +45,18 @@ class Root( object ):
|
||||||
@expose( Main_page )
|
@expose( Main_page )
|
||||||
@validate(
|
@validate(
|
||||||
note_title = unicode,
|
note_title = unicode,
|
||||||
|
invite_id = Valid_id( none_okay = True ),
|
||||||
)
|
)
|
||||||
def default( self, note_title ):
|
def default( self, note_title, invite_id = None ):
|
||||||
"""
|
"""
|
||||||
Convenience method for accessing a note in the main notebook by name rather than by note id.
|
Convenience method for accessing a note in the main notebook by name rather than by note id.
|
||||||
|
|
||||||
|
@type note_title: unicode
|
||||||
|
@param note_title: title of the note to return
|
||||||
|
@type invite_id: unicode
|
||||||
|
@param invite_id: id of the invite used to get to this note (optional)
|
||||||
|
@rtype: unicode
|
||||||
|
@return: rendered HTML page
|
||||||
"""
|
"""
|
||||||
# if the user is logged in and not using https, and they request the sign up or login note, then
|
# if the user is logged in and not using https, and they request the sign up or login note, then
|
||||||
# redirect to the https version of the page (if available)
|
# redirect to the https version of the page (if available)
|
||||||
|
@ -56,7 +64,10 @@ class Root( object ):
|
||||||
https_proxy_ip = self.__settings[ u"global" ].get( u"luminotes.https_proxy_ip" )
|
https_proxy_ip = self.__settings[ u"global" ].get( u"luminotes.https_proxy_ip" )
|
||||||
|
|
||||||
if note_title in ( u"sign_up", u"login" ) and https_url and cherrypy.request.remote_addr != https_proxy_ip:
|
if note_title in ( u"sign_up", u"login" ) and https_url and cherrypy.request.remote_addr != https_proxy_ip:
|
||||||
return dict( redirect = u"%s/%s" % ( https_url, note_title ) )
|
if invite_id:
|
||||||
|
return dict( redirect = u"%s/%s?invite_id=%s" % ( https_url, note_title, invite_id ) )
|
||||||
|
else:
|
||||||
|
return dict( redirect = u"%s/%s" % ( https_url, note_title ) )
|
||||||
|
|
||||||
result = self.__users.current( user_id = None )
|
result = self.__users.current( user_id = None )
|
||||||
first_notebook = result[ u"notebooks" ][ 0 ]
|
first_notebook = result[ u"notebooks" ][ 0 ]
|
||||||
|
@ -68,6 +79,8 @@ class Root( object ):
|
||||||
raise cherrypy.NotFound
|
raise cherrypy.NotFound
|
||||||
|
|
||||||
result.update( self.__notebooks.contents( first_notebook.object_id, user_id = user_id, note_id = note.object_id ) )
|
result.update( self.__notebooks.contents( first_notebook.object_id, user_id = user_id, note_id = note.object_id ) )
|
||||||
|
if invite_id:
|
||||||
|
result[ "invite_id" ] = invite_id
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ from Expire import strongly_expire
|
||||||
from view.Json import Json
|
from view.Json import Json
|
||||||
from view.Main_page import Main_page
|
from view.Main_page import Main_page
|
||||||
from view.Redeem_reset_note import Redeem_reset_note
|
from view.Redeem_reset_note import Redeem_reset_note
|
||||||
|
from view.Redeem_invite_note import Redeem_invite_note
|
||||||
|
|
||||||
|
|
||||||
USERNAME_PATTERN = re.compile( "^[a-zA-Z0-9]+$" )
|
USERNAME_PATTERN = re.compile( "^[a-zA-Z0-9]+$" )
|
||||||
|
@ -180,8 +181,9 @@ class Users( object ):
|
||||||
password_repeat = Valid_string( min = 1, max = 30 ),
|
password_repeat = Valid_string( min = 1, max = 30 ),
|
||||||
email_address = ( Valid_string( min = 0, max = 60 ) ),
|
email_address = ( Valid_string( min = 0, max = 60 ) ),
|
||||||
signup_button = unicode,
|
signup_button = unicode,
|
||||||
|
invite_id = Valid_id( none_okay = True ),
|
||||||
)
|
)
|
||||||
def signup( self, username, password, password_repeat, email_address, signup_button ):
|
def signup( self, username, password, password_repeat, email_address, signup_button, invite_id = None ):
|
||||||
"""
|
"""
|
||||||
Create a new User based on the given information. Start that user with their own Notebook and a
|
Create a new User based on the given information. Start that user with their own Notebook and a
|
||||||
"welcome to your wiki" Note. For convenience, login the newly created user as well.
|
"welcome to your wiki" Note. For convenience, login the newly created user as well.
|
||||||
|
@ -196,6 +198,8 @@ class Users( object ):
|
||||||
@param email_address: user's email address
|
@param email_address: user's email address
|
||||||
@type signup_button: unicode
|
@type signup_button: unicode
|
||||||
@param signup_button: ignored
|
@param signup_button: ignored
|
||||||
|
@type invite_id: unicode
|
||||||
|
@param invite_id: id of invite to redeem upon signup (optional)
|
||||||
@rtype: json dict
|
@rtype: json dict
|
||||||
@return: { 'redirect': url, 'authenticated': userdict }
|
@return: { 'redirect': url, 'authenticated': userdict }
|
||||||
@raise Signup_error: passwords don't match or the username is unavailable
|
@raise Signup_error: passwords don't match or the username is unavailable
|
||||||
|
@ -240,7 +244,17 @@ class Users( object ):
|
||||||
self.__database.execute( user.sql_save_notebook( trash_id, read_write = True, owner = True ), commit = False )
|
self.__database.execute( user.sql_save_notebook( trash_id, read_write = True, owner = True ), commit = False )
|
||||||
self.__database.commit()
|
self.__database.commit()
|
||||||
|
|
||||||
redirect = u"/notebooks/%s" % notebook.object_id
|
# if there's an invite_id, then redeem that invite and redirect to the invite's notebook
|
||||||
|
if invite_id:
|
||||||
|
invite = self.__database.load( Invite, invite_id )
|
||||||
|
if not invite:
|
||||||
|
raise Signup_error( u"The invite is unknown." )
|
||||||
|
|
||||||
|
self.convert_invite_to_access( invite, user_id )
|
||||||
|
redirect = u"/notebooks/%s" % invite.notebook_id
|
||||||
|
# otherwise, just redirect to the newly created notebook
|
||||||
|
else:
|
||||||
|
redirect = u"/notebooks/%s" % notebook.object_id
|
||||||
|
|
||||||
return dict(
|
return dict(
|
||||||
redirect = redirect,
|
redirect = redirect,
|
||||||
|
@ -316,8 +330,9 @@ class Users( object ):
|
||||||
username = ( Valid_string( min = 1, max = 30 ), valid_username ),
|
username = ( Valid_string( min = 1, max = 30 ), valid_username ),
|
||||||
password = Valid_string( min = 1, max = 30 ),
|
password = Valid_string( min = 1, max = 30 ),
|
||||||
login_button = unicode,
|
login_button = unicode,
|
||||||
|
invite_id = Valid_id( none_okay = True ),
|
||||||
)
|
)
|
||||||
def login( self, username, password, login_button ):
|
def login( self, username, password, login_button, invite_id = None ):
|
||||||
"""
|
"""
|
||||||
Attempt to authenticate the user. If successful, associate the given user with the current
|
Attempt to authenticate the user. If successful, associate the given user with the current
|
||||||
session.
|
session.
|
||||||
|
@ -326,6 +341,8 @@ class Users( object ):
|
||||||
@param username: username to login
|
@param username: username to login
|
||||||
@type password: unicode
|
@type password: unicode
|
||||||
@param password: the user's password
|
@param password: the user's password
|
||||||
|
@type invite_id: unicode
|
||||||
|
@param invite_id: id of invite to redeem upon login (optional)
|
||||||
@rtype: json dict
|
@rtype: json dict
|
||||||
@return: { 'redirect': url, 'authenticated': userdict }
|
@return: { 'redirect': url, 'authenticated': userdict }
|
||||||
@raise Authentication_error: invalid username or password
|
@raise Authentication_error: invalid username or password
|
||||||
|
@ -338,8 +355,16 @@ class Users( object ):
|
||||||
|
|
||||||
first_notebook = self.__database.select_one( Notebook, user.sql_load_notebooks( parents_only = True, undeleted_only = True ) )
|
first_notebook = self.__database.select_one( Notebook, user.sql_load_notebooks( parents_only = True, undeleted_only = True ) )
|
||||||
|
|
||||||
# redirect to the user's first notebook (if any)
|
# if there's an invite_id, then redeem that invite and redirect to the invite's notebook
|
||||||
if first_notebook:
|
if invite_id:
|
||||||
|
invite = self.__database.load( Invite, invite_id )
|
||||||
|
if not invite:
|
||||||
|
raise Authentication_error( u"The invite is unknown." )
|
||||||
|
|
||||||
|
self.convert_invite_to_access( invite, user.object_id )
|
||||||
|
redirect = u"/notebooks/%s" % invite.notebook_id
|
||||||
|
# otherwise, just redirect to the user's first notebook (if any)
|
||||||
|
elif first_notebook:
|
||||||
redirect = u"/notebooks/%s" % first_notebook.object_id
|
redirect = u"/notebooks/%s" % first_notebook.object_id
|
||||||
else:
|
else:
|
||||||
redirect = u"/"
|
redirect = u"/"
|
||||||
|
@ -741,7 +766,7 @@ class Users( object ):
|
||||||
# if the invite is already redeemed, then update the relevant entry in the user_notebook
|
# if the invite is already redeemed, then update the relevant entry in the user_notebook
|
||||||
# access table as well
|
# access table as well
|
||||||
if similar.redeemed_user_id is not None:
|
if similar.redeemed_user_id is not None:
|
||||||
redeemed_user = self.__database.load( User, redeemed_user_id )
|
redeemed_user = self.__database.load( User, similar.redeemed_user_id )
|
||||||
if redeemed_user:
|
if redeemed_user:
|
||||||
self.__database.execute( redeemed_user.sql_update_access( notebook_id, read_write, owner ) )
|
self.__database.execute( redeemed_user.sql_update_access( notebook_id, read_write, owner ) )
|
||||||
|
|
||||||
|
@ -833,8 +858,8 @@ class Users( object ):
|
||||||
@param invite_id: id of invite to redeem
|
@param invite_id: id of invite to redeem
|
||||||
@type user_id: unicode
|
@type user_id: unicode
|
||||||
@param user_id: id of current logged-in user (if any), determined by @grab_user_id
|
@param user_id: id of current logged-in user (if any), determined by @grab_user_id
|
||||||
@rtype:
|
@rtype: unicode
|
||||||
@return:
|
@return: rendered HTML page
|
||||||
@raise Validation_error: one of the arguments is invalid
|
@raise Validation_error: one of the arguments is invalid
|
||||||
@raise Invite_error: an error occured when redeeming the invite
|
@raise Invite_error: an error occured when redeeming the invite
|
||||||
"""
|
"""
|
||||||
|
@ -857,7 +882,32 @@ class Users( object ):
|
||||||
if invite.redeemed_user_id:
|
if invite.redeemed_user_id:
|
||||||
raise Invite_error( u"That invite has already been used. If you were the one who used it, then simply <a href=\"/login\">login</a> to your account." )
|
raise Invite_error( u"That invite has already been used. If you were the one who used it, then simply <a href=\"/login\">login</a> to your account." )
|
||||||
|
|
||||||
# TODO: give the user the option to sign up or login in order to redeem the invite
|
notebook = self.__database.load( Notebook, invite.notebook_id )
|
||||||
|
if not notebook:
|
||||||
|
raise Invite_error( "That notebook you've been invited to is unknown." )
|
||||||
|
|
||||||
|
anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ) )
|
||||||
|
if anonymous:
|
||||||
|
main_notebook = self.__database.select_one( Notebook, anonymous.sql_load_notebooks( undeleted_only = True ) )
|
||||||
|
invite_notebook = self.__database.load( Notebook, invite.notebook_id )
|
||||||
|
|
||||||
|
if not anonymous or not main_notebook or not invite_notebook:
|
||||||
|
raise Password_reset_error( "There was an error when redeeming your invite. Please contact %s." % self.__support_email )
|
||||||
|
|
||||||
|
# give the user the option to sign up or login in order to redeem the invite
|
||||||
|
result = self.current( anonymous.object_id )
|
||||||
|
result[ "notebook" ] = main_notebook
|
||||||
|
result[ "startup_notes" ] = self.__database.select_many( Note, main_notebook.sql_load_startup_notes() )
|
||||||
|
result[ "total_notes_count" ] = self.__database.select_one( Note, main_notebook.sql_count_notes() )
|
||||||
|
result[ "note_read_write" ] = False
|
||||||
|
result[ "notes" ] = [ Note.create(
|
||||||
|
object_id = u"redeem_invite",
|
||||||
|
contents = unicode( Redeem_invite_note( invite, invite_notebook ) ),
|
||||||
|
notebook_id = main_notebook.object_id,
|
||||||
|
) ]
|
||||||
|
result[ "invites" ] = []
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def convert_invite_to_access( self, invite, user_id ):
|
def convert_invite_to_access( self, invite, user_id ):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
from model.User import User
|
||||||
|
|
||||||
|
|
||||||
class Stub_database( object ):
|
class Stub_database( object ):
|
||||||
|
@ -7,10 +8,13 @@ class Stub_database( object ):
|
||||||
self.objects = {}
|
self.objects = {}
|
||||||
self.user_notebook = {} # map of user_id to ( notebook_id, read_write, owner )
|
self.user_notebook = {} # map of user_id to ( notebook_id, read_write, owner )
|
||||||
self.last_saved_obj = None
|
self.last_saved_obj = None
|
||||||
|
self.last_saved_user = None
|
||||||
self.__next_id = 0
|
self.__next_id = 0
|
||||||
|
|
||||||
def save( self, obj, commit = False ):
|
def save( self, obj, commit = False ):
|
||||||
self.last_saved_obj = obj
|
self.last_saved_obj = obj
|
||||||
|
if isinstance( obj, User ):
|
||||||
|
self.last_saved_user = obj
|
||||||
if obj.object_id in self.objects:
|
if obj.object_id in self.objects:
|
||||||
self.objects[ obj.object_id ].append( copy( obj ) )
|
self.objects[ obj.object_id ].append( copy( obj ) )
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -103,6 +103,18 @@ class Test_root( Test_controller ):
|
||||||
assert result[ u"notes" ][ 0 ].object_id == self.anon_note.object_id
|
assert result[ u"notes" ][ 0 ].object_id == self.anon_note.object_id
|
||||||
assert result[ u"notebook" ].object_id == self.anon_notebook.object_id
|
assert result[ u"notebook" ].object_id == self.anon_notebook.object_id
|
||||||
|
|
||||||
|
def test_default_with_invite_id( self ):
|
||||||
|
result = self.http_get(
|
||||||
|
"/my_note?invite_id=whee",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result
|
||||||
|
assert result[ u"notes" ]
|
||||||
|
assert len( result[ u"notes" ] ) == 1
|
||||||
|
assert result[ u"notes" ][ 0 ].object_id == self.anon_note.object_id
|
||||||
|
assert result[ u"notebook" ].object_id == self.anon_notebook.object_id
|
||||||
|
assert result[ u"invite_id" ] == u"whee"
|
||||||
|
|
||||||
def test_default_with_unknown_note( self ):
|
def test_default_with_unknown_note( self ):
|
||||||
result = self.http_get(
|
result = self.http_get(
|
||||||
"/unknown_note",
|
"/unknown_note",
|
||||||
|
|
|
@ -44,6 +44,9 @@ class Test_users( Test_controller ):
|
||||||
trash_id1 = self.database.next_id( Notebook )
|
trash_id1 = self.database.next_id( Notebook )
|
||||||
trash_id2 = self.database.next_id( Notebook )
|
trash_id2 = self.database.next_id( Notebook )
|
||||||
|
|
||||||
|
self.database.save( Notebook.create( trash_id1, u"trash" ) )
|
||||||
|
self.database.save( Notebook.create( trash_id2, u"trash" ) )
|
||||||
|
|
||||||
self.notebooks = [
|
self.notebooks = [
|
||||||
Notebook.create( notebook_id1, u"my notebook", trash_id = trash_id1 ),
|
Notebook.create( notebook_id1, u"my notebook", trash_id = trash_id1 ),
|
||||||
Notebook.create( notebook_id2, u"my other notebook", trash_id = trash_id2 ),
|
Notebook.create( notebook_id2, u"my other notebook", trash_id = trash_id2 ),
|
||||||
|
@ -158,6 +161,83 @@ class Test_users( Test_controller ):
|
||||||
assert rate_plan[ u"name" ] == u"super"
|
assert rate_plan[ u"name" ] == u"super"
|
||||||
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
||||||
|
|
||||||
|
def test_current_after_signup_with_invite_id( self ):
|
||||||
|
# trick send_invites() into using a fake SMTP server
|
||||||
|
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 ]
|
||||||
|
|
||||||
|
result = self.http_post( "/users/send_invites", dict(
|
||||||
|
notebook_id = self.notebooks[ 0 ].object_id,
|
||||||
|
email_addresses = email_addresses,
|
||||||
|
access = u"viewer",
|
||||||
|
invite_button = u"send invites",
|
||||||
|
), session_id = self.session_id )
|
||||||
|
|
||||||
|
matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message )
|
||||||
|
invite_id = matches.group( 2 )
|
||||||
|
|
||||||
|
result = self.http_post( "/users/signup", dict(
|
||||||
|
username = self.new_username,
|
||||||
|
password = self.new_password,
|
||||||
|
password_repeat = self.new_password,
|
||||||
|
email_address = self.new_email_address,
|
||||||
|
signup_button = u"sign up",
|
||||||
|
invite_id = invite_id,
|
||||||
|
) )
|
||||||
|
|
||||||
|
invite_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
|
||||||
|
assert invite_notebook_id == self.notebooks[ 0 ].object_id
|
||||||
|
|
||||||
|
user = self.database.last_saved_user
|
||||||
|
assert isinstance( user, User )
|
||||||
|
result = cherrypy.root.users.current( user.object_id )
|
||||||
|
|
||||||
|
assert result[ u"user" ].object_id == user.object_id
|
||||||
|
assert result[ u"user" ].username == self.new_username
|
||||||
|
assert result[ u"user" ].email_address == self.new_email_address
|
||||||
|
|
||||||
|
assert cherrypy.root.users.check_access( user.object_id, self.notebooks[ 0 ].object_id )
|
||||||
|
assert cherrypy.root.users.check_access( user.object_id, self.notebooks[ 0 ].trash_id )
|
||||||
|
|
||||||
|
# the notebook that the user was invited to should be in the list of returned notebooks
|
||||||
|
notebooks = dict( [ ( notebook.object_id, notebook ) for notebook in result[ u"notebooks" ] ] )
|
||||||
|
|
||||||
|
notebook = notebooks.get( invite_notebook_id )
|
||||||
|
assert notebook
|
||||||
|
assert notebook.revision
|
||||||
|
assert notebook.name == self.notebooks[ 0 ].name
|
||||||
|
assert notebook.trash_id
|
||||||
|
assert notebook.read_write == False
|
||||||
|
assert notebook.owner == False
|
||||||
|
|
||||||
|
notebook = notebooks.get( self.notebooks[ 0 ].trash_id )
|
||||||
|
assert notebook.revision
|
||||||
|
assert notebook.name == u"trash"
|
||||||
|
assert notebook.trash_id == None
|
||||||
|
assert notebook.read_write == False
|
||||||
|
assert notebook.owner == False
|
||||||
|
|
||||||
|
notebook = notebooks.get( self.anon_notebook.object_id )
|
||||||
|
assert notebook.revision == self.anon_notebook.revision
|
||||||
|
assert notebook.name == self.anon_notebook.name
|
||||||
|
assert notebook.trash_id == None
|
||||||
|
assert notebook.read_write == False
|
||||||
|
assert notebook.owner == False
|
||||||
|
|
||||||
|
assert result.get( u"login_url" ) is None
|
||||||
|
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/"
|
||||||
|
|
||||||
|
rate_plan = result[ u"rate_plan" ]
|
||||||
|
assert rate_plan[ u"name" ] == u"super"
|
||||||
|
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
||||||
|
|
||||||
def test_signup_with_different_passwords( self ):
|
def test_signup_with_different_passwords( self ):
|
||||||
result = self.http_post( "/users/signup", dict(
|
result = self.http_post( "/users/signup", dict(
|
||||||
username = self.new_username,
|
username = self.new_username,
|
||||||
|
@ -329,6 +409,41 @@ class Test_users( Test_controller ):
|
||||||
assert rate_plan[ u"name" ] == u"super"
|
assert rate_plan[ u"name" ] == u"super"
|
||||||
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
||||||
|
|
||||||
|
def test_current_after_login_with_invite_id( self ):
|
||||||
|
# trick send_invites() into using a fake SMTP server
|
||||||
|
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 ]
|
||||||
|
|
||||||
|
result = self.http_post( "/users/send_invites", dict(
|
||||||
|
notebook_id = self.notebooks[ 0 ].object_id,
|
||||||
|
email_addresses = email_addresses,
|
||||||
|
access = u"viewer",
|
||||||
|
invite_button = u"send invites",
|
||||||
|
), session_id = self.session_id )
|
||||||
|
|
||||||
|
matches = self.INVITE_LINK_PATTERN.search( smtplib.SMTP.message )
|
||||||
|
invite_id = matches.group( 2 )
|
||||||
|
|
||||||
|
result = self.http_post( "/users/login", dict(
|
||||||
|
username = self.username2,
|
||||||
|
password = self.password2,
|
||||||
|
invite_id = invite_id,
|
||||||
|
login_button = u"login",
|
||||||
|
) )
|
||||||
|
|
||||||
|
invite_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
|
||||||
|
assert invite_notebook_id == self.notebooks[ 0 ].object_id
|
||||||
|
|
||||||
|
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
|
||||||
|
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
|
||||||
|
|
||||||
def test_update_storage( self ):
|
def test_update_storage( self ):
|
||||||
previous_revision = self.user.revision
|
previous_revision = self.user.revision
|
||||||
|
|
||||||
|
@ -1614,3 +1729,11 @@ class Test_users( Test_controller ):
|
||||||
login_button = u"login",
|
login_button = u"login",
|
||||||
) )
|
) )
|
||||||
self.session_id = result[ u"session_id" ]
|
self.session_id = result[ u"session_id" ]
|
||||||
|
|
||||||
|
def login2( self ):
|
||||||
|
result = self.http_post( "/users/login", dict(
|
||||||
|
username = self.username2,
|
||||||
|
password = self.password2,
|
||||||
|
login_button = u"login",
|
||||||
|
) )
|
||||||
|
self.session_id = result[ u"session_id" ]
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
<th class="plan_name">Free</th>
|
<th class="plan_name">Free</th>
|
||||||
<th class="plan_name">Basic<br /><span class="price_text">$5<span class="month_text">/month</span></span></th>
|
<th class="plan_name">Basic<br /><span class="price_text">$5<span class="month_text">/month</span></span></th>
|
||||||
<th class="plan_name">Standard<br /><span class="price_text">$9<span class="month_text">/month</span></span></th>
|
<th class="plan_name">Standard<br /><span class="price_text">$9<span class="month_text">/month</span></span></th>
|
||||||
<th class="plan_name">Premium<br /><span class="price_text">$14<span class="month_text">/month</span></span></th>
|
<th class="plan_name">Premium<br /><span class="price_text">$19<span class="month_text">/month</span></span></th>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="feature_name">included storage space</td>
|
<td class="feature_name">included storage space</td>
|
||||||
<td>30 MB</td>
|
<td>30 MB</td>
|
||||||
<td>250 MB</td>
|
<td>250 MB</td>
|
||||||
<td>500 MB</td>
|
<td>500 MB</td>
|
||||||
<td>1000 MB</td>
|
<td>2000 MB</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="feature_name">unlimited wiki notebooks</td>
|
<td class="feature_name">unlimited wiki notebooks</td>
|
||||||
|
|
|
@ -13,6 +13,7 @@ function Wiki( invoker ) {
|
||||||
this.rate_plan = evalJSON( getElement( "rate_plan" ).value );
|
this.rate_plan = evalJSON( getElement( "rate_plan" ).value );
|
||||||
this.storage_usage_high = false;
|
this.storage_usage_high = false;
|
||||||
this.invites = evalJSON( getElement( "invites" ).value );
|
this.invites = evalJSON( getElement( "invites" ).value );
|
||||||
|
this.invite_id = getElement( "invite_id" ).value ;
|
||||||
|
|
||||||
var total_notes_count_node = getElement( "total_notes_count" );
|
var total_notes_count_node = getElement( "total_notes_count" );
|
||||||
if ( total_notes_count_node )
|
if ( total_notes_count_node )
|
||||||
|
@ -642,7 +643,11 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revisi
|
||||||
connect( editor, "hide_clicked", function ( event ) { self.hide_editor( event, editor ) } );
|
connect( editor, "hide_clicked", function ( event ) { self.hide_editor( event, editor ) } );
|
||||||
connect( editor, "invites_updated", function ( invites ) { self.invites = invites; self.share_notebook(); } );
|
connect( editor, "invites_updated", function ( invites ) { self.invites = invites; self.share_notebook(); } );
|
||||||
connect( editor, "submit_form", function ( url, form, callback ) {
|
connect( editor, "submit_form", function ( url, form, callback ) {
|
||||||
self.invoker.invoke( url, "POST", null, callback, form );
|
var args = {}
|
||||||
|
if ( url == "/users/signup" || url == "/users/login" )
|
||||||
|
args[ "invite_id" ] = self.invite_id;
|
||||||
|
|
||||||
|
self.invoker.invoke( url, "POST", args, callback, form );
|
||||||
} );
|
} );
|
||||||
connect( editor, "revoke_invite", function ( invite_id, callback ) {
|
connect( editor, "revoke_invite", function ( invite_id, callback ) {
|
||||||
self.invoker.invoke( "/users/revoke_invite", "POST", {
|
self.invoker.invoke( "/users/revoke_invite", "POST", {
|
||||||
|
@ -1346,23 +1351,20 @@ Wiki.prototype.display_invites = function ( invite_area ) {
|
||||||
"title": "revoke this person's notebook access"
|
"title": "revoke this person's notebook access"
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
var add_invite_to = null;
|
||||||
if ( invite.owner ) {
|
if ( invite.owner ) {
|
||||||
appendChildNodes(
|
add_invite_to = owners;
|
||||||
owners, createDOM( "div", { "class": "invite" },
|
|
||||||
invite.email_address, " ", revoke_button )
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
if ( invite.read_write )
|
if ( invite.read_write )
|
||||||
appendChildNodes(
|
add_invite_to = collaborators;
|
||||||
collaborators, createDOM( "div", { "class": "invite" },
|
|
||||||
invite.email_address, " ", revoke_button )
|
|
||||||
);
|
|
||||||
else
|
else
|
||||||
appendChildNodes(
|
add_invite_to = viewers;
|
||||||
viewers, createDOM( "div", { "class": "invite" },
|
|
||||||
invite.email_address, " ", revoke_button )
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appendChildNodes(
|
||||||
|
add_invite_to, createDOM( "div", { "class": "invite" },
|
||||||
|
invite.email_address, " ", revoke_button )
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var div = createDOM( "div" );
|
var div = createDOM( "div" );
|
||||||
|
|
|
@ -4,7 +4,11 @@ from Rounded_div import Rounded_div
|
||||||
|
|
||||||
class Link_area( Div ):
|
class Link_area( Div ):
|
||||||
def __init__( self, notebooks, notebook, total_notes_count, parent_id, notebook_path, user ):
|
def __init__( self, notebooks, notebook, total_notes_count, parent_id, notebook_path, user ):
|
||||||
linked_notebooks = [ nb for nb in notebooks if nb.read_write and nb.name not in ( u"trash" ) and nb.deleted is False ]
|
linked_notebooks = [ nb for nb in notebooks if
|
||||||
|
( nb.read_write or not nb.name.startswith( u"Luminotes" ) ) and
|
||||||
|
nb.name not in ( u"trash" ) and
|
||||||
|
nb.deleted is False
|
||||||
|
]
|
||||||
|
|
||||||
Div.__init__(
|
Div.__init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -30,6 +30,7 @@ class Main_page( Page ):
|
||||||
rename = False,
|
rename = False,
|
||||||
deleted_id = None,
|
deleted_id = None,
|
||||||
invites = None,
|
invites = None,
|
||||||
|
invite_id = None,
|
||||||
):
|
):
|
||||||
startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ]
|
startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ]
|
||||||
|
|
||||||
|
@ -100,6 +101,7 @@ class Main_page( Page ):
|
||||||
Input( type = u"hidden", name = u"rename", id = u"rename", value = json( rename ) ),
|
Input( type = u"hidden", name = u"rename", id = u"rename", value = json( rename ) ),
|
||||||
Input( type = u"hidden", name = u"deleted_id", id = u"deleted_id", value = deleted_id ),
|
Input( type = u"hidden", name = u"deleted_id", id = u"deleted_id", value = deleted_id ),
|
||||||
Input( type = u"hidden", name = u"invites", id = u"invites", value = json( invites ) ),
|
Input( type = u"hidden", name = u"invites", id = u"invites", value = json( invites ) ),
|
||||||
|
Input( type = u"hidden", name = u"invite_id", id = u"invite_id", value = invite_id ),
|
||||||
Div(
|
Div(
|
||||||
id = u"status_area",
|
id = u"status_area",
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
from Tags import Span, H3, P, A
|
||||||
|
|
||||||
|
|
||||||
|
class Redeem_invite_note( Span ):
|
||||||
|
def __init__( self, invite, notebook ):
|
||||||
|
title = None
|
||||||
|
|
||||||
|
Span.__init__(
|
||||||
|
self,
|
||||||
|
H3( notebook.name ),
|
||||||
|
P(
|
||||||
|
u"You are just seconds away from viewing \"%s\"." % notebook.name,
|
||||||
|
),
|
||||||
|
P(
|
||||||
|
u"If you already have a Luminotes account, then simply ",
|
||||||
|
A( u"login", href = u"/login?invite_id=%s" % invite.object_id, target = "_top" ),
|
||||||
|
u" to your account."
|
||||||
|
),
|
||||||
|
P(
|
||||||
|
u"Otherwise, please ",
|
||||||
|
A( u"sign up", href = u"/sign_up?invite_id=%s" % invite.object_id, target = "_top" ),
|
||||||
|
u" for a free account."
|
||||||
|
),
|
||||||
|
)
|
Reference in New Issue