diff --git a/.coveragerc b/.coveragerc index 5bacb50098f..fa7122d2efe 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,3 @@ [run] source = oscar +omit = *migrations* diff --git a/.gitignore b/.gitignore index 6afcde0ece8..ee161e8dbd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ # Packaging *.pyc *.egg-info -dist/ -build/ +/dist/ +/build/ # Vagrant .vagrant @@ -21,26 +21,28 @@ build/ .idea # Docs -docs/build/* +/docs/build/* *.pdf TODO # Test files .coverage +.noseids coverage.xml violations.txt nosetests.xml -htmlcov/* -.tox/* +/htmlcov/* +/.tox/* # Example sites settings_local.py -sites/sandbox/*.sqlite -sites/sandbox/assets/ -sites/sandbox/public/ -sites/sandbox/whoosh_index/ -sites/sandbox/logs/ -sites/demo/*.sqlite -sites/demo/assets/ -sites/demo/public/ -sites/demo/whoosh_index/ +/sites/sandbox/*.sqlite +/sites/sandbox/assets/ +/sites/sandbox/public/ +/sites/sandbox/whoosh_index/ +/sites/sandbox/logs/ +/sites/demo/*.sqlite +/sites/demo/assets/ +/sites/demo/public/ +/sites/demo/whoosh_index/ +/sites/demo/logs/ diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000000..0ac5e3165a8 --- /dev/null +++ b/.mailmap @@ -0,0 +1,11 @@ +Andrew Ingram AndrewIngram +Sebastian Vetter Sebastian Vetter Sebastian Vetter +Andy Sellick Andy Sellick +Patryk Zawadzki Patryk Zawadzki +John Papanastasiou John Papanastasiou +Jonathan Moss Jonathan Moss +Kura Kura +Mohammad Abbas Mohammad Abbas Mohammad Abbas +Oliver Randell OliverRandell +Paweł Kowalski Paweł Kowalski +Łukasz Lechowicz Łukasz Lechowicz diff --git a/.travis.yml b/.travis.yml index d1e24f2adfe..5c28d2bdcef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,21 @@ language: python python: - - 2.6 - - 2.7 +- '2.6' +- '2.7' env: -# NOTE: easy_install *will* downgrade if setup.py specifies a max version -# This leads to seemingly passing tests - - DJANGO_VERSION=1.4.5 -install: - - easy_install Django==$DJANGO_VERSION + global: + # $TRANSIFEX_PASSWORD for oscar_bot (used in transifex.sh) + secure: FuIlzEsGJiAwhaIRBmRNsq9eXmuzs25fX6BChknW4lDyVAySWMp0+Zps9Bd0JgfFYUG3Ip+OTmksYIoTUsG25ZJS9cq1IFt3QKUAN70YCI/4ZBLeIdICPEyxq+Km179+NeEXmBUug17RLMLxh3MWfO+RKUHK9yHIPNNpq0dNyoo= + matrix: + - DJANGO_VERSION=1.4.10 + - DJANGO_VERSION=1.5.5 +install: +- easy_install Django==$DJANGO_VERSION +before_script: + - mysql -e 'create database oscar_vagrant;' + - psql -c 'create database oscar_vagrant;' -U postgres script: - - make travis +- make travis after_success: - - coveralls +- coveralls +- ./transifex.sh diff --git a/AUTHORS b/AUTHORS index 685f5f1ad94..4936a886235 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,11 +1,18 @@ -The PRIMARY AUTHORS are: - -* David Winterbottom -* Andrew Ingram -* Sebastian Vetter - -Significant contributions from: - -* Patryk Zawadzki -* Tomasz Rybarczyk -* Janusz Harkot +David Winterbottom +Sebastian Vetter +Andrew Ingram +Jon Price +Maik Hoepfel +Asia Biega +Andy Sellick +Tomasz Rybarczyk +Eleni Lixourioti +OliverRandell +Kura +Paweł Kowalski +Dawid Lorenz +Pavel Vavruska +Jonathan Moss +Janusz Harkot +Patryk Zawadzki +AndrewIngram diff --git a/CHANGELOG.rst b/CHANGELOG.rst index efc0fd5855c..d713fdfe099 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,67 @@ Changelog For releases after 0.4, see the release notes in the docs. +0.4.11 - 2013-08-08 +------------------- + +Add extra blocks to order dashboard template. + +0.4.10 - 2013-07-03 +------------------- + +Extend range of bankcard expiry month field. + +0.4.9 - 2013-04-17 +------------------ + +Make ``AbstractStockRecord`` abstract (`#645`) + +.. _`#645`: https://github.com/tangentlabs/django-oscar/pull/645 + +0.4.8 - 2013-04-08 +------------------ + +Fix bug with order dashboard line editing (`#622`_) + +.. _`#622`: https://github.com/tangentlabs/django-oscar/pull/622 + +0.4.7 - 2013-03-20 +------------------ + +Fix bug with order dashboard searching (`#587`_) + +.. _`#587`: https://github.com/tangentlabs/django-oscar/pull/587 + +0.4.6 - 2013-03-05 +------------------ + +Fix dependencies in ``setup.py`` + +0.4.4 - 2013-01-25 +------------------ + +Extend ``get_class`` to support loading from non-Oscar packages + +0.4.4 - 2013-01-16 +------------------ + +Correct django-haystack in setup.py + +0.4.3 - 2013-01-16 +------------------ + +Pin django-haystack version as backwards-incompatiable changes are happening + +0.4.2 - 2012-12-14 +------------------ + +Nano-release to fix logout redirect bug + +0.4.1 - 2012-12-06 +------------------ + +Nano-release to bump dependency versions. + 0.4 - 2012-10-19 ---------------- @@ -53,6 +114,29 @@ changes. Please ask on the mailing list if any other problems are encountered. +0.3.7 - 2013-07-03 +------------------ +* Extend number of years in bankcard expiry field + +0.3.6 - 2013-04-08 +------------------- +* Fix line-handling bug in order dashboard. + +0.3.5 - 2012-09-28 +------------------ +A couple of minor adjustments for Tangent projects + +* Add handling of custom redirect after adding to basket +* Add recursive URL decoration + +0.3.4 - 2012-09-24 +------------------ + +* Rework price lookups in offer calculations (backport of functionality from 0.4) +* Add additional block to profile template + +Diff: https://github.com/tangentlabs/django-oscar/compare/0.3.3...0.3.4 + 0.3.3 - 2012-08-24 ------------------- @@ -111,6 +195,11 @@ instead of:: # base.html {% extends 'templates/base.html' %} +0.2.2 - 13 July 2012 +~~~~~~~~~~~~~~~~~~~~ + +Fixes a bug with applying absolute-discount benefits + 0.2.1 - 09 July 2012 -------------------- diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst old mode 100755 new mode 100644 diff --git a/MANIFEST.in b/MANIFEST.in index f1f19f30018..5d4e558ade7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,8 @@ include *.rst +include AUTHORS +include LICENSE recursive-include oscar/templates *.txt *.html recursive-include oscar/apps *.json recursive-include oscar/fixtures *.json recursive-include oscar/static * -recursive-include oscar/locale *.po +recursive-include oscar/locale *.po *.mo diff --git a/Makefile b/Makefile index 9bdc392af39..6b197f3d576 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ .PHONY: install upgrade sandbox demo coverage ci i18n lint travis docs install: - pip install -r requirements.txt --use-mirrors + pip install -r requirements.txt python setup.py develop upgrade: @@ -15,27 +15,42 @@ sandbox: install sites/sandbox/manage.py syncdb --noinput sites/sandbox/manage.py migrate # Import some fixtures - sites/sandbox/manage.py oscar_import_catalogue sites/_fixtures/books-catalogue.csv - sites/sandbox/manage.py oscar_import_catalogue_images sites/_fixtures/books-images.tar.gz + sites/sandbox/manage.py oscar_import_catalogue sites/sandbox/fixtures/*.csv + sites/sandbox/manage.py oscar_import_catalogue_images sites/sandbox/fixtures/images.tar.gz sites/sandbox/manage.py loaddata countries.json sites/_fixtures/pages.json sites/_fixtures/auth.json sites/_fixtures/ranges.json sites/_fixtures/offers.json - sites/sandbox/manage.py rebuild_index --noinput + sites/sandbox/manage.py clear_index --noinput + sites/sandbox/manage.py update_index catalogue + +geoip: + wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz + gunzip GeoLiteCity.dat.gz + mv GeoLiteCity.dat sites/demo/geoip demo: install - -rm -f sites/demo/db.sqlite + # Install additional requirements + pip install -r requirements_demo.txt # Create database + sites/demo/manage.py reset_db --router=default --noinput sites/demo/manage.py syncdb --noinput sites/demo/manage.py migrate - # Import some fixtures - sites/demo/manage.py oscar_import_catalogue sites/_fixtures/books-catalogue.csv - sites/demo/manage.py oscar_import_catalogue_images sites/_fixtures/books-images.tar.gz - sites/demo/manage.py loaddata countries.json sites/_fixtures/pages.json sites/_fixtures/auth.json sites/_fixtures/ranges.json - sites/demo/manage.py rebuild_index --noinput + # Import some core fixtures + sites/demo/manage.py loaddata countries.json sites/_fixtures/pages.json + # Create catalogue (create product classes from fixture than import CSV files) + sites/demo/manage.py loaddata sites/demo/fixtures/auth.json sites/demo/fixtures/offers.json + sites/demo/manage.py loaddata sites/demo/fixtures/product-classes.json sites/demo/fixtures/product-attributes.json sites/demo/fixtures/shipping-event-types.json + sites/demo/manage.py create_products --class=Books sites/demo/fixtures/books.csv + sites/demo/manage.py create_products --class=Downloads sites/demo/fixtures/downloads.csv + sites/demo/manage.py create_products --class=Clothing sites/demo/fixtures/clothing.csv + sites/demo/manage.py import_product_images sites/demo/fixtures/images/ + # Update search index + sites/demo/manage.py clear_index --noinput + sites/demo/manage.py update_index catalogue docs: cd docs && make html coverage: - coverage run ./runtests.py + coverage run ./runtests.py --with-xunit coverage xml -i # We probably should use upgrade instead of install here but we have a conflict @@ -51,6 +66,8 @@ lint: # and upgrade would overwrite it. We also build the sandbox as part of this target # to catch any errors that might come from that build process. travis: install lint coverage sandbox + pip install -r requirements_vagrant.txt + cd sites/sandbox && ./test_migrations.sh messages: # Create the .po files used for i18n @@ -62,12 +79,13 @@ compiledmessages: puppet: # Install puppet modules required to set-up a Vagrant box + mkdir -p sites/puppet/modules rm -rf sites/puppet/modules/* puppet module install --target-dir sites/puppet/modules/ saz-memcached -v 2.0.2 puppet module install --target-dir sites/puppet/modules/ puppetlabs/mysql puppet module install --target-dir sites/puppet/modules/ puppetlabs/apache git clone git://github.com/akumria/puppet-postgresql.git sites/puppet/modules/postgresql - git clone git://github.com/uggedal/puppet-module-python.git sites/puppet/modules/python + git clone git://github.com/puppetmodules/puppet-module-python.git sites/puppet/modules/python git clone git://github.com/codeinthehole/puppet-userconfig.git sites/puppet/modules/userconfig css: @@ -75,3 +93,13 @@ css: lessc oscar/static/oscar/less/styles.less > oscar/static/oscar/css/styles.css lessc oscar/static/oscar/less/responsive.less > oscar/static/oscar/css/responsive.css lessc oscar/static/oscar/less/dashboard.less > oscar/static/oscar/css/dashboard.css + +demo_css: + # Compile CSS for demo site + lessc sites/demo/static/demo/less/styles.less > sites/demo/static/demo/css/styles.css + lessc sites/demo/static/demo/less/responsive.less > sites/demo/static/demo/css/responsive.css + +clean: + # Remove files not in source control + find . -type f -name "*.pyc" -delete + rm -rf nosetests.xml coverage.xml htmlcov *.egg-info *.pdf dist violations.txt diff --git a/README.rst b/README.rst index afbb64ea41c..133a85e8f9f 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,5 @@ -.. image:: http://img692.imageshack.us/img692/6498/logovf.png +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/logos/oscar.png + :target: http://oscarcommerce.com =================================== Domain-driven e-commerce for Django @@ -19,29 +20,42 @@ sites rich in domain-specific business logic. Further reading: * `Official homepage`_ +* `Demo site`_ (a reference build of an Oscar project) * `Sandbox site`_ (an hourly build of the unstable master branch - it's experimental but feel free to explore and get a feel for the base Oscar - install. Polished demo site coming soon) + install.) * `Documentation`_ on the excellent `readthedocs.org`_ * `Google Group`_ - the mailing list is django-oscar@googlegroups.com * `Continuous integration homepage`_ on `travis-ci.org`_ * `Twitter account for news and updates`_ -* `Twitter account of all commits`_ * `crate.io page`_ * `PyPI page`_ * `Transifex project`_ - translating Oscar made easy Continuous integration status: -.. image:: https://secure.travis-ci.org/tangentlabs/django-oscar.png +.. image:: https://secure.travis-ci.org/tangentlabs/django-oscar.png?branch=master :target: http://travis-ci.org/#!/tangentlabs/django-oscar .. image:: https://coveralls.io/repos/tangentlabs/django-oscar/badge.png?branch=master :alt: Coverage :target: https://coveralls.io/r/tangentlabs/django-oscar +PyPI status: + +.. image:: https://pypip.in/v/django-oscar/badge.png + :target: https://crate.io/packages/django-oscar/ + +.. image:: https://pypip.in/d/django-oscar/badge.png + :target: https://crate.io/packages/django-oscar/ + +.. image:: https://d2weczhvl823v0.cloudfront.net/tangentlabs/django-oscar/trend.png + :alt: Bitdeli badge + :target: https://bitdeli.com/free + .. _`Official homepage`: http://oscarcommerce.com .. _`Sandbox site`: http://latest.oscarcommerce.com +.. _`Demo site`: http://demo.oscarcommerce.com .. _`Documentation`: http://django-oscar.readthedocs.org/en/latest/ .. _`readthedocs.org`: http://readthedocs.org .. _`Continuous integration homepage`: http://travis-ci.org/#!/tangentlabs/django-oscar @@ -54,22 +68,21 @@ Continuous integration status: .. _`Transifex project`: https://www.transifex.com/projects/p/django-oscar/ Oscar was written by `David Winterbottom`_ (`@codeinthehole`_) and is developed -and maintained by `Tangent Labs`_, a London-based digital agency, with help from -`Mirumee`_. - -.. _`Mirumee`: http://mirumee.com/ +and maintained by `Tangent Labs`_, a London-based digital agency. .. _`David Winterbottom`: http://codeinthehole.com .. _`@codeinthehole`: https://twitter.com/codeinthehole .. _`Tangent Labs`: http://www.tangentlabs.co.uk -.. _`Mirumee`: http://mirumee.com/ Screenshots ----------- -These are a few screenshots from the 'sandbox' example site that ships with -Oscar. It sports a simple design built with Twitter's Bootstrap_. It provides a -good starting point for quickly building elegant e-commerce sites. +Sandbox +~~~~~~~ + +These are screenshots from the 'sandbox' example site that ships with +Oscar. It sports a simple design built with Twitter's Bootstrap_ and provides a +good starting point for rapidly building elegant e-commerce sites. .. _Bootstrap: http://twitter.github.com/bootstrap/ @@ -86,15 +99,33 @@ good starting point for quickly building elegant e-commerce sites. :target: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/dashboard.png The sandbox site is also available to browse at -http://latest.oscarcommerce.com +http://latest.oscarcommerce.com. Dashboard users can be created using `this +gateway page`_. -You can have this sample shop running on your machine `in 5 commands`_. +The sandbox site can be set-up locally `in 5 commands`_. Want to +make changes? Check out the `contributing guidelines`_. +.. _`this gateway page`: http://latest.oscarcommerce.com/gateway/ .. _`in 5 commands`: http://django-oscar.readthedocs.org/en/latest/internals/sandbox.html#running-the-sandbox-locally +.. _`contributing guidelines`: http://django-oscar.readthedocs.org/en/latest/internals/contributing/index.html + +Demo +~~~~ -Want to make changes? Check out the `contributing guidelines`_. +Oscar also ships with a demo site, which is a reference build of an Oscar +project. It integrates with Oscar's stores_, PayPal_ and Datacash_ extensions. -.. _`contributing guidelines`: http://django-oscar.readthedocs.org/en/latest/contributing/index.html +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/demo.home.thumb.png + :target: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/demo.home.png + +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/demo.browse.thumb.png + :target: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/demo.browse.png + +The demo site is also available to browse at +http://demo.oscarcommerce.com + +.. _stores: https://github.com/tangentlabs/django-oscar-stores +.. _PayPal: https://github.com/tangentlabs/django-oscar-paypal Extensions ---------- @@ -110,7 +141,10 @@ The following extensions are stable and ready for use: functionality and loyalty schemes) * django-oscar-stores_ - Physical stores integration (opening hours, store locator etc) -* django-oscar-testsupport_ - Testing utilities for Oscar extensions. +* django-oscar-easyrec_ - Recomendations using EasyRec_ + locator etc) +* django-oscar-testsupport_ - Testing utilities for Oscar extensions + (deprecated) .. _django-oscar-datacash: https://github.com/tangentlabs/django-oscar-datacash .. _django-oscar-paymentexpress: https://github.com/tangentlabs/django-oscar-paymentexpress @@ -125,22 +159,44 @@ The following extensions are stable and ready for use: .. _Jirafe: https://jirafe.com/ .. _django-oscar-accounts: https://github.com/tangentlabs/django-oscar-accounts .. _django-oscar-testsupport: https://github.com/tangentlabs/django-oscar-testsupport +.. _django-oscar-easyrec: https://github.com/tangentlabs/django-oscar-easyrec +.. _EasyRec: http://easyrec.org/ -The following extensions are in development: +The following extensions are in development by Tangent: +* django-oscar-stripe_ - Integration with the Stripe_ payment gateway * django-oscar-gocardless_ - Integration with the GoCardless_ payment gateway * django-oscar-jirafe_ - Integration with the Jirafe_ analytics package * django-oscar-parachute_ - Import scripts for migrating away from non-Oscar platforms. +* django-oscar-eway_ - Integration with the eWay_ payment gateway. * django-oscar-approval_ - Approval workflow for authorising new orders/products. +.. _django-oscar-stripe: https://github.com/tangentlabs/django-oscar-stripe .. _django-oscar-stores: https://github.com/tangentlabs/django-oscar-stores .. _django-oscar-parachute: https://github.com/tangentlabs/django-oscar-parachute .. _django-oscar-approval: https://github.com/tangentlabs/django-oscar-approval +.. _Stripe: https://stripe.com +.. _django-oscar-eway: https://github.com/tangentlabs/django-oscar-eway +.. _eWay: https://www.eway.com.au + +The following are community-written extensions: + +* django-oscar-unicredit_ - Integration with the Unicredit payment gateway +* django-oscar-payments_ - Pluggable payments for Oscar +* django-oscar-recurly_ - Integration with the Recurly payment gateway +* oscar-sagepay_ - Payment integration with Sage Pay +* django-oscar-erp_ Let us know if you're writing a new one! +.. _django-oscar-unicredit: https://bitbucket.org/marsim/django-oscar-unicredit/ +.. _django-oscar-erp: https://bitbucket.org/zikzakmedia/django-oscar_erp +.. _django-oscar-payments: https://github.com/Lacrymology/django-oscar-payments +.. _django-oscar-recurly: https://github.com/mynameisgabe/django-oscar-recurly +.. _oscar-sagepay: https://github.com/udox/oscar-sagepay + License ------- @@ -151,10 +207,10 @@ Oscar is released under the permissive `New BSD license`_. Case studies ------------ -Oscar is still in active development, but is used in production by a range of +Oscar is still in active development but is used in production by a range of companies, from large multinationals to small, boutique stores: -Tangent projects: +Selected Tangent projects: * Tata Group - http://www.landmarkonthenet.com * Carlsberg - Their global "We Deliver More" platform is powered by Oscar (but @@ -162,6 +218,8 @@ Tangent projects: * Chocolate Box - https://www.thechocolatebox.com.au * The UK Labour party - http://shop.labour.org.uk * Meridian Audio - http://www.meridian-audio.co.uk +* Which Rightchoice - http://www.whichrightchoice.com +* Freetix - http://www.freetix.com.au/ .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/landmark.thumb.png :target: http://www.landmarkonthenet.com @@ -178,12 +236,22 @@ Tangent projects: .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/meridian.thumb.png :target: http://www.meridian-audio.co.uk +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/rightchoice.thumb.png + :target: http://www.whichrightchoice.com + +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/freetix.thumb.png + :target: http://www.freetix.com.au/ + Non-Tangent: * Dolbeau - http://www.dolbeau.ca/ * Sobusa - http://www.sobusa.fr/ * Laivee - http://laivee.pl * Colinss - http://colinss.com +* Audio App - https://audioapp.pl/ +* Anything Gift - http://www.anythinggift.co.uk +* FP Sport - http://www.fpsport.it +* Garmsby - https://garmsby.co.uk .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/dolbeau.thumb.png :target: http://www.dolbeau.ca @@ -197,12 +265,50 @@ Non-Tangent: .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/colinss.thumb.png :target: http://www.colinss.com +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/audioapp.thumb.png + :target: https://audioapp.pl + +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/anythinggift.thumb.png + :target: http://www.anythinggift.co.uk + +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/fpsport.thumb.png + :target: https://www.fpsport.it + +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/garmsby.thumb.png + :target: https://garmsby.co.uk + Many more on the way. If you use Oscar in production, please let us know. -Would you like to work on Oscar? --------------------------------- +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/logos/tangentlabs.jpg + :target: http://www.tangentlabs.co.uk/ + +Oscar resources +--------------- + +Presentations: -Tangent Labs are currently looking for python hackers to work on Oscar as well +.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/presentations/oscon2012.png + :target: https://speakerdeck.com/codeinthehole/writing-a-django-e-commerce-framework-1 + +Looking for commercial support? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are interested in having an Oscar project built for you, or for +development of an existing Oscar site, Tangent can +help. Please get in touch via `oscar@tangentlabs.co.uk`_ or via the `Tangent +Snowball`_ site. + +.. _`oscar@tangentlabs.co.uk`: mailto:oscar@tangentlabs.co.uk +.. _`Tangent Snowball`: http://www.tangentsnowball.com/products/oscar + +Want to get paid to work on Oscar? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`Tangent Labs`_ are currently looking for python hackers to work on Oscar as well as some of other internal products and e-commerce projects. If this sounds -interesting, please get in touch with @codeinthehole through Github or Twitter. -The position is in Tangent's London offices. +interesting, please email `recruitment@tangentlabs.co.uk`_. + +The position is in Tangent's London offices and you must have the appropriate +visas to work in the UK. + +.. _`recruitment@tangentlabs.co.uk`: mailto:recruitment@tangentlabs.co.uk diff --git a/create_migration.py b/create_migration.py deleted file mode 100755 index bae629cbe20..00000000000 --- a/create_migration.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -""" -Convenience script to create migrations -""" -from optparse import OptionParser - -from tests.config import configure -configure() - - -def create_migration(app_label, **kwargs): - from south.management.commands.schemamigration import Command - com = Command() - com.handle(app=app_label, **kwargs) - - -if __name__ == '__main__': - parser = OptionParser() - parser.add_option('-i', '--initial', dest='initial', - action='store_true', default=False) - parser.add_option('-a', '--auto', dest='auto', - action='store_true', default=False) - parser.add_option('-e', '--empty', dest='empty', - action='store_true', default=False) - parser.add_option('-n', '--name', dest='name', default='') - (options, args) = parser.parse_args() - create_migration(args[0], initial=options.initial, - auto=options.auto, empty=options.empty, - name=options.name) diff --git a/docs/Makefile b/docs/Makefile index df38f4040a5..e26baeeda2b 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -37,38 +37,38 @@ clean: -rm -rf $(BUILDDIR)/* html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + $(SPHINXBUILD) -a -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + $(SPHINXBUILD) -a -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + $(SPHINXBUILD) -a -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + $(SPHINXBUILD) -a -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + $(SPHINXBUILD) -a -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + $(SPHINXBUILD) -a -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + $(SPHINXBUILD) -a -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @@ -77,7 +77,7 @@ qthelp: @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-oscar.qhc" devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + $(SPHINXBUILD) -a -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @@ -86,45 +86,45 @@ devhelp: @echo "# devhelp" epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + $(SPHINXBUILD) -a -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + $(SPHINXBUILD) -a -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + $(SPHINXBUILD) -a -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + $(SPHINXBUILD) -a -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + $(SPHINXBUILD) -a -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + $(SPHINXBUILD) -a -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + $(SPHINXBUILD) -a -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + $(SPHINXBUILD) -a -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/images/logos/labs-ts-oscar.jpg b/docs/images/logos/labs-ts-oscar.jpg new file mode 100644 index 00000000000..7fa0316b7b3 Binary files /dev/null and b/docs/images/logos/labs-ts-oscar.jpg differ diff --git a/docs/images/logos/oscar.png b/docs/images/logos/oscar.png new file mode 100644 index 00000000000..bc1c2c67b1c Binary files /dev/null and b/docs/images/logos/oscar.png differ diff --git a/docs/images/logos/tangentlabs.jpg b/docs/images/logos/tangentlabs.jpg new file mode 100644 index 00000000000..0996607c84b Binary files /dev/null and b/docs/images/logos/tangentlabs.jpg differ diff --git a/docs/images/presentations/oscon2012.png b/docs/images/presentations/oscon2012.png new file mode 100644 index 00000000000..241fb1a7fed Binary files /dev/null and b/docs/images/presentations/oscon2012.png differ diff --git a/docs/images/screenshots/anythinggift.png b/docs/images/screenshots/anythinggift.png new file mode 100644 index 00000000000..c85a7643b9a Binary files /dev/null and b/docs/images/screenshots/anythinggift.png differ diff --git a/docs/images/screenshots/anythinggift.thumb.png b/docs/images/screenshots/anythinggift.thumb.png new file mode 100644 index 00000000000..e80836db5ae Binary files /dev/null and b/docs/images/screenshots/anythinggift.thumb.png differ diff --git a/docs/images/screenshots/audioapp.png b/docs/images/screenshots/audioapp.png new file mode 100644 index 00000000000..4b4ef4e16e4 Binary files /dev/null and b/docs/images/screenshots/audioapp.png differ diff --git a/docs/images/screenshots/audioapp.thumb.png b/docs/images/screenshots/audioapp.thumb.png new file mode 100644 index 00000000000..d73aad80c01 Binary files /dev/null and b/docs/images/screenshots/audioapp.thumb.png differ diff --git a/docs/images/screenshots/basket.thumb.png b/docs/images/screenshots/basket.thumb.png index 59b551a536a..dfe4bf888cd 100644 Binary files a/docs/images/screenshots/basket.thumb.png and b/docs/images/screenshots/basket.thumb.png differ diff --git a/docs/images/screenshots/browse.thumb.png b/docs/images/screenshots/browse.thumb.png index 83bad13a897..3f73ba5ad13 100644 Binary files a/docs/images/screenshots/browse.thumb.png and b/docs/images/screenshots/browse.thumb.png differ diff --git a/docs/images/screenshots/carlsberg.cch.thumb.png b/docs/images/screenshots/carlsberg.cch.thumb.png index 7a7d58f1a4b..8dcc00fac1c 100644 Binary files a/docs/images/screenshots/carlsberg.cch.thumb.png and b/docs/images/screenshots/carlsberg.cch.thumb.png differ diff --git a/docs/images/screenshots/chocolatebox.thumb.png b/docs/images/screenshots/chocolatebox.thumb.png index a0c08c27fa1..bed75579dde 100644 Binary files a/docs/images/screenshots/chocolatebox.thumb.png and b/docs/images/screenshots/chocolatebox.thumb.png differ diff --git a/docs/images/screenshots/colinss.thumb.png b/docs/images/screenshots/colinss.thumb.png index 033e1c4db3c..a7520ffc0fa 100644 Binary files a/docs/images/screenshots/colinss.thumb.png and b/docs/images/screenshots/colinss.thumb.png differ diff --git a/docs/images/screenshots/dashboard.thumb.png b/docs/images/screenshots/dashboard.thumb.png index f70f93099e6..10cc82eed6a 100644 Binary files a/docs/images/screenshots/dashboard.thumb.png and b/docs/images/screenshots/dashboard.thumb.png differ diff --git a/docs/images/screenshots/demo.browse.png b/docs/images/screenshots/demo.browse.png new file mode 100644 index 00000000000..e20da8a73d4 Binary files /dev/null and b/docs/images/screenshots/demo.browse.png differ diff --git a/docs/images/screenshots/demo.browse.thumb.png b/docs/images/screenshots/demo.browse.thumb.png new file mode 100644 index 00000000000..97e11757534 Binary files /dev/null and b/docs/images/screenshots/demo.browse.thumb.png differ diff --git a/docs/images/screenshots/demo.home.png b/docs/images/screenshots/demo.home.png new file mode 100644 index 00000000000..4d8764c1282 Binary files /dev/null and b/docs/images/screenshots/demo.home.png differ diff --git a/docs/images/screenshots/demo.home.thumb.png b/docs/images/screenshots/demo.home.thumb.png new file mode 100644 index 00000000000..a1ddac4edb5 Binary files /dev/null and b/docs/images/screenshots/demo.home.thumb.png differ diff --git a/docs/images/screenshots/detail.thumb.png b/docs/images/screenshots/detail.thumb.png index dec3da64f19..cfdc64beaab 100644 Binary files a/docs/images/screenshots/detail.thumb.png and b/docs/images/screenshots/detail.thumb.png differ diff --git a/docs/images/screenshots/dolbeau.thumb.png b/docs/images/screenshots/dolbeau.thumb.png index d5ebd45ba71..fdc51a53348 100644 Binary files a/docs/images/screenshots/dolbeau.thumb.png and b/docs/images/screenshots/dolbeau.thumb.png differ diff --git a/docs/images/screenshots/fpsport.thumb.png b/docs/images/screenshots/fpsport.thumb.png new file mode 100644 index 00000000000..fa8ee35f2c9 Binary files /dev/null and b/docs/images/screenshots/fpsport.thumb.png differ diff --git a/docs/images/screenshots/freetix.png b/docs/images/screenshots/freetix.png new file mode 100644 index 00000000000..b7ef6619d9c Binary files /dev/null and b/docs/images/screenshots/freetix.png differ diff --git a/docs/images/screenshots/freetix.thumb.png b/docs/images/screenshots/freetix.thumb.png new file mode 100644 index 00000000000..86a5648ce86 Binary files /dev/null and b/docs/images/screenshots/freetix.thumb.png differ diff --git a/docs/images/screenshots/garmsby.thumb.png b/docs/images/screenshots/garmsby.thumb.png new file mode 100644 index 00000000000..8ad722ae9f1 Binary files /dev/null and b/docs/images/screenshots/garmsby.thumb.png differ diff --git a/docs/images/screenshots/labourshop.thumb.png b/docs/images/screenshots/labourshop.thumb.png index 981d36a7a94..8cfae7e2710 100644 Binary files a/docs/images/screenshots/labourshop.thumb.png and b/docs/images/screenshots/labourshop.thumb.png differ diff --git a/docs/images/screenshots/laivee.thumb.png b/docs/images/screenshots/laivee.thumb.png index e540d70fa43..81880712fcd 100644 Binary files a/docs/images/screenshots/laivee.thumb.png and b/docs/images/screenshots/laivee.thumb.png differ diff --git a/docs/images/screenshots/landmark.thumb.png b/docs/images/screenshots/landmark.thumb.png index 5aff856036e..520533035ab 100644 Binary files a/docs/images/screenshots/landmark.thumb.png and b/docs/images/screenshots/landmark.thumb.png differ diff --git a/docs/images/screenshots/meridian.thumb.png b/docs/images/screenshots/meridian.thumb.png index cc5cc18109a..2291e25548c 100644 Binary files a/docs/images/screenshots/meridian.thumb.png and b/docs/images/screenshots/meridian.thumb.png differ diff --git a/docs/images/screenshots/oscarcommerce.thumb.png b/docs/images/screenshots/oscarcommerce.thumb.png index 0c5b1cd9639..e9a6ad09fad 100644 Binary files a/docs/images/screenshots/oscarcommerce.thumb.png and b/docs/images/screenshots/oscarcommerce.thumb.png differ diff --git a/docs/images/screenshots/readthedocs.thumb.png b/docs/images/screenshots/readthedocs.thumb.png index e2a83f91677..7385db184f4 100644 Binary files a/docs/images/screenshots/readthedocs.thumb.png and b/docs/images/screenshots/readthedocs.thumb.png differ diff --git a/docs/images/screenshots/rightchoice.png b/docs/images/screenshots/rightchoice.png new file mode 100644 index 00000000000..38a25e5b894 Binary files /dev/null and b/docs/images/screenshots/rightchoice.png differ diff --git a/docs/images/screenshots/rightchoice.thumb.png b/docs/images/screenshots/rightchoice.thumb.png new file mode 100644 index 00000000000..50bddf5b49e Binary files /dev/null and b/docs/images/screenshots/rightchoice.thumb.png differ diff --git a/docs/images/screenshots/sobusa.thumb.png b/docs/images/screenshots/sobusa.thumb.png index e7373f7e63a..c640ce1f1c2 100644 Binary files a/docs/images/screenshots/sobusa.thumb.png and b/docs/images/screenshots/sobusa.thumb.png differ diff --git a/docs/source/_old/offers.rst b/docs/source/_old/offers.rst deleted file mode 100644 index 1f0a9317bcb..00000000000 --- a/docs/source/_old/offers.rst +++ /dev/null @@ -1,75 +0,0 @@ -=================== -Offers and vouchers -=================== - -The ``oscar.offers`` app offers functionality for basket-level offers and vouchers. This covers -features such as: - -* 3-for-2 offers on fiction books -* Buy one book, get a DVD for half price -* A Christmas voucher code that gives 25% off all products until December 25th -* All students get £5 off their first order -* Visitors coming from an affiliate site get 10% off their order -* A voucher code that can be used once by each customer and gives 25% off - -In short, it is very flexible. - -Basket-level offers -------------------- -A conditional offer has metadata such as a name, description and date range (for when it is active) -but is defined by two things: - -* A *Condition*, which is some criteria that the user's basket has to have met. These are - either count- (e.g., must contain one fiction book) or value-based (e.g., must contain £10 or more - of DVDs). -* A *Benefit*, which is the discount that is applied to the basket. - -When an offer is applied the basket, the products that are used to meet the condition, and the -products discounted by the benefit are "consumed" so that they are not available for other offers. - -Offers come in 4 types: - -* *Site offers* - Offers that are available to all users of the site. Every time a basket is looked - up for a user, we attempt to apply these offers to give a discount. An example is a "3 for 2" offer - on all fiction books. - -* *Voucher offers* - Offers that are available if you have attached a specific voucher code to your basket. - For example, a voucher could be created that links to a 25% off offer on all products. - -* *User offers* - Offers available only to certain users - these are looked up by passing the ``auth.User`` - object to a look-up service that returns any relevant offers. This could be used to give student - users a discount or something like that. - -* *Session offers* - Offers available for the duration of the current session. These have to be inserted - into the session by some mechanism, but exist to allow functionality such as giving discount to - users that come from an affiliate site. - -In practice, all these offers are loaded and merged into a single set which is then applied to the basket. - -When creating an offer, you need to specify which type of offer it is. - -Vouchers --------- - -A voucher is essentially a code which links through to a set of basket-level -offers (see above). Note that you can have any number of vouchers attached to -a basket. - - -Basket discounts ----------------- - -* *Multibuy* - This gives the cheapest product in the condition range for free. - This is normally used to build 3-for-2 offers and similar. - -* *Percentage discount* - A percentage discount off the basket products that are - in the benefit range. - -* *Absolute discount* - An absolute discount off the basket products in the - benefit range. - -* *Fixed price* - - -* - - diff --git a/docs/source/conf.py b/docs/source/conf.py index 5d5584cd311..bb0916d2d4e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -227,6 +227,9 @@ [u'David Winterbottom'], 1) ] +# Autodoc settings +autoclass_content = 'both' + # Better documenting of Django models # See http://djangosnippets.org/snippets/2533/ diff --git a/docs/source/howto/how_do_i_translate_oscar.rst b/docs/source/howto/how_do_i_translate_oscar.rst deleted file mode 100644 index a6148232b94..00000000000 --- a/docs/source/howto/how_do_i_translate_oscar.rst +++ /dev/null @@ -1,58 +0,0 @@ -========================= -How do I translate Oscar? -========================= - -Before doing any translation work, ensure you are famility with `Django's i18n -guidelines`_. - -.. _`Django's i18n guidelines`: https://docs.djangoproject.com/en/dev/topics/i18n/ - -Contributing translations to Oscar ----------------------------------- - -To submit a new set of translations for Oscar, do the following: - -1. Fork the repo and install Oscar's repo then navigate to the ``oscar`` - folder:: - - git clone git@github.com:$USERNAME/django-oscar.git - cd django-oscar/ - mkvirtualenv oscar - make sandbox - -2. Generate the message files for a given language:: - - django-admin.py makemessages --locale=$LANGUAGE_CODE - -3. Use the Rosetta_ functionality within the sandbox to add translations for the - new messages files.:: - - cd sites/sandbox - ./manage.py runserver - open http://localhost:8000/rosetta - -.. _Rosetta: https://github.com/mbi/django-rosetta - -4. Send a pull request!:: - - git checkout -b new-translation - git add oscar/locale - git commit - git push origin new-translation - -Your work will be much appreciated. - -Translating Oscar within your project -------------------------------------- - -If Oscar does not provide translations for your language, or if you want to -provide your own, do the following. - -Within your project, create a locale folder and a symlink to Oscar so that ``./manage.py -makemessages`` finds Oscar's translatable strings:: - - mkdir locale i18n - ln -s $PATH_TO_OSCAR i18n/oscar - ./manage.py makemessages --symlinks --locale=de - -This will create the message files that you can now translate. diff --git a/docs/source/howto/how_to_change_a_url.rst b/docs/source/howto/how_to_change_a_url.rst index 8abe880a46e..682d777acf6 100644 --- a/docs/source/howto/how_to_change_a_url.rst +++ b/docs/source/howto/how_to_change_a_url.rst @@ -2,31 +2,24 @@ How to change an existing URL pattern ===================================== -Oscar has many views and associated URLs. Often you want to customised these +Oscar has many views and associated URLs. Often you want to customise these URLs for your domain. For instance, you might want to use American spellings rather than British (``catalog`` instead of ``catalogue``). -URLs in Oscar -------------- +This How-to describes how to do just that. +It builds upon the steps described in :doc:`/topics/customisation`. Please +read it first and ensure that you've: -Oscar's views and URLs use a tree of 'app' instances, each of which subclass -``oscar.core.application.Application`` and provide ``urls`` property. Oscar has -a root app instance in ``oscar/app.py`` which can be imported into your -``urls.py``:: +* Created a Python module with the the same label +* Added it as Django app to ``INSTALLED_APPS`` +* Created a custom ``app.py`` - # urls.py - from oscar.app import application - - urlpatterns = patterns('', - ... # Your other URLs - (r'', include(application.urls)), - ) - -Customising ------------ +Example +------- -In order to customise Oscar's URLs, you need to use a custom app instance in -your root ``urls.py`` instead of Oscar's default instance. Hence, to use +In order to customise Oscar's URLs, you need to use a custom app instance +instead of Oscar's default instance. ``/catalogue`` is wired up in the root +application, so we need to replace that. Hence, to use ``catalog`` instead of ``catalogue``, create a subclass of Oscar's main ``Application`` class and override the ``get_urls`` method:: @@ -57,3 +50,16 @@ Now modify your root ``urls.py`` to use your new application instance:: ) All URLs containing ``catalogue`` previously are now displayed as ``catalog``. + +If you wanted to change URLs of a sub-app (e.g. ``/catalogue/category/``), +you only need to replace the ``catalogue`` app. There's no need to change +your ``urls.py`` or touch the root ``application`` instance. ``application`` +instances dynamically load their sub-apps, so it just pick up your replacement:: + + # oscar/app.py + class Shop(Application): + name = None + + catalogue_app = get_class('catalogue.app', 'application') + customer_app = get_class('customer.app', 'application') + ... diff --git a/docs/source/howto/how_to_configure_shipping.rst b/docs/source/howto/how_to_configure_shipping.rst index cf431fa2d1a..c87212d3f3f 100644 --- a/docs/source/howto/how_to_configure_shipping.rst +++ b/docs/source/howto/how_to_configure_shipping.rst @@ -2,61 +2,127 @@ How to configure shipping ========================= -Checkout flow -------------- +Configuring shipping is not trivial. It generally requires creating a +'shipping' app within your project where you can define your own shipping +methods as well as a 'repository' class which determines when methods are +available. -Oscar's checkout is set-up to follow the following steps: +This recipe explains in more detail how Oscar models shipping as well as the +steps involved in configuring shipping for your project. -1. Manage basket -2. Enter/choose shipping address -3. Choose shipping method -4. Choose payment method -5. Preview -6. Enter payment details and submit +How Oscar handles shipping charges +---------------------------------- -Determining the methods available to a user -------------------------------------------- +Oscar uses a "repository" class to manage shipping charges. The class is used +in two ways: -At the shipping method stage, we use a repository object to look up the -shipping methods available to the user. These methods typically depend on: +* _It provides a list of shipping methods available to the user._ This is used to + generate the content for the shipping methods page of checkout, where the user + can choose a shipping method. The methods available generally depend on the + user, the basket and the shipping address. + +* _It allows a shipping method to be retrieved based on a identifying code._ When + a user selects a shipping method during checkout, it is persisted in the + session using a code. This code is used to retrieve the chosen shipping + method when it is required. + +The default shipping repository `can be seen here`_. It defaults to only +providing one shipping method, which has no charge. + +.. note:: + + Oscar's checkout process includes a page for choosing your shipping method. + If there is only one method available for your basket then it will be chosen + automatically and the user immediately redirected to the next step. + +Custom shipping charges +----------------------- + +In order to control shipping logic for your project, you need to define your own +repository class (see :doc:`how_to_override_a_core_class`). It normally makes +sense to subclass the core ``Repository`` class and override the +``get_shipping_methods`` and ``find_by_code`` methods. + +Here's a very simple example where all shipping costs are a fixed price, +irrespective of basket and shipping address:: + + # myproject/shipping/repository.py + + from decimal import Decimal as D + from oscar.apps.shipping import repository, methods as core_methods + + class Repository(repository.Repository): + methods = [core_methods.FixedPrice(D('9.99'))] + + def get_shipping_methods(self, user, basket, shipping_addr=None, **kwargs): + return self.prime_methods(basket, self.methods) + + def find_by_code(self, code, basket): + for method in self.methods: + if code == method.code: + return self.prime_method(basket, method) + +Note that both these methods must return 'primed' method instances, which means +the basket instance has been injected into the method. This allows the method +instance to return the shipping charge directly without requiring the basket to +be passed again (which is useful in templates). + +As you can see the ``get_shipping_methods`` can depend on several things: * the user in question (e.g., staff get cheaper shipping rates) * the basket (e.g., shipping is charged based on the weight of the basket) * the shipping address (e.g., overseas shipping is more expensive) -The default repository is ``oscar.apps.shipping.repository.Repository``, which -has a method ``get_shipping_methods`` for returning all available methods. By -default, the returned method will be ``oscar.apps.shipping.methods.Free``. +Here's a more involved example repository that has two fixed price charges:: -Set a custom shipping methods ------------------------------ + # myproject/shipping/repository.py -To apply your domain logic for shipping, you will need to override -the default repository class (see :doc:`how_to_override_a_core_class`) and alter -the implementation of the ``get_shipping_methods`` method. This method -should return a list of "shipping method" classes already instantiated -and holding a reference to the basket instance. + from decimal import Decimal as D + from oscar.apps.shipping import repository, methods as core_methods -Building a custom shipping method ---------------------------------- + # We create subclasses so we can give them different codes and names + class Standard(core_methods.FixedPrice): + code = 'standard' + name = _("Standard shipping") + + class Express(core_methods.FixedPrice): + code = 'express' + name = _("Express shipping") + + class Repository(repository.Repository): + methods = [Standard(D('10.00')), Express(D('20.00'))] + + def get_shipping_methods(self, user, basket, shipping_addr=None, **kwargs): + return self.prime_methods(basket, self.methods) + + def find_by_code(self, code, basket): + for method in self.methods: + if code == method.code: + return self.prime_method(basket, method) -A shipping method class must define two methods:: +.. _`can be seen here`: https://github.com/tangentlabs/django-oscar/blob/master/oscar/apps/shipping/repository.py - method.basket_charge_incl_tax() - method.basket_charge_excl_tax() +Shipping methods +---------------- -whose responsibilities should be clear. You can subclass ``oscar.apps.shipping.base.ShippingMethod`` -to provide the basic functionality. +The repository class is responsible for return shipping method instances. Oscar +defines several of these but it is easy to write your own, their interface is +simple. -Built-in shipping methods -------------------------- +The base shipping method class ``oscar.apps.shipping.base.ShippingMethod`` (that +all shipping methods should subclass has API: -Oscar comes with several built-in shipping methods which are easy to use -with a custom repository. +.. autoclass:: oscar.apps.shipping.base.ShippingMethod + :members: + +Core shipping methods +~~~~~~~~~~~~~~~~~~~~~ + +The shipping methods that ship with Oscar are: * ``oscar.apps.shipping.methods.Free``. No shipping charges. -* ``oscar.apps.shipping.methods.WeightBased``. This is a model-driven method +* ``oscar.apps.shipping.models.WeightBased``. This is a model-driven method that uses two models: ``WeightBased`` and ``WeightBand`` to provide charges for different weight bands. By default, the method will calculate the weight of a product by looking for a 'weight' attribute although this can be @@ -65,5 +131,24 @@ with a custom repository. * ``oscar.apps.shipping.methods.FixedPrice``. This simply charges a fixed price for shipping, irrespective of the basket contents. -* ``oscar.apps.shipping.methods.OrderAndItemCharges``. This is a model which +* ``oscar.apps.shipping.models.OrderAndItemCharges``. This is a model which specifies a per-order and a per-item level charge. + +To apply your domain logic for shipping, you will need to override +the default repository class (see :doc:`how_to_override_a_core_class`) and alter +the implementation of the ``get_shipping_methods`` method. This method +should return a list of "shipping method" classes already instantiated +and holding a reference to the basket instance. + +Building a custom shipping method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +At a minimum, a custom shipping method class should define a ``code`` and +``name`` attribute to distinguish it from other methods. It is also normal to +override the ``basket_charge_incl_tax`` and ``basket_charge_excl_tax`` methods +to implement your custom shipping charge logic. + +.. tip:: + + Most of the shipping logic should live in the repository class, the method + instance is only responsble for returning the charge for a given basket. diff --git a/docs/source/howto/how_to_configure_stock_messaging.rst b/docs/source/howto/how_to_configure_stock_messaging.rst index e9e7bcd738f..c4614bf1d72 100644 --- a/docs/source/howto/how_to_configure_stock_messaging.rst +++ b/docs/source/howto/how_to_configure_stock_messaging.rst @@ -2,42 +2,10 @@ How to configure stock messaging ================================ -Stock messaging is controlled on a per-partner basis. A product's stockrecord -has the following methods for messaging: +Stock messaging is controlled by an +:ref:`availability policy ` +which is loaded by the :ref:`strategy class `. -.. autoclass:: oscar.apps.partner.abstract_models.AbstractStockRecord - :members: availability, availability_code - -Both these methods delegate to a "partner wrapper" instance. These are defined -in the ``OSCAR_PARTNER_WRAPPERS`` setting which is a dict mapping from partner -code to a class path, for instance:: - - # settings.py - OSCAR_PARTNER_WRAPPERS = { - 'partner-a': 'myproject.wrappers.PartnerAWrapper', - } - -The default wrapper is :class:`oscar.apps.partner.wrappers.DefaultWrapper`, -which provides methods of the same name. - -.. autoclass:: oscar.apps.partner.wrappers.DefaultWrapper - :members: availability, availability_code - -Custom wrappers should subclass this class and override the appropriate methods. -Here's an example wrapper that provides custom availability messaging:: - - # myproject/wrappers.py - from oscar.apps.partner import wrappers - - - class PartnerAWrapper(wrappers.DefaultWrapper): - - def availability(self, stockrecord): - if stockrecord.net_stock_level > 0: - return "Available to buy now!" - return "Sorry, not available" - - def availability_code(self, stockrecord): - if stockrecord.net_stock_level > 0: - return "icon_tick" - return "icon_cross" +To set custom availability messaging, use your own strategy class to return the +appropriate availability policy. It's possible to return different availability +policies depending on the user, request and product in question. diff --git a/docs/source/howto/how_to_configure_the_dashboard_navigation.rst b/docs/source/howto/how_to_configure_the_dashboard_navigation.rst index 816162b230a..13d46864be5 100644 --- a/docs/source/howto/how_to_configure_the_dashboard_navigation.rst +++ b/docs/source/howto/how_to_configure_the_dashboard_navigation.rst @@ -45,7 +45,7 @@ Although you have your menu in the dashboard now, it doesn't look as nice as the other menu items that have icons displayed next to them. So you probably want to add an icon to your heading. -Oscar uses `Font Awesome`_ for its icons which makes it very simple to +Oscar uses `Font Awesome`_ for its icons which makes it very simple to add an icon to your dashboard menu. All you need to do is find the right icon for your menu item. Check out `the icon list`_ to find one. diff --git a/docs/source/howto/how_to_create_a_custom_benefit.rst b/docs/source/howto/how_to_create_a_custom_benefit.rst index a4c3fbf5193..e69bf32b109 100644 --- a/docs/source/howto/how_to_create_a_custom_benefit.rst +++ b/docs/source/howto/how_to_create_a_custom_benefit.rst @@ -23,7 +23,7 @@ Custom benefits A custom benefit can be used by creating a benefit class and registering it so it is available to be used. -A benefit class must by a proxy class and have the following methods:: +A benefit class must be a proxy class and have the following methods:: from oscar.apps.offer import models @@ -41,7 +41,7 @@ A benefit class must by a proxy class and have the following methods:: This is used in the dashboard when selecting benefits for offers. """ - def apply(self, basket, condition, offer=None): + def apply(self, basket, condition, offer): """ Apply the benefit to the passed basket and mark the appropriate items as consumed. @@ -88,7 +88,7 @@ Here's an example of a post-order action benefit:: description = "Changes customer's name" - def apply(self, basket, condition, offer=None): + def apply(self, basket, condition, offer): # We need to mark all items from the matched condition as 'consumed' # so that they are unavailable to be used with other offers. condition.consume_items(basket, ()) @@ -101,4 +101,3 @@ Here's an example of a post-order action benefit:: basket.owner.save() return "Your name has been changed to Barry!" return "We were unable to change your name as you are not signed in" - diff --git a/docs/source/howto/how_to_create_a_custom_condition.rst b/docs/source/howto/how_to_create_a_custom_condition.rst index 4210e201e50..c8f814f7ad2 100644 --- a/docs/source/howto/how_to_create_a_custom_condition.rst +++ b/docs/source/howto/how_to_create_a_custom_condition.rst @@ -16,8 +16,8 @@ Condition class. At a minimum, a custom condition must: * have a ``name`` attribute -* have an ``is_satisified`` method that takes a basket instance and returns a - boolean +* have an ``is_satisfied`` method that takes a basket instance and an offer + instance and returns a boolean It can also implement: @@ -32,7 +32,7 @@ It can also implement: * a ``is_partially_satisfied`` method that tests to see if the customer's basket partially satisfies the condition (ie when you might want to show them an - upsel message) + upsell message) Silly example:: @@ -44,7 +44,7 @@ Silly example:: class Meta: proxy = True - def is_satisfied(self, basket): + def is_satisfied(self, offer, basket): if not basket.owner: return False return basket.owner.first_name.lower() == 'barry' @@ -56,9 +56,9 @@ To make this condition available to be used in offers, do the following:: from oscar.apps.offer.custom import create_condition - create_range(BasketOwnerCalledBarry) + create_condition(BasketOwnerCalledBarry) -Now you should see this range in the dashboard when creating/updating an offer. +Now you should see this condition in the dashboard when creating/updating an offer. Deploying custom conditions --------------------------- @@ -66,4 +66,4 @@ Deploying custom conditions To avoid manual steps in each of your test/stage/production environments, use South's `data migrations`_ to create conditions. -.. _`data migrations`: http://south.readthedocs.org/en/latest/tutorial/part3.html#data-migrations \ No newline at end of file +.. _`data migrations`: http://south.readthedocs.org/en/latest/tutorial/part3.html#data-migrations diff --git a/docs/source/howto/how_to_customise_a_view.rst b/docs/source/howto/how_to_customise_a_view.rst new file mode 100644 index 00000000000..883853d70d3 --- /dev/null +++ b/docs/source/howto/how_to_customise_a_view.rst @@ -0,0 +1,49 @@ +======================= +How to customise a view +======================= + +Oscar has many views. This How-to describes how to customise one of them for +your project. It builds upon the steps described in +:doc:`/topics/customisation`. Please read it first and ensure that you've: + +* Created a Python module with the the same label +* Added it as Django app to ``INSTALLED_APPS`` +* Use custom ``app.py`` + +Example +------- + +Create a new homepage view class in ``myproject.promotions.views`` - you can subclass +Oscar's view if you like:: + + from oscar.apps.promotions.views import HomeView as CoreHomeView + + class HomeView(CoreHomeView): + template_name = 'promotions/new-homeview.html' + +In this example, we set a new template location but it's possible to customise the view +in any imaginable way. + +If you want to change the template, create the alternative template +``new-homeview.html``. This could either be +in a project-level ``templates`` folder that is added to your ``TEMPLATE_DIRS`` +settings, or a app-level ``templates`` folder within your 'promotions' app. For +now, put something simple in there, such as:: + + + +

