Lots of work on user storage quotas:
* Each model.User now has a current storage bytes and a rate plan. * model.User.to_dict() updated accordingly. * Minor Scheduler.add() change to bail of the given thread is None. * controller.Users.current() returns current user's rate plan details. * controller.Users.update_storage() now takes an optional callback. * Various methods in controller.Notebooks responsible for calling controller.Users.update_storage(). * Added rate plan details to config/Common.py. * Added quota utilization colors to style.css. * Implemented quota utilization calculation and display in Wiki.js. Still to-do: Return updated storage bytes where appropriate in controller.Notebook and update the client accordingly.
This commit is contained in:
parent
ebf1538313
commit
4d736d4821
|
@ -1,6 +1,9 @@
|
|||
import cherrypy
|
||||
|
||||
|
||||
MEGABYTE = 1024 * 1024
|
||||
|
||||
|
||||
settings = {
|
||||
"global": {
|
||||
"server.socket_port": 8081,
|
||||
|
@ -20,5 +23,19 @@ settings = {
|
|||
"luminotes.http_proxy_ip": "127.0.0.1",
|
||||
"luminotes.https_proxy_ip": "127.0.0.2",
|
||||
"luminotes.support_email": "support@luminotes.com",
|
||||
"luminotes.rate_plans": [
|
||||
{
|
||||
"name": "basic",
|
||||
"storage_quota_bytes": 30 * MEGABYTE,
|
||||
},
|
||||
{
|
||||
"name": "standard",
|
||||
"storage_quota_bytes": 100 * MEGABYTE,
|
||||
},
|
||||
{
|
||||
"name": "professional",
|
||||
"storage_quota_bytes": 300 * MEGABYTE,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ class Notebooks( object ):
|
|||
"""
|
||||
Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL.
|
||||
"""
|
||||
def __init__( self, scheduler, database ):
|
||||
def __init__( self, scheduler, database, users ):
|
||||
"""
|
||||
Create a new Notebooks object.
|
||||
|
||||
|
@ -41,11 +41,14 @@ class Notebooks( object ):
|
|||
@param scheduler: scheduler to use for asynchronous calls
|
||||
@type database: controller.Database
|
||||
@param database: database that notebooks are stored in
|
||||
@type users: controller.Users
|
||||
@param users: controller for all users, used here for updating storage utilization
|
||||
@rtype: Notebooks
|
||||
@return: newly constructed Notebooks
|
||||
"""
|
||||
self.__scheduler = scheduler
|
||||
self.__database = database
|
||||
self.__users = users
|
||||
|
||||
@expose( view = Main_page )
|
||||
@validate(
|
||||
|
@ -363,7 +366,11 @@ class Notebooks( object ):
|
|||
startup_changed = notebook.remove_startup_note( note )
|
||||
|
||||
if new_revision or startup_changed:
|
||||
self.__database.save( notebook )
|
||||
self.__database.save( notebook, self.__scheduler.thread )
|
||||
yield Scheduler.SLEEP
|
||||
self.__users.update_storage( user_id, self.__scheduler.thread )
|
||||
user = ( yield Scheduler.SLEEP )
|
||||
self.__database.save( user )
|
||||
|
||||
yield dict(
|
||||
new_revision = new_revision,
|
||||
|
@ -411,7 +418,11 @@ class Notebooks( object ):
|
|||
|
||||
if note:
|
||||
notebook.add_startup_note( note )
|
||||
self.__database.save( notebook )
|
||||
self.__database.save( notebook, self.__scheduler.thread )
|
||||
yield Scheduler.SLEEP
|
||||
self.__users.update_storage( user_id, self.__scheduler.thread )
|
||||
user = ( yield Scheduler.SLEEP )
|
||||
self.__database.save( user )
|
||||
|
||||
yield dict()
|
||||
|
||||
|
@ -456,7 +467,11 @@ class Notebooks( object ):
|
|||
|
||||
if note:
|
||||
notebook.remove_startup_note( note )
|
||||
self.__database.save( notebook )
|
||||
self.__database.save( notebook, self.__scheduler.thread )
|
||||
yield Scheduler.SLEEP
|
||||
self.__users.update_storage( user_id, self.__scheduler.thread )
|
||||
user = ( yield Scheduler.SLEEP )
|
||||
self.__database.save( user )
|
||||
|
||||
yield dict()
|
||||
|
||||
|
@ -508,7 +523,11 @@ class Notebooks( object ):
|
|||
notebook.trash.add_note( note )
|
||||
notebook.trash.add_startup_note( note )
|
||||
|
||||
self.__database.save( notebook )
|
||||
self.__database.save( notebook, self.__scheduler.thread )
|
||||
yield Scheduler.SLEEP
|
||||
self.__users.update_storage( user_id, self.__scheduler.thread )
|
||||
user = ( yield Scheduler.SLEEP )
|
||||
self.__database.save( user )
|
||||
|
||||
yield dict()
|
||||
|
||||
|
@ -567,7 +586,11 @@ class Notebooks( object ):
|
|||
notebook.add_note( note )
|
||||
notebook.add_startup_note( note )
|
||||
|
||||
self.__database.save( notebook )
|
||||
self.__database.save( notebook, self.__scheduler.thread )
|
||||
yield Scheduler.SLEEP
|
||||
self.__users.update_storage( user_id, self.__scheduler.thread )
|
||||
user = ( yield Scheduler.SLEEP )
|
||||
self.__database.save( user )
|
||||
|
||||
yield dict()
|
||||
|
||||
|
@ -613,7 +636,11 @@ class Notebooks( object ):
|
|||
notebook.trash.add_note( note )
|
||||
notebook.trash.add_startup_note( note )
|
||||
|
||||
self.__database.save( notebook )
|
||||
self.__database.save( notebook, self.__scheduler.thread )
|
||||
yield Scheduler.SLEEP
|
||||
self.__users.update_storage( user_id, self.__scheduler.thread )
|
||||
user = ( yield Scheduler.SLEEP )
|
||||
self.__database.save( user )
|
||||
|
||||
yield dict()
|
||||
|
||||
|
|
|
@ -33,13 +33,14 @@ class Root( object ):
|
|||
self.__scheduler = scheduler
|
||||
self.__database = database
|
||||
self.__settings = settings
|
||||
self.__notebooks = Notebooks( scheduler, database )
|
||||
self.__users = Users(
|
||||
scheduler,
|
||||
database,
|
||||
settings[ u"global" ].get( u"luminotes.http_url", u"" ),
|
||||
settings[ u"global" ].get( u"luminotes.https_url", u"" ),
|
||||
settings[ u"global" ].get( u"luminotes.rate_plans", [] ),
|
||||
)
|
||||
self.__notebooks = Notebooks( scheduler, database, self.__users )
|
||||
|
||||
@expose( view = Main_page )
|
||||
def index( self ):
|
||||
|
|
|
@ -125,6 +125,9 @@ class Scheduler( object ):
|
|||
@type args: tuple
|
||||
@param args: arguments to send() to the given thread when it is executed
|
||||
"""
|
||||
if thread is None:
|
||||
return
|
||||
|
||||
self.__idle.release()
|
||||
|
||||
if thread in self.__sleeping:
|
||||
|
|
|
@ -106,7 +106,7 @@ class Users( object ):
|
|||
"""
|
||||
Controller for dealing with users, corresponding to the "/users" URL.
|
||||
"""
|
||||
def __init__( self, scheduler, database, http_url, https_url ):
|
||||
def __init__( self, scheduler, database, http_url, https_url, rate_plans ):
|
||||
"""
|
||||
Create a new Users object.
|
||||
|
||||
|
@ -118,6 +118,8 @@ class Users( object ):
|
|||
@param http_url: base URL to use for non-SSL http requests, or an empty string
|
||||
@type https_url: unicode
|
||||
@param https_url: base URL to use for SSL http requests, or an empty string
|
||||
@type rate_plans: [ { "name": unicode, "storage_quota_bytes": int } ]
|
||||
@param rate_plans: list of configured rate plans
|
||||
@rtype: Users
|
||||
@return: newly constructed Users
|
||||
"""
|
||||
|
@ -125,6 +127,7 @@ class Users( object ):
|
|||
self.__database = database
|
||||
self.__http_url = http_url
|
||||
self.__https_url = https_url
|
||||
self.__rate_plans = rate_plans
|
||||
|
||||
@expose( view = Json )
|
||||
@update_auth
|
||||
|
@ -275,7 +278,14 @@ class Users( object ):
|
|||
@type user_id: unicode
|
||||
@param user_id: id of current logged-in user (if any), determined by @grab_user_id
|
||||
@rtype: json dict
|
||||
@return: { 'user': userdict or None, 'notebooks': notebooksdict, 'http_url': url }
|
||||
@return: {
|
||||
'user': userdict or None,
|
||||
'notebooks': notebooksdict,
|
||||
'startup_notes': noteslist,
|
||||
'http_url': url,
|
||||
'login_url': url,
|
||||
'rate_plan': rateplandict,
|
||||
}
|
||||
"""
|
||||
# if there's no logged-in user, default to the anonymous user
|
||||
self.__database.load( user_id or u"User anonymous", self.__scheduler.thread )
|
||||
|
@ -312,6 +322,7 @@ class Users( object ):
|
|||
startup_notes = include_startup_notes and len( notebooks ) > 0 and notebooks[ 0 ].startup_notes or [],
|
||||
http_url = self.__http_url,
|
||||
login_url = login_url,
|
||||
rate_plan = ( user.rate_plan < len( self.__rate_plans ) ) and self.__rate_plans[ user.rate_plan ] or {},
|
||||
)
|
||||
|
||||
def calculate_storage( self, user ):
|
||||
|
@ -344,13 +355,20 @@ class Users( object ):
|
|||
return total_bytes
|
||||
|
||||
@async
|
||||
def update_storage( self, user ):
|
||||
def update_storage( self, user_id, callback = None ):
|
||||
"""
|
||||
Calculate and record total storage utilization for the given user.
|
||||
@type user: User
|
||||
@param user: user for which to calculate storage utilization
|
||||
@type user_id: unicode or NoneType
|
||||
@param user_id: id of user for which to calculate storage utilization
|
||||
@type callback: generator or NoneType
|
||||
@param callback: generator to wakeup when the update is complete (optional)
|
||||
"""
|
||||
user.storage_bytes = self.calculate_storage( user )
|
||||
yield None
|
||||
self.__database.load( user_id, self.__scheduler.thread )
|
||||
user = ( yield Scheduler.SLEEP )
|
||||
|
||||
if user:
|
||||
user.storage_bytes = self.calculate_storage( user )
|
||||
|
||||
yield callback, user
|
||||
|
||||
scheduler = property( lambda self: self.__scheduler )
|
||||
|
|
|
@ -19,6 +19,16 @@ class Test_controller( object ):
|
|||
u"luminotes.https_url" : u"https://luminotes.com",
|
||||
u"luminotes.http_proxy_ip" : u"127.0.0.1",
|
||||
u"luminotes.https_proxy_ip" : u"127.0.0.2",
|
||||
"luminotes.rate_plans": [
|
||||
{
|
||||
"name": "super",
|
||||
"storage_quota_bytes": 1337,
|
||||
},
|
||||
{
|
||||
"name": "extra super",
|
||||
"storage_quota_bytes": 31337,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -66,12 +66,14 @@ class Test_notebooks( Test_controller ):
|
|||
result = self.http_get( "/notebooks/%s" % self.notebook.object_id )
|
||||
|
||||
assert result.get( u"notebook_id" ) == self.notebook.object_id
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_default_with_note( self ):
|
||||
result = self.http_get( "/notebooks/%s?note_id=%s" % ( self.notebook.object_id, self.note.object_id ) )
|
||||
|
||||
assert result.get( u"notebook_id" ) == self.notebook.object_id
|
||||
assert result.get( u"note_id" ) == self.note.object_id
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_default_with_note_and_revision( self ):
|
||||
result = self.http_get( "/notebooks/%s?note_id=%s&revision=%s" % (
|
||||
|
@ -83,6 +85,7 @@ class Test_notebooks( Test_controller ):
|
|||
assert result.get( u"notebook_id" ) == self.notebook.object_id
|
||||
assert result.get( u"note_id" ) == self.note.object_id
|
||||
assert result.get( u"revision" ) == unicode( self.note.revision )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_default_with_parent( self ):
|
||||
parent_id = "foo"
|
||||
|
@ -90,6 +93,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert result.get( u"notebook_id" ) == self.notebook.object_id
|
||||
assert result.get( u"parent_id" ) == parent_id
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_contents( self ):
|
||||
self.login()
|
||||
|
@ -105,6 +109,7 @@ class Test_notebooks( Test_controller ):
|
|||
assert notebook.object_id == self.notebook.object_id
|
||||
assert len( startup_notes ) == 1
|
||||
assert startup_notes[ 0 ] == self.note
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_contents_with_note( self ):
|
||||
self.login()
|
||||
|
@ -124,6 +129,7 @@ class Test_notebooks( Test_controller ):
|
|||
note = result[ "note" ]
|
||||
|
||||
assert note.object_id == self.note.object_id
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_contents_with_note_and_revision( self ):
|
||||
self.login()
|
||||
|
@ -147,6 +153,7 @@ class Test_notebooks( Test_controller ):
|
|||
note = result[ "note" ]
|
||||
|
||||
assert note.object_id == self.note.object_id
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_contents_without_login( self ):
|
||||
result = self.http_get(
|
||||
|
@ -155,6 +162,7 @@ class Test_notebooks( Test_controller ):
|
|||
)
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_load_note( self ):
|
||||
self.login()
|
||||
|
@ -169,6 +177,7 @@ class Test_notebooks( Test_controller ):
|
|||
assert note.object_id == self.note.object_id
|
||||
assert note.title == self.note.title
|
||||
assert note.contents == self.note.contents
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_load_note_with_revision( self ):
|
||||
self.login()
|
||||
|
@ -198,6 +207,7 @@ class Test_notebooks( Test_controller ):
|
|||
assert note.revision == previous_revision
|
||||
assert note.title == previous_title
|
||||
assert note.contents == previous_contents
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_load_note_without_login( self ):
|
||||
result = self.http_post( "/notebooks/load_note/", dict(
|
||||
|
@ -216,6 +226,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_load_unknown_note( self ):
|
||||
self.login()
|
||||
|
@ -227,6 +238,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
note = result[ "note" ]
|
||||
assert note == None
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_load_note_by_title( self ):
|
||||
self.login()
|
||||
|
@ -241,6 +253,7 @@ class Test_notebooks( Test_controller ):
|
|||
assert note.object_id == self.note.object_id
|
||||
assert note.title == self.note.title
|
||||
assert note.contents == self.note.contents
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_load_note_by_title_without_login( self ):
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
|
@ -249,6 +262,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_load_note_by_title_with_unknown_notebook( self ):
|
||||
self.login()
|
||||
|
@ -259,6 +273,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_load_unknown_note_by_title( self ):
|
||||
self.login()
|
||||
|
@ -270,6 +285,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
note = result[ "note" ]
|
||||
assert note == None
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_lookup_note_id( self ):
|
||||
self.login()
|
||||
|
@ -280,6 +296,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "note_id" ) == self.note.object_id
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_lookup_note_id_without_login( self ):
|
||||
result = self.http_post( "/notebooks/lookup_note_id/", dict(
|
||||
|
@ -288,6 +305,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_lookup_note_id_with_unknown_notebook( self ):
|
||||
self.login()
|
||||
|
@ -298,6 +316,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_lookup_unknown_note_id( self ):
|
||||
self.login()
|
||||
|
@ -308,6 +327,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "note_id" ) == None
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_save_note( self, startup = False ):
|
||||
self.login()
|
||||
|
@ -325,6 +345,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
|
||||
assert result[ "previous_revision" ] == previous_revision
|
||||
assert self.user.storage_bytes > 0
|
||||
|
||||
# make sure the old title can no longer be loaded
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
|
@ -367,6 +388,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_save_startup_note_without_login( self ):
|
||||
self.test_save_note_without_login( startup = True )
|
||||
|
@ -393,6 +415,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
|
||||
assert result[ "previous_revision" ] == previous_revision
|
||||
assert self.user.storage_bytes > 0
|
||||
|
||||
# make sure the old title can no longer be loaded
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
|
@ -446,6 +469,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
# now attempt to save over that note again without changing the contents
|
||||
previous_storage_bytes = self.user.storage_bytes
|
||||
previous_revision = self.note.revision
|
||||
result = self.http_post( "/notebooks/save_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
|
@ -458,6 +482,7 @@ class Test_notebooks( Test_controller ):
|
|||
# assert that the note wasn't actually updated the second time
|
||||
assert result[ "new_revision" ] == None
|
||||
assert result[ "previous_revision" ] == previous_revision
|
||||
assert self.user.storage_bytes == previous_storage_bytes
|
||||
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
|
@ -491,6 +516,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
# now attempt to save over that note again without changing the contents
|
||||
previous_storage_bytes = self.user.storage_bytes
|
||||
previous_revision = self.note.revision
|
||||
result = self.http_post( "/notebooks/save_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
|
@ -503,6 +529,7 @@ class Test_notebooks( Test_controller ):
|
|||
# assert that the note wasn't actually updated the second time
|
||||
assert result[ "new_revision" ] == None
|
||||
assert result[ "previous_revision" ] == previous_revision
|
||||
assert self.user.storage_bytes == previous_storage_bytes
|
||||
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
|
@ -553,6 +580,7 @@ class Test_notebooks( Test_controller ):
|
|||
# assert that the note wasn't actually updated the second time
|
||||
assert result[ "new_revision" ] == None
|
||||
assert result[ "previous_revision" ] == previous_revision
|
||||
assert self.user.storage_bytes > 0
|
||||
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
|
@ -602,6 +630,7 @@ class Test_notebooks( Test_controller ):
|
|||
assert result[ "new_revision" ]
|
||||
assert result[ "new_revision" ] not in ( first_revision, second_revision )
|
||||
assert result[ "previous_revision" ] == second_revision
|
||||
assert self.user.storage_bytes > 0
|
||||
|
||||
# make sure the first title can no longer be loaded
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
|
@ -646,6 +675,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_save_new_note( self, startup = False ):
|
||||
self.login()
|
||||
|
@ -663,6 +693,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
|
||||
assert result[ "previous_revision" ] == None
|
||||
assert self.user.storage_bytes > 0
|
||||
|
||||
# make sure the new title is now loadable
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
|
@ -705,6 +736,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
|
||||
assert result[ "previous_revision" ] == None
|
||||
assert self.user.storage_bytes > 0
|
||||
|
||||
# make sure the new title is now loadable
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
|
@ -738,6 +770,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
|
||||
assert result[ "previous_revision" ] == None
|
||||
assert self.user.storage_bytes > 0
|
||||
|
||||
# make sure the new title is now loadable
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
|
@ -770,6 +803,7 @@ class Test_notebooks( Test_controller ):
|
|||
assert len( notebook.startup_notes ) == 2
|
||||
assert notebook.startup_notes[ 0 ] == self.note
|
||||
assert notebook.startup_notes[ 1 ] == self.note2
|
||||
assert self.user.storage_bytes > 0
|
||||
|
||||
def test_add_startup_note_without_login( self ):
|
||||
result = self.http_post( "/notebooks/add_startup_note/", dict(
|
||||
|
@ -778,6 +812,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_add_startup_note_with_unknown_notebook( self ):
|
||||
self.login()
|
||||
|
@ -797,6 +832,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert len( notebook.startup_notes ) == 1
|
||||
assert notebook.startup_notes[ 0 ] == self.note
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_add_startup_unknown_note( self ):
|
||||
self.login()
|
||||
|
@ -816,6 +852,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert len( notebook.startup_notes ) == 1
|
||||
assert notebook.startup_notes[ 0 ] == self.note
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_remove_startup_note( self ):
|
||||
self.login()
|
||||
|
@ -834,6 +871,7 @@ class Test_notebooks( Test_controller ):
|
|||
notebook = result[ "notebook" ]
|
||||
|
||||
assert len( notebook.startup_notes ) == 0
|
||||
assert self.user.storage_bytes > 0
|
||||
|
||||
def test_remove_startup_note_without_login( self ):
|
||||
result = self.http_post( "/notebooks/remove_startup_note/", dict(
|
||||
|
@ -842,6 +880,7 @@ class Test_notebooks( Test_controller ):
|
|||
), session_id = self.session_id )
|
||||
|
||||
assert result.get( "error" )
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_remove_startup_note_with_unknown_notebook( self ):
|
||||
self.login()
|
||||
|
@ -861,6 +900,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert len( notebook.startup_notes ) == 1
|
||||
assert notebook.startup_notes[ 0 ] == self.note
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_remove_startup_unknown_note( self ):
|
||||
self.login()
|
||||
|
@ -880,6 +920,7 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert len( notebook.startup_notes ) == 1
|
||||
assert notebook.startup_notes[ 0 ] == self.note
|
||||
assert self.user.storage_bytes == 0
|
||||
|
||||
def test_is_startup_note( self ):
|
||||
self.login()
|
||||
|
|
|
@ -98,6 +98,10 @@ class Test_users( Test_controller ):
|
|||
else:
|
||||
assert startup_notes == []
|
||||
|
||||
rate_plan = result[ u"rate_plan" ]
|
||||
assert rate_plan[ u"name" ] == u"super"
|
||||
assert rate_plan[ u"storage_quota_bytes" ] == 1337
|
||||
|
||||
def test_current_with_startup_notes_after_signup( self ):
|
||||
self.test_current_after_signup( include_startup_notes = True )
|
||||
|
||||
|
@ -229,10 +233,37 @@ class Test_users( Test_controller ):
|
|||
def test_update_storage( self ):
|
||||
previous_revision = self.user.revision
|
||||
|
||||
cherrypy.root.users.update_storage( self.user )
|
||||
cherrypy.root.users.update_storage( self.user.object_id )
|
||||
self.scheduler.wait_until_idle()
|
||||
|
||||
expected_size = cherrypy.root.users.calculate_storage( self.user )
|
||||
|
||||
assert self.user.storage_bytes == expected_size
|
||||
assert self.user.revision > previous_revision
|
||||
|
||||
def test_update_storage_with_unknown_user_id( self ):
|
||||
original_revision = self.user.revision
|
||||
|
||||
cherrypy.root.users.update_storage( 77 )
|
||||
self.scheduler.wait_until_idle()
|
||||
|
||||
expected_size = cherrypy.root.users.calculate_storage( self.user )
|
||||
|
||||
assert self.user.storage_bytes == 0
|
||||
assert self.user.revision == original_revision
|
||||
|
||||
def test_update_storage_with_callback( self ):
|
||||
def gen():
|
||||
previous_revision = self.user.revision
|
||||
|
||||
cherrypy.root.users.update_storage( self.user.object_id, self.scheduler.thread )
|
||||
user = ( yield Scheduler.SLEEP )
|
||||
|
||||
expected_size = cherrypy.root.users.calculate_storage( self.user )
|
||||
assert user == self.user
|
||||
assert self.user.storage_bytes == expected_size
|
||||
assert self.user.revision > previous_revision
|
||||
|
||||
g = gen()
|
||||
self.scheduler.add( g )
|
||||
self.scheduler.wait_for( g )
|
||||
|
|
|
@ -13,7 +13,9 @@ class User( Persistent ):
|
|||
|
||||
def __setstate__( self, state ):
|
||||
if "_User__storage_bytes" not in state:
|
||||
state[ "_User__storage_bytes" ] = False
|
||||
state[ "_User__storage_bytes" ] = 0
|
||||
if "_User__rate_plan" not in state:
|
||||
state[ "_User__rate_plan" ] = 0
|
||||
|
||||
self.__dict__.update( state )
|
||||
|
||||
|
@ -40,6 +42,7 @@ class User( Persistent ):
|
|||
self.__email_address = email_address
|
||||
self.__notebooks = notebooks or []
|
||||
self.__storage_bytes = 0 # total storage bytes for this user's notebooks, notes, and revisions
|
||||
self.__rate_plan = 0 # each rate plan is an integer index into the array in config/Common.py
|
||||
|
||||
def __create_salt( self ):
|
||||
return "".join( [ random.choice( self.SALT_CHARS ) for i in range( self.SALT_SIZE ) ] )
|
||||
|
@ -82,6 +85,8 @@ class User( Persistent ):
|
|||
d = Persistent.to_dict( self )
|
||||
d.update( dict(
|
||||
username = self.username,
|
||||
storage_bytes = self.__storage_bytes,
|
||||
rate_plan = self.__rate_plan,
|
||||
) )
|
||||
|
||||
return d
|
||||
|
@ -99,10 +104,15 @@ class User( Persistent ):
|
|||
self.update_revision()
|
||||
self.__storage_bytes = storage_bytes
|
||||
|
||||
def __set_rate_plan( self, rate_plan ):
|
||||
self.update_revision()
|
||||
self.__rate_plan = rate_plan
|
||||
|
||||
username = property( lambda self: self.secondary_id )
|
||||
email_address = property( lambda self: self.__email_address )
|
||||
password = property( None, __set_password )
|
||||
storage_bytes = property( lambda self: self.__storage_bytes, __set_storage_bytes )
|
||||
rate_plan = property( lambda self: self.__rate_plan, __set_rate_plan )
|
||||
|
||||
# the notebooks (read-only and read-write) that this user has access to
|
||||
notebooks = property( lambda self: copy( self.__notebooks ), __set_notebooks )
|
||||
|
|
|
@ -17,6 +17,7 @@ class Test_user( object ):
|
|||
assert self.user.email_address == self.email_address
|
||||
assert self.user.notebooks == []
|
||||
assert self.user.storage_bytes == 0
|
||||
assert self.user.rate_plan == 0
|
||||
|
||||
def test_check_correct_password( self ):
|
||||
assert self.user.check_password( self.password ) == True
|
||||
|
@ -60,6 +61,14 @@ class Test_user( object ):
|
|||
assert self.user.storage_bytes == storage_bytes
|
||||
assert self.user.revision > previous_revision
|
||||
|
||||
def test_set_rate_plan( self ):
|
||||
previous_revision = self.user.revision
|
||||
rate_plan = 2
|
||||
self.user.rate_plan = rate_plan
|
||||
|
||||
assert self.user.rate_plan == rate_plan
|
||||
assert self.user.revision > previous_revision
|
||||
|
||||
|
||||
class Test_user_with_notebooks( object ):
|
||||
def setUp( self ):
|
||||
|
@ -116,3 +125,10 @@ class Test_user_with_notebooks( object ):
|
|||
trash = Notebook( trash_id, u"trash" )
|
||||
notebook = Notebook( notebook_id, u"my new notebook", trash )
|
||||
assert self.user.has_access( notebook.object_id ) == False
|
||||
|
||||
def test_to_dict( self ):
|
||||
d = self.user.to_dict()
|
||||
|
||||
assert d.get( "username" ) == self.username
|
||||
assert d.get( "storage_bytes" ) == self.user.storage_bytes
|
||||
assert d.get( "rate_plan" ) == self.user.rate_plan
|
||||
|
|
|
@ -346,6 +346,27 @@ ol li {
|
|||
padding: 0.25em 0.25em 0.25em 0.5em;
|
||||
}
|
||||
|
||||
.storage_usage_area {
|
||||
}
|
||||
|
||||
.storage_usage_low {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.storage_usage_medium {
|
||||
color: orange;
|
||||
font-weight: bold;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.storage_usage_high {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.current_notebook_name {
|
||||
background-color: #b0d0ff;
|
||||
-moz-border-radius: 0.5em;
|
||||
|
|
|
@ -12,6 +12,7 @@ function Wiki( invoker ) {
|
|||
this.search_results_editor = null; // editor for display of search results
|
||||
this.invoker = invoker;
|
||||
this.search_titles_only = true;
|
||||
this.rate_plan = null;
|
||||
|
||||
connect( this.invoker, "error_message", this, "display_error" );
|
||||
connect( "search_form", "onsubmit", this, "search" );
|
||||
|
@ -86,6 +87,9 @@ Wiki.prototype.display_user = function ( result ) {
|
|||
}, notebook.name ) ) );
|
||||
}
|
||||
|
||||
this.rate_plan = result.rate_plan;
|
||||
this.display_storage_usage( result.user.storage_bytes );
|
||||
|
||||
// display the name of the logged in user and a logout link
|
||||
appendChildNodes( user_span, "logged in as " + result.user.username );
|
||||
appendChildNodes( user_span, " | " );
|
||||
|
@ -99,6 +103,30 @@ Wiki.prototype.display_user = function ( result ) {
|
|||
} );
|
||||
}
|
||||
|
||||
Wiki.prototype.display_storage_usage = function( storage_bytes ) {
|
||||
// display the user's current storage usage
|
||||
var MEGABYTE = 1024 * 1024;
|
||||
function bytes_to_megabytes( storage_bytes ) {
|
||||
return Math.round( storage_bytes / MEGABYTE );
|
||||
}
|
||||
|
||||
var quota_bytes = this.rate_plan.storage_quota_bytes || 0;
|
||||
var usage_percent = Math.round( storage_bytes / quota_bytes * 100.0 );
|
||||
|
||||
if ( usage_percent > 90 )
|
||||
var storage_usage_class = "storage_usage_high";
|
||||
else if ( usage_percent > 75 )
|
||||
var storage_usage_class = "storage_usage_medium";
|
||||
else
|
||||
var storage_usage_class = "storage_usage_low";
|
||||
|
||||
replaceChildNodes(
|
||||
"storage_usage_area",
|
||||
createDOM( "div", { "class": storage_usage_class },
|
||||
bytes_to_megabytes( storage_bytes ) + " MB (" + usage_percent + "%) of " + bytes_to_megabytes( quota_bytes ) + " MB used" )
|
||||
);
|
||||
}
|
||||
|
||||
Wiki.prototype.populate = function ( result ) {
|
||||
this.notebook = result.notebook;
|
||||
var self = this;
|
||||
|
|
|
@ -11,4 +11,7 @@ class Link_area( Div ):
|
|||
Div(
|
||||
id = u"notebooks_area",
|
||||
),
|
||||
Div(
|
||||
id = u"storage_usage_area",
|
||||
),
|
||||
)
|
||||
|
|
Reference in New Issue
Block a user