Coding-with-Adam / response-reporting-dashboard

4 stars 1 forks source link

Application page #10

Open Coding-with-Adam opened 3 months ago

Coding-with-Adam commented 3 months ago

This is where we will discuss the application page.

supernyv commented 3 months ago
from dash import Dash, html, dcc, callback, Output, Input, State, register_page
import dash_bootstrap_components as dbc
import dash_mantine_components as dmc
import pandas as pd
from datetime import datetime

register_page(__name__)

name_input = dbc.Row([
    dbc.Label("First Name: ", width = 1),
    dbc.Col([
        dbc.Input(id= "id_first_name", placeholder = "Enter first name"),
        ],
        width = 5,
        ),
    dbc.Label("Last Name:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_last_name", placeholder = "Enter last name"),
        ],
        width = 5,
        ),
    ],
    class_name = "mb-3"
    )

gender_and_dob_input = dbc.Row([
    dbc.Label("Gender:", width = 1),
    dbc.Col([
        dbc.Select(
            options = [
            {"label":"Male", "value":"male"},
            {"label":"Female", "value":"female"},
            {"label":"Others", "value":"others"}
            ],
            value = "male",
            id = "id_gender_input")
        ],
        width = 5),
    dbc.Label("Date of Birth:", width = 2),
    dbc.Col([
        dmc.DatePicker(id= "id_dob", value = "1950/01/01"),
        ],
        width = 4,
        ),
    ],
    class_name = "mb-3"
    )

country_input = dbc.Row([
    dbc.Label("Nationality:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_nationality", placeholder = "Enter country of nationality"),
        ],
        width = 5,
        ),
    dbc.Label("City:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_city", placeholder = "Enter city of residence"),
        ],
        width = 5,
        ),
    ],
    class_name = "mb-3"
    )

credentials_input = dbc.Row([
    dbc.Label("Email:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_email", type = "email", placeholder = "Enter email", invalid = True),
        ],
        width = 5,
        ),
    dbc.Label("Password:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_password", type = "password", placeholder = "Enter password", invalid = True),
        ],
        width = 5,
        ),
    ],
    class_name = "mb-3"
    )

form = dbc.Card([
    dbc.CardBody([
        name_input,
        gender_and_dob_input,
        country_input,
        credentials_input,
        ])
    ])

layout = dbc.Container([
    html.Hr(),
    dbc.Row([
        html.H1("Become a vetted user for VOST", style = {"text-align" : "center"}),
        ]),
    html.Hr(),
    form,
    html.Hr(),
    dbc.Row([
        dbc.Col([
            dbc.Button("Submit", color = "primary")
            ])
        ])
    ],
    fluid = True
    )
supernyv commented 3 months ago

Still being worked on but this is what it looks like image

supernyv commented 3 months ago

For the date of birth, dmc.DatePicker looks much nicer and has more functionalities than the plain dash dcc.DatePickerSingle, but it seems there need to be some tweaking to make it have the theme of the cards like the other components of the form rather than the theme of the overall app.

JorgeMiguelGomes commented 3 months ago

Hello all, user registration fields should not include any PII, or that will turn into a legal nightmare in Europe (GPRD, mandatory DPO, etc) User registration should include First Name, Surname, Affiliation (meaning what entity they are representing), a check box for "Signatory of the Code of Practice on Disinformation" (boolean Yes/No), Country, institutional email address, website link We don't need any more data than this. Thank you!

On Tue, 9 Apr 2024, 08:25 Nyv, @.***> wrote:

For the date of birth, dmc.DatePicker looks much nicer and has more functionalities than the plain dash dcc.DatePickerSingle, but it seems there need to be some tweaking to make it have the theme of the cards like the other components of the form rather than the theme of the overall app.

— Reply to this email directly, view it on GitHub https://github.com/Coding-with-Adam/response-reporting-dashboard/issues/10#issuecomment-2044172060, or unsubscribe https://github.com/notifications/unsubscribe-auth/AIGDRCPMS6UZONCAXUI6DDTY4N3VJAVCNFSM6AAAAABF4VCSC2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBUGE3TEMBWGA . You are receiving this because you are subscribed to this thread.Message ID: <Coding-with-Adam/response-reporting-dashboard/issues/10/2044172060@ github.com>