You have successfully overridden the homepage template.

+ + + +Now you can hook it up in your local ``app.py``:: + + # myproject/promotions/app.py + from oscar.apps.promotions.app import PromotionsApplication as CorePromotionsApplication + + from myproject.promotions.views import HomeView + + class PromotionsApplication(CorePromotionsApplication): + home_view = HomeView + + application = PromotionsApplication() diff --git a/docs/source/howto/how_to_customise_an_app.rst b/docs/source/howto/how_to_customise_an_app.rst deleted file mode 100644 index 3e011f4bbbf..00000000000 --- a/docs/source/howto/how_to_customise_an_app.rst +++ /dev/null @@ -1,156 +0,0 @@ -======================= -How to customise an app -======================= - -A core part of how Oscar can be customised is to create a local version of one -of Oscar's apps so that it can be modified and extended. Creating a local -version of an app allows customisation of any of the classes within the -corresponding app in oscar. - -The way this is done involves a few steps, which are detailed here. - -Method -====== - -1. Create an app within your project with the same "app label" as an app in oscar. Eg, - to create a local version of ``oscar.apps.order``, create something like ``myproject.order``. - -2. Ensure the ``models.py`` in your local app imports all the models from Oscar's version:: - - # models.py - from oscar.apps.order.models import * - -3. Replace Oscar's version of the app with your new version in ``INSTALLED_APPS``. - - -Worked example -============== - -Suppose you want to modify the homepage view class, which by default is defined in -``oscar.apps.promotions.views.HomeView``. This view is bound to a URL within the -``PromotionsApplication`` class in ``oscar.apps.promotions.app`` - hence we need to -override this application class to be able to use a different view. - -By default, your base ``urls.py`` should include Oscar's URLs as so:: - - # urls.py - from oscar.app import application - - urlpatterns = patterns('', - ... - (r'', include(application.urls)), - ) - -To get control over the mapping between URLs and views, you need to use a local -``application`` instance, that (optionally) subclasses Oscar's. Hence, create -``myproject/app.py`` with contents:: - - # myproject/app.py - from oscar.app import Shop - - class BaseApplication(Shop): - pass - - application = BaseApplication() - -No customisation for now, that will come later, but you now have control over which -URLs and view functions are used. - -Now hook this up in your ``urls.py``:: - - # urls.py - from myproject.app import application - - urlpatterns = patterns('', - ... - (r'', include(application.urls)), - ) - -The next step is to create a local app with the same name as the app you want to override:: - - mkdir myproject/promotions - touch myproject/promotions/__init__.py - touch myproject/promotions/models.py - -The ``models.py`` file should import all models from the oscar app being overridden:: - - # myproject/promotions/models.py - from oscar.apps.promotions.models import * - -Now replace ``oscar.apps.promotions`` with ``myproject.promotions`` in the -``INSTALLED_APPS`` setting in your settings file. To do it, you will need to let -Oscar know that you're replacing the corresponding core app with yours. You can -do that by supplying an extra argument to ``get_core_apps`` function:: - - # myproject/settings.py - - from oscar import get_core_apps - # ... - INSTALLED_APPS = [ - ] + get_core_apps(['myproject.promotions']) - -The ``get_core_apps`` function will replace ``oscar.apps.promotions`` with -``myproject.promotions``. - -Now create a new homepage view class in ``myproject.promotions.views`` - you can subclass -Oscar's view if you like:: - - from oscar.apps.promotions.views import HomeView as CoreHomeView - - class HomeView(CoreHomeView): - template_name = 'promotions/new-homeview.html' - -In this example, we set a new template location but it's possible to customise the view -in any imaginable way. - -Now create the alternative template ``new-homeview.html``. This could either be -in a project-level ``templates`` folder that is added to your ``TEMPLATE_DIRS`` -settings, or a app-level ``templates`` folder within your 'promotions' app. For -now, put something simple in there, such as:: - - - -

