witten
/
luminotes
Archived
1
0
Fork 0

Finished remake of signup page. You can now click "signup" for non-free accounts even if you're not logged in.

This commit is contained in:
Dan Helfman 2008-03-24 22:33:00 +00:00
parent e9d2ea28d1
commit c452408106
17 changed files with 219 additions and 11 deletions

View File

@ -56,9 +56,10 @@ class Root( object ):
note_title = unicode,
invite_id = Valid_id( none_okay = True ),
after_login = Valid_string( min = 0, max = 100 ),
plan = Valid_int( none_okay = True ),
user_id = Valid_id( none_okay = True ),
)
def default( self, note_title, invite_id = None, after_login = None, user_id = None ):
def default( self, note_title, invite_id = None, after_login = None, plan = None, user_id = None ):
"""
Convenience method for accessing a note in the main notebook by name rather than by note id.
@ -68,6 +69,8 @@ class Root( object ):
@param invite_id: id of the invite used to get to this note (optional)
@type after_login: unicode
@param after_login: URL to redirect to after login (optional, must start with "/")
@type plan: int
@param plan: rate plan index (optional, defaults to None)
@rtype: unicode
@return: rendered HTML page
"""
@ -81,6 +84,8 @@ class Root( object ):
return dict( redirect = u"%s/%s?invite_id=%s" % ( https_url, note_title, invite_id ) )
if after_login:
return dict( redirect = u"%s/%s?after_login=%s" % ( https_url, note_title, after_login ) )
if plan:
return dict( redirect = u"%s/%s?plan=%s" % ( https_url, note_title, plan ) )
else:
return dict( redirect = u"%s/%s" % ( https_url, note_title ) )
@ -100,6 +105,8 @@ class Root( object ):
result[ "invite_id" ] = invite_id
if after_login and after_login.startswith( u"/" ):
result[ "after_login" ] = after_login
if plan:
result[ "signup_plan" ] = plan
return result

View File

