CS0001 Project task list
Phases and tasks
You should work on your project in phases, completing every task in each phase before moving on to the next. Apart from phase 0, you can tackle the tasks within each phase in any order you like.
If you're not an experienced programmer, we don't expect you to get much further than phase 3 before you run out of time.
Phase 0 tasks
- 0-GET Get the source code
- 0-RUN Get the app running and view it in a browser
- 0-CHANGE Make a change to a template and see it appear
Phase 1 tasks
- 1-TEMPLATE Add a new template and route to the app
- 1-ADD Add more data to the form and save it to the database
- 1-VALID Add basic data validation
- 1-STYLE Style your editor just how you like it
Phase 2 tasks
- 2-EDIT Allow for editing of a buggy by preloading its current values into the form
- 2-COST Calculate and save the game cost of the buggy
- 2-RULES Add validation according to the game rules
Phase 3 tasks
- 3-AUTOFILL Add auto-fill to the buggy form
- 3-MULTI_CREATE Allow different buggies to be created
- 3-MULTI_EDIT Allow different buggies to be edited
- 3-DEL Allow buggies to be deleted
- 3-ENV Switch between ports using environment variables
- 3-FLAG Display the pennant graphically
- 3-TESTS Write some tests
Phase 4 tasks
- 4-API Use the race server API for submitting the buggy data
- 4-USERS Add users (and sessions) so you know who is editing a buggy
- 4-REGISTER Make it possible to create new users
- 4-OWNER Ensure each buggy belongs to a user
- 4-PASS Add password protection to the users
Phase 5 tasks
- 5-VIZ Visual representation of the buggy
- 5-RESET Password reset
- 5-RACELOG Store a history of race results for the buggies in your app
- 5-ADMIN Add admin capabilities to superusers
Phase 6 tasks
Phase 0 tasks
The starter source code for the buggy editor app you're going to be developing already exists in your Gitlab repository: you just need to clone it to a place where you can edit it and run it.
CS0001 Students:
Follow the Help for Task 0-GET in the Getting Started subsection under the Term 3 Buggy Racing Project section on Moodle for further information about how to clone the buggy editor Flask application.
Once you have completed this task, record your notes about how you completed it by clicking on the "Add text" button next to this task. This will make generating your report for your final submission much easier!
Remember to consult the guidance on writing good task texts in the marking rubric for CS0001 when you write your notes.
CS1999 Resitting Students:
Follow the Help for Task 0-GET in the Getting Started section on Moodle for further information about how to clone the buggy editor Flask application.
Once you have completed this task, record your notes about how you completed it by clicking on the "Add text" button next to this task. This will make generating your report for your final submission much easier!
Remember to consult the guidance on writing good task texts in the marking rubric for CS1999 when you write your notes.
- If you're not comfortable finding your way around the file system yet, see the CompSci Superbasics.
You need to be able to deploy the buggy editor so you can see it running in your web browser. The buggy editor is written in python and runs in the Flask web server. You can see the code for the web server in the file app.py.
CS0001 Students:
Follow the instructions in the Getting Started subsection under the Term 3 Buggy Racing Project section for further information about how to run the buggy editor Flask application.
CS1999 Resitting Students:
Follow the instructions in the Getting Started section for further information about how to run the buggy editor Flask application.
-
The Buggy Editor Python code has dependencies on a number of libraries. The dependencies are documented in the
requirements.txtfile. If you have developed other Python code on your machine which requires specific versions of libraries to be installed viapip, you may want to do the installation using virtual environments. -
If you get an error indicating that something else is running on port 5001, you will need to use a different port (e.g. 5002). To do this, you can change the port to another number (e.g. 5002). The port that
app.pyruns on is specified near the bottom ofapp.pyin theallocated_portvariable. -
A more advanced solution would be to make use of Environment variables and change the
BUGGY_EDITOR_PORTenvironment variable to the desired port. You will use environment variables in task 3-ENV to switch between ports. -
When you first run
app.pyyou'll get the/route. By looking inapp.py, you can see it invokes theindex.htmltemplate. Read more about how Flask works by looking at the tech notes or the Term 2 tutorials material. -
You can see the webserver's activity in the terminal, and the result of its actions in the browser. This will be useful for debugging in future tasks.
-
When you want to stop the program running, click into the terminal where the webserver is running and press Ctrl-C. This interrupts the server and halts the execution of the program. If you go to http://localhost:5001 in your web browser now, you'll see a message saying you can't connect to the server. This is because you've killed it: it's no longer there.
In Flask, you can use the Jinja templating language to render an HTML page when a user makes a request. Jinja allows you to create templates with static HTML and dynamic features such as if statements and for loops.
In this task you must demonstrate that you can change a Flask template so the change appears in the browser.
Edit some text in templates/index.html and confirm you can see the changes when you access the corresponding page in the browser.
-
Read more about how templates work by looking at the tech notes.
-
If you change the template but the page in the browser does not change when you refresh it... what might be happening? What can go wrong here? Don't underestimate the importance of doing a simple change to the template before adding more complex logic!
Phase 1 tasks
1-TEMPLATE Add a new template and route to the app
Building a buggy is expensive! The cost of buggy components is currently only available on the race server.
We need a way to return the cost information to the Buggy Editor user when they request it.
Your webserver uses routes to choose which function to run to generate a response to a request. You can see that in app.py there are already a number of routes (e.g. @app.route('/buggy')), each of which renders a template, which results in HTML being sent back to the client (browser).
Create a new template (templates/info.html) to display the cost information and create a new route that matches /info to render the template info.html.
There are a number of options for retrieving the cost data from the race server, ranging from simple to more complex. These are described below. Choose whichever option you feel confident with but do try to stretch yourself. Remember: a portion of the project marks are reserved for completing extension tasks.
Core Task
Create a route called /info that renders a template with a button that links to the specification page on the buggy race server.
Level 1 Extension Task
Create a route called /info that renders a template with the contents of the specification page on the buggy race server duplicated (hardcoded) as HTML.
Level 2 Extension Task
Create a route called /info that renders a template with the specifications read directly from the JSON file version hosted on the buggy race server.
This is a better solution to hard coding values in Python or the template but more tricky. If you do it this way, you need to decide when to read the file; your decision will depend on how efficient you need your program to be.
-
Make sure the template extends the "base.html" in
info.htmlas mentioned in the Phase 1 Tech Notes. Why is this important? -
Hint for Level 1 Extension Task: You can view the the HTML for the specification tables using your browser's developer tools (under the "Inspector" tab in Firefox or "Elements" tab in Chrome). This can be copied and pasted directly into
info.html. -
Hint for Level 2 Extension Task: You can use the Python requests library to fetch the JSON from the specifications page on the race server:
buggy_json = requests.get("https://rhul.buggyrace.net/specs/data/types.json").json()
The only data being collected by the buggy form is the number of wheels. You need to add more fields to allow the user to "power up" their buggy with extra features such as those specified in the buggy specification.
Core Task
Update the buggy form to support the four fields already in the database and update the /buggies route to display these fields in the table.
One of these fields (i.e. qty_wheels) is already fully supported in the buggy editor source code we provided, including adding the value qty_wheels to the database. However, the remaining three have columns in the buggies table in database but the buggy form and app.py does not support updating their values for the buggy.
Level 1 Extension Task
Update the buggy form to support at least 10 fields, including the four fields for which support was added as part of the Core Task, and update the /buggies route to display these fields in the table.
Level 2 Extension Task
Update the buggy form to support at all 19 fields, including the four fields for which support was added as part of the Core Task, and update the /buggies route to display these fields in the table.
-
Start by adding the
flag_colorto thebuggy-form.htmltemplate. Investigate HTML tags for accepting data in forms: in particular, consider how radio and select might help you. -
Look at how the
/newroute inapp.pyretrieves the number of wheels and saves this information to the database, follow this pattern for retrieving and saving theflag_color. -
The database definition is held in
init_db.py. It holds the column name (and type) for each item. We've helped you by including the flag colour in the database definition so the column already exists. Note that the buggy specification and therefore the database uses the American English spelling of colour (i.e. "color"). You should do the same in your code to ensure compatibility. -
To view the contents of the database, use the
SQLite ViewerVSCode extension by Florian Klampfer. You will need to press the "refresh" icon to see any updates to the database. -
If you receive an "Error in update operation" message when you try to save data, check the relevant Tech Notes to help you identify the cause.
-
Refer to the Phase 1 Tech Notes for a step by step guide to completing this task. This link may also help you with using selections: Selection.
-
Hint for Level 1 Extension Task: Adding support for a buggy feature that isn't already in the database is more tricky because you will need to add a new column in the database for this item.
-
Hint for Level 1 Extension Task: This link may help you with using radio buttons: Radio Buttons.
If you enter an invalid value, like "banana", for the number of wheels when making a buggy, you should get an error. At the moment, you do not.
Core Task
Add either server-side OR client-side validation for the qty_wheels field and the flag_pattern field. The qty_wheels field must only accept integer inputs and gracefully reject non-integer inputs. The flag_pattern field must only accept the valid values stated in the buggy specification and gracefully reject invalid values.
Level 1 Extension Task
Add server-side AND client-side validation for the qty_wheels field and the flag_pattern field. The qty_wheels field must only accept integer inputs and gracefully reject non-integer inputs. The flag_pattern field must only accept the valid values stated in the buggy specification and gracefully reject invalid values.
Why isn't only using client-side JavaScript validation acceptable?
Level 2 Extension Task
Complete Level 1 Extension Task AND add server-side AND client-side validation for one or more additional buggy form fields of your choice. You should specify your chosen field in your report.
-
The incoming data sent by the form is always a string, so maybe use the Python isdigit() method in
app.pyto validate the value inputted as the quantity of wheels. -
You may also need to "clean" the string first? Python has a strip() method which strips off all leading and trailing spaces.
-
Consider how you will inform the user that an input is invalid. What should appear on the webpage?
Your editor looks identical to the basic one. Can you make it look prettier?
Use CSS and HTML to make it look beautiful and different from everybody else's. If you haven't used CSS before, don't worry, just choose to do something simple like changing the colour of the text.
Core Task
Make at least three changes to the HTML (layout) or CSS (using the style attribute for a HTML tag). You should state the changes you make in your report.
Note that using the style attribute for a HTML tag is bad practice because it can lead to maintenance and organisation issues in the long run.
Level 1 Extension Task
You can change the HTML as much as you need to get the look/layout you want, but remember the separation of concerns. This means your HTML should really only be describing the content of your web pages, while the CSS deals with its presentation. It's common to use the class attribute as the bridge between the two. Your editor is already using a style sheet - it's in static/app.css - so you should probably edit that (at least to start with).
Add at least two of your own CSS classes to the app.css stylesheet (and therefore, class attributes to the HTML). You should state the classes you've added to your report.
To complete this task, you also must have not used the style attribute in any of the HTML tags.
Level 2 Extension Task
Use media queries to make the app responsive to, for example, narrow or mobile screens OR add support for light/dark mode OR use a framework such as Tailwind or Bootstrap.
To complete this task, you also must have not used the style attribute in any of the HTML tags.
-
If you make changes to the stylesheet but your browser doesn't appear to load it (but keeps using the old one) you might need to empty your browser's cache. See the CS0001 tech notes.
-
If you want to add images, those should probably go in the
staticdirectory (the same place as the CSS). Make sure the file size of any images is as small as possible. Why? -
No matter how pretty, how beautiful, how fabulous you make your editor, remember that you must not compromise its usability or accessibility. For example, never put text directly on top of backgrounds that make it hard to read or distinguish. Make sure things the user should interact with look different from things they cannot. Consider how your site works in different form factors.
-
There are guidelines for calculating acceptable contrast between text/background colour combinations.
-
Accessibility is a big topic that is integral to web design. See MDN on accessibility for a good starting point. If you have any aspirations as a web developer, you have professional, ethical, and legal obligations to understand how to do this well.
-
Hint for Level 2 Extension Task: Look at W3School's tutorial on responsive web design.
Phase 2 tasks
As we only have one buggy, we could use the buggy-form.html template to allow both creation and editing of a buggy. This means we need to display the current values of the buggy when the user navigates to "Make Buggy".
When you choose to make a buggy, you should see the existing buggy's current values displayed from the database.
Core Task
Edit buggy-form.html so that at least 75% of the fields supported by your buggy editor are pre-filled with the buggy's values from the database. Note that if your buggy editor supports fewer than five fields, all fields must be pre-filled with the buggy's values from the database.
Level 1 Extension Task
2-EDIT does not have a Level 1 Extension Task.
Level 2 Extension Task
2-EDIT does not have a Level 2 Extension Task.
-
This task should be completed in the
/newroute inapp.py. -
You need to read the values for the existing buggy record out of the database with
SQL SELECT. Take a look at theshow_buggies()method inapp.py, this illustrates how to retrieve the buggy data from the database. -
Use the HTML input tag's value attribute to display the returned buggy information in the
buggy-form.htmltemplate. -
If you have used an HTML select tag for other buggy data such as the
flag_coloryou will need to handleselecttags'optionvalues differently: you'll need to use theselectedattribute too, combined with Jinja if statements.
The cost of the buggy (worked out using the game rules) affects whether it can be entered in some races. Add the cost to the database and display it in the buggy.html template.
In app.py change the /new route to calculate the cost of the buggy using the cost information from the buggy specifications data.
Core Task
Use two buggy fields (e.g. type of tyres and number of wheels) to calculate the cost of the buggy using the value stored in the database and prices of each item that are hardcoded in the calculation. You should store the total cost of the buggy in the database.
This is the simplest solution however there are some downsides to this, what do you think these are?
Level 1 Extension Task
Use three buggy fields to calculate the cost of the buggy using the values stored in the database. Note that prices of each item must NOT be hardcoded in the calculation. Instead, they can be read from a JSON download of the specification, from a hardcoded Python dictionary, or loaded directly from the JSON on the race server. You should store the total cost of the buggy in the database.
Level 2 Extension Task
Complete Level 1 Extension Task for four buggy fields rather than three.
-
Add a new integer column called
total_costto thebuggiesdatabase table and store the total cost there. -
Edit the
buggy.htmlto display the cost of the buggy when the user clicksShow Buggy -
Hint for Level 1 Extension Task: You can use the Python requests library to fetch the JSON from the buggy specifications data page on the race server. This option is more challenging but has the advantage of the data always being up to date. If you choose to do this, you can access individual item costs in a similar manner to the following:
buggy_json = requests.get("https://rhul.buggyrace.net/specs/data/types.json").json()tyres_cost = buggy_json['tyres'][request.form['tyres_type']]['cost']num_tyres = int(request.form['qty_tyres'])total_tyres_cost = tyres_cost * num_tyres
There are some configuration options that are not allowed (for example, the quantity of wheels must be even).
Add the specified game rules to your validation.
Note: this task is different to 1-VALID. 1-VALID is about ensuring that the values entered into the buggy form is sensible and reasonable. For example, entering text into a field that expects a numeric value is not sensible so it would need to be prevented. Meanwhile, this task is about ensuring that the buggy you create adheres to the rules of the buggy racing game.
Core Task
Ensure that your buggy editor enforces the rule that qty_wheels must be even.
Level 1 Extension Task
Complete Core Task and also ensure that your buggy editor enforces one of these rules: qty_tyres must be equal to or greater than the number of wheels OR flag_color_secondary must be different from flag_color (unless pattern is plain)**.
Level 2 Extension Task
Complete Level 1 Extension Task except that your buggy editor must enforce both of these rules (so three rules in total): qty_tyres must be equal to or greater than the number of wheels AND flag_color_secondary must be different from flag_color (unless pattern is plain).
-
Consider: How and where are you going to report the problems? Do you save the record anyway, even if there are violations? If the game rules changed, what would you need to do to your program?
-
Hint for Level 1 Extension Task: You'll need to do some validation based on more than one field too.
-
Hint for Level 1 Extension Task: If your buggy editor doesn't yet support some of the items you are applying rules to then you may want to add them in so you can test your rule logic. Use the hints in 1-ADD to help.
-
Hint for Level 2 Extension Task: Implementing the other games rules are formative (i.e. they will not yield any additional marks) but you should attempt to do so anyway. They can be found in bold text in the "Description/rules" column on the buggy specification page.
Phase 3 tasks
3-AUTOFILL Add auto-fill to the buggy form
In earlier tasks you added items to your buggy form. However, the user may not want to manually input values into each field when creating a buggy. It would be good to auto-populate any empty fields with values to create a buggy.
The buggy specification page lists all the possible items on a buggy alongside the set of default and possible values. Add a button that automatically inputs values for any field that has not been filled in by the user in your buggy-form.html.
Note: this task is different to 2-EDIT. 2-EDIT is about loading into the form the details of the existing buggy stored in the database to allow editing. Meanwhile, this task is about automatically filling the empty fields in the form with default values to remove the need for the user to fill in every field.
Core Task
Add a button to the buggy form that auto-fills all the fields supported by the form (but not necessarily all possible specification items specified on the buggy specification page) with the same hardcoded and valid values each time.
Level 1 Extension Task
Add a button to the buggy form that auto-fills all the fields supported by the form (but not necessarily all possible specification items specified on the buggy specification page) with random but valid values.
Level 2 Extension Task
Add a button to the buggy form that auto-fills all the fields supported by the form (but not necessarily all possible specification items specified on the buggy specification page) with random but valid values within a specified cost limit. This will be useful when we run races which have a maximum buggy cost limit for entry!
-
If you don't try to make autofill work within a cost, this task is a lot simpler - then it's really just loading a set of pre-defined values. That's not so helpful for the user though.
-
You can do this server-side (in
app.py) or client-side (with JavaScript). What are the pros and cons of each option? -
If you are doing this client-side with JavaScript, you could create a JavaScript function that uses the
getElementByIdfunction to retrieve each field in the form individually by itsidand set its value. For example:document.getElementById("qty_wheels").value = 4will populate the field with theidofqty_wheelsto 4. Note that theidattribute of an element is different to thenameattribute so you will need to ensure that each field in your form also has itsidattribute set. You can then add a button to the form that calls this JavaScript function when the button is clicked. -
If you are doing this server-side in
app.py, you can add another button to your HTML form. HTML supports forms having more than one submit button and you can detect which of the two (or more) buttons the user pressed as long as you give them different names using thenameattribute. This is the same attribute that you are already using to distinguish between other inputs use in the form. Then, inapp.py, when the form comes in you can test to see for the presence of those (only one will have data that is not empty) usingif request.form.get("name"), wherenameis whatever name you gave the button that was clicked. -
Hint for Level 1 Extension Task: To generate the random buggy configuration, you will need to randomly choose values. See Python's
randomlibrary and perhaps itsrandintfunction (or JavaScript'sMath.random()). -
Hint for Level 2 Extension Task: If you try to make autofill work within a specified cost, this can become a very difficult problem. How do you decide what values to pick for each setting? You may have to use a loop with a specified number of iterations to keep trying until the loop ends or a valid configuration is found.
-
Hint for Level 2 Extension Task: It may be helpful to improve on a configuration with each iteration of the loop rather than create an entirely new configuration with each iteration.
-
Hint for Level 2 Extension Task: Sometimes it might be impossible to make a legal buggy within the stated cost. How will you know? How will you report that to the user?
3-MULTI_CREATE Allow different buggies to be created
The default app only lets you save one buggy. You should be able to save different buggies so you can switch between them.
Modify the app to create a new buggy (so that there can be more than one).
Core Task
Update the buggy editor to support the creation of multiple buggies. This means that app.py should been updated to include an SQL statement to add buggies to the database.
Level 1 Extension Task
Complete Core Task and also ensure that the template rendered when accessing the /buggies route displays the details of all buggies in the database.
Level 2 Extension Task
Complete Level 1 Extension Task and also ensure that the /json route displays the details of all buggies in the database in JSON format.
-
You're going to need to change the operation on the database, because up until now you've been updating the (single) record in the database: now you will sometimes be inserting a (new) record. How will you decide which to do?
-
Hint for Level 1 Extension Task: You will need to update
app.pyto retrieve all rows in the database instead of one. -
Hint for Level 1 Extension Task: You will need to use a Jinja loop in the
buggy.htmltemplate. -
Hint for Level 2 Extension Task: To get started, you will need to update the SQL statement in
/jsonto retrieve all rows in the database instead of one.
3-MULTI_EDIT Allow different buggies to be edited
The default app only lets you save one buggy. Once you have updated the app to allow the creation of multiple buggies (see 3-MULTI_EDIT), you should be able edit the different buggies.
Modify the app to be able to edit a chosen buggy.
Note: this is not a small change and is designed to break your existing implementation of 2-EDIT - this is normal. Therefore, if 3-MULTI_EDIT is implemented successfully such that your buggy editor now allows the editing of different buggies, we will award full marks for 2-EDIT too. If 3-MULTI_EDIT is not implemented successfully, we will award full marks for 2-EDIT based on the criteria specified in 2-EDIT.
Core Task
Update the buggy editor to support the editing of multiple buggies. This means that app.py has been updated to include an SQL statement to update buggies in the database based on a given ID rather than always updating the same one.
Level 1 Extension Task
Complete Core Task and also ensure the /buggies route displays a link for each buggy that directs them to the buggy form that is pre-populated with the specification for that specific buggy.
Level 2 Extension Task
3-MULTI_EDIT does not have a Level 2 Extension Task.
-
When editing a buggy, you'll need to specify which buggy a request relates to. You can do that in Flask by incorporating its
idinto the route. -
Hint for Level 1 Extension Task: To add the link for each buggy, you will need to use Jinja to incorporate its
idinto the link to the route.
You have multiple buggies: you should be able to delete ones you don't want.
Modify the app to add a delete route that deletes a chosen buggy.
Core Task
Update the buggy editor to support the deletion of buggies. This means that app.py has been updated to include an SQL statement to delete buggies in the database based on a given ID.
Level 1 Extension Task
Complete Core Task and also ensure the /buggies route displays a link for each buggy that directs the user to the delete route that deletes that specific buggy.
Level 2 Extension Task
Complete Level 1 Extension Task and also ensure that the deletion is performed with a DELETE HTTP request.
-
When editing a buggy, you'll need to specify which buggy a request relates to. You can do that in Flask by incorporating its
idinto the route. -
You almost certainly need to use the
WHEREclause in your SQL. What happens if you don't? -
It's good practice to use the HTTP method
DELETEhere (or perhapsPOST, butDELETEis better). More importantly, you should not use the HTTP methodGETfor this route. Why not? -
Hint for Level 1 Extension Task: To add the link for each buggy, you will need to use Jinja to incorporate its
idinto the link to the route.
When you run your editor, it's always runs on port 5001, because the keyword argument port=allocated_port is being set when app.run() is called (at the bottom of app.py). allocated_port is set to the value of the BUGGY_EDITOR_PORT environment variable or, if this is not set, to 5001. You should not need to edit app.py when you wish to change the port your buggy editor runs on.
An environment variable is a variable whose value is set on the command line, outside of the running application, typically through functionality built into the operating system. An environment variable consists of a name/value pair, for example `BUGGY_EDITOR_PORT=5001.
Change your program to use the environment variable BUGGY_EDITOR_PORT to switch ports between runs of your buggy editor application.
Note: this must be done via environment variables to achieve marks for this task.
Core Task
Update your buggy editor to allow the BUGGY_EDITOR_PORT environment variable to be set on the command line and your editor uses it correctly.
Level 1 Extension Task
Update your buggy editor to allow the BUGGY_EDITOR_PORT environment variable to be set via a .env file and your editor uses it correctly.
Level 2 Extension Task
3-ENV does not have a Level 2 Extension Task.
-
To create an environment variable for
BUGGY_EDITOR_PORTand set its value to another port (e.g. 5002), stop your Flask server and go to the Terminal window:- If you are working on a Mac type
export BUGGY_EDITOR_PORT=5002 - If you are working on a command prompt terminal, type
set BUGGY_EDITOR_PORT=5002 - If you are working on a PowerShell terminal type
$Env:BUGGY_EDITOR_PORT = 5002
- If you are working on a Mac type
-
If everything has worked correctly, when you restart
app.pyyour buggy editor should run on the new port. Note that you may need to convert the environment variable to a numerical data type inapp.pybefore using it. Why? -
Now stop the Flask server and change the environment variable back to 5001. When you restart
app.pyyour buggy editor should run on port 5001 again. -
Hint for Level 1 Extension Task: To access a
.envfile inapp.py, you can use theload_dotenvfunction, which can be imported from thedotenvlibrary. -
Hint for Level 1 Extension Task: How can you handle clashes if the
BUGGY_EDITOR_PORTvalue is set via the Terminal window and thenload_dotenvis used to access the value from a.envfile? -
This task is really introducing you to the important concept of environment variables. These are settings that are part of the environment in which the program runs instead of being inside the program. All general programming languages provide a mechanism for accessing these from within a program's source code.
-
This is just one example of how some settings should not be hard-coded. Other classic examples, for programs in general, are configuration settings and passwords. This is really about understanding what is the same and what is different when you run different instances of your program. This is especially important when you think about how source code is shared, and what should (and should not) go into version control.
The user's choices for the colours and pattern of the buggy's pennant (flag) is visual information but is probably displayed as text.
Show a graphical representation of the pennant in the browser.
Core Task
Update your buggy editor to display a pennant graphic for each buggy in the database on the UI. At least one pattern must be supported, and the flag pattern/colours must be based on that buggy’s specification in the database.
Note: the pattern must not be plain, as this is essentially done by default.
Level 1 Extension Task
Update your buggy editor to display a pennant graphic for each buggy in the database on the UI. At least three patterns must be supported, and the flag pattern/colours must be based on that buggy’s specification in the database.
Note: the three patterns must not be plain, as this is essentially done by default.
Level 2 Extension Task
Update your buggy editor to display a pennant graphic for each buggy in the database on the UI. All six patterns (including plain) must be supported, and the flag pattern/colours must be based on that buggy’s specification in the database.
-
There are several of ways of doing this: but you can't simply have pre-prepared graphics for every possible flag because there are too many combinations.
-
Consider whether you are going to display the pennant before or after the form is submitted? Both are acceptable but what difference does your decision make?
-
Consider using CSS, SVG, or canvas to paint the flag (a small rectangle would be fine). What are the pros and cons of the different approaches?
-
Remember that the colours are specified in a very specific way: as CSS colours.
-
The tricky bit is creating the visualisation for each type of pattern. Our advice would be to remember that the patterns are a group of repeating elements.
You should be able to run automatic tests that confirm your app calculates the cost correctly... as well as other things too.
You probably know your app works because you've been running it (and playing with it) as you go along. But you should write some automated tests.
The idea is that you can run the same tests repeatedly, and get the same results every time. This way, if you make any changes to your code - adding a new feature, for example - you can be confident it does what you intended it to do and it doesn't break anything that is already working.
Core Task
Write at least one automated test for just this buggy that ensures the cost is calculated correctly for a specific buggy specification.
Level 1 Extension Task
Use the Flask Test Client to write at least one test that calls the API endpoint to retrieve a buggy and assert the buggy data in the response is as expected.
Level 2 Extension Task
Use the Selenium Web Driver to create a test that simulates a user using the browser to create a new buggy and assert the buggy is created successfully.
-
If you haven't already, you will need to refactor your code so you have a separate function for calculating the cost. The advantage of this is that you can then test this function in isolation from the rest of your code.
-
Python has libraries to make it easy to write unit tests, we suggest you use pytest. The documentation describes how you
assertthat what should happen, is happening.
Phase 4 tasks
Manually copying the JSON data from the editor to paste into the race server is clunky. You should use the buggy submission API instead.
Implement an upload to server feature for a selected buggy.
Core Task
Add support for uploading a selected buggy to the buggy race server where your username, API key, and API secret that are sent in the request to the API are hardcoded in the backend.
Level 1 Extension Task
Add support for uploading a selected buggy to the buggy race server where your username, API key, and API secret that are sent in the request to the API are inputted via a form in the frontend.
Level 2 Extension Task
Add support for uploading a selected buggy to the buggy race server where your username, API key, and API secret that are sent in the request to the API are obtained via environment variables.
-
You can find your API key and set your API secret in your API settings on the race server.
-
The value of
useris your username (in lowercase) that you use to login to the Buggy Race Server. -
You may want to test the API using curl or Thunder Client before you incorporate the code into your buggy editor.
-
For the Core Task, you will need form and send the send the API request in
app.pybecause that is where your hardcoded username, API key, and API secret are kept. -
Hint for Level 1 Extension Task: For Level 1 and 2 Extension Tasks, you can form and send the API request either in the frontend (using JavaScript) or in the backend (using Python).
-
If you complete this task in the backend, you'll need to import the
requestslibrary in order to construct thePOSTrequest used to submit the selected buggy to the server. Note that therequestslibrary is a different library to therequestlibrary provided as part of the Flask framework. A Flaskrequestobject contains the data that the client (for example, a web browser) has sent to your app's backend. Meanwhile, therequestslibrary allows your app to make HTTP requests to other sites, usually APIs.
Anyone can access your app and edit a buggy: ultimately only the person who created it should be able to. Add a login mechanism so you can tell the difference between users.
Add usernames to distinguish between users, and a mechanism for starting and ending a session (such as logging in and logging out). There is no need to implement user registration or password protection yet. You will do this in tasks 4-REGISTER and 4-PASS, respectively.
Core Task
Add support for logging in and logging out with sessions started/ended accordingly by creating/deleting cookies.
Level 1 Extension Task
4-USERS does not have a Level 1 Extension Task.
Level 2 Extension Task
4-USERS does not have a Level 2 Extension Task.
-
It is possible to implement user sessions without having any users in the database: just by inviting the user to type any username and keeping that for the session (that is: you could implement this without a database). That is, this is really about starting, maintaining, and ending a session. Here's the official Flask example that implements that.
-
Do not focus on implementing user registration and password checking just yet. These will be done in tasks 4-REGISTER and 4-PASS, respectively.
-
The most common way sessions are implemented is with cookies, which are tokens the server can use to recognise when requests are coming from the same client (browser). That's how Flask is doing it too. If you use your browser's developer tools to find the cookies, you can see how they change between requests as you use your app.
-
Remember to provide a way to end a session (i.e. log out) as well as starting it.
4-REGISTER Make it possible to create new users
Make it possible to create a new user to use when logging in with user logins, as per task 4-USERS.
Add a form to allow new users to register to use your buggy editor app.
Core Task
Add support for creating new users by creating a user registration form in the frontend and a new table users in the database. The backend should have a new route that processes the registration data and adds it to the database.
Level 1 Extension Task
4-REGISTER does not have a Level 1 Extension Task.
Level 2 Extension Task
4-REGISTER does not have a Level 2 Extension Task.
-
The page presumably needs to collect information needed for the user record. What fields do you need?
-
You can register users without any requiring them to set a password. Adding password protection is part of task 4-PASS so do not worry about it for now.
-
Task 4-USERS only really needed a username field in the login page. Once you have set up logging on with a username and password, you will need to update the login form. You can either do this now or when completing 4-PASS.
-
You'll probably need to use SQL to create a table
userswith an auto-incrementing primary keyidand string (VARCHAR()) fieldusername. -
What's the advantage of using an
idas the primary key instead of theusername(after all, you knowusernamehas to be unique)? -
What happens if someone tries to register a user with a username that already exists? You will will need a mechanism to stop this from happening.
-
You could use the
UNIQUEkeyword for theusernameandemailfields in theuserstable. This will stop a record from being added if it has a username or email address that is already in the database. How would you know this has happened?
Any user can edit any buggy but only the buggy's creator should be able to edit (or delete) it.
Associate buggies with a specific user and only grant access to buggies to the logged-in user.
Core Task
Add support for buggy ownership in the database by assigning each buggy to its owner upon creation. The frontend must only display the buggies that belong to the user currently logged on.
Level 1 Extension Task
Complete Core Task and also ensure that appropriate security mechanisms are in place to prevent users being able to view other users' buggies by simply changing the URL to include a buggy ID as a parameter.
Level 2 Extension Task
4-OWNER does not have a Level 2 Extension Task.
-
Maybe add a
user_idcolumn to the buggies table for the user's id - this is a foreign key. -
Ensure you use the foreign key in the
WHEREclause ofSELECTstatements. -
Remember to always set the
user_idcolumn when you create new buggy records. -
Should
user_idappear in the HTMLformwhen creating a buggy? What about when editing it? Perhaps you could use atype="hidden"to hide it? What's the problem with that? -
Hint for Level 1 Extension Task: You can check if the ID of the currently logged-on user matches the ID of the user that owns the buggy being accessed and redirect them to the login route if not. For example
return redirect("/login")
Currently anyone can login as any user they like with just a username... unless there is a password on user accounts.
Set and store a password for each user.
Core Task
Add support for password protecting user accounts. The password must be hashed with a unique salt for each user. You will also need to add password setting as part of the user registration process.
Level 1 Extension Task
4-PASS does not have a Level 1 Extension Task.
Level 2 Extension Task
4-PASS does not have a Level 2 Extension Task.
-
You must not store the password in plain text. Why not? Instead, you'll need to hash the password: we recommend using Python's
bcryptlibrary. -
You need to add password checking to part of the login process. Login verification means hashing the guess and comparing that with the stored value of the hashed password. If the values are the same, the guess was correct.
-
We expect you to hash your passwords with a salt. What's a salt? Why is it important? What attack is it preventing?
-
You will also need to add password setting as part of the user registration process. The registration process should automatically generate a salt for the new user, hash it and the inputted password, and store the salt and hash in the database.
Phase 5 tasks
The buggies are just a bunch of numbers and settings!
Construct a visual representation of each buggy that shows its configuration.
Core Task
Update your buggy editor to generate a visualisation that looks reasonably like a buggy and is based on at least one specification item.
Level 1 Extension Task
Update your buggy editor to generate a visualisation that looks reasonably like a buggy and is based on at least two specification items.
Level 2 Extension Task
Update your buggy editor to generate a visualisation that looks reasonably like a buggy and is based on at least three specification items.
-
This is presumably client-side using JavaScript. You can draw shapes natively, but it's a lot of work: there are a number of drawing libraries so we recommend you use one of those.
-
Although it's presumably client-side... could you do this server-side, with Python? How might that work? What are the pros and cons of this approach?
-
The two common ways of drawing in the browser are canvas or SVG.
A user who forgets their password can't log in.
There's no email address associated with a user, so to implement a reset password option using a one-use token system (see hints).
Core Task
Add support for password resets using a time-limited token requested by the user and provided by the admin. This includes a field in the database to keep track of the expiry of the token, a password reset form in the frontend, and appropriate password reset processing in the backend.
Level 1 Extension Task
5-RESET does not have a Level 1 Extension Task.
Level 2 Extension Task
5-RESET does not have a Level 2 Extension Task.
-
The admin user (see task 5-ADMIN) could potentially fix this instead instead by providing a new password to the user. Consider why having a password reset option when an admin user could provide a new password instead?
-
A token system lets an admin user (see task 5-ADMIN) add a token to a user's record. The admin then tells the user what the token is. The token should also have a time-limit. If the user goes to the reset password page and enters the token before it has expired, their new password is applied.
You don't know how well different buggies did in their races, but you should be able to see which ones have won more races, and how.
Store and present a log of race results for each buggy.
Core Task
Add support for keeping a log of race results for each buggy. This includes a table in the database that stores the buggy ID, race date/time, and result, a means to add the results to the database, and a route to show the results for a given buggy.
Level 1 Extension Task
5-RACELOG does not have a Level 1 Extension Task.
Level 2 Extension Task
5-RACELOG does not have a Level 2 Extension Task.
-
Consider how you will get the data and in what format(s)?
-
There's no table in the database for this, so maybe you'll need to add one, perhaps with SQL's
CREATE TABLE. -
Also consider how you will connect the race result records with the specific buggies?
-
What happens if a buggy is deleted after the race results have been stored in your database? What are the various remedies to that?
At least one user should be able to change other users (an admin or super user).
Use something like an is_admin setting in the users table and add superuser capability for manipulating buggies and users.
Core Task
Add support for an admin / superuser role. This role should include support for a minimum of any 4 out of 8 CRUD (Create, Read, Update, Delete) operations against buggies and users.
Level 1 Extension Task
Add support for an admin / superuser role. This role should include support for a minimum of any 6 out of 8 CRUD (Create, Read, Update, Delete) operations against buggies and users.
Level 2 Extension Task
Add support for an admin / superuser role. This role should include support for all 8 CRUD (Create, Read, Update, Delete) operations against buggies and users.
-
The
is_admincolumn in the users table should be set, so you can check in your python if thecurrent_userhas superpowers. -
How do you create a new admin user?
-
How about changing
init_db.pyso it creates an admin user when the server is first installed? What would you have to do to make this work? What mitigations could you put in place to minimise any risks that might arise from having an initialised admin user in the database? -
What happens if you delete the only remaining admin user? What's a design solution to this?
Phase 6 tasks
This task is a freestyle placeholder for other developments to the editor once you've done all the others.
Implement your own custom features.
Core Task
Implement one additional custom feature.
Level 1 Extension Task
Implement two additional custom features.
Level 2 Extension Task
Implement three additional custom features.
- You are free to choose any feature, although one idea is to implement a change log for each buggy.