29 | {% endmacro %}
30 |
--------------------------------------------------------------------------------
/web/application-example.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 |
4 | from nereid import Nereid
5 | from werkzeug.contrib.sessions import FilesystemSessionStore
6 | from nereid.contrib.locale import Babel
7 | from nereid.sessions import Session
8 |
9 | CWD = os.path.abspath(os.path.dirname(__file__))
10 |
11 | CONFIG = dict(
12 |
13 | # The name of database
14 | DATABASE_NAME='webshop',
15 |
16 | # Tryton Config file path
17 | TRYTON_CONFIG='../../etc/trytond.conf',
18 |
19 | # If the application is to be configured in the debug mode
20 | DEBUG=False,
21 |
22 | # The location where the translations of this template are stored
23 | TRANSLATIONS_PATH='i18n',
24 |
25 | # Secret Key: Replace this with something random
26 | # A good way to generate such a number would be
27 | #
28 | # >>> import os
29 | # >>> os.urandom(20)
30 | #
31 | SECRET_KEY='\xcd\x04}\x8d\\j-\x98b\xf2'
32 | )
33 |
34 | # Create a new application
35 | app = Nereid(static_folder='%s/static/' % CWD, static_url_path='/static')
36 |
37 | # Update the configuration with the above config values
38 | app.config.update(CONFIG)
39 |
40 |
41 | # Initialise the app, connect to cache and backend
42 | app.initialise()
43 |
44 | # Setup the filesystem cache
45 | app.session_interface.session_store = FilesystemSessionStore(
46 | '/tmp', session_class=Session
47 | )
48 |
49 | Babel(app)
50 |
51 |
52 | if __name__ == '__main__':
53 | app.debug = True
54 | app.run('0.0.0.0')
55 |
--------------------------------------------------------------------------------
/sale.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Invoice
4 |
5 | :copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Limited
6 | :license: GPLv3, see LICENSE for more details.
7 | """
8 | from trytond.pool import PoolMeta, Pool
9 | from nereid import abort
10 |
11 | __all__ = ['Sale']
12 | __metaclass__ = PoolMeta
13 |
14 |
15 | class Sale:
16 | __name__ = 'sale.sale'
17 |
18 | def ga_purchase_data(self, **kwargs):
19 | '''
20 | Return a dictionary that can be JSON serialised as expected by
21 | google analytics as a purchase confirmation
22 | '''
23 | return {
24 | 'id': self.reference,
25 | 'revenue': str(self.total_amount),
26 | 'tax': str(self.tax_amount),
27 | }
28 |
29 | def _add_or_update(self, product_id, quantity, action='set'):
30 | """
31 | Raise 400 if someone tries to add gift card to cart using
32 | add_to_cart method
33 | """
34 | Product = Pool().get('product.product')
35 |
36 | if Product(product_id).is_gift_card:
37 | abort(400)
38 |
39 | return super(Sale, self)._add_or_update(product_id, quantity, action)
40 |
41 | def _get_email_template_paths(self):
42 | """
43 | Returns a tuple of the form:
44 | (html_template, text_template)
45 | """
46 | return (
47 | 'nereid_webshop/templates/emails/sale-confirmation-html.jinja',
48 | 'nereid_webshop/templates/emails/sale-confirmation-text.jinja'
49 | )
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, Openlabs Technologies & Consulting (P) Limited
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | * Neither the name of the {organization} nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/templates/webshop/my-cards.jinja:
--------------------------------------------------------------------------------
1 | {% extends 'account-base.jinja' %}
2 |
3 | {% from '_helpers.jinja' import render_payment_profile %}
4 |
5 | {% block main %}
6 |
7 |
It seems that email id: {{ email }} you provided for guest checkout is tied to an existing account.
13 | Guest Checkout
14 | with different email or Sign In to continue.
15 |
65 |
66 | {% endblock main %}
67 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | nereid-webshop
2 | ==============
3 |
4 | .. image:: https://travis-ci.org/openlabs/nereid-webshop.png?branch=develop
5 | :target: https://travis-ci.org/openlabs/nereid-webshop
6 |
7 | .. image:: https://coveralls.io/repos/openlabs/nereid-webshop/badge.png?branch=develop
8 | :target: https://coveralls.io/r/openlabs/nereid-webshop
9 |
10 | Full Webshop based on Tryton Nereid
11 |
12 |
13 | Installation
14 | ------------
15 |
16 | Setting this module up is similar to the setup of any other tryton module.
17 |
18 |
19 | Step 1: Create a virtualenv
20 | ```````````````````````````
21 |
22 | ::
23 |
24 | virtualenv webshop
25 |
26 | You can now activate the virtualenv
27 |
28 | ::
29 |
30 | cd webshop
31 | source bin/activate
32 |
33 |
34 | Step 2: Clone and Setup the module
35 | ```````````````````````````````````
36 | ::
37 |
38 | git clone git@github.com:openlabs/nereid-webshop.git
39 | cd nereid-webshop
40 | python setup.py install
41 |
42 |
43 | This command would install all the required dependencies for the module to
44 | function.
45 |
46 | Step 3: Setup Database
47 | ```````````````````````
48 |
49 | The module should now be available on the modules list and can be
50 | installed into any database. Setup a website as shown below:
51 |
52 | .. image:: docs/source/_static/img//website.png
53 |
54 |
55 | You will have to create a guest user for nereid. The guest user would be
56 | the user which would be available in the context when there are no users
57 | logged into the website.
58 |
59 | Ensure that you have the following too:
60 |
61 | * A pricelist
62 | * A payment_term
63 |
64 | Step 4: Create an application script
65 | ````````````````````````````````````
66 |
67 | Create an `application.py` script which could lauch the application. A
68 | reference is provided in the web folder (`application-example.py
69 | `_).
70 |
71 | In most cases the only changes you may need are:
72 |
73 | * the DATABASE_NAME which should be the name of the database (from step 3).
74 | * the TRYTON_CONFIG which should be the location of the tryton config
75 | file.
76 |
77 | You should now be able to run the development server by running the
78 | application using::
79 |
80 | python application.py
81 |
82 | On pointing the browser to `localhost:5000 `_ you
83 | should be able to see the home page.
84 |
85 | .. image:: docs/source/_static/img/homepage.png
86 |
87 | Step 5: Production Deployment
88 | `````````````````````````````
89 |
90 | TODO
91 |
92 |
93 | Step 6: Customization
94 | `````````````````````
95 |
96 | For base customization in webshop you have to inherit base.jinja as follow::
97 |
98 | {% extends "webshop/base.jinja" %}
99 |
100 |
101 | 6.1: Favicon and logo
102 | *********************
103 |
104 | Set custom favicon by setting icon path in *SHOP_FAVICON* variable before extending *webshop/base.jinja* as follows::
105 |
106 | {% set SHOP_FAVICON = "" %}
107 | {% extends "webshop/base.jinja" %}
108 |
--------------------------------------------------------------------------------
/templates/webshop/checkout/signin.jinja:
--------------------------------------------------------------------------------
1 | {% extends 'checkout/base.jinja' %}
2 |
3 | {% from '_helpers.jinja' import render_field %}
4 |
5 | {% set checkout_step = 1 %}
6 | {% set checkout_step_name = _('Please Sign In') %}
7 |
8 |
9 | {% block main %}
10 |
11 |
12 |
13 |
14 |
15 |
{{ _('Returning Customers') }}
16 |
{{ _('Sign in below to checkout with an existing account') }}
17 |
18 |
19 |
20 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
{{ _('New Customers') }}
43 |
{{ _('Enter the email where you would like to receive order information.') }}
94 | {% trans %}Oops! No Products found for your search criteria.{% endtrans %}
95 | {% if facets %}
96 |
97 | {% trans %} To try a search without filters,{% endtrans %}
98 | {% trans %}click here{% endtrans %}
99 | {% endif %}
100 |
101 |
102 |
103 | {% endif %}
104 |
105 |
106 |
107 |
108 | {% endblock main %}
109 |
110 | {% block scripts %}
111 | {{ super() }}
112 |
124 | {% endblock %}
125 |
--------------------------------------------------------------------------------
/webshop.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | '''
3 | website
4 |
5 | :copyright: (c) 2013-2015 by Openlabs Technologies & Consulting (P) Ltd.
6 | :license: GPLv3, see LICENSE for more details
7 |
8 | '''
9 | import os
10 |
11 | from flask.helpers import send_from_directory
12 | from trytond.model import ModelSQL, fields
13 | from trytond.pool import Pool, PoolMeta
14 | from nereid import current_app, route, render_template, request, jsonify
15 | from trytond.pyson import Eval, Not
16 |
17 | __metaclass__ = PoolMeta
18 | __all__ = ['WebShop', 'BannerCategory', 'Banner', 'Article', 'Website']
19 |
20 | #: Get the static folder. The static folder also
21 | #: goes into the site packages
22 | STATIC_FOLDER = os.path.join(
23 | os.path.abspath(
24 | os.path.dirname(__file__)
25 | ), 'static'
26 | )
27 |
28 |
29 | class WebShop(ModelSQL):
30 | "website"
31 | __name__ = "nereid.webshop"
32 |
33 | @classmethod
34 | @route("/static-webshop/", methods=["GET"])
35 | def send_static_file(self, filename):
36 | """Function used internally to send static files from the static
37 | folder to the browser.
38 | """
39 | cache_timeout = current_app.get_send_file_max_age(filename)
40 | return send_from_directory(
41 | STATIC_FOLDER, filename,
42 | cache_timeout=cache_timeout
43 | )
44 |
45 |
46 | class BannerCategory:
47 | """Collection of related Banners"""
48 | __name__ = 'nereid.cms.banner.category'
49 |
50 | @staticmethod
51 | def check_xml_record(records, values):
52 | return True
53 |
54 |
55 | class Banner:
56 | """Banner for CMS"""
57 | __name__ = 'nereid.cms.banner'
58 |
59 | @staticmethod
60 | def check_xml_record(records, values):
61 | return True
62 |
63 |
64 | class Article:
65 | "CMS Articles"
66 | __name__ = 'nereid.cms.article'
67 |
68 | @staticmethod
69 | def check_xml_record(records, values):
70 | """The webshop module creates a bunch of commonly used articles on
71 | webshops. Since tryton does not allow records created via XML to be
72 | edited, this method explicitly allows users to modify the articles
73 | created by the module.
74 | """
75 | return True
76 |
77 |
78 | class Website:
79 | "Nereid Website"
80 | __name__ = 'nereid.website'
81 |
82 | cms_root_menu = fields.Many2One(
83 | 'nereid.cms.menuitem', "CMS root menu", ondelete='RESTRICT',
84 | select=True,
85 | )
86 |
87 | show_site_message = fields.Boolean('Show Site Message')
88 | site_message = fields.Char(
89 | 'Site Message',
90 | states={
91 | 'readonly': Not(Eval('show_site_message', False)),
92 | 'required': Eval('show_site_message', False)
93 | },
94 | depends=['show_site_message']
95 | )
96 |
97 | @classmethod
98 | @route('/sitemap', methods=["GET"])
99 | def render_sitemap(cls):
100 | """
101 | Return the sitemap.
102 | """
103 | Node = Pool().get('product.tree_node')
104 |
105 | # Search for nodes, sort by sequence.
106 | nodes = Node.search([
107 | ('parent', '=', None),
108 | ], order=[
109 | ('sequence', 'ASC'),
110 | ])
111 |
112 | return render_template('sitemap.jinja', nodes=nodes)
113 |
114 | @classmethod
115 | def auto_complete(cls, phrase):
116 | """
117 | Customizable method which returns a list of dictionaries
118 | according to the search query. The search service used can
119 | be modified in downstream modules.
120 |
121 | The front-end expects a jsonified list of dictionaries. For example,
122 | a downstream implementation of this method could return -:
123 | [
124 | ...
125 | {
126 | "value": ""
127 | }, {
128 | "value": "Nexus 6"
129 | }
130 | ...
131 | ]
132 | """
133 | return []
134 |
135 | @classmethod
136 | @route('/search-auto-complete')
137 | def search_auto_complete(cls):
138 | """
139 | Handler for auto-completing search.
140 | """
141 | return jsonify(results=cls.auto_complete(
142 | request.args.get('q', '')
143 | ))
144 |
145 | @classmethod
146 | @route('/search')
147 | def quick_search(cls):
148 | """
149 | Downstream implementation of quick_search().
150 |
151 | TODO:
152 | * Add article search.
153 | """
154 | return super(Website, cls).quick_search()
155 |
--------------------------------------------------------------------------------
/templates/webshop/address-edit.jinja:
--------------------------------------------------------------------------------
1 | {% extends 'account-base.jinja' %}
2 |
3 | {% from '_helpers.jinja' import render_field %}
4 |
5 | {% block main %}
6 |
7 |
{{ _('Add Note') }}
99 | 102 | 103 |