Commit a2f3a520 authored by Shashank Kumar's avatar Shashank Kumar

Adds Sign In feature

Sign In feature added in modules/signin/
Sign In UI added in ui/signin.kv
Tests for Sign In feature added in tests/signin/
docs/developer.md updated with Sign In feature description
changelog updated
.gitignore updated to ignore database file
parent 26413bd3
# For Tests
.pytest_cache
.coverage
# Database File
new_contributor_wizard.db
v0.0.1
## June 9 2018
- Added SignIn feature.
## June 3 2018
- Reporting coverage while running Gitlab CI.
......
......@@ -87,3 +87,19 @@ 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.
### SignIn Module
- `modules/signin` contains the python login for Sign In. They can be described as below.
- `signin.py` contains KIVY integration for the Sign Ip module
- `validations.py` contains user information validation class and methods
- `updatedb.py` contains Sqlite3 integration for the Sign In module
- `exceptions.py` contains Sign In module specific exceptions
- `ui/signin.kv` file contains KIVY widget tree which in turn renders the UI for the Sign In module.
- `tests/signin/` contains written tests for the Sign In module. They can be described as below.
- test_updatedb.py - contains tests for `modules/signin/updatedb.py` and validates all the database operations defined in it.
- test_validation.py - contains tests for `modules/signin/validations.py` and validated all the methods define in it.
......@@ -4,7 +4,7 @@ Root Kivy Application
from kivy.app import App
from kivy.config import Config
from modules.welcome import WelcomeScreen
from modules.signin.signin import SignIn
class NewContributorWizard(App):
......@@ -13,7 +13,7 @@ class NewContributorWizard(App):
'''
def build(self):
return WelcomeScreen()
return SignIn()
if __name__ == '__main__':
......
'''
This class contains signin module specific exceptions
'''
class UserError(Exception):
'''
UserError class can be used to raise exception related to
User's information
'''
def __init__(self, message):
super().__init__(message)
class PasswordError(Exception):
'''
PasswordError class can be used to raise exception related to
User's Password information
'''
def __init__(self, message):
super().__init__(message)
'''
Class for SignIn Screen
'''
import logging
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.clock import Clock
from modules.signin.validations import Validation
from modules.signin.updatedb import UpdateDB
from modules.signin.exceptions import UserError, PasswordError
Builder.load_file('./ui/signin.kv')
class SignIn(BoxLayout):
'''
Declaration of SignIn Screen Class
'''
def __init__(self, **kwargs):
'''
Creating instance of vaidation class
Creating instance of updateBD class
'''
self.validation = Validation()
self.update_db = UpdateDB()
super().__init__(**kwargs)
@staticmethod
def prompt_error_message(widget_object, label, original_text, error_text):
'''
Displays error message on the UI on the respective label widget
'''
widget_object.ids[label].text = error_text
widget_object.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]
)
widget_object.ids[label].text = original_text
widget_object.ids[label].color = (255, 255, 255, 1)
Clock.schedule_once(replace_label, 2)
def validate(self, widget_object):
'''
Validating Email and Password provided by user
'''
email_validation = True
pass_validation = True
user_email = widget_object.ids["user_email"].text
if not self.validation.validate_email(user_email):
email_validation = False
self.prompt_error_message(
widget_object,
'email_label',
'Email',
'Invalid Email Address'
)
password = widget_object.ids['password'].text
if not self.validation.validate_password(password):
pass_validation = False
self.prompt_error_message(
widget_object,
'password_label',
'Password',
'Enter Password'
)
return email_validation and pass_validation
def sign_up(self, widget_object):
'''
Signin user in case of successful validation
Prompting error message to the user otherwise
'''
if self.validate(widget_object):
user_email = widget_object.ids["user_email"].text
user_pass = widget_object.ids["password"].text
user_info = [
user_email,
user_pass,
]
try:
user_info = self.update_db.sign_in_user(*user_info)
widget_object.ids['user_info_screen'].clear_widgets()
widget_object.ids['submit_info'].clear_widgets()
widget_object.ids['signup_info'].clear_widgets()
widget_object.ids['sign_in_top_label'].size_hint = (1, 1)
widget_object.ids['sign_in_top_label'].font_size = 30
widget_object.ids['sign_in_top_label'].halign = 'center'
confirmation_text = 'Congratulations\n\n{}\n\nYou have successfully logged in!'
widget_object.ids['sign_in_top_label'].text = confirmation_text.format(user_info[3])
except UserError:
self.prompt_error_message(
widget_object,
'email_label',
'Email',
'Email does not exist!'
)
except PasswordError:
self.prompt_error_message(
widget_object,
'password_label',
'Password',
'Password is incorrect!'
)
'''
This modules contain classes to query sqlite3 database
'''
import sqlite3
import hashlib
from modules.signin.exceptions import UserError, PasswordError
class UpdateDB:
'''
UpdateDB class connects with new_contributor_wizard.db and query
to check user's infomation and sign them in
'''
def __init__(self, db_name=''):
if not db_name:
self.connection = sqlite3.connect('new_contributor_wizard.db')
self.db_name = 'new_contributor_wizard.db'
else:
self.connection = sqlite3.connect(db_name)
self.db_name = db_name
self.db_cursor = self.connection.cursor()
try:
self.db_cursor.execute('''CREATE TABLE USERS
(id VARCHAR(36) PRIMARY KEY,
email UNIQUE,
pass VARCHAR(64),
fullname TEXT,
language TEXT,
timezone TEXT)
''')
self.connection.commit()
self.connection.close()
except sqlite3.OperationalError:
self.connection.close()
@staticmethod
def clean_email(user_email):
'''
clean_email removes unnecessary spaces from Full Name
'''
return user_email.strip('\t\n\r ')
@staticmethod
def hash_password(password):
'''
hash_password converts plain text password into sha256 hash
'''
hased_pass = hashlib.sha256(password.encode()).hexdigest()
return hased_pass
def sign_in_user(self, *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
'''
self.connection = sqlite3.connect(self.db_name)
self.db_cursor = self.connection.cursor()
user_email = self.clean_email(user_info[0])
hased_pass = self.hash_password(user_info[1])
user_info = (user_email, )
sign_in_query = '''SELECT * FROM USERS
WHERE email=?
'''
user_info = self.db_cursor.execute(sign_in_query, user_info).fetchone()
self.connection.close()
if not user_info:
raise UserError('Email does not exist!')
elif user_info[2] != hased_pass:
raise PasswordError('Password is incorrect!')
return user_info
'''
This module contains Validation classes
'''
import re
class Validation:
'''
This class contains functions to validate Email and Password
provided by the user
'''
@staticmethod
def validate_email(user_email):
'''
Validating Email provided to check for proper mail format and
only alphabets
'''
user_email = user_email.strip('\t\n\r ')
if not re.match(r'[^@]+@[^@]+\.[^@]+', user_email):
return False
user_email = user_email.strip(' ').split('.')
for parts in user_email:
parts = parts.split('@')
if not all(part.isalpha() for part in parts):
return False
return True
@staticmethod
def validate_password(password):
'''
Validating whether or not the first password is submitted by
the user
'''
return password
'''
Class for Welcome Screen
'''
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
Builder.load_file('./ui/welcome.kv')
class WelcomeScreen(GridLayout):
'''
Declaration of Welcome Screen which is the first screen to show
after Kivy application is running
'''
def __init__(self, **kwargs):
super(WelcomeScreen, self).__init__(**kwargs)
import pytest
import sqlite3
import os
from modules.signin.updatedb import UpdateDB
from modules.signin.exceptions import UserError, PasswordError
class TestUpdateDB:
def setup_method(self):
self.update_db = UpdateDB('tempDB.db')
self.connection = sqlite3.connect('tempDB.db')
self.db_cursor = self.connection.cursor()
sign_up_query = '''INSERT INTO USERS
(email,
pass,
fullname,
language,
timezone)
VALUES
('abc@shanky.xyz',
'605fcc4bcef3ce81c818304b6e9e9074977a84be7493acae33ac1ab2e56d99dc',
'Shashank Kumar',
'English',
'UTC+5:30')
'''
self.db_cursor.execute(sign_up_query)
self.connection.commit()
self.connection.close()
def test_clean_email(self):
assert self.update_db.clean_email('abc@shanky.xyz ') == 'abc@shanky.xyz'
assert self.update_db.clean_email(' abc@shanky.xyz ') == 'abc@shanky.xyz'
assert self.update_db.clean_email('abc@shanky.xyz ') == 'abc@shanky.xyz'
assert self.update_db.clean_email('\nabc@shanky.xyz ') == 'abc@shanky.xyz'
def test_hash_password(self):
assert self.update_db.hash_password('mynewpass') == '605fcc4bcef3ce81c818304b6e9e9074977a84be7493acae33ac1ab2e56d99dc'
def test_sign_in(self):
user_info = self.update_db.sign_in_user(
'abc@shanky.xyz',
'mynewpass',
)
assert user_info
with pytest.raises(UserError):
user_info = self.update_db.sign_in_user(
'shashankkumarkushwaha@gmail.com',
'mynewpass',
)
with pytest.raises(PasswordError):
user_info = self.update_db.sign_in_user(
'abc@shanky.xyz',
'myoldpass',
)
def teardown_method(self):
os.remove('tempDB.db')
import pytest
from modules.signin.validations import Validation
class TestValidation:
def test_validate_email(self):
assert Validation.validate_email('abc@shanky.xyz')
assert Validation.validate_email(' abc@shanky.xyz')
assert Validation.validate_email('abc@shanky.xyz ')
assert Validation.validate_email(' abc@shanky.xyz ')
assert not Validation.validate_email('abcshanky.xyz')
assert not Validation.validate_email('abc@!shanky.xyz')
assert not Validation.validate_email('')
assert not Validation.validate_email(' ')
def test_validate_password(self):
assert Validation.validate_password('mynewpass')
assert not Validation.validate_password('')
import pytest
from datetime import datetime as dt
from modules.communication.sample_module import SampleCommunicationModule
@pytest.mark.incremental
class Testsample:
def test_return_datetime_object(self):
resultant_datetime_object = SampleCommunicationModule.return_datetime_object("3/3/2018 12:02", "%m/%d/%Y %H:%M")
assert type(dt.now()) == type(resultant_datetime_object)
def test_unix_time_of_communication(self):
resultant_unix_timestamp = SampleCommunicationModule.unix_time_of_communication("3/3/2018 12:02", "%m/%d/%Y %H:%M")
assert 1520078520 == resultant_unix_timestamp
<SignIn>:
BoxLayout:
size_hint: 0.35, 1
orientation: 'vertical'
Label:
text_size: self.size
size: self.texture_size
text: 'New'
halign: 'right'
valign: 'bottom'
font_name: 'ui/assets/fonts/Anton-Regular.ttf'
font_size: 50
padding_x: 10
Label:
text_size: self.size
size: self.texture_size
text: 'Contributor'
halign: 'right'
valign: 'middle'
font_name: 'ui/assets/fonts/Anton-Regular.ttf'
font_size: 50
padding_x: 10
Label:
text_size: self.size
size: self.texture_size
text: 'Wizard'
halign: 'right'
valign: 'top'
font_name: 'ui/assets/fonts/Anton-Regular.ttf'
font_size: 50
padding_x: 10
BoxLayout:
size_hint: 0.65, 1
orientation: 'vertical'
canvas.before:
Color:
rgba: 0.114, 0.306, 0.537, 1
Rectangle:
pos: self.pos
size: self.size
Label:
size_hint: 1, 0.16
text: 'Welcome Back'
font_name: 'ui/assets/fonts/VarelaRound-Regular.ttf'
font_size: 20
id: sign_in_top_label
BoxLayout:
size_hint: 1, 0.5
FloatLayout:
size_hint: 0.20, 1
BoxLayout:
size_hint: 0.5, 1
id: user_info_screen
orientation: 'vertical'
Label:
text_size: self.size
size: self.texture_size
text: 'Email'
font_name: 'ui/assets/fonts/VarelaRound-Regular.ttf'
font_size: 20
halign: 'right'
id: email_label
BoxLayout:
size_hint: 1, 0.5
padding: 20, 0, 0, 0
TextInput:
multiline: False
font_name: 'ui/assets/fonts/VarelaRound-Regular.ttf'
font_size: 18
halign: 'right'
id: user_email
Label:
text_size: self.size
size: self.texture_size
text: 'Password'
font_name: 'ui/assets/fonts/VarelaRound-Regular.ttf'
font_size: 20
halign: 'right'
id: password_label
BoxLayout:
size_hint: 1, 0.5
padding: 20, 0, 0, 0
TextInput:
multiline: False
font_name: 'ui/assets/fonts/VarelaRound-Regular.ttf'
font_size: 18
halign: 'right'
password: True
id: password
FloatLayout:
size_hint: 0.30, 1
BoxLayout:
size_hint: 1, 0.16
id: submit_info
FloatLayout:
Button:
text_size: self.size
size: self.texture_size
size_hint: 0.7, 0.6
text: 'Sign In'
background_normal: ''
background_color: 0, 0, 0, 1
font_name: 'ui/assets/fonts/VarelaRound-Regular.ttf'
font_size: 20
padding: 0, 0
halign: 'center'
valign: 'middle'
on_press: root.sign_up(root)
FloatLayout:
BoxLayout:
size_hint: 1, 0.16
id: signup_info
Label:
text: 'New User? Sign Up'
font_name: 'ui/assets/fonts/VarelaRound-Regular.ttf'
font_size: 20
<WelcomeScreen>:
cols: 2
Label:
text: "Hello"
Label:
text: "World"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment