# COMP1010 Final Exam Notes - Alex Ward (z5592080)

### Printing an ordered list in python
To print a python list (e.g. list_name = [red, blue, green]) as an orded list in normal python (i.e. not a flask app):

    for index, item in enumerate(list_name):
        print(f"{index + 1}. {item}")

### Appending a word to a list within a dictionary
Suppose you have a list within a dictionary. Let the dictionary be called 'dict' with has a key, 'key', and a value that exists as a list. If we want to add a word to the list associated with a certain key, we can input the following code:

    dict[key].append(word)

### Creating a key and value in a dictionary
Consider our same dictionary 'dict' we can assign a value to a key using the following code:

    dict[key] = value

This will creating a dictionary that looks like the following:

    dict = {
        key : value,
        ...
    }

### Printing the rows of a dictionary
If we have a simple dictionary filled with row

    dict = {
        key : value,
        key : value,
        ...
    }

we can easily print each row by running:

    for key,value in dict.items():
        print(f"{key} : {value}")

### Finding the most common item of a list
This is a two step process. Forming a dictionary which assigns a count to each different item, and then sorting the list
1. To formulate our 'count' dictioanry, we set up a loop:

    ```
    item_count = {}

    for item in items:
        if item not in item_count:
            count = items.count(item)
            item_count[item] = count
    ```

2. To sort the dictionary, we want to arrange the rows by value (v). This can be done in the following way (see block 1). Here 'dict' is the dictionary. We then form this into a list and take the last value to find the mode (see block 2).
    ```
    sorted_dict = {key: value for key, value in sorted(dict.items(), key=lambda item: item[1])}
    ```
    ```
    mode = list(sorted_dict.items())[-1]
    ```

This is the quickest way to do it, but you could also go through manually and check if the next row of the dictionary has a value higher than the previous. It should also be noted that you can add in ```, reverse=True``` directly after the ```item[1]``` to put it in descending order. In this case, we read the 0th entry in the ```mode``` line for our highest score. Please also note that ```mode``` is just reading the key of the last value, not the value as well. We can change this as expected (using .value() for only the value or .keys() for only the key).

### Forming a list of items, (key, value), into one string
While the example of the ```mode``` seen above just gives us one number, if we wanted to take the top 5 items in out list, for example, then we just replace the ```[-1]``` with ```[0:5]```. This now gives us a list:

    top_five = [(key1, value1), (key2, value2), (key3, value3),...etc.]

Now, to format thing into a list of strings, we loop across the

### Random integer between 1 and 100
To get a random integer between 1 and 100, we can use the ```random``` package in python. Then we do the following:

    import random

    number = random.randint(1,100)

If we change the numbers in the brackets we could get ourselves a different range (i.e. 5,10 for integers between 5 and 10)

### Toggle off and on autocomplete for an html textbox
    p.input(type="text", name="name", autocomplete="off", required=True)

This will create a textbook that must be completed in order to submit (required=True) and will not retain your previous result when the page is returned to using an in app button (i.e. and inputted back button, NOT the browser back button)

### Redirecting to a previous page.
Suppose we have a flask app with the following routes set up:

    @app.route("/", methods=['POST', 'GET'])
    def home():
        response = ...etc
    return str(response)

    @app.route("/page_two", methods=['POST', 'GET'])
    def page_two():
        response = ...etc
    return str(response)

If we want to go back to the home page from page_two, we must do the following:

1. Make sure the ```import flask``` bit includes the following:

    ```
    from flask import Flask, redirect, url_for
    ```

2. Make sure that your button in the second page that is returning you to the homepage is in a p.form. An example can be seen below. In the example, we see that the button is taking us to a route called "/back". From our flask app above, this route does not exist, so we will need to create it, and this is where the redirect happens

    ```
    p.from(
        p.input(type="submit", value="Back", formaction="/back")
    )
    ```

3. Create the "/back" route by doing what is below. We can see we are using this ```url_for()``` function which basically tells it where you want to go back to

    ```
    @app.route("/back", methods=['POST', 'GET'])
    def back():
        return redirect(url_for('home'))
    ```

### looking across a list to make list-items in pyhtml
    p.li(item) for item in list

### Dictionaries with lists
Suppose you have a dictionary

    dict = {
        key : [value1, value2, ...etc.]
    }

If you may extract the list by typing

    list_of_key_values = dict[key]

This will create a list called list_of_key_values that is just [value1, value2, ...etc.]

### CSS colour palettes
* General: #003366 (Navy), #008080 (Teal), #98FF98 (Mint Green), #f9f9f9 (Light Grey)
    * You can pick a better shade with the color picker, but this is a good starting point.
* #FFFFF0 (Ivory White)

