Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Application page #10

Open
Coding-with-Adam opened this issue Apr 8, 2024 · 21 comments
Open

Application page #10

Coding-with-Adam opened this issue Apr 8, 2024 · 21 comments

Comments

@Coding-with-Adam
Copy link
Owner

This is where we will discuss the application page.

@supernyv
Copy link
Collaborator

supernyv commented Apr 9, 2024

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
Copy link
Collaborator

supernyv commented Apr 9, 2024

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

@supernyv
Copy link
Collaborator

supernyv commented Apr 9, 2024

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
Copy link
Collaborator

JorgeMiguelGomes commented Apr 9, 2024 via email

@Coding-with-Adam
Copy link
Owner Author

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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

image

@Coding-with-Adam
Copy link
Owner Author

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
Copy link
Owner Author

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
Copy link
Owner Author

Todo:

  • remove France so country so field is blank when the form first loads
  • connect the page to a local database

@supernyv
Copy link
Collaborator

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
Copy link
Collaborator

JorgeMiguelGomes commented Apr 16, 2024 via email

@supernyv
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

supernyv commented Apr 16, 2024

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:

  • mysql database server
  • a user in the database with name vost_user and password vost
  • a database in the server called vost_db (can be taken from nyv_branch and run locally)

@supernyv
Copy link
Collaborator

image

@supernyv
Copy link
Collaborator

supernyv commented May 1, 2024

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:

  • Individuals go through the application process on the app to become vetted users (what for?)
  • Individuals go through a registration process to become users of the response reporting app (where they choose a username and a password to be used for login)

If there is just one process:

  • Individuals go through the application process on the app, after which they become vetted users and are able to login in the app with the email (and password, to be decided), alla of which they provide during the application 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
Copy link
Collaborator

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
Copy link
Collaborator

supernyv commented May 7, 2024

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
Copy link
Collaborator

JorgeMiguelGomes commented May 7, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants