├── templates ├── base.jinja ├── home.jinja ├── login.jinja ├── sales.jinja ├── _helpers.jinja ├── account.jinja ├── add-card.jinja ├── address.jinja ├── article.jinja ├── checkout.jinja ├── my-cards.jinja ├── product.jinja ├── sale.jinja ├── sitemap.jinja ├── wishlist.jinja ├── wishlists.jinja ├── account-base.jinja ├── address-add.jinja ├── address-edit.jinja ├── article-rst.jinja ├── catalog │ ├── node.html │ └── gift-card.html ├── new-password.jinja ├── product-list.jinja ├── registration.jinja ├── shopping-cart.jinja ├── checkout │ ├── base.jinja │ ├── signin.jinja │ ├── payment_method.jinja │ ├── billing_address.jinja │ ├── shipping_address.jinja │ └── signin-email-in-use.jinja ├── reset-password.jinja ├── save-new-address.jinja ├── article-category.jinja ├── change-password.jinja ├── search-results.jinja ├── emails │ ├── reset-html.jinja │ ├── reset-text.jinja │ ├── activation-html.jinja │ ├── activation-text.jinja │ ├── sale-confirmation-html.jinja │ └── sale-confirmation-text.jinja └── webshop │ ├── emails │ ├── reset-text.jinja │ ├── activation-text.jinja │ ├── reset-html.jinja │ ├── sale-confirmation-text.jinja │ ├── activation-html.jinja │ └── sale-confirmation-html.jinja │ ├── article-rst.jinja │ ├── sitemap.jinja │ ├── article.jinja │ ├── new-password.jinja │ ├── article-category.jinja │ ├── product-list.jinja │ ├── change-password.jinja │ ├── reset-password.jinja │ ├── account-base.jinja │ ├── my-cards.jinja │ ├── address.jinja │ ├── checkout │ ├── signin-email-in-use.jinja │ ├── signin.jinja │ ├── billing_address.jinja │ └── shipping_address.jinja │ ├── wishlists.jinja │ ├── registration.jinja │ ├── save-new-address.jinja │ ├── login.jinja │ ├── catalog │ ├── gift-card.html │ └── node.html │ ├── address-add.jinja │ ├── add-card.jinja │ ├── account.jinja │ ├── search-results.jinja │ ├── address-edit.jinja │ ├── sales.jinja │ ├── sale.jinja │ ├── wishlist.jinja │ └── home.jinja ├── web ├── templates │ ├── base.jinja │ ├── home.jinja │ ├── login.jinja │ ├── sales.jinja │ ├── _helpers.jinja │ ├── account.jinja │ ├── address.jinja │ ├── article.jinja │ ├── category.jinja │ ├── checkout.jinja │ ├── product.jinja │ ├── wishlist.jinja │ ├── account-base.jinja │ ├── address-edit.jinja │ ├── registration.jinja │ ├── shopping-cart.jinja │ ├── article-category.jinja │ └── save-new-address.jinja └── application-example.py ├── dev_requirements.txt ├── static ├── images │ ├── ok.png │ ├── close.png │ ├── flags.png │ ├── next.png │ ├── prev.png │ ├── favicon.ico │ ├── loading.gif │ ├── social.png │ ├── cart-logo.png │ ├── grabbing.png │ ├── no-image.jpg │ ├── sold-out.png │ ├── gnacode-logo.png │ ├── assurance │ │ ├── heart.png │ │ ├── lock.png │ │ ├── dropicon.psd │ │ ├── customer-service.png │ │ └── warranty-services.png │ ├── button-active.png │ ├── button-normal.png │ ├── products │ │ ├── tee1.jpg │ │ └── tee2.jpg │ ├── backgrounds │ │ ├── menbg.png │ │ └── womenbg.png │ ├── google-plus-icon.png │ ├── nereid_cart_logo.png │ ├── recaptcha-example.gif │ ├── sample-img │ │ ├── jeans.jpg │ │ ├── shirts.jpg │ │ ├── banner1.jpg │ │ ├── banner2.jpg │ │ ├── banner3.jpg │ │ ├── polo-tees.jpg │ │ ├── men-tshirt.jpg │ │ ├── winterwear.jpg │ │ ├── casual-shirt.jpg │ │ └── formal-shirts │ │ │ ├── shirt1.jpg │ │ │ ├── shirt2.jpg │ │ │ ├── shirt3.jpg │ │ │ ├── shirt4.jpg │ │ │ ├── shirt5.jpg │ │ │ ├── shirt6.jpg │ │ │ ├── shirt7.jpg │ │ │ ├── shirt8.jpg │ │ │ └── shirt9.jpg │ ├── payment-gateway │ │ ├── solo.png │ │ ├── visa.png │ │ ├── cirrus.png │ │ ├── paypal.png │ │ ├── mastercard.png │ │ ├── western-union.png │ │ ├── american-express.png │ │ └── payment-icons-set │ │ │ └── payment-icons-set.psd │ ├── youtube-meluncurkan-flat-icon-1.jpg │ └── youtube-meluncurkan-flat-icon-1.png ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── js │ ├── app.js │ ├── html5shiv.js │ ├── easyzoom.min.js │ ├── respond.min.js │ └── imagesloaded.pkgd.min.js └── css │ ├── typeaheadjs.css │ ├── jquery.pnotify.default.css │ ├── lightbox.css │ └── owl.carousel.css ├── .coveragerc ├── docs └── source │ ├── _static │ └── img │ │ ├── website.png │ │ └── homepage.png │ └── index.rst ├── setup.cfg ├── MANIFEST.in ├── .travis.yml ├── tryton.cfg ├── webshop.xml ├── requirements.txt ├── view └── website_form.xml ├── product.xml ├── .gitignore ├── Dockerfile ├── __init__.py ├── shipment.py ├── invoice.py ├── tests ├── test_css.py ├── __init__.py ├── test_views_depends.py ├── test_invoice.py └── test_website.py ├── sale.py ├── LICENSE ├── cart.py ├── forms.py ├── fabfile.py ├── README.rst ├── party.py ├── setup.py ├── webshop.py └── product.py /templates/base.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/base.jinja' %} -------------------------------------------------------------------------------- /templates/home.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/home.jinja' %} -------------------------------------------------------------------------------- /templates/login.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/login.jinja' %} -------------------------------------------------------------------------------- /templates/sales.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/sales.jinja' %} -------------------------------------------------------------------------------- /web/templates/base.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/base.jinja' %} -------------------------------------------------------------------------------- /web/templates/home.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/home.jinja' %} -------------------------------------------------------------------------------- /templates/_helpers.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/_helpers.jinja' %} -------------------------------------------------------------------------------- /templates/account.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/account.jinja' %} -------------------------------------------------------------------------------- /templates/add-card.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/add-card.jinja' %} -------------------------------------------------------------------------------- /templates/address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/address.jinja' %} -------------------------------------------------------------------------------- /templates/article.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/article.jinja' %} -------------------------------------------------------------------------------- /templates/checkout.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/checkout.jinja' %} -------------------------------------------------------------------------------- /templates/my-cards.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/my-cards.jinja' %} -------------------------------------------------------------------------------- /templates/product.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/product.jinja' %} -------------------------------------------------------------------------------- /templates/sale.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/sale.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/sitemap.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/sitemap.jinja' %} -------------------------------------------------------------------------------- /templates/wishlist.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/wishlist.jinja' %} -------------------------------------------------------------------------------- /web/templates/login.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/login.jinja' %} -------------------------------------------------------------------------------- /web/templates/sales.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/sales.jinja' %} -------------------------------------------------------------------------------- /templates/wishlists.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/wishlists.jinja' %} -------------------------------------------------------------------------------- /web/templates/_helpers.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/_helpers.jinja' %} -------------------------------------------------------------------------------- /web/templates/account.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/account.jinja' %} -------------------------------------------------------------------------------- /web/templates/address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/address.jinja' %} -------------------------------------------------------------------------------- /web/templates/article.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/article.jinja' %} -------------------------------------------------------------------------------- /web/templates/category.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/category.jinja' %} -------------------------------------------------------------------------------- /web/templates/checkout.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/checkout.jinja' %} -------------------------------------------------------------------------------- /web/templates/product.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/product.jinja' %} -------------------------------------------------------------------------------- /web/templates/wishlist.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/wishlist.jinja' %} -------------------------------------------------------------------------------- /templates/account-base.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/account-base.jinja' %} -------------------------------------------------------------------------------- /templates/address-add.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/address-add.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/address-edit.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/address-edit.jinja' %} -------------------------------------------------------------------------------- /templates/article-rst.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/article-rst.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/catalog/node.html: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/catalog/node.html' %} 2 | -------------------------------------------------------------------------------- /templates/new-password.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/new-password.jinja' %} -------------------------------------------------------------------------------- /templates/product-list.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/product-list.jinja' %} -------------------------------------------------------------------------------- /templates/registration.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/registration.jinja' %} -------------------------------------------------------------------------------- /templates/shopping-cart.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/shopping-cart.jinja' %} -------------------------------------------------------------------------------- /templates/checkout/base.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/checkout/base.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/reset-password.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/reset-password.jinja' %} -------------------------------------------------------------------------------- /templates/save-new-address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/save-new-address.jinja' %} -------------------------------------------------------------------------------- /web/templates/account-base.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/account-base.jinja' %} -------------------------------------------------------------------------------- /web/templates/address-edit.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/address-edit.jinja' %} -------------------------------------------------------------------------------- /web/templates/registration.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/registration.jinja' %} -------------------------------------------------------------------------------- /web/templates/shopping-cart.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/shopping-cart.jinja' %} -------------------------------------------------------------------------------- /templates/article-category.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/article-category.jinja' %} -------------------------------------------------------------------------------- /templates/catalog/gift-card.html: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/catalog/gift-card.html' %} 2 | -------------------------------------------------------------------------------- /templates/change-password.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/change-password.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/checkout/signin.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/checkout/signin.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/search-results.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/search-results.jinja' %} 2 | -------------------------------------------------------------------------------- /web/templates/article-category.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/article-category.jinja' %} -------------------------------------------------------------------------------- /web/templates/save-new-address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/save-new-address.jinja' %} -------------------------------------------------------------------------------- /templates/emails/reset-html.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/emails/reset-html.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/emails/reset-text.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/emails/reset-text.jinja' %} 2 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | # Install requirements 2 | 3 | coveralls 4 | -r requirements.txt 5 | -------------------------------------------------------------------------------- /static/images/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/ok.png -------------------------------------------------------------------------------- /templates/checkout/payment_method.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/checkout/payment_method.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/emails/activation-html.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/emails/activation-html.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/emails/activation-text.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/emails/activation-text.jinja' %} 2 | -------------------------------------------------------------------------------- /static/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/close.png -------------------------------------------------------------------------------- /static/images/flags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/flags.png -------------------------------------------------------------------------------- /static/images/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/next.png -------------------------------------------------------------------------------- /static/images/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/prev.png -------------------------------------------------------------------------------- /templates/checkout/billing_address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/checkout/billing_address.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/checkout/shipping_address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/checkout/shipping_address.jinja' %} 2 | -------------------------------------------------------------------------------- /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/favicon.ico -------------------------------------------------------------------------------- /static/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/loading.gif -------------------------------------------------------------------------------- /static/images/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/social.png -------------------------------------------------------------------------------- /templates/checkout/signin-email-in-use.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'webshop/checkout/signin-email-in-use.jinja' %} 2 | -------------------------------------------------------------------------------- /static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/images/cart-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/cart-logo.png -------------------------------------------------------------------------------- /static/images/grabbing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/grabbing.png -------------------------------------------------------------------------------- /static/images/no-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/no-image.jpg -------------------------------------------------------------------------------- /static/images/sold-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sold-out.png -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = trytond.modules.nereid_webshop 3 | 4 | [report] 5 | omit = */tests/*, */fabfile.py 6 | -------------------------------------------------------------------------------- /static/images/gnacode-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/gnacode-logo.png -------------------------------------------------------------------------------- /static/images/assurance/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/assurance/heart.png -------------------------------------------------------------------------------- /static/images/assurance/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/assurance/lock.png -------------------------------------------------------------------------------- /static/images/button-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/button-active.png -------------------------------------------------------------------------------- /static/images/button-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/button-normal.png -------------------------------------------------------------------------------- /static/images/products/tee1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/products/tee1.jpg -------------------------------------------------------------------------------- /static/images/products/tee2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/products/tee2.jpg -------------------------------------------------------------------------------- /docs/source/_static/img/website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/docs/source/_static/img/website.png -------------------------------------------------------------------------------- /static/images/backgrounds/menbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/backgrounds/menbg.png -------------------------------------------------------------------------------- /static/images/google-plus-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/google-plus-icon.png -------------------------------------------------------------------------------- /static/images/nereid_cart_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/nereid_cart_logo.png -------------------------------------------------------------------------------- /static/images/recaptcha-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/recaptcha-example.gif -------------------------------------------------------------------------------- /static/images/sample-img/jeans.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/jeans.jpg -------------------------------------------------------------------------------- /static/images/sample-img/shirts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/shirts.jpg -------------------------------------------------------------------------------- /docs/source/_static/img/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/docs/source/_static/img/homepage.png -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/images/assurance/dropicon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/assurance/dropicon.psd -------------------------------------------------------------------------------- /static/images/backgrounds/womenbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/backgrounds/womenbg.png -------------------------------------------------------------------------------- /static/images/payment-gateway/solo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/payment-gateway/solo.png -------------------------------------------------------------------------------- /static/images/payment-gateway/visa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/payment-gateway/visa.png -------------------------------------------------------------------------------- /static/images/sample-img/banner1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/banner1.jpg -------------------------------------------------------------------------------- /static/images/sample-img/banner2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/banner2.jpg -------------------------------------------------------------------------------- /static/images/sample-img/banner3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/banner3.jpg -------------------------------------------------------------------------------- /static/images/sample-img/polo-tees.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/polo-tees.jpg -------------------------------------------------------------------------------- /static/images/payment-gateway/cirrus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/payment-gateway/cirrus.png -------------------------------------------------------------------------------- /static/images/payment-gateway/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/payment-gateway/paypal.png -------------------------------------------------------------------------------- /static/images/sample-img/men-tshirt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/men-tshirt.jpg -------------------------------------------------------------------------------- /static/images/sample-img/winterwear.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/winterwear.jpg -------------------------------------------------------------------------------- /templates/emails/sale-confirmation-html.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'nereid_webshop/templates/webshop/emails/sale-confirmation-html.jinja' %} 2 | -------------------------------------------------------------------------------- /templates/emails/sale-confirmation-text.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'nereid_webshop/templates/webshop/emails/sale-confirmation-text.jinja' %} 2 | -------------------------------------------------------------------------------- /static/images/sample-img/casual-shirt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/casual-shirt.jpg -------------------------------------------------------------------------------- /static/images/assurance/customer-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/assurance/customer-service.png -------------------------------------------------------------------------------- /static/images/assurance/warranty-services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/assurance/warranty-services.png -------------------------------------------------------------------------------- /static/images/payment-gateway/mastercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/payment-gateway/mastercard.png -------------------------------------------------------------------------------- /static/images/payment-gateway/western-union.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/payment-gateway/western-union.png -------------------------------------------------------------------------------- /static/images/payment-gateway/american-express.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/payment-gateway/american-express.png -------------------------------------------------------------------------------- /static/images/sample-img/formal-shirts/shirt1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/formal-shirts/shirt1.jpg -------------------------------------------------------------------------------- /static/images/sample-img/formal-shirts/shirt2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/formal-shirts/shirt2.jpg -------------------------------------------------------------------------------- /static/images/sample-img/formal-shirts/shirt3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/formal-shirts/shirt3.jpg -------------------------------------------------------------------------------- /static/images/sample-img/formal-shirts/shirt4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/formal-shirts/shirt4.jpg -------------------------------------------------------------------------------- /static/images/sample-img/formal-shirts/shirt5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/formal-shirts/shirt5.jpg -------------------------------------------------------------------------------- /static/images/sample-img/formal-shirts/shirt6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/formal-shirts/shirt6.jpg -------------------------------------------------------------------------------- /static/images/sample-img/formal-shirts/shirt7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/formal-shirts/shirt7.jpg -------------------------------------------------------------------------------- /static/images/sample-img/formal-shirts/shirt8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/formal-shirts/shirt8.jpg -------------------------------------------------------------------------------- /static/images/sample-img/formal-shirts/shirt9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/sample-img/formal-shirts/shirt9.jpg -------------------------------------------------------------------------------- /static/images/youtube-meluncurkan-flat-icon-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/youtube-meluncurkan-flat-icon-1.jpg -------------------------------------------------------------------------------- /static/images/youtube-meluncurkan-flat-icon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/youtube-meluncurkan-flat-icon-1.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude=.svn,CVS,.bzr,.hg,.git,__pycache__,build,dist,upload.py,src,docs,trytond*,pycountry-*,*.egg 3 | ignore=E126 4 | 5 | max-complexity=10 6 | max-line-length=80 7 | -------------------------------------------------------------------------------- /static/images/payment-gateway/payment-icons-set/payment-icons-set.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATA/nereid-webshop/develop/static/images/payment-gateway/payment-icons-set/payment-icons-set.psd -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include INSTALL 2 | include README 3 | include TODO 4 | include COPYRIGHT 5 | include CHANGELOG 6 | include MIGRATION 7 | include LICENSE 8 | include Makefile 9 | graft templates 10 | graft static 11 | -------------------------------------------------------------------------------- /templates/webshop/emails/reset-text.jinja: -------------------------------------------------------------------------------- 1 | {% trans %} 2 | Dear User, 3 | 4 | To reset password please click the link below or paste it to your browser: 5 | {% endtrans %} 6 | {{ nereid_user.get_reset_password_link(_external=True) }} 7 | 8 | {% trans %} 9 | Thanks, 10 | 11 | Team 12 | {% endtrans %} -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: 5 | - pip install flake8 6 | - pip install -r dev_requirements.txt 7 | script: 8 | - coverage run setup.py test 9 | - flake8 . 10 | after_success: 11 | coveralls 12 | notifications: 13 | email: 14 | - ci-notify@openlabs.co.in 15 | -------------------------------------------------------------------------------- /tryton.cfg: -------------------------------------------------------------------------------- 1 | [tryton] 2 | version=3.2.2.9 3 | depends: 4 | nereid 5 | nereid_cms 6 | nereid_catalog 7 | nereid_cart_b2c 8 | nereid_checkout 9 | nereid_catalog_tree 10 | nereid_image_transformation 11 | nereid_wishlist 12 | gift_card 13 | xml: 14 | cms.xml 15 | product.xml 16 | webshop.xml 17 | -------------------------------------------------------------------------------- /webshop.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nereid.website 6 | 7 | website_form 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Install zikzakmedia's sale shop module from bitbucket. 2 | # Depend on a commit as in further commit migration in sqlit is broken 3 | https://bitbucket.org/zikzakmedia/trytond-sale_shop/get/c08a55a96f605e96c72253ce22a24251c86c5bb9.tar.gz 4 | 5 | # Install gift card module from develop 6 | https://github.com/openlabs/trytond-gift-card/archive/develop.tar.gz 7 | 8 | # Install this package itself 9 | . 10 | -------------------------------------------------------------------------------- /templates/webshop/article-rst.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'article.jinja' %} 2 | 3 | {% block main %} 4 |
5 |
6 |
7 |

{{ article.title }}

8 |
9 |
10 |
11 |
12 |

{{ article.content|rst|safe }}

13 |
14 |
15 |
16 | 17 | {% endblock main %} 18 | -------------------------------------------------------------------------------- /templates/webshop/emails/activation-text.jinja: -------------------------------------------------------------------------------- 1 | {% trans %} 2 | Dear {% endtrans %} {{ nereid_user.display_name }}{% trans %}, 3 | 4 | Thank you for signing up. {% endtrans %} 5 | 6 | {% trans %} 7 | To verify your account please click on the link below or copy 8 | and paste it to your browser: 9 | {% endtrans %} 10 | 11 | {{ nereid_user.get_activation_link(_external=True) }} 12 | 13 | {% trans %} 14 | Thanks, 15 | 16 | Team 17 | {% endtrans %} 18 | -------------------------------------------------------------------------------- /view/website_form.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/webshop/emails/reset-html.jinja: -------------------------------------------------------------------------------- 1 | {% trans %} 2 | Dear user, 3 |
4 |
5 | To reset password please click the link below: 6 |
7 | {% endtrans %} 8 | {% trans %}click here {% endtrans %} 9 | {% trans %} 10 |
11 | or copy and paste it to your browser: 12 | {% endtrans %} 13 |
14 |
15 | {{ nereid_user.get_reset_password_link(_external=True) }} 16 |
17 |
18 | {% trans %} 19 | Thanks, 20 |
21 | Team 22 | {% endtrans %} -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Nereid Webshop documentation master file, created by 2 | sphinx-quickstart on Mon Apr 7 10:04:38 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Nereid Webshop's documentation! 7 | ========================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | 23 | -------------------------------------------------------------------------------- /templates/webshop/emails/sale-confirmation-text.jinja: -------------------------------------------------------------------------------- 1 | Dear {{ customer_name }}, 2 | 3 | Date: {{ sale.sale_date|dateformat }} 4 | Reference: #{{ sale.reference }} 5 | Amount: {{ sale.total_amount|currencyformat(sale.currency.code) }} 6 | 7 | Thank you for shopping with us. 8 | 9 | {% if has_request_context() %} 10 | To view your order details please click on the link below or copy 11 | and paste it to your browser: 12 | 13 | {{ url_for('sale.sale.render', active_id=sale.id, access_code=sale.guest_access_code, _external=True) }} 14 | {% endif %} 15 | 16 | Thanks, 17 | {{ sale.company.party.name }} 18 | -------------------------------------------------------------------------------- /templates/webshop/emails/activation-html.jinja: -------------------------------------------------------------------------------- 1 | {% trans %} 2 | Dear {% endtrans %} {{ nereid_user.display_name }} {% trans %}, 3 |
4 | Thank you for signing up. {% endtrans %} 5 |
6 |
7 | {% trans %} 8 | To verify your account please 9 | {% endtrans %} 10 | {% trans %}click here {% endtrans %} 11 | {% trans %} 12 | or copy and paste it to your browser: 13 | {% endtrans %} 14 |
15 | {{ nereid_user.get_activation_link(_external=True) }} 16 |
17 |
18 | {% trans %} 19 | Thanks, 20 |
21 | Team 22 | {% endtrans %} 23 | -------------------------------------------------------------------------------- /product.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Default Images 6 | Folder having default images for webshop 7 | 8 | 9 | no-product-image 10 | 11 | remote 12 | http://placehold.it/350&text=No%20Product%20Image.png 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/webshop/sitemap.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% block main %} 4 |
5 |

Products Sitemap

6 |
7 | {% if nodes %} 8 | 20 | {% endif %} 21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /static/js/app.js: -------------------------------------------------------------------------------- 1 | // Script to hide Special offers ticker when you click on close icon 2 | $('.new-offers .fa-times-circle').click(function() { 3 | event.preventDefault(); 4 | $('.new-offers').hide(); 5 | }); 6 | 7 | // Script which keeps the menu bar on the top when the site scrolls down. 8 | $(function(){ 9 | if ($('.sticky').length == 0) { 10 | return; 11 | } 12 | var stickyTop = $('.sticky').offset().top; // returns number 13 | $(window).scroll(function(){ // scroll event 14 | var windowTop = $(window).scrollTop(); // returns number 15 | if (stickyTop < windowTop) { 16 | $('.sticky').css({ position: 'fixed', top: 0 }); 17 | } else { 18 | $('.sticky').css('position','static'); 19 | } 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /templates/webshop/article.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'article-category.jinja' %} 2 | 3 | {% block title %}{{ article.title }} {{ super() }}{% endblock title %} 4 | {% block meta_description %}{{ (article.description or '')|striptags|truncate(300) }}{% endblock %} 5 | 6 | {% block breadcrumb %} 7 | {{ super() }} 8 | {{ render_breadcrumb_item(article.title, article.get_absolute_url()) }} 9 | {% endblock breadcrumb %} 10 | 11 | {% block main %} 12 |
13 |
14 |
15 |

{{ article.title }}

16 |
17 |
18 |
19 |
20 |

{{ article.content|safe }}

21 |
22 |
23 |
24 | {% endblock main %} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | bin/ 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Installer logs 24 | pip-log.txt 25 | pip-delete-this-directory.txt 26 | 27 | # Unit test / coverage reports 28 | .tox/ 29 | .coverage 30 | .cache 31 | nosetests.xml 32 | coverage.xml 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Django stuff: 43 | *.log 44 | *.pot 45 | 46 | # Sphinx documentation 47 | docs/_build/ 48 | 49 | # Nereid 50 | app_local.py 51 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Nereid-Webshop 2 | # 3 | # VERSION 3.2.0.1 4 | 5 | FROM openlabs/tryton:3.2 6 | MAINTAINER Prakash Pandey 7 | 8 | RUN apt-get -y update 9 | 10 | # * Setup psycopg2 since you want to connect to postgres 11 | # database 12 | # * Install pillow since image-transaformation uses it 13 | RUN apt-get -y -q install python-dev libpq-dev python-pillow gunicorn python-gevent python-psycopg2 14 | 15 | ADD . /opt/nereid-webshop/ 16 | WORKDIR /opt/nereid-webshop/ 17 | RUN pip install -r requirements.txt 18 | 19 | WORKDIR /opt/nereid-webshop/web 20 | 21 | # SET data_path to a volume on the server 22 | VOLUME /var/lib/trytond 23 | 24 | EXPOSE 9000 25 | CMD ["-b", "0.0.0.0:9000", "--error-logfile", "-", "-k", "gevent", "-w", "4", "application_example:app"] 26 | ENTRYPOINT ["/usr/bin/gunicorn"] 27 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | 4 | :copyright: (c) 2013-14 by Openlabs Technologies & Consulting (P) Ltd. 5 | :license: GPLv3, see LICENSE for more details 6 | 7 | ''' 8 | from trytond.pool import Pool 9 | from webshop import WebShop, BannerCategory, Banner, Article, Website 10 | from product import Product 11 | from invoice import Invoice 12 | from sale import Sale 13 | from party import Address 14 | from shipment import ShipmentOut 15 | from cart import Cart 16 | 17 | 18 | def register(): 19 | Pool.register( 20 | WebShop, 21 | BannerCategory, 22 | Banner, 23 | Article, 24 | Product, 25 | Invoice, 26 | Address, 27 | ShipmentOut, 28 | Sale, 29 | Website, 30 | Cart, 31 | module='nereid_webshop', type_='model' 32 | ) 33 | -------------------------------------------------------------------------------- /shipment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | shipment 4 | 5 | :copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Ltd. 6 | :license: GPLv3, see LICENSE for more details 7 | 8 | ''' 9 | from trytond.model import ModelView, Workflow 10 | from trytond.pool import PoolMeta 11 | 12 | __metaclass__ = PoolMeta 13 | 14 | __all__ = ['ShipmentOut'] 15 | 16 | 17 | class ShipmentOut: 18 | __name__ = 'stock.shipment.out' 19 | 20 | def send_shipment_alert(self): 21 | """Alert user about shipment status. 22 | """ 23 | # XXX: Not implemented yet 24 | return 25 | 26 | @classmethod 27 | @ModelView.button 28 | @Workflow.transition('done') 29 | def done(cls, shipments): 30 | """Mark shipment done and send an alert to user. 31 | """ 32 | super(ShipmentOut, cls).done(shipments) 33 | 34 | for shipment in shipments: 35 | shipment.send_shipment_alert() 36 | -------------------------------------------------------------------------------- /templates/webshop/emails/sale-confirmation-html.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'sale_confirmation_email/emails/sale-confirmation-html.html' %} 2 | 3 | {% block body %} 4 | Dear {{ customer_name }}, 5 |
6 |
7 | Date: {{ sale.sale_date|dateformat }} 8 |
9 | Reference: #{{ sale.reference }} 10 |
11 | Amount: {{ sale.total_amount|currencyformat(sale.currency.code) }} 12 |
13 |
14 | Thank you for shopping from our store. 15 |

16 | 17 | {% if has_request_context() %} 18 | To view your order details please 19 | click here 20 | or copy and paste it to your browser: 21 | 22 |
23 | {{ url_for('sale.sale.render', active_id=sale.id, access_code=sale.guest_access_code, _external=True) }} 24 | {% endif %} 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /templates/webshop/new-password.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_field %} 4 | 5 | {% block main %} 6 |
7 | 22 |
23 | {% endblock main %} 24 | -------------------------------------------------------------------------------- /templates/webshop/article-category.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'home.jinja' %} 2 | 3 | {% block title %}{{ category and category.title }} {{ super() }}{% endblock title %} 4 | {% block meta_description %}{{ (category and category.description or '')|striptags|truncate(300) }}{% endblock %} 5 | 6 | {% block breadcrumb %} 7 | {{ super() }} 8 | {% if category %} 9 | {{ render_breadcrumb_item(category.title, category.get_absolute_url()) }} 10 | {% endif %} 11 | {% endblock breadcrumb %} 12 | 13 | {% block main %} 14 |
15 |
16 |
17 |

{{ category.title }}

18 |
19 |
20 |
21 |
22 | 31 |
32 |
33 |
34 | {% endblock main %} 35 | -------------------------------------------------------------------------------- /templates/webshop/product-list.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_pagination, render_product_list %} 4 | 5 | {% block breadcrumb %} 6 | {{ super() }} 7 | {% endblock breadcrumb %} 8 | 9 | {% block main %} 10 |
11 |
12 | 13 | 14 |
15 |
16 |
17 |

{{ _('All Products') }}