### Other CSS notes:
* text-align : Positions text (can wirte in pixles, or position(center, right, ...etc.))
* font-size : Changes size of font (in pixles, words (large, small, larger, medium,...etc.))
* border-radius: Rounds the boarders of things like input boxes, buttons, or text boxes (in px)
* "border: 2px solid black; " : Common way of creating a solid black border around an object (like the ones mentioned above)
    * Can change the px amount, the color, and the 'solid' (to something like dashed, dotted, double)
* It is common to make links have an underline and appear a different colour when hovered over. We can do this in the following way:

    ```
    .class_name:hover {
        text-decoration: underline;
        color: pick_colour_to_change_to;
    }
    ```
    Here, one replaces ".class_name" with the value to which class_ is attributed in the main workbook (i.e. what you have called the element you want to apply this hover function to).
* We can also apply this ```text-decoration``` attribute to do 'overline' (line above text), 'line-through' (to cross out the text), or 'none' (to keep text, such as a link, looking normal).
    * You can then change the thinckness with the ```text-decoration-thickness``` attribute.
* "font-weight: bold; " makes text bold.
* "font-style: italic; " makes the text italicised
* For all elements that you want to apply CSS to, you will have to do something like the following
    ```
    p.p(class_="paragraph")(
        "Hello World!"
    )
    ```
    This is then refered to a .paragraph in the CSS file

### Formmethod
* As a general rule of thumb, it is usually safe to just type @app.route("/...", methods=['POST', 'GET']), and for your submit button on the previous page to just have p.input(..., formaction='/...'). Sometimes, however, we need to add to the submit button p.input(..., formaction='/...', formmethod='post' [NOTE: Could also be 'get']). This usually happens when when we have more than one submit button and they do different things (e.g. One might just send you to a new page, while one might to a redirect page, get data and ammend it, then send you back to the original page transfering the amended data). If things don't quite work how they should, you can try adding this as a trouble shooting method. Otherwise, it doesn't matter.
* We use " formmethod='post' " if we submit data
* We use " formmethod='get' " if we just go to a different page.

### An example of adding an error message

    @app.route("/", methods=['POST', 'GET'])
    def home():
        error  = request.args.get("error")
        message = ""
        if error == "xyz"
            message = "zyx"
        elif error == ...
            message = ...

        ...
    return str(response)

    @app.route("/-redirect", methods=['POST', 'GET'])
    def home_redirect():
        ...
        return redirect(url_for("home", error="xyz"))
        ...
        return redirect(url_for("home", error=...))

* NOTE: The redirect page must include if-elif/else statements to do something different on the home page depending on a condition.

### Cookies steps
1. Set up a secret key " app.config['SECRET_KEY']="dfhuiwhfiuhviwehfuiwdhfsadf" "
    * NOTE: It will usually send an error message that tell you you don't have it
2. To create a cookie (session) we check that one doesn't already exist first:
    ```
    if '...' not in session:
        session['...'] = []
    ```
    Session is a dictionary, so we are just assigning a list as the value
3. Now we want to add items to that values list:
    ```
    session['...'].append(item)
    ```
    This is outside the previous if statment, because in all istances the cookie dictionary now exists.
4. Finally we MUST tell it that we have modified the session so we throw in:
    ```
    session.modified = True
    ```

### JSON steps
1. We import the jsonhelpers. This can be found in the lecture notes, but just in case, here is a copy of what it has.
    ```
    import json
    import os

    # empty_data should be what data we want to put in there if the file doesn't exist
    # either {} or [] as we are working with json files
    def create_if_not_exist(filename, empty_data):
        if not os.path.exists(filename):
            write_json_file(filename, empty_data)

    # Main function 1 - Open a file
    def write_json_file(filename, info_to_save):
        my_file = open(filename, "w")
        json.dump(info_to_save, my_file)
        my_file.close()

    # Main function 2 - Read a file
    def read_json_file(filename):
        my_file = open(filename, "r")
        information = json.load(my_file)
        my_file.close()
        return information
    ```
2. Once we have have imported these three json function, we always create_if_not_exist first.
    ```
    create_if_not_exist("file_name.json", {} [OR [] if you want it as a list] )
    ```
3. We then must read the file and give it a variable name
    ```
    file_name = read_json_file("file_name.json")
    ```
4. Now that we have a variable assigned to it in our main python file, we can add the data too it.
    ```
    file_name.append(item) for a list OR
    file_name[key] = value for a dictionary where each key has one assigned value.
    ```
    These can be addapted if the the value is a list or something else.
5. Now just update the json file
    ```
    write_json_file("file_name.json", file_name)
    ```

### Extracting data from complex data structures
* Consider a the following data structure:
    ```
    dict = {
        key1 : value1,
        key2 : value2,
        key3 : {
            key3_1 : value 3_1,
            key3_2 : value 3_2
            key3_3 : {
                key3_3_1 : value3_3_1,
                key3_3_2 : value3_3_2
            },
            key3_4 : value3_4,
            key3_5 : [value3_5_1, value3_5_2, value3_5_3,...etc.]
        },
        key4 : [
            {
                key4_1_1 : value4_1_1,
                key4_1_2 : value4_1_2
            },
            {
                key4_2_1 : value4_2_1,
                key4_2_2 : value4_2_2
            },
            {
                key4_3_1 : value4_3_1
                key4_3_2 : valu34_3_2
            }
        ]
    }
    ```
    This looks quite complex, but breaking it down, we see that it is just a dictionary with 4 keys.
* The first two keys just have an assigned value. To extract any data from there we can just use (for key1 for example)
    ```
    value1 = dict['key1']
    ```
    And that should give us the associated value. If we do this to key2, we get the same one value, but for key3 and key4, we get a bit more of a mess. We would get another dictionary for key3, and a list of dictionaries for key 4
* Key3 can be broken down as having an assigned value of a dictionary. This dictionary has 5 'sub-keys'. 'Sub-keys' 3_1, 3_2, and 3_4 are just assigned single values. This means that we can easily extract 'sub-values' 3_1, 3_2, and 3_4 by running (for key3_1 for example)
    ```
    value3_1 = dict['key3']['key3_1']
    ```
    Now, if we did this for 'sub-key' 3_3, we would encounter a bit of a mess. The value is another dictionary, but we can apply similar logic. In this case, for value of key3_3_1 (for example) we can run something like,
    ```
    value3_3_1 = dict['key3']['key3_1']['key3_3_1']
    ```
    And we can do the same thing to find value3_3_2
* If we tried to find value3_5, we would get a list of strings. If we want to view the whole list, then this is fine, but often we want to do something to each of those values in a loop. Even if we wanted to extract the n-th value, we have to do something differently. The cleanest and simplest way is in two steps. First is to get out list (Here list = value3_5)
    ```
    list = dict['key3']['key3_5']
    ```
    And the second is to apply our oridinary functions to the list
    ```
    nineth_item = list[8]
        # or do do something to all the values in a loop:
    for item in list:
        print(f"{index} : {item}")
* Finally, key4's value is a list of dictionaries. Since it is a list (and not a dictionary of dicionaries [that would be similar to key3_3 above]) we can only single a dictionary out by knowing it's index. We can also just apply our usual for loops to the list. Just imagine each dictionary is an item.
* Where things become complex is seeing if any of the dictionaries in out list have a certain value associated with a known key. There are ways to do it nicely, but it is honest just easier to do similar to the following:
    ```
    # List for or the 'could also do'
    has_value = []

    for dict in list:
        value = dict['key']
        if value = needed_value:
            return True
            # OR could also do
            has_value.append(dict['name']) # If the dictionary has a given name
    ```

### Showing a list of things as individual items in PyHTML
Suppose there is a list of items that you want to display on a flask app that is defined as the following:
```
list = [item1, item2, item3, item4,...etc.]
```
If we want to print each item on its own paragraph is a loop, we can type in our response statement:
```
*[p.p(item) for item in list]
```
Often we want to format it as a list, so we make each of our items list items;
```
list = [p.li(item1), p.li(item2), p.li(item3), p.li(item4),...etc.]
```
and then we can just write in out response statement
```
*[p.ul(item) for item in list]      (works for p.ol as well)
```

### Round to nearest integer
    nearest_integer = round(number) # NOTE: 'math' package must have already been installed

### Drop-down menus
If we have a list of items that we want to form into a drop down menu, we can do the following.
1. MANUALLY:
    ```
    p.submit(name='name')(
        p.option('a', value='a'),
        p.option('b', value='b'),
        p.option('c', value='c'),
        ...etc.
    )
    ```
2. IN A LOOP:
    ```
    p.submit(name='name')(
        *[p.option(item) for item in list]      (assuming list is called 'list')
    )
    ```
You must place a submit button at the end of the drop-down menu to transfer data

### Radio buttons
If we have a list of items that we want to form into radio buttons, we can do the following.
1. MANUALLY:
    ```
    p.label('a', p.input(type='radio', name='name', value='a')),
    p.label('b', p.input(type='radio', name='name', value='b')),
    p.lable('c', p.input(type='radio', name='name', value='c')),
    ```
2. IN A LOOP:
    ```
    *[p.label(item), p.input(type='radio', name='name', value=item) for item in list]
    ```
Please note, the value only tells the app what to send when called upon in a request.form.get. To actually display a name next to the item, you need to give it a label. You will also need to add a submit button to actually send data and go to a different page

### Check boxes
If we have a list of items that we want to form into check boxes, we can do the following.
1. MANUALLY:
    ```
    p.label('a', p.input(type='checkbox', name='name', value='a')),
    p.label('b', p.input(type='checkbox', name='name', value='b')),
    p.lable('c', p.input(type='checkbox', name='name', value='c')),
    ```
2. IN A LOOP:
    ```
    *[p.label(item), p.input(type='checkbox', name='name', value=item) for item in list]
    ```
Again, we need to add the label here to actually display text. You will again need a submit button to actually send data and go to a different page.

### Links
    link = p.a(href=url_for('link_page', name='name'))(text_to_show)

'Link_page' refers to function of the page you are traveling to next. As in what is written in the def ...() under the @app.route. The name really just refers to any information that needs to be sent in the link. I.e. if it is taking you to a different page for each link, then the variable that will change in the 'link_page' section would he that name variable. It does not have to be called name. This variable is recieved in the next page with request.args.get("name") (assuming the variable is called "name")

### Usability and user heuristics
* Five components of usability:
    1. Learnability
    2. Efficiency
    3. Memorability
    4. Errors
    5. Satisfaction
* Learnability
    * How quickly is the user able to learn the website and do basics task?
* Efficiency
    * How quickly can the user get what they need to do, done?
* Memorability
    * Will the knowledge of how to use the website be quickly forgotten?
    * Will the user know what to do after coming back in 1, 2, 5, 10 years?
* Errors
    * Are errors easily made?
    * How severe are the errors?
    * How easily can the user recover those errors?
* Satisfaction
    * Is the website nice to look at?
    * Is it pleasant to use

* Ten components of user heuristics
    1. Visability of system status
        * User's know where they are on the website and where they can do.
        * Navigation buttons do a good job of this.
        * Progress bars on a form do a good job of this.
    2. Match real-world layouts
        * Design should follow logical pathways and patterns. Order matters.
        * Match the knowledge level of the user. Use phrases on concepts that the intended user is formilliar with. Label things to ensure constant clarity.
    3. User control and freedom
        * Always allow users to go undo a mistake.
        * Make the undo process easy.
        * Ensure the back/cancel/exit button is easily accessible and obvious. It should be clearly labelled.
    4. Consistency and Standards
        * Make sure that every object that does a different thing is labelled something clearly different.
        * Ensure that all objects that do the same thing are labelled in an idential manner.
    5. Error prevention
        * Error messages are important, but minimising the need for them is more important.
        * If there is only a finite set of options, maybe a drop-down menu is better than a text-box.
        * If something is error prone and unnecessary, is it really needed on the website.
        * Minimise the room a user has for error.
    6. Recognition rather than recall
        * Present the user with the important information from the previous page that they may need again on this page.
        * Don't force them to hold information. Present everything they need to successfully complete the task.
        * Offer context so they don't have to remember what the point is.
    7. Fexibility and efficiency of use
        * Provide shortcuts for the user so they don't have to repeat monotonous tasks.
        * Allow for personalisation so the user can taylor the site to their own needs.
        * Let the user add the features they want and remove the features they don't.
    8. Aesthetic and minimalist design
        * Keep features to a minimum. You don't want to overload the user.
        * While some designs look nicer than others, it is important to keep the website clean so that the user can understand it most efficiently.
        * Make sure the main elements are most obvious to the user.
        * Make sure that the features are satisfying to use.
    9. Help the user recognise, diagnose, and recover from errors
        * Use traditional error messages that are clear.
        * Use bold and red text to point out what is wrong and what is important in the error message.
        * Use simple language so they know what to fix.
        * Give solutions not just a problem.
    10. Help and documentation
        * Ensure it is easy for the useer to find help.

### Checking attribution
* Suppose we have a variable x. It may be a string, a float, an integer, a list, a dictionary, or a boolean. It is often common to run code:
    ```
    if x:
        print(x)
    ```
Which essenitally tells says, if x is not empty ('' (empty string), {} (empty dictionary), [] (empty list), 0 (empty integer), 0.0 (empty float)) or not false, then print it. We can replace print(x) with any other functions, methods, or transformations

### More on decimal places
Suppose you have a float that is stored in the variable 'price'. We can round it to 2 decimal places in the display by placing it in a string ```f"{price:.2f}"```. Please note, that price must be a float, no a string. Also, once we have placed it in this string, we cannot do maths with it. to add the price to something else, we would need to use the original, unrounded, 'price' variable

### Checking the existance of json files
    if os.path.exists("passwords.json"):
        print("exists")

### Linking to CSS style sheet
* Each new @app.route that is to have css styling done to it must have the following code placed in its p.head section
    ```
    p.link(rel="stylesheet", href="/static/styles.css")
    ```
    Make sure the file is called styles.css and is in the static folder



