Skip to content

Mvc framework#1

Open
AlikZi wants to merge 14 commits into
newMasterfrom
mvcFramework
Open

Mvc framework#1
AlikZi wants to merge 14 commits into
newMasterfrom
mvcFramework

Conversation

@AlikZi
Copy link
Copy Markdown
Owner

@AlikZi AlikZi commented Mar 5, 2019

No description provided.

Copy link
Copy Markdown

@dmitrymrn dmitrymrn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty solid refactoring!
The next steps would be to logic from the routes to services and get rid of the repetitive code.

Comment thread project/__init__.py Outdated


# Connect to Database and create database session
engine = create_engine('sqlite:///furniturecatalog.db',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to move this to the config file.

Comment thread project/__init__.py Outdated
app.register_blueprint(jsonAPI)
app.register_blueprint(auth)


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: lots of extra newlines in general, also there should be only one new line at the end of each Python file (PEP8).

Comment thread project/__init__.py Outdated
from categories.routes import categories
from products.routes import products
from jsonAPI.routes import jsonAPI
from auth.routes import auth
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually, all imports are grouped at the top of the file and alphabetized.
https://www.python.org/dev/peps/pep-0008/#imports

Comment thread project/addproducts.py Outdated
from database_setup import Base, Category, Product, User
from models import Base, Category, Product, User

engine = create_engine('sqlite:///furniturecatalog.db')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a single DB connector. You already have it in the __init__ file above, so you could just use it here.

Comment thread project/auth/routes.py Outdated
# declaratives can be accessed through a DBSession instance
Base.metadata.bind = engine

DBSession = sessionmaker(bind=engine)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here - create a single DB connector class and instantiate it whenever you need it.

Comment thread project/auth/routes.py Outdated


CLIENT_ID = json.loads(
open('client_secrets.json', 'r').read())['web']['client_id']
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest creating a config service so you could do something like this:

Suggested change
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.

Comment thread run.py
from project import app

if __name__ == '__main__':
app.run('0.0.0.0', port=8000) No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Comment thread project/categories/routes.py Outdated
def showCategories():
"""App route function for main page to show all categories."""
# Check if user is logged in
isLogin = 'email' in login_session
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a repetitive pattern, create a new method is_user_aiuthorized() and call it instead from all routes that require authorization.

Comment thread project/categories/routes.py Outdated
latestProducts = (session.query(Product)
.order_by(Product.created_date.desc()).limit(8))
return render_template('index.html', categories=categories,
isLogin=isLogin, latestProducts=latestProducts)
Copy link
Copy Markdown

@dmitrymrn dmitrymrn Mar 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
    )

Copy link
Copy Markdown

@dmitrymrn dmitrymrn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/

Comment thread project/__init__.py
app.register_blueprint(categories)
app.register_blueprint(products)
app.register_blueprint(jsonAPI)
app.register_blueprint(auth)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice and clean! 🔥

Comment thread project/addproducts.py
session.add(product12)
session.commit()

print('All products are added')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread project/auth/routes.py Outdated
# 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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: nowadays, it's a good habit to use .format() instead of % for string formatting. For ex:

Suggested change
% access_token)
url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={}'.format(
access_token,
)```

Comment thread project/auth/routes.py


def getUserID(email):
"""Returns user.id if exists, otherwise returns none."""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the comments!

Comment thread project/auth/routes.py Outdated
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
Copy link
Copy Markdown

@dmitrymrn dmitrymrn Mar 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 response

and 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.

Comment thread project/categories/routes.py Outdated
categories=category_service.get_all_categories(),
isLogin=auth_service.is_user_authorized(),
latestProducts=(product_service
.get_latest_products(8)))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: assign 8 to a constant, for ex DEFAULT_CATEGORY_PRODUCT_NUMBER and use the constant.

Comment thread project/products/routes.py Outdated
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()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Suggested change
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.

Comment thread project/auth/routes.py Outdated
'email'])
session.add(newUser)
session.commit()
user = session.query(User).filter_by(email=login_session['email']).one()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a part of the AuthService, as well as creating a new user (above). I.e.:

Suggested change
user = session.query(User).filter_by(email=login_session['email']).one()
user = user_service.get_user_by_email(login_session['email'])

Comment thread project/categories/routes.py Outdated
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()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here:

Suggested change
categoryToEdit = session.query(Category).filter_by(id=cat_id).one()
categoryToEdit = category_service.get_category_by_id(category_id)

Comment thread project/categories/routes.py Outdated
isCreator = login_session['email'] == category.user.email
return render_template('category.html',
products=(product_service
.get_products_by_cat_id(cat_id)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with method names:

Suggested change
.get_products_by_cat_id(cat_id)),
.get_products_by_category_id(category_id)),

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

Successfully merging this pull request may close these issues.

2 participants