├── README.md ├── flask_shopify.py └── setup.py /README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | `pip install flask-shopify` 4 | 5 | ## Settings/Config 6 | 7 | The API Key and shared secret should be set on the config of 8 | the app 9 | 10 | ``` 11 | SHOPIFY_SHARED_SECRET = Your app's shared secret key with Shopify. 12 | SHOPIFY_API_KEY = Your app's API key. 13 | ``` 14 | 15 | ## Example usage 16 | 17 | ### Setup the app 18 | 19 | ``` 20 | from flask_shopify import Shopify 21 | 22 | app = Flask(__name__) 23 | shopify = Shopify(app) 24 | ``` 25 | 26 | or, if you are using the factory pattern 27 | 28 | ``` 29 | from flask_shopify import Shopify 30 | 31 | shopify = Shopify() 32 | 33 | # ... 34 | # when app is available 35 | 36 | shoify.init_app(app) 37 | ``` 38 | 39 | ### Using `shopify_login_required` decorator 40 | 41 | The decorator allows protecting handles with a shopify session. 42 | 43 | Before you can use the decorator, ensure that a login view is 44 | configured. This is where fulfil will redirect the user is there 45 | is no login session yet. 46 | 47 | ``` 48 | # Set the login view if you plan on using shopify_login_required 49 | # decorator 50 | shopify.login_view = 'auth.login' 51 | ``` 52 | 53 | Using decorator 54 | 55 | ``` 56 | from flask_shopify import shopify_login_required 57 | 58 | @shopify_login_required 59 | @app.route('/product') 60 | def product(): 61 | product = shopify.Product.find( 62 | request.args['id'] 63 | ) 64 | ``` 65 | 66 | ### When using admin links 67 | 68 | If you are using admin links, then shopify launches the url 69 | with the shop in the parameters. You can use the `assert_shop` 70 | decorator to ensure that the current shop the user is logged 71 | into is also the shop where the user clicked the admin link. 72 | 73 | ``` 74 | from flask_shopify import shopify_login_required, assert_shop 75 | 76 | @shopify_login_required 77 | @assert_shop 78 | @app.route('/product') 79 | def product(): 80 | product = shopify.Product.find( 81 | request.args['id'] 82 | ) 83 | ``` 84 | 85 | 86 | ### Login, logout and oauth authentication 87 | 88 | Built-in helpers achieve all of this. 89 | 90 | #### Login 91 | 92 | ``` 93 | @app.route('/login') 94 | def login(): 95 | shop = request.args.get('shop') 96 | if shop: 97 | return shopify.install( 98 | shop, 99 | ['write_products'], 100 | url_for('.authenticate', _external=True) 101 | ) 102 | return render_template('select-shop.html') 103 | ``` 104 | 105 | #### Authenticate after Oauth dance 106 | 107 | ``` 108 | @app.route('/authenticate') 109 | def authenticate(): 110 | shopify_session = shopify.authenticate() 111 | if shopify_session: 112 | return redirect( 113 | session.pop('next_url', url_for('.index')) 114 | ) 115 | return "Login Failed" 116 | ``` 117 | 118 | #### Logout 119 | 120 | ``` 121 | @app.route('/logout') 122 | def logout(): 123 | shopify.logout() 124 | return redirect('home') 125 | ``` 126 | -------------------------------------------------------------------------------- /flask_shopify.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | flask.ext.shopify 4 | ~~~~~~~~~~~~~~~~~ 5 | 6 | :copyright: (c) 2017 by Fulfil.IO Inc. 7 | :license: BSD, see LICENSE for more details. 8 | """ 9 | from functools import wraps 10 | 11 | from flask import ( 12 | request, redirect, url_for, _request_ctx_stack, session, 13 | current_app, abort 14 | ) 15 | import shopify 16 | 17 | 18 | def assert_shop(func): 19 | """ 20 | Ensure that the shop in the session is the same as the shop in a landing 21 | page where something like an admin link is used. 22 | """ 23 | @wraps(func) 24 | def wrapper(*args, **kwargs): 25 | if request.shopify_session.url == request.args.get('shop'): 26 | return func(*args, **kwargs) 27 | else: 28 | current_app.shopify.logout() 29 | if current_app.shopify.login_view: 30 | return redirect( 31 | url_for( 32 | current_app.shopify.login_view, 33 | shop=request.args.get('shop') 34 | ) 35 | ) 36 | else: 37 | abort(403) 38 | return wrapper 39 | 40 | 41 | def shopify_login_required(func): 42 | """ 43 | Ensure that there is a login token in the session. 44 | """ 45 | @wraps(func) 46 | def decorated_view(*args, **kwargs): 47 | shop_n_token = current_app.shopify.tokengetter_func() 48 | if not shop_n_token: 49 | return redirect(url_for( 50 | current_app.shopify.login_view, 51 | shop=request.args.get('shop'), 52 | next=request.url 53 | )) 54 | 55 | with shopify.Session.temp(*shop_n_token): 56 | return func(*args, **kwargs) 57 | 58 | return decorated_view 59 | 60 | 61 | class Shopify(object): 62 | """ 63 | Shopify flask extension 64 | 65 | Required Flask settings:: 66 | 67 | SHOPIFY_SHARED_SECRET 68 | SHOPIFY_API_KEY 69 | 70 | Configuring:: 71 | 72 | ``` 73 | from flask_shopify import Shopify 74 | 75 | app = Flask(__name__) 76 | shopify = Shopify(app) 77 | 78 | # Set the login view if you plan on using shopify_login_required 79 | # decorator 80 | shopify.login_view = 'auth.login' 81 | 82 | """ 83 | 84 | def __init__(self, app=None): 85 | if app is not None: 86 | self.init_app(self.app) 87 | self.tokengetter_func = self._session_token_getter 88 | self.tokensetter_func = self._session_token_setter 89 | self.login_view = None 90 | 91 | def init_app(self, app): 92 | app.shopify = self 93 | shopify.Session.setup( 94 | api_key=app.config['SHOPIFY_API_KEY'], 95 | secret=app.config['SHOPIFY_SHARED_SECRET'] 96 | ) 97 | app.before_request(self.before_request) 98 | 99 | def before_request(self): 100 | """ 101 | Add the shopify_session to the request if possible. 102 | If there is no token, shopify_session is set to None. 103 | """ 104 | shop_token = self.tokengetter_func() 105 | 106 | ctx = _request_ctx_stack.top 107 | if shop_token is not None: 108 | # should be a valid token 109 | shop_session = shopify.Session(*shop_token) 110 | shopify.ShopifyResource.activate_session(shop_session) 111 | ctx.request.shopify_session = shop_session 112 | else: 113 | # not logged in, no session created 114 | ctx.request.shopify_session = None 115 | 116 | def install(self, shop_subdomain, scopes=None, redirect_uri=None): 117 | """Returns a redirect response to the "permission" URL with 118 | the given shop. This will then prompt the user to install the app 119 | which will then send them to the welcome view. 120 | 121 | :param url: myshopify.com subdomain. 122 | :type url: str. 123 | """ 124 | if scopes is None: 125 | scopes = self.app.config.get('SHOPIFY_SCOPES', []) 126 | shop_session = shopify.Session("%s.myshopify.com" % shop_subdomain) 127 | permission_url = shop_session.create_permission_url( 128 | scopes, redirect_uri 129 | ) 130 | return redirect(permission_url) 131 | 132 | def authenticate(self): 133 | shop_session = shopify.Session(request.args['shop']) 134 | token = shop_session.request_token(request.args) 135 | shopify.ShopifyResource.activate_session(shop_session) 136 | self.tokensetter_func(request.args['shop'], token) 137 | return shop_session 138 | 139 | def token_getter(self, f): 140 | """Registers a function as tokengetter. The tokengetter has to return 141 | a tuple of ``(url, token)`` with the user's token and secret. 142 | If the data is unavailable, the function must return `None`. 143 | """ 144 | self.tokengetter_func = f 145 | return f 146 | 147 | def token_setter(self, f): 148 | """Registers a function as tokensetter. The tokensetter will be sent 149 | the shop and the token. 150 | """ 151 | self.tokensetter_func = f 152 | return f 153 | 154 | @classmethod 155 | def _session_token_getter(cls): 156 | try: 157 | return session['SHOPIFY_SHOP'], session['SHOPIFY_TOKEN'] 158 | except KeyError: 159 | return None 160 | 161 | @classmethod 162 | def _session_token_setter(cls, shop, token): 163 | session['SHOPIFY_SHOP'] = shop 164 | session['SHOPIFY_TOKEN'] = token 165 | 166 | def logout(self): 167 | session.pop('SHOPIFY_SHOP', None) 168 | session.pop('SHOPIFY_TOKEN', None) 169 | shopify.ShopifyResource.clear_session() 170 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flask-Shopify 3 | ------------- 4 | 5 | """ 6 | from setuptools import setup 7 | 8 | 9 | setup( 10 | name='Flask-Shopify', 11 | version='0.2', 12 | url='https://github.com/fulfilio/flask-shopify', 13 | license='BSD', 14 | author='Fulfil.IO Inc.', 15 | author_email='hello@fulfil.io', 16 | description='Shopify Flask', 17 | long_description=__doc__, 18 | py_modules=['flask_shopify'], 19 | zip_safe=False, 20 | include_package_data=True, 21 | platforms='any', 22 | install_requires=[ 23 | 'Flask', 24 | 'ShopifyApi', 25 | ], 26 | classifiers=[ 27 | 'Environment :: Web Environment', 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: BSD License', 30 | 'Operating System :: OS Independent', 31 | 'Programming Language :: Python', 32 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 33 | 'Topic :: Software Development :: Libraries :: Python Modules' 34 | ] 35 | ) 36 | --------------------------------------------------------------------------------