-- Confidencialidade: Esta mensagem (e eventuais ficheiros anexos) é destinada exclusivamente às pessoas nela indicadas e tem natureza confidencial. Se receber esta mensagem por engano, por favor contacte o remetente e elimine a mensagem e ficheiros, sem tomar conhecimento do respectivo conteúdo e sem reproduzi-la ou divulgá-la. Confidentiality Warning: This e-mail message (and any attached files) is confidential and is intended solely for the use of the individual or entity to whom it is addressed. lf you are not the intended recipient of this message please notify the sender and delete and destroy all copies immediately.

Coding-with-Adam commented 3 months ago

Thanks for your input @JorgeMiguelGomes

@supernyv once you have the final code with all the changes, just paste it in a new comment below and I'll add it to the main branch

supernyv commented 3 months ago

Hi @Coding-with-Adam, below is a working code (with the required fields and input criteria) but will be final once the database is set-up (will set up one in the following days, while waiting for the final database from Jorge's team). Note that you also need the country list csv file in the assets folder of nyv_branch to have the dropdown for country selection work

supernyv commented 3 months ago
from dash import Dash, html, dcc, callback, Output, Input, State, register_page, ctx
import dash_bootstrap_components as dbc
import pandas as pd
import re
from datetime import datetime

register_page(__name__)

countries = pd.read_csv("assets/countries_list.csv")
country_names = countries["country_name"]

#_________________________________________Form Input Components_________________________________________#

name_input = dbc.Row([
    dbc.Label("First Name: ", width = 1),
    dbc.Col([
        dbc.Input(id= "id_first_name", placeholder = "Enter your first name", invalid = True),
        ],
        width = 5,
        ),
    dbc.Label("Last Name:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_last_name", placeholder = "Enter your last name", invalid = True),
        ],
        width = 5,
        ),
    ],
    class_name = "mb-3"
    )

affiliation_input = dbc.Row([
    dbc.Label("Affiliation:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_affiliatiion", placeholder = "The name of the entity you are representing"),
        ],
        width = 5),
    dbc.Label("Signatory of the Code of Practice on Disinformation:", width = 4),
    dbc.Col([
        dbc.Select(
            options = [
            {"label":"Yes", "value":"yes"},
            {"label":"No", "value":"no"},
            ],
            value = "yes",
            id = "id_signatory")
        ],
        width = 2,
        ),
    ],
    class_name = "mb-3"
    )

country_input = dbc.Row([
    dbc.Label("Website:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_website", placeholder = "Enter the website of the entity"),
        ],
        width = 5,
        ),
    dbc.Label("Country:", width = 1),
    dbc.Col([
        dcc.Dropdown([
            {"label" : country, "value" : country} for country in country_names
            ],
            value = "France",
            clearable = False,
            searchable = True),
        ],
        width = 5,
        ),
    ],
    class_name = "mb-3"
    )

credentials_input = dbc.Row([
    dbc.Label("Work Email:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_email", type = "email", placeholder = "Enter your work email", invalid = True),
        ],
        width = 5,
        ),
    dbc.Label("Password:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_pwd", type = "password", placeholder = "Enter a strong password", invalid = True),
        ],
        width = 5,
        )
    ],
    class_name = "mb-3"
    )

#_________________________________________Form body_________________________________________#

form = dbc.Card([
    dbc.CardBody([
        name_input,
        affiliation_input,
        country_input,
        credentials_input,
        ])
    ])

#_________________________________________Form Layout_________________________________________#

layout = dbc.Container([
    html.Hr(),
    dbc.Row([
        html.H1("Become a vetted user for VOST", style = {"text-align" : "center"}),
        ]),
    html.Hr(),
    form,
    html.Hr(),
    dbc.Row([
        dbc.Col([
            dbc.Button("Submit", id = "id_submit_button", color = "primary")
            ])
        ]),
    dbc.Row([
        dbc.Col(id = "id_test_output")
        ])
    ],
    fluid = True
    )

#_________________________________________Callbacks_________________________________________#

@callback(
    Output("id_first_name", "invalid"),
    Input("id_first_name", "value"),
    prevent_initial_call = True)
def verify_names(first_name):
    name_criteria = r"[a-zA-Z]{2,}"
    first_name_match = re.match(name_criteria, first_name)
    if first_name_match:
        return False
    return True

@callback(
    Output("id_last_name", "invalid"),
    Input("id_last_name", "value"),
    prevent_initial_call = True)
def verify_names(last_name):
    name_criteria = r"[a-zA-Z]{2,}"
    last_name_match = re.match(name_criteria, last_name)
    if last_name_match:
        return False
    return True

@callback(
    Output("id_email", "invalid"),
    Input("id_email", "value"),
    prevent_initial_call = True
    )
def verify_email(user_email):
    email_criteria = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
    result = re.match(email_criteria, user_email)
    if result:
        return False
    return True

@callback(
    Output("id_pwd", "invalid"),
    Input("id_pwd", "value"),
    prevent_initial_call = True
    )
def verify_password(user_password):
    if user_password:
        if len(user_password) > 5:
            return False
        return True
    return True

@callback(
    Output("id_test_output", "children"),
    Input("id_submit_button", "n_clicks"),
    Input("id_first_name", "invalid"),
    Input("id_last_name", "invalid"),
    Input("id_email", "invalid"),
    Input("id_pwd", "invalid"),
    prevent_initial_call = True)
def submit_button_click(submit_click, f_name_invalid, l_name_invalid, email_invalid, pwd_invalid):
    all_status = [f_name_invalid, l_name_invalid, email_invalid, pwd_invalid]
    if (ctx.triggered_id == "id_submit_button"):
        if True in all_status :
            return "Incorrect information. Form rejected"
        return "Submission successful"
supernyv commented 3 months ago

image

Coding-with-Adam commented 3 months ago

Amazing work, especially with the regular expressions. Well done, @supernyv . I'm adding this application page right now. I removed the password section since @JorgeMiguelGomes didn't mention it.

Coding-with-Adam commented 3 months ago

Like you said, @supernyv , now we need to connect the submit button to transfer the info to VOST (cc @JorgeMiguelGomes ) database. We're making cool progress. Here's the result:

image

Coding-with-Adam commented 3 months ago

Todo:

supernyv commented 3 months ago

Hi Adam. Done now, but one point I wanted to clarify before pasting the code, since it has a great impact on the design of the database and the form: The country, is it the country of the user or the country of the entity they are representing? @JorgeMiguelGomes

JorgeMiguelGomes commented 3 months ago

Country should be of the entity they are representing. Thank you, again, for the amazing work you are doing.

On Tue, 16 Apr 2024, 22:13 Nyv, @.***> wrote:

Hi Adam. Done now, but one point I wanted to clarify before pasting the code, since it has a great impact on the design of the database and the form: The country, is it the country of the user or the country of the entity they are representing? @JorgeMiguelGomes https://github.com/JorgeMiguelGomes

— Reply to this email directly, view it on GitHub https://github.com/Coding-with-Adam/response-reporting-dashboard/issues/10#issuecomment-2059932133, or unsubscribe https://github.com/notifications/unsubscribe-auth/AIGDRCMMZWD6VQGBEI7UI73Y5WIBDAVCNFSM6AAAAABF4VCSC2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANJZHEZTEMJTGM . You are receiving this because you were mentioned.Message ID: @.*** .com>

-- Confidencialidade: Esta mensagem (e eventuais ficheiros anexos) é destinada exclusivamente às pessoas nela indicadas e tem natureza confidencial. Se receber esta mensagem por engano, por favor contacte o remetente e elimine a mensagem e ficheiros, sem tomar conhecimento do respectivo conteúdo e sem reproduzi-la ou divulgá-la. Confidentiality Warning: This e-mail message (and any attached files) is confidential and is intended solely for the use of the individual or entity to whom it is addressed. lf you are not the intended recipient of this message please notify the sender and delete and destroy all copies immediately.

supernyv commented 3 months ago

Great, thanks Jorge. I'll paste the new script here in a moment and at the same time I'll open a new issue for the database model for you to review and decide what should be included in the database. I understand we are allowed to use any database we can use for now but for the model it might be better to have you decide on it earlier. I already have a complete design from which we can start the discussion.

supernyv commented 3 months ago
from dash import Dash, html, dcc, callback, Output, Input, State, register_page, ctx
import dash_bootstrap_components as dbc
import pandas as pd
import re
from datetime import datetime
from utils.app_queries import register_user

register_page(__name__)

countries = pd.read_csv("assets/countries_list.csv") #To replace with a read query from database
country_names = countries["country_name"]

#_________________________________________Form Input Components_________________________________________#

name_input = dbc.Row([
    dbc.Label("First Name: ", width = 1),
    dbc.Col([
        dbc.Input(id= "id_first_name_in", placeholder = "Enter your first name", invalid = True),
        ],
        width = 5,
        ),
    dbc.Label("Last Name:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_last_name_in", placeholder = "Enter your last name", invalid = True),
        ],
        width = 5,
        ),
    ],
    class_name = "mb-3"
    )

affiliation_input = dbc.Row([
    dbc.Label("Affiliation:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_affiliatiion_in", placeholder = "The name of the entity you are representing"),
        ],
        width = 5),
    dbc.Label("Signatory of the Code of Practice on Disinformation:", width = 4),
    dbc.Col([
        dbc.Select(
            options = [
            {"label":"Yes", "value":"yes"},
            {"label":"No", "value":"no"},
            ],
            value = "yes",
            id = "id_signatory_status")
        ],
        width = 2,
        ),
    ],
    class_name = "mb-3"
    )

contacts_input = dbc.Row([
    dbc.Label("Website:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_website_in", placeholder = "Enter the website of the entity"),
        ],
        width = 5,
        ),
    dbc.Label("Work Email:", width = 1),
    dbc.Col([
        dbc.Input(id= "id_email_in", type = "email", placeholder = "Enter your work email", invalid = True),
        ],
        width = 5,
        ),
    ],
    class_name = "mb-3"
    )

country_input = dbc.Row([
    dbc.Label("Country:", width = 1),
    dbc.Col([
        dbc.Select([
            {"label" : country, "value" : country} for country in country_names
            ],
            id = "id_country_in",
            value = "",
            invalid = True,
            placeholder = "Select the country of the entity",
            required = True),
        ],
        width = 5,
        ),
    ],
    class_name = "mb-3"
    )

#_________________________________________Form body_________________________________________#

form = dbc.Card([
    dbc.CardBody([
        name_input,
        affiliation_input,
        contacts_input,
        country_input,
        ])
    ])

#_________________________________________Form Layout_________________________________________#

layout = dbc.Container([
    dcc.Store(id = "id_registration_data", storage_type = "local", data = {"registered_user":False}),
    dcc.Location(id="id_url", refresh = True),

    html.Hr(),
    dbc.Row([
        html.H1("New User Registration", style = {"text-align" : "center"}),
        ]),
    html.Hr(),
    form,
    html.Hr(),
    dbc.Row([
        dbc.Col([
            dbc.Button("Submit", id = "id_submit_button", color = "primary")
            ])
        ]),
    dbc.Row([
        dbc.Col(id = "id_registration_message")
        ])
    ],
    fluid = True
    )

#_________________________________________Callbacks_________________________________________#

@callback(
    Output("id_first_name_in", "invalid"),
    Input("id_first_name_in", "value"),
    prevent_initial_call = True
    )
def verify_first_name(first_name):
    name_criteria = r"[a-zA-Z]{2,}"
    first_name_match = re.match(name_criteria, first_name)
    if first_name_match:
        return False
    return True

@callback(
    Output("id_last_name_in", "invalid"),
    Input("id_last_name_in", "value"),
    prevent_initial_call = True
    )
def verify_last_name(last_name):
    name_criteria = r"[a-zA-Z]{2,}"
    last_name_match = re.match(name_criteria, last_name)
    if last_name_match:
        return False
    return True

@callback(
    Output("id_email_in", "invalid"),
    Input("id_email_in", "value"),
    prevent_initial_call = True
    )
def verify_email(user_email):
    email_criteria = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
    result = re.match(email_criteria, user_email)
    if result:
        return False
    return True

@callback(
    Output("id_country_in", "invalid"),
    Input("id_country_in", "value"),
    prevent_initial_call = True
    )
def verify_country(entity_country):
    if entity_country == "":
        return True
    return False

