-
Notifications
You must be signed in to change notification settings - Fork 0
Mvc framework #1
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
Open
AlikZi
wants to merge
14
commits into
newMaster
Choose a base branch
from
mvcFramework
base: newMaster
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
51ed801
Add config.py and run.py files
AlikZi c1d8370
Move category routes from init.py to categories package
AlikZi 42e0265
Move product routes from init.py to products package
AlikZi c93d944
Move json and login routes to 'jsonAPI' and 'auth' packages
AlikZi 89a2a21
Create database connector module
AlikZi 82d2a7b
Create services for categories, products, config and authentification
AlikZi c668d31
Remove repetitiveness using methods from services
AlikZi 8e3bd80
Start using DBConnector from db.py
AlikZi af6f7f5
Fix login button
AlikZi 26eedd0
Make db.py PEP8 compliant
AlikZi 0b719f0
Create a single database session
AlikZi c1f90fa
Add User Services and use them instead of quering database
AlikZi 3ecbd12
Use full word in variable names instead of shortened
AlikZi a548afd
Style google sign-in button
AlikZi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| ''' | ||
| Project 4 for Udacity Fullstack Nanodegree | ||
| Author: Aleksandr Zonis | ||
| ''' | ||
|
|
||
| # Modules from Flask Library | ||
| from flask import Flask | ||
| import config | ||
|
|
||
| # Route modules | ||
| from categories.routes import categories | ||
| from products.routes import products | ||
| from jsonAPI.routes import jsonAPI | ||
| from auth.routes import auth | ||
|
|
||
| app = Flask(__name__) | ||
|
|
||
| # Configure the Application | ||
| app.config.from_object(config.DevelopmentConfig) | ||
|
|
||
| # Register Routes | ||
| app.register_blueprint(categories) | ||
| app.register_blueprint(products) | ||
| app.register_blueprint(jsonAPI) | ||
| app.register_blueprint(auth) | ||
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| # Modules from Flask Library | ||
| from flask import Flask, render_template, request, flash, Blueprint | ||
| from flask import session as login_session | ||
| from flask import make_response, redirect, jsonify, url_for | ||
| # Access database | ||
| from project.models import Base, Category, Product, User | ||
| from project.db import session | ||
| # Services | ||
| from project.services.categories import CategoryService | ||
| from project.services.config import ConfigService | ||
| from project.services.user import UserService | ||
|
|
||
| import random | ||
| import string | ||
| import httplib2 | ||
| import json | ||
| import requests | ||
|
|
||
| # Authentification modules | ||
| from oauth2client.client import flow_from_clientsecrets | ||
| from oauth2client.client import FlowExchangeError | ||
|
|
||
|
|
||
| auth = Blueprint('auth', __name__) | ||
|
|
||
|
|
||
| # Instantiate services | ||
| category_service = CategoryService() | ||
| config_service = ConfigService() | ||
| user_service = UserService() | ||
|
|
||
| # Get Client ID | ||
| CLIENT_ID = config_service.get_setting('client_id') | ||
|
|
||
| # Status Code Constansts | ||
| HTTP_STATUS_CODE_OK = 200 | ||
| HTTP_STATUS_CODE_UNAUTHORIZED = 401 | ||
| HTTP_STATUS_CODE_ERROR = 500 | ||
|
|
||
| @auth.route('/login') | ||
| def showLogin(): | ||
| """App route function to display login page.""" | ||
| state = ''.join(random.choice(string.ascii_uppercase + string.digits) | ||
| for x in range(32)) | ||
| login_session['state'] = state | ||
| return render_template('login.html', | ||
| STATE=state, | ||
| categories=category_service.get_all_categories(), | ||
| CLIENT_ID=CLIENT_ID) | ||
|
|
||
|
|
||
| @auth.route('/googleConnect', methods=['POST']) | ||
| def googleConnect(): | ||
| """Connect via Google Account and fetch User info.""" | ||
| # Validate state token | ||
| if request.args.get('state') != login_session['state']: | ||
| make_json_response('Invalid state parameter.', | ||
| HTTP_STATUS_CODE_UNAUTHORIZED) | ||
| # Obtain authorization code | ||
| code = request.data | ||
|
|
||
| try: | ||
| # Upgrade the authorization code into a credentials object | ||
| oauth_flow = flow_from_clientsecrets('client_secrets.json', scope='') | ||
| oauth_flow.redirect_uri = 'postmessage' | ||
| credentials = oauth_flow.step2_exchange(code) | ||
| except FlowExchangeError: | ||
| make_json_response('Invalid state parameter.', | ||
| HTTP_STATUS_CODE_UNAUTHORIZED) | ||
|
|
||
| # Check that the access token is valid. | ||
| access_token = credentials.access_token | ||
| url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={}' | ||
| .format(access_token)) | ||
| h = requests.get(url=url) | ||
| result = json.loads(h.text) | ||
| # If there was an error in the access token info, abort. | ||
| if result.get('error') is not None: | ||
| make_json_response('error', HTTP_STATUS_CODE_ERROR) | ||
|
|
||
| # Verify that the access token is used for the intended user. | ||
| gplus_id = credentials.id_token['sub'] | ||
| if result['user_id'] != gplus_id: | ||
| make_json_response('Invalid state parameter.', | ||
| HTTP_STATUS_CODE_UNAUTHORIZED) | ||
|
|
||
| # Verify that the access token is valid for this app. | ||
| if result['issued_to'] != CLIENT_ID: | ||
| make_json_response('Invalid state parameter.', | ||
| HTTP_STATUS_CODE_UNAUTHORIZED) | ||
|
|
||
| stored_access_token = login_session.get('access_token') | ||
| stored_gplus_id = login_session.get('gplus_id') | ||
| if stored_access_token is not None and gplus_id == stored_gplus_id: | ||
| make_json_response('Current user is already connected.', | ||
| HTTP_STATUS_CODE_OK) | ||
|
|
||
| # Store the access token in the session for later use. | ||
| login_session['access_token'] = credentials.access_token | ||
| login_session['gplus_id'] = gplus_id | ||
|
|
||
| # Get user info | ||
| userinfo_url = "https://www.googleapis.com/oauth2/v1/userinfo" | ||
| params = {'access_token': credentials.access_token, 'alt': 'json'} | ||
| answer = requests.get(userinfo_url, params=params) | ||
|
|
||
| data = answer.json() | ||
|
|
||
| login_session['username'] = data['email'] | ||
| login_session['email'] = data['email'] | ||
| # ADD PROVIDER TO LOGIN SESSION | ||
| login_session['provider'] = 'google' | ||
|
|
||
| # see if user exists, if it doesn't make a new one | ||
| user_id = getUserID(data["email"]) | ||
| if not user_id: | ||
| user_id = createUser(login_session) | ||
| login_session['user_id'] = user_id | ||
| result = "User is authorized!" | ||
| flash("You are now logged in as {}".format(login_session['username'])) | ||
| return result | ||
|
|
||
|
|
||
| @auth.route('/googleDisconnect') | ||
| def googleDisconnect(): | ||
| """Disconnect user""" | ||
| # Check if user is connected, | ||
| # only disconnect a connected user. | ||
| access_token = login_session.get('access_token') | ||
| if access_token is None: | ||
| response = make_response( | ||
| json.dumps('Current user not connected.'), 401) | ||
| response.headers['Content-Type'] = 'application/json' | ||
| flash("Current user not connected") | ||
| return redirect(url_for('showCategories')) | ||
| response = make_response(json.dumps('Successfully disconnected.'), 200) | ||
| response.headers['Content-Type'] = 'application/json' | ||
| # Delete all login_session info | ||
| del login_session['gplus_id'] | ||
| del login_session['access_token'] | ||
| del login_session['username'] | ||
| del login_session['email'] | ||
| del login_session['user_id'] | ||
| del login_session['provider'] | ||
| flash("You have successfully been logged out.") | ||
| return redirect(url_for('categories.showCategories')) | ||
|
|
||
|
|
||
| # User Helper Functions | ||
| def make_json_response(data, status): | ||
| """Makes http response, accepts message and status code""" | ||
| response = make_response(data, status) | ||
| response.headers['Content-Type'] = 'application/json' | ||
| return response | ||
|
|
||
|
|
||
| def createUser(login_session): | ||
| """Creates new login_session user, returns user.id.""" | ||
| newUser = User(name=login_session['username'], email=login_session[ | ||
| 'email']) | ||
| session.add(newUser) | ||
| session.commit() | ||
| user = user_service.get_user_by_email(login_session['email']) | ||
| return user.id | ||
|
|
||
|
|
||
| def getUserInfo(user_id): | ||
| """Returns User object""" | ||
| user = user_service.get_user_by_id(user_id) | ||
| return user | ||
|
|
||
|
|
||
| def getUserID(email): | ||
| """Returns user.id if exists, otherwise returns none.""" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I love the comments! |
||
| try: | ||
| user = user_service.get_user_by_email(email) | ||
| return user.id | ||
| except: | ||
| return None | ||
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| # Modules from Flask Library | ||
| from flask import Flask, render_template, request, flash, Blueprint | ||
| from flask import session as login_session | ||
| from flask import make_response, redirect, jsonify, url_for | ||
| # Access database | ||
| from project.models import Category, Product, User | ||
| from project.db import session | ||
| # Services | ||
| from project.services.categories import CategoryService | ||
| from project.services.auth import AuthService | ||
| from project.services.products import ProductService | ||
|
|
||
| import random | ||
| import string | ||
| import httplib2 | ||
| import json | ||
| import requests | ||
|
|
||
|
|
||
| # Create Blueprint 'categories' | ||
| categories = Blueprint('categories', __name__) | ||
|
|
||
|
|
||
| # Instantiate services | ||
| category_service = CategoryService() | ||
| product_service = ProductService() | ||
| auth_service = AuthService() | ||
|
|
||
| DEFAULT_CATEGORY_PRODUCT_NUMBER = 8 | ||
|
|
||
| @categories.route('/') | ||
| @categories.route('/categories/') | ||
| def showCategories(): | ||
| """App route function for main page to show all categories.""" | ||
| return render_template('index.html', | ||
| categories=category_service.get_all_categories(), | ||
| isLogin=auth_service.is_user_authorized(), | ||
| latestProducts=(product_service | ||
| .get_latest_products(DEFAULT_CATEGORY_PRODUCT_NUMBER))) | ||
|
|
||
|
|
||
| @categories.route('/categories/<int:category_id>/') | ||
| def showCategoryProducts(category_id): | ||
| """App route function to show Products of the selected Category.""" | ||
| isLogin = auth_service.is_user_authorized() | ||
| # Pick category selected by user | ||
| category = category_service.get_category_by_id(category_id) | ||
| # Check if user is Creator of Category | ||
| isCreator = False | ||
| if isLogin: | ||
| isCreator = login_session['email'] == category.user.email | ||
| return render_template('category.html', | ||
| products=(product_service | ||
| .get_products_by_category_id(category_id)), | ||
| categories=category_service.get_all_categories(), | ||
| category=category, | ||
| isLogin=isLogin, | ||
| countProducts=(product_service | ||
| .products_count(category_id)), | ||
| isCreator=isCreator) | ||
|
|
||
|
|
||
| @categories.route('/addcategory/', methods=['GET', 'POST']) | ||
| def addCategory(): | ||
| """App route function to add new Category to the Category Table.""" | ||
| if 'email' not in login_session: | ||
| return redirect('/login') | ||
| if request.method == 'POST': | ||
| if request.form['name'] == '': | ||
| flash("Please, enter Category Name") | ||
| return render_template('addcategory.html', | ||
| categories=(category_service | ||
| .get_all_categories()), | ||
| isLogin=auth_service.is_user_authorized()) | ||
| newCategory = Category(name=request.form['name'], | ||
| user_id=login_session['user_id']) | ||
| session.add(newCategory) | ||
| session.commit() | ||
| flash('New Category {} Successfully Created'.format(newCategory.name)) | ||
| return redirect(url_for('categories.showCategories')) | ||
| else: | ||
| return render_template('addcategory.html', | ||
| categories=(category_service | ||
| .get_all_categories()), | ||
| isLogin=auth_service.is_user_authorized()) | ||
|
|
||
|
|
||
| @categories.route('/editcategory/<int:category_id>/', | ||
| methods=['GET', 'POST']) | ||
| def editCategory(category_id): | ||
| """App route function to edit existing Category.""" | ||
| # If user is not logged in, inform him about it and redirect | ||
| if 'email' not in login_session: | ||
| flash("You need to Log In if you want to edit") | ||
| return redirect('/categories') | ||
| # Get category that is selected to be edited | ||
| categoryToEdit = category_service.get_category_by_id(category_id) | ||
| # Check if user is Creator, if not inform that he cannot | ||
| # do changes | ||
| if not login_session['email'] == categoryToEdit.user.email: | ||
| flash("You need to be Creator of the category to be able to edit") | ||
| return redirect('/categories') | ||
| if request.method == 'POST': | ||
| if request.form['name']: | ||
| categoryToEdit.name = request.form['name'] | ||
| else: | ||
| flash('Please, enter category name.') | ||
| return render_template('editcategory.html', | ||
| categories=(category_service | ||
| .get_all_categories()), | ||
| isLogin=auth_service.is_user_authorized(), | ||
| categoryToEdit=categoryToEdit) | ||
| session.add(categoryToEdit) | ||
| session.commit() | ||
| flash('You successfully \ | ||
| updated category to {}'.format(categoryToEdit.name)) | ||
| return redirect(url_for('categories.showCategoryProducts', | ||
| category_id=category_id)) | ||
| else: | ||
| return render_template('editcategory.html', | ||
| categories=(category_service | ||
| .get_all_categories()), | ||
| isLogin=auth_service.is_user_authorized(), | ||
| categoryToEdit=categoryToEdit) | ||
|
|
||
|
|
||
| @categories.route('/deletecategory/<int:category_id>/', methods=['GET', 'POST']) | ||
| def deleteCategory(category_id): | ||
| """App route function to delete existing Category""" | ||
| # If user is not logged in, inform him about it and redirect | ||
| if 'email' not in login_session: | ||
| flash("You need to Log In if you want to delete the category.") | ||
| return redirect('/categories') | ||
| # Get category that is selected to be edited | ||
| categoryToDelete = category_service.get_category_by_id(category_id) | ||
| # Check if user is Creator, if not inform that he cannot | ||
| # do changes | ||
| if not login_session['email'] == categoryToDelete.user.email: | ||
| flash("You need to be a Creator of the category\ | ||
| to be able to delete it") | ||
| return redirect('/categories') | ||
| # Check for method and do appropriate. If 'POST' -> delete Category, | ||
| # if 'GET' -> render 'deletecategory.html' | ||
| if request.method == 'POST': | ||
| session.delete(categoryToDelete) | ||
| session.commit() | ||
| flash('You successfully deleted \ | ||
| category "{}"'.format(categoryToDelete.name)) | ||
| return redirect(url_for('categories.showCategories')) | ||
| else: | ||
| return render_template('deletecategory.html', | ||
| categories=(category_service | ||
| .get_all_categories()), | ||
| isLogin=auth_service.is_user_authorized(), | ||
| categoryToDelete=categoryToDelete) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| class Config(): | ||
| SECRET_KEY = 'super_secret_key' | ||
| DEBUG = False | ||
|
|
||
|
|
||
| class DevelopmentConfig(Config): | ||
| DEBUG = True | ||
|
|
||
| class ProductionConfig(Config): | ||
| DEBUG = False |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| from sqlalchemy import create_engine | ||
| from sqlalchemy.orm import sessionmaker | ||
|
|
||
| from models import Base | ||
|
|
||
|
|
||
| class DBConnector(): | ||
|
|
||
| engine = create_engine('sqlite:///furniturecatalog.db', | ||
| connect_args={'check_same_thread': False}) | ||
| Base.metadata.bind = engine | ||
|
|
||
| def __init__(self): | ||
| self.DBSession = sessionmaker(bind=self.engine) | ||
|
|
||
| def get_session(self): | ||
| return self.DBSession() | ||
|
|
||
| # Connect to the database and create session | ||
| session = DBConnector().get_session() |
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice and clean! 🔥