The application is covered by tests that are written using the doctest style. In this section you will find all those tests.
The app variable is binded in out tests setup to a brand new created Merlot object. The Merlot object has a title:
>>> app.title
u'Merlot \u2014 Project Management Software'
When the application is created, containers for projects, clients and users get also created:
>>> 'projects' in app
True
>>> 'clients' in app
True
>>> 'users' in app
True
>>> project_container = app['projects']
>>> client_container = app['clients']
>>> userfolder = app['users']
Let’s check that projects_container and clients_container are what we expect them to be:
>>> project_container.title
u'Projects'
>>> client_container.title
u'Clients'
>>> from merlot.interfaces import IProjectContainer, IClientContainer
>>> IProjectContainer.providedBy(project_container)
True
>>> IClientContainer.providedBy(client_container)
True
There are a few local utilities registered in the Merlot application. We have local utilities to manage users and authentication:
>>> from zope.component import getUtility
>>> from zope.app.authentication.interfaces import IAuthenticatorPlugin
>>> from zope.app.security.interfaces import IAuthentication
>>> users = getUtility(IAuthenticatorPlugin, 'users', context=app)
>>> auth = getUtility(IAuthentication, context=app)
Also, an integer IDs local utility is registered. This allow us to create an integer ID for each object and later look up objects by their IDs:
>>> from zope.intid.interfaces import IIntIds
>>> intids = getUtility(IIntIds, name='intids', context=app)
The last local utility registered is a relation catalog utility that allow us to keep track of relations between objects:
>>> from zc.relation.interfaces import ICatalog
>>> rel_catalog = getUtility(ICatalog, context=app)
A global utility is registered to resolve to an object given a path and vice versa:
>>> from z3c.objpath.interfaces import IObjectPath
>>> object_path = getUtility(IObjectPath)
>>> object_path.path(app['projects'])
u'/app/projects'
>>> object_path.resolve(u'/app/projects') == app['projects']
True
Starred tasks are tasks that the user selects to appear allways on top on his or her dashboard. They are implemented as annotations on the user account model. That is, the objects that store the users information (the model implements merlot.interfaces.IAccount). The annotations are managed via an adapter.
We need to create an account object:
>>> from merlot.auth import Account
>>> user_account = Account('testuser', 'secret', u'Test User')
Now we can adapt the account object and manipulate a list of integer values which are intended to be tasks integer IDs that you can get by using the integer IDs local utility:
>>> from merlot.interfaces import IStarredTasks
>>> starred_tasks = IStarredTasks(user_account)
>>> starred_tasks.getStarredTasks()
[]
>>> starred_tasks.addStarredTask(23)
>>> starred_tasks.addStarredTask(44523)
>>> starred_tasks.getStarredTasks()
[23, 44523]
And we can delete items:
>>> starred_tasks.removeStarredTask(44523)
>>> starred_tasks.getStarredTasks()
[23]
If we try to delete an item that doesn’t exist, nothing happens:
>>> starred_tasks.removeStarredTask(344)
>>> starred_tasks.getStarredTasks()
[23]
We create a project:
>>> from datetime import datetime
>>> from merlot.project import Project
>>> project = Project()
>>> project.title = u'Testing'
>>> project.start_date = datetime(2010, 10, 10).date()
>>> project.id = 'test'
>>> app['test'] = project
We make a catalog query to find the project we’ve just added:
>>> from zope.component import getUtility
>>> from zope.catalog.interfaces import ICatalog
>>> catalog = getUtility(ICatalog, context=app)
>>> result = catalog.searchResults(title='Testing')
>>> len(result)
1
>>> list(result)[0].title
u'Testing'
>>> list(result)[0].start_date
datetime.date(2010, 10, 10)
Let’s test the project and task indexes. To do so, let’s create a task a log inside that task:
>>> from merlot.project import Task, Log
>>> task = Task()
>>> task.title = 'A sample task'
>>> task.description = ''
>>> task.id = 'a-sample-task'
>>> app['test'][task.id] = task
>>> log = Log()
>>> log.description = 'Bla, bla'
>>> app['test'][task.id]['1'] = log
Let’s see what’s in the project and task indexes for the log we’ve just created according to the catalog:
>>> result = catalog.searchResults(description='bla')
>>> len(result)
1
>>> logi = list(result)[0]
>>> logi.description
'Bla, bla'
Well, let’s see what’s the intid associated with our project and task:
>>> from zope.intid.interfaces import IIntIds
>>> intids = getUtility(IIntIds, name='intids', context=app)
>>> project_id = intids.getId(app['test'])
>>> task_id = intids.getId(app['test']['a-sample-task'])
And finally we can compare the actual intids with the values indexed by the catalog:
>>> project_id == logi.project()
True
>>> task_id == logi.task()
True
In Merlot there are only two roles: authenticated and anonymous. If you are authenticated you have full access to the system; if you are not logged in, you have no permissions.
We need to create a user to play with later. First of all we access the site as admin:
>>> from zope.app.wsgi.testlayer import Browser
>>> browser = Browser()
>>> browser.handleErrors = False
>>> browser.addHeader('Authorization', 'Basic admin:admin')
>>> browser.open('http://localhost/app')
>>> 'Logged in as: Manager' in browser.contents
True
Now we create a new user. To do so, we click on the Users tab and then on the Add new User link:
>>> browser.getLink('Users').click()
>>> 'There are currently no users.' in browser.contents
True
>>> browser.getLink('Add new User').click()
We fill the add user form:
>>> browser.getControl(name="form.id").value = u'user'
>>> browser.getControl(name="form.real_name").value = u'Testing User'
>>> browser.getControl(name="form.password").value = u'secret'
>>> browser.getControl(name="form.confirm_password").value = u'secret'
Submit the form and check that the changes were saved:
>>> browser.getControl("Add user").click()
>>> 'User added' in browser.contents
True
>>> 'Testing User' in browser.contents
True
We are now ready to start testing permissions. First of all, let’s log out from the site to test that we can’t access any pages:
>>> browser = Browser()
>>> browser.open('http://localhost/app')
>>> browser.url
'http://localhost/app/@@login?camefrom=http%3A%2F%2Flocalhost%2Fapp%2F%40%40index'
We can’t access the projects container:
>>> browser.open('http://localhost/app/projects')
>>> browser.url
'http://localhost/app/@@login?camefrom=http%3A%2F%2Flocalhost%2Fapp%2Fprojects%2F%40%40index'
We can’t access the reports either:
>>> browser.open('http://localhost/app/@@logs-report')
>>> browser.url
'http://localhost/app/@@login?camefrom=http%3A%2F%2Flocalhost%2Fapp%2F%40%40logs-report'
>>> browser.open('http://localhost/app/@@tasks-report')
>>> browser.url
'http://localhost/app/@@login?camefrom=http%3A%2F%2Flocalhost%2Fapp%2F%40%40tasks-report'
We can’t access the clients container:
>>> browser.open('http://localhost/app/clients')
>>> browser.url
'http://localhost/app/@@login?camefrom=http%3A%2F%2Flocalhost%2Fapp%2Fclients%2F%40%40index'
Now we authenticate using the user we created using the login form:
>>> browser.open('http://localhost/app')
>>> browser.getControl(name="form.username").value = u'user'
>>> browser.getControl(name="form.password").value = u'secret'
>>> browser.getControl("Login").click()
>>> 'You are logged in.' in browser.contents
True
And we can access everything. For example, we can access the projects container:
>>> browser.getLink('Projects').click()
>>> browser.url
'http://localhost/app/projects'
We can also access the reports:
>>> browser.open('http://localhost/app/@@logs-report')
>>> browser.url
'http://localhost/app/@@logs-report'
>>> browser.open('http://localhost/app/@@tasks-report')
>>> browser.url
'http://localhost/app/@@tasks-report'
We can also access the clients container:
>>> browser.open('http://localhost/app/clients')
>>> browser.url
'http://localhost/app/clients'
Let’s go to the user folder and create a new user:
>>> browser.getLink('Users').click()
>>> browser.getLink('Add new User').click()
If we try too set a user name with strange characters, the form submission will fail:
>>> browser.getControl(name="form.id").value = u'jdoe/'
>>> browser.getControl(name="form.real_name").value = u'John Doe'
>>> browser.getControl(name="form.password").value = u'easy'
>>> browser.getControl(name="form.confirm_password").value = u'easy'
>>> browser.getControl("Add user").click()
>>> 'Invalid user name, only characters in [a-z0-9] are allowed' in \
... browser.contents
True
Let’s also check that no user were created in the ZODB by checking that the only existing user is the one we created at the beginning:
>>> users = app['users']
>>> len(users.values())
1
>>> users.values()[0].id
'user'
Let’s fix the user name in the form and see what happens if we enter different values in the password and confirm password fields:
>>> browser.getControl(name="form.id").value = u'jdoe'
>>> browser.getControl(name="form.real_name").value = u'John Doe'
>>> browser.getControl(name="form.password").value = u'something'
>>> browser.getControl(name="form.confirm_password").value = u'different'
>>> browser.getControl("Add user").click()
>>> 'Passwords does not match' in browser.contents
True
Let’s finally fill the form properly and create the user:
>>> browser.getControl(name="form.password").value = u'something'
>>> browser.getControl(name="form.confirm_password").value = u'something'
>>> browser.getControl("Add user").click()
>>> 'User added' in browser.contents
True
>>> 'John Doe' in browser.contents
True
And the user is now persisted:
>>> len(users.values())
2
>>> 'jdoe' in [u.id for u in users.values()]
True
We can now edit the user we’ve just added:
>>> browser.getLink('edit', index=0).click()
>>> 'jdoe' in browser.contents
True
There is a username field in the edit form, but its value can’t be changed. We don’t allow user IDs to change as they are used to reference users in other parts of the system:
>>> try:
... browser.getControl(name='form.id').value = 'changed'
... except AttributeError as detail:
... detail
AttributeError("control 'form.id' is readonly",)
Let’s change the real name to something else and save the changes:
>>> browser.getControl(name='form.real_name').value = u'Something Else'
>>> browser.getControl('Save').click()
>>> 'Changes saved' in browser.contents
True
We got redirected to the container user folder:
>>> browser.url
'http://localhost/app/users'
And the change is in place:
>>> 'Something Else' in browser.contents
True
Let’s check that the password for the user Something Else was no modified. So we logout:
>>> browser = Browser()
And we use the login form to login into the site:
>>> browser.open('http://localhost/app')
>>> browser.getControl(name="form.username").value = u'jdoe'
>>> browser.getControl(name="form.password").value = u'something'
>>> browser.getControl("Login").click()
>>> 'You are logged in.' in browser.contents
True
Let’s change the password of the user we first created:
>>> browser.getLink('Users').click()
>>> browser.getLink('edit', index=1).click()
>>> 'Testing User' in browser.contents
True
Once again, if we enter different values for the password and confirm password fields, we get a validation error:
>>> browser.getControl(name="form.password").value = u'super'
>>> browser.getControl(name="form.confirm_password").value = u'super2'
>>> browser.getControl('Save').click()
>>> 'Passwords does not match' in browser.contents
True
>>> browser.url
'http://localhost/app/users/user/edit'
So, let’s change the password for real:
>>> browser.getControl(name="form.password").value = u'super'
>>> browser.getControl(name="form.confirm_password").value = u'super'
>>> browser.getControl('Save').click()
>>> 'Changes saved' in browser.contents
True
>>> browser.url
'http://localhost/app/users'
Now let’s try to change our own password:
>>> browser.getLink('Users').click()
>>> browser.getLink('edit', index=0).click()
>>> 'jdoe' in browser.contents
True
>>> browser.getControl(name="form.password").value = u'supersecret'
>>> browser.getControl(name="form.confirm_password").value = u'supersecret'
>>> browser.getControl('Save').click()
>>> 'Changes saved' in browser.contents
True
As our credentials changed, we are kicked off the site:
>>> browser.url
'http://localhost/app/@@login?camefrom=http%3A%2F%2Flocalhost%2Fapp%2Fusers%2F%40%40index'
The old credentials are no longer valid:
>>> browser.getControl(name="form.username").value = u'jdoe'
>>> browser.getControl(name="form.password").value = u'something'
>>> browser.getControl("Login").click()
>>> 'Invalid username and/or password' in browser.contents
True
Let’s login back using the new password:
>>> browser.getControl(name="form.username").value = u'jdoe'
>>> browser.getControl(name="form.password").value = u'supersecret'
>>> browser.getControl("Login").click()
>>> 'You are logged in.' in browser.contents
True
Now let’s delete the user Testing User:
>>> browser.getLink('Users').click()
>>> browser.getLink('delete', index=1).click()
>>> 'Are you sure you want to delete the "user" item?' in browser.contents
True
We can cancel the deletion, in that case, the user won’t be deleted and we will get redirected to the user listing:
>>> browser.getControl('Cancel').click()
>>> browser.url
'http://localhost/app/users'
>>> 'Testing User' in browser.contents
True
Well, let’s delete the user for real now:
>>> browser.getLink('Users').click()
>>> browser.getLink('delete', index=1).click()
>>> 'Are you sure you want to delete the "user" item?' in browser.contents
True
>>> browser.getControl('Delete').click()
>>> 'User deleted.' in browser.contents
True
And let’s logout from the site:
>>> browser.getLink('Logout').click()
>>> browser.url.startswith('http://localhost/app/@@login')
True
We need to create a user to play with later. First of all we access the site as admin:
>>> from zope.app.wsgi.testlayer import Browser
>>> browser = Browser()
>>> browser.handleErrors = False
>>> browser.addHeader('Authorization', 'Basic admin:admin')
>>> browser.open('http://localhost/app')
>>> 'Logged in as: Manager' in browser.contents
True
Now we create a new user that we will use through this document:
>>> browser.getLink('Users').click()
>>> browser.getLink('Add new User').click()
>>> browser.getControl(name="form.id").value = u'user'
>>> browser.getControl(name="form.real_name").value = u'Testing User'
>>> browser.getControl(name="form.password").value = u'secret'
>>> browser.getControl(name="form.confirm_password").value = u'secret'
>>> browser.getControl("Add user").click()
>>> 'User added' in browser.contents
True
>>> 'Testing User' in browser.contents
True
Let’s log in with the user we’ve just created:
>>> browser = Browser()
>>> browser.open('http://localhost/app')
>>> browser.getControl(name="form.username").value = u'user'
>>> browser.getControl(name="form.password").value = u'secret'
>>> browser.getControl("Login").click()
>>> 'You are logged in.' in browser.contents
True
Let’s create a client. First we click to Clients tab:
>>> browser.getLink('Clients').click()
>>> 'There are currently no clients.' in browser.contents
True
And we add a client:
>>> browser.getLink('Add new Client').click()
>>> browser.getControl(name='form.title').value = u'Acme'
>>> browser.getControl(name='form.type').value = ('NGO',)
>>> browser.getControl("Add client").click()
>>> 'Client added' in browser.contents
True
>>> 'There are currently no clients.' in browser.contents
False
We edit the client we’ve just created:
>>> browser.getLink('edit').click()
>>> browser.url
'http://localhost/app/clients/1/edit'
>>> browser.getControl(name='form.title').value = u'Some Company'
>>> browser.getControl(name='form.type').value = ('Company',)
>>> browser.getControl('Save').click()
>>> 'Changes saved' in browser.contents
True
Let’s now create a new project associated with the client we created:
>>> browser.getLink('Projects').click()
>>> browser.getLink('Add new Project').click()
>>> browser.getControl(name="form.title").value = u'Project'
>>> browser.getControl(name='form.client').value = ('/app/clients/1',)
>>> browser.getControl('Add project').click()
>>> 'Project added' in browser.contents
True
If we try to delete the client, we will not be able to because it has a client associated:
>>> browser.getLink('Clients').click()
>>> browser.getLink('delete').click()
>>> 'Are you sure you want to delete the "Some Company" item?' in \
... browser.contents
True
>>> browser.getControl('Delete').click()
>>> 'This client cannot be deleted because it has projects associated' in \
... browser.contents
True
So, let’s delete the project and try again:
>>> browser.getLink('Projects').click()
>>> browser.getLink('delete').click()
>>> 'Are you sure you want to delete the "Project" item?' in \
... browser.contents
True
>>> browser.getControl('Delete').click()
>>> 'Project deleted' in browser.contents
True
Now we go back and try to delete the client:
>>> browser.getLink('Clients').click()
>>> browser.getLink('delete').click()
>>> 'Are you sure you want to delete the "Some Company" item?' in \
... browser.contents
True
>>> browser.getControl('Delete').click()
>>> 'Client deleted' in browser.contents
True
We need to create a user to play with later. First of all we access the site as admin:
>>> from zope.app.wsgi.testlayer import Browser
>>> browser = Browser()
>>> browser.handleErrors = False
>>> browser.addHeader('Authorization', 'Basic admin:admin')
>>> browser.open('http://localhost/app')
>>> 'Logged in as: Manager' in browser.contents
True
Now we create a new user:
>>> browser.getLink('Users').click()
>>> browser.getLink('Add new User').click()
>>> browser.getControl(name="form.id").value = u'user'
>>> browser.getControl(name="form.real_name").value = u'Testing User'
>>> browser.getControl(name="form.password").value = u'secret'
>>> browser.getControl(name="form.confirm_password").value = u'secret'
>>> browser.getControl("Add user").click()
>>> 'User added' in browser.contents
True
>>> 'Testing User' in browser.contents
True
We are now ready to start testing projects. Let’s log in with the user we’ve just created:
>>> browser = Browser()
>>> browser.open('http://localhost/app')
>>> browser.getControl(name="form.username").value = u'user'
>>> browser.getControl(name="form.password").value = u'secret'
>>> browser.getControl("Login").click()
>>> 'You are logged in.' in browser.contents
True
Since we must associate projects to clients, let’s start by creating a client:
>>> browser.getLink('Clients').click()
>>> browser.getLink('Add new Client').click()
>>> browser.getControl(name="form.title").value = u'Acme'
>>> browser.getControl("Add client").click()
>>> 'Client added' in browser.contents
True
Now we can create a project. Let’s try to submit the add project form after filling just the title and check that we get proper validation errors to fill required fields:
>>> browser.getLink('Projects').click()
>>> browser.getLink('Add new Project').click()
>>> browser.getControl(name="form.title").value = u'Develop a web app'
>>> browser.getControl("Add project").click()
>>> 'Required input is missing' in browser.contents
True
The project status defaults to In progress:
>>> browser.getControl(name='form.status').value
['In progress']
Also, the start date is automatically set to today:
>>> from datetime import datetime, timedelta
>>> from merlot.lib import DATE_FORMAT
>>> today = datetime.today().strftime(DATE_FORMAT)
>>> browser.getControl(name='form.start_date').value == today
True
If we try to set an end date prior to the start date, we will also get a validation error:
>>> yesterday = datetime.today() - timedelta(1)
>>> yesterday = yesterday.strftime(DATE_FORMAT)
>>> browser.getControl(name='form.end_date').value = yesterday
>>> browser.getControl("Add project").click()
>>> 'Start date must preceed end date' in browser.contents
True
So let’s set the end date to tomorrow and fill the client field to finally add the project:
>>> tomorrow = datetime.today() + timedelta(1)
>>> tomorrow = tomorrow.strftime(DATE_FORMAT)
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl(name='form.client').value = ('/app/clients/1',)
>>> browser.getControl("Add project").click()
>>> 'Project added' in browser.contents
True
And we were redirected to the created project view. Notice how the URL was generated based on the title we used:
>>> browser.url
'http://localhost/app/projects/develop-a-web-app'
>>> 'Project view: Develop a web app' in browser.contents
True
Projects can contain tasks, so let’s create a task, but first let’s verify that the priority field defaults to Normal, that the status field defaults to In progress and that start_date default to the current date:
>>> browser.getLink('Add new Task').click()
>>> browser.getControl(name='form.priority').value
['Normal']
>>> browser.getControl(name='form.status').value
['In progress']
>>> browser.getControl(name='form.start_date').value == today
True
No we will fill some fields and submit the form. Once again, if we set an end date prior to the start date, we get a validation error:
>>> browser.getControl(name="form.title").value = u'Define requirements'
>>> browser.getControl(name='form.end_date').value = yesterday
>>> browser.getControl("Add task").click()
>>> 'Start date must preceed end date' in browser.contents
True
Let’s set the end date to tomorrow and add the task:
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl("Add task").click()
>>> 'Task added' in browser.contents
True
We are still in the project view:
>>> browser.url
'http://localhost/app/projects/develop-a-web-app'
>>> 'Project view: Develop a web app' in browser.contents
True
Let’s quickly add another task:
>>> browser.getLink('Add new Task').click()
>>> browser.getControl(name="form.title").value = u'Testing'
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl("Add task").click()
>>> 'Task added' in browser.contents
True
We can delete a task from the project view:
>>> browser.getLink('delete', index=2).click()
>>> 'Are you sure you want to delete the "Testing" item?' in \
... browser.contents
True
>>> browser.getControl('Delete').click()
>>> 'Task deleted.' in browser.contents
True
And we are still in the project view:
>>> browser.url
'http://localhost/app/projects/develop-a-web-app'
>>> 'Project view: Develop a web app' in browser.contents
True
In order to track the time that a task takes, you can associate time logs to them. Let’s go the the task view, and there we can add a log:
>>> browser.getLink('Define requirements').click()
>>> 'Task view: Define requirements' in browser.contents
True
>>> browser.getControl(name='form.description').value = u'Write document'
>>> browser.getControl(name='form.date').value == today
True
>>> browser.getControl(name='form.hours').value = u'6'
>>> browser.getControl(name='form.remaining').value = u'2.4'
>>> browser.getControl('Add log').click()
>>> 'Log added' in browser.contents
True
>>> 'Write document' in browser.contents
True
We are still in the task view:
>>> 'Task view: Define requirements' in browser.contents
True
The remaining hours set when adding a log updates the remaining hours field in the task:
>>> from decimal import Decimal
>>> task = app['projects']['develop-a-web-app']['define-requirements']
>>> task.remaining == Decimal('2.4')
True
Let’s check that there are some required fields to add a log by submitting the form without filling any field:
>>> browser.getControl('Add log').click()
>>> 'Required input is missing' in browser.contents
True
Let’s mark the current task as starred, but before, let’s check what are the current starred tasks for the authenticated user:
>>> from merlot.interfaces import IStarredTasks
>>> from zope.component import getUtility
>>> from zope.app.authentication.interfaces import IAuthenticatorPlugin
>>> from zope.intid.interfaces import IIntIds
>>> user = app['users']['user']
>>> starred_tasks = IStarredTasks(user)
>>> starred_tasks.getStarredTasks()
[]
Now we mark the task as starred:
>>> browser.getLink(url=('http://localhost/app/projects/develop-a-web-app/'
... 'define-requirements/toggle-starred')).click()
Now the task is marked as starred for the current user:
>>> intids = getUtility(IIntIds, name='intids', context=app)
>>> intid = intids.getId(task)
>>> starred_tasks.getStarredTasks() == [intid]
True
>>> link = browser.getLink(url=('http://localhost/app/projects/'
... 'develop-a-web-app/define-requirements/'
... 'toggle-starred'))
>>> link.attrs['class'] == 'starred-selected'
True
Let’s quickly create another task and mark it as starred:
>>> browser.getLink('Develop a web app').click()
>>> browser.getLink('Add new Task').click()
>>> browser.getControl(name="form.title").value = u'New task'
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl("Add task").click()
>>> 'Task added' in browser.contents
True
>>> browser.getLink('New task').click()
>>> browser.getLink(url=('http://localhost/app/projects/develop-a-web-app/'
... 'new-task/toggle-starred')).click()
Let’s check that it is actually marked as starred for the authenticated user:
>>> newtask = app['projects']['develop-a-web-app']['new-task']
>>> newtask_intid = intids.getId(newtask)
>>> starred_tasks.getStarredTasks() == [intid, newtask_intid]
True
Let’s now edit the first task and change the hours estimate to 10:
>>> browser.getLink('Develop a web app').click()
>>> browser.getLink('Define requirements').click()
>>> browser.getLink('Edit').click()
>>> browser.getControl(name='form.estimate').value = '10'
>>> browser.getControl('Save').click()
>>> 'Changes saved' in browser.contents
True
The changes persisted:
>>> task.estimate == Decimal(10)
True
Logs can also be edited:
>>> browser.getLink('edit', index=1).click()
>>> browser.getControl(name='form.description').value = 'New description'
>>> browser.getControl('Save').click()
>>> 'Changes saved' in browser.contents
True
>>> 'New description' in browser.contents
True
>>> 'Write document' in browser.contents
False
If a task is deleted, it will be automatically removed from all users’ starred tasks lists. Lets delete one of the tasks and check that it’s also removed from the starred tasks list of the authenticated user:
>>> browser.getLink('Delete').click()
>>> 'Are you sure you want to delete the "Define requirements" item?' in \
... browser.contents
True
>>> browser.getControl('Delete').click()
>>> 'Task deleted' in browser.contents
True
>>> starred_tasks.getStarredTasks() == [newtask_intid]
True
Moreover, if we delete the project that contains an starred task, then that task is also removed from all users’ starred tasks lists. Let’s delete the project and test this:
>>> browser.getLink('Delete').click()
>>> 'Are you sure you want to delete the "Develop a web app" item?' in \
... browser.contents
True
>>> browser.getControl('Delete').click()
>>> 'Project deleted' in browser.contents
True
>>> starred_tasks.getStarredTasks()
[]
Let’s now create a new project:
>>> browser.getLink('Add new Project').click()
>>> browser.getControl(name="form.title").value = u'Project'
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl(name='form.client').value = ('/app/clients/1',)
>>> browser.getControl("Add project").click()
>>> 'Project added' in browser.contents
True
Let’s create another project with the same title and check that the IDs don’t clash:
>>> browser.getLink('Projects').click()
>>> browser.getLink('Add new Project').click()
>>> browser.getControl(name="form.title").value = u'Project'
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl(name='form.client').value = ('/app/clients/1',)
>>> browser.getControl("Add project").click()
>>> 'Project added' in browser.contents
True
>>> browser.url
'http://localhost/app/projects/project1'
Let’s edit the current project by changing the title and start date:
>>> browser.getLink('Edit').click()
>>> browser.getControl(name="form.title").value = u'Project 2'
>>> browser.getControl(name='form.start_date').value = yesterday
>>> browser.getControl('Save').click()
>>> 'Changes saved' in browser.contents
True
>>> browser.url
'http://localhost/app/projects/project1'
And let’s check that the changes persisted:
>>> project1 = app['projects']['project1']
>>> project1.title
u'Project 2'
>>> project1.start_date == datetime.today().date() - timedelta(1)
True
There are currently two reports available, a Logs report and a Tasks report. Read on to learn about what those are about.
We need to create a user to play with later. First of all we access the site as admin:
>>> from zope.app.wsgi.testlayer import Browser
>>> browser = Browser()
>>> browser.handleErrors = False
>>> browser.addHeader('Authorization', 'Basic admin:admin')
>>> browser.open('http://localhost/app')
>>> 'Logged in as: Manager' in browser.contents
True
Now we create a new user:
>>> browser.getLink('Users').click()
>>> browser.getLink('Add new User').click()
>>> browser.getControl(name="form.id").value = u'user'
>>> browser.getControl(name="form.real_name").value = u'Testing User'
>>> browser.getControl(name="form.password").value = u'secret'
>>> browser.getControl(name="form.confirm_password").value = u'secret'
>>> browser.getControl("Add user").click()
>>> 'User added' in browser.contents
True
>>> 'Testing User' in browser.contents
True
Let’s log in with the user we’ve just created:
>>> browser = Browser()
>>> browser.open('http://localhost/app')
>>> browser.getControl(name="form.username").value = u'user'
>>> browser.getControl(name="form.password").value = u'secret'
>>> browser.getControl("Login").click()
>>> 'You are logged in.' in browser.contents
True
Since we must associate projects to clients, let’s start by creating a client:
>>> browser.getLink('Clients').click()
>>> browser.getLink('Add new Client').click()
>>> browser.getControl(name="form.title").value = u'Acme'
>>> browser.getControl("Add client").click()
>>> 'Client added' in browser.contents
True
Let’s now create two new projects, but first let’s set some formatted strings with dates:
>>> from datetime import datetime, timedelta
>>> from merlot.lib import DATE_FORMAT
>>> today = datetime.today().strftime(DATE_FORMAT)
>>> yesterday = datetime.today() - timedelta(1)
>>> yesterday = yesterday.strftime(DATE_FORMAT)
>>> tomorrow = datetime.today() + timedelta(1)
>>> tomorrow = tomorrow.strftime(DATE_FORMAT)
>>> browser.getLink('Projects').click()
>>> browser.getLink('Add new Project').click()
>>> browser.getControl(name="form.title").value = u'Project One'
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl(name='form.client').value = ('/app/clients/1',)
>>> browser.getControl("Add project").click()
>>> 'Project added' in browser.contents
True
>>> browser.getLink('Projects').click()
>>> browser.getLink('Add new Project').click()
>>> browser.getControl(name="form.title").value = u'Project Two'
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl(name='form.client').value = ('/app/clients/1',)
>>> browser.getControl("Add project").click()
>>> 'Project added' in browser.contents
True
Let’s create one task in each of the projects we created. We create a task in Project Two:
>>> browser.getLink('Add new Task').click()
>>> browser.getControl(name='form.start_date').value == today
True
>>> browser.getControl(name="form.title").value = u'Define requirements'
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl("Add task").click()
>>> 'Task added' in browser.contents
True
And we also create a task in Project One:
>>> browser.getLink('Projects').click()
>>> browser.getLink('Project One').click()
>>> browser.getLink('Add new Task').click()
>>> browser.getControl(name="form.title").value = u'A simple task'
>>> browser.getControl(name='form.end_date').value = tomorrow
>>> browser.getControl("Add task").click()
>>> 'Task added' in browser.contents
True
Let’s now log some time in both tasks. First we add a couple of logs in the task we’ve just created:
>>> browser.getLink('A simple task').click()
>>> 'Task view: A simple task' in browser.contents
True
>>> browser.getControl(name='form.description').value = u'Write document'
>>> browser.getControl(name='form.date').value = yesterday
>>> browser.getControl(name='form.hours').value = u'6'
>>> browser.getControl(name='form.remaining').value = u'2.4'
>>> browser.getControl('Add log').click()
>>> 'Log added' in browser.contents
True
>>> browser.getControl(name='form.description').value = u'Close this task'
>>> browser.getControl(name='form.date').value = today
>>> browser.getControl(name='form.hours').value = u'3'
>>> browser.getControl(name='form.remaining').value = u'0'
>>> browser.getControl('Add log').click()
>>> 'Log added' in browser.contents
True
And now we add a couple of logs in the task we created for Project Two:
>>> browser.getLink('Projects').click()
>>> browser.getLink('Project Two').click()
>>> browser.getLink('Define requirements').click()
>>> 'Task view: Define requirements' in browser.contents
True
>>> browser.getControl(name='form.description').value = u'Meeting with Joe'
>>> browser.getControl(name='form.hours').value = u'4'
>>> browser.getControl(name='form.remaining').value = u'2'
>>> browser.getControl('Add log').click()
>>> 'Log added' in browser.contents
True
>>> browser.getControl(name='form.description').value = u'Finish document'
>>> browser.getControl(name='form.hours').value = u'3'
>>> browser.getControl(name='form.date').value = tomorrow
>>> browser.getControl(name='form.remaining').value = u'0'
>>> browser.getControl('Add log').click()
>>> 'Log added' in browser.contents
True
We are now ready to run the Logs report. This report queries the log entries in a range of dates filtering by project and user. The results are presented in a flat table.
So, to get to the report screen, we click on the Reports tab and then on the Logs report link:
>>> browser.getLink('Reports').click()
>>> browser.getLink('Logs report').click()
>>> 'Run logs report' in browser.contents
True
The from and to dates are set to today:
>>> browser.getControl(name='form.from_date').value == today
True
>>> browser.getControl(name='form.to_date').value == today
True
All users and all projects are selected by default:
>>> browser.getControl(name='form.project_or_client').value
['all']
>>> browser.getControl(name='form.user').value
['all']
So, if we run the report with those options, we should get only today’s logs:
>>> browser.getControl('Submit').click()
>>> 'Write document' in browser.contents
False
>>> 'Close this task' in browser.contents
True
>>> 'Meeting with Joe' in browser.contents
True
>>> 'Finish document' in browser.contents
False
If we set the from date to yesterday, we will also get yesterday’s logs:
>>> browser.getControl(name='form.from_date').value = yesterday
>>> browser.getControl('Submit').click()
>>> 'Write document' in browser.contents
True
>>> 'Close this task' in browser.contents
True
>>> 'Meeting with Joe' in browser.contents
True
>>> 'Finish document' in browser.contents
False
If we rescrict the report to Project One:
>>> browser.getControl(name='form.project_or_client').value = \
... ('/app/projects/project-one',)
>>> browser.getControl('Submit').click()
>>> 'Write document' in browser.contents
True
>>> 'Close this task' in browser.contents
True
>>> 'Meeting with Joe' in browser.contents
False
>>> 'Finish document' in browser.contents
False
The report can be downloaded in CSV format. Let’s select all projects again, resubmit the report and download the CSV file:
>>> browser.getControl(name='form.project_or_client').value = ('all',)
>>> browser.getControl('Submit').click()
>>> browser.getLink('Download CSV').click()
>>> csv = ('User,Project,Task,Description,Date,Hours\r\n'
... 'user,Project One,A simple task,Write document,%s,6\r\n'
... 'user,Project One,A simple task,Close this task,%s,3\r\n'
... 'user,Project Two,Define requirements,Meeting with Joe,%s,4\r\n'
... ) % (yesterday, today, today)
>>> browser.contents == csv
True
Another report is the Tasks report, which queries the tasks worked by a user (or all of them) in a range of dates. The results are presented ordered by project and by task, showing the total amount of hours used for each task and listing the users that logged some time in that task. A sum of hours worked in the project during the period being queried is also displayed.
Let’s go the the Tasks report page:
>>> browser.open('http://localhost/app')
>>> browser.getLink('Reports').click()
>>> browser.getLink('Tasks report').click()
>>> 'Run tasks report' in browser.contents
True
The from and to dates are set to today:
>>> browser.getControl(name='form.from_date').value == today
True
>>> browser.getControl(name='form.to_date').value == today
True
All users and all projects are selected by default:
>>> browser.getControl(name='form.projects').value
['all']
>>> browser.getControl(name='form.user').value
['all']
So, if we run the report with those options, we should get only today’s tasks, which in this case are both tasks we created:
>>> browser.getControl('Submit').click()
>>> 'A simple task' in browser.contents
True
>>> 'Define requirements' in browser.contents
True
And if we restrict the report to Project Two, only Define requirements will be in the results:
>>> browser.getControl(name='form.projects').value = \
... ('/app/projects/project-two',)
>>> browser.getControl('Submit').click()
>>> 'A simple task' in browser.contents
False
>>> 'Define requirements' in browser.contents
True