This homework is composed of two parts.
Part 1: Due on Friday May 1, 11am. Submit to https://www.crowdgrader.org/crowdgrader/venues/view_venue/4633#
Part 2: Due on Thursday May 7, 11pm Submit to https://www.crowdgrader.org/crowdgrader/rubric/view/4634#
Both parts will be submitted to CrowdGrader; the URL will be provided as the submission deadline approaches.
You can see an example of both parts at http://lucadealfaroucsc.pythonanywhere.com/hw4
NOTE: This is a complex homework. It is very unlikely that you can complete either part in one day, even if you are an experienced developer. Start working on it asap.
First, go into your py4web directory and patch py4web to the latest version. We need this unfortunately.
cd py4web
git pull
Then, get the starter code.
cd apps
git clone https://luca_de_alfaro@bitbucket.org/luca_de_alfaro/hw4_starter.git hw4
This will create a hw4 directory where you can work.
In this assignment, you need to build an address book.
All the pages of this assignment (for Part 1 and Part 2) should be accessible only for logged in users.
You can find the documentation on the login system here; here is a short summary.
To force users to login before they access a controller, use
@action.uses(auth.user, ...)
To get the user, do:
auth.current_user
To get the email (or first name, or last name) of the current user:
auth.current_user.get('email')
Furthermore, when you create a database table, you can specify default values for its fields. So, if you want to add the user email to any data created by the user, you can do:
def get_user_email():
return auth.current_user.get('email')
db.define_table('person',
Field('user_email', default=get_user_email),
...)
db.person.id.readable = False
db.person.user_email.readable = False
You need to define the default value to be a callable function, because you want it to be called every time you create a row in the table person. You don't want to have the same default (the same user) for the life of your web application!
You have to build an app consisting of the pages:
And of the buttons on the left, so the ones dealing with First Name and Last Name. You can leave the phone numbers part blank, as in:
The buttons should enable you to add a contact, edit a contact, and delete a contact. Make sure the form is signed (specify csrf_session=...) and make sure the URLs for the delete buttons are signed.
You can solve this using only one database table. The table must have fields for the first and last names, of course, but also (see above in Auth section) a field called user_email
to store the email of the user who created the contact. So if jane@example.com created three contacts, and jack@example.com created two, you have in total in the database (at least) five contacts, three with user_email
field jane@example.com, and two with user_email
equal to jack@example.com.
Now the important thing is, you should only show to each user their own contacts! So when jane@example.com is logged in, you should just show the contacts with field user_email
equal to jane@example.com; and similarly for jack and every other user.
You must ensure, by checking explicitly, that users are only editing or deleting their own contacts.
In Part 2, you have to build out the portion of the application that deals with phones. If you could not get Part 1 to work properly, please look at the assignments you grade in CrowdGrader to see how others have solved Part 1; you need to fix Part 1 as part of doing Part 2 (the rubric for Part 2 extends the one for Part 1).
Each phone has:
A contact can have multiple phone numbers, so you need to use a separate table (this is a requirement, it is NOT an option, so don't even think of using the same table), with a many-to-one relation to the table of contacts.
Displaying the contacts with their phone numbers
When you display a contact, you need to display the list of phone numbers and their names on the sides of the contact, like so:
Hint: one easy way to generate the above table is as follows. TSuppose you loop over the rows of the contacts for a user:
for row in db(...).select(...):
The "problem" is that the rows above contain the contact information, but not the phone numbers and their names. The phone numbers are in another table! So you must, for each row, go read the phone numbers and their names from the other table, and somehow augment the row with that information. You can use the following method to do so:
# For every row that is one of the contacts.
# We get all the rows out as a list (see below).
rows = db(...).select(...).aslist():
# and then we iterate on each one
for row in rows:
# Here we must fish out of the db the phone numbers
# attached to the contact, and produce a nice string like
# "354242 (Home), 34343423 (Vacation)" for the contact.
s = ...
# and we can simply assign the nice string to a field of the row!
# No matter that the field did not originally exist in the database.
row["phone_numbers"] = s
# So at the end, we can return "nice" rows, each one with our nice string.
return dict(rows=rows, ...)
The reason you need to use the .as_list method is that you will need to iterate on the rows twice: once in the controller, in the for loop above, to add the phone number information, and once in the template, to generate the table. An iterator, such as the one returned by db.select(...), can be iterated only once. So we transform the results into a list, which allows multiple iterations.
The one catch if you use the .as_list method is that each row is then a dictionary, and the fields need to be accessed via row["field"]
rather than row.field
.
Editing the phones
Next to each row of phone numbers are buttons to edit the numbers associated with a contact.
Clicking on one should take you to a page that looks like this:
Here, the Back button takes you again to "Your Address Book". The button to add a phone number enables you to add another phone number (with its name) for the contact. The button to edit a phone number enables to edit it, like so:
(and the form you need to use for inserting is similar). You need also to implement a signed button to delete a phone for a contact. All forms and buttons need CSRF protection via signatures.
You must ensure, by checking explicitly, that users are only editing or deleting phone numbers for their own contacts.
The difficulty, in adding or deleting phone numbers for a contact, is that you need to make sure that the data you add to the table is linked, via the foreign key, to the appropiate contact in the table of contacts.
Previously, for a contact, you have been able to create a form via (assuming you call the table 'person'):
form = Form(db.person, csrf_session=session, formstyle=FormStyleBulma)
In Part 2, however, the information in the form does not directly correspond to what you write to the database. To the user, you should only ask for Phone and Kind (or Name, as you like). In the database, you must also store the foreign key.
So it may be easier for you to create a form synthetically, independently from a database table. The documentation for forms can be found here: http://py4web.com/_documentation/static/index.html#chapter-10. An easy way is to do:
form = Form([Field('phone'), Field('kind')], csrf_session=session,
formstyle=FormStyleBulma)
(this is very similar to the example at the beginning of that chapter). If you need to specify initial data, you can do:
form = Form([Field('phone'), Field('kind')],
record=dict(phone="123 456", kind="Mobile"),
csrf_session=session,
formstyle=FormStyleBulma)
so the record= specifies the values that should be in the form for the user to modify (you need this to allow editing).
In either case, if form.accepted
is True, you can then find the phone in form.vars['phone']
, and so forth. Once you have those variables, you can then use database commands to directly insert or update (see here and here) the data in the table that stores phone numbers.