You have successfully overridden the homepage template.

- - - -Next, create a new ``app.py`` for your local promotions app which maps your new ``HomeView`` -class to the homepage URL:: - - # myproject/promotions/app.py - from oscar.apps.promotions.app import PromotionsApplication as CorePromotionsApplication - - from myproject.promotions.views import HomeView - - class PromotionsApplication(CorePromotionsApplication): - home_view = HomeView - - application = PromotionsApplication() - -Finally, hook up the new view to the homepage URL:: - - # myproject/app.py - from oscar.app import Shop - - from myproject.promotions.app import application as promotions_app - - class BaseApplication(Shop): - promotions_app = promotions_app - - application = BaseApplication() - -Quite long-winded, but once this step is done, you have lots of freedom to customise -the app in question. - -Django admin ------------- - -One pain point with replacing one of Oscar's apps with a local one in -``INSTALLED_APPS`` is that admin integration is lost from the original -app. If you'd like to use the Django admin functionality you just need -to run the register code in the replaced app's ``admin.py``:: - - # myprojects/promotions/admin.py - import oscar.apps.promotions.admin - -This isn't great but we haven't found a better way as of yet. diff --git a/docs/source/howto/how_to_customise_models.rst b/docs/source/howto/how_to_customise_models.rst index 8b354b207fd..cf608ec414d 100644 --- a/docs/source/howto/how_to_customise_models.rst +++ b/docs/source/howto/how_to_customise_models.rst @@ -2,16 +2,19 @@ How to customise models ======================= -You must first create a local version of the app that you wish to customise. This -involves creating a local app with the same name and importing the equivalent models -from Oscar into it. +This How-to describes how to replace Oscar models with your own. This allows you +to add fields and custom methods. It builds upon the steps described in +:doc:`/topics/customisation`. Please read it first and ensure that you've: + +* Created a Python module with the the same app label +* Added it as Django app to ``INSTALLED_APPS`` Example ------- -Suppose you want to add a video_url field to the core product model. This means that -you want your application to use a subclass of ``oscar.apps.catalogue.models.Product`` which -has an additional field. +Suppose you want to add a ``video_url`` field to the core product model. This means +that you want your application to use a subclass of +:class:`oscar.apps.catalogue.abstract_models.AbstractProduct` which has an additional field. The first step is to create a local version of the "catalogue" app. At a minimum, this involves creating ``catalogue/models.py`` within your project and changing ``INSTALLED_APPS`` @@ -32,33 +35,36 @@ Next, you can modify the ``Product`` model through subclassing:: Make sure to import the remaining Oscar models at the bottom of your file. -The last thing you need to do now is make Django update the database schema and -create a new column in the product table. We recommend to use South migrations -for this (internally Oscar already uses it) so all you need to do is create a -new schema migration. Depending on your setup you should follow one of these -two options: - -1. You **have not** run ``./manage.py migrate`` before +.. tip:: - You can simply generate a new initial migration using:: + Using ``from ... import *`` is strange isn't it? Yes it is, but it needs to + be done at the bottom of the module due to the way Django registers models. + The order that model classes are imported makes a difference, with only the + first one for a given class name being registered. - ./manage.py schemamigration catalogue --initial +The last thing you need to do now is make Django update the database schema and +create a new column in the product table. We recommend using South migrations +for this (internally Oscar already does this) so all you need to do is create a +new schema migration. -2. You **have** run ``./manage.py migrate`` before +It is possible to simply create a new catalogue migration (using ``./manage.py +schemamigration catalogue --auto``) but this isn't recommended as any +dependencies between migrations will need to be applied manually (by adding a +``depends_on`` attribute to the migration class). - You have to copy the ``migrations`` directory from ``oscar/apps/catalogue`` - (the same as the ``models.py`` you just copied) and put it into your - ``catalogue`` app. - Now create a new (additional) schemamigration using the ``schemamigration`` - management command and follow the instructions:: +The recommended way to handle migrations is to copy the ``migrations`` directory +from ``oscar/apps/catalogue`` into your new ``catalogue`` app. Then you can +create a new (additional) schemamigration using the ``schemamigration`` +management command:: ./manage.py schemamigration catalogue --auto +which will pick up any customisations to the product model. + To apply the migration you just created, all you have to do is run ``./manage.py migrate catalogue`` and the new column is added to the product table in the database. - Customising Products -------------------- diff --git a/docs/source/howto/how_to_customise_oscar_communications.rst b/docs/source/howto/how_to_customise_oscar_communications.rst new file mode 100644 index 00000000000..bea0c591d4a --- /dev/null +++ b/docs/source/howto/how_to_customise_oscar_communications.rst @@ -0,0 +1,85 @@ +Customising Oscar's communications +================================== + +Oscar provides the ability to customise the emails sent out to customers. + +There are two main ways this can be achieved, either in code (via template +files) or in the database (via Dashboard > Content > Email templates). + +Communications API +------------------ + +First, it's important to understand a little about how the Communications API +works. + +Oscar has a model called a ``CommunicationEventType``. When preparing an email +to send out to a customer, the client code will do something like this:: + + commtype_code = 'SOME_EVENT' + context = {'customer': customer, 'something_else': 'Some more context.'} + + try: + event_type = CommunicationEventType.objects.get(code=commtype_code) + except CommunicationEventType.DoesNotExist: + messages = CommunicationEventType.objects.get_and_render(commtype_code, ctx) + else: + messages = event_type.get_messages(ctx) + +What's happening here is: + +- The code defines an arbitrary communication type code to be treated as the + reference for this particular type of communication. For example, the + communication type code used when sending an order email confirmation is + ``'ORDER_PLACED'``. +- The database is checked for a ``CommunicationEventType`` with this + communication type code. If it does, it renders the messages using that model + instance, passing in some context. +- Otherwise, it uses the ``get_and_render()`` method to render the messages, + which uses templates instead. + +So, your first step when customising the emails sent out is to work out what +communication type code is being used to send out the email. The easiest way to +work this out is usually to look through the email templates in +``templates/oscar/customer/emails``: if the email template is called, say, +``commtype_order_placed_body.html``, then the code will be ``'ORDER_PLACED'``. +See 'Customising through code' below. + +Customising through code +------------------------ + +Customising emails through code uses Django's standard template inheritance. + +The first step is to locate the template for the particular email, which is +usually in templates/oscar/customer/emails. Then, in a template directory that +takes precedence over the oscar templates directory, copy the file and customise +it. For example, to override the +``templates/oscar/customer/emails/commtype_order_placed_body.html`` template, +create the file ``customer/emails/commtype_order_placed_body.html`` in your +template directory. + +Note that usually emails have three template files associated with them: the +email subject line (``commtype_CODE_subject.txt``), the html version +(``commtype_CODE_body.html``) and the text version (commtype_CODE_body.txt). +Usually you will want to make sure you override BOTH the html and the text +version. + +Customising through code will not work if there is a template defined in the +database instead (see below). + + +Customising through the database +-------------------------------- + +Oscar provides a dashboard interface to allow admins to customise the emails. + +To enable this for a particular communication event type, log in to the admin +site and create a new ``CommunicationEventType``. The code you use is the +important thing: it needs to match the communication event code used when +rendering the messages. For example, to override the order confirmation email, +you need to create a ``CommunicationEventType`` with a code ``'ORDER_PLACED'``. + +Once you have created the ``CommunicationEventType``, you can edit it using the +(much better) dashboard interface at Dashboard > Content > Email templates. + +If you have an email template defined in the database it will override any +template files. \ No newline at end of file diff --git a/docs/source/howto/how_to_customise_templates.rst b/docs/source/howto/how_to_customise_templates.rst index 1da423e4319..b5df0fa47f4 100644 --- a/docs/source/howto/how_to_customise_templates.rst +++ b/docs/source/howto/how_to_customise_templates.rst @@ -23,6 +23,7 @@ as so:: TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', + 'django.template.loaders.eggs.Loader', ) import os @@ -88,12 +89,12 @@ Apart from overriding ``catalogue/partials/product.html`` to change the looks for all products, you can also override it for individual products by placing templates in ``catalogue/partials/product/upc-%s.html`` or ``catalogue/partials/product/class-%s.html``, where ``%s`` is the product's UPC -or product class in lower case, respectively. +or class's slug, respectively. Example: Changing the analytics package --------------------------------------- -Support you want to use an alternative analytics package to Google analytics. +Suppose you want to use an alternative analytics package to Google analytics. We can achieve this by overriding templates where the analytics urchin is loaded and called. @@ -102,7 +103,7 @@ Analytics partial. We want to replace this with our own code. To do this, create a new ``base.html`` in your project that subclasses the original:: # yourproject/templates/base.html - {% extends oscar/base.html %} + {% extends 'oscar/base.html' %} {% block tracking %} + {% endblock %} diff --git a/oscar/templates/oscar/basket/basket.html b/oscar/templates/oscar/basket/basket.html index 2b5427e73e9..44400803a47 100644 --- a/oscar/templates/oscar/basket/basket.html +++ b/oscar/templates/oscar/basket/basket.html @@ -1,17 +1,27 @@ {% extends "layout.html" %} -{% load currency_filters %} {% load thumbnail %} {% load i18n %} +{% load url from future %} {% block title %} -{% trans "Basket" %} | {{ block.super }} + {% trans "Basket" %} | {{ block.super }} +{% endblock %} + +{% block breadcrumbs %} + {% endblock %} {# Hide mini-basket so we don't have two baskets to keep in sync #} {% block mini_basket %}{% endblock %} {% block headertext %} -{% trans "Basket" %} + {% trans "Basket" %} {% endblock %} {% block content %} @@ -20,9 +30,10 @@ {% endblock content %} {% block onbodyload %} -{# We pass in the URL to send AJAX requests to #} -var options = { - 'basketURL': '{% url basket:summary %}' -}; -oscar.basket.init(options); + {{ block.super }} + {# We pass in the URL to send AJAX requests to #} + var options = { + 'basketURL': '{% url 'basket:summary' %}' + }; + oscar.basket.init(options); {% endblock %} diff --git a/oscar/templates/oscar/basket/messages/new_total.html b/oscar/templates/oscar/basket/messages/new_total.html index 1fd10a57cab..32a6b809d37 100644 --- a/oscar/templates/oscar/basket/messages/new_total.html +++ b/oscar/templates/oscar/basket/messages/new_total.html @@ -1,3 +1,4 @@ +{% load url from future %} {% load i18n %} {% load currency_filters %} @@ -5,15 +6,17 @@ {% if basket.is_empty %} {% trans "Your basket is now empty" %} {% else %} - {% blocktrans with total=basket.total_incl_tax|currency %} - Basket total now {{ total }}. - {% endblocktrans %} + {% if basket.is_tax_known %} + {% trans "Basket total now" %} {{ basket.total_incl_tax|currency:basket.currency }} + {% else %} + {% trans "Basket total now" %} {{ basket.total_excl_tax|currency:basket.currency }} + {% endif %} {% endif %}

-{% if show_buttons %} +{% if include_buttons %}

- {% trans "View basket" %} - {% trans "Checkout now" %} + {% trans "View basket" %} + {% trans "Checkout now" %}

{% endif %} diff --git a/oscar/templates/oscar/basket/partials/basket_content.html b/oscar/templates/oscar/basket/partials/basket_content.html index 43adc4169a5..2fb592d5443 100644 --- a/oscar/templates/oscar/basket/partials/basket_content.html +++ b/oscar/templates/oscar/basket/partials/basket_content.html @@ -1,172 +1,211 @@ +{% load url from future %} {% load i18n %} {% load thumbnail %} {% load currency_filters %} +{% load stockrecord_tags %} {% if basket_warnings %}
{% trans "Important messages about items in your basket" %}
- {% for warning in basket_warnings %} -
{{ warning }}
- {% endfor %} + {% for warning in basket_warnings %} +
{{ warning }}
+ {% endfor %} {% endif %} {% if upsell_messages %} -
-

{% trans "You could be missing out on offers!" %}

- {% for upsell in upsell_messages %} - {% blocktrans with message=upsell.message url=upsell.offer.get_absolute_url offer_name=upsell.offer.name %} -
{{ message }} to qualify for the {{ offer_name }} special offer
- {% endblocktrans %} - {% endfor %} -
+
+

{% trans "You could be missing out on offers!" %}

+ {% for upsell in upsell_messages %} + {% blocktrans with message=upsell.message url=upsell.offer.get_absolute_url offer_name=upsell.offer.name %} +
{{ message }} to qualify for the {{ offer_name }} special offer
+ {% endblocktrans %} + {% endfor %} +
{% endif %} {% if not basket.is_empty %} {% block basket_form_headers %} -
-
-

{% trans "Items to buy now" %}

-

{% trans "Quantity" %}

-

{% trans "Price" %}

+
+
+

{% trans "Items to buy now" %}

+

{% trans "Quantity" %}

+

{% trans "Price" %}

+

{% trans "Total" %}

+
-
{% endblock %} {% block basket_form_main %} -
- {% csrf_token %} - {{ formset.management_form }} + + {% csrf_token %} + {{ formset.management_form }} - {% for form in formset %} -
-
-
- {{ form.id }} -
- {% with image=form.instance.product.primary_image %} - {% thumbnail image.original "200x200" upscale=False as thumb %} - {{ product.get_title }} - {% endthumbnail %} - {% endwith %} -
-

{{ form.instance.description }}

-

{{ form.instance.product.stockrecord.availability }}

-
-
-
- {{ form.quantity }} - - {% trans "Remove" %} - {% if request.user.is_authenticated %} - | {% trans "Save for later" %} - {% endif %} -
- {{ form.save_for_later }} - {{ form.DELETE }} + {% for form in formset %} + {% with line=form.instance product=form.instance.product %} + {% session_strategy request product as session %} +
+
+
+ {{ form.id }} + {% with image=product.primary_image %} + {% thumbnail image.original "100x100" upscale=False as thumb %} + + {{ product.get_title }} + + {% endthumbnail %} + {% endwith %} +
+
+

{{ line.description }}

+

{{ session.availability.message }}

+
+
+
+ {{ form.quantity }} + + {% trans "Remove" %} + {% if request.user.is_authenticated %} + | {% trans "Save for later" %} + {% endif %} +
+ {{ form.save_for_later }} + {{ form.DELETE }} +
+ {% for field_errors in form.errors.values %} + {% for error in field_errors %} + {{ error }} + {% endfor %} + {% endfor %} +
+
+
+

+ {% if session.price.is_tax_known %} + {{ session.price.incl_tax|currency:session.price.currency }} + {% else %} + {{ session.price.excl_tax|currency:session.price.currency }} + {% endif %} +

+
+
+

+ {% if basket.is_tax_known %} + {{ line.line_price_incl_tax|currency:basket.currency }} + {% else %} + {{ line.line_price_excl_tax|currency:basket.currency }} + {% endif %} +

+
- {% for field_errors in form.errors.values %} - {% for error in field_errors %} - {{ error }} - {% endfor %} - {% endfor %}
-
-
-

{{ form.instance.unit_price_incl_tax|currency }}

-

-
-
- {% endfor %} - + {% endwith %} + {% endfor %} + {% endblock %}
{% block vouchers %} -
-
-

{% trans "Voucher/promo code" %}

-
- +
+
+

{% trans "Voucher/promo code" %}

+
+ -
+
{% endblock vouchers %} {% block baskettotals %} -
-
-

{% trans "Totals" %}

+
+
+

{% trans "Totals" %}

+
+ {% include 'basket/partials/basket_totals.html' with editable=1 %}
- {% include 'basket/partials/basket_totals.html' with editable=1 %} -
{% endblock baskettotals %}
{% block formactions %} -
- {% trans "Proceed to checkout" %} + {% endblock formactions %} {% else %} {% block emptybasket %} -

{% trans "Your basket is empty." %}

-

{% trans "Continue shopping" %}

+

+ {% trans "Your basket is empty." %} + {% trans "Continue shopping" %} +

{% endblock %} {% endif %} -{% if request.user.is_authenticated %} -
-
-

{% trans "To buy later" %}

-
- - {% if not saved_formset %} -

{% trans "Your saved basket is empty." %}

- - {% else %} -
-

{% trans "Items" %}

-

{% trans "Price" %}

-
 
-
-
- {% csrf_token %} - {{ saved_formset.management_form }} - {% for form in saved_formset %} -
-
- {{ form.id }} -
- {% with image=form.instance.product.primary_image %} - {% thumbnail image.original "200x200" upscale=False as thumb %} - {{ product.get_title }} - {% endthumbnail %} - {% endwith %} -
-

{{ form.instance.description }}

-

{{ form.instance.product.stockrecord.availability }}

- {% trans "Remove" %} -
- {{ form.move_to_basket }} - {{ form.DELETE }} -
-
-

{{ form.instance.unit_price_incl_tax|currency }}

- +{% block savedbasket %} + {% if request.user.is_authenticated and saved_formset %} +
+
+

{% trans "To buy later" %}

+
+
+

{% trans "Items" %}

+

{% trans "Price" %}

+
 
+
+ + {% csrf_token %} + {{ saved_formset.management_form }} + {% for form in saved_formset %} + {% session_strategy request form.instance.product as session %} +
+
+
+ {{ form.id }} + {% with image=form.instance.product.primary_image %} + {% thumbnail image.original "100x100" upscale=False as thumb %} + + {{ form.instance.product.get_title }} + + {% endthumbnail %} + {% endwith %} +
+
+

{{ form.instance.description }}

+

{{ session.availability.message }}

+ {% trans "Remove" %} +
+ {{ form.move_to_basket }} + {{ form.DELETE }} +
+
+ {% session_strategy request form.instance.product as saved %} +
+

+ {% if saved.price.is_tax_known %} + {{ saved.price.incl_tax|currency:saved.price.currency }} + {% else %} + {{ saved.price.excl_tax|currency:saved.price.currency }} + {% endif %} +

+
+ +
+
+ {% endfor %} +
- {% endfor %} - {% endif %} -
-{% endif %} +{% endblock %} diff --git a/oscar/templates/oscar/basket/partials/basket_quick.html b/oscar/templates/oscar/basket/partials/basket_quick.html index 1cc3713cbf6..ec6278b89f0 100644 --- a/oscar/templates/oscar/basket/partials/basket_quick.html +++ b/oscar/templates/oscar/basket/partials/basket_quick.html @@ -1,37 +1,45 @@ -{% load currency_filters %} +{% load url from future %} {% load history_tags %} {% load basket_tags %} +{% load currency_filters %} {% load thumbnail %} {% load i18n %} +{% load staticfiles %} -
    -{% if request.basket.num_lines %} - {% for line in request.basket.all_lines %} -
  • -
    -
    -
    - {% with image=line.product.primary_image %} - {% thumbnail image.original "100x100" upscale=False as thumb %} - {{ product.get_title }} - {% endthumbnail %} - {% endwith %} +
      + {% if request.basket.num_lines %} + {% for line in request.basket.all_lines %} +
    • +
      +
      +
      + {% with image=line.product.primary_image %} + {% thumbnail image.original "100x100" upscale=False as thumb %} + {{ line.product.get_title }} + {% endthumbnail %} + {% endwith %} +
      +
      + +
      {% trans "Qty" %} {{ line.quantity }}
      +
      {{ line.unit_price_excl_tax|currency:request.basket.currency }}
      -
    - -
    {% trans "Qty" %} {{ line.quantity }}
    -
    {{ line.unit_price_excl_tax|currency }}
    -
    -
  • - {% endfor %} -
  • -

    {% trans "Total Excl Tax:" %} {{ request.basket.total_excl_tax|currency }} {% trans "Total:" %} {{ basket.total_incl_tax|currency }}

    -

    {% trans "View Basket" %}

    -

    {% trans "Checkout" %}

    -
  • -{% else %} -
  • {% trans "Your basket is empty." %}

  • -{% endif %} + + {% endfor %} +
  • +

    + {% if request.basket.is_tax_known %} + {% trans "Total:" %} {{ request.basket.total_incl_tax|currency:request.basket.currency }} + {% else %} + {% trans "Total:" %} {{ request.basket.total_excl_tax|currency:request.basket.currency }} + {% endif %} +

    +

    {% trans "View basket" %}

    +

    {% trans "Checkout" %}

    +
  • + {% else %} +
  • {% trans "Your basket is empty." %}

  • + {% endif %}
diff --git a/oscar/templates/oscar/basket/partials/basket_totals.html b/oscar/templates/oscar/basket/partials/basket_totals.html index 5eb2a1d1dd8..68c4d9f87bf 100644 --- a/oscar/templates/oscar/basket/partials/basket_totals.html +++ b/oscar/templates/oscar/basket/partials/basket_totals.html @@ -1,137 +1,213 @@ + +{% load url from future %} {% load i18n %} {% load currency_filters %}
- - - - {% with offer_discounts=basket.offer_discounts voucher_discounts=basket.grouped_voucher_discounts %} - {% if offer_discounts or voucher_discounts %} - {# Basket total will be discounted so we show a before and after version #} - - + + {% with offer_discounts=basket.offer_discounts voucher_discounts=basket.grouped_voucher_discounts %} + {% if offer_discounts or voucher_discounts %} + {# Basket total will be discounted so we show a before and after version #} + + + + - {% if offer_discounts %} - {% for discount in offer_discounts %} - - - - - {% endfor %} - {% endif %} + {% if offer_discounts %} + {% for discount in offer_discounts %} + + + + + {% endfor %} + {% endif %} - {% if voucher_discounts %} - - - - {% for discount in voucher_discounts %} - - - - - {% endfor %} - {% endif %} + {% if voucher_discounts %} + + + + {% for discount in voucher_discounts %} + + + + + {% endfor %} + {% endif %} + + + + + + {% else %} + {# No discounts to basket #} + + + + + {% endif %} + {% endwith %} - - - - {% else %} - {# No discounts to basket #} - - - + + - {% endif %} - {% endwith %} - - - - + {% block shipping_totals %} + + + + {% if not shipping_method.is_discounted %} + + + + + {% else %} + {# As shipping is discounted, we break it down into its original charge and a discount #} + + + + + + + + + {# This section needs adjustmen to when taxes are shown separately #} + + {% with discount=shipping_method.get_discount %} + + + {% endwith %} + + + + + + {% endif %} + {% endblock %} - - + + + + + + + + + + + + + + {% endif %} - - - {% if not shipping_method.is_discounted %} - - - - - {% else %} - {# As shipping is discounted, we break it down into its original charge and a discount #} - - - - - - - - - - {% with discount=shipping_method.get_discount %} - - - {% endwith %} - - - - - - {% endif %} + {% endblock %} + + {% block post_order_action_totals %} + {% if basket.post_order_actions %} + + + + + + + + + {% for discount in basket.post_order_actions %} + + + + {% endfor %} + {% endif %} + {% endblock %} - {% if basket.post_order_actions %} - - - - {% for discount in basket.post_order_actions %} - - + - {% endfor %} - {% endif %} - - - - - - - - - + {% if not order_total.is_tax_known %} + + + + {% endif %}

{% trans "Basket" %}

{% trans "Basket total (before discounts)" %}{{ basket.total_incl_tax_excl_discounts|currency }}

{% trans "Basket" %}

{% trans "Basket total (before discounts)" %} + {% if basket.is_tax_known and not show_tax_separately %} + {{ basket.total_incl_tax_excl_discounts|currency:basket.currency }} + {% else %} + {{ basket.total_excl_tax_excl_discounts|currency:basket.currency }} + {% endif %} +
{% trans "Discount" %} {{ discount.name }} - {% if discount.description %} -
{{ discount.description }} - {% endif %} -
-{{ discount.discount|currency }}
{% trans "Discount" %} {{ discount.name }} + {% if discount.description %} +
{{ discount.description }} + {% endif %} +
-{{ discount.discount|currency:basket.currency }}

{% trans "Vouchers" %}

- {{ discount.voucher.name }} ({{ discount.voucher.code }}) - {% if editable %} -
- {% csrf_token %} - -
- {% endif %} -
-{{ discount.discount|currency }}

{% trans "Vouchers" %}

+ {{ discount.voucher.name }} ({{ discount.voucher.code }}) + {% if editable %} +
+ {% csrf_token %} + +
+ {% endif %} +
-{{ discount.discount|currency:basket.currency }}
{% trans "Basket total (after discounts)" %} + {% if not show_tax_separately and basket.is_tax_known %} + {{ basket.total_incl_tax|currency:basket.currency }} + {% else %} + {{ basket.total_excl_tax|currency:basket.currency }} + {% endif %} +
{% trans "Basket total" %} + {% if not show_tax_separately and basket.is_tax_known %} + {{ basket.total_incl_tax|currency:basket.currency }} + {% else %} + {{ basket.total_excl_tax|currency:basket.currency }} + {% endif %} +
{% trans "Basket total (after discounts)" %}{{ basket.total_incl_tax|currency }}
{% trans "Basket total" %}{{ basket.total_incl_tax|currency }} 
 
+

{% trans "Shipping" %}

+ {% if shipping_methods|length > 1 and editable %} + {% trans "Alternative shipping methods can be chosen during checkout" %} + {% endif %} +
+ {{ shipping_method.name }} + + {% if not show_tax_separately and shipping_method.is_tax_known %} + {{ shipping_method.charge_incl_tax|currency:basket.currency }} + {% else %} + {{ shipping_method.charge_excl_tax|currency:basket.currency }} + {% endif %} +
{% trans "Shipping method" %}{{ shipping_method.name }}
{% trans "Shipping total (before discounts)" %} + {% if not show_tax_separately %} + {{ shipping_method.charge_incl_tax_before_discount|currency:basket.currency }} + {% else %} + {{ shipping_method.charge_excl_tax_before_discount|currency:basket.currency }} + {% endif %} +
{% trans "Discount" %} {{ discount.name }}-{{ discount.discount|currency:basket.currency }}
+ {% trans "Shipping total (after discounts)" %} + {{ shipping_method.charge_incl_tax|currency:basket.currency }}
-

{% trans "Shipping" %}

- {% if shipping_methods|length > 1 and editable %} - {% trans "Alternative shipping methods can be chosen during checkout" %} + {% block tax_totals %} + {% if show_tax_separately %} +
 
+

{% trans "Tax" %}

+
{% trans "Basket" %} + {{ basket.total_tax|currency:basket.currency }} +
{% trans "Shipping" %} + {{ shipping_method.tax|currency:basket.currency }} +
- {% trans "Shipping" %} - {{ shipping_method.name }} - {{ shipping_method.basket_charge_incl_tax|currency }}
{% trans "Shipping method" %}{{ shipping_method.name }}
{% trans "Shipping total (before discounts)" %}{{ shipping_method.basket_charge_incl_tax_before_discount|currency }}
{% trans "Discount" %} {{ discount.name }}-{{ discount.discount|currency }}
- {% trans "Shipping total (after discounts)" %} - {{ shipping_method.basket_charge_incl_tax|currency }}
 

{% trans "Post order actions" %}

{% trans "These will be applied once your order is placed." %}
+ {{ discount.name }}
+

{{ discount.description }}

+
 

{% trans "Post order actions" %}

{% trans "These will be applied once your order is placed." %}
- {{ discount.name }}
-

{{ discount.description }}

+

{% trans "Order total" %}

+

+ {% if order_total.is_tax_known %} + {{ order_total.incl_tax|currency:basket.currency }} + {% else %} + {{ order_total.excl_tax|currency:basket.currency }} + {% endif %} +

 

{% trans "Order total" %}

{{ order_total_incl_tax|currency }}

{% trans "Taxes will be added during checkout." %}
diff --git a/oscar/templates/oscar/catalogue/browse.html b/oscar/templates/oscar/catalogue/browse.html index 6e36d6bb1c9..a761bca1b66 100644 --- a/oscar/templates/oscar/catalogue/browse.html +++ b/oscar/templates/oscar/catalogue/browse.html @@ -1,55 +1,63 @@ {% extends "layout_2_col.html" %} -{% load currency_filters %} + +{% load url from future %} {% load basket_tags %} {% load promotion_tags %} +{% load category_tags %} {% load product_tags %} {% load i18n %} - {% block title %} -{{ summary }} | {{ block.super }} + {{ summary }} | {{ block.super }} +{% endblock %} + +{% block description %} + {{ category.description }} {% endblock %} -{% block headertext %}{{ summary }}{% endblock %} +{% block headertext %}{{ summary }}{% endblock %} {% block breadcrumbs %} - {% endblock breadcrumbs %} {% block column_left %} -
- {% if categories %} -
+ {% endfor %} + +
+ {% endif %} {% endblock %} {% block content %} @@ -62,17 +70,17 @@
{% endif %} - {% if products.count %} -
-
-
    - {% for product in products %} -
  1. {% render_product product %}
  2. - {% endfor %} -
- {% include "partials/pagination.html" %} -
-
+ {% if products %} +
+
+
    + {% for product in products %} +
  1. {% render_product product %}
  2. + {% endfor %} +
+ {% include "partials/pagination.html" %} +
+
{% else %}

{% trans "No products found." %}

{% endif %} diff --git a/oscar/templates/oscar/catalogue/detail.html b/oscar/templates/oscar/catalogue/detail.html index 374767379e4..7dcb82484b5 100644 --- a/oscar/templates/oscar/catalogue/detail.html +++ b/oscar/templates/oscar/catalogue/detail.html @@ -1,34 +1,46 @@ {% extends "layout.html" %} -{% load currency_filters %} + +{% load url from future %} {% load history_tags %} +{% load currency_filters %} {% load reviews_tags %} {% load staticfiles %} {% load product_tags %} +{% load display_tags %} {% load i18n %} - -{% block extrastyles %} - - -{% endblock %} +{% load stockrecord_tags %} {% block title %} {{ product.title }} | {{ block.super }} {% endblock %} +{% block description %} + {{ product.description }} +{% endblock %} + {% block breadcrumbs %} {% endblock %} @@ -37,19 +49,8 @@ {% endblock header %} {% block content %} -
- {% comment %} - This is a but clunky here. Better to have some kind of JS-driven dashboard menu that - pops out when clicked. A bit like the Django-Debug-Toolbar button - {% endcomment %} - {% if user.is_authenticated and user.is_staff %} - - {% endif %} +
@@ -61,73 +62,97 @@ {% block product_main %}
+ {% comment %} + This is a but clunky here. Better to have some kind of JS-driven dashboard menu that + pops out when clicked. A bit like the Django-Debug-Toolbar button + {% endcomment %} + {% if user.is_authenticated and user.is_staff %} + {% trans "Edit this product" %} + {% endif %}

{{ product.get_title }}

- + {% block product_stock_record %} - {% include "catalogue/partials/stock_record.html" %} + {% include "catalogue/partials/stock_record.html" with verbose=1 %} {% endblock %} - - {% if product.rating != None %} -

- - {% blocktrans count reviews|length as num_reviews %} - 1 custom review - {% plural %} - {{ num_reviews }} customer reviews - {% endblocktrans %} - -   - {% trans "Write a review" %} -

- {% else %} -

- {% trans "Write a review" %} -

- {% endif %} - + + {% iffeature "reviews" %} + {% if product.rating != None %} +

+ + + + + + + + {% blocktrans count reviews|length as num_reviews %} + 1 custom review + {% plural %} + {{ num_reviews }} customer reviews + {% endblocktrans %} + +   + {% else %} +

+ {% endif %} + {% if not product|has_review_by:user %} + + {% trans "Write a review" %} + + {% endif %} +

+ {% endiffeature %} +
{% block product_basket_form %} - {% include "catalogue/partials/add_to_basket_form.html" %} + {% include "catalogue/partials/add_to_basket_form.html" %} {% endblock %}
{% endblock %}
- - {% block product_description %} + + {% block product_description %} {% if product.description %}

{% trans "Product Description" %}

{{ product.description|safe }}

{% endif %} - {% endblock %} - - {% block product_info %} -
-

{% trans "Product Information" %}

-
+ {% endblock %} + + {% block product_info %} +
+

{% trans "Product Information" %}

+
- {% if product.upc %} + {% if product.upc %} - - - {% endif %} - - - - {% if product.stockrecord %} - - - - - + + {% endif %} - - + + + {% session_strategy request product as session %} + {% if session.price.exists %} + + + + {% if session.price.is_tax_known %} + + + + + + + {% endif %} + + + + {% endif %} {% for av in product.attribute_values.all %} @@ -135,90 +160,78 @@

{% trans "Product Information" %}

{% endfor %} - - - - + {% iffeature "reviews" %} + + + + + {% endiffeature %}
{% trans "UPC" %}{{ product.upc }}
{% trans "Product class" %}{{ product.product_class.name }}
{% trans "Price (excl. tax)" %}{{ product.stockrecord.price_incl_tax|currency }}
{% trans "Price (incl. tax)" %}{{ product.stockrecord.price_excl_tax|currency }}{% trans "UPC" %}{{ product.upc }}
{% trans "Availability" %}{{ product.stockrecord.availability }}{% trans "Product class" %}{{ product.get_product_class.name }}
{% trans "Price (excl. tax)" %}{{ session.price.excl_tax|currency:session.price.currency }}
{% trans "Price (incl. tax)" %}{{ session.price.incl_tax|currency:session.price.currency }}
{% trans "Tax" %}{{ session.price.tax|currency:session.price.currency }}
{% trans "Availability" %}{{ session.availability.message }}
{{ av.value }}
{% trans "Num reviews" %}{{ reviews.count|default:0 }}
{% trans "Num reviews" %}{{ reviews.count|default:0 }}
- {% endblock %} - - {% block product_review %} -
-
- {% if reviews %} - {% trans "See all reviews" %} - {% endif %} -

{% trans "Customer Reviews" %}

-
- - {% if not reviews %} -

- {% trans "This product does not have any reviews yet" %} - - {% trans "be the first to write one" %}. -

- {% else %} -
- {% for review in reviews|slice:":3" %} - {% include 'catalogue/partials/review.html' %} - {% endfor %} + {% endblock %} + + {% iffeature "reviews" %} + {% block product_review %} +
+
+ {% if reviews|length > 3 %} + {% trans "See all reviews" %} + {% endif %} +

{% trans "Customer Reviews" %}

- {% endif %} -
- {% endblock product_review %} - -
- {% if product.related_products.count %} -
-
-

{% trans "Related items" %}

-
- -
- {% endif %} - {% if product.recommended_products.count %} -
-
-

{% trans "Recommended items" %}

-
- + {% endfor %} + + {% endif %} +
+ {% endblock product_review %} + {% endiffeature %} + + {% if product.related_products.count %} +
+

{% trans "Related items" %}

- {% endif %} +
    + {% for product in product.related_products.all|slice:":6" %} +
  • + {% render_product product %} +
  • + {% endfor %} +
+ {% endif %} - {% recently_viewed_products %} + {% if product.recommended_products.count %} +
+

{% trans "Recommended items" %}

+
+
    + {% for product in product.recommended_products.all|slice:":6" %} +
  • + {% render_product product %} +
  • + {% endfor %} +
+ {% endif %} + + {% recently_viewed_products %} -
{% endblock content %} {% block extrascripts %} -{{ block.super }} - - - - + {{ block.super }} {% endblock %} diff --git a/oscar/templates/oscar/catalogue/partials/add_to_basket_form.html b/oscar/templates/oscar/catalogue/partials/add_to_basket_form.html index db243262a1b..0439bd255f4 100644 --- a/oscar/templates/oscar/catalogue/partials/add_to_basket_form.html +++ b/oscar/templates/oscar/catalogue/partials/add_to_basket_form.html @@ -1,24 +1,28 @@ +{% load url from future %} {% load basket_tags %} {% load i18n %} +{% load stockrecord_tags %} -{% if product.is_available_to_buy %} - {% basket_form request.basket product as basket_form %} -
+{% session_strategy request product as session %} + +{% if session.availability.is_available_to_buy %} + {% basket_form request product as basket_form %} + {% csrf_token %} {% include "partials/form_fields.html" with form=basket_form %} -
- -
+
+ {% include "catalogue/partials/add_to_wishlist.html" %} {% else %} - {% if has_active_alert %} -

{% trans "You have an active stock alert for this product." %}

- {% else %} -
- {% csrf_token %} -

{% trans "You can get an email alert when this product is back in stock." %}

- {% include "partials/form_fields.html" with form=alert_form %} - -
- {% endif %} + {% if has_active_alert %} +

{% trans "You have an active stock alert for this product." %}

+ {% else %} +
+ {% csrf_token %} +

{% trans "You can get an email alert when this product is back in stock." %}

+ {% include "partials/form_fields.html" with form=alert_form %} + +
+ {% include "catalogue/partials/add_to_wishlist.html" %} + {% endif %} {% endif %} diff --git a/oscar/templates/oscar/catalogue/partials/add_to_basket_form_compact.html b/oscar/templates/oscar/catalogue/partials/add_to_basket_form_compact.html index 59799ed3692..c767be8906d 100644 --- a/oscar/templates/oscar/catalogue/partials/add_to_basket_form_compact.html +++ b/oscar/templates/oscar/catalogue/partials/add_to_basket_form_compact.html @@ -1,11 +1,17 @@ +{% load url from future %} {% load basket_tags %} {% load i18n %} +{% load stockrecord_tags %} -{% basket_form request.basket product as basket_form single %} -
- {% csrf_token %} - {{ basket_form.as_p }} - {% if product.is_available_to_buy %} - - {% endif %} -
+{% session_strategy request product as session %} + +{% if session.availability.is_available_to_buy %} + {% basket_form request product as basket_form single %} +
+ {% csrf_token %} + {{ basket_form.as_p }} + +
+{% else %} + {% trans "Add to basket" %} +{% endif %} diff --git a/oscar/templates/oscar/catalogue/partials/add_to_wishlist.html b/oscar/templates/oscar/catalogue/partials/add_to_wishlist.html new file mode 100644 index 00000000000..e6c9bc19350 --- /dev/null +++ b/oscar/templates/oscar/catalogue/partials/add_to_wishlist.html @@ -0,0 +1,48 @@ +{% load i18n %} +{% load url from future %} +{% load wishlist_tags %} + +{% if user.is_authenticated %} + {% with wishlists=user.wishlists.all %} + {# Select wishlists that contains product #} + {% wishlists_containing_product wishlists product as product_wishlists %} + {% if wishlists|length > 0 %} + +
+ {% trans "Add to wish list" %} + +
+ {% else %} + {# 1 or no existing wishlists - show a simple button #} +
+ {% csrf_token %} + +
+ {% endif %} + + {% for wishlist in product_wishlists %} +
+ {% csrf_token %} + {% blocktrans with name=wishlist.name url=wishlist.get_absolute_url %} + Product is in '{{ name }}' wishlist. + {% endblocktrans %} + +
+ {% endfor %} + {% endwith %} +{% else %} +
+{% endif %} diff --git a/oscar/templates/oscar/catalogue/partials/gallery.html b/oscar/templates/oscar/catalogue/partials/gallery.html index 2824b506a63..5d0a72b3b96 100644 --- a/oscar/templates/oscar/catalogue/partials/gallery.html +++ b/oscar/templates/oscar/catalogue/partials/gallery.html @@ -1,63 +1,55 @@ {% load thumbnail %} {% load i18n %} - - +{% load staticfiles %} {% with all_images=product.images.all %} {# use length rather then count as the images get queried anyways #} {% if all_images|length > 1 %} -