@ -10,7 +10,7 @@ from model.Note import Note
from model.Password_reset import Password_reset
from model.Invite import Invite
from Expose import expose
from Validate import validate, Valid_string, Valid_bool, Validation_error
from Validate import validate, Valid_string, Valid_bool, Valid_int, Validation_error
from Database import Valid_id, end_transaction
from Expire import strongly_expire
from view.Json import Json
@ -21,6 +21,7 @@ from view.Blank_page import Blank_page
from view.Thanks_note import Thanks_note
from view.Thanks_error_note import Thanks_error_note
from view.Processing_note import Processing_note
from view.Form_submit_page import Form_submit_page
USERNAME_PATTERN = re.compile( "^[a-zA-Z0-9]+$" )
@ -205,8 +206,9 @@ class Users( object ):
email_address = ( Valid_string( min = 0, max = 60 ) ),
signup_button = unicode,
invite_id = Valid_id( none_okay = True ),
rate_plan = Valid_int( none_okay = True ),
)
def signup( self, username, password, password_repeat, email_address, signup_button, invite_id = None ):
def signup( self, username, password, password_repeat, email_address, signup_button, invite_id = None, rate_plan = None ):
"""
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.
@ -223,6 +225,9 @@ class Users( object ):
@param signup_button: ignored
@type invite_id: unicode
@param invite_id: id of invite to redeem upon signup (optional)
@type rate_plan: int
@param rate_plan: index of rate plan to signup for (optional). if greater than zero, redirect
to PayPal subscribe page after signup
@rtype: json dict
@return: { 'redirect': url, 'authenticated': userdict }
@raise Signup_error: passwords don't match or the username is unavailable
@ -275,6 +280,9 @@ class Users( object ):
self.convert_invite_to_access( invite, user_id )
redirect = u"/notebooks/%s" % invite.notebook_id
# if there's a requested rate plan, then redirect to the PayPal subscribe page
elif rate_plan and rate_plan > 0:
redirect = u"/users/subscribe?rate_plan=%s" % rate_plan
# otherwise, just redirect to the newly created notebook
else:
redirect = u"/notebooks/%s" % notebook.object_id
@ -284,6 +292,39 @@ class Users( object ):
authenticated = user,
)
@expose( view = Form_submit_page )
@grab_user_id
@validate(
rate_plan = Valid_int(),
user_id = Valid_id(),
)
def subscribe( self, rate_plan, user_id ):
"""
Submit a subscription form to PayPal, allowing the user to subscribe to the given rate plan.
@type rate_plan: int
@param rate_plan: index of rate plan to subscribe to
@type user_id: unicode
@param user_id: id of current logged-in user
@rtype: dict
@return: { 'form': subscription_form_html }
@raise Signup_error: invalid rate plan, no logged-in user, or missing subscribe button
"""
if rate_plan == 0 or rate_plan >= len( self.__rate_plans ):
raise Signup_error( u"The rate plan is invalid." )
plan = self.__rate_plans[ rate_plan ]
button = plan.get( u"button" )
if not button or not button.strip():
raise Signup_error(
u"Sorry, that rate plan is not configured for subscriptions. Please contact %s." % \
( self.__support_email or u"support" )
)
return dict(
form = button % user_id,
)
@expose()
@end_transaction
@grab_user_id

View File

@ -142,12 +142,14 @@ class Valid_int( object ):
"""
Validator for an integer value.
"""
def __init__( self, min = None, max = None ):
def __init__( self, min = None, max = None, none_okay = False ):
self.min = min
self.max = max
self.message = None
self.__none_okay = none_okay
def __call__( self, value ):
if self.__none_okay and value in ( None, "None", "" ): return None
value = int( value )
if self.min is not None and value < self.min:

View File

@ -238,6 +238,21 @@ class Test_root( Test_controller ):
assert result.get( u"after_login" ) is None
assert result[ u"user" ].object_id == self.anonymous.object_id
def test_default_with_plan( self ):
plan = u"17"
result = self.http_get(
"/my_note?plan=%s" % plan,
)
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"signup_plan" ] == 17
assert result[ u"user" ].object_id == self.anonymous.object_id
def test_default_after_login( self ):
self.login()

View File

@ -93,6 +93,18 @@ class Test_users( Test_controller ):
assert result[ u"redirect" ].startswith( u"/notebooks/" )
def test_signup_with_rate_plan( self ):
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",
rate_plan = u"2",
) )
assert result[ u"redirect" ] == u"/users/subscribe?rate_plan=2"
def test_signup_without_email_address( self ):
result = self.http_post( "/users/signup", dict(
username = self.new_username,
@ -250,6 +262,62 @@ class Test_users( Test_controller ):
assert rate_plan[ u"name" ] == u"super"
assert rate_plan[ u"storage_quota_bytes" ] == 1337 * 10
def test_current_after_signup_with_rate_plan( self ):
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",
rate_plan = u"2",
) )
session_id = result[ u"session_id" ]
assert result[ u"redirect" ] == u"/users/subscribe?rate_plan=2"
user = self.database.last_saved_obj
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
notebooks = result[ u"notebooks" ]
notebook = notebooks[ 0 ]
assert notebook.object_id
assert notebook.revision
assert notebook.name == u"my notebook"
assert notebook.trash_id
assert notebook.read_write == True
assert notebook.owner == True
assert notebook.rank == 0
notebook = notebooks[ 1 ]
assert notebook.object_id == notebooks[ 0 ].trash_id
assert notebook.revision
assert notebook.name == u"trash"
assert notebook.trash_id == None
assert notebook.read_write == True
assert notebook.owner == True
assert notebook.rank == None
notebook = notebooks[ 2 ]
assert notebook.object_id == 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 notebook.rank == None
assert result.get( u"login_url" ) is None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"
rate_plan = result[ u"rate_plan" ]
assert rate_plan[ u"name" ] == u"super"
assert rate_plan[ u"storage_quota_bytes" ] == 1337 * 10
def test_signup_with_different_passwords( self ):
result = self.http_post( "/users/signup", dict(
username = self.new_username,
@ -261,6 +329,58 @@ class Test_users( Test_controller ):
assert result[ u"error" ]
def test_subscribe( self ):
self.login()
result = self.http_post( "/users/subscribe", dict(
rate_plan = u"1",
), session_id = self.session_id )
form = result.get( u"form" )
plan = self.settings[ u"global" ][ u"luminotes.rate_plans" ][ 1 ]
assert form == plan[ u"button" ] % self.user.object_id
def test_subscribe_with_free_rate_plan( self ):
self.login()
result = self.http_post( "/users/subscribe", dict(
rate_plan = u"0",
), session_id = self.session_id )
assert u"plan" in result[ u"error" ]
assert u"invalid" in result[ u"error" ]
def test_subscribe_with_invalid_rate_plan( self ):
self.login()
result = self.http_post( "/users/subscribe", dict(
rate_plan = u"17",
), session_id = self.session_id )
assert u"plan" in result[ u"error" ]
assert u"invalid" in result[ u"error" ]
def test_subscribe_without_login( self ):
result = self.http_post( "/users/subscribe", dict(
rate_plan = u"1",
) )
assert u"user" in result[ u"error" ]
assert u"invalid" in result[ u"error" ]
def test_subscribe_without_subscribe_button( self ):
self.login()
self.settings[ u"global" ][ u"luminotes.rate_plans" ][ 1 ][ u"button" ] = u" "
result = self.http_post( "/users/subscribe", dict(
rate_plan = u"1",
), session_id = self.session_id )
print result
assert u"not configured" in result[ u"error" ]
def test_demo( self ):
result = self.http_post( "/users/demo", dict() )

View File

@ -348,6 +348,7 @@
.upgrade_left_area {
width: 400px;
margin-top: 1.5em;
margin-bottom: 1em;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

View File

@ -17,6 +17,7 @@ function Wiki( invoker ) {
this.invites = evalJSON( getElement( "invites" ).value );
this.invite_id = getElement( "invite_id" ).value;
this.after_login = getElement( "after_login" ).value;
this.signup_plan = getElement( "signup_plan" ).value;
this.font_size = null;
var total_notes_count_node = getElement( "total_notes_count" );
@ -693,10 +694,12 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revisi
connect( editor, "invites_updated", function ( invites ) { self.invites = invites; self.share_notebook(); } );
connect( editor, "submit_form", function ( url, form, callback ) {
var args = {}
if ( url == "/users/signup" || url == "/users/login" ) {
if ( url == "/users/signup" ) {
args[ "invite_id" ] = self.invite_id;
if ( url == "/users/login" )
args[ "after_login" ] = self.after_login;
args[ "rate_plan" ] = self.signup_plan;
} else if ( url == "/users/login" ) {
args[ "invite_id" ] = self.invite_id;
args[ "after_login" ] = self.after_login;
}
self.invoker.invoke( url, "POST", args, callback, form );

16
view/Form_submit_page.py Normal file
View File

@ -0,0 +1,16 @@
from Tags import Html, Head, Body, Script
class Form_submit_page( Html ):
def __init__( self, form ):
Html.__init__(
self,
Head(),
Body(
form,
Script( # auto-submit the form
u"document.forms[ 0 ].submit();",
type = u"text/javascript",
),
),
)

View File

@ -132,7 +132,7 @@ class Front_page( Product_page ):
separator = u"",
),
Div(
u"-Scott Tiner",
u"-Scott Tiner, Technical Writer",
class_ = u"quote_signature"
),
class_ = u"quote",

View File

@ -32,6 +32,7 @@ class Main_page( Page ):
invites = None,
invite_id = None,
after_login = None,
signup_plan = None,
):
startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ]
@ -108,6 +109,7 @@ class Main_page( Page ):
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 ),
Input( type = u"hidden", name = u"after_login", id = u"after_login", value = after_login ),
Input( type = u"hidden", name = u"signup_plan", id = u"signup_plan", value = signup_plan ),
Div(
id = u"status_area",
),

View File

@ -98,8 +98,8 @@ class Upgrade_page( Product_page ):
alt = u"More room to stretch out",
),
Ul(
Li( u"More room for your wiki notes." ),
Li( u"More room for your documents and files." ),
Li( u"More space for your wiki notes." ),
Li( u"More space for your documents and files." ),
class_ = u"upgrade_text",
),
Img(
@ -176,7 +176,8 @@ class Upgrade_page( Product_page ):
class_ = u"price_text",
separator = u"",
),
user and user.username and user.rate_plan != index and plan.get( u"button" ).strip() and plan.get( u"button" ) % user.object_id,
user and user.username not in ( u"anonymous", None ) and user.rate_plan != index \
and plan.get( u"button" ).strip() and plan.get( u"button" ) % user.object_id or None,
) or None,
( not user or user.username in ( u"anonymous", None ) ) and Div(
A(