@callback(
    Output("id_registration_message", "children"),
    Output("id_registration_data", "data"),
    Input("id_submit_button", "n_clicks"),
    State("id_first_name_in", "invalid"),
    State("id_last_name_in", "invalid"),
    State("id_email_in", "invalid"),
    State("id_country_in", "invalid"),
    State("id_first_name_in", "value"),
    State("id_last_name_in", "value"),
    State("id_affiliatiion_in", "value"),
    State("id_signatory_status", "value"),
    State("id_website_in", "value"),
    State("id_email_in", "value"),
    State("id_country_in", "value"),
    prevent_initial_call = True
    )
def submit_button_click(submit_click, f_name_invalid, l_name_invalid, email_invalid, country_invalid,
    f_name_in, l_name_in, affiliation_in, status_in, website_in, email_in, country_in):

    invalid_inputs = [f_name_invalid, l_name_invalid, email_invalid, country_invalid]

    if (ctx.triggered_id == "id_submit_button") and not (True in invalid_inputs):
        query_output_message = register_user(email_in, f_name_in, l_name_in, affiliation_in,
            website_in, status_in, country_in)
        if query_output_message == "Success":
            return "Submission successful", {"registered_user":True}
        elif query_output_message == "Existing User":
            return "The input email is already taken", {"registered_user":False}
        else:
            return query_output_message, {"registered_user":False}

    return "Incorrect information", {"registered_user":False}

@callback(
    Output("id_url", "pathname"),
    Input("id_registration_data", "data"),
    prevent_initial_call = True
    )
def load_user_home_page(registration_data):
    if registration_data["registered_user"] == True:
        return "/login"
    else:
        return "/application"
supernyv commented 3 months ago

But now because the code connects with the database to add new users, it won't run without the database set-up. I created two modules in the "utils" folder that deal with all the queries and connections to the database, the only things needed from the running computer are:

supernyv commented 3 months ago

image

supernyv commented 2 months ago

Hi @JorgeMiguelGomes, I would also like to come back to the application and raise the question about Registration vs Application. Do we actually have two processes or just one? If there are two processes:

If there is just one process:

In the first case, we need two pages, one for application and another for registration. In the second case we need just one page for application.

JorgeMiguelGomes commented 2 months ago

Hi @supernyv, We have two processes here, and the reason is due to safety and to make sure we don't have external actors trying to manipulate the platform. There should a registration process, that the admin of the platform can then approve (or deny). After the users are approved they can login and insert data in the form. Does this make sense?

supernyv commented 2 months ago

Hi @Jorge Yes, I understand. In other words, you mean that the registration (by the user) and approval (by an admin) are the two processes. Such that we do not have application vs registration (which are the same) but we have application vs approval.

So, how about adding a password field in the application form?

JorgeMiguelGomes commented 2 months ago

That makes perfect sense. And also password validation? (Retype password to check if they are the same?)

On Tue, 7 May 2024, 03:31 Nyv, @.***> wrote:

Hi @jorge https://github.com/jorge Yes, I understand. In other words, you mean that the registration (by the user) and approval (by an admin) are the two processes. Such that we do not have application vs registration (which are the same) but we have application vs approval.

So, how about adding a password field in the application form?

— Reply to this email directly, view it on GitHub https://github.com/Coding-with-Adam/response-reporting-dashboard/issues/10#issuecomment-2097307828, or unsubscribe https://github.com/notifications/unsubscribe-auth/AIGDRCKNEDD67OL26MYUGJ3ZBA4JPAVCNFSM6AAAAABF4VCSC2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOJXGMYDOOBSHA . You are receiving this because you were mentioned.Message ID: @.*** .com>

-- Confidencialidade: Esta mensagem (e eventuais ficheiros anexos) é destinada exclusivamente às pessoas nela indicadas e tem natureza confidencial. Se receber esta mensagem por engano, por favor contacte o remetente e elimine a mensagem e ficheiros, sem tomar conhecimento do respectivo conteúdo e sem reproduzi-la ou divulgá-la. Confidentiality Warning: This e-mail message (and any attached files) is confidential and is intended solely for the use of the individual or entity to whom it is addressed. lf you are not the intended recipient of this message please notify the sender and delete and destroy all copies immediately.