18 |
19 |
20 |
21 |
22 | {% for product in products %} 23 | {{ render_product_list(product, product_list_name="Products Master List") }} 24 | {% endfor %} 25 |
26 |
27 | {{ render_pagination(products, endpoint='product.product.render_list') }} 28 |
29 |
30 |
31 | 32 |
33 | {% endblock main%} 34 | -------------------------------------------------------------------------------- /templates/webshop/change-password.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_field %} 4 | 5 | {% block main %} 6 |
7 | 23 |
24 | {% endblock main %} -------------------------------------------------------------------------------- /invoice.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 | import tempfile 9 | from trytond.pool import PoolMeta, Pool 10 | from nereid import route, login_required, abort, \ 11 | current_user 12 | from nereid.helpers import send_file 13 | 14 | __all__ = ['Invoice'] 15 | __metaclass__ = PoolMeta 16 | 17 | 18 | class Invoice: 19 | 20 | __name__ = 'account.invoice' 21 | 22 | @route('/orders/invoice//download') 23 | @login_required 24 | def download_invoice(self): 25 | """ 26 | Allow user to download invoice. 27 | """ 28 | Report = Pool().get('account.invoice', type='report') 29 | 30 | if self.party != current_user.party: 31 | abort(403) 32 | 33 | vals = Report.execute([self.id], {}) 34 | with tempfile.NamedTemporaryFile(delete=False) as file: 35 | file.write(vals[1]) 36 | return send_file( 37 | file.name, 38 | as_attachment=True, 39 | attachment_filename=vals[3], 40 | ) 41 | -------------------------------------------------------------------------------- /templates/webshop/reset-password.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% block main %} 4 |
5 | 25 |
26 | {% endblock main %} 27 | -------------------------------------------------------------------------------- /tests/test_css.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | CSS Testing 4 | 5 | :copyright: (C) 2014 by Openlabs Technologies & Consulting (P) Limited 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from os.path import join 9 | from cssutils import CSSParser 10 | 11 | import unittest 12 | import trytond.tests.test_tryton 13 | 14 | dir = 'static/css/' 15 | 16 | 17 | class CSSTest(unittest.TestCase): 18 | """ 19 | Test case for CSS. 20 | """ 21 | 22 | def validate(self, filename): 23 | """ 24 | Uses cssutils to validate a css file. 25 | Prints output using a logger. 26 | """ 27 | CSSParser(raiseExceptions=True).parseFile(filename, validate=True) 28 | 29 | def test_css(self): 30 | """ 31 | Test for CSS validation using W3C standards. 32 | """ 33 | cssfile = join(dir, 'style.css') 34 | self.validate(cssfile) 35 | 36 | 37 | def suite(): 38 | """ 39 | Define suite 40 | """ 41 | test_suite = trytond.tests.test_tryton.suite() 42 | test_suite.addTests( 43 | unittest.TestLoader().loadTestsFromTestCase(CSSTest) 44 | ) 45 | return test_suite 46 | 47 | if __name__ == '__main__': 48 | unittest.TextTestRunner(verbosity=2).run(suite()) 49 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | __init__ 4 | 5 | Test Suite 6 | 7 | :copyright: (c) 2013 by Openlabs Technologies & Consulting (P) Limited 8 | :license: BSD, see LICENSE for more details. 9 | """ 10 | import unittest 11 | 12 | import trytond.tests.test_tryton 13 | 14 | from .test_views_depends import TestViewsDepends 15 | from .test_invoice import TestDownloadInvoice 16 | from .test_css import CSSTest 17 | from .test_templates import TestTemplates 18 | from .test_gift_card import TestGiftCard 19 | from .test_website import TestWebsite 20 | 21 | 22 | def suite(): 23 | """ 24 | Define suite 25 | """ 26 | test_suite = trytond.tests.test_tryton.suite() 27 | test_suite.addTests([ 28 | unittest.TestLoader().loadTestsFromTestCase(CSSTest), 29 | unittest.TestLoader().loadTestsFromTestCase(TestViewsDepends), 30 | unittest.TestLoader().loadTestsFromTestCase(TestDownloadInvoice), 31 | unittest.TestLoader().loadTestsFromTestCase(TestTemplates), 32 | unittest.TestLoader().loadTestsFromTestCase(TestGiftCard), 33 | unittest.TestLoader().loadTestsFromTestCase(TestWebsite), 34 | ]) 35 | return test_suite 36 | 37 | 38 | if __name__ == '__main__': 39 | unittest.TextTestRunner(verbosity=2).run(suite()) 40 | -------------------------------------------------------------------------------- /templates/webshop/account-base.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% macro account_header(title='My Account', active='') %} 4 |
5 |

{{ title }}

6 | 28 |
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 | 35 | {% endblock main %} -------------------------------------------------------------------------------- /tests/test_views_depends.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | test_views_depends 4 | 5 | Test tryton views and fields dependency. 6 | 7 | :copyright: (C) 2013 by Openlabs Technologies & Consulting (P) Limited 8 | :license: BSD, see LICENSE for more details. 9 | """ 10 | import sys 11 | import os 12 | DIR = os.path.abspath(os.path.normpath(os.path.join( 13 | __file__, '..', '..', '..', '..', '..', 'trytond' 14 | ))) 15 | if os.path.isdir(DIR): 16 | sys.path.insert(0, os.path.dirname(DIR)) 17 | import unittest 18 | 19 | import trytond.tests.test_tryton 20 | from trytond.tests.test_tryton import test_view, test_depends 21 | 22 | 23 | class TestViewsDepends(unittest.TestCase): 24 | ''' 25 | Test views and depends 26 | ''' 27 | 28 | def setUp(self): 29 | """ 30 | Set up data used in the tests. 31 | this method is called before each test function execution. 32 | """ 33 | trytond.tests.test_tryton.install_module('nereid_webshop') 34 | 35 | @unittest.skip("Skipping since there is no views") 36 | def test0005views(self): 37 | ''' 38 | Test views. 39 | ''' 40 | test_view('nereid_webshop') 41 | 42 | def test0006depends(self): 43 | ''' 44 | Test depends. 45 | ''' 46 | test_depends() 47 | 48 | 49 | def suite(): 50 | """ 51 | Define suite 52 | """ 53 | test_suite = trytond.tests.test_tryton.suite() 54 | test_suite.addTests( 55 | unittest.TestLoader().loadTestsFromTestCase(TestViewsDepends) 56 | ) 57 | return test_suite 58 | 59 | if __name__ == '__main__': 60 | unittest.TextTestRunner(verbosity=2).run(suite()) 61 | -------------------------------------------------------------------------------- /templates/webshop/address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'account-base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_address, render_field %} 4 | 5 | {% block main %} 6 | 36 | {% endblock main %} 37 | 38 | {% block script_tags %} 39 | {{ super() }} 40 | 55 | {% endblock script_tags %} 56 | -------------------------------------------------------------------------------- /static/css/typeaheadjs.css: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | Obtained and modified from 3 | http://stackoverflow.com/questions/18167246/typeahead-problems-with-bootstrap-3-0-rc1 4 | https://github.com/bassjobsen/typeahead.js-bootstrap-css/ 5 | under GNU General Public License. 6 | ********************************************************/ 7 | span.twitter-typeahead .tt-dropdown-menu { 8 | position: absolute; 9 | top: 100%; 10 | left: 0; 11 | z-index: 1000; 12 | display: none; 13 | float: left; 14 | min-width: 160px; 15 | padding: 5px 0; 16 | margin: 2px 0 0; 17 | list-style: none; 18 | font-size: 14px; 19 | text-align: left; 20 | background-color: #ffffff; 21 | border: 1px solid #cccccc; 22 | border: 1px solid rgba(0, 0, 0, 0.15); 23 | border-radius: 4px; 24 | -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 25 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 26 | background-clip: padding-box; 27 | } 28 | span.twitter-typeahead .tt-suggestion > p { 29 | display: block; 30 | padding: 3px 20px; 31 | clear: both; 32 | font-weight: normal; 33 | line-height: 1.42857143; 34 | color: #333333; 35 | white-space: nowrap; 36 | } 37 | span.twitter-typeahead .tt-suggestion > p:hover, 38 | span.twitter-typeahead .tt-suggestion > p:focus { 39 | color: #ffffff; 40 | text-decoration: none; 41 | outline: 0; 42 | background-color: #428bca; 43 | } 44 | span.twitter-typeahead .tt-suggestion.tt-cursor { 45 | color: #ffffff; 46 | background-color: #428bca; 47 | } 48 | span.twitter-typeahead { 49 | width: 100%; 50 | } 51 | .input-group span.twitter-typeahead { 52 | display: block !important; 53 | } 54 | .input-group span.twitter-typeahead .tt-dropdown-menu { 55 | top: 32px !important; 56 | width: 100%; 57 | } 58 | .input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu { 59 | top: 44px !important; 60 | } 61 | .input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu { 62 | top: 28px !important; 63 | } -------------------------------------------------------------------------------- /cart.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cart.py 4 | 5 | :copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Limited 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from trytond.pool import PoolMeta, Pool 9 | 10 | 11 | __all__ = ['Cart'] 12 | __metaclass__ = PoolMeta 13 | 14 | 15 | class Cart: 16 | __name__ = 'nereid.cart' 17 | 18 | @classmethod 19 | def _login_event_handler(cls, user=None): 20 | """ 21 | Handle the case when cart has a line with gift card product 22 | """ 23 | SaleLine = Pool().get('sale.line') 24 | 25 | # Find the guest cart in current session 26 | guest_cart = cls.find_cart(None) 27 | 28 | if not guest_cart: # pragma: no cover 29 | return 30 | 31 | # There is a cart 32 | if guest_cart.sale and guest_cart.sale.lines: 33 | to_cart = cls.open_cart(True) 34 | # Transfer lines from one cart to another 35 | for from_line in guest_cart.sale.lines: 36 | if not from_line.product.is_gift_card: 37 | to_line = to_cart.sale._add_or_update( 38 | from_line.product.id, from_line.quantity 39 | ) 40 | else: 41 | values = { 42 | 'product': from_line.product.id, 43 | 'sale': to_cart.sale.id, 44 | 'type': from_line.type, 45 | 'unit': from_line.unit.id, 46 | 'quantity': from_line.quantity, 47 | 'sequence': from_line.sequence, 48 | 'description': from_line.description, 49 | 'recipient_email': from_line.recipient_email, 50 | 'recipient_name': from_line.recipient_name, 51 | 'message': from_line.message, 52 | 'gc_price': from_line.gc_price, 53 | 'unit_price': from_line.unit_price 54 | } 55 | to_line = SaleLine(**values) 56 | to_line.save() 57 | 58 | # Clear and delete the old cart 59 | guest_cart._clear_cart() 60 | -------------------------------------------------------------------------------- /static/css/jquery.pnotify.default.css: -------------------------------------------------------------------------------- 1 | /* 2 | Document : jquery.pnotify.default.css 3 | Created on : Nov 23, 2009, 3:14:10 PM 4 | Author : Hunter Perrin 5 | Version : 1.2.0 6 | Link : http://pinesframework.org/pnotify/ 7 | Description: 8 | Default styling for Pines Notify jQuery plugin. 9 | */ 10 | /* -- Notice */ 11 | .ui-pnotify { 12 | top: 25px; 13 | right: 25px; 14 | position: absolute; 15 | height: auto; 16 | /* Ensures notices are above everything */ 17 | z-index: 9999; 18 | } 19 | /* Hides position: fixed from IE6 */ 20 | html > body .ui-pnotify { 21 | position: fixed; 22 | } 23 | .ui-pnotify .ui-pnotify-shadow { 24 | -webkit-box-shadow: 0px 2px 10px rgba(50, 50, 50, 0.5); 25 | -moz-box-shadow: 0px 2px 10px rgba(50, 50, 50, 0.5); 26 | box-shadow: 0px 2px 10px rgba(50, 50, 50, 0.5); 27 | } 28 | .ui-pnotify-container { 29 | background-position: 0 0; 30 | padding: .8em; 31 | height: 100%; 32 | margin: 0; 33 | } 34 | .ui-pnotify-sharp { 35 | -webkit-border-radius: 0; 36 | -moz-border-radius: 0; 37 | border-radius: 0; 38 | } 39 | .ui-pnotify-closer, .ui-pnotify-sticker { 40 | float: right; 41 | margin-left: .2em; 42 | } 43 | .ui-pnotify-title { 44 | display: block; 45 | margin-bottom: .4em; 46 | } 47 | .ui-pnotify-text { 48 | display: block; 49 | } 50 | .ui-pnotify-icon, .ui-pnotify-icon span { 51 | display: block; 52 | float: left; 53 | margin-right: .2em; 54 | } 55 | /* -- History Pulldown */ 56 | .ui-pnotify-history-container { 57 | position: absolute; 58 | top: 0; 59 | right: 18px; 60 | width: 70px; 61 | border-top: none; 62 | padding: 0; 63 | -webkit-border-top-left-radius: 0; 64 | -moz-border-top-left-radius: 0; 65 | border-top-left-radius: 0; 66 | -webkit-border-top-right-radius: 0; 67 | -moz-border-top-right-radius: 0; 68 | border-top-right-radius: 0; 69 | /* Ensures history container is above notices. */ 70 | z-index: 10000; 71 | } 72 | .ui-pnotify-history-container .ui-pnotify-history-header { 73 | padding: 2px; 74 | } 75 | .ui-pnotify-history-container button { 76 | cursor: pointer; 77 | display: block; 78 | width: 100%; 79 | } 80 | .ui-pnotify-history-container .ui-pnotify-history-pulldown { 81 | display: block; 82 | margin: 0 auto; 83 | } 84 | .alert{ 85 | background-color: #fcf8e3; 86 | } 87 | .alert-info { 88 | color: #3a87ad; 89 | background-color: #d9edf7; 90 | border-color: #bce8f1; 91 | } -------------------------------------------------------------------------------- /static/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d 5 |
6 | 46 |
47 | 48 | {% endblock main %} 49 | 50 | {% block script_tags %} 51 | {{ super() }} 52 | 55 | {% endblock script_tags %} 56 | -------------------------------------------------------------------------------- /forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | forms.py 4 | 5 | :copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Limited 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | from flask_wtf import Form 9 | from wtforms import TextField, TextAreaField, SelectField, DecimalField, \ 10 | validators 11 | from wtforms.validators import ValidationError 12 | from nereid import abort 13 | 14 | from trytond.pool import Pool 15 | 16 | 17 | class GiftCardForm(Form): 18 | """ 19 | A form for purchasing gift cards 20 | """ 21 | 22 | recipient_name = TextField('Recipient Name', [validators.Optional()]) 23 | recipient_email = TextField('Recipient Email') 24 | message = TextAreaField('Message', [validators.Optional()]) 25 | selected_amount = SelectField('Select Amount', choices=[], coerce=int) 26 | open_amount = DecimalField('Amount', default=0) 27 | 28 | def __init__(self, product, *args, **kwargs): 29 | super(GiftCardForm, self).__init__(*args, **kwargs) 30 | Product = Pool().get('product.product') 31 | 32 | if not isinstance(product, Product): 33 | abort(400) 34 | 35 | try: 36 | self.gc_product, = Product.search([ 37 | ('id', '=', product.id), 38 | ('is_gift_card', '=', True) 39 | ], limit=1) 40 | except ValueError as e: 41 | e.message = 'Expected Gift Card, Got %s' % (product.rec_name) 42 | raise 43 | 44 | self.fill_choices() 45 | 46 | if self.gc_product.gift_card_delivery_mode in ['virtual', 'combined']: 47 | self.recipient_email.validators = [ 48 | validators.Required(), validators.Email() 49 | ] 50 | else: 51 | self.recipient_email.validators = [ 52 | validators.Optional(), validators.Email() 53 | ] 54 | 55 | def fill_choices(self): 56 | choices = [] 57 | if self.gc_product.allow_open_amount: 58 | choices = [(0, 'Set my Own')] 59 | 60 | self.selected_amount.choices = choices + [ 61 | (p.id, p.price) for p in self.gc_product.gift_card_prices 62 | ] 63 | 64 | def validate_open_amount(form, field): 65 | if not form.gc_product.allow_open_amount: 66 | return 67 | 68 | if (form.selected_amount.data == 0) and not ( 69 | form.gc_product.gc_min <= field.data <= form.gc_product.gc_max 70 | ): 71 | raise ValidationError( 72 | "Amount between %s and %s is allowed." % ( 73 | form.gc_product.gc_min, form.gc_product.gc_max 74 | ) 75 | ) 76 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile 3 | 4 | :copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Limited 5 | :license: BSD, see LICENSE for more details. 6 | """ 7 | import os 8 | import getpass 9 | 10 | from fabric.tasks import execute 11 | from fabric.api import sudo, cd, prefix, run, env 12 | from fabric.decorators import hosts 13 | 14 | # Forward the SSH agent so that git pull works 15 | env.forward_agent = True 16 | env.use_ssh_config = True 17 | 18 | hipchat_notification_token = open( 19 | os.path.expanduser('~/.hipchat-token') 20 | ).read().replace('\n', '') 21 | 22 | 23 | def _update_schema(database, module=None): 24 | "Run trytond and update schema for given database" 25 | if module: 26 | run( 27 | 'trytond -c etc/trytond.conf -u %s -d %s' % ( 28 | module, database 29 | ) 30 | ) 31 | else: 32 | run('trytond -c etc/trytond.conf -u nereid_webshop -d %s' % database) 33 | 34 | 35 | @hosts('%s@demo.openlabs.us' % getpass.getuser()) 36 | def deploy_staging(schema_update=False): 37 | "Deploy to staging" 38 | root_path = '/opt/webshop_demo' 39 | sudo('chmod -R g+rw %s' % root_path) 40 | 41 | with cd(root_path): 42 | with prefix("source %s/bin/activate" % root_path): 43 | with cd('nereid-webshop'): 44 | run('git fetch') 45 | run('git checkout origin/develop') 46 | run('python setup.py install') 47 | 48 | if schema_update: 49 | execute(_update_schema, 'webshop') 50 | 51 | sudo('supervisorctl restart all') 52 | 53 | 54 | @hosts('%s@demo.openlabs.us' % getpass.getuser()) 55 | def update_module(module): 56 | "Deploy to staging" 57 | root_path = '/opt/webshop-demo' 58 | sudo('chmod -R g+rw %s' % root_path) 59 | 60 | with cd(root_path): 61 | with prefix("source %s/bin/activate" % root_path): 62 | with cd(module): 63 | run('git fetch') 64 | run('git checkout origin/develop') 65 | run('python setup.py install') 66 | 67 | execute(_update_schema, 'webshop', module.replace('-', '_')) 68 | 69 | sudo('supervisorctl restart all') 70 | 71 | 72 | @hosts('%s@demo.openlabs.us' % getpass.getuser()) 73 | def update_documentation(): 74 | """ 75 | Update the documentation on the current host. 76 | 77 | This method is host agnostic 78 | """ 79 | root_path = '/opt/webshop-demo' 80 | 81 | with cd(root_path): 82 | with prefix("source %s/bin/activate" % root_path): 83 | run('pip install sphinx_rtd_theme') 84 | with cd('nereid-webshop'): 85 | run('python setup.py build_sphinx') 86 | -------------------------------------------------------------------------------- /templates/webshop/wishlists.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'account-base.jinja' %} 2 | 3 | {% block main %} 4 | 56 | {% endblock main %} 57 | -------------------------------------------------------------------------------- /static/js/easyzoom.min.js: -------------------------------------------------------------------------------- 1 | (function(b,c){function a(x,d){var m={id:"zoom-panel",parent:"body",error:'

There has been a problem attempting to loading the image.

',loading:'

Loading

',cursor:"crosshair",touch:true}; 2 | this.opts=b.extend({},m,d);var q=this,u=false,p=true,y=false,f,e,n,l,k,j,i,h,g,r;function v(){q.ele={$target:b(x),$source:b("img",x),$parent:b(q.opts.parent),$loader:b(q.opts.loading),$panel:b('
')}; 3 | o(q.ele.$target.attr("href"));q.ele.$target.on("click",function(z){z.preventDefault();}).on("mouseenter",function(z){y=true;w(z);}).on("mousemove",function(z){if(!y){y=true; 4 | w(z);}else{s(z);}}).on("mouseleave",function(){q.hide();y=false;});if(q.opts.touch&&"ontouchstart" in document.documentElement){x.addEventListener("touchstart",function(z){if(z.touches.length===1){z.preventDefault(); 5 | y=true;w(z);}},false);x.addEventListener("touchmove",function(z){if(z.touches.length===1){z.preventDefault();s(z);}},false);x.addEventListener("touchend",function(){q.hide(); 6 | y=false;},false);}return q;}function o(z){u=false;q.ele.$target.css("cursor","progress");q.ele.$loader.appendTo(q.ele.$target);q.ele.$zoomed=q.loadimg(z).on("error",function(){p=false; 7 | t();}).on("load",function(){u=true;q.ele.$target.css("cursor",q.opts.cursor);q.ele.$loader.detach();q.ele.$panel.html(q.ele.$zoomed.css("position","absolute")); 8 | if(y){q.ele.$target.trigger("mouseenter");}});}function t(){q.ele.$panel.html(q.opts.error);}function s(D){if(D.type.indexOf("touch")===0){f=D.touches[0].pageX; 9 | e=D.touches[0].pageY;}else{f=D.pageX||f;e=D.pageY||e;}var C=q.ele.$source.offset(),A=f-C.left,B=e-C.top,E=A*g,z=B*r;E=(E>k)?k:E;z=(z>h)?h:z;if(E>0&&z>0){q.ele.$zoomed.css({left:-E,top:-z}); 10 | }}function w(z){if(q.ele.$panel.parent().length===0){q.ele.$panel.appendTo(q.ele.$parent).css("opacity",0);}q.ele.$panel.stop().animate({opacity:1},200); 11 | n=q.ele.$source.width();j=q.ele.$source.height();l=q.ele.$panel.width();i=q.ele.$panel.height();k=q.ele.$zoomed.width()-l;h=q.ele.$zoomed.height()-i;g=k/n; 12 | r=h/j;s(z);}this.loadimg=function(A){var z=new Image();z.src=A+"?"+(new Date()).getTime();z.onload=function(){z=null;};return b(z);};this.hide=function(){if(q.ele.$panel.parent().length){q.ele.$panel.stop().animate({opacity:0},200,function(){q.ele.$panel=q.ele.$panel.detach(); 13 | });}};this.update=function(z){this.hide();o(z);};return x.tagName.toLowerCase()==="a"?v():c;}b.fn.easyZoom=function(d){return this.each(function(){b.data(this,"easyZoom",new a(this,d)); 14 | });};a.prototype.gallery=function(d,g){var f=this,e=g?b(g):this.ele.$parent;e.on("click",d,function(j){j.preventDefault();var i=b(this).addClass("thumbnail-loading"),k=i.attr("href"),h=i.data("easyzoomSource"); 15 | f.loadimg(h).on("load",function(){f.ele.$source.attr("src",h);f.ele.$target.attr("href",k);f.update(k);i.removeClass("thumbnail-loading");});});};})(jQuery); 16 | -------------------------------------------------------------------------------- /templates/webshop/registration.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_field %} 4 | 5 | {% block breadcrumb %} 6 | {{ super() }} 7 | {{ render_breadcrumb_item(_('Registration'), url_for('nereid.user.registration')) }} 8 | {% endblock breadcrumb %} 9 | 10 | {% block main %} 11 |
12 |
13 | 14 | 61 |
62 |
63 | 64 | {% endblock main %} 65 | -------------------------------------------------------------------------------- /templates/webshop/save-new-address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'account-base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_address %} 4 | 5 | {% block main %} 6 | 7 | 66 | {% endblock main %} 67 | -------------------------------------------------------------------------------- /templates/webshop/login.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_field %} 4 | 5 | {% block breadcrumb %} 6 | {{ super() }} 7 | {{ render_breadcrumb_item(_('Login'), url_for('nereid.website.login')) }} 8 | {% endblock breadcrumb %} 9 | 10 | {% block main %} 11 |
12 |
13 | 14 | 64 |
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 | 65 |
66 | {% endblock main %} 67 | 68 | 69 | {% block ga_page_view %} 70 | {{ google_analytics_add_product() }} 71 | ga('ec:setAction', 'checkout', {'step': 1}); 72 | {% endblock ga_page_view %} 73 | -------------------------------------------------------------------------------- /party.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | party 4 | 5 | :copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Ltd. 6 | :license: GPLv3, see LICENSE for more details 7 | 8 | ''' 9 | import os 10 | import logging 11 | from wtforms import TextField, validators 12 | 13 | from trytond.pool import PoolMeta, Pool 14 | from trytond.modules.nereid.party import AddressForm 15 | from trytond.config import CONFIG 16 | from nereid import request, current_app 17 | 18 | from trytond.modules.nereid_checkout.i18n import _ 19 | 20 | __metaclass__ = PoolMeta 21 | __all__ = ['Address'] 22 | 23 | geoip = None 24 | try: 25 | from pygeoip import GeoIP 26 | except ImportError: 27 | logging.error("pygeoip is not installed") 28 | else: 29 | path = os.environ.get('GEOIP_DATA_PATH', CONFIG.get('geoip_data_path')) 30 | if path: 31 | geoip = GeoIP(path) 32 | 33 | 34 | class WebshopAddressForm(AddressForm): 35 | """Custom address form for webshop 36 | """ 37 | 38 | phone = TextField(_('Phone'), [validators.Required(), ]) 39 | 40 | def get_default_country(self): 41 | """Get the default country based on geoip data. 42 | """ 43 | if not geoip: 44 | return None 45 | 46 | Country = Pool().get('country.country') 47 | try: 48 | current_app.logger.debug( 49 | "GeoIP lookup for remote address: %s" % request.remote_addr 50 | ) 51 | country, = Country.search([ 52 | ('code', '=', geoip.country_code_by_addr(request.remote_addr)) 53 | ]) 54 | except ValueError: 55 | return None 56 | return country 57 | 58 | def __init__(self, formdata=None, obj=None, prefix='', **kwargs): 59 | 60 | # While choices can be assigned after the form is constructed, default 61 | # cannot be. The form's data is picked from the first available of 62 | # formdata, obj data, and kwargs. 63 | # Once the data has been resolved, changing the default won't do 64 | # anything. 65 | default_country = self.get_default_country() 66 | if default_country: 67 | kwargs.setdefault('country', default_country.id) 68 | 69 | super(WebshopAddressForm, self).__init__( 70 | formdata, obj, prefix, **kwargs 71 | ) 72 | 73 | 74 | class Address: 75 | __name__ = 'party.address' 76 | 77 | @classmethod 78 | def get_address_form(cls, address=None): 79 | """ 80 | Return an initialised Address form that can be validated and used to 81 | create/update addresses 82 | 83 | :param address: If an active record is provided it is used to autofill 84 | the form. 85 | """ 86 | if address: 87 | form = WebshopAddressForm( 88 | request.form, 89 | name=address.name, 90 | street=address.street, 91 | streetbis=address.streetbis, 92 | zip=address.zip, 93 | city=address.city, 94 | country=address.country and address.country.id, 95 | subdivision=address.subdivision and address.subdivision.id, 96 | email=address.party.email, 97 | phone=address.phone_number and address.phone_number.value 98 | ) 99 | else: 100 | address_name = "" if request.nereid_user.is_anonymous() else \ 101 | request.nereid_user.display_name 102 | form = WebshopAddressForm(request.form, name=address_name) 103 | 104 | return form 105 | -------------------------------------------------------------------------------- /templates/webshop/catalog/gift-card.html: -------------------------------------------------------------------------------- 1 | {% extends 'product.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_product, render_field %} 4 | 5 | {% block product_name_price %} 6 | {# product name/price section #} 7 |

8 | {{ product.name }} 9 |

10 |

11 | {{ product.code }} 12 |

13 | {% if product.allow_open_amount %} 14 |
15 | 16 |

17 | {{ product.gc_min|currencyformat(request.nereid_currency.code) }} - {{ product.gc_max|currencyformat(request.nereid_currency.code) }} 18 |

19 |
20 | {% endif %} 21 | {% endblock product_name_price %} 22 | 23 | {% block buy_buttons %} 24 |
25 | {% if product.salable %} 26 |
27 |
28 |
29 |
30 |
31 | {{ render_field(form.selected_amount, required=required, id="selected_amount") }} 32 |
33 | {% if product.allow_open_amount %} 34 |
35 | {{ render_field(form.open_amount, placeholder="Enter Amount") }} 36 |
37 | {% endif %} 38 |
39 | 40 |
41 |
42 | {{ render_field(form.recipient_name, placeholder="Enter Name", type="text") }} 43 |
44 | {% if product.gift_card_delivery_mode in ['virtual', 'combined'] %} 45 |
46 | {{ render_field(form.recipient_email, placeholder="Enter Email", type="email", required=required) }} 47 |
48 | {% endif %} 49 |
50 |
51 |
52 | {{ render_field(form.message, label_text="Message (max. 100 words)", placeholder="Enter Message", type="textarea", maxlength="100") }} 53 |
54 |
55 |
56 |
57 | 58 | 59 |
60 |
61 |
62 |
63 |
64 | {% else %} 65 |
66 | This product is not for sale 67 |
68 | {% endif %} 69 |
70 |
71 | {% endblock buy_buttons %} 72 | 73 | {% block scripts %} 74 | {{ super() }} 75 | 76 | {% if product.allow_open_amount %} 77 | 88 | {% endif %} 89 | {% endblock %} 90 | -------------------------------------------------------------------------------- /templates/webshop/address-add.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'account-base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_field %} 4 | 5 | {% block main %} 6 | 60 | {% endblock main %} 61 | 62 | 63 | {% block scripts %} 64 | {{ super() }} 65 | 66 | 88 | {% endblock scripts %} 89 | -------------------------------------------------------------------------------- /templates/webshop/add-card.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'account-base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_field, render_address %} 4 | 5 | {% block main %} 6 | 67 | {% endblock main %} 68 | 69 | {% block script_tags %} 70 | {{ super }} 71 | 91 | {% endblock script_tags %} 92 | -------------------------------------------------------------------------------- /templates/webshop/account.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'account-base.jinja' %} 2 | 3 | {% block main %} 4 | 79 | {% endblock main %} 80 | -------------------------------------------------------------------------------- /templates/webshop/catalog/node.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_pagination, render_product_list %} 4 | 5 | {% block title %}{{ node.name }} {{ super() }}{% endblock %} 6 | {% block meta_description %}{{ (node.description or '')|striptags|truncate(300) }}{% endblock %} 7 | 8 | {% block breadcrumb %} 9 | {{ super() }} 10 | {% for url, title in make_tree_crumbs(node=node, add_home=False)[1:] %} 11 | {{ render_breadcrumb_item(title, url) }} 12 | {% endfor %} 13 | {% endblock breadcrumb %} 14 | 15 | {% block main %} 16 |
17 |
18 | 19 |
20 |
21 |

{{ node.name }}

22 | {% if node.description %} 23 |

24 | {{ node.description|safe }} 25 |

26 | {% endif %} 27 |
28 |
29 | 30 | {% if products.page==1 and node.children %} 31 |
32 | {% for child in node.children %} 33 |
34 | 46 |
47 | {% endfor %} 48 |
49 | {% endif %} 50 | 51 |
52 |
53 |
54 | {{ products.begin_count }} to {{ products.end_count }} of {{ products.count }} results for {{ node.name }} 55 |
56 |
57 |
58 |
59 | {% for product in products %} 60 |
61 | {{ render_product_list(product, node=node, product_list_name="Node List") }} 62 |
63 | {% endfor %} 64 |
65 |
66 |
67 | {{ render_pagination(products, endpoint='product.tree_node.render', active_id=node.id, slug=node.slug) }} 68 |
69 |
70 | 71 |
72 |
73 | {% endblock main%} 74 | 75 | 76 | {% block scripts %} 77 | {{ super() }} 78 | 98 | {% endblock scripts %} 99 | 100 | {% block ga_page_view %} 101 | // 1. Send product and impression data with pageview. 102 | {% for product in products %} 103 | {% if product.__name__ == 'product.template' %} 104 | {# the product could also template, in which case use the first variant #} 105 | {% set product = product.products_displayed_on_eshop[0] %} 106 | {% endif %} 107 | ga('ec:addImpression', {{ product.ga_product_data(list=node.name, position=loop.index)|tojson|safe }}); 108 | {% endfor %} 109 | {% endblock ga_page_view %} 110 | -------------------------------------------------------------------------------- /static/js/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.1: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b= %s.%s, < %s.%s' % ( 74 | MODULE2PREFIX.get(dep, 'trytond'), dep, 75 | major_version, minor_version, major_version, 76 | minor_version + 1 77 | ) 78 | ) 79 | requires.append( 80 | 'trytond >= %s.%s, < %s.%s' % 81 | (major_version, minor_version, major_version, minor_version + 1) 82 | ) 83 | 84 | setup( 85 | name='openlabs_nereid_webshop', 86 | version=info.get('version'), 87 | description="Nereid Webshop", 88 | author="Openlabs Technologies & consulting (P) Limited", 89 | author_email='info@openlabs.co.in', 90 | url='http://www.openlabs.co.in', 91 | classifiers=[ 92 | 'Development Status :: 4 - Beta', 93 | 'Environment :: Plugins', 94 | 'Intended Audience :: Developers', 95 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 96 | 'Natural Language :: English', 97 | 'Operating System :: OS Independent', 98 | 'Programming Language :: Python', 99 | 'Framework :: Tryton', 100 | 'Topic :: Office/Business', 101 | ], 102 | packages=[ 103 | 'trytond.modules.nereid_webshop', 104 | 'trytond.modules.nereid_webshop.tests', 105 | ], 106 | package_dir={ 107 | 'trytond.modules.nereid_webshop': '.', 108 | 'trytond.modules.nereid_webshop.tests': 'tests', 109 | }, 110 | package_data={ 111 | 'trytond.modules.nereid_webshop': info.get('xml', []) 112 | + ['tryton.cfg', 'locale/*.po', 'tests/*.rst'] 113 | + ['i18n/*.pot', 'i18n/pt_BR/LC_MESSAGES/*', 'view/*.xml'] 114 | + list(get_files("templates/")) 115 | + list(get_files("static/")), 116 | }, 117 | license='GPL-3', 118 | install_requires=requires, 119 | zip_safe=False, 120 | entry_points=""" 121 | [trytond.modules] 122 | nereid_webshop = trytond.modules.nereid_webshop 123 | """, 124 | test_suite='tests.suite', 125 | test_loader='trytond.test_loader:Loader', 126 | tests_require=[ 127 | 'pycountry', 128 | 'openlabs_payment_gateway_authorize_net >= %s.%s, < %s.%s' % 129 | (major_version, minor_version, major_version, minor_version + 1), 130 | 'cssutils', 131 | ], 132 | cmdclass={ 133 | 'test': SQLiteTest, 134 | }, 135 | ) 136 | -------------------------------------------------------------------------------- /templates/webshop/search-results.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% from 'webshop/_helpers.jinja' import render_es_pagination, render_pagination, render_search_filter_field, render_product_list with context %} 4 | 5 | {% block breadcrumb %} 6 | {{ super() }} 7 | {{ render_breadcrumb_item(_('Search Results'), url_for('nereid.website.quick_search', q=request.args.get('q', ''))) }} 8 | {% endblock breadcrumb %} 9 | 10 | {% block main %} 11 | 12 |
13 |
14 | {% if facets %} 15 | {% block sidebar %} 16 | 39 | {% endblock %} 40 | {% endif %} 41 |
42 | 43 |
44 | 45 |
46 |

{% trans %}You searched for{% endtrans %}: {{ request.args.get('q', '') }}

47 |
48 |
49 | {% block filter %} 50 | 51 | 62 | {% endblock filter %} 63 | 64 | {% if products %} 65 |
66 |
67 |
68 | {{ products.begin_count }} to {{ products.end_count }} of {{ products.count }} results for '{{ request.args.get('q', '') }}' 69 |
70 |
71 |
72 |
73 | {% for product in products %} 74 |
75 | {{ render_product_list(product, product_list_name="Search Results") }} 76 |
77 | {% endfor %} 78 |
79 |
80 |
81 | {% if facets %} 82 | {{ render_es_pagination(products) }} 83 | {% else %} 84 | {{ render_pagination(products, endpoint='nereid.website.quick_search', q=request.args.get('q', '')) }} 85 | {% endif %} 86 |
87 |
88 | 89 | {% else %} 90 | 91 |
92 |
93 |
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 | 65 | {% endblock main %} 66 | 67 | {% block script_tags %} 68 | {{ super() }} 69 | 70 | 108 | {% endblock script_tags %} 109 | -------------------------------------------------------------------------------- /static/css/lightbox.css: -------------------------------------------------------------------------------- 1 | /* Preload images */ 2 | body:after { 3 | content: url(../images/close.png) url(../images/loading.gif) url(../images/prev.png) url(../images/next.png); 4 | display: none; 5 | } 6 | 7 | .lightboxOverlay { 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | z-index: 9999; 12 | background-color: black; 13 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); 14 | opacity: 0.8; 15 | display: none; 16 | } 17 | 18 | .lightbox { 19 | position: absolute; 20 | left: 0; 21 | width: 100%; 22 | z-index: 10000; 23 | text-align: center; 24 | line-height: 0; 25 | font-weight: normal; 26 | } 27 | 28 | .lightbox .lb-image { 29 | display: block; 30 | height: auto; 31 | max-width: inherit; 32 | -webkit-border-radius: 3px; 33 | -moz-border-radius: 3px; 34 | -ms-border-radius: 3px; 35 | -o-border-radius: 3px; 36 | border-radius: 3px; 37 | } 38 | 39 | .lightbox a img { 40 | border: none; 41 | } 42 | 43 | .lb-outerContainer { 44 | position: relative; 45 | background-color: white; 46 | *zoom: 1; 47 | width: 250px; 48 | height: 250px; 49 | margin: 0 auto; 50 | -webkit-border-radius: 4px; 51 | -moz-border-radius: 4px; 52 | -ms-border-radius: 4px; 53 | -o-border-radius: 4px; 54 | border-radius: 4px; 55 | } 56 | 57 | .lb-outerContainer:after { 58 | content: ""; 59 | display: table; 60 | clear: both; 61 | } 62 | 63 | .lb-container { 64 | padding: 4px; 65 | } 66 | 67 | .lb-loader { 68 | position: absolute; 69 | top: 43%; 70 | left: 0; 71 | height: 25%; 72 | width: 100%; 73 | text-align: center; 74 | line-height: 0; 75 | } 76 | 77 | .lb-cancel { 78 | display: block; 79 | width: 32px; 80 | height: 32px; 81 | margin: 0 auto; 82 | background: url(../images/loading.gif) no-repeat; 83 | } 84 | 85 | .lb-nav { 86 | position: absolute; 87 | top: 0; 88 | left: 0; 89 | height: 100%; 90 | width: 100%; 91 | z-index: 10; 92 | } 93 | 94 | .lb-container > .nav { 95 | left: 0; 96 | } 97 | 98 | .lb-nav a { 99 | outline: none; 100 | background-image: url('data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='); 101 | } 102 | 103 | .lb-prev, .lb-next { 104 | height: 100%; 105 | cursor: pointer; 106 | display: block; 107 | } 108 | 109 | .lb-nav a.lb-prev { 110 | width: 34%; 111 | left: 0; 112 | float: left; 113 | background: url(../images/prev.png) left 48% no-repeat; 114 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 115 | opacity: 0; 116 | -webkit-transition: opacity 0.6s; 117 | -moz-transition: opacity 0.6s; 118 | -o-transition: opacity 0.6s; 119 | transition: opacity 0.6s; 120 | } 121 | 122 | .lb-nav a.lb-prev:hover { 123 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); 124 | opacity: 1; 125 | } 126 | 127 | .lb-nav a.lb-next { 128 | width: 64%; 129 | right: 0; 130 | float: right; 131 | background: url(../images/next.png) right 48% no-repeat; 132 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 133 | opacity: 0; 134 | -webkit-transition: opacity 0.6s; 135 | -moz-transition: opacity 0.6s; 136 | -o-transition: opacity 0.6s; 137 | transition: opacity 0.6s; 138 | } 139 | 140 | .lb-nav a.lb-next:hover { 141 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); 142 | opacity: 1; 143 | } 144 | 145 | .lb-dataContainer { 146 | margin: 0 auto; 147 | padding-top: 5px; 148 | *zoom: 1; 149 | width: 100%; 150 | -moz-border-radius-bottomleft: 4px; 151 | -webkit-border-bottom-left-radius: 4px; 152 | border-bottom-left-radius: 4px; 153 | -moz-border-radius-bottomright: 4px; 154 | -webkit-border-bottom-right-radius: 4px; 155 | border-bottom-right-radius: 4px; 156 | } 157 | 158 | .lb-dataContainer:after { 159 | content: ""; 160 | display: table; 161 | clear: both; 162 | } 163 | 164 | .lb-data { 165 | padding: 0 4px; 166 | color: #ccc; 167 | } 168 | 169 | .lb-data .lb-details { 170 | width: 85%; 171 | float: left; 172 | text-align: left; 173 | line-height: 1.1em; 174 | } 175 | 176 | .lb-data .lb-caption { 177 | font-size: 13px; 178 | font-weight: bold; 179 | line-height: 1em; 180 | } 181 | 182 | .lb-data .lb-number { 183 | display: block; 184 | clear: left; 185 | padding-bottom: 1em; 186 | font-size: 12px; 187 | color: #999999; 188 | } 189 | 190 | .lb-data .lb-close { 191 | display: block; 192 | float: right; 193 | margin-right: -30px; 194 | width: 30px; 195 | height: 30px; 196 | background: url(../images/close.png) top right no-repeat; 197 | text-align: right; 198 | outline: none; 199 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); 200 | opacity: 0.7; 201 | -webkit-transition: opacity 0.2s; 202 | -moz-transition: opacity 0.2s; 203 | -o-transition: opacity 0.2s; 204 | transition: opacity 0.2s; 205 | } 206 | 207 | .lb-data .lb-close:hover { 208 | cursor: pointer; 209 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); 210 | opacity: 1; 211 | } 212 | -------------------------------------------------------------------------------- /tests/test_invoice.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Invoice test suite 4 | 5 | :copyright: (C) 2014 by Openlabs Technologies & Consulting (P) Limited 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | import unittest 9 | import trytond.tests.test_tryton 10 | from trytond.tests.test_tryton import POOL, USER, DB_NAME, CONTEXT 11 | from trytond.transaction import Transaction 12 | from trytond.modules.nereid_cart_b2c.tests.test_product import BaseTestCase 13 | 14 | 15 | class TestDownloadInvoice(BaseTestCase): 16 | 17 | def setUp(self): 18 | 19 | trytond.tests.test_tryton.install_module( 20 | 'nereid_webshop' 21 | ) 22 | super(TestDownloadInvoice, self).setUp() 23 | 24 | self.UomCategory = POOL.get('product.uom.category') 25 | self.Company = POOL.get('company.company') 26 | self.Account = POOL.get('account.invoice') 27 | self.AccountLine = POOL.get('account.invoice.line') 28 | self.Category = POOL.get('product.category') 29 | self.Node = POOL.get('product.tree_node') 30 | 31 | def create_website(self): 32 | """ 33 | Creates a website. Since the fields required to make this could 34 | change depending on modules installed and this is a base test case 35 | the creation is separated to another method 36 | """ 37 | node, = self.Node.create([{ 38 | 'name': 'root', 39 | 'slug': 'root', 40 | 'type_': 'catalog', 41 | }]) 42 | 43 | return self.NereidWebsite.create([{ 44 | 'name': 'localhost', 45 | 'shop': self.shop, 46 | 'company': self.company.id, 47 | 'application_user': USER, 48 | 'default_locale': self.locale_en_us.id, 49 | 'guest_user': self.guest_user, 50 | 'countries': [('add', self.available_countries)], 51 | 'currencies': [('add', [self.usd.id])], 52 | }]) 53 | 54 | def setup_defaults(self): 55 | """ 56 | Setting up default values. 57 | """ 58 | super(TestDownloadInvoice, self).setup_defaults() 59 | 60 | def test_0010_download_invoice(self): 61 | """ 62 | Test to download invoice from a sale 63 | """ 64 | Address = POOL.get('party.address') 65 | 66 | with Transaction().start(DB_NAME, USER, CONTEXT): 67 | self.setup_defaults() 68 | app = self.get_app() 69 | 70 | party2, = self.Party.create([{ 71 | 'name': 'Registered User', 72 | }]) 73 | 74 | self.registered_user, = self.NereidUser.create([{ 75 | 'party': party2.id, 76 | 'display_name': 'Registered User', 77 | 'email': 'example@example.com', 78 | 'password': 'password', 79 | 'company': self.company.id, 80 | }]) 81 | 82 | uom, = self.Uom.search([], limit=1) 83 | # Create sale 84 | address, = Address.create([{ 85 | 'party': party2.id, 86 | 'name': 'Name', 87 | 'street': 'Street', 88 | 'streetbis': 'StreetBis', 89 | 'zip': 'zip', 90 | 'city': 'City', 91 | 'country': self.available_countries[0].id, 92 | 'subdivision': 93 | self.available_countries[0].subdivisions[0].id, 94 | }]) 95 | sale, = self.Sale.create([{ 96 | 'party': party2, 97 | 'company': self.company.id, 98 | 'invoice_address': address.id, 99 | 'shipment_address': address.id, 100 | 'currency': self.usd.id, 101 | 'lines': [ 102 | ('create', [{ 103 | 'product': self.product1.id, 104 | 'quantity': 1, 105 | 'unit': self.template1.sale_uom.id, 106 | 'unit_price': self.template1.list_price, 107 | 'description': 'description', 108 | }])] 109 | }]) 110 | self.Sale.quote([sale]) 111 | self.Sale.confirm([sale]) 112 | with Transaction().set_context(company=self.company.id): 113 | 114 | self.Sale.process([sale]) 115 | self.Account.post(sale.invoices) 116 | with app.test_client() as c: 117 | # Loged in user tries to download invoice 118 | self.login(c, 'example@example.com', 'password') 119 | response = c.get( 120 | '/orders/invoice/%s/download' % (sale.invoices[0].id, ) 121 | ) 122 | self.assertEqual(response.status_code, 200) 123 | 124 | 125 | def suite(): 126 | "Test suite" 127 | test_suite = unittest.TestSuite() 128 | test_suite.addTests( 129 | unittest.TestLoader().loadTestsFromTestCase(TestDownloadInvoice) 130 | ) 131 | return test_suite 132 | 133 | 134 | if __name__ == '__main__': 135 | unittest.TextTestRunner(verbosity=2).run(suite()) 136 | -------------------------------------------------------------------------------- /templates/webshop/sales.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'account-base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_pagination %} 4 | 5 | {% block main %} 6 | 7 | 101 | {% endblock main %} 102 | -------------------------------------------------------------------------------- /templates/webshop/checkout/billing_address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'checkout/base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_field %} 4 | 5 | {% set checkout_step = 3 %} 6 | {% set checkout_step_name = _('Billing Address') %} 7 | 8 | 9 | {% block main %} 10 |
11 |
12 | 13 |
14 |
15 |

{{ _('Billing Address') }}

16 |
17 |
18 | {% if not current_user.is_anonymous() and addresses %} 19 |

{{ _('Choose from an existing address below') }}

20 |
21 | {% for address in addresses %} 22 |
23 |
24 | {{ render_address(address, edit=False) }} 25 |
26 |
27 | 28 | 29 | 32 |
33 |
34 | {% endfor %} 35 |
36 | 37 | {# 38 | 39 | 40 | 47 | #} 48 | 49 |
50 |
{{ _('or') }}
51 |
52 | {% endif %} 53 | 54 |

55 | {% if addresses %} 56 | {{ _('Add a new address below') }} 57 | {% else %} 58 | {{ _('Enter your billing address') }} 59 | {% endif %} 60 |

61 | 62 |
63 | {{ address_form.hidden_tag() }} 64 | {{ render_field(address_form.name, required=required) }} 65 | {{ render_field(address_form.street, placeholder=_("Street address, P.O. box, company name, c/o"), class_="form-control", label_text= _('Address Line 1'), required=required) }} 66 | {{ render_field(address_form.streetbis, placeholder=_("Apartment, suite, unit, building, floor, etc."), class_="form-control", label_text= _('Address Line 2')) }} 67 |
68 |
69 | {{ render_field(address_form.city, required=required) }} 70 |
71 |
72 | {{ render_field(address_form.zip, required=required) }} 73 |
74 |
75 | {{ render_field(address_form.country) }} 76 |
77 |
78 | 79 | 81 |
82 |
83 | {{ render_field(address_form.phone, required=required) }} 84 |
85 |
86 | 89 |
90 |
91 |
92 |
93 |
94 | 95 | 96 | {{ cart_summary() }} 97 |
98 |
99 |
100 |
101 | {% endblock main %} 102 | 103 | {% block scripts %} 104 | {{ super() }} 105 | 126 | {% endblock scripts %} 127 | 128 | 129 | {% block ga_page_view %} 130 | {{ google_analytics_add_product() }} 131 | ga('ec:setAction', 'checkout', {'step': 5}); 132 | {% endblock ga_page_view %} 133 | -------------------------------------------------------------------------------- /templates/webshop/sale.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'account-base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_address %} 4 | 5 | {% block main %} 6 | 7 | 112 | {% endblock main %} 113 | 114 | {% block scripts %} 115 | {{ super() }} 116 | 138 | {% endblock scripts %} 139 | 140 | 141 | {% block ga_page_view %} 142 | {% if confirmation %} 143 | {% for line in sale.lines if line.product %} 144 | ga('ec:addProduct', {{ line.product.ga_product_data(price=line.unit_price, quantity=line.quantity|string)|tojson|safe }}); 145 | {% endfor %} 146 | ga('ec:setAction', 'purchase', {{ sale.ga_purchase_data()|tojson|safe }}); 147 | {% endif %} 148 | {% endblock ga_page_view %} 149 | -------------------------------------------------------------------------------- /templates/webshop/wishlist.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'account-base.jinja' %} 2 | {% from '_helpers.jinja' import render_product_list %} 3 | {% block main %} 4 | 96 | {% endblock main %} 97 | {% block script_tags %} 98 | {{ super() }} 99 | 115 | {% endblock script_tags %} 116 | -------------------------------------------------------------------------------- /templates/webshop/home.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'base.jinja' %} 2 | 3 | {% block breadcrumb %} 4 | {{ render_breadcrumb_item(_('Home'), url_for('nereid.website.home')) }} 5 | {% endblock breadcrumb %} 6 | 7 | {% block main %} 8 | {% block banner %} 9 | {% set home_page_banners = get_banner_category('Home Page Banners') and get_banner_category('Home Page Banners').published_banners %} 10 | {% if home_page_banners %} 11 |
12 |
13 |
14 | 15 |
16 | 51 |
52 |
53 |
54 | {% endif %} 55 | {% endblock banner %} 56 | 57 |
58 |
59 | {% set featured_products = request.nereid_website.featured_products_node and request.nereid_website.featured_products_node.get_products(per_page=3) %} 60 | {% if featured_products %} 61 | 77 |
78 | {% endif %} 79 |
80 |
81 | ... 82 |
83 |

Product Name

84 |

Description

85 |
86 |
87 |
88 | ... 89 |
90 |

Product Name

91 |

Description

92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
    101 |
  • 102 |
    103 | 104 |
    SECURE PAYMENT
    105 |

    Payment via credit card or Paypal

    106 |
    107 |
  • 108 |
  • 109 |
    110 | 111 |
    WARRANTY & SERVICE
    112 |

    Our customer service is simple and effective.

    113 |
    114 |
  • 115 |
  • 116 |
    117 | 118 |
    QUALITY
    119 |

    We are Certified Quality and Environment. And Living Heritage Company.

    120 |
    121 |
  • 122 |
  • 123 |
    124 | 125 |
    CUSTOMER SERVICE
    126 |

    A doubt, a question? reach us at 0000-000-0000.

    127 |
    128 |
  • 129 |
130 |
131 |
132 |
133 | {% endblock main %} 134 | 135 | 136 | {% block script_tags %} 137 | {{ super() }} 138 | 141 | {% endblock script_tags %} 142 | 143 | 144 | {% block scripts %} 145 | {{ super() }} 146 | 160 | {% endblock scripts %} 161 | -------------------------------------------------------------------------------- /static/js/imagesloaded.pkgd.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * imagesLoaded PACKAGED v3.1.8 3 | * JavaScript is all like "You images are done yet or what?" 4 | * MIT License 5 | */ 6 | 7 | (function(){function e(){}function t(e,t){for(var n=e.length;n--;)if(e[n].listener===t)return n;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var i=e.prototype,r=this,o=r.EventEmitter;i.getListeners=function(e){var t,n,i=this._getEvents();if("object"==typeof e){t={};for(n in i)i.hasOwnProperty(n)&&e.test(n)&&(t[n]=i[n])}else t=i[e]||(i[e]=[]);return t},i.flattenListeners=function(e){var t,n=[];for(t=0;e.length>t;t+=1)n.push(e[t].listener);return n},i.getListenersAsObject=function(e){var t,n=this.getListeners(e);return n instanceof Array&&(t={},t[e]=n),t||n},i.addListener=function(e,n){var i,r=this.getListenersAsObject(e),o="object"==typeof n;for(i in r)r.hasOwnProperty(i)&&-1===t(r[i],n)&&r[i].push(o?n:{listener:n,once:!1});return this},i.on=n("addListener"),i.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},i.once=n("addOnceListener"),i.defineEvent=function(e){return this.getListeners(e),this},i.defineEvents=function(e){for(var t=0;e.length>t;t+=1)this.defineEvent(e[t]);return this},i.removeListener=function(e,n){var i,r,o=this.getListenersAsObject(e);for(r in o)o.hasOwnProperty(r)&&(i=t(o[r],n),-1!==i&&o[r].splice(i,1));return this},i.off=n("removeListener"),i.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},i.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},i.manipulateListeners=function(e,t,n){var i,r,o=e?this.removeListener:this.addListener,s=e?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(i=n.length;i--;)o.call(this,t,n[i]);else for(i in t)t.hasOwnProperty(i)&&(r=t[i])&&("function"==typeof r?o.call(this,i,r):s.call(this,i,r));return this},i.removeEvent=function(e){var t,n=typeof e,i=this._getEvents();if("string"===n)delete i[e];else if("object"===n)for(t in i)i.hasOwnProperty(t)&&e.test(t)&&delete i[t];else delete this._events;return this},i.removeAllListeners=n("removeEvent"),i.emitEvent=function(e,t){var n,i,r,o,s=this.getListenersAsObject(e);for(r in s)if(s.hasOwnProperty(r))for(i=s[r].length;i--;)n=s[r][i],n.once===!0&&this.removeListener(e,n.listener),o=n.listener.apply(this,t||[]),o===this._getOnceReturnValue()&&this.removeListener(e,n.listener);return this},i.trigger=n("emitEvent"),i.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},i.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},i._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},i._getEvents=function(){return this._events||(this._events={})},e.noConflict=function(){return r.EventEmitter=o,e},"function"==typeof define&&define.amd?define("eventEmitter/EventEmitter",[],function(){return e}):"object"==typeof module&&module.exports?module.exports=e:this.EventEmitter=e}).call(this),function(e){function t(t){var n=e.event;return n.target=n.target||n.srcElement||t,n}var n=document.documentElement,i=function(){};n.addEventListener?i=function(e,t,n){e.addEventListener(t,n,!1)}:n.attachEvent&&(i=function(e,n,i){e[n+i]=i.handleEvent?function(){var n=t(e);i.handleEvent.call(i,n)}:function(){var n=t(e);i.call(e,n)},e.attachEvent("on"+n,e[n+i])});var r=function(){};n.removeEventListener?r=function(e,t,n){e.removeEventListener(t,n,!1)}:n.detachEvent&&(r=function(e,t,n){e.detachEvent("on"+t,e[t+n]);try{delete e[t+n]}catch(i){e[t+n]=void 0}});var o={bind:i,unbind:r};"function"==typeof define&&define.amd?define("eventie/eventie",o):e.eventie=o}(this),function(e,t){"function"==typeof define&&define.amd?define(["eventEmitter/EventEmitter","eventie/eventie"],function(n,i){return t(e,n,i)}):"object"==typeof exports?module.exports=t(e,require("wolfy87-eventemitter"),require("eventie")):e.imagesLoaded=t(e,e.EventEmitter,e.eventie)}(window,function(e,t,n){function i(e,t){for(var n in t)e[n]=t[n];return e}function r(e){return"[object Array]"===d.call(e)}function o(e){var t=[];if(r(e))t=e;else if("number"==typeof e.length)for(var n=0,i=e.length;i>n;n++)t.push(e[n]);else t.push(e);return t}function s(e,t,n){if(!(this instanceof s))return new s(e,t);"string"==typeof e&&(e=document.querySelectorAll(e)),this.elements=o(e),this.options=i({},this.options),"function"==typeof t?n=t:i(this.options,t),n&&this.on("always",n),this.getImages(),a&&(this.jqDeferred=new a.Deferred);var r=this;setTimeout(function(){r.check()})}function f(e){this.img=e}function c(e){this.src=e,v[e]=this}var a=e.jQuery,u=e.console,h=u!==void 0,d=Object.prototype.toString;s.prototype=new t,s.prototype.options={},s.prototype.getImages=function(){this.images=[];for(var e=0,t=this.elements.length;t>e;e++){var n=this.elements[e];"IMG"===n.nodeName&&this.addImage(n);var i=n.nodeType;if(i&&(1===i||9===i||11===i))for(var r=n.querySelectorAll("img"),o=0,s=r.length;s>o;o++){var f=r[o];this.addImage(f)}}},s.prototype.addImage=function(e){var t=new f(e);this.images.push(t)},s.prototype.check=function(){function e(e,r){return t.options.debug&&h&&u.log("confirm",e,r),t.progress(e),n++,n===i&&t.complete(),!0}var t=this,n=0,i=this.images.length;if(this.hasAnyBroken=!1,!i)return this.complete(),void 0;for(var r=0;i>r;r++){var o=this.images[r];o.on("confirm",e),o.check()}},s.prototype.progress=function(e){this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded;var t=this;setTimeout(function(){t.emit("progress",t,e),t.jqDeferred&&t.jqDeferred.notify&&t.jqDeferred.notify(t,e)})},s.prototype.complete=function(){var e=this.hasAnyBroken?"fail":"done";this.isComplete=!0;var t=this;setTimeout(function(){if(t.emit(e,t),t.emit("always",t),t.jqDeferred){var n=t.hasAnyBroken?"reject":"resolve";t.jqDeferred[n](t)}})},a&&(a.fn.imagesLoaded=function(e,t){var n=new s(this,e,t);return n.jqDeferred.promise(a(this))}),f.prototype=new t,f.prototype.check=function(){var e=v[this.img.src]||new c(this.img.src);if(e.isConfirmed)return this.confirm(e.isLoaded,"cached was confirmed"),void 0;if(this.img.complete&&void 0!==this.img.naturalWidth)return this.confirm(0!==this.img.naturalWidth,"naturalWidth"),void 0;var t=this;e.on("confirm",function(e,n){return t.confirm(e.isLoaded,n),!0}),e.check()},f.prototype.confirm=function(e,t){this.isLoaded=e,this.emit("confirm",this,t)};var v={};return c.prototype=new t,c.prototype.check=function(){if(!this.isChecked){var e=new Image;n.bind(e,"load",this),n.bind(e,"error",this),e.src=this.src,this.isChecked=!0}},c.prototype.handleEvent=function(e){var t="on"+e.type;this[t]&&this[t](e)},c.prototype.onload=function(e){this.confirm(!0,"onload"),this.unbindProxyEvents(e)},c.prototype.onerror=function(e){this.confirm(!1,"onerror"),this.unbindProxyEvents(e)},c.prototype.confirm=function(e,t){this.isConfirmed=!0,this.isLoaded=e,this.emit("confirm",this,t)},c.prototype.unbindProxyEvents=function(e){n.unbind(e.target,"load",this),n.unbind(e.target,"error",this)},s}); -------------------------------------------------------------------------------- /templates/webshop/checkout/shipping_address.jinja: -------------------------------------------------------------------------------- 1 | {% extends 'checkout/base.jinja' %} 2 | 3 | {% from '_helpers.jinja' import render_field, render_address %} 4 | 5 | {% set checkout_step = 2 %} 6 | {% set checkout_step_name = _('Shipping Address') %} 7 | 8 | 9 | {% block main %} 10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 | {% if not current_user.is_anonymous() and addresses %} 18 |

{{ _('Choose an existing Shipping Address') }}

19 |
20 | {% for address in addresses %} 21 |
22 |
23 |
24 | {{ render_address(address, edit=False) }} 25 |
26 |
27 | 28 | 29 | 32 |
33 |
34 |
35 | {% if loop.index is divisibleby(2) %} 36 |
37 |
38 | {% endif %} 39 | {% endfor %} 40 |
41 | 42 | {# 43 | 44 | 45 |
46 | 49 |
50 | #} 51 | 52 |
53 |
{{ _('or') }}
54 |
55 | {% endif %} 56 | 57 |

58 | {% if addresses %} 59 | {{ _('Add a new address below') }} 60 | {% else %} 61 | {{ _('Enter your shipping address') }} 62 | {% endif %} 63 |

64 |
65 |
66 | 67 |
68 | {{ address_form.hidden_tag() }} 69 | {{ render_field(address_form.name, required=required, class_="input-lg form-control") }} 70 | 71 | {{ render_field(address_form.street, placeholder=_("Street address, P.O. box, company name, c/o"), label_text= _('Address'), required=required, class_="input-lg form-control") }} 72 | {{ render_field(address_form.streetbis, placeholder=_("Apartment, suite, unit, building, floor, etc."), show_label=False, class_="input-lg form-control") }} 73 |
74 |
75 | {{ render_field(address_form.city, required=required, class_="input-lg form-control") }} 76 |
77 |
78 | {{ render_field(address_form.zip, required=required, class_="input-lg form-control") }} 79 |
80 |
81 | {{ render_field(address_form.country, class_="input-lg form-control") }} 82 |
83 |
84 |
85 | 86 | 88 |
89 |
90 |
91 | {{ render_field(address_form.phone, required=required, class_="input-lg form-control", type='tel') }} 92 |
93 |
94 | 97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | 107 | {{ cart_summary() }} 108 |
109 |
110 |
111 |
112 | {% endblock main %} 113 | 114 | {% block scripts %} 115 | {{ super() }} 116 | 137 | {% endblock scripts %} 138 | 139 | 140 | {% block ga_page_view %} 141 | {{ google_analytics_add_product() }} 142 | ga('ec:setAction', 'checkout', {'step': 2}); 143 | {% endblock ga_page_view %} 144 | -------------------------------------------------------------------------------- /product.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | product 4 | 5 | :copyright: (c) 2014 by Openlabs Technologies & Consulting (P) Ltd. 6 | :license: GPLv3, see LICENSE for more details 7 | 8 | ''' 9 | from decimal import Decimal 10 | 11 | from trytond.pool import Pool, PoolMeta 12 | from jinja2.filters import do_striptags 13 | from werkzeug.exceptions import NotFound 14 | 15 | from nereid import jsonify, flash, request, url_for, route, redirect, \ 16 | render_template, abort 17 | from nereid.contrib.locale import make_lazy_gettext 18 | 19 | _ = make_lazy_gettext('gift_card') 20 | 21 | from forms import GiftCardForm 22 | 23 | 24 | __all__ = ['Product'] 25 | 26 | __metaclass__ = PoolMeta 27 | 28 | 29 | class Product: 30 | "Product extension for Nereid" 31 | __name__ = "product.product" 32 | 33 | def get_default_image(self, name): 34 | "Returns default product image" 35 | ModelData = Pool().get('ir.model.data') 36 | 37 | # Get default image from default_image_set of product template 38 | # or product variant. 39 | if self.use_template_images: 40 | if self.template.default_image_set: 41 | return self.template.default_image_set.image.id 42 | elif self.default_image_set: 43 | return self.default_image_set.image.id 44 | 45 | # Fallback condition if there is no default_image_set defined 46 | images = self.get_images() 47 | if images: 48 | return images[0].id 49 | else: 50 | return ModelData.get_id("nereid_webshop", "mystery_box") 51 | 52 | def ga_product_data(self, **kwargs): 53 | ''' 54 | Return a dictionary of the product information as expected by Google 55 | Analytics 56 | 57 | Other possible values for kwargs include 58 | 59 | :param list: The name of the list in which this impression is to be 60 | recorded 61 | :param position: Integer position of the item on the view 62 | ''' 63 | rv = { 64 | 'id': self.code or unicode(self.id), 65 | 'name': self.name, 66 | 'category': self.category and self.category.name or None, 67 | } 68 | rv.update(kwargs) 69 | return rv 70 | 71 | def json_ld(self, **kwargs): 72 | ''' 73 | Returns a JSON serializable dictionary of the product with the Product 74 | schema markup. 75 | 76 | See: http://schema.org/Product 77 | 78 | Any key value pairs passed to kwargs overwrites default information. 79 | ''' 80 | sale_price = self.sale_price(1) 81 | 82 | return { 83 | "@context": "http://schema.org", 84 | "@type": "Product", 85 | 86 | "name": self.name, 87 | "sku": self.code, 88 | "description": do_striptags(self.description), 89 | "offers": { 90 | "@type": "Offer", 91 | "availability": "http://schema.org/InStock", 92 | "price": str(sale_price), 93 | "priceCurrency": request.nereid_currency.code, 94 | }, 95 | "image": self.default_image.transform_command().thumbnail( 96 | 500, 500, 'a').url(_external=True), 97 | "url": self.get_absolute_url(_external=True), 98 | } 99 | 100 | @classmethod 101 | @route('/product/') 102 | @route('/product//') 103 | def render(cls, uri, path=None): 104 | """ 105 | Render gift card template if product is of type gift card 106 | """ 107 | render_obj = super(Product, cls).render(uri, path) 108 | 109 | if not isinstance(render_obj, NotFound) \ 110 | and render_obj.context['product'].is_gift_card: 111 | # Render gift card 112 | return redirect( 113 | url_for('product.product.render_gift_card', uri=uri) 114 | ) 115 | return render_obj 116 | 117 | @classmethod 118 | @route('/gift-card/', methods=['GET', 'POST']) 119 | def render_gift_card(cls, uri): 120 | """ 121 | Add gift card as a new line in cart 122 | Request: 123 | 'GET': Renders gift card page 124 | 'POST': Buy Gift Card 125 | Response: 126 | 'OK' if X-HTTPRequest 127 | Redirect to shopping cart if normal request 128 | """ 129 | SaleLine = Pool().get('sale.line') 130 | Cart = Pool().get('nereid.cart') 131 | 132 | try: 133 | product, = cls.search([ 134 | ('displayed_on_eshop', '=', True), 135 | ('uri', '=', uri), 136 | ('template.active', '=', True), 137 | ('is_gift_card', '=', True) 138 | ], limit=1) 139 | except ValueError: 140 | abort(404) 141 | 142 | form = GiftCardForm(product) 143 | 144 | if form.validate_on_submit(): 145 | cart = Cart.open_cart(create_order=True) 146 | 147 | # Code to add gift card as a line to cart 148 | values = { 149 | 'product': product.id, 150 | 'sale': cart.sale.id, 151 | 'type': 'line', 152 | 'sequence': 10, 153 | 'quantity': 1, 154 | 'unit': None, 155 | 'description': None, 156 | 'recipient_email': form.recipient_email.data, 157 | 'recipient_name': form.recipient_name.data, 158 | 'message': form.message.data, 159 | } 160 | values.update(SaleLine(**values).on_change_product()) 161 | 162 | # Here 0 means the default option to enter open amount is 163 | # selected 164 | if form.selected_amount.data != 0: 165 | values.update({'gc_price': form.selected_amount.data}) 166 | values.update(SaleLine(**values).on_change_gc_price()) 167 | else: 168 | values.update({'unit_price': Decimal(form.open_amount.data)}) 169 | 170 | order_line = SaleLine(**values) 171 | order_line.save() 172 | 173 | message = 'Gift Card has been added to your cart' 174 | if request.is_xhr: # pragma: no cover 175 | return jsonify(message=message) 176 | 177 | flash(_(message), 'info') 178 | return redirect(url_for('nereid.cart.view_cart')) 179 | 180 | return render_template( 181 | 'catalog/gift-card.html', product=product, form=form 182 | ) 183 | 184 | def get_absolute_url(self, **kwargs): 185 | """ 186 | Return gift card URL if product is a gift card 187 | """ 188 | if self.is_gift_card: 189 | return url_for( 190 | 'product.product.render_gift_card', uri=self.uri, **kwargs 191 | ) 192 | return super(Product, self).get_absolute_url(**kwargs) 193 | -------------------------------------------------------------------------------- /tests/test_website.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | Test Website 4 | 5 | :copyright: (c) 2014 by Openlabs Technologies & Consulting (P) LTD 6 | :license: GPLv3, see LICENSE for more details 7 | ''' 8 | from decimal import Decimal 9 | from random import choice 10 | import json 11 | 12 | from trytond.tests.test_tryton import USER, DB_NAME, CONTEXT 13 | from trytond.transaction import Transaction 14 | from test_base import BaseTestCase 15 | 16 | 17 | class TestWebsite(BaseTestCase): 18 | """ 19 | Test case for website. 20 | """ 21 | 22 | def create_test_products(self): 23 | # Create product templates with products 24 | self._create_product_template( 25 | 'product 1', 26 | [{ 27 | 'category': self.category.id, 28 | 'type': 'goods', 29 | 'salable': True, 30 | 'list_price': Decimal('10'), 31 | 'cost_price': Decimal('5'), 32 | 'account_expense': self._get_account_by_kind('expense').id, 33 | 'account_revenue': self._get_account_by_kind('revenue').id, 34 | }], 35 | uri='product-1', 36 | ) 37 | self._create_product_template( 38 | 'product 2', 39 | [{ 40 | 'category': self.category2.id, 41 | 'type': 'goods', 42 | 'salable': True, 43 | 'list_price': Decimal('20'), 44 | 'cost_price': Decimal('5'), 45 | 'account_expense': self._get_account_by_kind('expense').id, 46 | 'account_revenue': self._get_account_by_kind('revenue').id, 47 | }], 48 | uri='product-2', 49 | ) 50 | self._create_product_template( 51 | 'product 3', 52 | [{ 53 | 'category': self.category3.id, 54 | 'type': 'goods', 55 | 'list_price': Decimal('30'), 56 | 'cost_price': Decimal('5'), 57 | 'account_expense': self._get_account_by_kind('expense').id, 58 | 'account_revenue': self._get_account_by_kind('revenue').id, 59 | }], 60 | uri='product-3', 61 | ) 62 | self._create_product_template( 63 | 'product 4', 64 | [{ 65 | 'category': self.category3.id, 66 | 'type': 'goods', 67 | 'list_price': Decimal('30'), 68 | 'cost_price': Decimal('5'), 69 | 'account_expense': self._get_account_by_kind('expense').id, 70 | 'account_revenue': self._get_account_by_kind('revenue').id, 71 | }], 72 | uri='product-4', 73 | ) 74 | 75 | def _create_product_template( 76 | self, name, vlist, uri, uom=u'Unit', displayed_on_eshop=True 77 | ): 78 | """ 79 | Create a product template with products and return its ID 80 | 81 | :param name: Name of the product 82 | :param vlist: List of dictionaries of values to create 83 | :param uri: uri of product template 84 | :param uom: Note it is the name of UOM (not symbol or code) 85 | :param displayed_on_eshop: Boolean field to display product 86 | on shop or not 87 | """ 88 | _code_list = [] 89 | code = choice('ABCDEFGHIJK') 90 | while code in _code_list: 91 | code = choice('ABCDEFGHIJK') 92 | else: 93 | _code_list.append(code) 94 | 95 | for values in vlist: 96 | values['name'] = name 97 | values['default_uom'], = self.Uom.search( 98 | [('name', '=', uom)], limit=1 99 | ) 100 | values['sale_uom'], = self.Uom.search( 101 | [('name', '=', uom)], limit=1 102 | ) 103 | values['products'] = [ 104 | ('create', [{ 105 | 'uri': uri, 106 | 'displayed_on_eshop': displayed_on_eshop, 107 | 'code': code, 108 | }]) 109 | ] 110 | return self.ProductTemplate.create(vlist) 111 | 112 | def test_0010_sitemap(self): 113 | """ 114 | Tests the rendering of the sitemap. 115 | """ 116 | with Transaction().start(DB_NAME, USER, context=CONTEXT): 117 | self.setup_defaults() 118 | app = self.get_app() 119 | 120 | node1, = self.Node.create([{ 121 | 'name': 'Node1', 122 | 'type_': 'catalog', 123 | 'slug': 'node1', 124 | }]) 125 | 126 | node2, = self.Node.create([{ 127 | 'name': 'Node2', 128 | 'type_': 'catalog', 129 | 'slug': 'node2', 130 | 'display': 'product.template', 131 | }]) 132 | 133 | node3, = self.Node.create([{ 134 | 'name': 'Node3', 135 | 'type_': 'catalog', 136 | 'slug': 'node3', 137 | }]) 138 | 139 | node4, = self.Node.create([{ 140 | 'name': 'Node4', 141 | 'type_': 'catalog', 142 | 'slug': 'node4', 143 | }]) 144 | 145 | node5, = self.Node.create([{ 146 | 'name': 'Node5', 147 | 'type_': 'catalog', 148 | 'slug': 'node5', 149 | }]) 150 | 151 | self.Node.write([node2], { 152 | 'parent': node1 153 | }) 154 | 155 | self.Node.write([node3], { 156 | 'parent': node1, 157 | }) 158 | 159 | self.Node.write([node4], { 160 | 'parent': node3, 161 | }) 162 | 163 | self.Node.write([node5], { 164 | 'parent': node4, 165 | }) 166 | 167 | with app.test_client() as c: 168 | rv = c.get('/sitemap') 169 | self.assertEqual(rv.status_code, 200) 170 | 171 | self.assertIn('Node1', rv.data) 172 | self.assertIn('Node2', rv.data) 173 | self.assertIn('Node3', rv.data) 174 | self.assertIn('Node4', rv.data) 175 | 176 | # Beyond depth of 2, will not show. 177 | self.assertNotIn('Node5', rv.data) 178 | 179 | def test_0020_search_data(self): 180 | """ 181 | Tests that the auto-complete search URL returns JSON product data. 182 | """ 183 | with Transaction().start(DB_NAME, USER, context=CONTEXT): 184 | self.setup_defaults() 185 | app = self.get_app() 186 | 187 | with app.test_client() as c: 188 | self.create_test_products() 189 | 190 | rv = c.get('/search-auto-complete?q=product') 191 | self.assertEqual(rv.status_code, 200) 192 | 193 | data = json.loads(rv.data) 194 | 195 | self.assertEquals(data['results'], []) 196 | -------------------------------------------------------------------------------- /static/css/owl.carousel.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Core Owl Carousel CSS File 3 | * v1.24 4 | */ 5 | 6 | /* clearfix */ 7 | .owl-carousel .owl-wrapper:after { 8 | content: "."; 9 | display: block; 10 | clear: both; 11 | visibility: hidden; 12 | line-height: 0; 13 | height: 0; 14 | } 15 | /* display none until init */ 16 | .owl-carousel{ 17 | display: none; 18 | position: relative; 19 | width: 100%; 20 | -ms-touch-action: pan-y; 21 | } 22 | .owl-carousel .owl-wrapper{ 23 | display: none; 24 | position: relative; 25 | -webkit-transform: translate3d(0px, 0px, 0px); 26 | } 27 | .owl-carousel .owl-wrapper-outer{ 28 | overflow: hidden; 29 | position: relative; 30 | width: 100%; 31 | } 32 | .owl-carousel .owl-wrapper-outer.autoHeight{ 33 | -webkit-transition: height 500ms ease-in-out; 34 | -moz-transition: height 500ms ease-in-out; 35 | -ms-transition: height 500ms ease-in-out; 36 | -o-transition: height 500ms ease-in-out; 37 | transition: height 500ms ease-in-out; 38 | } 39 | 40 | .owl-carousel .owl-item{ 41 | float: left; 42 | } 43 | .owl-controls .owl-page, 44 | .owl-controls .owl-buttons div{ 45 | cursor: pointer; 46 | } 47 | .owl-controls { 48 | -webkit-user-select: none; 49 | -khtml-user-select: none; 50 | -moz-user-select: none; 51 | -ms-user-select: none; 52 | user-select: none; 53 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 54 | } 55 | 56 | /* mouse grab icon */ 57 | .grabbing { 58 | cursor:url(../images/grabbing.png) 8 8, move; 59 | } 60 | 61 | /* fix */ 62 | .owl-carousel .owl-wrapper, 63 | .owl-carousel .owl-item{ 64 | -webkit-backface-visibility: hidden; 65 | -moz-backface-visibility: hidden; 66 | -ms-backface-visibility: hidden; 67 | -webkit-transform: translate3d(0,0,0); 68 | -moz-transform: translate3d(0,0,0); 69 | -ms-transform: translate3d(0,0,0); 70 | } 71 | 72 | /* CSS3 Transitions */ 73 | 74 | .owl-origin { 75 | -webkit-perspective: 1200px; 76 | -webkit-perspective-origin-x : 50%; 77 | -webkit-perspective-origin-y : 50%; 78 | -moz-perspective : 1200px; 79 | -moz-perspective-origin-x : 50%; 80 | -moz-perspective-origin-y : 50%; 81 | perspective : 1200px; 82 | } 83 | /* fade */ 84 | .owl-fade-out { 85 | z-index: 10; 86 | -webkit-animation: fadeOut .7s both ease; 87 | -moz-animation: fadeOut .7s both ease; 88 | animation: fadeOut .7s both ease; 89 | } 90 | .owl-fade-in { 91 | -webkit-animation: fadeIn .7s both ease; 92 | -moz-animation: fadeIn .7s both ease; 93 | animation: fadeIn .7s both ease; 94 | } 95 | /* backSlide */ 96 | .owl-backSlide-out { 97 | -webkit-animation: backSlideOut 1s both ease; 98 | -moz-animation: backSlideOut 1s both ease; 99 | animation: backSlideOut 1s both ease; 100 | } 101 | .owl-backSlide-in { 102 | -webkit-animation: backSlideIn 1s both ease; 103 | -moz-animation: backSlideIn 1s both ease; 104 | animation: backSlideIn 1s both ease; 105 | } 106 | /* goDown */ 107 | .owl-goDown-out { 108 | -webkit-animation: scaleToFade .7s ease both; 109 | -moz-animation: scaleToFade .7s ease both; 110 | animation: scaleToFade .7s ease both; 111 | } 112 | .owl-goDown-in { 113 | -webkit-animation: goDown .6s ease both; 114 | -moz-animation: goDown .6s ease both; 115 | animation: goDown .6s ease both; 116 | } 117 | /* scaleUp */ 118 | .owl-fadeUp-in { 119 | -webkit-animation: scaleUpFrom .5s ease both; 120 | -moz-animation: scaleUpFrom .5s ease both; 121 | animation: scaleUpFrom .5s ease both; 122 | } 123 | 124 | .owl-fadeUp-out { 125 | -webkit-animation: scaleUpTo .5s ease both; 126 | -moz-animation: scaleUpTo .5s ease both; 127 | animation: scaleUpTo .5s ease both; 128 | } 129 | /* Keyframes */ 130 | /*empty*/ 131 | @-webkit-keyframes empty { 132 | 0% {opacity: 1} 133 | } 134 | @-moz-keyframes empty { 135 | 0% {opacity: 1} 136 | } 137 | @keyframes empty { 138 | 0% {opacity: 1} 139 | } 140 | @-webkit-keyframes fadeIn { 141 | 0% { opacity:0; } 142 | 100% { opacity:1; } 143 | } 144 | @-moz-keyframes fadeIn { 145 | 0% { opacity:0; } 146 | 100% { opacity:1; } 147 | } 148 | @keyframes fadeIn { 149 | 0% { opacity:0; } 150 | 100% { opacity:1; } 151 | } 152 | @-webkit-keyframes fadeOut { 153 | 0% { opacity:1; } 154 | 100% { opacity:0; } 155 | } 156 | @-moz-keyframes fadeOut { 157 | 0% { opacity:1; } 158 | 100% { opacity:0; } 159 | } 160 | @keyframes fadeOut { 161 | 0% { opacity:1; } 162 | 100% { opacity:0; } 163 | } 164 | @-webkit-keyframes backSlideOut { 165 | 25% { opacity: .5; -webkit-transform: translateZ(-500px); } 166 | 75% { opacity: .5; -webkit-transform: translateZ(-500px) translateX(-200%); } 167 | 100% { opacity: .5; -webkit-transform: translateZ(-500px) translateX(-200%); } 168 | } 169 | @-moz-keyframes backSlideOut { 170 | 25% { opacity: .5; -moz-transform: translateZ(-500px); } 171 | 75% { opacity: .5; -moz-transform: translateZ(-500px) translateX(-200%); } 172 | 100% { opacity: .5; -moz-transform: translateZ(-500px) translateX(-200%); } 173 | } 174 | @keyframes backSlideOut { 175 | 25% { opacity: .5; transform: translateZ(-500px); } 176 | 75% { opacity: .5; transform: translateZ(-500px) translateX(-200%); } 177 | 100% { opacity: .5; transform: translateZ(-500px) translateX(-200%); } 178 | } 179 | @-webkit-keyframes backSlideIn { 180 | 0%, 25% { opacity: .5; -webkit-transform: translateZ(-500px) translateX(200%); } 181 | 75% { opacity: .5; -webkit-transform: translateZ(-500px); } 182 | 100% { opacity: 1; -webkit-transform: translateZ(0) translateX(0); } 183 | } 184 | @-moz-keyframes backSlideIn { 185 | 0%, 25% { opacity: .5; -moz-transform: translateZ(-500px) translateX(200%); } 186 | 75% { opacity: .5; -moz-transform: translateZ(-500px); } 187 | 100% { opacity: 1; -moz-transform: translateZ(0) translateX(0); } 188 | } 189 | @keyframes backSlideIn { 190 | 0%, 25% { opacity: .5; transform: translateZ(-500px) translateX(200%); } 191 | 75% { opacity: .5; transform: translateZ(-500px); } 192 | 100% { opacity: 1; transform: translateZ(0) translateX(0); } 193 | } 194 | @-webkit-keyframes scaleToFade { 195 | to { opacity: 0; -webkit-transform: scale(.8); } 196 | } 197 | @-moz-keyframes scaleToFade { 198 | to { opacity: 0; -moz-transform: scale(.8); } 199 | } 200 | @keyframes scaleToFade { 201 | to { opacity: 0; transform: scale(.8); } 202 | } 203 | @-webkit-keyframes goDown { 204 | from { -webkit-transform: translateY(-100%); } 205 | } 206 | @-moz-keyframes goDown { 207 | from { -moz-transform: translateY(-100%); } 208 | } 209 | @keyframes goDown { 210 | from { transform: translateY(-100%); } 211 | } 212 | 213 | @-webkit-keyframes scaleUpFrom { 214 | from { opacity: 0; -webkit-transform: scale(1.5); } 215 | } 216 | @-moz-keyframes scaleUpFrom { 217 | from { opacity: 0; -moz-transform: scale(1.5); } 218 | } 219 | @keyframes scaleUpFrom { 220 | from { opacity: 0; transform: scale(1.5); } 221 | } 222 | 223 | @-webkit-keyframes scaleUpTo { 224 | to { opacity: 0; -webkit-transform: scale(1.5); } 225 | } 226 | @-moz-keyframes scaleUpTo { 227 | to { opacity: 0; -moz-transform: scale(1.5); } 228 | } 229 | @keyframes scaleUpTo { 230 | to { opacity: 0; transform: scale(1.5); } 231 | } 232 | --------------------------------------------------------------------------------