Mvc framework#1
Conversation
|
|
||
|
|
||
| # Connect to Database and create database session | ||
| engine = create_engine('sqlite:///furniturecatalog.db', |
There was a problem hiding this comment.
It's better to move this to the config file.
| app.register_blueprint(jsonAPI) | ||
| app.register_blueprint(auth) | ||
|
|
||
|
|
There was a problem hiding this comment.
Nit: lots of extra newlines in general, also there should be only one new line at the end of each Python file (PEP8).
| from categories.routes import categories | ||
| from products.routes import products | ||
| from jsonAPI.routes import jsonAPI | ||
| from auth.routes import auth |
There was a problem hiding this comment.
Usually, all imports are grouped at the top of the file and alphabetized.
https://www.python.org/dev/peps/pep-0008/#imports
| from database_setup import Base, Category, Product, User | ||
| from models import Base, Category, Product, User | ||
|
|
||
| engine = create_engine('sqlite:///furniturecatalog.db') |
There was a problem hiding this comment.
There should be a single DB connector. You already have it in the __init__ file above, so you could just use it here.
| # declaratives can be accessed through a DBSession instance | ||
| Base.metadata.bind = engine | ||
|
|
||
| DBSession = sessionmaker(bind=engine) |
There was a problem hiding this comment.
Same here - create a single DB connector class and instantiate it whenever you need it.
|
|
||
|
|
||
| CLIENT_ID = json.loads( | ||
| open('client_secrets.json', 'r').read())['web']['client_id'] |
There was a problem hiding this comment.
I would suggest creating a config service so you could do something like this:
| open('client_secrets.json', 'r').read())['web']['client_id'] | |
| config_service = ConfigService() | |
| CLIENT_ID = config_service.get_setting('web_client_id') |
ConfigService would just load and parse JSON on init and store it in a class variable.
| from project import app | ||
|
|
||
| if __name__ == '__main__': | ||
| app.run('0.0.0.0', port=8000) No newline at end of file |
| def showCategories(): | ||
| """App route function for main page to show all categories.""" | ||
| # Check if user is logged in | ||
| isLogin = 'email' in login_session |
There was a problem hiding this comment.
This is a repetitive pattern, create a new method is_user_aiuthorized() and call it instead from all routes that require authorization.
| latestProducts = (session.query(Product) | ||
| .order_by(Product.created_date.desc()).limit(8)) | ||
| return render_template('index.html', categories=categories, | ||
| isLogin=isLogin, latestProducts=latestProducts) |
There was a problem hiding this comment.
This is a very good start!
The next step would be to decouple logic from the routes and create services. For example, you could create services subdirectory and categories.py in it. This file would contain CategoryService with a set of methods, which you could call from your routes. For ex:
services/categories.py:
from project.db import DBConnector # this does not exist, you have to create it
from project.models import Category
class CategoryService():
def __init__(self):
self.session = DBConnector().get_session()
def get_all_categories(self):
return self.session.query(Category).order_by(Category.name).all()
def get_category_by_id(self, category_id):
return self.session.query(Category).filter_by(id=cat_id).one()categories/routes.py
Once you do this for categories, products, and authentication, showCategories could look like this:
import AuthService, CategoryService, ProductService
auth_service = AuthService()
category_service = CategoryService()
product_service = ProductService()
@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(limit=8)
)
dmitrymrn
left a comment
There was a problem hiding this comment.
Very nice job! You're almost there. I left several notes on further improvements.
Also, check this https://www.python.org/dev/peps/pep-0020/ out and then https://miguelgfierro.com/blog/2018/python-pro-tips-understanding-explicit-is-better-than-implicit/
| app.register_blueprint(categories) | ||
| app.register_blueprint(products) | ||
| app.register_blueprint(jsonAPI) | ||
| app.register_blueprint(auth) |
| session.add(product12) | ||
| session.commit() | ||
|
|
||
| print('All products are added') |
There was a problem hiding this comment.
The only thing I'd recommend to change is the file name. addproducts.py reads like a service to handle product addition. In fact, you're creating some sample data. How about mock_catalog_data.py? I'd also suggest creating setup subdirectory and moving all project installation/mocking files there.
| # Check that the access token is valid. | ||
| access_token = credentials.access_token | ||
| url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s' | ||
| % access_token) |
There was a problem hiding this comment.
Nit: nowadays, it's a good habit to use .format() instead of % for string formatting. For ex:
| % access_token) | |
| url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={}'.format( | |
| access_token, | |
| )``` |
|
|
||
|
|
||
| def getUserID(email): | ||
| """Returns user.id if exists, otherwise returns none.""" |
| if request.args.get('state') != login_session['state']: | ||
| response = make_response(json.dumps('Invalid state parameter.'), 401) | ||
| response.headers['Content-Type'] = 'application/json' | ||
| return response |
There was a problem hiding this comment.
This pattern is repeated over 10 times in this file. When you see something like this, it's always a good candidate for refactoring and DRYing your code.
Additionally, it's better (according to code conventions) to use constants for repeated and/or static values. Response statuses are a good example.
In this particular case, you could do something like this:
HTTP_STATUS_CODE_OK = 200
HTTP_STATUS_CODE_UNAUTHORIZED = 401
HTTP_STATUS_CODE_ERROR = 500
def make_json_response(data, status):
response = make_response(data, status)
response.headers['Content-Type'] = 'application/json'
return responseand then
@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']:
return make_json_response('Invalid state parameter.', HTTP_STATUS_CODE_UNAUTHORIZED)P.S. as far as I remember, you don't need to do json.dumps for simple data types like strings or ints.
| categories=category_service.get_all_categories(), | ||
| isLogin=auth_service.is_user_authorized(), | ||
| latestProducts=(product_service | ||
| .get_latest_products(8))) |
There was a problem hiding this comment.
Same here: assign 8 to a constant, for ex DEFAULT_CATEGORY_PRODUCT_NUMBER and use the constant.
| flash("You need to Log In if you want to delete product") | ||
| return redirect('/login') | ||
| # Get the Product that User wants to delete | ||
| productToDelete = session.query(Product).filter_by(id=prod_id).one() |
There was a problem hiding this comment.
You did a good job moving most of DB calls to services! However, there should be no two ways to get objects of a certain type. You either always use a service (preferred) or use queries against models directly (better to avoid). For ex (you already have this method in your service):
| productToDelete = session.query(Product).filter_by(id=prod_id).one() | |
| productToDelete = product_service.get_product_by_id(prod_id) |
Also, it's a good habit to spell out words in full in variable names. I.e. product_id instead of prod_id, category_name instead of cat_name, etc.
| 'email']) | ||
| session.add(newUser) | ||
| session.commit() | ||
| user = session.query(User).filter_by(email=login_session['email']).one() |
There was a problem hiding this comment.
This can be a part of the AuthService, as well as creating a new user (above). I.e.:
| user = session.query(User).filter_by(email=login_session['email']).one() | |
| user = user_service.get_user_by_email(login_session['email']) |
| flash("You need to Log In if you want to edit") | ||
| return redirect('/categories') | ||
| # Get category that is selected to be edited | ||
| categoryToEdit = session.query(Category).filter_by(id=cat_id).one() |
There was a problem hiding this comment.
Same here:
| categoryToEdit = session.query(Category).filter_by(id=cat_id).one() | |
| categoryToEdit = category_service.get_category_by_id(category_id) |
| isCreator = login_session['email'] == category.user.email | ||
| return render_template('category.html', | ||
| products=(product_service | ||
| .get_products_by_cat_id(cat_id)), |
There was a problem hiding this comment.
Same with method names:
| .get_products_by_cat_id(cat_id)), | |
| .get_products_by_category_id(category_id)), |
No description provided.