Commit 63b0484c authored by Shashank Kumar's avatar Shashank Kumar

feat(signup): Initial SignUp Module

parent 9f575257
# For Tests
.pytest_cache
.coverage
# Database File
new_contributor_wizard.db
......@@ -12,11 +12,12 @@ before_script:
pylint:
type: test
script:
- pipenv run pylint main.py
- pipenv run pylint main
- pipenv run pylint settings
- pipenv run pylint modules
pytest:
type: test
script:
- pipenv run pytest tests/
- pipenv run pytest tests
- pipenv run pytest --cov=modules
......@@ -54,7 +54,8 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=pointless-string-statement,
disable=duplicate-code,
pointless-string-statement,
useless-super-delegation,
parameter-unpacking,
unpacking-in-except,
......
v0.0.1
## June 11 2018
## June 11 2018 ( Shashank Kumar )
- Adding Dashboard feature
- Adding blog, cli, communication, encryption, how_to_use, vcs and way_ahead modules for courseware
- Adding application, profile and theme modules for settings
## June 9 2018 ( Shashank Kumar )
- Added SignIn feature.
## June 7 2018 ( Shashank Kumar )
- Added SignUp feature.
## June 3 2018
## June 3 2018 ( Shashank Kumar )
- Reporting coverage while running Gitlab CI.
## June 1 2018
## June 1 2018 ( Shashank Kumar )
- Initial app setup using Kivy.
- Integrate pylint, pytest and support for Gitlab CI.
......@@ -78,6 +78,8 @@ There are two type of testing being done for this application.
- `main.py` - It contains the Root Kivy Application which is to run in order to start the GUI.
- `settings.py` - This modules sets up the database which is later integrated with the entire application.
- `data` - It contains static application data which can be used by application at anytime. It helps when the machine is offine.
- `docs` - This directory should and only contain the documentations for developers, contributors and end users.
......@@ -86,7 +88,46 @@ There are two type of testing being done for this application.
- `tests` - This directory should and only contain the Test written for the application, both for application logic and GUI.
- `ui` - This directory should and only contain the `.kv` files which uses Kivy Language in order to create the widget tree. Hence, making the UI designing easier.
- `ui` - This directory should and only contain the `.kv` files which uses Kivy Language in order to create the widget tree.
### Sign Up Module
- `modules/signup/` contains the Python logic for Sign Up. They can be described as below.
- `exceptions.py` contains SignUp module specific custom exception classes
- `services.py` contains service functions for SignUp module
- `signup.py` contains KIVY UI and other integrations for SignUp module
- `validations.py` contains user information validation functions
- `utils.py` contains utility functions to help out with SignUp operations
- `ui/signup.kv` file contains KIVY widget tree which in turn renders the UI for the Sign Up module.
- `tests/signup/` contains written tests for the Sign Up module. They can be described as below.
- `test_services.py` - contains tests for `modules/signup/services.py`
- `test_utils.py` - contains tests for `modules/signup/utils.py`
- `test_validation.py` - contains tests for `modules/signup/validations.py`
<<<<<<< HEAD
### SignIn Module
`modules/signin/` contains the Python logic for Sign In. They can be described as below.
- `exceptions.py` contains SignIn module specific custom exception classes
- `services.py` contains service functions for SignIn module
- `signin.py` contains KIVY UI and other integrations for SignIn module
- `validations.py` contains user information validation functions
- `utils.py` contains utility functions to help out with SignIn operations
- `ui/signin.kv` file contains KIVY widget tree which in turn renders the UI for the Sign Up module.
- `tests/signin/` contains written tests for the Sign Up module. They can be described as below.
- `test_services.py` - contains tests for `modules/signin/services.py`
- `test_utils.py` - contains tests for `modules/signin/utils.py`
- `test_validation.py` - contains tests for `modules/signin/validations.py`
=======
>>>>>>> d6b460424e6b55369e231ec38aae947e963f3dc8
### The Dashboard
......@@ -119,4 +160,3 @@ There are two type of testing being done for this application.
- `application.py` module contains application specific settings like language
- `profile.py` module contains user related settings like name, email etc
- `theme.py` module contains application theme related settings like color
......@@ -3,25 +3,41 @@ Root Kivy Application
'''
from kivy.app import App
from kivy.config import Config
from kivy.uix.screenmanager import ScreenManager
from modules.dashboard.dashboard import Dashboard
from settings import initializing_database
from modules.signup import signup
from modules.signin import signin
from modules.dashboard import dashboard
class NewContributorWizard(App):
'''
Declaration of Root Kivy App which contains Root Widget
'''
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen_manager_obj = ScreenManager()
self.screen_manager_obj.add_widget(signup.SignUp(name='signup'))
self.screen_manager_obj.add_widget(signin.SignIn(name='signin'))
self.screen_manager_obj.add_widget(dashboard.Dashboard(name='dashboard'))
def build(self):
return Dashboard()
return self.screen_manager_obj
if __name__ == '__main__':
'''
Setting up things
'''
initializing_database()
'''
Setting window width to 720px and height to 480px
'''
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '480')
Config.set('graphics', 'resizable', False)
'''
Running Kivy application and building root Widget
......
......@@ -5,6 +5,7 @@ from copy import copy as cp
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from modules.blog.blog import Blog
from modules.cli.cli import CLI
......@@ -21,12 +22,12 @@ from modules.settings.theme import ThemeSettings
Builder.load_file('./ui/dashboard.kv')
class Dashboard(BoxLayout):
class Dashboard(BoxLayout, Screen):
'''
Dashboard class to integrate courseware and settings
'''
def __init__(self, **kwargs):
super(Dashboard, self).__init__(**kwargs)
super().__init__(**kwargs)
self.all_menu_screens = {
'application_settings': ApplicationSettings(),
'blog': Blog(),
......@@ -40,7 +41,6 @@ class Dashboard(BoxLayout):
'way_ahead': WayAhead(),
}
self.all_menu_items = list(self.all_menu_screens.keys())
self.enable_menu('how_to_use')
def on_touch_down(self, touch):
......@@ -51,6 +51,7 @@ class Dashboard(BoxLayout):
for menu_item in self.all_menu_items:
if self.ids[menu_item+'_box'].collide_point(*touch.pos):
self.enable_menu(menu_item)
return super().on_touch_down(touch)
def enable_menu(self, menu_item_to_enable):
'''
......
'''
This class contains signin module specific exceptions
'''
class SignInError(Exception):
'''
SignInError class can be used to raise exception related to
Sign In module
'''
def __init__(self, message):
self.message = message
super().__init__(message)
'''
This modules contain classes to query sqlite3 database
'''
from settings import initializing_database, USER_INFOMATION_TABLE
from modules.signin.exceptions import SignInError
from modules.signin.utils import (
clean_email,
hash_password
)
def sign_in_user(**user_info):
'''
sign_in_user would try to check for user's email and hashed
password in the database
Would result in a UserError if email doesn't exist
Would result in a PasswordError if password doesn't match
'''
connection = initializing_database()
db_cursor = connection.cursor()
user_info['email'] = clean_email(user_info['email'])
hashed_pass = hash_password(user_info['password'])
sign_in_query = 'SELECT * FROM {} WHERE email=?'.format(USER_INFOMATION_TABLE)
user_info = db_cursor.execute(sign_in_query, (user_info['email'], )).fetchone()
if not user_info:
raise SignInError('Email does not exist!')
elif user_info[2] != hashed_pass:
raise SignInError('Password is incorrect!')
return user_info
'''
Class for SignIn Screen
'''
import logging
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen
from modules.signin.services import sign_in_user
from modules.signin.exceptions import SignInError
from modules.signin.validations import (
validate_email,
validate_password
)
Builder.load_file('./ui/signin.kv')
class SignIn(BoxLayout, Screen):
'''
Declaration of SignIn Screen Class
'''
def __init__(self, **kwargs):
'''
Creating instance of vaidation class
Creating instance of updateBD class
'''
super().__init__(**kwargs)
def on_touch_down(self, touch):
'''
on_touch_down is triggered when user clicks the application anywhere
overiding this function here to perform actions on selection of menu items
'''
if self.ids['signup_info'].collide_point(*touch.pos):
self.manager.transition.direction = 'up'
self.manager.current = 'signup'
return super().on_touch_down(touch)
def prompt_error_message(self, label, error_text):
'''
Displays error message on the UI on the respective label widget
'''
original_text = self.ids[label].text
self.ids[label].text = error_text
self.ids[label].color = (255, 0, 0, 1)
def replace_label(*args):
'''
Replacing original text in label
delay time is defined by args[0]
'''
logging.info(
'\'%s\' changed to \'%s\' after %s seconds',
error_text,
original_text,
args[0]
)
self.ids[label].text = original_text
self.ids[label].color = (255, 255, 255, 1)
Clock.schedule_once(replace_label, 2)
def validate(self):
'''
Validating Email and Password provided by user
'''
email_validation = True
pass_validation = True
user_email = self.ids["user_email"].text
try:
validate_email(user_email)
except SignInError as error:
email_validation = False
self.prompt_error_message(
'email_label',
error.message,
)
password = self.ids['password'].text
try:
validate_password(password)
except SignInError as error:
pass_validation = False
self.prompt_error_message(
'password_label',
error.message,
)
return email_validation and pass_validation
def sign_up(self):
'''
Signin user in case of successful validation
Prompting error message to the user otherwise
'''
if self.validate():
user_email = self.ids["user_email"].text
user_pass = self.ids["password"].text
user_info = {
'email': user_email,
'password': user_pass,
}
try:
user_info = sign_in_user(**user_info)
self.manager.transition.direction = 'left'
self.manager.current = 'dashboard'
except SignInError as error:
self.prompt_error_message(
'email_label',
error.message
)
'''
This module contains utility functions
'''
import hashlib
def clean_email(user_email):
'''
clean_email removes unnecessary spaces from Full Name
'''
return user_email.strip('\t\n\r ')
def hash_password(password):
'''
hash_password converts plain text password into sha256 hash
'''
hased_pass = hashlib.sha256(password.encode()).hexdigest()
return hased_pass
'''
This module contains Validation functions
'''
import re
from modules.signin.utils import clean_email
from modules.signin.exceptions import SignInError
def validate_email(user_email):
'''
Validating Email provided to check for proper mail format and
only alphabets
'''
user_email = clean_email(user_email)
if not re.match(r'[^@]+@[^@]+\.[^@]+', user_email):
raise SignInError('Incorrect Format')
user_email = user_email.strip(' ').split('.')
for parts in user_email:
parts = parts.split('@')
if not all(part.isalpha() for part in parts):
raise SignInError('Incorrect Format')
return True
def validate_password(password):
'''
Validating whether or not the password is submitted by
the user
'''
if not password:
raise SignInError('Enter password')
return True
'''
Exceptions module contains exception classes specific to signup module
'''
class SignUpError(Exception):
'''
SignUpError class can be used to raise exception related to
User's imput
'''
def __init__(self, message):
self.message = message
super().__init__(message)
'''
This module contains services to be utilized by the application
'''
import sqlite3
from settings import initializing_database, USER_INFOMATION_TABLE
from modules.signup.exceptions import SignUpError
from modules.signup.utils import (
generate_uuid,
clean_email,
clean_full_name,
hash_password
)
def sign_up_user(**user_info):
'''
sign_up_user creates connection with the sqlite3 database,
calls methods to clean up full_name, convert password into
hash and query database to save user's information.
Would result in a False statement if the Email is already
present.
'''
connection = initializing_database()
db_cursor = connection.cursor()
user_info['table_name'] = USER_INFOMATION_TABLE
user_info['user_id'] = generate_uuid()
user_info['email'] = clean_email(user_info['email'])
user_info['password'] = hash_password(user_info['password'])
user_info['full_name'] = clean_full_name(user_info['full_name'])
try:
sign_up_query = '''
INSERT INTO {table_name} VALUES
("{user_id}",
"{email}",
"{password}",
"{full_name}",
"{language}",
"{timezone}")
'''
db_cursor.execute(sign_up_query.format(**user_info))
connection.commit()
except sqlite3.IntegrityError:
raise SignUpError('Email already exists')
return True
'''
Class for SignUp Screen
'''
import logging
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen
from modules.signup.services import sign_up_user
from modules.signup.exceptions import SignUpError
from modules.signup.validations import (
validate_email,
validate_first_pass,
validate_confirm_pass,
validate_full_name
)
Builder.load_file('./ui/signup.kv')
class SignUp(BoxLayout, Screen):
'''
Declaration of SignUp Screen Class
'''
def __init__(self, **kwargs):
'''
Creating instance of vaidation class
Creating instance of updateBD class
'''
super().__init__(**kwargs)
def on_touch_down(self, touch):
'''
on_touch_down is triggered when user clicks the application anywhere
overiding this function here to perform actions on selection of menu items
'''
if self.ids['login_info'].collide_point(*touch.pos):
self.manager.transition.direction = 'down'
self.manager.current = 'signin'
return super().on_touch_down(touch)
def prompt_error_message(self, label, error_text):
'''
Displays error message on the UI on the respective label widget
'''
original_text = self.ids[label].text
self.ids[label].text = error_text
self.ids[label].color = (255, 0, 0, 1)
def replace_label(*args):
'''
Replacing original text in label
delay time is defined by args[0]
'''
logging.info(
'\'%s\' changed to \'%s\' after %s seconds',
error_text,
original_text,
args[0]
)
self.ids[label].text = original_text
self.ids[label].color = (255, 255, 255, 1)
Clock.schedule_once(replace_label, 2)
def validate(self):
'''
Validating Email, Password and Full Name provided by user
'''
email_validation = True
pass_validation = True
full_name_validation = True
user_email = self.ids['user_email'].text
try:
validate_email(user_email)
except SignUpError as error:
email_validation = False
self.prompt_error_message(
'email_label',
error.message,
)
first_pass = self.ids['first_pass'].text
try:
validate_first_pass(first_pass)
except SignUpError as error:
pass_validation = False
self.prompt_error_message(
'first_pass_label',
error.message,
)
confirm_pass = self.ids['confirm_pass'].text
try:
validate_confirm_pass(first_pass, confirm_pass)
except SignUpError as error:
pass_validation = False
self.prompt_error_message(
'confirm_pass_label',
error.message,
)
full_name = self.ids['user_full_name'].text
try:
validate_full_name(full_name)
except SignUpError as error:
full_name_validation = False
self.prompt_error_message(
'full_name_label',
error.message,
)
return email_validation and pass_validation and full_name_validation
def sign_up(self):
'''
Signing Up user's account in case of successful validation
Prompting error message to the user otherwise
'''
if self.validate():
user_email = self.ids['user_email'].text
user_pass = self.ids['first_pass'].text
user_full_name = self.ids['user_full_name'].text
user_language = self.ids['user_language'].text
user_timezone = self.ids['user_timezone'].text
user_info = {
'email': user_email,
'password': user_pass,
'full_name': user_full_name,
'language': user_language,
'timezone': user_timezone
}
try:
sign_up_user(**user_info)
self.manager.transition.direction = 'left'
self.manager.current = 'dashboard'
except SignUpError as error:
self.prompt_error_message(
'email_label',
error.message
)
'''
This module contains utility functions
'''
import hashlib
from uuid import uuid4
def generate_uuid():
'''
generate_uuid return a universally unique identifier string
'''
return str(uuid4())
def clean_email(user_email):
'''
clean_email removes unnecessary spaces from Full Name
'''
return user_email.strip('\t\n\r ')
def clean_full_name(full_name):
'''
clean_full_name removes unnecessary spaces from Full Name
'''
full_name = ' '.join(full_name.split())
return full_name.strip('\t\n\r ')
def hash_password(password):
'''
hash_password converts plain text password into sha256 hash
'''
hased_pass = hashlib.sha256(password.encode()).hexdigest()
return hased_pass
'''
This module contains Validation functions
'''
import re
from modules.signup.exceptions import SignUpError
from modules.signup.utils import (
clean_email,
clean_full_name)
def validate_email(user_email):
'''
Validating Email provided to check for proper mail format and
only alphabets
'''
user_email = clean_email(user_email)
if not re.match(r'[^@]+@[^@]+\.[^@]+', user_email):
raise SignUpError('Incorrect Format')
user_email = user_email.strip(' ').split('.')
for parts in user_email:
parts = parts.split('@')
if not all(part.isalpha() for part in parts):
raise SignUpError('Incorrect Format')
return True
def validate_first_pass(first_pass):
'''
Validating whether or not the first password is submitted by
the user
'''
if not first_pass:
raise SignUpError('Enter password')
elif len(first_pass) < 6:
raise SignUpError('Password is short')
return True
def validate_confirm_pass(first_pass, confirm_pass):
'''
Validating whether both the password submitted by the user
match each other
'''
if first_pass != confirm_pass:
raise SignUpError('Password is not same')
return True
def validate_full_name(full_name):
'''
Validating whether Full Name is provided by user and is in
correct alphabet format
'''
full_name = clean_full_name(full_name)
if not full_name:
raise SignUpError('Enter Full Name')
for name in full_name.split():
if not name.isalpha():
raise SignUpError('Incorrect Format')
return True
'''
Initializing application dependencies
- Sqlite3 database
'''
import sqlite3
DATABASE_FILE = 'new_contributor_wizard.db'