Browse Source

Merge branch 'master' into issue/1093/search

Conflicts:
	oscar/apps/dashboard/catalogue/views.py
	oscar/apps/search/facets.py
	tests/functional/catalogue/catalogue_tests.py
master
Maik Hoepfel 10 years ago
parent
commit
c46ff56049
100 changed files with 2644 additions and 1587 deletions
  1. 7
    5
      .travis.yml
  2. 9
    1
      Makefile
  3. 3
    13
      README.rst
  4. 35
    0
      create_migration.sh
  5. 1
    1
      docs/source/howto/how_to_configure_shipping.rst
  6. 28
    10
      docs/source/internals/getting_started.rst
  7. 0
    27
      docs/source/ref/settings.rst
  8. 160
    10
      docs/source/releases/v0.8.rst
  9. 4
    0
      docs/source/topics/class_loading_explained.rst
  10. 20
    0
      docs/source/topics/fork_app.rst
  11. 1
    1
      docs/source/topics/prices_and_availability.rst
  12. 1
    0
      oscar/__init__.py
  13. 12
    22
      oscar/app.py
  14. 1
    0
      oscar/apps/address/__init__.py
  15. 22
    4
      oscar/apps/address/abstract_models.py
  16. 8
    0
      oscar/apps/address/config.py
  17. 66
    126
      oscar/apps/address/migrations/0001_initial.py
  18. 7
    4
      oscar/apps/address/models.py
  19. 131
    0
      oscar/apps/address/south_migrations/0001_initial.py
  20. 0
    0
      oscar/apps/address/south_migrations/0002_auto__chg_field_useraddress_postcode.py
  21. 0
    0
      oscar/apps/address/south_migrations/0003_auto__add_field_country_display_order.py
  22. 0
    0
      oscar/apps/address/south_migrations/0004_convert_is_highlighted.py
  23. 0
    0
      oscar/apps/address/south_migrations/0005_auto__del_field_country_is_highlighted.py
  24. 0
    0
      oscar/apps/address/south_migrations/0006_auto__add_unique_useraddress_hash_user.py
  25. 0
    0
      oscar/apps/address/south_migrations/0007_auto__chg_field_useraddress_postcode.py
  26. 0
    0
      oscar/apps/address/south_migrations/0008_auto__chg_field_useraddress_phone_number.py
  27. 0
    0
      oscar/apps/address/south_migrations/0009_no_null_in_charfields.py
  28. 0
    0
      oscar/apps/address/south_migrations/0010_auto__chg_field_useraddress_first_name__chg_field_useraddress_title__c.py
  29. 0
    0
      oscar/apps/address/south_migrations/0011_auto__chg_field_useraddress_search_text.py
  30. 0
    0
      oscar/apps/address/south_migrations/0012_auto__del_index_country_iso_3166_1_a3__chg_field_country_iso_3166_1_nu.py
  31. 0
    0
      oscar/apps/address/south_migrations/__init__.py
  32. 1
    0
      oscar/apps/analytics/__init__.py
  33. 11
    3
      oscar/apps/analytics/abstract_models.py
  34. 11
    0
      oscar/apps/analytics/config.py
  35. 30
    254
      oscar/apps/analytics/migrations/0001_initial.py
  36. 74
    0
      oscar/apps/analytics/migrations/0002_auto_20140805_1510.py
  37. 17
    9
      oscar/apps/analytics/models.py
  38. 254
    0
      oscar/apps/analytics/south_migrations/0001_initial.py
  39. 0
    0
      oscar/apps/analytics/south_migrations/__init__.py
  40. 1
    0
      oscar/apps/basket/__init__.py
  41. 8
    2
      oscar/apps/basket/abstract_models.py
  42. 8
    0
      oscar/apps/basket/config.py
  43. 5
    5
      oscar/apps/basket/forms.py
  44. 16
    7
      oscar/apps/basket/middleware.py
  45. 32
    302
      oscar/apps/basket/migrations/0001_initial.py
  46. 63
    0
      oscar/apps/basket/migrations/0002_auto_20140805_1510.py
  47. 10
    6
      oscar/apps/basket/models.py
  48. 302
    0
      oscar/apps/basket/south_migrations/0001_initial.py
  49. 0
    0
      oscar/apps/basket/south_migrations/0002_auto__add_field_line_price_incl_tax.py
  50. 0
    0
      oscar/apps/basket/south_migrations/0003_auto__add_field_line_price_excl_tax.py
  51. 0
    0
      oscar/apps/basket/south_migrations/0004_auto__add_field_line_stockrecord.py
  52. 0
    0
      oscar/apps/basket/south_migrations/0005_auto__add_field_line_price_currency.py
  53. 0
    0
      oscar/apps/basket/south_migrations/0006_auto__chg_field_line_stockrecord.py
  54. 0
    0
      oscar/apps/basket/south_migrations/__init__.py
  55. 34
    38
      oscar/apps/basket/views.py
  56. 1
    0
      oscar/apps/catalogue/__init__.py
  57. 172
    54
      oscar/apps/catalogue/abstract_models.py
  58. 11
    0
      oscar/apps/catalogue/config.py
  59. 0
    4
      oscar/apps/catalogue/managers.py
  60. 291
    403
      oscar/apps/catalogue/migrations/0001_initial.py
  61. 38
    23
      oscar/apps/catalogue/models.py
  62. 9
    4
      oscar/apps/catalogue/reviews/abstract_models.py
  63. 5
    2
      oscar/apps/catalogue/reviews/app.py
  64. 62
    231
      oscar/apps/catalogue/reviews/migrations/0001_initial.py
  65. 7
    4
      oscar/apps/catalogue/reviews/models.py
  66. 237
    0
      oscar/apps/catalogue/reviews/south_migrations/0001_initial.py
  67. 0
    0
      oscar/apps/catalogue/reviews/south_migrations/0002_no_null_in_charfields.py
  68. 0
    0
      oscar/apps/catalogue/reviews/south_migrations/0003_auto__chg_field_productreview_name__chg_field_productreview_homepage__.py
  69. 0
    0
      oscar/apps/catalogue/reviews/south_migrations/__init__.py
  70. 4
    5
      oscar/apps/catalogue/reviews/views.py
  71. 403
    0
      oscar/apps/catalogue/south_migrations/0001_initial.py
  72. 0
    0
      oscar/apps/catalogue/south_migrations/0002_auto__add_field_product_status__add_field_category_description__add_fi.py
  73. 0
    0
      oscar/apps/catalogue/south_migrations/0003_auto__add_unique_product_upc__chg_field_productcontributor_role.py
  74. 0
    0
      oscar/apps/catalogue/south_migrations/0004_auto__chg_field_productattributevalue_value_boolean.py
  75. 0
    0
      oscar/apps/catalogue/south_migrations/0005_auto__chg_field_productattributevalue_value_boolean__add_field_product.py
  76. 0
    0
      oscar/apps/catalogue/south_migrations/0006_auto__add_field_product_is_discountable.py
  77. 0
    0
      oscar/apps/catalogue/south_migrations/0007_auto__add_field_productclass_requires_shipping__add_field_productclass.py
  78. 0
    0
      oscar/apps/catalogue/south_migrations/0008_auto__add_unique_option_code.py
  79. 0
    0
      oscar/apps/catalogue/south_migrations/0009_auto__add_field_product_rating.py
  80. 0
    0
      oscar/apps/catalogue/south_migrations/0010_call_update_product_ratings.py
  81. 0
    0
      oscar/apps/catalogue/south_migrations/0011_auto__chg_field_productimage_original__chg_field_category_image.py
  82. 0
    0
      oscar/apps/catalogue/south_migrations/0012_auto__chg_field_productattributevalue_value_boolean.py
  83. 0
    0
      oscar/apps/catalogue/south_migrations/0013_add_file_attributes.py
  84. 0
    0
      oscar/apps/catalogue/south_migrations/0014_auto__del_field_productcategory_is_canonical.py
  85. 0
    0
      oscar/apps/catalogue/south_migrations/0015_auto__chg_field_product_upc.py
  86. 0
    0
      oscar/apps/catalogue/south_migrations/0016_customer.py
  87. 0
    0
      oscar/apps/catalogue/south_migrations/0017_auto__del_contributor__del_productcontributor__del_contributorrole__de.py
  88. 0
    0
      oscar/apps/catalogue/south_migrations/0018_auto__chg_field_product_product_class.py
  89. 0
    0
      oscar/apps/catalogue/south_migrations/0019_no_null_in_charfields.py
  90. 0
    0
      oscar/apps/catalogue/south_migrations/0020_auto__chg_field_productattributevalue_value_text__chg_field_productatt.py
  91. 0
    0
      oscar/apps/catalogue/south_migrations/0021_auto__add_unique_productattributevalue_attribute_product__add_unique_p.py
  92. 0
    0
      oscar/apps/catalogue/south_migrations/0022_auto__del_field_product_score.py
  93. 0
    0
      oscar/apps/catalogue/south_migrations/0023_auto.py
  94. 0
    0
      oscar/apps/catalogue/south_migrations/0024_auto__del_attributeentity__del_attributeentitytype__del_field_producta.py
  95. 0
    0
      oscar/apps/catalogue/south_migrations/0025_auto__add_field_product_structure.py
  96. 0
    0
      oscar/apps/catalogue/south_migrations/0026_determine_product_structure.py
  97. 0
    0
      oscar/apps/catalogue/south_migrations/__init__.py
  98. 10
    7
      oscar/apps/catalogue/views.py
  99. 1
    0
      oscar/apps/checkout/__init__.py
  100. 0
    0
      oscar/apps/checkout/config.py

+ 7
- 5
.travis.yml View File

10
     # $TRANSIFEX_PASSWORD for oscar_bot (used in transifex.sh)
10
     # $TRANSIFEX_PASSWORD for oscar_bot (used in transifex.sh)
11
     secure: FuIlzEsGJiAwhaIRBmRNsq9eXmuzs25fX6BChknW4lDyVAySWMp0+Zps9Bd0JgfFYUG3Ip+OTmksYIoTUsG25ZJS9cq1IFt3QKUAN70YCI/4ZBLeIdICPEyxq+Km179+NeEXmBUug17RLMLxh3MWfO+RKUHK9yHIPNNpq0dNyoo=
11
     secure: FuIlzEsGJiAwhaIRBmRNsq9eXmuzs25fX6BChknW4lDyVAySWMp0+Zps9Bd0JgfFYUG3Ip+OTmksYIoTUsG25ZJS9cq1IFt3QKUAN70YCI/4ZBLeIdICPEyxq+Km179+NeEXmBUug17RLMLxh3MWfO+RKUHK9yHIPNNpq0dNyoo=
12
   matrix:
12
   matrix:
13
-    - DJANGO_VERSION=1.5.8
14
-    - DJANGO_VERSION=1.6.5
13
+    - DJANGO=Django==1.6.5
14
+    - DJANGO=https://www.djangoproject.com/download/1.7c2/tarball/#egg=Django
15
 
15
 
16
 matrix:
16
 matrix:
17
   allow_failures:
17
   allow_failures:
18
-    - python: '3.3'
19
-    - python: '3.4'
18
+    - env: "DJANGO=Django==1.6.5"
19
+      python: '3.3'
20
+    - env: "DJANGO=Django==1.6.5"
21
+      python: '3.4'
20
 
22
 
21
 install:
23
 install:
22
-  - easy_install Django==$DJANGO_VERSION
24
+  - easy_install $DJANGO
23
 
25
 
24
 before_script:
26
 before_script:
25
     # Create testing databases for running migrations against
27
     # Create testing databases for running migrations against

+ 9
- 1
Makefile View File

12
 	-rm -rf sites/sandbox/public/static
12
 	-rm -rf sites/sandbox/public/static
13
 	-rm -f sites/sandbox/db.sqlite
13
 	-rm -f sites/sandbox/db.sqlite
14
 	# Create database
14
 	# Create database
15
+	# 'syncdb' is identical to migrate in Django 1.7+; but calling it twice should have no effect
15
 	sites/sandbox/manage.py syncdb --noinput
16
 	sites/sandbox/manage.py syncdb --noinput
16
 	sites/sandbox/manage.py migrate
17
 	sites/sandbox/manage.py migrate
17
 	# Import some fixtures. Order is important as JSON fixtures include primary keys
18
 	# Import some fixtures. Order is important as JSON fixtures include primary keys
18
-	sites/sandbox/manage.py loaddata sites/sandbox/fixtures/variants.json
19
+	sites/sandbox/manage.py loaddata sites/sandbox/fixtures/child_products.json
19
 	sites/sandbox/manage.py oscar_import_catalogue sites/sandbox/fixtures/*.csv
20
 	sites/sandbox/manage.py oscar_import_catalogue sites/sandbox/fixtures/*.csv
20
 	sites/sandbox/manage.py oscar_import_catalogue_images sites/sandbox/fixtures/images.tar.gz
21
 	sites/sandbox/manage.py oscar_import_catalogue_images sites/sandbox/fixtures/images.tar.gz
21
 	sites/sandbox/manage.py oscar_populate_countries
22
 	sites/sandbox/manage.py oscar_populate_countries
121
 preflight: lint
122
 preflight: lint
122
     # Bare minimum of tests to run before pushing to master
123
     # Bare minimum of tests to run before pushing to master
123
 	./runtests.py
124
 	./runtests.py
125
+
126
+todo:
127
+	# Look for areas of the code that need updating when some event has taken place (like 
128
+	# Oscar dropping support for a Django version)
129
+	-grep -rnH TODO *.txt
130
+	-grep -rnH TODO oscar/apps/
131
+	-grep -rnH "django.VERSION" oscar/apps

+ 3
- 13
README.rst View File

175
 * django-oscar-unicredit_ - Integration with the Unicredit payment gateway
175
 * django-oscar-unicredit_ - Integration with the Unicredit payment gateway
176
 * django-oscar-payments_ - Pluggable payments for Oscar
176
 * django-oscar-payments_ - Pluggable payments for Oscar
177
 * django-oscar-recurly_ - Integration with the Recurly payment gateway
177
 * django-oscar-recurly_ - Integration with the Recurly payment gateway
178
+* django-oscar-adyen_ - Integration with the Adyen payment gateway
178
 * oscar-sagepay_ - Payment integration with Sage Pay
179
 * oscar-sagepay_ - Payment integration with Sage Pay
179
 * django-oscar-erp_
180
 * django-oscar-erp_
180
 
181
 
184
 .. _django-oscar-erp: https://bitbucket.org/zikzakmedia/django-oscar_erp
185
 .. _django-oscar-erp: https://bitbucket.org/zikzakmedia/django-oscar_erp
185
 .. _django-oscar-payments: https://github.com/Lacrymology/django-oscar-payments
186
 .. _django-oscar-payments: https://github.com/Lacrymology/django-oscar-payments
186
 .. _django-oscar-recurly: https://github.com/mynameisgabe/django-oscar-recurly
187
 .. _django-oscar-recurly: https://github.com/mynameisgabe/django-oscar-recurly
188
+.. _django-oscar-adyen: https://github.com/oscaro/django-oscar-adyen
187
 .. _oscar-sagepay: https://github.com/udox/oscar-sagepay
189
 .. _oscar-sagepay: https://github.com/udox/oscar-sagepay
188
 
190
 
189
 License
191
 License
236
 Non-Tangent:
238
 Non-Tangent:
237
 
239
 
238
 * Dolbeau - http://www.dolbeau.ca
240
 * Dolbeau - http://www.dolbeau.ca
239
-* Sobusa - http://www.sobusa.fr
240
-* Laivee - http://laivee.pl
241
-* Colinss - http://colinss.com
242
 * Audio App - https://audioapp.pl
241
 * Audio App - https://audioapp.pl
243
 * Anything Gift - http://www.anythinggift.co.uk
242
 * Anything Gift - http://www.anythinggift.co.uk
244
 * FP Sport - http://www.fpsport.it
243
 * FP Sport - http://www.fpsport.it
250
 .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/dolbeau.thumb.png
249
 .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/dolbeau.thumb.png
251
     :target: http://www.dolbeau.ca
250
     :target: http://www.dolbeau.ca
252
 
251
 
253
-.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/sobusa.thumb.png
254
-    :target: http://www.sobusa.fr
255
-
256
-.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/laivee.thumb.png
257
-    :target: http://www.laivee.pl
258
-
259
-.. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/colinss.thumb.png
260
-    :target: http://www.colinss.com
261
-
262
 .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/audioapp.thumb.png
252
 .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/audioapp.thumb.png
263
     :target: https://audioapp.pl
253
     :target: https://audioapp.pl
264
 
254
 
266
     :target: http://www.anythinggift.co.uk
256
     :target: http://www.anythinggift.co.uk
267
 
257
 
268
 .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/fpsport.thumb.png
258
 .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/fpsport.thumb.png
269
-    :target: https://www.fpsport.it
259
+    :target: http://www.fpsport.it
270
 
260
 
271
 .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/garmsby.thumb.png
261
 .. image:: https://github.com/tangentlabs/django-oscar/raw/master/docs/images/screenshots/garmsby.thumb.png
272
     :target: https://garmsby.co.uk
262
     :target: https://garmsby.co.uk

+ 35
- 0
create_migration.sh View File

1
+#!/usr/bin/env bash
2
+#
3
+# Rather verbose and destructive script to create both South migrations and the
4
+# new native migrations (which we need as we support both Django 1.6 and 1.7).
5
+# This will install and uninstall Django versions in your virtualenv, only work
6
+# with the default SQLite database, destroy that database repeatedly
7
+
8
+# Grab current version of Django from virtualenv
9
+DJANGO_VERSION=$(pip freeze | awk 'BEGIN {FS="=="} /Django/ {print $2}')
10
+SOUTH_VERSION=$(pip freeze | awk 'BEGIN {FS="=="} /South/ {print $2}')
11
+
12
+APPS=( analytics checkout address shipping catalogue reviews partner basket payment \
13
+       offer order customer promotions search voucher wishlists )
14
+
15
+echo "Uninstalling Django(==$DJANGO_VERSION) and South(==$SOUTH_VERSION)"
16
+pip uninstall Django South -y
17
+
18
+echo "Generating Django-native (>=1.7) migrations"
19
+pip install https://www.djangoproject.com/download/1.7c2/tarball/
20
+rm -f sites/sandbox/db.sqlite
21
+sites/sandbox/manage.py migrate
22
+sites/sandbox/manage.py makemigrations ${APPS[@]}
23
+
24
+echo "Generating Django 1.6 migrations"
25
+pip install "Django==1.6.5" "South==1.0"
26
+rm -f sites/sandbox/db.sqlite
27
+sites/sandbox/manage.py syncdb --noinput
28
+sites/sandbox/manage.py migrate
29
+for APP in "${APPS[@]}"
30
+do
31
+    sites/sandbox/manage.py schemamigration $APP --auto
32
+done
33
+
34
+echo "Restoring Django(==$DJANGO_VERSION) and South(==$SOUTH_VERSION)"
35
+pip install "Django==$DJANGO_VERSION" "South==$SOUTH_VERSION"

+ 1
- 1
docs/source/howto/how_to_configure_shipping.rst View File

85
                self, basket, user=None, shipping_addr=None, 
85
                self, basket, user=None, shipping_addr=None, 
86
                request=None, **kwargs):
86
                request=None, **kwargs):
87
            methods = (methods.Standard())
87
            methods = (methods.Standard())
88
-           if shipping_addr and shipping.addr.country.code == 'GB':
88
+           if shipping_addr and shipping_addr.country.code == 'GB':
89
                # Express is only available in the UK
89
                # Express is only available in the UK
90
                methods = (methods.Standard(), methods.Express())
90
                methods = (methods.Standard(), methods.Express())
91
            return methods
91
            return methods

+ 28
- 10
docs/source/internals/getting_started.rst View File

84
         'django.contrib.staticfiles',
84
         'django.contrib.staticfiles',
85
         'django.contrib.flatpages',
85
         'django.contrib.flatpages',
86
         ...
86
         ...
87
-        'south',
88
         'compressor',
87
         'compressor',
89
     ] + get_core_apps()
88
     ] + get_core_apps()
90
 
89
 
107
 
106
 
108
 Next, add ``oscar.apps.basket.middleware.BasketMiddleware`` and
107
 Next, add ``oscar.apps.basket.middleware.BasketMiddleware`` and
109
 ``django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`` to
108
 ``django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`` to
110
-your ``MIDDLEWARE_CLASSES`` setting. If you're running on Django 1.5, it is
111
-also recommended to use ``django.middleware.transaction.TransactionMiddleware``:
109
+your ``MIDDLEWARE_CLASSES`` setting.
112
 
110
 
113
 .. code-block:: django
111
 .. code-block:: django
114
 
112
 
115
     MIDDLEWARE_CLASSES = (
113
     MIDDLEWARE_CLASSES = (
116
         ...
114
         ...
117
         'oscar.apps.basket.middleware.BasketMiddleware',
115
         'oscar.apps.basket.middleware.BasketMiddleware',
118
-        'django.middleware.transaction.TransactionMiddleware',  # Django 1.5 only
119
         'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
116
         'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
120
     )
117
     )
121
 
118
 
122
-If you're running Django 1.6 or above, you should enable ``ATOMIC_REQUESTS``
123
-instead (see database settings above).
124
-
125
 Set your auth backends to:
119
 Set your auth backends to:
126
 
120
 
127
 .. code-block:: django
121
 .. code-block:: django
237
             'PASSWORD': '',
231
             'PASSWORD': '',
238
             'HOST': '',
232
             'HOST': '',
239
             'PORT': '',
233
             'PORT': '',
240
-            'ATOMIC_REQUESTS': True,  # Django 1.6+
234
+            'ATOMIC_REQUESTS': True,
241
         }
235
         }
242
     }
236
     }
243
 
237
 
255
 You should now have an empty, but running Oscar install that you can browse at
249
 You should now have an empty, but running Oscar install that you can browse at
256
 http://localhost:8000.
250
 http://localhost:8000.
257
 
251
 
258
-Fixtures
259
-========
252
+Migrations
253
+----------
254
+
255
+Oscar ships with two sets of migrations. If you're running Django 1.7, you
256
+don't need to do anything; Django's migration framework will detect them
257
+automatically and will do the right thing.
258
+If you're running Django 1.6, you need to install `South`_::
259
+
260
+.. code-block:: bash
261
+
262
+    $ pip install South
263
+
264
+And you need to add it to your installed apps:
265
+
266
+.. code-block:: django
267
+
268
+    INSTALLED_APPS = [
269
+        ...
270
+        'south',
271
+    ] + get_core_apps()
272
+
273
+.. _South: http://south.readthedocs.org/en/latest/
274
+
275
+
276
+Initial data
277
+============
260
 
278
 
261
 The default checkout process requires a shipping address with a country.  Oscar
279
 The default checkout process requires a shipping address with a country.  Oscar
262
 uses a model for countries with flags that indicate which are valid shipping
280
 uses a model for countries with flags that indicate which are valid shipping

+ 0
- 27
docs/source/ref/settings.rst View File

312
 Offer settings
312
 Offer settings
313
 ==============
313
 ==============
314
 
314
 
315
-``OSCAR_OFFER_BLACKLIST_PRODUCT``
316
----------------------------------
317
-
318
-Default: ``None``
319
-
320
-A function which takes a product as its sole parameter and returns a boolean
321
-indicating if the product is blacklisted from offers or not.
322
-
323
-Example::
324
-
325
-    from decimal import Decimal as D
326
-
327
-    def is_expensive(product):
328
-        if product.has_stockrecord:
329
-            return product.stockrecord.price_incl_tax > D('1000.00')
330
-        return False
331
-
332
-    # Don't allow expensive products to be in offers
333
-    OSCAR_OFFER_BLACKLIST_PRODUCT = is_expensive
334
-
335
 ``OSCAR_OFFER_ROUNDING_FUNCTION``
315
 ``OSCAR_OFFER_ROUNDING_FUNCTION``
336
 ---------------------------------
316
 ---------------------------------
337
 
317
 
364
 
344
 
365
 The name of the cookie for the open basket.
345
 The name of the cookie for the open basket.
366
 
346
 
367
-``OSCAR_BASKET_COOKIE_SAVED``
368
------------------------------
369
-
370
-Default: ``'oscar_saved_basket'``
371
-
372
-The name of the cookie for the saved basket.
373
-
374
 Currency settings
347
 Currency settings
375
 =================
348
 =================
376
 
349
 

+ 160
- 10
docs/source/releases/v0.8.rst View File

37
 Compatibility
37
 Compatibility
38
 -------------
38
 -------------
39
 
39
 
40
-Oscar 0.8 is compatible with Django 1.5-1.7. 
40
+This release adds support for Django 1.7. Per our policy of always supporting
41
+two versions of Django, support for Django 1.5 has been dropped.
41
 
42
 
42
-Support for Python 2.6 has been dropped; Oscar works with Python 2.7, 3.3
43
-and 3.4.
43
+This release also adds full Python 3.3 and 3.4 support. But due to South
44
+not supporting Python 3, Python 3 support is only supported in combination
45
+with Django 1.7 and it's new migration framework.
44
 
46
 
45
 .. _new_in_0.8:
47
 .. _new_in_0.8:
46
 
48
 
80
 Some properties and method names have also been updated to the new naming. The
82
 Some properties and method names have also been updated to the new naming. The
81
 old ones will throw a deprecation warning.
83
 old ones will throw a deprecation warning.
82
 
84
 
85
+Better handling of child products in product dashboard
86
+------------------------------------------------------
87
+Together with the changes above, the dashboard experience for child products
88
+has been improved. The difference between a parent product and a stand-alone
89
+product is hidden from the user; a user can now add and remove child products
90
+on any suitable product. When the first child product is added, a stand-alone
91
+product becomes a parent product; and vice versa.
92
+In the front-end, the old name of "product variants" has been kept.
93
+
94
+Django 1.7 support
95
+------------------
96
+
97
+Oscar 0.8 comes with support for Django 1.7 out of the box. The app refactor
98
+and the new migration framework are both great improvements to Django. Oscar
99
+now ships with sets of migrations both for South and the new native
100
+migrations framework.
101
+Unfortunately, the changes in Django required a few breaking changes when
102
+upgrading Oscar both for users staying on Django 1.6 and for users upgrading to
103
+Django 1.7 at the same time. These are detailed in the section for
104
+backwards-incompatible changes.
105
+
83
 Reworked shipping app
106
 Reworked shipping app
84
 ~~~~~~~~~~~~~~~~~~~~~
107
 ~~~~~~~~~~~~~~~~~~~~~
85
 
108
 
204
   populate the country databases. It replaces the ``countries.json`` fixture.
227
   populate the country databases. It replaces the ``countries.json`` fixture.
205
   The command relies on the ``pycountry`` library being installed.
228
   The command relies on the ``pycountry`` library being installed.
206
 
229
 
230
+* It is now possible to use product attributes to add a relation to arbitrary
231
+  model instances. There was some (presumably broken) support for it before,
232
+  but you should now be able to use product attributes of type ``entity`` as
233
+  expected. There's currently no frontend or dashboard support for it, as there
234
+  is no good default behaviour.
235
+
236
+* Oscar has a new dependency, django-tables2_. It's a handy library that helps
237
+  when displaying tabular data, allowing sorting, etc. It also makes it easier
238
+  to adapt e.g. the product list view in the dashboard to additional fields.
239
+
240
+* ``jquery-ui-datepicker`` has been replaced in the dashboard by
241
+  bootstrap-datetimepicker_. We still ship with ``jquery-ui-datepicker`` and
242
+  ``JQuery UI`` as it's in use in the frontend.
243
+
244
+.. _django-tables2: http://django-tables2.readthedocs.org/en/latest/
245
+.. _bootstrap-datetimepicker: http://www.malot.fr/bootstrap-datetimepicker/
246
+
207
 .. _incompatible_changes_in_0.8:
247
 .. _incompatible_changes_in_0.8:
208
 
248
 
209
 Backwards incompatible changes in 0.8
249
 Backwards incompatible changes in 0.8
210
 -------------------------------------
250
 -------------------------------------
211
 
251
 
212
-.. _incompatible_shipping_changes_in_0.8:
252
+Migrations
253
+~~~~~~~~~~
254
+
255
+* South is not a dependency of Oscar anymore: This means it won't get installed
256
+  automatically when you install Oscar. If you are on Django 1.6 and want to
257
+  use South, you will need to explicitly install it and add it to your
258
+  requirements.
259
+* Only South >= 1.0 is supported: South 1.0 is a backwards compatible release
260
+  explicitly released to help with the upgrade path to Django 1.7. Please make
261
+  sure you update accordingly if you intend to keep using South. Older versions
262
+  of South will look in the wrong directories and will break with this Oscar
263
+  release.
264
+* Rename your South ``migrations`` directories: To avoid
265
+  clashes between Django's migrations and South's migrations, you should rename
266
+  all your South migrations directories (including those of forked Oscar apps)
267
+  to ``south_migrations``. South 1.0 will check those first before falling back
268
+  to ``migrations``.
269
+* Upgrading to new-style migrations: If you're upgrading to Django 1.7, you
270
+  will need to follow the `instructions to upgrade from South`_ for your own
271
+  apps. For any forked Oscar apps, you will need to copy Oscar's initial
272
+  migrations into your emptied ``migrations`` directory first, because Oscar's
273
+  set of migrations depend on each other. You can then create migrations for
274
+  your changes by calling ``./manage.py makemigrations``. Django should
275
+  detect that the database layout already matches the state of migrations; so
276
+  a call to ``migrate`` should fake the migrations.
277
+
278
+.. _instructions to upgrade from South: https://docs.djangoproject.com/en/1.7/topics/migrations/#upgrading-from-south
213
 
279
 
214
 Product structure
280
 Product structure
215
 ~~~~~~~~~~~~~~~~~
281
 ~~~~~~~~~~~~~~~~~
216
 
282
 
217
-Generally, backwards compatibility has been preserved. Two changes are
283
+Generally, backwards compatibility has been preserved. Those changes are
218
 unavoidable:
284
 unavoidable:
219
 
285
 
220
 * You now need to explicitly set product structure when creating a product;
286
 * You now need to explicitly set product structure when creating a product;
224
   deprecation warning), but if you used the old related name in a query lookup
290
   deprecation warning), but if you used the old related name in a query lookup
225
   (e.g. ``products.filter(variants__title='foo')``, you will have to change it
291
   (e.g. ``products.filter(variants__title='foo')``, you will have to change it
226
   to ``children``.
292
   to ``children``.
293
+* Template blocks and CSS classes have been renamed.
227
 
294
 
228
 The following methods and properties have been deprecated:
295
 The following methods and properties have been deprecated:
229
 
296
 
236
 * ``Strategy.select_variant_stockrecords`` - Use
303
 * ``Strategy.select_variant_stockrecords`` - Use
237
   ``select_children_stockrecords`` instead.
304
   ``select_children_stockrecords`` instead.
238
 
305
 
306
+Furthermore, CSS classes and template blocks have been updated. Please follow
307
+the following renaming pattern:
308
+* ``variant-product`` becomes ``child-product``
309
+* ``product_variants`` becomes ``child_products``
310
+* ``variants`` becomes ``children``
311
+* ``variant`` becomes ``child``
312
+
313
+Product editing
314
+---------------
315
+The dashboard improvements for child products meant slight changes to both
316
+``ProductCreateUpdateView`` and ``ProductForm``. Notably, ``ProductForm`` now
317
+gets a ``parent`` kwarg. Please review your customisations for compatibility
318
+with the updated code.
319
+
320
+.. _incompatible_shipping_changes_in_0.8:
321
+
239
 Shipping
322
 Shipping
240
 ~~~~~~~~
323
 ~~~~~~~~
241
 
324
 
341
 * ``oscar.apps.shipping.Scales`` has been renamed and moved to
424
 * ``oscar.apps.shipping.Scales`` has been renamed and moved to
342
   ``oscar.apps.shipping.scales.Scale``, and is now overridable.
425
   ``oscar.apps.shipping.scales.Scale``, and is now overridable.
343
 
426
 
427
+Email address handling
428
+----------------------
429
+
430
+In theory, the local part of an email is case-sensitive. In practice, many
431
+users don't know about this and most email servers don't consider the
432
+capitalisation. Because of this, Oscar now disregards capitalisation when
433
+looking up emails (e.g. when a user logs in).
434
+Storing behaviour is unaltered: When a user's email address is stored (e.g.
435
+when registering or checking out), the local part is unaltered and
436
+the host portion is lowercased.
437
+
438
+.. warning::
439
+
440
+   Those changes mean you might now have multiple users with email addresses
441
+   that Oscar considers identical. Please use the new
442
+   ``oscar_find_duplicate_emails`` management command to check your database
443
+   and deal with any conflicts accordingly.
444
+
445
+Django 1.7 support
446
+------------------
447
+
448
+If you have any plans to upgrade to Django 1.7, more changes beyond
449
+addressing migrations are necessary:
450
+
451
+* You should be aware that Django 1.7 now enforces uniqueness of app labels.
452
+  Oscar dashboard apps now ship with app configs that set their app label
453
+  to ``{oldname}_dashboard``.
454
+* If you have forked any Oscar apps, you must add app configs to them, and
455
+  have them inherit from the Oscar one. See the appropriate section in
456
+  :doc:`/topics/fork_app` for an example.
457
+* Double-check that you address migrations as detailed above.
458
+* Django now enforces that no calls happen to the model registry during
459
+  app startup. This mostly means that you should avoid module-level calls to
460
+  ``get_model``, as that only works with a fully initialised model registry.
461
+
344
 Misc
462
 Misc
345
 ~~~~
463
 ~~~~
346
 
464
 
377
   trivial ``ProductAttribute.get_validator`` and the unused
495
   trivial ``ProductAttribute.get_validator`` and the unused
378
   ``ProductAttribute.is_value_valid`` methods have been removed.
496
   ``ProductAttribute.is_value_valid`` methods have been removed.
379
 
497
 
380
-* It is now possible to use product attributes to add a relation to arbitrary
381
-  model instances. There was some (presumably broken) support for it before,
382
-  but you should now be able to use product attributes of type ``entity`` as
383
-  expected. There's currently no frontend or dashboard support for it, as there
384
-  is no good default behaviour.
498
+* The ``RangeProductFileUpload`` model has been moved from the ranges
499
+  dashboard app to the offers app. The migrations that have been naively
500
+  drop and re-create the model; any data is lost! This is probably not an
501
+  issue, as the model is only used while an range upload is in progress. If
502
+  you need to keep the data, ensure you migrate it across.
503
+
504
+* ``oscar.core.loading.get_model`` now raises a ``LookupError`` instead of an
505
+  ``ImportError`` if a model can't be found. That brings it more in line with
506
+  what Django does since the app refactor.
507
+
508
+* ``CommunicationEventType.category`` was storing a localised string, which
509
+  breaks when switching locale. It now uses ``choices`` to map between the
510
+  value and a localised string. Unfortunately, if you're using this feature
511
+  and not running an English locale, you will need to migrate the existing
512
+  data to the English values.
513
+
514
+* Support for the ``OSCAR_OFFER_BLACKLIST_PRODUCT`` setting has been removed.
515
+  It was only partially supported: it prevented products from being
516
+  added to a range, but offers could be applied to the products nonetheless.
517
+  To prevent an offer being applied to a product, use ``is_discountable`` or
518
+  override ``get_is_discountable`` on your product instances.
385
 
519
 
386
 * ``Category.get_ancestors`` used to return a list of ancestors and would
520
 * ``Category.get_ancestors`` used to return a list of ancestors and would
387
   default to include itself. For consistency with get_descendants and to avoid
521
   default to include itself. For consistency with get_descendants and to avoid
455
     Please double-check it's outcome and make sure to do something similar
589
     Please double-check it's outcome and make sure to do something similar
456
     if you have forked the catalogue app.
590
     if you have forked the catalogue app.
457
 
591
 
592
+.. note::
593
+
594
+    The migration numbers below refer to the numbers of the South migrations.
595
+    Oscar 0.8 ships with a set of new initial migrations for Django's new
596
+    native migrations framework. They include all the changes detailed below.
597
+
458
 .. note::
598
 .. note::
459
 
599
 
460
     Be sure to read the detailed instructions for
600
     Be sure to read the detailed instructions for
475
       and do entity attribute changes and model deletions.
615
       and do entity attribute changes and model deletions.
476
     - ``0025`` & ``0026`` - Schema & data migration to determine and save Product structure.
616
     - ``0025`` & ``0026`` - Schema & data migration to determine and save Product structure.
477
 
617
 
618
+* Offer:
619
+
620
+    - ``0033`` - Use an ``AutoSlug`` field for ``Range`` models
621
+    - ``0034`` - Add moved ``RangedProductFileUpload`` model.
622
+
478
 * Order:
623
 * Order:
479
 
624
 
480
     - ``0029`` - Add ``unique_together`` to ``PaymentEventQuantity`` and ``ShippingEventQuantity``
625
     - ``0029`` - Add ``unique_together`` to ``PaymentEventQuantity`` and ``ShippingEventQuantity``
489
 
634
 
490
     - ``0006`` - Add ``unique_together`` to ``OrderedProduct``
635
     - ``0006`` - Add ``unique_together`` to ``OrderedProduct``
491
 
636
 
637
+* Ranges dashboard:
638
+
639
+    - ``0003`` - Drop ``RangeProductFileUpload`` from ``ranges`` app. This is
640
+                 a destructive change!
641
+
492
 * Shipping:
642
 * Shipping:
493
 
643
 
494
     - ``0007`` - Change ``WeightBand.upper_limit`` from ``FloatField`` to ``DecimalField``
644
     - ``0007`` - Change ``WeightBand.upper_limit`` from ``FloatField`` to ``DecimalField``

+ 4
- 0
docs/source/topics/class_loading_explained.rst View File

64
 the class is overridden, it will not require code changes. Care should be taken
64
 the class is overridden, it will not require code changes. Care should be taken
65
 when doing this, as this is a tricky trade-off between maintainability and
65
 when doing this, as this is a tricky trade-off between maintainability and
66
 added complexity.
66
 added complexity.
67
+Please note that we cannot recommend ever using ``get_model`` in your own code.
68
+Especially pre-Django 1.7, model initialisation is a tricky process and it's
69
+easy to run into circular import issues.
70
+
67
 
71
 
68
 Testing
72
 Testing
69
 -------
73
 -------

+ 20
- 0
docs/source/topics/fork_app.rst View File

52
     import oscar.apps.order.admin
52
     import oscar.apps.order.admin
53
 
53
 
54
 This isn't great but we haven't found a better way as of yet.
54
 This isn't great but we haven't found a better way as of yet.
55
+
56
+Django 1.7+: Use supplied app config
57
+====================================
58
+
59
+Oscar ships with an app config for each app, which sets app labels and
60
+runs startup code. You need to make sure that happens.
61
+
62
+.. code-block: django
63
+
64
+    # yourproject/order/config.py
65
+
66
+    from oscar.apps.order import config
67
+
68
+
69
+    class OrderConfig(config.OrderConfig):
70
+        name = 'yourproject.order'
71
+
72
+    # yourproject/order/__init__.py
73
+
74
+    default_app_config = 'yourproject.order.config.OrderConfig'

+ 1
- 1
docs/source/topics/prices_and_availability.rst View File

276
         """
276
         """
277
 
277
 
278
         def strategy(self, request=None, user=None, **kwargs):
278
         def strategy(self, request=None, user=None, **kwargs):
279
-            return UKStrategy(territory)
279
+            return UKStrategy()
280
 
280
 
281
 
281
 
282
     class IncludingVAT(strategy.FixedRateTax):
282
     class IncludingVAT(strategy.FixedRateTax):

+ 1
- 0
oscar/__init__.py View File

64
     'haystack',
64
     'haystack',
65
     'treebeard',
65
     'treebeard',
66
     'sorl.thumbnail',
66
     'sorl.thumbnail',
67
+    'django_tables2',
67
 ]
68
 ]
68
 
69
 
69
 
70
 

+ 12
- 22
oscar/app.py View File

48
                 login_forbidden(auth_views.password_reset_done),
48
                 login_forbidden(auth_views.password_reset_done),
49
                 name='password-reset-done')]
49
                 name='password-reset-done')]
50
 
50
 
51
-        # Django <=1.5: uses uidb36 to encode the user's primary key
51
+        # Django <=1.5: uses uidb36 to encode the user's primary key (support has been removed)
52
         # Django 1.6:   uses uidb64 to encode the user's primary key, but
52
         # Django 1.6:   uses uidb64 to encode the user's primary key, but
53
         #               but supports legacy links
53
         #               but supports legacy links
54
         # Django > 1.7: used uidb64 to encode the user's primary key
54
         # Django > 1.7: used uidb64 to encode the user's primary key
55
         # see https://docs.djangoproject.com/en/dev/releases/1.6/#django-contrib-auth-password-reset-uses-base-64-encoding-of-user-pk
55
         # see https://docs.djangoproject.com/en/dev/releases/1.6/#django-contrib-auth-password-reset-uses-base-64-encoding-of-user-pk
56
-        if django.VERSION < (1, 6):
56
+        urls.append(
57
+            url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
58
+                login_forbidden(auth_views.password_reset_confirm),
59
+                {
60
+                    'post_reset_redirect': reverse_lazy('password-reset-complete'),
61
+                    'set_password_form': self.set_password_form,
62
+                },
63
+                name='password-reset-confirm'))
64
+        if django.VERSION < (1, 7):
57
             urls.append(
65
             urls.append(
58
                 url(r'^password-reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
66
                 url(r'^password-reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
59
-                    login_forbidden(auth_views.password_reset_confirm),
67
+                    login_forbidden(auth_views.password_reset_confirm_uidb36),
60
                     {
68
                     {
61
                         'post_reset_redirect': reverse_lazy('password-reset-complete'),
69
                         'post_reset_redirect': reverse_lazy('password-reset-complete'),
62
                         'set_password_form': self.set_password_form,
70
                         'set_password_form': self.set_password_form,
63
-                    },
64
-                    name='password-reset-confirm'))
65
-        else:
66
-            urls.append(
67
-                url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
68
-                    login_forbidden(auth_views.password_reset_confirm),
69
-                    {
70
-                        'post_reset_redirect': reverse_lazy('password-reset-complete'),
71
-                        'set_password_form': self.set_password_form,
72
-                    },
73
-                    name='password-reset-confirm'))
74
-            if django.VERSION < (1, 7):
75
-                urls.append(
76
-                    url(r'^password-reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
77
-                        login_forbidden(auth_views.password_reset_confirm_uidb36),
78
-                        {
79
-                            'post_reset_redirect': reverse_lazy('password-reset-complete'),
80
-                            'set_password_form': self.set_password_form,
81
-                        }))
71
+                    }))
82
 
72
 
83
         urls += [
73
         urls += [
84
             url(r'^password-reset/complete/$',
74
             url(r'^password-reset/complete/$',

+ 1
- 0
oscar/apps/address/__init__.py View File

1
+default_app_config = 'oscar.apps.address.config.AddressConfig'

+ 22
- 4
oscar/apps/address/abstract_models.py View File

2
 import zlib
2
 import zlib
3
 
3
 
4
 from django.db import models
4
 from django.db import models
5
+from django.utils.encoding import python_2_unicode_compatible
5
 from django.utils.translation import ugettext_lazy as _, pgettext_lazy
6
 from django.utils.translation import ugettext_lazy as _, pgettext_lazy
6
 from django.core import exceptions
7
 from django.core import exceptions
7
 
8
 
8
 from oscar.core.compat import AUTH_USER_MODEL
9
 from oscar.core.compat import AUTH_USER_MODEL
9
 from oscar.models.fields import UppercaseCharField, PhoneNumberField
10
 from oscar.models.fields import UppercaseCharField, PhoneNumberField
10
-from six.moves import filter
11
+from django.utils.six.moves import filter
11
 
12
 
12
 
13
 
14
+@python_2_unicode_compatible
13
 class AbstractAddress(models.Model):
15
 class AbstractAddress(models.Model):
14
     """
16
     """
15
     Superclass address object
17
     Superclass address object
233
     search_text = models.TextField(
235
     search_text = models.TextField(
234
         _("Search text - used only for searching addresses"), editable=False)
236
         _("Search text - used only for searching addresses"), editable=False)
235
 
237
 
236
-    def __unicode__(self):
238
+    def __str__(self):
237
         return self.summary
239
         return self.summary
238
 
240
 
239
     class Meta:
241
     class Meta:
373
         return fields
375
         return fields
374
 
376
 
375
 
377
 
378
+@python_2_unicode_compatible
376
 class AbstractCountry(models.Model):
379
 class AbstractCountry(models.Model):
377
     """
380
     """
378
     International Organization for Standardization (ISO) 3166-1 Country list.
381
     International Organization for Standardization (ISO) 3166-1 Country list.
402
 
405
 
403
     class Meta:
406
     class Meta:
404
         abstract = True
407
         abstract = True
408
+        app_label = 'address'
405
         verbose_name = _('Country')
409
         verbose_name = _('Country')
406
         verbose_name_plural = _('Countries')
410
         verbose_name_plural = _('Countries')
407
         ordering = ('-display_order', 'printable_name',)
411
         ordering = ('-display_order', 'printable_name',)
408
 
412
 
409
-    def __unicode__(self):
413
+    def __str__(self):
410
         return self.printable_name or self.name
414
         return self.printable_name or self.name
411
 
415
 
412
     @property
416
     @property
435
 
439
 
436
     A shipping address should not be edited once the order has been placed -
440
     A shipping address should not be edited once the order has been placed -
437
     it should be read-only after that.
441
     it should be read-only after that.
442
+
443
+    NOTE:
444
+    ShippingAddress is a model of the order app. But moving it there is tricky
445
+    due to circular import issues that are amplified by get_model/get_class
446
+    calls pre-Django 1.7 to register receivers. So...
447
+    TODO: Once Django 1.6 support is dropped, move AbstractBillingAddress and
448
+    AbstractShippingAddress to the order app, and move
449
+    PartnerAddress to the partner app.
438
     """
450
     """
451
+
439
     phone_number = PhoneNumberField(
452
     phone_number = PhoneNumberField(
440
         _("Phone number"), blank=True,
453
         _("Phone number"), blank=True,
441
         help_text=_("In case we need to call you about your order"))
454
         help_text=_("In case we need to call you about your order"))
446
 
459
 
447
     class Meta:
460
     class Meta:
448
         abstract = True
461
         abstract = True
462
+        # ShippingAddress is registered in order/models.py
463
+        app_label = 'order'
449
         verbose_name = _("Shipping address")
464
         verbose_name = _("Shipping address")
450
         verbose_name_plural = _("Shipping addresses")
465
         verbose_name_plural = _("Shipping addresses")
451
 
466
 
518
 
533
 
519
     class Meta:
534
     class Meta:
520
         abstract = True
535
         abstract = True
536
+        app_label = 'address'
521
         verbose_name = _("User address")
537
         verbose_name = _("User address")
522
         verbose_name_plural = _("User addresses")
538
         verbose_name_plural = _("User addresses")
523
         ordering = ['-num_orders']
539
         ordering = ['-num_orders']
537
 
553
 
538
 
554
 
539
 class AbstractBillingAddress(AbstractAddress):
555
 class AbstractBillingAddress(AbstractAddress):
540
-
541
     class Meta:
556
     class Meta:
542
         abstract = True
557
         abstract = True
558
+        # BillingAddress is registered in order/models.py
559
+        app_label = 'order'
543
         verbose_name = _("Billing address")
560
         verbose_name = _("Billing address")
544
         verbose_name_plural = _("Billing addresses")
561
         verbose_name_plural = _("Billing addresses")
545
 
562
 
564
 
581
 
565
     class Meta:
582
     class Meta:
566
         abstract = True
583
         abstract = True
584
+        app_label = 'partner'
567
         verbose_name = _("Partner address")
585
         verbose_name = _("Partner address")
568
         verbose_name_plural = _("Partner addresses")
586
         verbose_name_plural = _("Partner addresses")

+ 8
- 0
oscar/apps/address/config.py View File

1
+from django.apps import AppConfig
2
+from django.utils.translation import ugettext_lazy as _
3
+
4
+
5
+class AddressConfig(AppConfig):
6
+    label = 'address'
7
+    name = 'oscar.apps.address'
8
+    verbose_name = _('Address')

+ 66
- 126
oscar/apps/address/migrations/0001_initial.py View File

1
-# encoding: utf-8
2
-import datetime
3
-from south.db import db
4
-from south.v2 import SchemaMigration
5
-from django.db import models
6
-from oscar.core.compat import AUTH_USER_MODEL, AUTH_USER_MODEL_NAME
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
7
 
3
 
8
-class Migration(SchemaMigration):
4
+from django.db import models, migrations
5
+import oscar.models.fields
6
+from django.conf import settings
9
 
7
 
10
-    def forwards(self, orm):
11
 
8
 
12
-        # Adding model 'UserAddress'
13
-        db.create_table('address_useraddress', (
14
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
15
-            ('title', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)),
16
-            ('first_name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
17
-            ('last_name', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
18
-            ('line1', self.gf('django.db.models.fields.CharField')(max_length=255)),
19
-            ('line2', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
20
-            ('line3', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
21
-            ('line4', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
22
-            ('state', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
23
-            ('postcode', self.gf('django.db.models.fields.CharField')(max_length=64)),
24
-            ('country', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['address.Country'])),
25
-            ('search_text', self.gf('django.db.models.fields.CharField')(max_length=1000)),
26
-            ('phone_number', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
27
-            ('notes', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
28
-            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='addresses', to=orm[AUTH_USER_MODEL])),
29
-            ('is_default_for_shipping', self.gf('django.db.models.fields.BooleanField')(default=False)),
30
-            ('is_default_for_billing', self.gf('django.db.models.fields.BooleanField')(default=False)),
31
-            ('num_orders', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
32
-            ('hash', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
33
-            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
34
-        ))
35
-        db.send_create_signal('address', ['UserAddress'])
9
+class Migration(migrations.Migration):
36
 
10
 
37
-        # Adding model 'Country'
38
-        db.create_table('address_country', (
39
-            ('iso_3166_1_a2', self.gf('django.db.models.fields.CharField')(max_length=2, primary_key=True)),
40
-            ('iso_3166_1_a3', self.gf('django.db.models.fields.CharField')(max_length=3, null=True, db_index=True)),
41
-            ('iso_3166_1_numeric', self.gf('django.db.models.fields.PositiveSmallIntegerField')(null=True, db_index=True)),
42
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
43
-            ('printable_name', self.gf('django.db.models.fields.CharField')(max_length=128)),
44
-            ('is_highlighted', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True)),
45
-            ('is_shipping_country', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True)),
46
-        ))
47
-        db.send_create_signal('address', ['Country'])
11
+    dependencies = [
12
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+    ]
48
 
14
 
49
-
50
-    def backwards(self, orm):
51
-
52
-        # Deleting model 'UserAddress'
53
-        db.delete_table('address_useraddress')
54
-
55
-        # Deleting model 'Country'
56
-        db.delete_table('address_country')
57
-
58
-
59
-    models = {
60
-        'address.country': {
61
-            'Meta': {'ordering': "('-is_highlighted', 'name')", 'object_name': 'Country'},
62
-            'is_highlighted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
63
-            'is_shipping_country': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
64
-            'iso_3166_1_a2': ('django.db.models.fields.CharField', [], {'max_length': '2', 'primary_key': 'True'}),
65
-            'iso_3166_1_a3': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'db_index': 'True'}),
66
-            'iso_3166_1_numeric': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'db_index': 'True'}),
67
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
68
-            'printable_name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
69
-        },
70
-        'address.useraddress': {
71
-            'Meta': {'ordering': "['-num_orders']", 'object_name': 'UserAddress'},
72
-            'country': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['address.Country']"}),
73
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
74
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
75
-            'hash': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
76
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
77
-            'is_default_for_billing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
78
-            'is_default_for_shipping': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
79
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
80
-            'line1': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
81
-            'line2': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
82
-            'line3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
83
-            'line4': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
84
-            'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
85
-            'num_orders': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
86
-            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
87
-            'postcode': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
88
-            'search_text': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
89
-            'state': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
90
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
91
-            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'addresses'", 'to': "orm['{0}']".format(AUTH_USER_MODEL)})
92
-        },
93
-        'auth.group': {
94
-            'Meta': {'object_name': 'Group'},
95
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
97
-            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
98
-        },
99
-        'auth.permission': {
100
-            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
101
-            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
102
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
103
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
104
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
105
-        },
106
-        AUTH_USER_MODEL: {
107
-            'Meta': {'object_name': AUTH_USER_MODEL_NAME},
108
-            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
109
-            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
110
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
111
-            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
112
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
113
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
114
-            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
115
-            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
116
-            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
117
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
118
-            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
119
-            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
120
-            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
121
-        },
122
-        'contenttypes.contenttype': {
123
-            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
124
-            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
125
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
126
-            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
127
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
128
-        }
129
-    }
130
-
131
-    complete_apps = ['address']
15
+    operations = [
16
+        migrations.CreateModel(
17
+            name='Country',
18
+            fields=[
19
+                ('iso_3166_1_a2', models.CharField(verbose_name='ISO 3166-1 alpha-2', primary_key=True, serialize=False, max_length=2)),
20
+                ('iso_3166_1_a3', models.CharField(verbose_name='ISO 3166-1 alpha-3', blank=True, max_length=3)),
21
+                ('iso_3166_1_numeric', models.CharField(verbose_name='ISO 3166-1 numeric', blank=True, max_length=3)),
22
+                ('printable_name', models.CharField(verbose_name='Country name', max_length=128)),
23
+                ('name', models.CharField(verbose_name='Official name', max_length=128)),
24
+                ('display_order', models.PositiveSmallIntegerField(verbose_name='Display order', help_text='Higher the number, higher the country in the list.', db_index=True, default=0)),
25
+                ('is_shipping_country', models.BooleanField(verbose_name='Is shipping country', default=False, db_index=True)),
26
+            ],
27
+            options={
28
+                'verbose_name': 'Country',
29
+                'verbose_name_plural': 'Countries',
30
+                'ordering': ('-display_order', 'printable_name'),
31
+                'abstract': False,
32
+            },
33
+            bases=(models.Model,),
34
+        ),
35
+        migrations.CreateModel(
36
+            name='UserAddress',
37
+            fields=[
38
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
39
+                ('title', models.CharField(verbose_name='Title', choices=[('Mr', 'Mr'), ('Miss', 'Miss'), ('Mrs', 'Mrs'), ('Ms', 'Ms'), ('Dr', 'Dr')], blank=True, max_length=64)),
40
+                ('first_name', models.CharField(verbose_name='First name', blank=True, max_length=255)),
41
+                ('last_name', models.CharField(verbose_name='Last name', blank=True, max_length=255)),
42
+                ('line1', models.CharField(verbose_name='First line of address', max_length=255)),
43
+                ('line2', models.CharField(verbose_name='Second line of address', blank=True, max_length=255)),
44
+                ('line3', models.CharField(verbose_name='Third line of address', blank=True, max_length=255)),
45
+                ('line4', models.CharField(verbose_name='City', blank=True, max_length=255)),
46
+                ('state', models.CharField(verbose_name='State/County', blank=True, max_length=255)),
47
+                ('postcode', oscar.models.fields.UppercaseCharField(verbose_name='Post/Zip-code', blank=True, max_length=64)),
48
+                ('search_text', models.TextField(editable=False, verbose_name='Search text - used only for searching addresses')),
49
+                ('phone_number', oscar.models.fields.PhoneNumberField(verbose_name='Phone number', blank=True, help_text='In case we need to call you about your order')),
50
+                ('notes', models.TextField(verbose_name='Instructions', blank=True, help_text='Tell us anything we should know when delivering your order.')),
51
+                ('is_default_for_shipping', models.BooleanField(verbose_name='Default shipping address?', default=False)),
52
+                ('is_default_for_billing', models.BooleanField(verbose_name='Default billing address?', default=False)),
53
+                ('num_orders', models.PositiveIntegerField(verbose_name='Number of Orders', default=0)),
54
+                ('hash', models.CharField(editable=False, verbose_name='Address Hash', db_index=True, max_length=255)),
55
+                ('date_created', models.DateTimeField(verbose_name='Date Created', auto_now_add=True)),
56
+                ('country', models.ForeignKey(verbose_name='Country', to='address.Country')),
57
+                ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)),
58
+            ],
59
+            options={
60
+                'verbose_name': 'User address',
61
+                'verbose_name_plural': 'User addresses',
62
+                'ordering': ['-num_orders'],
63
+                'abstract': False,
64
+            },
65
+            bases=(models.Model,),
66
+        ),
67
+        migrations.AlterUniqueTogether(
68
+            name='useraddress',
69
+            unique_together=set([('user', 'hash')]),
70
+        ),
71
+    ]

+ 7
- 4
oscar/apps/address/models.py View File

1
+from oscar.core.loading import is_model_registered
1
 from oscar.apps.address.abstract_models import (
2
 from oscar.apps.address.abstract_models import (
2
     AbstractUserAddress, AbstractCountry)
3
     AbstractUserAddress, AbstractCountry)
3
 
4
 
4
 
5
 
5
-class UserAddress(AbstractUserAddress):
6
-    pass
6
+if not is_model_registered('address', 'UserAddress'):
7
+    class UserAddress(AbstractUserAddress):
8
+        pass
7
 
9
 
8
 
10
 
9
-class Country(AbstractCountry):
10
-    pass
11
+if not is_model_registered('address', 'Country'):
12
+    class Country(AbstractCountry):
13
+        pass

+ 131
- 0
oscar/apps/address/south_migrations/0001_initial.py View File

1
+# encoding: utf-8
2
+import datetime
3
+from south.db import db
4
+from south.v2 import SchemaMigration
5
+from django.db import models
6
+from oscar.core.compat import AUTH_USER_MODEL, AUTH_USER_MODEL_NAME
7
+
8
+class Migration(SchemaMigration):
9
+
10
+    def forwards(self, orm):
11
+
12
+        # Adding model 'UserAddress'
13
+        db.create_table('address_useraddress', (
14
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
15
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)),
16
+            ('first_name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
17
+            ('last_name', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
18
+            ('line1', self.gf('django.db.models.fields.CharField')(max_length=255)),
19
+            ('line2', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
20
+            ('line3', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
21
+            ('line4', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
22
+            ('state', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
23
+            ('postcode', self.gf('django.db.models.fields.CharField')(max_length=64)),
24
+            ('country', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['address.Country'])),
25
+            ('search_text', self.gf('django.db.models.fields.CharField')(max_length=1000)),
26
+            ('phone_number', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
27
+            ('notes', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
28
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='addresses', to=orm[AUTH_USER_MODEL])),
29
+            ('is_default_for_shipping', self.gf('django.db.models.fields.BooleanField')(default=False)),
30
+            ('is_default_for_billing', self.gf('django.db.models.fields.BooleanField')(default=False)),
31
+            ('num_orders', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
32
+            ('hash', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
33
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
34
+        ))
35
+        db.send_create_signal('address', ['UserAddress'])
36
+
37
+        # Adding model 'Country'
38
+        db.create_table('address_country', (
39
+            ('iso_3166_1_a2', self.gf('django.db.models.fields.CharField')(max_length=2, primary_key=True)),
40
+            ('iso_3166_1_a3', self.gf('django.db.models.fields.CharField')(max_length=3, null=True, db_index=True)),
41
+            ('iso_3166_1_numeric', self.gf('django.db.models.fields.PositiveSmallIntegerField')(null=True, db_index=True)),
42
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
43
+            ('printable_name', self.gf('django.db.models.fields.CharField')(max_length=128)),
44
+            ('is_highlighted', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True)),
45
+            ('is_shipping_country', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True)),
46
+        ))
47
+        db.send_create_signal('address', ['Country'])
48
+
49
+
50
+    def backwards(self, orm):
51
+
52
+        # Deleting model 'UserAddress'
53
+        db.delete_table('address_useraddress')
54
+
55
+        # Deleting model 'Country'
56
+        db.delete_table('address_country')
57
+
58
+
59
+    models = {
60
+        'address.country': {
61
+            'Meta': {'ordering': "('-is_highlighted', 'name')", 'object_name': 'Country'},
62
+            'is_highlighted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
63
+            'is_shipping_country': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
64
+            'iso_3166_1_a2': ('django.db.models.fields.CharField', [], {'max_length': '2', 'primary_key': 'True'}),
65
+            'iso_3166_1_a3': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'db_index': 'True'}),
66
+            'iso_3166_1_numeric': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'db_index': 'True'}),
67
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
68
+            'printable_name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
69
+        },
70
+        'address.useraddress': {
71
+            'Meta': {'ordering': "['-num_orders']", 'object_name': 'UserAddress'},
72
+            'country': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['address.Country']"}),
73
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
74
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
75
+            'hash': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
76
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
77
+            'is_default_for_billing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
78
+            'is_default_for_shipping': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
79
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
80
+            'line1': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
81
+            'line2': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
82
+            'line3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
83
+            'line4': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
84
+            'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
85
+            'num_orders': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
86
+            'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
87
+            'postcode': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
88
+            'search_text': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
89
+            'state': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
90
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
91
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'addresses'", 'to': "orm['{0}']".format(AUTH_USER_MODEL)})
92
+        },
93
+        'auth.group': {
94
+            'Meta': {'object_name': 'Group'},
95
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
97
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
98
+        },
99
+        'auth.permission': {
100
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
101
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
102
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
103
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
104
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
105
+        },
106
+        AUTH_USER_MODEL: {
107
+            'Meta': {'object_name': AUTH_USER_MODEL_NAME},
108
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
109
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
110
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
111
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
112
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
113
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
114
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
115
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
116
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
117
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
118
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
119
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
120
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
121
+        },
122
+        'contenttypes.contenttype': {
123
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
124
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
125
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
126
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
127
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
128
+        }
129
+    }
130
+
131
+    complete_apps = ['address']

oscar/apps/address/migrations/0002_auto__chg_field_useraddress_postcode.py → oscar/apps/address/south_migrations/0002_auto__chg_field_useraddress_postcode.py View File


oscar/apps/address/migrations/0003_auto__add_field_country_display_order.py → oscar/apps/address/south_migrations/0003_auto__add_field_country_display_order.py View File


oscar/apps/address/migrations/0004_convert_is_highlighted.py → oscar/apps/address/south_migrations/0004_convert_is_highlighted.py View File


oscar/apps/address/migrations/0005_auto__del_field_country_is_highlighted.py → oscar/apps/address/south_migrations/0005_auto__del_field_country_is_highlighted.py View File


oscar/apps/address/migrations/0006_auto__add_unique_useraddress_hash_user.py → oscar/apps/address/south_migrations/0006_auto__add_unique_useraddress_hash_user.py View File


oscar/apps/address/migrations/0007_auto__chg_field_useraddress_postcode.py → oscar/apps/address/south_migrations/0007_auto__chg_field_useraddress_postcode.py View File


oscar/apps/address/migrations/0008_auto__chg_field_useraddress_phone_number.py → oscar/apps/address/south_migrations/0008_auto__chg_field_useraddress_phone_number.py View File


oscar/apps/address/migrations/0009_no_null_in_charfields.py → oscar/apps/address/south_migrations/0009_no_null_in_charfields.py View File


oscar/apps/address/migrations/0010_auto__chg_field_useraddress_first_name__chg_field_useraddress_title__c.py → oscar/apps/address/south_migrations/0010_auto__chg_field_useraddress_first_name__chg_field_useraddress_title__c.py View File


oscar/apps/address/migrations/0011_auto__chg_field_useraddress_search_text.py → oscar/apps/address/south_migrations/0011_auto__chg_field_useraddress_search_text.py View File


oscar/apps/address/migrations/0012_auto__del_index_country_iso_3166_1_a3__chg_field_country_iso_3166_1_nu.py → oscar/apps/address/south_migrations/0012_auto__del_index_country_iso_3166_1_a3__chg_field_country_iso_3166_1_nu.py View File


oscar/apps/checkout/migrations/__init__.py → oscar/apps/address/south_migrations/__init__.py View File


+ 1
- 0
oscar/apps/analytics/__init__.py View File

1
+default_app_config = 'oscar.apps.analytics.config.AnalyticsConfig'

+ 11
- 3
oscar/apps/analytics/abstract_models.py View File

1
 from decimal import Decimal
1
 from decimal import Decimal
2
 
2
 
3
 from django.db import models
3
 from django.db import models
4
+from django.utils.encoding import python_2_unicode_compatible
4
 from django.utils.translation import ugettext_lazy as _
5
 from django.utils.translation import ugettext_lazy as _
5
 from oscar.core.compat import AUTH_USER_MODEL
6
 from oscar.core.compat import AUTH_USER_MODEL
6
 
7
 
7
 
8
 
9
+@python_2_unicode_compatible
8
 class AbstractProductRecord(models.Model):
10
 class AbstractProductRecord(models.Model):
9
     """
11
     """
10
     A record of a how popular a product is.
12
     A record of a how popular a product is.
29
 
31
 
30
     class Meta:
32
     class Meta:
31
         abstract = True
33
         abstract = True
34
+        app_label = 'analytics'
32
         ordering = ['-num_purchases']
35
         ordering = ['-num_purchases']
33
         verbose_name = _('Product record')
36
         verbose_name = _('Product record')
34
         verbose_name_plural = _('Product records')
37
         verbose_name_plural = _('Product records')
35
 
38
 
36
-    def __unicode__(self):
39
+    def __str__(self):
37
         return _("Record for '%s'") % self.product
40
         return _("Record for '%s'") % self.product
38
 
41
 
39
 
42
 
64
 
67
 
65
     class Meta:
68
     class Meta:
66
         abstract = True
69
         abstract = True
70
+        app_label = 'analytics'
67
         verbose_name = _('User record')
71
         verbose_name = _('User record')
68
         verbose_name_plural = _('User records')
72
         verbose_name_plural = _('User records')
69
 
73
 
70
 
74
 
75
+@python_2_unicode_compatible
71
 class AbstractUserProductView(models.Model):
76
 class AbstractUserProductView(models.Model):
72
 
77
 
73
     user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("User"))
78
     user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("User"))
76
 
81
 
77
     class Meta:
82
     class Meta:
78
         abstract = True
83
         abstract = True
84
+        app_label = 'analytics'
79
         verbose_name = _('User product view')
85
         verbose_name = _('User product view')
80
         verbose_name_plural = _('User product views')
86
         verbose_name_plural = _('User product views')
81
 
87
 
82
-    def __unicode__(self):
88
+    def __str__(self):
83
         return _("%(user)s viewed '%(product)s'") % {
89
         return _("%(user)s viewed '%(product)s'") % {
84
             'user': self.user, 'product': self.product}
90
             'user': self.user, 'product': self.product}
85
 
91
 
86
 
92
 
93
+@python_2_unicode_compatible
87
 class AbstractUserSearch(models.Model):
94
 class AbstractUserSearch(models.Model):
88
 
95
 
89
     user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("User"))
96
     user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("User"))
92
 
99
 
93
     class Meta:
100
     class Meta:
94
         abstract = True
101
         abstract = True
102
+        app_label = 'analytics'
95
         verbose_name = _("User search query")
103
         verbose_name = _("User search query")
96
         verbose_name_plural = _("User search queries")
104
         verbose_name_plural = _("User search queries")
97
 
105
 
98
-    def __unicode__(self):
106
+    def __str__(self):
99
         return _("%(user)s searched for '%(query)s'") % {
107
         return _("%(user)s searched for '%(query)s'") % {
100
             'user': self.user,
108
             'user': self.user,
101
             'query': self.query}
109
             'query': self.query}

+ 11
- 0
oscar/apps/analytics/config.py View File

1
+from django.apps import AppConfig
2
+from django.utils.translation import ugettext_lazy as _
3
+
4
+
5
+class AnalyticsConfig(AppConfig):
6
+    label = 'analytics'
7
+    name = 'oscar.apps.analytics'
8
+    verbose_name = _('Analytics')
9
+
10
+    def ready(self):
11
+        from . import receivers  # noqa

+ 30
- 254
oscar/apps/analytics/migrations/0001_initial.py View File

1
-# encoding: utf-8
2
-import datetime
3
-from south.db import db
4
-from south.v2 import SchemaMigration
5
-from django.db import models
6
-from oscar.core.compat import AUTH_USER_MODEL, AUTH_USER_MODEL_NAME
7
-
8
-class Migration(SchemaMigration):
9
-    depends_on = (
10
-        ('customer', '0001_initial'),
11
-        ('catalogue', '0001_initial'),
12
-    )
13
-
14
-    def forwards(self, orm):
15
-        
16
-        # Adding model 'ProductRecord'
17
-        db.create_table('analytics_productrecord', (
18
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
19
-            ('product', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['catalogue.Product'], unique=True)),
20
-            ('num_views', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
21
-            ('num_basket_additions', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
22
-            ('num_purchases', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True)),
23
-            ('score', self.gf('django.db.models.fields.FloatField')(default=0.0)),
24
-        ))
25
-        db.send_create_signal('analytics', ['ProductRecord'])
26
-
27
-        # Adding model 'UserRecord'
28
-        db.create_table('analytics_userrecord', (
29
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
30
-            ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm[AUTH_USER_MODEL], unique=True)),
31
-            ('num_product_views', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
32
-            ('num_basket_additions', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
33
-            ('num_orders', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True)),
34
-            ('num_order_lines', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True)),
35
-            ('num_order_items', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True)),
36
-            ('total_spent', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=12, decimal_places=2)),
37
-            ('date_last_order', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
38
-        ))
39
-        db.send_create_signal('analytics', ['UserRecord'])
40
-
41
-        # Adding model 'UserProductView'
42
-        db.create_table('analytics_userproductview', (
43
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
44
-            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm[AUTH_USER_MODEL])),
45
-            ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
46
-            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
47
-        ))
48
-        db.send_create_signal('analytics', ['UserProductView'])
49
-
50
-        # Adding model 'UserSearch'
51
-        db.create_table('analytics_usersearch', (
52
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
53
-            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm[AUTH_USER_MODEL])),
54
-            ('query', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
55
-            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
56
-        ))
57
-        db.send_create_signal('analytics', ['UserSearch'])
58
-
59
-
60
-    def backwards(self, orm):
61
-        
62
-        # Deleting model 'ProductRecord'
63
-        db.delete_table('analytics_productrecord')
64
-
65
-        # Deleting model 'UserRecord'
66
-        db.delete_table('analytics_userrecord')
67
-
68
-        # Deleting model 'UserProductView'
69
-        db.delete_table('analytics_userproductview')
70
-
71
-        # Deleting model 'UserSearch'
72
-        db.delete_table('analytics_usersearch')
73
-
74
-
75
-    models = {
76
-        'analytics.productrecord': {
77
-            'Meta': {'ordering': "['-num_purchases']", 'object_name': 'ProductRecord'},
78
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
79
-            'num_basket_additions': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
80
-            'num_purchases': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
81
-            'num_views': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
82
-            'product': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['catalogue.Product']", 'unique': 'True'}),
83
-            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0'})
84
-        },
85
-        'analytics.userproductview': {
86
-            'Meta': {'object_name': 'UserProductView'},
87
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
88
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
89
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
90
-            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['{0}']".format(AUTH_USER_MODEL)})
91
-        },
92
-        'analytics.userrecord': {
93
-            'Meta': {'object_name': 'UserRecord'},
94
-            'date_last_order': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
95
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96
-            'num_basket_additions': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
97
-            'num_order_items': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
98
-            'num_order_lines': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
99
-            'num_orders': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
100
-            'num_product_views': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
101
-            'total_spent': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '12', 'decimal_places': '2'}),
102
-            'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['{0}']".format(AUTH_USER_MODEL), 'unique': 'True'})
103
-        },
104
-        'analytics.usersearch': {
105
-            'Meta': {'object_name': 'UserSearch'},
106
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
107
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
108
-            'query': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
109
-            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['{0}']".format(AUTH_USER_MODEL)})
110
-        },
111
-        'auth.group': {
112
-            'Meta': {'object_name': 'Group'},
113
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
115
-            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
116
-        },
117
-        'auth.permission': {
118
-            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
119
-            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
120
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
121
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
122
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
123
-        },
124
-        AUTH_USER_MODEL: {
125
-            'Meta': {'object_name': AUTH_USER_MODEL_NAME},
126
-            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
127
-            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
128
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
129
-            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
130
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
131
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
132
-            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
133
-            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
134
-            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
135
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
136
-            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
137
-            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
138
-            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
139
-        },
140
-        'catalogue.attributeentity': {
141
-            'Meta': {'object_name': 'AttributeEntity'},
142
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
143
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
144
-            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
145
-            'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': "orm['catalogue.AttributeEntityType']"})
146
-        },
147
-        'catalogue.attributeentitytype': {
148
-            'Meta': {'object_name': 'AttributeEntityType'},
149
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
150
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
151
-            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'})
152
-        },
153
-        'catalogue.attributeoption': {
154
-            'Meta': {'object_name': 'AttributeOption'},
155
-            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': "orm['catalogue.AttributeOptionGroup']"}),
156
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
157
-            'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
158
-        },
159
-        'catalogue.attributeoptiongroup': {
160
-            'Meta': {'object_name': 'AttributeOptionGroup'},
161
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
162
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
163
-        },
164
-        'catalogue.category': {
165
-            'Meta': {'ordering': "['name']", 'object_name': 'Category'},
166
-            'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
167
-            'full_name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}),
168
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
169
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
170
-            'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
171
-            'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
172
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '1024', 'db_index': 'True'})
173
-        },
174
-        'catalogue.option': {
175
-            'Meta': {'object_name': 'Option'},
176
-            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
177
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
178
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
179
-            'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
180
-        },
181
-        'catalogue.product': {
182
-            'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
183
-            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.ProductAttribute']", 'through': "orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
184
-            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Category']", 'through': "orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
185
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
186
-            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
187
-            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
188
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
189
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': "orm['catalogue.Product']"}),
190
-            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductClass']", 'null': 'True'}),
191
-            'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
192
-            'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Product']", 'symmetrical': 'False', 'through': "orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
193
-            'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
194
-            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}),
195
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
196
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
197
-            'upc': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'})
198
-        },
199
-        'catalogue.productattribute': {
200
-            'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
201
-            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
202
-            'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
203
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
204
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
205
-            'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
206
-            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': "orm['catalogue.ProductClass']"}),
207
-            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
208
-            'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
209
-        },
210
-        'catalogue.productattributevalue': {
211
-            'Meta': {'object_name': 'ProductAttributeValue'},
212
-            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductAttribute']"}),
213
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
214
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
215
-            'value_boolean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
216
-            'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
217
-            'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
218
-            'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
219
-            'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
220
-            'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
221
-            'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
222
-            'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
223
-        },
224
-        'catalogue.productcategory': {
225
-            'Meta': {'ordering': "['-is_canonical']", 'object_name': 'ProductCategory'},
226
-            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Category']"}),
227
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
228
-            'is_canonical': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
229
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
230
-        },
231
-        'catalogue.productclass': {
232
-            'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
233
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
234
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
235
-            'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
236
-            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'})
237
-        },
238
-        'catalogue.productrecommendation': {
239
-            'Meta': {'object_name': 'ProductRecommendation'},
240
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
241
-            'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': "orm['catalogue.Product']"}),
242
-            'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
243
-            'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
244
-        },
245
-        'contenttypes.contenttype': {
246
-            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
247
-            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
248
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
249
-            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
250
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
251
-        }
252
-    }
253
-
254
-    complete_apps = ['analytics']
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+    ]
11
+
12
+    operations = [
13
+        migrations.CreateModel(
14
+            name='ProductRecord',
15
+            fields=[
16
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
17
+                ('num_views', models.PositiveIntegerField(verbose_name='Views', default=0)),
18
+                ('num_basket_additions', models.PositiveIntegerField(verbose_name='Basket Additions', default=0)),
19
+                ('num_purchases', models.PositiveIntegerField(verbose_name='Purchases', db_index=True, default=0)),
20
+                ('score', models.FloatField(verbose_name='Score', default=0.0)),
21
+            ],
22
+            options={
23
+                'verbose_name': 'Product record',
24
+                'verbose_name_plural': 'Product records',
25
+                'ordering': ['-num_purchases'],
26
+                'abstract': False,
27
+            },
28
+            bases=(models.Model,),
29
+        ),
30
+    ]

+ 74
- 0
oscar/apps/analytics/migrations/0002_auto_20140805_1510.py View File

1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+from django.conf import settings
6
+from decimal import Decimal
7
+
8
+
9
+class Migration(migrations.Migration):
10
+
11
+    dependencies = [
12
+        ('catalogue', '0001_initial'),
13
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14
+        ('analytics', '0001_initial'),
15
+    ]
16
+
17
+    operations = [
18
+        migrations.AddField(
19
+            model_name='productrecord',
20
+            name='product',
21
+            field=models.OneToOneField(verbose_name='Product', to='catalogue.Product'),
22
+            preserve_default=True,
23
+        ),
24
+        migrations.CreateModel(
25
+            name='UserProductView',
26
+            fields=[
27
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
28
+                ('date_created', models.DateTimeField(verbose_name='Date Created', auto_now_add=True)),
29
+                ('product', models.ForeignKey(verbose_name='Product', to='catalogue.Product')),
30
+                ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)),
31
+            ],
32
+            options={
33
+                'verbose_name': 'User product view',
34
+                'verbose_name_plural': 'User product views',
35
+                'abstract': False,
36
+            },
37
+            bases=(models.Model,),
38
+        ),
39
+        migrations.CreateModel(
40
+            name='UserRecord',
41
+            fields=[
42
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
43
+                ('num_product_views', models.PositiveIntegerField(verbose_name='Product Views', default=0)),
44
+                ('num_basket_additions', models.PositiveIntegerField(verbose_name='Basket Additions', default=0)),
45
+                ('num_orders', models.PositiveIntegerField(verbose_name='Orders', db_index=True, default=0)),
46
+                ('num_order_lines', models.PositiveIntegerField(verbose_name='Order Lines', db_index=True, default=0)),
47
+                ('num_order_items', models.PositiveIntegerField(verbose_name='Order Items', db_index=True, default=0)),
48
+                ('total_spent', models.DecimalField(verbose_name='Total Spent', max_digits=12, decimal_places=2, default=Decimal('0.00'))),
49
+                ('date_last_order', models.DateTimeField(verbose_name='Last Order Date', blank=True, null=True)),
50
+                ('user', models.OneToOneField(verbose_name='User', to=settings.AUTH_USER_MODEL)),
51
+            ],
52
+            options={
53
+                'verbose_name': 'User record',
54
+                'verbose_name_plural': 'User records',
55
+                'abstract': False,
56
+            },
57
+            bases=(models.Model,),
58
+        ),
59
+        migrations.CreateModel(
60
+            name='UserSearch',
61
+            fields=[
62
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
63
+                ('query', models.CharField(verbose_name='Search term', db_index=True, max_length=255)),
64
+                ('date_created', models.DateTimeField(verbose_name='Date Created', auto_now_add=True)),
65
+                ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)),
66
+            ],
67
+            options={
68
+                'verbose_name': 'User search query',
69
+                'verbose_name_plural': 'User search queries',
70
+                'abstract': False,
71
+            },
72
+            bases=(models.Model,),
73
+        ),
74
+    ]

+ 17
- 9
oscar/apps/analytics/models.py View File

1
+import django
2
+
3
+from oscar.core.loading import is_model_registered
1
 from oscar.apps.analytics.abstract_models import (
4
 from oscar.apps.analytics.abstract_models import (
2
     AbstractProductRecord, AbstractUserRecord,
5
     AbstractProductRecord, AbstractUserRecord,
3
     AbstractUserProductView, AbstractUserSearch)
6
     AbstractUserProductView, AbstractUserSearch)
4
 
7
 
5
 
8
 
6
-class ProductRecord(AbstractProductRecord):
7
-    pass
9
+if not is_model_registered('analytics', 'ProductRecord'):
10
+    class ProductRecord(AbstractProductRecord):
11
+        pass
8
 
12
 
9
 
13
 
10
-class UserRecord(AbstractUserRecord):
11
-    pass
14
+if not is_model_registered('analytics', 'UserRecord'):
15
+    class UserRecord(AbstractUserRecord):
16
+        pass
12
 
17
 
13
 
18
 
14
-class UserProductView(AbstractUserProductView):
15
-    pass
19
+if not is_model_registered('analytics', 'UserProductView'):
20
+    class UserProductView(AbstractUserProductView):
21
+        pass
16
 
22
 
17
 
23
 
18
-class UserSearch(AbstractUserSearch):
19
-    pass
24
+if not is_model_registered('analytics', 'UserSearch'):
25
+    class UserSearch(AbstractUserSearch):
26
+        pass
20
 
27
 
21
 
28
 
22
-from .receivers import *  # noqa
29
+if django.VERSION < (1, 7):
30
+    from . import receivers  # noqa

+ 254
- 0
oscar/apps/analytics/south_migrations/0001_initial.py View File

1
+# encoding: utf-8
2
+import datetime
3
+from south.db import db
4
+from south.v2 import SchemaMigration
5
+from django.db import models
6
+from oscar.core.compat import AUTH_USER_MODEL, AUTH_USER_MODEL_NAME
7
+
8
+class Migration(SchemaMigration):
9
+    depends_on = (
10
+        ('customer', '0001_initial'),
11
+        ('catalogue', '0001_initial'),
12
+    )
13
+
14
+    def forwards(self, orm):
15
+        
16
+        # Adding model 'ProductRecord'
17
+        db.create_table('analytics_productrecord', (
18
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
19
+            ('product', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['catalogue.Product'], unique=True)),
20
+            ('num_views', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
21
+            ('num_basket_additions', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
22
+            ('num_purchases', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True)),
23
+            ('score', self.gf('django.db.models.fields.FloatField')(default=0.0)),
24
+        ))
25
+        db.send_create_signal('analytics', ['ProductRecord'])
26
+
27
+        # Adding model 'UserRecord'
28
+        db.create_table('analytics_userrecord', (
29
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
30
+            ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm[AUTH_USER_MODEL], unique=True)),
31
+            ('num_product_views', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
32
+            ('num_basket_additions', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
33
+            ('num_orders', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True)),
34
+            ('num_order_lines', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True)),
35
+            ('num_order_items', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, db_index=True)),
36
+            ('total_spent', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=12, decimal_places=2)),
37
+            ('date_last_order', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
38
+        ))
39
+        db.send_create_signal('analytics', ['UserRecord'])
40
+
41
+        # Adding model 'UserProductView'
42
+        db.create_table('analytics_userproductview', (
43
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
44
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm[AUTH_USER_MODEL])),
45
+            ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
46
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
47
+        ))
48
+        db.send_create_signal('analytics', ['UserProductView'])
49
+
50
+        # Adding model 'UserSearch'
51
+        db.create_table('analytics_usersearch', (
52
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
53
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm[AUTH_USER_MODEL])),
54
+            ('query', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
55
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
56
+        ))
57
+        db.send_create_signal('analytics', ['UserSearch'])
58
+
59
+
60
+    def backwards(self, orm):
61
+        
62
+        # Deleting model 'ProductRecord'
63
+        db.delete_table('analytics_productrecord')
64
+
65
+        # Deleting model 'UserRecord'
66
+        db.delete_table('analytics_userrecord')
67
+
68
+        # Deleting model 'UserProductView'
69
+        db.delete_table('analytics_userproductview')
70
+
71
+        # Deleting model 'UserSearch'
72
+        db.delete_table('analytics_usersearch')
73
+
74
+
75
+    models = {
76
+        'analytics.productrecord': {
77
+            'Meta': {'ordering': "['-num_purchases']", 'object_name': 'ProductRecord'},
78
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
79
+            'num_basket_additions': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
80
+            'num_purchases': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
81
+            'num_views': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
82
+            'product': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['catalogue.Product']", 'unique': 'True'}),
83
+            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0'})
84
+        },
85
+        'analytics.userproductview': {
86
+            'Meta': {'object_name': 'UserProductView'},
87
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
88
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
89
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
90
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['{0}']".format(AUTH_USER_MODEL)})
91
+        },
92
+        'analytics.userrecord': {
93
+            'Meta': {'object_name': 'UserRecord'},
94
+            'date_last_order': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
95
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96
+            'num_basket_additions': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
97
+            'num_order_items': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
98
+            'num_order_lines': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
99
+            'num_orders': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
100
+            'num_product_views': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
101
+            'total_spent': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '12', 'decimal_places': '2'}),
102
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['{0}']".format(AUTH_USER_MODEL), 'unique': 'True'})
103
+        },
104
+        'analytics.usersearch': {
105
+            'Meta': {'object_name': 'UserSearch'},
106
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
107
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
108
+            'query': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
109
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['{0}']".format(AUTH_USER_MODEL)})
110
+        },
111
+        'auth.group': {
112
+            'Meta': {'object_name': 'Group'},
113
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
115
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
116
+        },
117
+        'auth.permission': {
118
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
119
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
120
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
121
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
122
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
123
+        },
124
+        AUTH_USER_MODEL: {
125
+            'Meta': {'object_name': AUTH_USER_MODEL_NAME},
126
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
127
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
128
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
129
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
130
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
131
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
132
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
133
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
134
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
135
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
136
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
137
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
138
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
139
+        },
140
+        'catalogue.attributeentity': {
141
+            'Meta': {'object_name': 'AttributeEntity'},
142
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
143
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
144
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
145
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': "orm['catalogue.AttributeEntityType']"})
146
+        },
147
+        'catalogue.attributeentitytype': {
148
+            'Meta': {'object_name': 'AttributeEntityType'},
149
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
150
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
151
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'})
152
+        },
153
+        'catalogue.attributeoption': {
154
+            'Meta': {'object_name': 'AttributeOption'},
155
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': "orm['catalogue.AttributeOptionGroup']"}),
156
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
157
+            'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
158
+        },
159
+        'catalogue.attributeoptiongroup': {
160
+            'Meta': {'object_name': 'AttributeOptionGroup'},
161
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
162
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
163
+        },
164
+        'catalogue.category': {
165
+            'Meta': {'ordering': "['name']", 'object_name': 'Category'},
166
+            'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
167
+            'full_name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}),
168
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
169
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
170
+            'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
171
+            'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
172
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '1024', 'db_index': 'True'})
173
+        },
174
+        'catalogue.option': {
175
+            'Meta': {'object_name': 'Option'},
176
+            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
177
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
178
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
179
+            'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
180
+        },
181
+        'catalogue.product': {
182
+            'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
183
+            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.ProductAttribute']", 'through': "orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
184
+            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Category']", 'through': "orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
185
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
186
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
187
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
188
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
189
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': "orm['catalogue.Product']"}),
190
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductClass']", 'null': 'True'}),
191
+            'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
192
+            'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Product']", 'symmetrical': 'False', 'through': "orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
193
+            'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
194
+            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}),
195
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
196
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
197
+            'upc': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'})
198
+        },
199
+        'catalogue.productattribute': {
200
+            'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
201
+            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
202
+            'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
203
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
204
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
205
+            'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
206
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': "orm['catalogue.ProductClass']"}),
207
+            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
208
+            'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
209
+        },
210
+        'catalogue.productattributevalue': {
211
+            'Meta': {'object_name': 'ProductAttributeValue'},
212
+            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductAttribute']"}),
213
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
214
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
215
+            'value_boolean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
216
+            'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
217
+            'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
218
+            'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
219
+            'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
220
+            'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
221
+            'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
222
+            'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
223
+        },
224
+        'catalogue.productcategory': {
225
+            'Meta': {'ordering': "['-is_canonical']", 'object_name': 'ProductCategory'},
226
+            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Category']"}),
227
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
228
+            'is_canonical': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
229
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
230
+        },
231
+        'catalogue.productclass': {
232
+            'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
233
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
234
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
235
+            'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
236
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'})
237
+        },
238
+        'catalogue.productrecommendation': {
239
+            'Meta': {'object_name': 'ProductRecommendation'},
240
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
241
+            'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': "orm['catalogue.Product']"}),
242
+            'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
243
+            'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
244
+        },
245
+        'contenttypes.contenttype': {
246
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
247
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
248
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
249
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
250
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
251
+        }
252
+    }
253
+
254
+    complete_apps = ['analytics']

oscar/apps/dashboard/ranges/migrations/__init__.py → oscar/apps/analytics/south_migrations/__init__.py View File


+ 1
- 0
oscar/apps/basket/__init__.py View File

1
+default_app_config = 'oscar.apps.basket.config.BasketConfig'

+ 8
- 2
oscar/apps/basket/abstract_models.py View File

4
 from django.db import models
4
 from django.db import models
5
 from django.db.models import Sum
5
 from django.db.models import Sum
6
 from django.conf import settings
6
 from django.conf import settings
7
+from django.utils.encoding import python_2_unicode_compatible
7
 from django.utils.timezone import now
8
 from django.utils.timezone import now
8
 from django.utils.translation import ugettext_lazy as _
9
 from django.utils.translation import ugettext_lazy as _
9
 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
10
 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
14
 from oscar.templatetags.currency_filters import currency
15
 from oscar.templatetags.currency_filters import currency
15
 
16
 
16
 
17
 
18
+@python_2_unicode_compatible
17
 class AbstractBasket(models.Model):
19
 class AbstractBasket(models.Model):
18
     """
20
     """
19
     Basket object
21
     Basket object
55
 
57
 
56
     class Meta:
58
     class Meta:
57
         abstract = True
59
         abstract = True
60
+        app_label = 'basket'
58
         verbose_name = _('Basket')
61
         verbose_name = _('Basket')
59
         verbose_name_plural = _('Baskets')
62
         verbose_name_plural = _('Baskets')
60
 
63
 
73
         self._lines = None
76
         self._lines = None
74
         self.offer_applications = results.OfferApplications()
77
         self.offer_applications = results.OfferApplications()
75
 
78
 
76
-    def __unicode__(self):
79
+    def __str__(self):
77
         return _(
80
         return _(
78
             u"%(status)s basket (owner: %(owner)s, lines: %(num_lines)d)") \
81
             u"%(status)s basket (owner: %(owner)s, lines: %(num_lines)d)") \
79
             % {'status': self.status,
82
             % {'status': self.status,
537
             return 0
540
             return 0
538
 
541
 
539
 
542
 
543
+@python_2_unicode_compatible
540
 class AbstractLine(models.Model):
544
 class AbstractLine(models.Model):
541
     """
545
     """
542
     A line of a basket (product and a quantity)
546
     A line of a basket (product and a quantity)
584
 
588
 
585
     class Meta:
589
     class Meta:
586
         abstract = True
590
         abstract = True
591
+        app_label = 'basket'
587
         unique_together = ("basket", "line_reference")
592
         unique_together = ("basket", "line_reference")
588
         verbose_name = _('Basket line')
593
         verbose_name = _('Basket line')
589
         verbose_name_plural = _('Basket lines')
594
         verbose_name_plural = _('Basket lines')
590
 
595
 
591
-    def __unicode__(self):
596
+    def __str__(self):
592
         return _(
597
         return _(
593
             u"Basket #%(basket_id)d, Product #%(product_id)d, quantity"
598
             u"Basket #%(basket_id)d, Product #%(product_id)d, quantity"
594
             u" %(quantity)d") % {'basket_id': self.basket.pk,
599
             u" %(quantity)d") % {'basket_id': self.basket.pk,
831
 
836
 
832
     class Meta:
837
     class Meta:
833
         abstract = True
838
         abstract = True
839
+        app_label = 'basket'
834
         verbose_name = _('Line attribute')
840
         verbose_name = _('Line attribute')
835
         verbose_name_plural = _('Line attributes')
841
         verbose_name_plural = _('Line attributes')

+ 8
- 0
oscar/apps/basket/config.py View File

1
+from django.apps import AppConfig
2
+from django.utils.translation import ugettext_lazy as _
3
+
4
+
5
+class BasketConfig(AppConfig):
6
+    label = 'basket'
7
+    name = 'oscar.apps.basket'
8
+    verbose_name = _('Basket')

+ 5
- 5
oscar/apps/basket/forms.py View File

135
 
135
 
136
     def __init__(self, basket, product, *args, **kwargs):
136
     def __init__(self, basket, product, *args, **kwargs):
137
         # Note, the product passed in here isn't necessarily the product being
137
         # Note, the product passed in here isn't necessarily the product being
138
-        # added to the basket. For group products, it is the *parent* product
138
+        # added to the basket. For child products, it is the *parent* product
139
         # that gets passed to the form. An optional product_id param is passed
139
         # that gets passed to the form. An optional product_id param is passed
140
         # to indicate the ID of the child product being added to the basket.
140
         # to indicate the ID of the child product being added to the basket.
141
         self.basket = basket
141
         self.basket = basket
142
-        self.base_product = product
142
+        self.parent_product = product
143
 
143
 
144
         super(AddToBasketForm, self).__init__(*args, **kwargs)
144
         super(AddToBasketForm, self).__init__(*args, **kwargs)
145
 
145
 
200
 
200
 
201
     def clean_child_id(self):
201
     def clean_child_id(self):
202
         try:
202
         try:
203
-            child = self.base_product.children.get(
203
+            child = self.parent_product.children.get(
204
                 id=self.cleaned_data['child_id'])
204
                 id=self.cleaned_data['child_id'])
205
         except Product.DoesNotExist:
205
         except Product.DoesNotExist:
206
             raise forms.ValidationError(
206
             raise forms.ValidationError(
235
         """
235
         """
236
         # Note, the child product attribute is saved in the clean_child_id
236
         # Note, the child product attribute is saved in the clean_child_id
237
         # method
237
         # method
238
-        return getattr(self, 'child_product', self.base_product)
238
+        return getattr(self, 'child_product', self.parent_product)
239
 
239
 
240
     def clean(self):
240
     def clean(self):
241
         info = self.basket.strategy.fetch_for_product(self.product)
241
         info = self.basket.strategy.fetch_for_product(self.product)
265
         Return submitted options in a clean format
265
         Return submitted options in a clean format
266
         """
266
         """
267
         options = []
267
         options = []
268
-        for option in self.base_product.options:
268
+        for option in self.parent_product.options:
269
             if option.code in self.cleaned_data:
269
             if option.code in self.cleaned_data:
270
                 options.append({
270
                 options.append({
271
                     'option': option,
271
                     'option': option,

+ 16
- 7
oscar/apps/basket/middleware.py View File

70
                 and request.basket._wrapped is empty):
70
                 and request.basket._wrapped is empty):
71
             return response
71
             return response
72
 
72
 
73
+        cookie_key = self.get_cookie_key(request)
73
         # Check if we need to set a cookie. If the cookies is already available
74
         # Check if we need to set a cookie. If the cookies is already available
74
         # but is set in the cookies_to_delete list then we need to re-set it.
75
         # but is set in the cookies_to_delete list then we need to re-set it.
75
         has_basket_cookie = (
76
         has_basket_cookie = (
76
-            settings.OSCAR_BASKET_COOKIE_OPEN in request.COOKIES
77
-            and settings.OSCAR_BASKET_COOKIE_OPEN not in cookies_to_delete)
77
+            cookie_key in request.COOKIES
78
+            and cookie_key not in cookies_to_delete)
78
 
79
 
79
         # If a basket has had products added to it, but the user is anonymous
80
         # If a basket has had products added to it, but the user is anonymous
80
         # then we need to assign it to a cookie
81
         # then we need to assign it to a cookie
82
                 and not has_basket_cookie):
83
                 and not has_basket_cookie):
83
             cookie = self.get_basket_hash(request.basket.id)
84
             cookie = self.get_basket_hash(request.basket.id)
84
             response.set_cookie(
85
             response.set_cookie(
85
-                settings.OSCAR_BASKET_COOKIE_OPEN, cookie,
86
+                cookie_key, cookie,
86
                 max_age=settings.OSCAR_BASKET_COOKIE_LIFETIME, httponly=True)
87
                 max_age=settings.OSCAR_BASKET_COOKIE_LIFETIME, httponly=True)
87
         return response
88
         return response
88
 
89
 
90
+    def get_cookie_key(self, request):
91
+        """
92
+        Returns the cookie name to use for storing a cookie basket.
93
+
94
+        The method serves as a useful hook in multi-site scenarios where
95
+        different baskets might be needed.
96
+        """
97
+        return settings.OSCAR_BASKET_COOKIE_OPEN
98
+
89
     def process_template_response(self, request, response):
99
     def process_template_response(self, request, response):
90
         if hasattr(response, 'context_data'):
100
         if hasattr(response, 'context_data'):
91
             if response.context_data is None:
101
             if response.context_data is None:
114
             return request._basket_cache
124
             return request._basket_cache
115
 
125
 
116
         manager = Basket.open
126
         manager = Basket.open
117
-        cookie_basket = self.get_cookie_basket(
118
-            settings.OSCAR_BASKET_COOKIE_OPEN, request, manager)
127
+        cookie_key = self.get_cookie_key(request)
128
+        cookie_basket = self.get_cookie_basket(cookie_key, request, manager)
119
 
129
 
120
         if hasattr(request, 'user') and request.user.is_authenticated():
130
         if hasattr(request, 'user') and request.user.is_authenticated():
121
             # Signed-in user: if they have a cookie basket too, it means
131
             # Signed-in user: if they have a cookie basket too, it means
137
 
147
 
138
             if cookie_basket:
148
             if cookie_basket:
139
                 self.merge_baskets(basket, cookie_basket)
149
                 self.merge_baskets(basket, cookie_basket)
140
-                request.cookies_to_delete.append(
141
-                    settings.OSCAR_BASKET_COOKIE_OPEN)
150
+                request.cookies_to_delete.append(cookie_key)
142
 
151
 
143
         elif cookie_basket:
152
         elif cookie_basket:
144
             # Anonymous user with a basket tied to the cookie
153
             # Anonymous user with a basket tied to the cookie

+ 32
- 302
oscar/apps/basket/migrations/0001_initial.py View File

1
-# encoding: utf-8
2
-import datetime
3
-from south.db import db
4
-from south.v2 import SchemaMigration
5
-from django.db import models
6
-from oscar.core.compat import AUTH_USER_MODEL, AUTH_USER_MODEL_NAME
7
-
8
-class Migration(SchemaMigration):
9
-    depends_on = (
10
-        ('catalogue', '0001_initial'),
11
-        ('voucher', '0001_initial'),
12
-        ('offer', '0001_initial'),
13
-    )
14
-
15
-    def forwards(self, orm):
16
-        
17
-        # Adding model 'Basket'
18
-        db.create_table('basket_basket', (
19
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
20
-            ('owner', self.gf('django.db.models.fields.related.ForeignKey')(related_name='baskets', null=True, to=orm[AUTH_USER_MODEL])),
21
-            ('status', self.gf('django.db.models.fields.CharField')(default='Open', max_length=128)),
22
-            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
23
-            ('date_merged', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
24
-            ('date_submitted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
25
-        ))
26
-        db.send_create_signal('basket', ['Basket'])
27
-
28
-        # Adding M2M table for field vouchers on 'Basket'
29
-        db.create_table('basket_basket_vouchers', (
30
-            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
31
-            ('basket', models.ForeignKey(orm['basket.basket'], null=False)),
32
-            ('voucher', models.ForeignKey(orm['voucher.voucher'], null=False))
33
-        ))
34
-        db.create_unique('basket_basket_vouchers', ['basket_id', 'voucher_id'])
35
-
36
-        # Adding model 'Line'
37
-        db.create_table('basket_line', (
38
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
39
-            ('basket', self.gf('django.db.models.fields.related.ForeignKey')(related_name='lines', to=orm['basket.Basket'])),
40
-            ('line_reference', self.gf('django.db.models.fields.SlugField')(max_length=128, db_index=True)),
41
-            ('product', self.gf('django.db.models.fields.related.ForeignKey')(related_name='basket_lines', to=orm['catalogue.Product'])),
42
-            ('quantity', self.gf('django.db.models.fields.PositiveIntegerField')(default=1)),
43
-            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
44
-        ))
45
-        db.send_create_signal('basket', ['Line'])
46
-
47
-        # Adding unique constraint on 'Line', fields ['basket', 'line_reference']
48
-        db.create_unique('basket_line', ['basket_id', 'line_reference'])
49
-
50
-        # Adding model 'LineAttribute'
51
-        db.create_table('basket_lineattribute', (
52
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
53
-            ('line', self.gf('django.db.models.fields.related.ForeignKey')(related_name='attributes', to=orm['basket.Line'])),
54
-            ('option', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Option'])),
55
-            ('value', self.gf('django.db.models.fields.CharField')(max_length=255)),
56
-        ))
57
-        db.send_create_signal('basket', ['LineAttribute'])
58
-
59
-
60
-    def backwards(self, orm):
61
-        
62
-        # Removing unique constraint on 'Line', fields ['basket', 'line_reference']
63
-        db.delete_unique('basket_line', ['basket_id', 'line_reference'])
64
-
65
-        # Deleting model 'Basket'
66
-        db.delete_table('basket_basket')
67
-
68
-        # Removing M2M table for field vouchers on 'Basket'
69
-        db.delete_table('basket_basket_vouchers')
70
-
71
-        # Deleting model 'Line'
72
-        db.delete_table('basket_line')
73
-
74
-        # Deleting model 'LineAttribute'
75
-        db.delete_table('basket_lineattribute')
76
-
77
-
78
-    models = {
79
-        'auth.group': {
80
-            'Meta': {'object_name': 'Group'},
81
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
82
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
83
-            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
84
-        },
85
-        'auth.permission': {
86
-            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
87
-            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
88
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
89
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
90
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
91
-        },
92
-        AUTH_USER_MODEL: {
93
-            'Meta': {'object_name': AUTH_USER_MODEL_NAME},
94
-            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
95
-            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
96
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
97
-            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
98
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
99
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
100
-            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
101
-            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
102
-            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
103
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
104
-            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
105
-            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
106
-            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
107
-        },
108
-        'basket.basket': {
109
-            'Meta': {'object_name': 'Basket'},
110
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
111
-            'date_merged': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
112
-            'date_submitted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
113
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114
-            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'baskets'", 'null': 'True', 'to': "orm['{0}']".format(AUTH_USER_MODEL)}),
115
-            'status': ('django.db.models.fields.CharField', [], {'default': "'Open'", 'max_length': '128'}),
116
-            'vouchers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['voucher.Voucher']", 'null': 'True', 'symmetrical': 'False'})
117
-        },
118
-        'basket.line': {
119
-            'Meta': {'unique_together': "(('basket', 'line_reference'),)", 'object_name': 'Line'},
120
-            'basket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'lines'", 'to': "orm['basket.Basket']"}),
121
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
122
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
123
-            'line_reference': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
124
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'basket_lines'", 'to': "orm['catalogue.Product']"}),
125
-            'quantity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'})
126
-        },
127
-        'basket.lineattribute': {
128
-            'Meta': {'object_name': 'LineAttribute'},
129
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
130
-            'line': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['basket.Line']"}),
131
-            'option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Option']"}),
132
-            'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
133
-        },
134
-        'catalogue.attributeentity': {
135
-            'Meta': {'object_name': 'AttributeEntity'},
136
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
137
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
138
-            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
139
-            'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': "orm['catalogue.AttributeEntityType']"})
140
-        },
141
-        'catalogue.attributeentitytype': {
142
-            'Meta': {'object_name': 'AttributeEntityType'},
143
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
144
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
145
-            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'})
146
-        },
147
-        'catalogue.attributeoption': {
148
-            'Meta': {'object_name': 'AttributeOption'},
149
-            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': "orm['catalogue.AttributeOptionGroup']"}),
150
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
151
-            'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
152
-        },
153
-        'catalogue.attributeoptiongroup': {
154
-            'Meta': {'object_name': 'AttributeOptionGroup'},
155
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
156
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
157
-        },
158
-        'catalogue.category': {
159
-            'Meta': {'ordering': "['name']", 'object_name': 'Category'},
160
-            'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
161
-            'full_name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}),
162
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
163
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
164
-            'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
165
-            'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
166
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '1024', 'db_index': 'True'})
167
-        },
168
-        'catalogue.option': {
169
-            'Meta': {'object_name': 'Option'},
170
-            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
171
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
172
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
173
-            'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
174
-        },
175
-        'catalogue.product': {
176
-            'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
177
-            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.ProductAttribute']", 'through': "orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
178
-            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Category']", 'through': "orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
179
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
180
-            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
181
-            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
182
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
183
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': "orm['catalogue.Product']"}),
184
-            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductClass']", 'null': 'True'}),
185
-            'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
186
-            'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Product']", 'symmetrical': 'False', 'through': "orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
187
-            'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
188
-            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}),
189
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
190
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
191
-            'upc': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'})
192
-        },
193
-        'catalogue.productattribute': {
194
-            'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
195
-            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
196
-            'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
197
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
198
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
199
-            'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
200
-            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': "orm['catalogue.ProductClass']"}),
201
-            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
202
-            'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
203
-        },
204
-        'catalogue.productattributevalue': {
205
-            'Meta': {'object_name': 'ProductAttributeValue'},
206
-            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductAttribute']"}),
207
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
208
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
209
-            'value_boolean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
210
-            'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
211
-            'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
212
-            'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
213
-            'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
214
-            'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
215
-            'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
216
-            'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
217
-        },
218
-        'catalogue.productcategory': {
219
-            'Meta': {'ordering': "['-is_canonical']", 'object_name': 'ProductCategory'},
220
-            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Category']"}),
221
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
222
-            'is_canonical': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
223
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
224
-        },
225
-        'catalogue.productclass': {
226
-            'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
227
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
228
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
229
-            'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
230
-            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'})
231
-        },
232
-        'catalogue.productrecommendation': {
233
-            'Meta': {'object_name': 'ProductRecommendation'},
234
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
235
-            'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': "orm['catalogue.Product']"}),
236
-            'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
237
-            'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
238
-        },
239
-        'contenttypes.contenttype': {
240
-            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
241
-            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
242
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
243
-            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
244
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
245
-        },
246
-        'offer.benefit': {
247
-            'Meta': {'object_name': 'Benefit'},
248
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
249
-            'max_affected_items': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
250
-            'range': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['offer.Range']", 'null': 'True', 'blank': 'True'}),
251
-            'type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
252
-            'value': ('oscar.models.fields.PositiveDecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2', 'blank': 'True'})
253
-        },
254
-        'offer.condition': {
255
-            'Meta': {'object_name': 'Condition'},
256
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
257
-            'range': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['offer.Range']"}),
258
-            'type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
259
-            'value': ('oscar.models.fields.PositiveDecimalField', [], {'max_digits': '12', 'decimal_places': '2'})
260
-        },
261
-        'offer.conditionaloffer': {
262
-            'Meta': {'ordering': "['-priority']", 'object_name': 'ConditionalOffer'},
263
-            'benefit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['offer.Benefit']"}),
264
-            'condition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['offer.Condition']"}),
265
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
266
-            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
267
-            'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
268
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
269
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
270
-            'offer_type': ('django.db.models.fields.CharField', [], {'default': "'Site'", 'max_length': '128'}),
271
-            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
272
-            'redirect_url': ('oscar.models.fields.ExtendedURLField', [], {'max_length': '200', 'blank': 'True'}),
273
-            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
274
-            'total_discount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '12', 'decimal_places': '2'})
275
-        },
276
-        'offer.range': {
277
-            'Meta': {'object_name': 'Range'},
278
-            'classes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'classes'", 'blank': 'True', 'to': "orm['catalogue.ProductClass']"}),
279
-            'excluded_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'excludes'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
280
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
281
-            'included_categories': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'includes'", 'blank': 'True', 'to': "orm['catalogue.Category']"}),
282
-            'included_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'includes'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
283
-            'includes_all_products': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
284
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
285
-        },
286
-        'voucher.voucher': {
287
-            'Meta': {'object_name': 'Voucher'},
288
-            'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
289
-            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
290
-            'end_date': ('django.db.models.fields.DateField', [], {}),
291
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
292
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
293
-            'num_basket_additions': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
294
-            'num_orders': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
295
-            'offers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'vouchers'", 'symmetrical': 'False', 'to': "orm['offer.ConditionalOffer']"}),
296
-            'start_date': ('django.db.models.fields.DateField', [], {}),
297
-            'total_discount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '12', 'decimal_places': '2'}),
298
-            'usage': ('django.db.models.fields.CharField', [], {'default': "'Multi-use'", 'max_length': '128'})
299
-        }
300
-    }
301
-
302
-    complete_apps = ['basket']
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+from django.conf import settings
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12
+    ]
13
+
14
+    operations = [
15
+        migrations.CreateModel(
16
+            name='Basket',
17
+            fields=[
18
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
19
+                ('status', models.CharField(verbose_name='Status', choices=[('Open', 'Open - currently active'), ('Merged', 'Merged - superceded by another basket'), ('Saved', 'Saved - for items to be purchased later'), ('Frozen', 'Frozen - the basket cannot be modified'), ('Submitted', 'Submitted - has been ordered at the checkout')], default='Open', max_length=128)),
20
+                ('date_created', models.DateTimeField(verbose_name='Date created', auto_now_add=True)),
21
+                ('date_merged', models.DateTimeField(verbose_name='Date merged', blank=True, null=True)),
22
+                ('date_submitted', models.DateTimeField(verbose_name='Date submitted', blank=True, null=True)),
23
+                ('owner', models.ForeignKey(verbose_name='Owner', to=settings.AUTH_USER_MODEL, null=True)),
24
+            ],
25
+            options={
26
+                'verbose_name': 'Basket',
27
+                'verbose_name_plural': 'Baskets',
28
+                'abstract': False,
29
+            },
30
+            bases=(models.Model,),
31
+        ),
32
+    ]

+ 63
- 0
oscar/apps/basket/migrations/0002_auto_20140805_1510.py View File

1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('catalogue', '0001_initial'),
11
+        ('voucher', '0001_initial'),
12
+        ('basket', '0001_initial'),
13
+        ('partner', '0001_initial'),
14
+    ]
15
+
16
+    operations = [
17
+        migrations.AddField(
18
+            model_name='basket',
19
+            name='vouchers',
20
+            field=models.ManyToManyField(verbose_name='Vouchers', blank=True, to='voucher.Voucher', null=True),
21
+            preserve_default=True,
22
+        ),
23
+        migrations.CreateModel(
24
+            name='Line',
25
+            fields=[
26
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
27
+                ('line_reference', models.SlugField(verbose_name='Line Reference', max_length=128)),
28
+                ('quantity', models.PositiveIntegerField(verbose_name='Quantity', default=1)),
29
+                ('price_currency', models.CharField(verbose_name='Currency', default='GBP', max_length=12)),
30
+                ('price_excl_tax', models.DecimalField(verbose_name='Price excl. Tax', max_digits=12, decimal_places=2, null=True)),
31
+                ('price_incl_tax', models.DecimalField(verbose_name='Price incl. Tax', max_digits=12, decimal_places=2, null=True)),
32
+                ('date_created', models.DateTimeField(verbose_name='Date Created', auto_now_add=True)),
33
+                ('basket', models.ForeignKey(verbose_name='Basket', to='basket.Basket')),
34
+                ('product', models.ForeignKey(verbose_name='Product', to='catalogue.Product')),
35
+                ('stockrecord', models.ForeignKey(to='partner.StockRecord')),
36
+            ],
37
+            options={
38
+                'verbose_name': 'Basket line',
39
+                'verbose_name_plural': 'Basket lines',
40
+                'abstract': False,
41
+            },
42
+            bases=(models.Model,),
43
+        ),
44
+        migrations.AlterUniqueTogether(
45
+            name='line',
46
+            unique_together=set([('basket', 'line_reference')]),
47
+        ),
48
+        migrations.CreateModel(
49
+            name='LineAttribute',
50
+            fields=[
51
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
52
+                ('value', models.CharField(verbose_name='Value', max_length=255)),
53
+                ('line', models.ForeignKey(verbose_name='Line', to='basket.Line')),
54
+                ('option', models.ForeignKey(verbose_name='Option', to='catalogue.Option')),
55
+            ],
56
+            options={
57
+                'verbose_name': 'Line attribute',
58
+                'verbose_name_plural': 'Line attributes',
59
+                'abstract': False,
60
+            },
61
+            bases=(models.Model,),
62
+        ),
63
+    ]

+ 10
- 6
oscar/apps/basket/models.py View File

1
+from oscar.core.loading import is_model_registered
1
 from oscar.apps.basket.abstract_models import (
2
 from oscar.apps.basket.abstract_models import (
2
     AbstractBasket, AbstractLine, AbstractLineAttribute)
3
     AbstractBasket, AbstractLine, AbstractLineAttribute)
3
 
4
 
6
     pass
7
     pass
7
 
8
 
8
 
9
 
9
-class Basket(AbstractBasket):
10
-    pass
10
+if not is_model_registered('basket', 'Basket'):
11
+    class Basket(AbstractBasket):
12
+        pass
11
 
13
 
12
 
14
 
13
-class Line(AbstractLine):
14
-    pass
15
+if not is_model_registered('basket', 'Line'):
16
+    class Line(AbstractLine):
17
+        pass
15
 
18
 
16
 
19
 
17
-class LineAttribute(AbstractLineAttribute):
18
-    pass
20
+if not is_model_registered('basket', 'LineAttribute'):
21
+    class LineAttribute(AbstractLineAttribute):
22
+        pass

+ 302
- 0
oscar/apps/basket/south_migrations/0001_initial.py View File

1
+# encoding: utf-8
2
+import datetime
3
+from south.db import db
4
+from south.v2 import SchemaMigration
5
+from django.db import models
6
+from oscar.core.compat import AUTH_USER_MODEL, AUTH_USER_MODEL_NAME
7
+
8
+class Migration(SchemaMigration):
9
+    depends_on = (
10
+        ('catalogue', '0001_initial'),
11
+        ('voucher', '0001_initial'),
12
+        ('offer', '0001_initial'),
13
+    )
14
+
15
+    def forwards(self, orm):
16
+        
17
+        # Adding model 'Basket'
18
+        db.create_table('basket_basket', (
19
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
20
+            ('owner', self.gf('django.db.models.fields.related.ForeignKey')(related_name='baskets', null=True, to=orm[AUTH_USER_MODEL])),
21
+            ('status', self.gf('django.db.models.fields.CharField')(default='Open', max_length=128)),
22
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
23
+            ('date_merged', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
24
+            ('date_submitted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
25
+        ))
26
+        db.send_create_signal('basket', ['Basket'])
27
+
28
+        # Adding M2M table for field vouchers on 'Basket'
29
+        db.create_table('basket_basket_vouchers', (
30
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
31
+            ('basket', models.ForeignKey(orm['basket.basket'], null=False)),
32
+            ('voucher', models.ForeignKey(orm['voucher.voucher'], null=False))
33
+        ))
34
+        db.create_unique('basket_basket_vouchers', ['basket_id', 'voucher_id'])
35
+
36
+        # Adding model 'Line'
37
+        db.create_table('basket_line', (
38
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
39
+            ('basket', self.gf('django.db.models.fields.related.ForeignKey')(related_name='lines', to=orm['basket.Basket'])),
40
+            ('line_reference', self.gf('django.db.models.fields.SlugField')(max_length=128, db_index=True)),
41
+            ('product', self.gf('django.db.models.fields.related.ForeignKey')(related_name='basket_lines', to=orm['catalogue.Product'])),
42
+            ('quantity', self.gf('django.db.models.fields.PositiveIntegerField')(default=1)),
43
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
44
+        ))
45
+        db.send_create_signal('basket', ['Line'])
46
+
47
+        # Adding unique constraint on 'Line', fields ['basket', 'line_reference']
48
+        db.create_unique('basket_line', ['basket_id', 'line_reference'])
49
+
50
+        # Adding model 'LineAttribute'
51
+        db.create_table('basket_lineattribute', (
52
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
53
+            ('line', self.gf('django.db.models.fields.related.ForeignKey')(related_name='attributes', to=orm['basket.Line'])),
54
+            ('option', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Option'])),
55
+            ('value', self.gf('django.db.models.fields.CharField')(max_length=255)),
56
+        ))
57
+        db.send_create_signal('basket', ['LineAttribute'])
58
+
59
+
60
+    def backwards(self, orm):
61
+        
62
+        # Removing unique constraint on 'Line', fields ['basket', 'line_reference']
63
+        db.delete_unique('basket_line', ['basket_id', 'line_reference'])
64
+
65
+        # Deleting model 'Basket'
66
+        db.delete_table('basket_basket')
67
+
68
+        # Removing M2M table for field vouchers on 'Basket'
69
+        db.delete_table('basket_basket_vouchers')
70
+
71
+        # Deleting model 'Line'
72
+        db.delete_table('basket_line')
73
+
74
+        # Deleting model 'LineAttribute'
75
+        db.delete_table('basket_lineattribute')
76
+
77
+
78
+    models = {
79
+        'auth.group': {
80
+            'Meta': {'object_name': 'Group'},
81
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
82
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
83
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
84
+        },
85
+        'auth.permission': {
86
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
87
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
88
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
89
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
90
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
91
+        },
92
+        AUTH_USER_MODEL: {
93
+            'Meta': {'object_name': AUTH_USER_MODEL_NAME},
94
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
95
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
96
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
97
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
98
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
99
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
100
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
101
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
102
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
103
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
104
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
105
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
106
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
107
+        },
108
+        'basket.basket': {
109
+            'Meta': {'object_name': 'Basket'},
110
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
111
+            'date_merged': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
112
+            'date_submitted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
113
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'baskets'", 'null': 'True', 'to': "orm['{0}']".format(AUTH_USER_MODEL)}),
115
+            'status': ('django.db.models.fields.CharField', [], {'default': "'Open'", 'max_length': '128'}),
116
+            'vouchers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['voucher.Voucher']", 'null': 'True', 'symmetrical': 'False'})
117
+        },
118
+        'basket.line': {
119
+            'Meta': {'unique_together': "(('basket', 'line_reference'),)", 'object_name': 'Line'},
120
+            'basket': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'lines'", 'to': "orm['basket.Basket']"}),
121
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
122
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
123
+            'line_reference': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
124
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'basket_lines'", 'to': "orm['catalogue.Product']"}),
125
+            'quantity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'})
126
+        },
127
+        'basket.lineattribute': {
128
+            'Meta': {'object_name': 'LineAttribute'},
129
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
130
+            'line': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['basket.Line']"}),
131
+            'option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Option']"}),
132
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
133
+        },
134
+        'catalogue.attributeentity': {
135
+            'Meta': {'object_name': 'AttributeEntity'},
136
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
137
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
138
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
139
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': "orm['catalogue.AttributeEntityType']"})
140
+        },
141
+        'catalogue.attributeentitytype': {
142
+            'Meta': {'object_name': 'AttributeEntityType'},
143
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
144
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
145
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'})
146
+        },
147
+        'catalogue.attributeoption': {
148
+            'Meta': {'object_name': 'AttributeOption'},
149
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': "orm['catalogue.AttributeOptionGroup']"}),
150
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
151
+            'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
152
+        },
153
+        'catalogue.attributeoptiongroup': {
154
+            'Meta': {'object_name': 'AttributeOptionGroup'},
155
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
156
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
157
+        },
158
+        'catalogue.category': {
159
+            'Meta': {'ordering': "['name']", 'object_name': 'Category'},
160
+            'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
161
+            'full_name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}),
162
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
163
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
164
+            'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
165
+            'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
166
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '1024', 'db_index': 'True'})
167
+        },
168
+        'catalogue.option': {
169
+            'Meta': {'object_name': 'Option'},
170
+            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
171
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
172
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
173
+            'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
174
+        },
175
+        'catalogue.product': {
176
+            'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
177
+            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.ProductAttribute']", 'through': "orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
178
+            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Category']", 'through': "orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
179
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
180
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
181
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
182
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
183
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': "orm['catalogue.Product']"}),
184
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductClass']", 'null': 'True'}),
185
+            'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
186
+            'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Product']", 'symmetrical': 'False', 'through': "orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
187
+            'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
188
+            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}),
189
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
190
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
191
+            'upc': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'})
192
+        },
193
+        'catalogue.productattribute': {
194
+            'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
195
+            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
196
+            'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
197
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
198
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
199
+            'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
200
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': "orm['catalogue.ProductClass']"}),
201
+            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
202
+            'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
203
+        },
204
+        'catalogue.productattributevalue': {
205
+            'Meta': {'object_name': 'ProductAttributeValue'},
206
+            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductAttribute']"}),
207
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
208
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
209
+            'value_boolean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
210
+            'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
211
+            'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
212
+            'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
213
+            'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
214
+            'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
215
+            'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
216
+            'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
217
+        },
218
+        'catalogue.productcategory': {
219
+            'Meta': {'ordering': "['-is_canonical']", 'object_name': 'ProductCategory'},
220
+            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Category']"}),
221
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
222
+            'is_canonical': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
223
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
224
+        },
225
+        'catalogue.productclass': {
226
+            'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
227
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
228
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
229
+            'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
230
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'})
231
+        },
232
+        'catalogue.productrecommendation': {
233
+            'Meta': {'object_name': 'ProductRecommendation'},
234
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
235
+            'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': "orm['catalogue.Product']"}),
236
+            'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
237
+            'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
238
+        },
239
+        'contenttypes.contenttype': {
240
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
241
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
242
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
243
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
244
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
245
+        },
246
+        'offer.benefit': {
247
+            'Meta': {'object_name': 'Benefit'},
248
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
249
+            'max_affected_items': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
250
+            'range': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['offer.Range']", 'null': 'True', 'blank': 'True'}),
251
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
252
+            'value': ('oscar.models.fields.PositiveDecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2', 'blank': 'True'})
253
+        },
254
+        'offer.condition': {
255
+            'Meta': {'object_name': 'Condition'},
256
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
257
+            'range': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['offer.Range']"}),
258
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
259
+            'value': ('oscar.models.fields.PositiveDecimalField', [], {'max_digits': '12', 'decimal_places': '2'})
260
+        },
261
+        'offer.conditionaloffer': {
262
+            'Meta': {'ordering': "['-priority']", 'object_name': 'ConditionalOffer'},
263
+            'benefit': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['offer.Benefit']"}),
264
+            'condition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['offer.Condition']"}),
265
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
266
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
267
+            'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
268
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
269
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
270
+            'offer_type': ('django.db.models.fields.CharField', [], {'default': "'Site'", 'max_length': '128'}),
271
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
272
+            'redirect_url': ('oscar.models.fields.ExtendedURLField', [], {'max_length': '200', 'blank': 'True'}),
273
+            'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
274
+            'total_discount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '12', 'decimal_places': '2'})
275
+        },
276
+        'offer.range': {
277
+            'Meta': {'object_name': 'Range'},
278
+            'classes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'classes'", 'blank': 'True', 'to': "orm['catalogue.ProductClass']"}),
279
+            'excluded_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'excludes'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
280
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
281
+            'included_categories': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'includes'", 'blank': 'True', 'to': "orm['catalogue.Category']"}),
282
+            'included_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'includes'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
283
+            'includes_all_products': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
284
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
285
+        },
286
+        'voucher.voucher': {
287
+            'Meta': {'object_name': 'Voucher'},
288
+            'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
289
+            'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
290
+            'end_date': ('django.db.models.fields.DateField', [], {}),
291
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
292
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
293
+            'num_basket_additions': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
294
+            'num_orders': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
295
+            'offers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'vouchers'", 'symmetrical': 'False', 'to': "orm['offer.ConditionalOffer']"}),
296
+            'start_date': ('django.db.models.fields.DateField', [], {}),
297
+            'total_discount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '12', 'decimal_places': '2'}),
298
+            'usage': ('django.db.models.fields.CharField', [], {'default': "'Multi-use'", 'max_length': '128'})
299
+        }
300
+    }
301
+
302
+    complete_apps = ['basket']

oscar/apps/basket/migrations/0002_auto__add_field_line_price_incl_tax.py → oscar/apps/basket/south_migrations/0002_auto__add_field_line_price_incl_tax.py View File


oscar/apps/basket/migrations/0003_auto__add_field_line_price_excl_tax.py → oscar/apps/basket/south_migrations/0003_auto__add_field_line_price_excl_tax.py View File


oscar/apps/basket/migrations/0004_auto__add_field_line_stockrecord.py → oscar/apps/basket/south_migrations/0004_auto__add_field_line_stockrecord.py View File


oscar/apps/basket/migrations/0005_auto__add_field_line_price_currency.py → oscar/apps/basket/south_migrations/0005_auto__add_field_line_price_currency.py View File


oscar/apps/basket/migrations/0006_auto__chg_field_line_stockrecord.py → oscar/apps/basket/south_migrations/0006_auto__chg_field_line_stockrecord.py View File


sites/us/apps/checkout/migrations/__init__.py → oscar/apps/basket/south_migrations/__init__.py View File


+ 34
- 38
oscar/apps/basket/views.py View File

1
 import json
1
 import json
2
-from six.moves.urllib import parse
3
 
2
 
3
+from django import shortcuts
4
 from django.contrib import messages
4
 from django.contrib import messages
5
+from django.shortcuts import redirect
5
 from django.template.loader import render_to_string
6
 from django.template.loader import render_to_string
6
 from django.template import RequestContext
7
 from django.template import RequestContext
7
-from django.core.urlresolvers import reverse, resolve
8
-from django.http import HttpResponseRedirect, Http404, HttpResponse
8
+from django.core.urlresolvers import reverse
9
+from django.http import HttpResponse
9
 from django.views.generic import FormView, View
10
 from django.views.generic import FormView, View
11
+from django.utils.http import is_safe_url
10
 from django.utils.translation import ugettext_lazy as _
12
 from django.utils.translation import ugettext_lazy as _
11
 from django.core.exceptions import ObjectDoesNotExist
13
 from django.core.exceptions import ObjectDoesNotExist
12
-from django import shortcuts
14
+
13
 from extra_views import ModelFormSetView
15
 from extra_views import ModelFormSetView
14
 
16
 
15
 from oscar.core import ajax
17
 from oscar.core import ajax
18
+from oscar.core.utils import redirect_to_referrer, safe_referrer
16
 from oscar.apps.basket import signals
19
 from oscar.apps.basket import signals
17
 from oscar.core.loading import get_class, get_classes, get_model
20
 from oscar.core.loading import get_class, get_classes, get_model
21
+
18
 Applicator = get_class('offer.utils', 'Applicator')
22
 Applicator = get_class('offer.utils', 'Applicator')
19
 (BasketLineFormSet, BasketLineForm, AddToBasketForm, BasketVoucherForm,
23
 (BasketLineFormSet, BasketLineForm, AddToBasketForm, BasketVoucherForm,
20
  SavedLineFormSet, SavedLineForm) \
24
  SavedLineFormSet, SavedLineForm) \
121
 
125
 
122
     def get_upsell_messages(self, basket):
126
     def get_upsell_messages(self, basket):
123
         offers = Applicator().get_offers(self.request, basket)
127
         offers = Applicator().get_offers(self.request, basket)
124
-        applied_offers = basket.offer_applications.offers.values()
128
+        applied_offers = list(basket.offer_applications.offers.values())
125
         msgs = []
129
         msgs = []
126
         for offer in offers:
130
         for offer in offers:
127
             if offer.is_condition_partially_satisfied(basket) \
131
             if offer.is_condition_partially_satisfied(basket) \
132
                 msgs.append(data)
136
                 msgs.append(data)
133
         return msgs
137
         return msgs
134
 
138
 
139
+    def get_basket_voucher_form(self):
140
+        """
141
+        This is a separate method so that it's easy to e.g. not return a form
142
+        if there are no vouchers available.
143
+        """
144
+        return BasketVoucherForm()
145
+
135
     def get_context_data(self, **kwargs):
146
     def get_context_data(self, **kwargs):
136
         context = super(BasketView, self).get_context_data(**kwargs)
147
         context = super(BasketView, self).get_context_data(**kwargs)
137
-        context['voucher_form'] = BasketVoucherForm()
148
+        context['voucher_form'] = self.get_basket_voucher_form()
138
 
149
 
139
         # Shipping information is included to give an idea of the total order
150
         # Shipping information is included to give an idea of the total order
140
         # cost.  It is also important for PayPal Express where the customer
151
         # cost.  It is also important for PayPal Express where the customer
176
         return context
187
         return context
177
 
188
 
178
     def get_success_url(self):
189
     def get_success_url(self):
179
-        return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
190
+        return safe_referrer(self.request.META, 'basket:summary')
180
 
191
 
181
     def formset_valid(self, formset):
192
     def formset_valid(self, formset):
182
         # Store offers before any changes are made so we can inform the user of
193
         # Store offers before any changes are made so we can inform the user of
206
                     msg = _("You can't save an item for later if you're "
217
                     msg = _("You can't save an item for later if you're "
207
                             "not logged in!")
218
                             "not logged in!")
208
                     flash_messages.error(msg)
219
                     flash_messages.error(msg)
209
-                    return HttpResponseRedirect(self.get_success_url())
220
+                    return redirect(self.get_success_url())
210
 
221
 
211
         if save_for_later:
222
         if save_for_later:
212
             # No need to call super if we're moving lines to the saved basket
223
             # No need to call super if we're moving lines to the saved basket
213
-            response = HttpResponseRedirect(self.get_success_url())
224
+            response = redirect(self.get_success_url())
214
         else:
225
         else:
215
             # Save changes to basket as per normal
226
             # Save changes to basket as per normal
216
             response = super(BasketView, self).formset_valid(formset)
227
             response = super(BasketView, self).formset_valid(formset)
305
         clean_msgs = [m.replace('* ', '') for m in msgs if m.startswith('* ')]
316
         clean_msgs = [m.replace('* ', '') for m in msgs if m.startswith('* ')]
306
         messages.error(self.request, ",".join(clean_msgs))
317
         messages.error(self.request, ",".join(clean_msgs))
307
 
318
 
308
-        return HttpResponseRedirect(
309
-            self.request.META.get('HTTP_REFERER', reverse('basket:summary')))
319
+        return redirect_to_referrer(self.request.META, 'basket:summary')
310
 
320
 
311
     def form_valid(self, form):
321
     def form_valid(self, form):
312
         offers_before = self.request.basket.applied_offers()
322
         offers_before = self.request.basket.applied_offers()
335
              'quantity': form.cleaned_data['quantity']})
345
              'quantity': form.cleaned_data['quantity']})
336
 
346
 
337
     def get_success_url(self):
347
     def get_success_url(self):
338
-        url = None
339
-        if self.request.POST.get('next'):
340
-            url = self.request.POST.get('next')
341
-        elif 'HTTP_REFERER' in self.request.META:
342
-            url = self.request.META['HTTP_REFERER']
343
-        if url:
344
-            # We only allow internal URLs so we see if the url resolves
345
-            try:
346
-                resolve(parse.urlparse(url).path)
347
-            except Http404:
348
-                url = None
349
-        if url is None:
350
-            url = reverse('basket:summary')
351
-        return url
348
+        post_url = self.request.POST.get('next')
349
+        if post_url and is_safe_url(post_url):
350
+            return post_url
351
+        return safe_referrer(self.request.META, 'basket:summary')
352
 
352
 
353
 
353
 
354
 class VoucherAddView(FormView):
354
 class VoucherAddView(FormView):
357
     add_signal = signals.voucher_addition
357
     add_signal = signals.voucher_addition
358
 
358
 
359
     def get(self, request, *args, **kwargs):
359
     def get(self, request, *args, **kwargs):
360
-        return HttpResponseRedirect(reverse('basket:summary'))
360
+        return redirect('basket:summary')
361
 
361
 
362
     def apply_voucher_to_basket(self, voucher):
362
     def apply_voucher_to_basket(self, voucher):
363
         if not voucher.is_active():
363
         if not voucher.is_active():
402
     def form_valid(self, form):
402
     def form_valid(self, form):
403
         code = form.cleaned_data['code']
403
         code = form.cleaned_data['code']
404
         if not self.request.basket.id:
404
         if not self.request.basket.id:
405
-            return HttpResponseRedirect(
406
-                self.request.META.get('HTTP_REFERER',
407
-                                      reverse('basket:summary')))
405
+            return redirect_to_referrer(self.request.META, 'basket:summary')
408
         if self.request.basket.contains_voucher(code):
406
         if self.request.basket.contains_voucher(code):
409
             messages.error(
407
             messages.error(
410
                 self.request,
408
                 self.request,
420
                         'code': code})
418
                         'code': code})
421
             else:
419
             else:
422
                 self.apply_voucher_to_basket(voucher)
420
                 self.apply_voucher_to_basket(voucher)
423
-        return HttpResponseRedirect(
424
-            self.request.META.get('HTTP_REFERER', reverse('basket:summary')))
421
+        return redirect_to_referrer(self.request.META, 'basket:summary')
425
 
422
 
426
     def form_invalid(self, form):
423
     def form_invalid(self, form):
427
         messages.error(self.request, _("Please enter a voucher code"))
424
         messages.error(self.request, _("Please enter a voucher code"))
428
-        return HttpResponseRedirect(reverse('basket:summary') + '#voucher')
425
+        return redirect(reverse('basket:summary') + '#voucher')
429
 
426
 
430
 
427
 
431
 class VoucherRemoveView(View):
428
 class VoucherRemoveView(View):
434
     http_method_names = ['post']
431
     http_method_names = ['post']
435
 
432
 
436
     def post(self, request, *args, **kwargs):
433
     def post(self, request, *args, **kwargs):
437
-        response = HttpResponseRedirect(reverse('basket:summary'))
434
+        response = redirect('basket:summary')
438
 
435
 
439
         voucher_id = kwargs['pk']
436
         voucher_id = kwargs['pk']
440
         if not request.basket.id:
437
         if not request.basket.id:
465
     can_delete = True
462
     can_delete = True
466
 
463
 
467
     def get(self, request, *args, **kwargs):
464
     def get(self, request, *args, **kwargs):
468
-        return HttpResponseRedirect(reverse('basket:summary'))
465
+        return redirect('basket:summary')
469
 
466
 
470
     def get_queryset(self):
467
     def get_queryset(self):
471
         try:
468
         try:
477
             return []
474
             return []
478
 
475
 
479
     def get_success_url(self):
476
     def get_success_url(self):
480
-        return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
477
+        return safe_referrer(self.request.META, 'basket:summary')
481
 
478
 
482
     def get_formset_kwargs(self):
479
     def get_formset_kwargs(self):
483
         kwargs = super(SavedView, self).get_formset_kwargs()
480
         kwargs = super(SavedView, self).get_formset_kwargs()
504
             # As we're changing the basket, we need to check if it qualifies
501
             # As we're changing the basket, we need to check if it qualifies
505
             # for any new offers.
502
             # for any new offers.
506
             apply_messages(self.request, offers_before)
503
             apply_messages(self.request, offers_before)
507
-            response = HttpResponseRedirect(self.get_success_url())
504
+            response = redirect(self.get_success_url())
508
         else:
505
         else:
509
             response = super(SavedView, self).formset_valid(formset)
506
             response = super(SavedView, self).formset_valid(formset)
510
         return response
507
         return response
515
             '\n'.join(
512
             '\n'.join(
516
                 error for ed in formset.errors for el
513
                 error for ed in formset.errors for el
517
                 in ed.values() for error in el))
514
                 in ed.values() for error in el))
518
-        return HttpResponseRedirect(
519
-            self.request.META.get('HTTP_REFERER', reverse('basket:summary')))
515
+        return redirect_to_referrer(self.request.META, 'basket:summary')

+ 1
- 0
oscar/apps/catalogue/__init__.py View File

1
+default_app_config = 'oscar.apps.catalogue.config.CatalogueConfig'

+ 172
- 54
oscar/apps/catalogue/abstract_models.py View File

1
-from django.core.urlresolvers import reverse
2
 import os
1
 import os
3
-import six
2
+from django.utils import six
4
 from itertools import chain
3
 from itertools import chain
5
 from datetime import datetime, date
4
 from datetime import datetime, date
6
 import logging
5
 import logging
11
 from django.contrib.staticfiles.finders import find
10
 from django.contrib.staticfiles.finders import find
12
 from django.core.exceptions import ValidationError, ImproperlyConfigured
11
 from django.core.exceptions import ValidationError, ImproperlyConfigured
13
 from django.core.files.base import File
12
 from django.core.files.base import File
13
+from django.core.urlresolvers import reverse
14
 from django.core.validators import RegexValidator
14
 from django.core.validators import RegexValidator
15
 from django.db import models
15
 from django.db import models
16
 from django.db.models import Sum, Count
16
 from django.db.models import Sum, Count
17
+from django.utils.encoding import python_2_unicode_compatible
17
 from django.utils.translation import ugettext_lazy as _, pgettext_lazy
18
 from django.utils.translation import ugettext_lazy as _, pgettext_lazy
18
 from django.utils.functional import cached_property
19
 from django.utils.functional import cached_property
19
 from django.contrib.contenttypes.generic import GenericForeignKey
20
 from django.contrib.contenttypes.generic import GenericForeignKey
20
 from django.contrib.contenttypes.models import ContentType
21
 from django.contrib.contenttypes.models import ContentType
21
 
22
 
22
 from treebeard.mp_tree import MP_Node
23
 from treebeard.mp_tree import MP_Node
23
-from oscar.core.decorators import deprecated
24
 
24
 
25
+from oscar.core.decorators import deprecated
25
 from oscar.core.utils import slugify
26
 from oscar.core.utils import slugify
26
 from oscar.core.loading import get_classes, get_model
27
 from oscar.core.loading import get_classes, get_model
27
 from oscar.models.fields import NullCharField, AutoSlugField
28
 from oscar.models.fields import NullCharField, AutoSlugField
30
     'catalogue.managers', ['ProductManager', 'BrowsableProductManager'])
31
     'catalogue.managers', ['ProductManager', 'BrowsableProductManager'])
31
 
32
 
32
 
33
 
34
+@python_2_unicode_compatible
33
 class AbstractProductClass(models.Model):
35
 class AbstractProductClass(models.Model):
34
     """
36
     """
35
     Used for defining options and attributes for a subset of products.
37
     Used for defining options and attributes for a subset of products.
61
 
63
 
62
     class Meta:
64
     class Meta:
63
         abstract = True
65
         abstract = True
66
+        app_label = 'catalogue'
64
         ordering = ['name']
67
         ordering = ['name']
65
-        verbose_name = _("Product Class")
66
-        verbose_name_plural = _("Product Classes")
68
+        verbose_name = _("Product class")
69
+        verbose_name_plural = _("Product classes")
67
 
70
 
68
-    def __unicode__(self):
71
+    def __str__(self):
69
         return self.name
72
         return self.name
70
 
73
 
74
+    @property
75
+    def has_attributes(self):
76
+        return self.attributes.exists()
77
+
71
 
78
 
79
+@python_2_unicode_compatible
72
 class AbstractCategory(MP_Node):
80
 class AbstractCategory(MP_Node):
73
     """
81
     """
74
     A product category. Merely used for navigational purposes; has no
82
     A product category. Merely used for navigational purposes; has no
88
     _slug_separator = '/'
96
     _slug_separator = '/'
89
     _full_name_separator = ' > '
97
     _full_name_separator = ' > '
90
 
98
 
91
-    def __unicode__(self):
99
+    def __str__(self):
92
         return self.full_name
100
         return self.full_name
93
 
101
 
94
     def update_slug(self, commit=True):
102
     def update_slug(self, commit=True):
172
 
180
 
173
     class Meta:
181
     class Meta:
174
         abstract = True
182
         abstract = True
183
+        app_label = 'catalogue'
175
         ordering = ['full_name']
184
         ordering = ['full_name']
176
         verbose_name = _('Category')
185
         verbose_name = _('Category')
177
         verbose_name_plural = _('Categories')
186
         verbose_name_plural = _('Categories')
183
         return self.get_children().count()
192
         return self.get_children().count()
184
 
193
 
185
 
194
 
195
+@python_2_unicode_compatible
186
 class AbstractProductCategory(models.Model):
196
 class AbstractProductCategory(models.Model):
187
     """
197
     """
188
     Joining model between products and categories. Exists to allow customising.
198
     Joining model between products and categories. Exists to allow customising.
193
 
203
 
194
     class Meta:
204
     class Meta:
195
         abstract = True
205
         abstract = True
206
+        app_label = 'catalogue'
196
         ordering = ['product', 'category']
207
         ordering = ['product', 'category']
197
-        verbose_name = _('Product Category')
198
-        verbose_name_plural = _('Product Categories')
199
         unique_together = ('product', 'category')
208
         unique_together = ('product', 'category')
209
+        verbose_name = _('Product category')
210
+        verbose_name_plural = _('Product categories')
200
 
211
 
201
-    def __unicode__(self):
212
+    def __str__(self):
202
         return u"<productcategory for product '%s'>" % self.product
213
         return u"<productcategory for product '%s'>" % self.product
203
 
214
 
204
 
215
 
216
+@python_2_unicode_compatible
205
 class AbstractProduct(models.Model):
217
 class AbstractProduct(models.Model):
206
     """
218
     """
207
     The base product object
219
     The base product object
243
                     " this product)."))
255
                     " this product)."))
244
 
256
 
245
     # Title is mandatory for canonical products but optional for child products
257
     # Title is mandatory for canonical products but optional for child products
246
-    title = models.CharField(_('Product title'), max_length=255, blank=True)
258
+    title = models.CharField(pgettext_lazy(u'Product title', u'Title'),
259
+                             max_length=255, blank=True)
247
     slug = models.SlugField(_('Slug'), max_length=255, unique=False)
260
     slug = models.SlugField(_('Slug'), max_length=255, unique=False)
248
     description = models.TextField(_('Description'), blank=True)
261
     description = models.TextField(_('Description'), blank=True)
249
 
262
 
251
     #: None for child products, they inherit their parent's product class
264
     #: None for child products, they inherit their parent's product class
252
     product_class = models.ForeignKey(
265
     product_class = models.ForeignKey(
253
         'catalogue.ProductClass', null=True, on_delete=models.PROTECT,
266
         'catalogue.ProductClass', null=True, on_delete=models.PROTECT,
254
-        verbose_name=_('Product Type'), related_name="products",
267
+        verbose_name=_('Product type'), related_name="products",
255
         help_text=_("Choose what type of product this is"))
268
         help_text=_("Choose what type of product this is"))
256
     attributes = models.ManyToManyField(
269
     attributes = models.ManyToManyField(
257
         'catalogue.ProductAttribute',
270
         'catalogue.ProductAttribute',
289
     #: Determines if a product may be used in an offer. It is illegal to
302
     #: Determines if a product may be used in an offer. It is illegal to
290
     #: discount some types of product (e.g. ebooks) and this field helps
303
     #: discount some types of product (e.g. ebooks) and this field helps
291
     #: merchants from avoiding discounting such products
304
     #: merchants from avoiding discounting such products
305
+    #: Note that this flag is ignored for child products; they inherit from
306
+    #: the parent product.
292
     is_discountable = models.BooleanField(
307
     is_discountable = models.BooleanField(
293
         _("Is discountable?"), default=True, help_text=_(
308
         _("Is discountable?"), default=True, help_text=_(
294
             "This flag indicates if this product can be used in an offer "
309
             "This flag indicates if this product can be used in an offer "
299
 
314
 
300
     class Meta:
315
     class Meta:
301
         abstract = True
316
         abstract = True
317
+        app_label = 'catalogue'
302
         ordering = ['-date_created']
318
         ordering = ['-date_created']
303
         verbose_name = _('Product')
319
         verbose_name = _('Product')
304
         verbose_name_plural = _('Products')
320
         verbose_name_plural = _('Products')
307
         super(AbstractProduct, self).__init__(*args, **kwargs)
323
         super(AbstractProduct, self).__init__(*args, **kwargs)
308
         self.attr = ProductAttributesContainer(product=self)
324
         self.attr = ProductAttributesContainer(product=self)
309
 
325
 
310
-    def __unicode__(self):
326
+    def __str__(self):
311
         if self.is_child:
327
         if self.is_child:
312
             return u"%s (%s)" % (self.get_title(), self.attribute_summary)
328
             return u"%s (%s)" % (self.get_title(), self.attribute_summary)
313
         return self.get_title()
329
         return self.get_title()
320
                        kwargs={'product_slug': self.slug, 'pk': self.id})
336
                        kwargs={'product_slug': self.slug, 'pk': self.id})
321
 
337
 
322
     def clean(self):
338
     def clean(self):
323
-        # call clean method for product structure
339
+        """
340
+        Validate a product. Those are the rules:
341
+
342
+        +---------------+-------------+--------------+--------------+
343
+        |               | stand alone | parent       | child        |
344
+        +---------------+-------------+--------------+--------------+
345
+        | title         | required    | required     | optional     |
346
+        +---------------+-------------+--------------+--------------+
347
+        | product class | required    | required     | must be None |
348
+        +---------------+-------------+--------------+--------------+
349
+        | parent        | forbidden   | forbidden    | required     |
350
+        +---------------+-------------+--------------+--------------+
351
+        | stockrecords  | 0 or more   | forbidden    | required     |
352
+        +---------------+-------------+--------------+--------------+
353
+        | categories    | 1 or more   | 1 or more    | forbidden    |
354
+        +---------------+-------------+--------------+--------------+
355
+        | attributes    | optional    | optional     | optional     |
356
+        +---------------+-------------+--------------+--------------+
357
+        | rec. products | optional    | optional     | unsupported  |
358
+        +---------------+-------------+--------------+--------------+
359
+
360
+        Because the validation logic is quite complex, validation is delegated
361
+        to the sub method appropriate for the product's structure.
362
+        """
324
         getattr(self, '_clean_%s' % self.structure)()
363
         getattr(self, '_clean_%s' % self.structure)()
325
         if not self.is_parent:
364
         if not self.is_parent:
326
             self.attr.validate_attributes()
365
             self.attr.validate_attributes()
345
         if self.parent_id and not self.parent.is_parent:
384
         if self.parent_id and not self.parent.is_parent:
346
             raise ValidationError(
385
             raise ValidationError(
347
                 _("You can only assign child products to parent products."))
386
                 _("You can only assign child products to parent products."))
387
+        if self.product_class:
388
+            raise ValidationError(
389
+                _("A child product can't have a product class."))
348
 
390
 
349
     def _clean_parent(self):
391
     def _clean_parent(self):
350
         """
392
         """
375
     def is_child(self):
417
     def is_child(self):
376
         return self.structure == self.CHILD
418
         return self.structure == self.CHILD
377
 
419
 
420
+    def can_be_parent(self, give_reason=False):
421
+        """
422
+        Helps decide if a the product can be turned into a parent product.
423
+        """
424
+        reason = None
425
+        if self.is_child:
426
+            reason = _('The specified parent product is a child product.')
427
+        if self.has_stockrecords:
428
+            reason = _(
429
+                "One can't add a child product to a product with stock"
430
+                " records.")
431
+        is_valid = reason is None
432
+        if give_reason:
433
+            return is_valid, reason
434
+        else:
435
+            return is_valid
436
+
378
     @property
437
     @property
379
     def options(self):
438
     def options(self):
380
         pclass = self.get_product_class()
439
         pclass = self.get_product_class()
409
         return ", ".join(pairs)
468
         return ", ".join(pairs)
410
 
469
 
411
     @property
470
     @property
412
-    def min_variant_price_incl_tax(self):
471
+    def min_child_price_incl_tax(self):
413
         """
472
         """
414
-        Return minimum variant price including tax
473
+        Return minimum child product price including tax
415
         """
474
         """
416
-        return self._min_variant_price('price_incl_tax')
475
+        return self._min_child_price('price_incl_tax')
417
 
476
 
418
     @property
477
     @property
419
-    def min_variant_price_excl_tax(self):
478
+    def min_child_price_excl_tax(self):
420
         """
479
         """
421
-        Return minimum variant price excluding tax
480
+        Return minimum child product price excluding tax
422
         """
481
         """
423
-        return self._min_variant_price('price_excl_tax')
482
+        return self._min_child_price('price_excl_tax')
424
 
483
 
425
-    def _min_variant_price(self, property):
484
+    def _min_child_price(self, property):
426
         """
485
         """
427
-        Return minimum variant price
486
+        Return minimum child product price
428
         """
487
         """
429
         prices = []
488
         prices = []
430
-        for variant in self.variants.all():
431
-            if variant.has_stockrecords:
432
-                prices.append(getattr(variant.stockrecord, property))
489
+        for child in self.children.all():
490
+            if child.has_stockrecords:
491
+                prices.append(getattr(child.stockrecord, property))
433
         if not prices:
492
         if not prices:
434
             return None
493
             return None
435
         prices.sort()
494
         prices.sort()
436
         return prices[0]
495
         return prices[0]
437
 
496
 
438
-    # Deprecated properties
497
+    # The properties below are based on deprecated naming conventions
439
 
498
 
440
     @property
499
     @property
441
     @deprecated
500
     @deprecated
457
     @deprecated
516
     @deprecated
458
     def is_group(self):
517
     def is_group(self):
459
         """
518
         """
460
-        Test if this is a top level product and has more than 0 variants
519
+        Test if this is a parent product
461
         """
520
         """
462
         return self.is_parent
521
         return self.is_parent
463
 
522
 
464
     @property
523
     @property
524
+    @deprecated
465
     def is_variant(self):
525
     def is_variant(self):
466
         """Return True if a product is not a top level product"""
526
         """Return True if a product is not a top level product"""
467
         return self.is_child
527
         return self.is_child
468
 
528
 
469
-    # Wrappers
529
+    @property
530
+    @deprecated
531
+    def min_variant_price_incl_tax(self):
532
+        """
533
+        Return minimum variant price including tax
534
+        """
535
+        return self._min_child_price('price_incl_tax')
536
+
537
+    @property
538
+    @deprecated
539
+    def min_variant_price_excl_tax(self):
540
+        """
541
+        Return minimum variant price excluding tax
542
+        """
543
+        return self._min_child_price('price_excl_tax')
544
+
545
+    # Wrappers for child products
470
 
546
 
471
     def get_title(self):
547
     def get_title(self):
472
         """
548
         """
480
 
556
 
481
     def get_product_class(self):
557
     def get_product_class(self):
482
         """
558
         """
483
-        Return a product's item class
559
+        Return a product's item class. Child products inherit their parent's.
484
         """
560
         """
485
-        if self.product_class_id or self.product_class:
486
-            return self.product_class
487
-        if self.parent and self.parent.product_class:
561
+        if self.is_child:
488
             return self.parent.product_class
562
             return self.parent.product_class
489
-        return None
563
+        else:
564
+            return self.product_class
490
     get_product_class.short_description = _("Product class")
565
     get_product_class.short_description = _("Product class")
491
 
566
 
567
+    def get_is_discountable(self):
568
+        """
569
+        At the moment, is_discountable can't be set individually for child
570
+        products; they inherit it from their parent.
571
+        """
572
+        if self.is_child:
573
+            return self.parent.is_discountable
574
+        else:
575
+            return self.is_discountable
576
+
577
+    def get_categories(self):
578
+        """
579
+        Return a product's categories or parent's if there is a parent product.
580
+        """
581
+        if self.is_child:
582
+            return self.parent.categories
583
+        else:
584
+            return self.categories
585
+    get_categories.short_description = _("Categories")
586
+
492
     # Images
587
     # Images
493
 
588
 
494
     def get_missing_image(self):
589
     def get_missing_image(self):
589
 
684
 
590
     class Meta:
685
     class Meta:
591
         abstract = True
686
         abstract = True
592
-        verbose_name = _('Product Recommendation')
593
-        verbose_name_plural = _('Product Recomendations')
687
+        app_label = 'catalogue'
594
         ordering = ['primary', '-ranking']
688
         ordering = ['primary', '-ranking']
595
         unique_together = ('primary', 'recommendation')
689
         unique_together = ('primary', 'recommendation')
690
+        verbose_name = _('Product recommendation')
691
+        verbose_name_plural = _('Product recomendations')
596
 
692
 
597
 
693
 
598
 class ProductAttributesContainer(object):
694
 class ProductAttributesContainer(object):
614
 
710
 
615
     def __getattr__(self, name):
711
     def __getattr__(self, name):
616
         if not name.startswith('_') and not self.initialised:
712
         if not name.startswith('_') and not self.initialised:
617
-            values = list(self.get_values().select_related('attribute'))
713
+            values = self.get_values().select_related('attribute')
618
             for v in values:
714
             for v in values:
619
                 setattr(self, v.attribute.code, v.value)
715
                 setattr(self, v.attribute.code, v.value)
620
             self.initialised = True
716
             self.initialised = True
661
                 attribute.save_value(self.product, value)
757
                 attribute.save_value(self.product, value)
662
 
758
 
663
 
759
 
760
+@python_2_unicode_compatible
664
 class AbstractProductAttribute(models.Model):
761
 class AbstractProductAttribute(models.Model):
665
     """
762
     """
666
     Defines an attribute for a product class. (For example, number_of_pages for
763
     Defines an attribute for a product class. (For example, number_of_pages for
712
 
809
 
713
     class Meta:
810
     class Meta:
714
         abstract = True
811
         abstract = True
812
+        app_label = 'catalogue'
715
         ordering = ['code']
813
         ordering = ['code']
716
-        verbose_name = _('Product Attribute')
717
-        verbose_name_plural = _('Product Attributes')
814
+        verbose_name = _('Product attribute')
815
+        verbose_name_plural = _('Product attributes')
718
 
816
 
719
     @property
817
     @property
720
     def is_option(self):
818
     def is_option(self):
724
     def is_file(self):
822
     def is_file(self):
725
         return self.type in [self.FILE, self.IMAGE]
823
         return self.type in [self.FILE, self.IMAGE]
726
 
824
 
727
-    def __unicode__(self):
825
+    def __str__(self):
728
         return self.name
826
         return self.name
729
 
827
 
730
     def save_value(self, product, value):
828
     def save_value(self, product, value):
815
     _validate_image = _validate_file
913
     _validate_image = _validate_file
816
 
914
 
817
 
915
 
916
+@python_2_unicode_compatible
818
 class AbstractProductAttributeValue(models.Model):
917
 class AbstractProductAttributeValue(models.Model):
819
     """
918
     """
820
     The "through" model for the m2m relationship between catalogue.Product and
919
     The "through" model for the m2m relationship between catalogue.Product and
866
 
965
 
867
     class Meta:
966
     class Meta:
868
         abstract = True
967
         abstract = True
869
-        verbose_name = _('Product Attribute Value')
870
-        verbose_name_plural = _('Product Attribute Values')
968
+        app_label = 'catalogue'
871
         unique_together = ('attribute', 'product')
969
         unique_together = ('attribute', 'product')
970
+        verbose_name = _('Product attribute value')
971
+        verbose_name_plural = _('Product attribute values')
872
 
972
 
873
-    def __unicode__(self):
973
+    def __str__(self):
874
         return self.summary()
974
         return self.summary()
875
 
975
 
876
     def summary(self):
976
     def summary(self):
900
         Returns the unicode representation of the related model. You likely
1000
         Returns the unicode representation of the related model. You likely
901
         want to customise this (and maybe _entity_as_html) if you use entities.
1001
         want to customise this (and maybe _entity_as_html) if you use entities.
902
         """
1002
         """
903
-        return unicode(self.value)
1003
+        return six.text_type(self.value)
904
 
1004
 
905
     @property
1005
     @property
906
     def value_as_html(self):
1006
     def value_as_html(self):
917
         return mark_safe(self.value)
1017
         return mark_safe(self.value)
918
 
1018
 
919
 
1019
 
1020
+@python_2_unicode_compatible
920
 class AbstractAttributeOptionGroup(models.Model):
1021
 class AbstractAttributeOptionGroup(models.Model):
921
     """
1022
     """
922
     Defines a group of options that collectively may be used as an
1023
     Defines a group of options that collectively may be used as an
926
     """
1027
     """
927
     name = models.CharField(_('Name'), max_length=128)
1028
     name = models.CharField(_('Name'), max_length=128)
928
 
1029
 
929
-    def __unicode__(self):
1030
+    def __str__(self):
930
         return self.name
1031
         return self.name
931
 
1032
 
932
     class Meta:
1033
     class Meta:
933
         abstract = True
1034
         abstract = True
934
-        verbose_name = _('Attribute Option Group')
935
-        verbose_name_plural = _('Attribute Option Groups')
1035
+        app_label = 'catalogue'
1036
+        verbose_name = _('Attribute option group')
1037
+        verbose_name_plural = _('Attribute option groups')
936
 
1038
 
937
     @property
1039
     @property
938
     def option_summary(self):
1040
     def option_summary(self):
940
         return ", ".join(options)
1042
         return ", ".join(options)
941
 
1043
 
942
 
1044
 
1045
+@python_2_unicode_compatible
943
 class AbstractAttributeOption(models.Model):
1046
 class AbstractAttributeOption(models.Model):
944
     """
1047
     """
945
     Provides an option within an option group for an attribute type
1048
     Provides an option within an option group for an attribute type
950
         verbose_name=_("Group"))
1053
         verbose_name=_("Group"))
951
     option = models.CharField(_('Option'), max_length=255)
1054
     option = models.CharField(_('Option'), max_length=255)
952
 
1055
 
953
-    def __unicode__(self):
1056
+    def __str__(self):
954
         return self.option
1057
         return self.option
955
 
1058
 
956
     class Meta:
1059
     class Meta:
957
         abstract = True
1060
         abstract = True
958
-        verbose_name = _('Attribute Option')
959
-        verbose_name_plural = _('Attribute Options')
1061
+        app_label = 'catalogue'
1062
+        verbose_name = _('Attribute option')
1063
+        verbose_name_plural = _('Attribute options')
960
 
1064
 
961
 
1065
 
1066
+@python_2_unicode_compatible
962
 class AbstractOption(models.Model):
1067
 class AbstractOption(models.Model):
963
     """
1068
     """
964
     An option that can be selected for a particular item when the product
1069
     An option that can be selected for a particular item when the product
985
 
1090
 
986
     class Meta:
1091
     class Meta:
987
         abstract = True
1092
         abstract = True
1093
+        app_label = 'catalogue'
988
         verbose_name = _("Option")
1094
         verbose_name = _("Option")
989
         verbose_name_plural = _("Options")
1095
         verbose_name_plural = _("Options")
990
 
1096
 
991
-    def __unicode__(self):
1097
+    def __str__(self):
992
         return self.name
1098
         return self.name
993
 
1099
 
994
     @property
1100
     @property
1034
                                            settings.MEDIA_ROOT))
1140
                                            settings.MEDIA_ROOT))
1035
 
1141
 
1036
 
1142
 
1143
+@python_2_unicode_compatible
1037
 class AbstractProductImage(models.Model):
1144
 class AbstractProductImage(models.Model):
1038
     """
1145
     """
1039
     An image of a product
1146
     An image of a product
1053
 
1160
 
1054
     class Meta:
1161
     class Meta:
1055
         abstract = True
1162
         abstract = True
1056
-        unique_together = ("product", "display_order")
1163
+        app_label = 'catalogue'
1057
         # Any custom models should ensure that this ordering is unchanged, or
1164
         # Any custom models should ensure that this ordering is unchanged, or
1058
         # your query count will explode. See AbstractProduct.primary_image.
1165
         # your query count will explode. See AbstractProduct.primary_image.
1059
         ordering = ["display_order"]
1166
         ordering = ["display_order"]
1060
-        verbose_name = _('Product Image')
1061
-        verbose_name_plural = _('Product Images')
1167
+        unique_together = ("product", "display_order")
1168
+        verbose_name = _('Product image')
1169
+        verbose_name_plural = _('Product images')
1062
 
1170
 
1063
-    def __unicode__(self):
1171
+    def __str__(self):
1064
         return u"Image of '%s'" % self.product
1172
         return u"Image of '%s'" % self.product
1065
 
1173
 
1066
     def is_primary(self):
1174
     def is_primary(self):
1068
         Return bool if image display order is 0
1176
         Return bool if image display order is 0
1069
         """
1177
         """
1070
         return self.display_order == 0
1178
         return self.display_order == 0
1179
+
1180
+    def delete(self, *args, **kwargs):
1181
+        """
1182
+        Always keep the display_order as consecutive integers. This avoids
1183
+        issue #855.
1184
+        """
1185
+        super(AbstractProductImage, self).delete(*args, **kwargs)
1186
+        for idx, image in enumerate(self.product.images.all()):
1187
+            image.display_order = idx
1188
+            image.save()

+ 11
- 0
oscar/apps/catalogue/config.py View File

1
+from django.apps import AppConfig
2
+from django.utils.translation import ugettext_lazy as _
3
+
4
+
5
+class CatalogueConfig(AppConfig):
6
+    label = 'catalogue'
7
+    name = 'oscar.apps.catalogue'
8
+    verbose_name = _('Catalogue')
9
+
10
+    def ready(self):
11
+        from . import receivers  # noqa

+ 0
- 4
oscar/apps/catalogue/managers.py View File

40
     def base_queryset(self):
40
     def base_queryset(self):
41
         return self.get_queryset().base_queryset()
41
         return self.get_queryset().base_queryset()
42
 
42
 
43
-    get_query_set = get_queryset  # Django 1.5 compatibility fix
44
-
45
 
43
 
46
 class BrowsableProductManager(ProductManager):
44
 class BrowsableProductManager(ProductManager):
47
     """
45
     """
52
 
50
 
53
     def get_queryset(self):
51
     def get_queryset(self):
54
         return super(BrowsableProductManager, self).get_queryset().browsable()
52
         return super(BrowsableProductManager, self).get_queryset().browsable()
55
-
56
-    get_query_set = get_queryset  # Django 1.5 compatibility fix

+ 291
- 403
oscar/apps/catalogue/migrations/0001_initial.py View File

1
-# encoding: utf-8
2
-import datetime
3
-from south.db import db
4
-from south.v2 import SchemaMigration
5
-from django.db import models
6
-
7
-class Migration(SchemaMigration):
8
-
9
-    def forwards(self, orm):
10
-
11
-        # Adding model 'ProductRecommendation'
12
-        db.create_table('catalogue_productrecommendation', (
13
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14
-            ('primary', self.gf('django.db.models.fields.related.ForeignKey')(related_name='primary_recommendations', to=orm['catalogue.Product'])),
15
-            ('recommendation', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
16
-            ('ranking', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=0)),
17
-        ))
18
-        db.send_create_signal('catalogue', ['ProductRecommendation'])
19
-
20
-        # Adding model 'ProductClass'
21
-        db.create_table('catalogue_productclass', (
22
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
23
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
24
-            ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128, db_index=True)),
25
-        ))
26
-        db.send_create_signal('catalogue', ['ProductClass'])
27
-
28
-        # Adding M2M table for field options on 'ProductClass'
29
-        db.create_table('catalogue_productclass_options', (
30
-            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
31
-            ('productclass', models.ForeignKey(orm['catalogue.productclass'], null=False)),
32
-            ('option', models.ForeignKey(orm['catalogue.option'], null=False))
33
-        ))
34
-        db.create_unique('catalogue_productclass_options', ['productclass_id', 'option_id'])
35
-
36
-        # Adding model 'Category'
37
-        db.create_table('catalogue_category', (
38
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
39
-            ('path', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
40
-            ('depth', self.gf('django.db.models.fields.PositiveIntegerField')()),
41
-            ('numchild', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
42
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
43
-            ('slug',
44
-             self.gf('django.db.models.fields.SlugField')(max_length=255, db_index=True)),
45
-            ('full_name',
46
-             self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
47
-        ))
48
-        db.send_create_signal('catalogue', ['Category'])
49
-
50
-        # Adding model 'ProductCategory'
51
-        db.create_table('catalogue_productcategory', (
52
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
53
-            ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
54
-            ('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Category'])),
55
-            ('is_canonical', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True)),
56
-        ))
57
-        db.send_create_signal('catalogue', ['ProductCategory'])
58
-
59
-        # Adding model 'Product'
60
-        db.create_table('catalogue_product', (
61
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
62
-            ('upc', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=64, null=True, blank=True)),
63
-            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='variants', null=True, to=orm['catalogue.Product'])),
64
-            ('title', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
65
-            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=255, db_index=True)),
66
-            ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
67
-            ('product_class', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ProductClass'], null=True)),
68
-            ('score', self.gf('django.db.models.fields.FloatField')(default=0.0, db_index=True)),
69
-            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
70
-            ('date_updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, db_index=True, blank=True)),
71
-        ))
72
-        db.send_create_signal('catalogue', ['Product'])
73
-
74
-        # Adding M2M table for field product_options on 'Product'
75
-        db.create_table('catalogue_product_product_options', (
76
-            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
77
-            ('product', models.ForeignKey(orm['catalogue.product'], null=False)),
78
-            ('option', models.ForeignKey(orm['catalogue.option'], null=False))
79
-        ))
80
-        db.create_unique('catalogue_product_product_options', ['product_id', 'option_id'])
81
-
82
-        # Adding M2M table for field related_products on 'Product'
83
-        db.create_table('catalogue_product_related_products', (
84
-            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
85
-            ('from_product', models.ForeignKey(orm['catalogue.product'], null=False)),
86
-            ('to_product', models.ForeignKey(orm['catalogue.product'], null=False))
87
-        ))
88
-        db.create_unique('catalogue_product_related_products', ['from_product_id', 'to_product_id'])
89
-
90
-        # Adding model 'ContributorRole'
91
-        db.create_table('catalogue_contributorrole', (
92
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
93
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=50)),
94
-            ('name_plural', self.gf('django.db.models.fields.CharField')(max_length=50)),
95
-            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)),
96
-        ))
97
-        db.send_create_signal('catalogue', ['ContributorRole'])
98
-
99
-        # Adding model 'Contributor'
100
-        db.create_table('catalogue_contributor', (
101
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
102
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
103
-            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=255, db_index=True)),
104
-        ))
105
-        db.send_create_signal('catalogue', ['Contributor'])
106
-
107
-        # Adding model 'ProductContributor'
108
-        db.create_table('catalogue_productcontributor', (
109
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
110
-            ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
111
-            ('contributor', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Contributor'])),
112
-            ('role', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ContributorRole'])),
113
-        ))
114
-        db.send_create_signal('catalogue', ['ProductContributor'])
115
-
116
-        # Adding model 'ProductAttribute'
117
-        db.create_table('catalogue_productattribute', (
118
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
119
-            ('product_class', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='attributes', null=True, to=orm['catalogue.ProductClass'])),
120
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
121
-            ('code', self.gf('django.db.models.fields.SlugField')(max_length=128, db_index=True)),
122
-            ('type', self.gf('django.db.models.fields.CharField')(default='text', max_length=20)),
123
-            ('option_group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.AttributeOptionGroup'], null=True, blank=True)),
124
-            ('entity_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.AttributeEntityType'], null=True, blank=True)),
125
-            ('required', self.gf('django.db.models.fields.BooleanField')(default=False)),
126
-        ))
127
-        db.send_create_signal('catalogue', ['ProductAttribute'])
128
-
129
-        # Adding model 'ProductAttributeValue'
130
-        db.create_table('catalogue_productattributevalue', (
131
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
132
-            ('attribute', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ProductAttribute'])),
133
-            ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
134
-            ('value_text', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
135
-            ('value_integer', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
136
-            ('value_boolean', self.gf('django.db.models.fields.BooleanField')(default=False)),
137
-            ('value_float', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
138
-            ('value_richtext', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
139
-            ('value_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)),
140
-            ('value_option', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.AttributeOption'], null=True, blank=True)),
141
-            ('value_entity', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.AttributeEntity'], null=True, blank=True)),
142
-        ))
143
-        db.send_create_signal('catalogue', ['ProductAttributeValue'])
144
-
145
-        # Adding model 'AttributeOptionGroup'
146
-        db.create_table('catalogue_attributeoptiongroup', (
147
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
148
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
149
-        ))
150
-        db.send_create_signal('catalogue', ['AttributeOptionGroup'])
151
-
152
-        # Adding model 'AttributeOption'
153
-        db.create_table('catalogue_attributeoption', (
154
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
155
-            ('group', self.gf('django.db.models.fields.related.ForeignKey')(related_name='options', to=orm['catalogue.AttributeOptionGroup'])),
156
-            ('option', self.gf('django.db.models.fields.CharField')(max_length=255)),
157
-        ))
158
-        db.send_create_signal('catalogue', ['AttributeOption'])
159
-
160
-        # Adding model 'AttributeEntity'
161
-        db.create_table('catalogue_attributeentity', (
162
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
163
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
164
-            ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=255, blank=True)),
165
-            ('type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='entities', to=orm['catalogue.AttributeEntityType'])),
166
-        ))
167
-        db.send_create_signal('catalogue', ['AttributeEntity'])
168
-
169
-        # Adding model 'AttributeEntityType'
170
-        db.create_table('catalogue_attributeentitytype', (
171
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
172
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
173
-            ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=255, blank=True)),
174
-        ))
175
-        db.send_create_signal('catalogue', ['AttributeEntityType'])
176
-
177
-        # Adding model 'Option'
178
-        db.create_table('catalogue_option', (
179
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
180
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
181
-            ('code', self.gf('django.db.models.fields.SlugField')(max_length=128, db_index=True)),
182
-            ('type', self.gf('django.db.models.fields.CharField')(default='Required', max_length=128)),
183
-        ))
184
-        db.send_create_signal('catalogue', ['Option'])
185
-
186
-        # Adding model 'ProductImage'
187
-        db.create_table('catalogue_productimage', (
188
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
189
-            ('product', self.gf('django.db.models.fields.related.ForeignKey')(related_name='images', to=orm['catalogue.Product'])),
190
-            ('original', self.gf('django.db.models.fields.files.ImageField')(max_length=100)),
191
-            ('caption', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True)),
192
-            ('display_order', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
193
-            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
194
-        ))
195
-        db.send_create_signal('catalogue', ['ProductImage'])
196
-
197
-        # Adding unique constraint on 'ProductImage', fields ['product', 'display_order']
198
-        db.create_unique('catalogue_productimage', ['product_id', 'display_order'])
199
-
200
-
201
-    def backwards(self, orm):
202
-
203
-        # Removing unique constraint on 'ProductImage', fields ['product', 'display_order']
204
-        db.delete_unique('catalogue_productimage', ['product_id', 'display_order'])
205
-
206
-        # Deleting model 'ProductRecommendation'
207
-        db.delete_table('catalogue_productrecommendation')
208
-
209
-        # Deleting model 'ProductClass'
210
-        db.delete_table('catalogue_productclass')
211
-
212
-        # Removing M2M table for field options on 'ProductClass'
213
-        db.delete_table('catalogue_productclass_options')
214
-
215
-        # Deleting model 'Category'
216
-        db.delete_table('catalogue_category')
217
-
218
-        # Deleting model 'ProductCategory'
219
-        db.delete_table('catalogue_productcategory')
220
-
221
-        # Deleting model 'Product'
222
-        db.delete_table('catalogue_product')
223
-
224
-        # Removing M2M table for field product_options on 'Product'
225
-        db.delete_table('catalogue_product_product_options')
226
-
227
-        # Removing M2M table for field related_products on 'Product'
228
-        db.delete_table('catalogue_product_related_products')
229
-
230
-        # Deleting model 'ContributorRole'
231
-        db.delete_table('catalogue_contributorrole')
232
-
233
-        # Deleting model 'Contributor'
234
-        db.delete_table('catalogue_contributor')
235
-
236
-        # Deleting model 'ProductContributor'
237
-        db.delete_table('catalogue_productcontributor')
238
-
239
-        # Deleting model 'ProductAttribute'
240
-        db.delete_table('catalogue_productattribute')
241
-
242
-        # Deleting model 'ProductAttributeValue'
243
-        db.delete_table('catalogue_productattributevalue')
244
-
245
-        # Deleting model 'AttributeOptionGroup'
246
-        db.delete_table('catalogue_attributeoptiongroup')
247
-
248
-        # Deleting model 'AttributeOption'
249
-        db.delete_table('catalogue_attributeoption')
250
-
251
-        # Deleting model 'AttributeEntity'
252
-        db.delete_table('catalogue_attributeentity')
253
-
254
-        # Deleting model 'AttributeEntityType'
255
-        db.delete_table('catalogue_attributeentitytype')
256
-
257
-        # Deleting model 'Option'
258
-        db.delete_table('catalogue_option')
259
-
260
-        # Deleting model 'ProductImage'
261
-        db.delete_table('catalogue_productimage')
262
-
263
-
264
-    models = {
265
-        'catalogue.attributeentity': {
266
-            'Meta': {'object_name': 'AttributeEntity'},
267
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
268
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
269
-            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
270
-            'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': "orm['catalogue.AttributeEntityType']"})
271
-        },
272
-        'catalogue.attributeentitytype': {
273
-            'Meta': {'object_name': 'AttributeEntityType'},
274
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
276
-            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'})
277
-        },
278
-        'catalogue.attributeoption': {
279
-            'Meta': {'object_name': 'AttributeOption'},
280
-            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': "orm['catalogue.AttributeOptionGroup']"}),
281
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
282
-            'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
283
-        },
284
-        'catalogue.attributeoptiongroup': {
285
-            'Meta': {'object_name': 'AttributeOptionGroup'},
286
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
287
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
288
-        },
289
-        'catalogue.category': {
290
-            'Meta': {'ordering': "['name']", 'object_name': 'Category'},
291
-            'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
292
-            'full_name': ('django.db.models.fields.CharField', [],
293
-                          {'max_length': '255', 'db_index': 'True'}),
294
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
295
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
296
-            'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
297
-            'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
298
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length':
299
-                                                               '255', 'db_index': 'True'})
300
-        },
301
-        'catalogue.contributor': {
302
-            'Meta': {'object_name': 'Contributor'},
303
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
304
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
305
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'})
306
-        },
307
-        'catalogue.contributorrole': {
308
-            'Meta': {'object_name': 'ContributorRole'},
309
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
310
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
311
-            'name_plural': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
312
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
313
-        },
314
-        'catalogue.option': {
315
-            'Meta': {'object_name': 'Option'},
316
-            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
317
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
318
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
319
-            'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
320
-        },
321
-        'catalogue.product': {
322
-            'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
323
-            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.ProductAttribute']", 'through': "orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
324
-            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Category']", 'through': "orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
325
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
326
-            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
327
-            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
328
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
329
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': "orm['catalogue.Product']"}),
330
-            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductClass']", 'null': 'True'}),
331
-            'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
332
-            'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Product']", 'symmetrical': 'False', 'through': "orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
333
-            'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
334
-            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}),
335
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
336
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
337
-            'upc': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'})
338
-        },
339
-        'catalogue.productattribute': {
340
-            'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
341
-            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
342
-            'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
343
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
344
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
345
-            'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
346
-            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': "orm['catalogue.ProductClass']"}),
347
-            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
348
-            'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
349
-        },
350
-        'catalogue.productattributevalue': {
351
-            'Meta': {'object_name': 'ProductAttributeValue'},
352
-            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductAttribute']"}),
353
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
354
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
355
-            'value_boolean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
356
-            'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
357
-            'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
358
-            'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
359
-            'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
360
-            'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
361
-            'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
362
-            'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
363
-        },
364
-        'catalogue.productcategory': {
365
-            'Meta': {'ordering': "['-is_canonical']", 'object_name': 'ProductCategory'},
366
-            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Category']"}),
367
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
368
-            'is_canonical': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
369
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
370
-        },
371
-        'catalogue.productclass': {
372
-            'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
373
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
374
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
375
-            'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
376
-            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'})
377
-        },
378
-        'catalogue.productcontributor': {
379
-            'Meta': {'object_name': 'ProductContributor'},
380
-            'contributor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Contributor']"}),
381
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
382
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
383
-            'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ContributorRole']"})
384
-        },
385
-        'catalogue.productimage': {
386
-            'Meta': {'ordering': "['display_order']", 'unique_together': "(('product', 'display_order'),)", 'object_name': 'ProductImage'},
387
-            'caption': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
388
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
389
-            'display_order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
390
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
391
-            'original': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
392
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'images'", 'to': "orm['catalogue.Product']"})
393
-        },
394
-        'catalogue.productrecommendation': {
395
-            'Meta': {'object_name': 'ProductRecommendation'},
396
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
397
-            'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': "orm['catalogue.Product']"}),
398
-            'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
399
-            'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
400
-        }
401
-    }
402
-
403
-    complete_apps = ['catalogue']
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+import oscar.models.fields
6
+import django.core.validators
7
+import django.db.models.deletion
8
+import oscar.models.fields.autoslugfield
9
+
10
+
11
+class Migration(migrations.Migration):
12
+
13
+    dependencies = [
14
+        ('contenttypes', '0001_initial'),
15
+    ]
16
+
17
+    operations = [
18
+        migrations.CreateModel(
19
+            name='AttributeOption',
20
+            fields=[
21
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
22
+                ('option', models.CharField(verbose_name='Option', max_length=255)),
23
+            ],
24
+            options={
25
+                'verbose_name': 'Attribute option',
26
+                'verbose_name_plural': 'Attribute options',
27
+                'abstract': False,
28
+            },
29
+            bases=(models.Model,),
30
+        ),
31
+        migrations.CreateModel(
32
+            name='AttributeOptionGroup',
33
+            fields=[
34
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
35
+                ('name', models.CharField(verbose_name='Name', max_length=128)),
36
+            ],
37
+            options={
38
+                'verbose_name': 'Attribute option group',
39
+                'verbose_name_plural': 'Attribute option groups',
40
+                'abstract': False,
41
+            },
42
+            bases=(models.Model,),
43
+        ),
44
+        migrations.AddField(
45
+            model_name='attributeoption',
46
+            name='group',
47
+            field=models.ForeignKey(verbose_name='Group', to='catalogue.AttributeOptionGroup'),
48
+            preserve_default=True,
49
+        ),
50
+        migrations.CreateModel(
51
+            name='Category',
52
+            fields=[
53
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
54
+                ('path', models.CharField(unique=True, max_length=255)),
55
+                ('depth', models.PositiveIntegerField()),
56
+                ('numchild', models.PositiveIntegerField(default=0)),
57
+                ('name', models.CharField(verbose_name='Name', db_index=True, max_length=255)),
58
+                ('description', models.TextField(verbose_name='Description', blank=True)),
59
+                ('image', models.ImageField(verbose_name='Image', upload_to='categories', blank=True, null=True, max_length=255)),
60
+                ('slug', models.SlugField(editable=False, verbose_name='Slug', max_length=255)),
61
+                ('full_name', models.CharField(editable=False, verbose_name='Full Name', db_index=True, max_length=255)),
62
+            ],
63
+            options={
64
+                'verbose_name': 'Category',
65
+                'verbose_name_plural': 'Categories',
66
+                'ordering': ['full_name'],
67
+                'abstract': False,
68
+            },
69
+            bases=(models.Model,),
70
+        ),
71
+        migrations.CreateModel(
72
+            name='Option',
73
+            fields=[
74
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
75
+                ('name', models.CharField(verbose_name='Name', max_length=128)),
76
+                ('code', oscar.models.fields.autoslugfield.AutoSlugField(editable=False, verbose_name='Code', blank=True, max_length=128, populate_from='name', unique=True)),
77
+                ('type', models.CharField(verbose_name='Status', choices=[('Required', 'Required - a value for this option must be specified'), ('Optional', 'Optional - a value for this option can be omitted')], default='Required', max_length=128)),
78
+            ],
79
+            options={
80
+                'verbose_name': 'Option',
81
+                'verbose_name_plural': 'Options',
82
+                'abstract': False,
83
+            },
84
+            bases=(models.Model,),
85
+        ),
86
+        migrations.CreateModel(
87
+            name='Product',
88
+            fields=[
89
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
90
+                ('structure', models.CharField(verbose_name='Product structure', choices=[('standalone', 'Stand-alone product'), ('parent', 'Parent product'), ('child', 'Child product')], default='standalone', max_length=10)),
91
+                ('upc', oscar.models.fields.NullCharField(verbose_name='UPC', max_length=64, help_text='Universal Product Code (UPC) is an identifier for a product which is not specific to a particular  supplier. Eg an ISBN for a book.', unique=True)),
92
+                ('title', models.CharField(verbose_name='Title', blank=True, max_length=255)),
93
+                ('slug', models.SlugField(verbose_name='Slug', max_length=255)),
94
+                ('description', models.TextField(verbose_name='Description', blank=True)),
95
+                ('rating', models.FloatField(editable=False, verbose_name='Rating', null=True)),
96
+                ('date_created', models.DateTimeField(verbose_name='Date created', auto_now_add=True)),
97
+                ('date_updated', models.DateTimeField(verbose_name='Date updated', auto_now=True, db_index=True)),
98
+                ('is_discountable', models.BooleanField(verbose_name='Is discountable?', help_text='This flag indicates if this product can be used in an offer or not', default=True)),
99
+                ('parent', models.ForeignKey(verbose_name='Parent product', blank=True, help_text="Only choose a parent product if you're creating a child product.  For example if this is a size 4 of a particular t-shirt.  Leave blank if this is a stand-alone product (i.e. there is only one version of this product).", to='catalogue.Product', null=True)),
100
+                ('product_options', models.ManyToManyField(verbose_name='Product Options', blank=True, to='catalogue.Option')),
101
+            ],
102
+            options={
103
+                'verbose_name': 'Product',
104
+                'verbose_name_plural': 'Products',
105
+                'ordering': ['-date_created'],
106
+                'abstract': False,
107
+            },
108
+            bases=(models.Model,),
109
+        ),
110
+        migrations.CreateModel(
111
+            name='ProductAttribute',
112
+            fields=[
113
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
114
+                ('name', models.CharField(verbose_name='Name', max_length=128)),
115
+                ('code', models.SlugField(verbose_name='Code', validators=[django.core.validators.RegexValidator(regex='^[a-zA-Z\\-_][0-9a-zA-Z\\-_]*$', message="Code can only contain the letters a-z, A-Z, digits, minus and underscores, and can't start with a digit")], max_length=128)),
116
+                ('type', models.CharField(verbose_name='Type', choices=[('text', 'Text'), ('integer', 'Integer'), ('boolean', 'True / False'), ('float', 'Float'), ('richtext', 'Rich Text'), ('date', 'Date'), ('option', 'Option'), ('entity', 'Entity'), ('file', 'File'), ('image', 'Image')], default='text', max_length=20)),
117
+                ('required', models.BooleanField(verbose_name='Required', default=False)),
118
+                ('option_group', models.ForeignKey(verbose_name='Option Group', blank=True, help_text='Select an option group if using type "Option"', to='catalogue.AttributeOptionGroup', null=True)),
119
+            ],
120
+            options={
121
+                'verbose_name': 'Product attribute',
122
+                'verbose_name_plural': 'Product attributes',
123
+                'ordering': ['code'],
124
+                'abstract': False,
125
+            },
126
+            bases=(models.Model,),
127
+        ),
128
+        migrations.CreateModel(
129
+            name='ProductAttributeValue',
130
+            fields=[
131
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
132
+                ('value_text', models.TextField(verbose_name='Text', blank=True, null=True)),
133
+                ('value_integer', models.IntegerField(verbose_name='Integer', blank=True, null=True)),
134
+                ('value_boolean', models.NullBooleanField(verbose_name='Boolean')),
135
+                ('value_float', models.FloatField(verbose_name='Float', blank=True, null=True)),
136
+                ('value_richtext', models.TextField(verbose_name='Richtext', blank=True, null=True)),
137
+                ('value_date', models.DateField(verbose_name='Date', blank=True, null=True)),
138
+                ('value_file', models.FileField(null=True, upload_to='images/products/%Y/%m/', blank=True, max_length=255)),
139
+                ('value_image', models.ImageField(null=True, upload_to='images/products/%Y/%m/', blank=True, max_length=255)),
140
+                ('entity_object_id', models.PositiveIntegerField(editable=False, blank=True, null=True)),
141
+                ('attribute', models.ForeignKey(verbose_name='Attribute', to='catalogue.ProductAttribute')),
142
+                ('entity_content_type', models.ForeignKey(editable=False, blank=True, to='contenttypes.ContentType', null=True)),
143
+            ],
144
+            options={
145
+                'verbose_name': 'Product attribute value',
146
+                'verbose_name_plural': 'Product attribute values',
147
+                'abstract': False,
148
+            },
149
+            bases=(models.Model,),
150
+        ),
151
+        migrations.AddField(
152
+            model_name='product',
153
+            name='attributes',
154
+            field=models.ManyToManyField(verbose_name='Attributes', through='catalogue.ProductAttributeValue', to='catalogue.ProductAttribute'),
155
+            preserve_default=True,
156
+        ),
157
+        migrations.AddField(
158
+            model_name='productattributevalue',
159
+            name='product',
160
+            field=models.ForeignKey(verbose_name='Product', to='catalogue.Product'),
161
+            preserve_default=True,
162
+        ),
163
+        migrations.AddField(
164
+            model_name='productattributevalue',
165
+            name='value_option',
166
+            field=models.ForeignKey(verbose_name='Value Option', blank=True, to='catalogue.AttributeOption', null=True),
167
+            preserve_default=True,
168
+        ),
169
+        migrations.AlterUniqueTogether(
170
+            name='productattributevalue',
171
+            unique_together=set([('attribute', 'product')]),
172
+        ),
173
+        migrations.CreateModel(
174
+            name='ProductCategory',
175
+            fields=[
176
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
177
+                ('category', models.ForeignKey(verbose_name='Category', to='catalogue.Category')),
178
+            ],
179
+            options={
180
+                'verbose_name': 'Product category',
181
+                'verbose_name_plural': 'Product categories',
182
+                'ordering': ['product', 'category'],
183
+                'abstract': False,
184
+            },
185
+            bases=(models.Model,),
186
+        ),
187
+        migrations.AddField(
188
+            model_name='product',
189
+            name='categories',
190
+            field=models.ManyToManyField(verbose_name='Categories', through='catalogue.ProductCategory', to='catalogue.Category'),
191
+            preserve_default=True,
192
+        ),
193
+        migrations.AddField(
194
+            model_name='productcategory',
195
+            name='product',
196
+            field=models.ForeignKey(verbose_name='Product', to='catalogue.Product'),
197
+            preserve_default=True,
198
+        ),
199
+        migrations.AlterUniqueTogether(
200
+            name='productcategory',
201
+            unique_together=set([('product', 'category')]),
202
+        ),
203
+        migrations.CreateModel(
204
+            name='ProductClass',
205
+            fields=[
206
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
207
+                ('name', models.CharField(verbose_name='Name', max_length=128)),
208
+                ('slug', oscar.models.fields.autoslugfield.AutoSlugField(editable=False, verbose_name='Slug', blank=True, max_length=128, populate_from='name', unique=True)),
209
+                ('requires_shipping', models.BooleanField(verbose_name='Requires shipping?', default=True)),
210
+                ('track_stock', models.BooleanField(verbose_name='Track stock levels?', default=True)),
211
+                ('options', models.ManyToManyField(verbose_name='Options', blank=True, to='catalogue.Option')),
212
+            ],
213
+            options={
214
+                'verbose_name': 'Product class',
215
+                'verbose_name_plural': 'Product classes',
216
+                'ordering': ['name'],
217
+                'abstract': False,
218
+            },
219
+            bases=(models.Model,),
220
+        ),
221
+        migrations.AddField(
222
+            model_name='productattribute',
223
+            name='product_class',
224
+            field=models.ForeignKey(verbose_name='Product Type', blank=True, to='catalogue.ProductClass', null=True),
225
+            preserve_default=True,
226
+        ),
227
+        migrations.AddField(
228
+            model_name='product',
229
+            name='product_class',
230
+            field=models.ForeignKey(verbose_name='Product Type', on_delete=django.db.models.deletion.PROTECT, help_text='Choose what type of product this is', to='catalogue.ProductClass', null=True),
231
+            preserve_default=True,
232
+        ),
233
+        migrations.CreateModel(
234
+            name='ProductImage',
235
+            fields=[
236
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
237
+                ('original', models.ImageField(verbose_name='Original', upload_to='images/products/%Y/%m/', max_length=255)),
238
+                ('caption', models.CharField(verbose_name='Caption', blank=True, max_length=200)),
239
+                ('display_order', models.PositiveIntegerField(verbose_name='Display Order', help_text='An image with a display order of zero will be the primary image for a product', default=0)),
240
+                ('date_created', models.DateTimeField(verbose_name='Date Created', auto_now_add=True)),
241
+                ('product', models.ForeignKey(verbose_name='Product', to='catalogue.Product')),
242
+            ],
243
+            options={
244
+                'verbose_name': 'Product image',
245
+                'verbose_name_plural': 'Product images',
246
+                'ordering': ['display_order'],
247
+                'abstract': False,
248
+            },
249
+            bases=(models.Model,),
250
+        ),
251
+        migrations.AlterUniqueTogether(
252
+            name='productimage',
253
+            unique_together=set([('product', 'display_order')]),
254
+        ),
255
+        migrations.CreateModel(
256
+            name='ProductRecommendation',
257
+            fields=[
258
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
259
+                ('ranking', models.PositiveSmallIntegerField(verbose_name='Ranking', help_text='Determines order of the products. A product with a higher value will appear before one with a lower ranking.', default=0)),
260
+            ],
261
+            options={
262
+                'verbose_name': 'Product recommendation',
263
+                'verbose_name_plural': 'Product recomendations',
264
+                'ordering': ['primary', '-ranking'],
265
+                'abstract': False,
266
+            },
267
+            bases=(models.Model,),
268
+        ),
269
+        migrations.AddField(
270
+            model_name='product',
271
+            name='recommended_products',
272
+            field=models.ManyToManyField(verbose_name='Recommended Products', through='catalogue.ProductRecommendation', blank=True, to='catalogue.Product'),
273
+            preserve_default=True,
274
+        ),
275
+        migrations.AddField(
276
+            model_name='productrecommendation',
277
+            name='primary',
278
+            field=models.ForeignKey(verbose_name='Primary Product', to='catalogue.Product'),
279
+            preserve_default=True,
280
+        ),
281
+        migrations.AddField(
282
+            model_name='productrecommendation',
283
+            name='recommendation',
284
+            field=models.ForeignKey(verbose_name='Recommended Product', to='catalogue.Product'),
285
+            preserve_default=True,
286
+        ),
287
+        migrations.AlterUniqueTogether(
288
+            name='productrecommendation',
289
+            unique_together=set([('primary', 'recommendation')]),
290
+        ),
291
+    ]

+ 38
- 23
oscar/apps/catalogue/models.py View File

1
+import django
2
+
1
 """
3
 """
2
 Vanilla product models
4
 Vanilla product models
3
 """
5
 """
6
+from oscar.core.loading import is_model_registered
4
 from oscar.apps.catalogue.abstract_models import *  # noqa
7
 from oscar.apps.catalogue.abstract_models import *  # noqa
5
 
8
 
6
 
9
 
7
-class ProductClass(AbstractProductClass):
8
-    pass
10
+if not is_model_registered('catalogue', 'ProductClass'):
11
+    class ProductClass(AbstractProductClass):
12
+        pass
9
 
13
 
10
 
14
 
11
-class Category(AbstractCategory):
12
-    pass
15
+if not is_model_registered('catalogue', 'Category'):
16
+    class Category(AbstractCategory):
17
+        pass
13
 
18
 
14
 
19
 
15
-class ProductCategory(AbstractProductCategory):
16
-    pass
20
+if not is_model_registered('catalogue', 'ProductCategory'):
21
+    class ProductCategory(AbstractProductCategory):
22
+        pass
17
 
23
 
18
 
24
 
19
-class Product(AbstractProduct):
20
-    pass
25
+if not is_model_registered('catalogue', 'Product'):
26
+    class Product(AbstractProduct):
27
+        pass
21
 
28
 
22
 
29
 
23
-class ProductRecommendation(AbstractProductRecommendation):
24
-    pass
30
+if not is_model_registered('catalogue', 'ProductRecommendation'):
31
+    class ProductRecommendation(AbstractProductRecommendation):
32
+        pass
25
 
33
 
26
 
34
 
27
-class ProductAttribute(AbstractProductAttribute):
28
-    pass
35
+if not is_model_registered('catalogue', 'ProductAttribute'):
36
+    class ProductAttribute(AbstractProductAttribute):
37
+        pass
29
 
38
 
30
 
39
 
31
-class ProductAttributeValue(AbstractProductAttributeValue):
32
-    pass
40
+if not is_model_registered('catalogue', 'ProductAttributeValue'):
41
+    class ProductAttributeValue(AbstractProductAttributeValue):
42
+        pass
33
 
43
 
34
 
44
 
35
-class AttributeOptionGroup(AbstractAttributeOptionGroup):
36
-    pass
45
+if not is_model_registered('catalogue', 'AttributeOptionGroup'):
46
+    class AttributeOptionGroup(AbstractAttributeOptionGroup):
47
+        pass
37
 
48
 
38
 
49
 
39
-class AttributeOption(AbstractAttributeOption):
40
-    pass
50
+if not is_model_registered('catalogue', 'AttributeOption'):
51
+    class AttributeOption(AbstractAttributeOption):
52
+        pass
41
 
53
 
42
 
54
 
43
-class Option(AbstractOption):
44
-    pass
55
+if not is_model_registered('catalogue', 'Option'):
56
+    class Option(AbstractOption):
57
+        pass
45
 
58
 
46
 
59
 
47
-class ProductImage(AbstractProductImage):
48
-    pass
60
+if not is_model_registered('catalogue', 'ProductImage'):
61
+    class ProductImage(AbstractProductImage):
62
+        pass
49
 
63
 
50
 
64
 
51
-from .receivers import *  # noqa
65
+if django.VERSION < (1, 7):
66
+    from . import receivers  # noqa

+ 9
- 4
oscar/apps/catalogue/reviews/abstract_models.py View File

3
 from django.core.urlresolvers import reverse
3
 from django.core.urlresolvers import reverse
4
 from django.db import models
4
 from django.db import models
5
 from django.db.models import Sum, Count
5
 from django.db.models import Sum, Count
6
+from django.utils.encoding import python_2_unicode_compatible
6
 from django.utils.translation import ugettext_lazy as _, pgettext_lazy
7
 from django.utils.translation import ugettext_lazy as _, pgettext_lazy
7
 
8
 
8
 from oscar.apps.catalogue.reviews.managers import ApprovedReviewsManager
9
 from oscar.apps.catalogue.reviews.managers import ApprovedReviewsManager
10
 from oscar.core import validators
11
 from oscar.core import validators
11
 
12
 
12
 
13
 
14
+@python_2_unicode_compatible
13
 class AbstractProductReview(models.Model):
15
 class AbstractProductReview(models.Model):
14
     """
16
     """
15
     A review of a product
17
     A review of a product
43
     email = models.EmailField(_("Email"), blank=True)
45
     email = models.EmailField(_("Email"), blank=True)
44
     homepage = models.URLField(_("URL"), blank=True)
46
     homepage = models.URLField(_("URL"), blank=True)
45
 
47
 
46
-    FOR_MODERATION, APPROVED, REJECTED = list(range(0, 3))
48
+    FOR_MODERATION, APPROVED, REJECTED = 0, 1, 2
47
     STATUS_CHOICES = (
49
     STATUS_CHOICES = (
48
         (FOR_MODERATION, _("Requires moderation")),
50
         (FOR_MODERATION, _("Requires moderation")),
49
         (APPROVED, _("Approved")),
51
         (APPROVED, _("Approved")),
69
 
71
 
70
     class Meta:
72
     class Meta:
71
         abstract = True
73
         abstract = True
74
+        app_label = 'reviews'
72
         ordering = ['-delta_votes', 'id']
75
         ordering = ['-delta_votes', 'id']
73
         unique_together = (('product', 'user'),)
76
         unique_together = (('product', 'user'),)
74
         verbose_name = _('Product review')
77
         verbose_name = _('Product review')
82
         }
85
         }
83
         return reverse('catalogue:reviews-detail', kwargs=kwargs)
86
         return reverse('catalogue:reviews-detail', kwargs=kwargs)
84
 
87
 
85
-    def __unicode__(self):
88
+    def __str__(self):
86
         return self.title
89
         return self.title
87
 
90
 
88
     def clean(self):
91
     def clean(self):
165
         review
168
         review
166
         """
169
         """
167
         if not user.is_authenticated():
170
         if not user.is_authenticated():
168
-            return False, u"Only signed in users can vote"
171
+            return False, _(u"Only signed in users can vote")
169
         vote = self.votes.model(review=self, user=user, delta=1)
172
         vote = self.votes.model(review=self, user=user, delta=1)
170
         try:
173
         try:
171
             vote.full_clean()
174
             vote.full_clean()
174
         return True, ""
177
         return True, ""
175
 
178
 
176
 
179
 
180
+@python_2_unicode_compatible
177
 class AbstractVote(models.Model):
181
 class AbstractVote(models.Model):
178
     """
182
     """
179
     Records user ratings as yes/no vote.
183
     Records user ratings as yes/no vote.
193
 
197
 
194
     class Meta:
198
     class Meta:
195
         abstract = True
199
         abstract = True
200
+        app_label = 'reviews'
196
         ordering = ['-date_created']
201
         ordering = ['-date_created']
197
         unique_together = (('user', 'review'),)
202
         unique_together = (('user', 'review'),)
198
         verbose_name = _('Vote')
203
         verbose_name = _('Vote')
199
         verbose_name_plural = _('Votes')
204
         verbose_name_plural = _('Votes')
200
 
205
 
201
-    def __unicode__(self):
206
+    def __str__(self):
202
         return u"%s vote for %s" % (self.delta, self.review)
207
         return u"%s vote for %s" % (self.delta, self.review)
203
 
208
 
204
     def clean(self):
209
     def clean(self):

+ 5
- 2
oscar/apps/catalogue/reviews/app.py View File

1
 from django.conf.urls import url
1
 from django.conf.urls import url
2
+from django.contrib.auth.decorators import login_required
2
 
3
 
3
 from oscar.core.application import Application
4
 from oscar.core.application import Application
4
 from oscar.core.loading import get_class
5
 from oscar.core.loading import get_class
17
         urls = [
18
         urls = [
18
             url(r'^(?P<pk>\d+)/$', self.detail_view.as_view(),
19
             url(r'^(?P<pk>\d+)/$', self.detail_view.as_view(),
19
                 name='reviews-detail'),
20
                 name='reviews-detail'),
20
-            url(r'^add/$', self.create_view.as_view(), name='reviews-add'),
21
-            url(r'^(?P<pk>\d+)/vote/$', self.vote_view.as_view(),
21
+            url(r'^add/$', self.create_view.as_view(),
22
+                name='reviews-add'),
23
+            url(r'^(?P<pk>\d+)/vote/$',
24
+                login_required(self.vote_view.as_view()),
22
                 name='reviews-vote'),
25
                 name='reviews-vote'),
23
             url(r'^$', self.list_view.as_view(), name='reviews-list'),
26
             url(r'^$', self.list_view.as_view(), name='reviews-list'),
24
         ]
27
         ]

+ 62
- 231
oscar/apps/catalogue/reviews/migrations/0001_initial.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
-import datetime
3
-from south.db import db
4
-from south.v2 import SchemaMigration
5
-from django.db import models
2
+from __future__ import unicode_literals
6
 
3
 
7
-from oscar.core.compat import AUTH_USER_MODEL, AUTH_USER_MODEL_NAME
4
+from django.db import models, migrations
5
+import oscar.core.validators
6
+import django.db.models.deletion
7
+from django.conf import settings
8
 
8
 
9
 
9
 
10
-class Migration(SchemaMigration):
10
+class Migration(migrations.Migration):
11
 
11
 
12
-    def forwards(self, orm):
13
-        # Adding model 'ProductReview'
14
-        db.create_table(u'reviews_productreview', (
15
-            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
16
-            ('product', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reviews', null=True, on_delete=models.SET_NULL, to=orm['catalogue.Product'])),
17
-            ('score', self.gf('django.db.models.fields.SmallIntegerField')()),
18
-            ('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
19
-            ('body', self.gf('django.db.models.fields.TextField')()),
20
-            ('user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='reviews', null=True, to=orm[AUTH_USER_MODEL])),
21
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
22
-            ('email', self.gf('django.db.models.fields.EmailField')(max_length=75, null=True, blank=True)),
23
-            ('homepage', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)),
24
-            ('status', self.gf('django.db.models.fields.SmallIntegerField')(default=1)),
25
-            ('total_votes', self.gf('django.db.models.fields.IntegerField')(default=0)),
26
-            ('delta_votes', self.gf('django.db.models.fields.IntegerField')(default=0, db_index=True)),
27
-            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
28
-        ))
29
-        db.send_create_signal(u'reviews', ['ProductReview'])
12
+    dependencies = [
13
+        ('catalogue', '0001_initial'),
14
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15
+    ]
30
 
16
 
31
-        # Adding unique constraint on 'ProductReview', fields ['product', 'user']
32
-        db.create_unique(u'reviews_productreview', ['product_id', 'user_id'])
33
-
34
-        # Adding model 'Vote'
35
-        db.create_table(u'reviews_vote', (
36
-            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
37
-            ('review', self.gf('django.db.models.fields.related.ForeignKey')(related_name='votes', to=orm['reviews.ProductReview'])),
38
-            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='review_votes', to=orm[AUTH_USER_MODEL])),
39
-            ('delta', self.gf('django.db.models.fields.SmallIntegerField')()),
40
-            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
41
-        ))
42
-        db.send_create_signal(u'reviews', ['Vote'])
43
-
44
-        # Adding unique constraint on 'Vote', fields ['user', 'review']
45
-        db.create_unique(u'reviews_vote', ['user_id', 'review_id'])
46
-
47
-
48
-    def backwards(self, orm):
49
-        # Removing unique constraint on 'Vote', fields ['user', 'review']
50
-        db.delete_unique(u'reviews_vote', ['user_id', 'review_id'])
51
-
52
-        # Removing unique constraint on 'ProductReview', fields ['product', 'user']
53
-        db.delete_unique(u'reviews_productreview', ['product_id', 'user_id'])
54
-
55
-        # Deleting model 'ProductReview'
56
-        db.delete_table(u'reviews_productreview')
57
-
58
-        # Deleting model 'Vote'
59
-        db.delete_table(u'reviews_vote')
60
-
61
-
62
-    models = {
63
-        u'auth.group': {
64
-            'Meta': {'object_name': 'Group'},
65
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
66
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
67
-            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
68
-        },
69
-        u'auth.permission': {
70
-            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
71
-            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
72
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
73
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
74
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
75
-        },
76
-        AUTH_USER_MODEL: {
77
-            'Meta': {'object_name': AUTH_USER_MODEL_NAME},
78
-            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
79
-            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
80
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
81
-            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
82
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
83
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
84
-            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
85
-            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
86
-            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
87
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
88
-            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
89
-            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
90
-            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
91
-        },
92
-        u'catalogue.attributeentity': {
93
-            'Meta': {'object_name': 'AttributeEntity'},
94
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
95
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
96
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'}),
97
-            'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': u"orm['catalogue.AttributeEntityType']"})
98
-        },
99
-        u'catalogue.attributeentitytype': {
100
-            'Meta': {'object_name': 'AttributeEntityType'},
101
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
102
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
103
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'})
104
-        },
105
-        u'catalogue.attributeoption': {
106
-            'Meta': {'object_name': 'AttributeOption'},
107
-            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': u"orm['catalogue.AttributeOptionGroup']"}),
108
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
109
-            'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
110
-        },
111
-        u'catalogue.attributeoptiongroup': {
112
-            'Meta': {'object_name': 'AttributeOptionGroup'},
113
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
115
-        },
116
-        u'catalogue.category': {
117
-            'Meta': {'ordering': "['full_name']", 'object_name': 'Category'},
118
-            'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
119
-            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
120
-            'full_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
121
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
122
-            'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
123
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
124
-            'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
125
-            'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
126
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'})
127
-        },
128
-        u'catalogue.option': {
129
-            'Meta': {'object_name': 'Option'},
130
-            'code': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}),
131
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
132
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
133
-            'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
134
-        },
135
-        u'catalogue.product': {
136
-            'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
137
-            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.ProductAttribute']", 'through': u"orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
138
-            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Category']", 'through': u"orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
139
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
140
-            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
141
-            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
142
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
143
-            'is_discountable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
144
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': u"orm['catalogue.Product']"}),
145
-            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'products'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['catalogue.ProductClass']"}),
146
-            'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
147
-            'rating': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
148
-            'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Product']", 'symmetrical': 'False', 'through': u"orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
149
-            'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': u"orm['catalogue.Product']"}),
150
-            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}),
151
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
152
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
153
-            'upc': ('oscar.models.fields.NullCharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
154
-        },
155
-        u'catalogue.productattribute': {
156
-            'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
157
-            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128'}),
158
-            'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
159
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
160
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
161
-            'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
162
-            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': u"orm['catalogue.ProductClass']"}),
163
-            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
164
-            'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
165
-        },
166
-        u'catalogue.productattributevalue': {
167
-            'Meta': {'object_name': 'ProductAttributeValue'},
168
-            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.ProductAttribute']"}),
169
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
170
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_values'", 'to': u"orm['catalogue.Product']"}),
171
-            'value_boolean': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
172
-            'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
173
-            'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
174
-            'value_file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
175
-            'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
176
-            'value_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
177
-            'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
178
-            'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
179
-            'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
180
-            'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
181
-        },
182
-        u'catalogue.productcategory': {
183
-            'Meta': {'ordering': "['product', 'category']", 'object_name': 'ProductCategory'},
184
-            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Category']"}),
185
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
186
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"})
187
-        },
188
-        u'catalogue.productclass': {
189
-            'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
190
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
191
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
192
-            'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
193
-            'requires_shipping': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
194
-            'slug': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}),
195
-            'track_stock': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
196
-        },
197
-        u'catalogue.productrecommendation': {
198
-            'Meta': {'object_name': 'ProductRecommendation'},
199
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
200
-            'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': u"orm['catalogue.Product']"}),
201
-            'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
202
-            'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"})
203
-        },
204
-        u'contenttypes.contenttype': {
205
-            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
206
-            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
207
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
208
-            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
209
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
210
-        },
211
-        u'reviews.productreview': {
212
-            'Meta': {'ordering': "['-delta_votes', 'id']", 'unique_together': "(('product', 'user'),)", 'object_name': 'ProductReview'},
213
-            'body': ('django.db.models.fields.TextField', [], {}),
214
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
215
-            'delta_votes': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
216
-            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
217
-            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
218
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
219
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
220
-            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['catalogue.Product']"}),
221
-            'score': ('django.db.models.fields.SmallIntegerField', [], {}),
222
-            'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
223
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
224
-            'total_votes': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
225
-            'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'reviews'", 'null': 'True', 'to': u"orm['{0}']".format(AUTH_USER_MODEL)})
226
-        },
227
-        u'reviews.vote': {
228
-            'Meta': {'ordering': "['-date_created']", 'unique_together': "(('user', 'review'),)", 'object_name': 'Vote'},
229
-            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
230
-            'delta': ('django.db.models.fields.SmallIntegerField', [], {}),
231
-            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
232
-            'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': u"orm['reviews.ProductReview']"}),
233
-            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'review_votes'", 'to': u"orm['{0}']".format(AUTH_USER_MODEL)})
234
-        }
235
-    }
236
-
237
-    complete_apps = ['reviews']
17
+    operations = [
18
+        migrations.CreateModel(
19
+            name='ProductReview',
20
+            fields=[
21
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
22
+                ('score', models.SmallIntegerField(verbose_name='Score', choices=[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)])),
23
+                ('title', models.CharField(verbose_name='Title', validators=[oscar.core.validators.non_whitespace], max_length=255)),
24
+                ('body', models.TextField(verbose_name='Body')),
25
+                ('name', models.CharField(verbose_name='Name', blank=True, max_length=255)),
26
+                ('email', models.EmailField(verbose_name='Email', blank=True, max_length=75)),
27
+                ('homepage', models.URLField(verbose_name='URL', blank=True)),
28
+                ('status', models.SmallIntegerField(verbose_name='Status', choices=[(0, 'Requires moderation'), (1, 'Approved'), (2, 'Rejected')], default=1)),
29
+                ('total_votes', models.IntegerField(verbose_name='Total Votes', default=0)),
30
+                ('delta_votes', models.IntegerField(verbose_name='Delta Votes', db_index=True, default=0)),
31
+                ('date_created', models.DateTimeField(auto_now_add=True)),
32
+                ('product', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to='catalogue.Product', null=True)),
33
+                ('user', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True)),
34
+            ],
35
+            options={
36
+                'verbose_name': 'Product review',
37
+                'verbose_name_plural': 'Product reviews',
38
+                'ordering': ['-delta_votes', 'id'],
39
+                'abstract': False,
40
+            },
41
+            bases=(models.Model,),
42
+        ),
43
+        migrations.AlterUniqueTogether(
44
+            name='productreview',
45
+            unique_together=set([('product', 'user')]),
46
+        ),
47
+        migrations.CreateModel(
48
+            name='Vote',
49
+            fields=[
50
+                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
51
+                ('delta', models.SmallIntegerField(verbose_name='Delta', choices=[(1, 'Up'), (-1, 'Down')])),
52
+                ('date_created', models.DateTimeField(auto_now_add=True)),
53
+                ('review', models.ForeignKey(to='reviews.ProductReview')),
54
+                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
55
+            ],
56
+            options={
57
+                'verbose_name': 'Vote',
58
+                'verbose_name_plural': 'Votes',
59
+                'ordering': ['-date_created'],
60
+                'abstract': False,
61
+            },
62
+            bases=(models.Model,),
63
+        ),
64
+        migrations.AlterUniqueTogether(
65
+            name='vote',
66
+            unique_together=set([('user', 'review')]),
67
+        ),
68
+    ]

+ 7
- 4
oscar/apps/catalogue/reviews/models.py View File

1
+from oscar.core.loading import is_model_registered
1
 from oscar.apps.catalogue.reviews.abstract_models import \
2
 from oscar.apps.catalogue.reviews.abstract_models import \
2
     AbstractProductReview, AbstractVote
3
     AbstractProductReview, AbstractVote
3
 
4
 
4
 
5
 
5
-class ProductReview(AbstractProductReview):
6
-    pass
6
+if not is_model_registered('reviews', 'ProductReview'):
7
+    class ProductReview(AbstractProductReview):
8
+        pass
7
 
9
 
8
 
10
 
9
-class Vote(AbstractVote):
10
-    pass
11
+if not is_model_registered('reviews', 'Vote'):
12
+    class Vote(AbstractVote):
13
+        pass

+ 237
- 0
oscar/apps/catalogue/reviews/south_migrations/0001_initial.py View File

1
+# -*- coding: utf-8 -*-
2
+import datetime
3
+from south.db import db
4
+from south.v2 import SchemaMigration
5
+from django.db import models
6
+
7
+from oscar.core.compat import AUTH_USER_MODEL, AUTH_USER_MODEL_NAME
8
+
9
+
10
+class Migration(SchemaMigration):
11
+
12
+    def forwards(self, orm):
13
+        # Adding model 'ProductReview'
14
+        db.create_table(u'reviews_productreview', (
15
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
16
+            ('product', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reviews', null=True, on_delete=models.SET_NULL, to=orm['catalogue.Product'])),
17
+            ('score', self.gf('django.db.models.fields.SmallIntegerField')()),
18
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
19
+            ('body', self.gf('django.db.models.fields.TextField')()),
20
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='reviews', null=True, to=orm[AUTH_USER_MODEL])),
21
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
22
+            ('email', self.gf('django.db.models.fields.EmailField')(max_length=75, null=True, blank=True)),
23
+            ('homepage', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)),
24
+            ('status', self.gf('django.db.models.fields.SmallIntegerField')(default=1)),
25
+            ('total_votes', self.gf('django.db.models.fields.IntegerField')(default=0)),
26
+            ('delta_votes', self.gf('django.db.models.fields.IntegerField')(default=0, db_index=True)),
27
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
28
+        ))
29
+        db.send_create_signal(u'reviews', ['ProductReview'])
30
+
31
+        # Adding unique constraint on 'ProductReview', fields ['product', 'user']
32
+        db.create_unique(u'reviews_productreview', ['product_id', 'user_id'])
33
+
34
+        # Adding model 'Vote'
35
+        db.create_table(u'reviews_vote', (
36
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
37
+            ('review', self.gf('django.db.models.fields.related.ForeignKey')(related_name='votes', to=orm['reviews.ProductReview'])),
38
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='review_votes', to=orm[AUTH_USER_MODEL])),
39
+            ('delta', self.gf('django.db.models.fields.SmallIntegerField')()),
40
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
41
+        ))
42
+        db.send_create_signal(u'reviews', ['Vote'])
43
+
44
+        # Adding unique constraint on 'Vote', fields ['user', 'review']
45
+        db.create_unique(u'reviews_vote', ['user_id', 'review_id'])
46
+
47
+
48
+    def backwards(self, orm):
49
+        # Removing unique constraint on 'Vote', fields ['user', 'review']
50
+        db.delete_unique(u'reviews_vote', ['user_id', 'review_id'])
51
+
52
+        # Removing unique constraint on 'ProductReview', fields ['product', 'user']
53
+        db.delete_unique(u'reviews_productreview', ['product_id', 'user_id'])
54
+
55
+        # Deleting model 'ProductReview'
56
+        db.delete_table(u'reviews_productreview')
57
+
58
+        # Deleting model 'Vote'
59
+        db.delete_table(u'reviews_vote')
60
+
61
+
62
+    models = {
63
+        u'auth.group': {
64
+            'Meta': {'object_name': 'Group'},
65
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
66
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
67
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
68
+        },
69
+        u'auth.permission': {
70
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
71
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
72
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
73
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
74
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
75
+        },
76
+        AUTH_USER_MODEL: {
77
+            'Meta': {'object_name': AUTH_USER_MODEL_NAME},
78
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
79
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
80
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
81
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
82
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
83
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
84
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
85
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
86
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
87
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
88
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
89
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
90
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
91
+        },
92
+        u'catalogue.attributeentity': {
93
+            'Meta': {'object_name': 'AttributeEntity'},
94
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
95
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
96
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'}),
97
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': u"orm['catalogue.AttributeEntityType']"})
98
+        },
99
+        u'catalogue.attributeentitytype': {
100
+            'Meta': {'object_name': 'AttributeEntityType'},
101
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
102
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
103
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'})
104
+        },
105
+        u'catalogue.attributeoption': {
106
+            'Meta': {'object_name': 'AttributeOption'},
107
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': u"orm['catalogue.AttributeOptionGroup']"}),
108
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
109
+            'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
110
+        },
111
+        u'catalogue.attributeoptiongroup': {
112
+            'Meta': {'object_name': 'AttributeOptionGroup'},
113
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
115
+        },
116
+        u'catalogue.category': {
117
+            'Meta': {'ordering': "['full_name']", 'object_name': 'Category'},
118
+            'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
119
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
120
+            'full_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
121
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
122
+            'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
123
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
124
+            'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
125
+            'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
126
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'})
127
+        },
128
+        u'catalogue.option': {
129
+            'Meta': {'object_name': 'Option'},
130
+            'code': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}),
131
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
132
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
133
+            'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
134
+        },
135
+        u'catalogue.product': {
136
+            'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
137
+            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.ProductAttribute']", 'through': u"orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
138
+            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Category']", 'through': u"orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
139
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
140
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
141
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
142
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
143
+            'is_discountable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
144
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': u"orm['catalogue.Product']"}),
145
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'products'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['catalogue.ProductClass']"}),
146
+            'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
147
+            'rating': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
148
+            'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Product']", 'symmetrical': 'False', 'through': u"orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
149
+            'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': u"orm['catalogue.Product']"}),
150
+            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}),
151
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
152
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
153
+            'upc': ('oscar.models.fields.NullCharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
154
+        },
155
+        u'catalogue.productattribute': {
156
+            'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
157
+            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128'}),
158
+            'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
159
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
160
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
161
+            'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
162
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': u"orm['catalogue.ProductClass']"}),
163
+            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
164
+            'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
165
+        },
166
+        u'catalogue.productattributevalue': {
167
+            'Meta': {'object_name': 'ProductAttributeValue'},
168
+            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.ProductAttribute']"}),
169
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
170
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_values'", 'to': u"orm['catalogue.Product']"}),
171
+            'value_boolean': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
172
+            'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
173
+            'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
174
+            'value_file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
175
+            'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
176
+            'value_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
177
+            'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
178
+            'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
179
+            'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
180
+            'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
181
+        },
182
+        u'catalogue.productcategory': {
183
+            'Meta': {'ordering': "['product', 'category']", 'object_name': 'ProductCategory'},
184
+            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Category']"}),
185
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
186
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"})
187
+        },
188
+        u'catalogue.productclass': {
189
+            'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
190
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
191
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
192
+            'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
193
+            'requires_shipping': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
194
+            'slug': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}),
195
+            'track_stock': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
196
+        },
197
+        u'catalogue.productrecommendation': {
198
+            'Meta': {'object_name': 'ProductRecommendation'},
199
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
200
+            'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': u"orm['catalogue.Product']"}),
201
+            'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
202
+            'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"})
203
+        },
204
+        u'contenttypes.contenttype': {
205
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
206
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
207
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
208
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
209
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
210
+        },
211
+        u'reviews.productreview': {
212
+            'Meta': {'ordering': "['-delta_votes', 'id']", 'unique_together': "(('product', 'user'),)", 'object_name': 'ProductReview'},
213
+            'body': ('django.db.models.fields.TextField', [], {}),
214
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
215
+            'delta_votes': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
216
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
217
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
218
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
219
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
220
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['catalogue.Product']"}),
221
+            'score': ('django.db.models.fields.SmallIntegerField', [], {}),
222
+            'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
223
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
224
+            'total_votes': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
225
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'reviews'", 'null': 'True', 'to': u"orm['{0}']".format(AUTH_USER_MODEL)})
226
+        },
227
+        u'reviews.vote': {
228
+            'Meta': {'ordering': "['-date_created']", 'unique_together': "(('user', 'review'),)", 'object_name': 'Vote'},
229
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
230
+            'delta': ('django.db.models.fields.SmallIntegerField', [], {}),
231
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
232
+            'review': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': u"orm['reviews.ProductReview']"}),
233
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'review_votes'", 'to': u"orm['{0}']".format(AUTH_USER_MODEL)})
234
+        }
235
+    }
236
+
237
+    complete_apps = ['reviews']

oscar/apps/catalogue/reviews/migrations/0002_no_null_in_charfields.py → oscar/apps/catalogue/reviews/south_migrations/0002_no_null_in_charfields.py View File


oscar/apps/catalogue/reviews/migrations/0003_auto__chg_field_productreview_name__chg_field_productreview_homepage__.py → oscar/apps/catalogue/reviews/south_migrations/0003_auto__chg_field_productreview_name__chg_field_productreview_homepage__.py View File


sites/us/apps/partner/migrations/__init__.py → oscar/apps/catalogue/reviews/south_migrations/__init__.py View File


+ 4
- 5
oscar/apps/catalogue/reviews/views.py View File

1
-from django.http import HttpResponseRedirect
2
-from django.shortcuts import get_object_or_404
1
+from django.shortcuts import get_object_or_404, redirect
3
 from django.views.generic import ListView, DetailView, CreateView, View
2
 from django.views.generic import ListView, DetailView, CreateView, View
4
 from django.contrib import messages
3
 from django.contrib import messages
5
 from oscar.core.loading import get_model
4
 from oscar.core.loading import get_model
6
 from django.utils.translation import ugettext_lazy as _
5
 from django.utils.translation import ugettext_lazy as _
7
 
6
 
8
 from oscar.core.loading import get_classes
7
 from oscar.core.loading import get_classes
8
+from oscar.core.utils import redirect_to_referrer
9
 from oscar.apps.catalogue.reviews.signals import review_added
9
 from oscar.apps.catalogue.reviews.signals import review_added
10
 
10
 
11
 ProductReviewForm, VoteForm = get_classes(
11
 ProductReviewForm, VoteForm = get_classes(
32
             else:
32
             else:
33
                 message = _("You can't leave a review for this product.")
33
                 message = _("You can't leave a review for this product.")
34
             messages.warning(self.request, message)
34
             messages.warning(self.request, message)
35
-            return HttpResponseRedirect(self.product.get_absolute_url())
35
+            return redirect(self.product.get_absolute_url())
36
 
36
 
37
         return super(CreateProductReview, self).dispatch(
37
         return super(CreateProductReview, self).dispatch(
38
             request, *args, **kwargs)
38
             request, *args, **kwargs)
98
             for error_list in form.errors.values():
98
             for error_list in form.errors.values():
99
                 for msg in error_list:
99
                 for msg in error_list:
100
                     messages.error(request, msg)
100
                     messages.error(request, msg)
101
-        return HttpResponseRedirect(
102
-            request.META.get('HTTP_REFERER', product.get_absolute_url()))
101
+        return redirect_to_referrer(request.META, product.get_absolute_url())
103
 
102
 
104
 
103
 
105
 class ProductReviewList(ListView):
104
 class ProductReviewList(ListView):

+ 403
- 0
oscar/apps/catalogue/south_migrations/0001_initial.py View File

1
+# encoding: utf-8
2
+import datetime
3
+from south.db import db
4
+from south.v2 import SchemaMigration
5
+from django.db import models
6
+
7
+class Migration(SchemaMigration):
8
+
9
+    def forwards(self, orm):
10
+
11
+        # Adding model 'ProductRecommendation'
12
+        db.create_table('catalogue_productrecommendation', (
13
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14
+            ('primary', self.gf('django.db.models.fields.related.ForeignKey')(related_name='primary_recommendations', to=orm['catalogue.Product'])),
15
+            ('recommendation', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
16
+            ('ranking', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=0)),
17
+        ))
18
+        db.send_create_signal('catalogue', ['ProductRecommendation'])
19
+
20
+        # Adding model 'ProductClass'
21
+        db.create_table('catalogue_productclass', (
22
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
23
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
24
+            ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128, db_index=True)),
25
+        ))
26
+        db.send_create_signal('catalogue', ['ProductClass'])
27
+
28
+        # Adding M2M table for field options on 'ProductClass'
29
+        db.create_table('catalogue_productclass_options', (
30
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
31
+            ('productclass', models.ForeignKey(orm['catalogue.productclass'], null=False)),
32
+            ('option', models.ForeignKey(orm['catalogue.option'], null=False))
33
+        ))
34
+        db.create_unique('catalogue_productclass_options', ['productclass_id', 'option_id'])
35
+
36
+        # Adding model 'Category'
37
+        db.create_table('catalogue_category', (
38
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
39
+            ('path', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
40
+            ('depth', self.gf('django.db.models.fields.PositiveIntegerField')()),
41
+            ('numchild', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
42
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
43
+            ('slug',
44
+             self.gf('django.db.models.fields.SlugField')(max_length=255, db_index=True)),
45
+            ('full_name',
46
+             self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
47
+        ))
48
+        db.send_create_signal('catalogue', ['Category'])
49
+
50
+        # Adding model 'ProductCategory'
51
+        db.create_table('catalogue_productcategory', (
52
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
53
+            ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
54
+            ('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Category'])),
55
+            ('is_canonical', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True)),
56
+        ))
57
+        db.send_create_signal('catalogue', ['ProductCategory'])
58
+
59
+        # Adding model 'Product'
60
+        db.create_table('catalogue_product', (
61
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
62
+            ('upc', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=64, null=True, blank=True)),
63
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='variants', null=True, to=orm['catalogue.Product'])),
64
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
65
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=255, db_index=True)),
66
+            ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
67
+            ('product_class', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ProductClass'], null=True)),
68
+            ('score', self.gf('django.db.models.fields.FloatField')(default=0.0, db_index=True)),
69
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
70
+            ('date_updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, db_index=True, blank=True)),
71
+        ))
72
+        db.send_create_signal('catalogue', ['Product'])
73
+
74
+        # Adding M2M table for field product_options on 'Product'
75
+        db.create_table('catalogue_product_product_options', (
76
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
77
+            ('product', models.ForeignKey(orm['catalogue.product'], null=False)),
78
+            ('option', models.ForeignKey(orm['catalogue.option'], null=False))
79
+        ))
80
+        db.create_unique('catalogue_product_product_options', ['product_id', 'option_id'])
81
+
82
+        # Adding M2M table for field related_products on 'Product'
83
+        db.create_table('catalogue_product_related_products', (
84
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
85
+            ('from_product', models.ForeignKey(orm['catalogue.product'], null=False)),
86
+            ('to_product', models.ForeignKey(orm['catalogue.product'], null=False))
87
+        ))
88
+        db.create_unique('catalogue_product_related_products', ['from_product_id', 'to_product_id'])
89
+
90
+        # Adding model 'ContributorRole'
91
+        db.create_table('catalogue_contributorrole', (
92
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
93
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=50)),
94
+            ('name_plural', self.gf('django.db.models.fields.CharField')(max_length=50)),
95
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)),
96
+        ))
97
+        db.send_create_signal('catalogue', ['ContributorRole'])
98
+
99
+        # Adding model 'Contributor'
100
+        db.create_table('catalogue_contributor', (
101
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
102
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
103
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=255, db_index=True)),
104
+        ))
105
+        db.send_create_signal('catalogue', ['Contributor'])
106
+
107
+        # Adding model 'ProductContributor'
108
+        db.create_table('catalogue_productcontributor', (
109
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
110
+            ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
111
+            ('contributor', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Contributor'])),
112
+            ('role', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ContributorRole'])),
113
+        ))
114
+        db.send_create_signal('catalogue', ['ProductContributor'])
115
+
116
+        # Adding model 'ProductAttribute'
117
+        db.create_table('catalogue_productattribute', (
118
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
119
+            ('product_class', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='attributes', null=True, to=orm['catalogue.ProductClass'])),
120
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
121
+            ('code', self.gf('django.db.models.fields.SlugField')(max_length=128, db_index=True)),
122
+            ('type', self.gf('django.db.models.fields.CharField')(default='text', max_length=20)),
123
+            ('option_group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.AttributeOptionGroup'], null=True, blank=True)),
124
+            ('entity_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.AttributeEntityType'], null=True, blank=True)),
125
+            ('required', self.gf('django.db.models.fields.BooleanField')(default=False)),
126
+        ))
127
+        db.send_create_signal('catalogue', ['ProductAttribute'])
128
+
129
+        # Adding model 'ProductAttributeValue'
130
+        db.create_table('catalogue_productattributevalue', (
131
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
132
+            ('attribute', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ProductAttribute'])),
133
+            ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])),
134
+            ('value_text', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
135
+            ('value_integer', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
136
+            ('value_boolean', self.gf('django.db.models.fields.BooleanField')(default=False)),
137
+            ('value_float', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)),
138
+            ('value_richtext', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
139
+            ('value_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)),
140
+            ('value_option', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.AttributeOption'], null=True, blank=True)),
141
+            ('value_entity', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.AttributeEntity'], null=True, blank=True)),
142
+        ))
143
+        db.send_create_signal('catalogue', ['ProductAttributeValue'])
144
+
145
+        # Adding model 'AttributeOptionGroup'
146
+        db.create_table('catalogue_attributeoptiongroup', (
147
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
148
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
149
+        ))
150
+        db.send_create_signal('catalogue', ['AttributeOptionGroup'])
151
+
152
+        # Adding model 'AttributeOption'
153
+        db.create_table('catalogue_attributeoption', (
154
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
155
+            ('group', self.gf('django.db.models.fields.related.ForeignKey')(related_name='options', to=orm['catalogue.AttributeOptionGroup'])),
156
+            ('option', self.gf('django.db.models.fields.CharField')(max_length=255)),
157
+        ))
158
+        db.send_create_signal('catalogue', ['AttributeOption'])
159
+
160
+        # Adding model 'AttributeEntity'
161
+        db.create_table('catalogue_attributeentity', (
162
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
163
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
164
+            ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=255, blank=True)),
165
+            ('type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='entities', to=orm['catalogue.AttributeEntityType'])),
166
+        ))
167
+        db.send_create_signal('catalogue', ['AttributeEntity'])
168
+
169
+        # Adding model 'AttributeEntityType'
170
+        db.create_table('catalogue_attributeentitytype', (
171
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
172
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
173
+            ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=255, blank=True)),
174
+        ))
175
+        db.send_create_signal('catalogue', ['AttributeEntityType'])
176
+
177
+        # Adding model 'Option'
178
+        db.create_table('catalogue_option', (
179
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
180
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
181
+            ('code', self.gf('django.db.models.fields.SlugField')(max_length=128, db_index=True)),
182
+            ('type', self.gf('django.db.models.fields.CharField')(default='Required', max_length=128)),
183
+        ))
184
+        db.send_create_signal('catalogue', ['Option'])
185
+
186
+        # Adding model 'ProductImage'
187
+        db.create_table('catalogue_productimage', (
188
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
189
+            ('product', self.gf('django.db.models.fields.related.ForeignKey')(related_name='images', to=orm['catalogue.Product'])),
190
+            ('original', self.gf('django.db.models.fields.files.ImageField')(max_length=100)),
191
+            ('caption', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True)),
192
+            ('display_order', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
193
+            ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
194
+        ))
195
+        db.send_create_signal('catalogue', ['ProductImage'])
196
+
197
+        # Adding unique constraint on 'ProductImage', fields ['product', 'display_order']
198
+        db.create_unique('catalogue_productimage', ['product_id', 'display_order'])
199
+
200
+
201
+    def backwards(self, orm):
202
+
203
+        # Removing unique constraint on 'ProductImage', fields ['product', 'display_order']
204
+        db.delete_unique('catalogue_productimage', ['product_id', 'display_order'])
205
+
206
+        # Deleting model 'ProductRecommendation'
207
+        db.delete_table('catalogue_productrecommendation')
208
+
209
+        # Deleting model 'ProductClass'
210
+        db.delete_table('catalogue_productclass')
211
+
212
+        # Removing M2M table for field options on 'ProductClass'
213
+        db.delete_table('catalogue_productclass_options')
214
+
215
+        # Deleting model 'Category'
216
+        db.delete_table('catalogue_category')
217
+
218
+        # Deleting model 'ProductCategory'
219
+        db.delete_table('catalogue_productcategory')
220
+
221
+        # Deleting model 'Product'
222
+        db.delete_table('catalogue_product')
223
+
224
+        # Removing M2M table for field product_options on 'Product'
225
+        db.delete_table('catalogue_product_product_options')
226
+
227
+        # Removing M2M table for field related_products on 'Product'
228
+        db.delete_table('catalogue_product_related_products')
229
+
230
+        # Deleting model 'ContributorRole'
231
+        db.delete_table('catalogue_contributorrole')
232
+
233
+        # Deleting model 'Contributor'
234
+        db.delete_table('catalogue_contributor')
235
+
236
+        # Deleting model 'ProductContributor'
237
+        db.delete_table('catalogue_productcontributor')
238
+
239
+        # Deleting model 'ProductAttribute'
240
+        db.delete_table('catalogue_productattribute')
241
+
242
+        # Deleting model 'ProductAttributeValue'
243
+        db.delete_table('catalogue_productattributevalue')
244
+
245
+        # Deleting model 'AttributeOptionGroup'
246
+        db.delete_table('catalogue_attributeoptiongroup')
247
+
248
+        # Deleting model 'AttributeOption'
249
+        db.delete_table('catalogue_attributeoption')
250
+
251
+        # Deleting model 'AttributeEntity'
252
+        db.delete_table('catalogue_attributeentity')
253
+
254
+        # Deleting model 'AttributeEntityType'
255
+        db.delete_table('catalogue_attributeentitytype')
256
+
257
+        # Deleting model 'Option'
258
+        db.delete_table('catalogue_option')
259
+
260
+        # Deleting model 'ProductImage'
261
+        db.delete_table('catalogue_productimage')
262
+
263
+
264
+    models = {
265
+        'catalogue.attributeentity': {
266
+            'Meta': {'object_name': 'AttributeEntity'},
267
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
268
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
269
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
270
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': "orm['catalogue.AttributeEntityType']"})
271
+        },
272
+        'catalogue.attributeentitytype': {
273
+            'Meta': {'object_name': 'AttributeEntityType'},
274
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
276
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'})
277
+        },
278
+        'catalogue.attributeoption': {
279
+            'Meta': {'object_name': 'AttributeOption'},
280
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': "orm['catalogue.AttributeOptionGroup']"}),
281
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
282
+            'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
283
+        },
284
+        'catalogue.attributeoptiongroup': {
285
+            'Meta': {'object_name': 'AttributeOptionGroup'},
286
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
287
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
288
+        },
289
+        'catalogue.category': {
290
+            'Meta': {'ordering': "['name']", 'object_name': 'Category'},
291
+            'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
292
+            'full_name': ('django.db.models.fields.CharField', [],
293
+                          {'max_length': '255', 'db_index': 'True'}),
294
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
295
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
296
+            'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
297
+            'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
298
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length':
299
+                                                               '255', 'db_index': 'True'})
300
+        },
301
+        'catalogue.contributor': {
302
+            'Meta': {'object_name': 'Contributor'},
303
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
304
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
305
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'})
306
+        },
307
+        'catalogue.contributorrole': {
308
+            'Meta': {'object_name': 'ContributorRole'},
309
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
310
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
311
+            'name_plural': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
312
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
313
+        },
314
+        'catalogue.option': {
315
+            'Meta': {'object_name': 'Option'},
316
+            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
317
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
318
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
319
+            'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
320
+        },
321
+        'catalogue.product': {
322
+            'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
323
+            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.ProductAttribute']", 'through': "orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
324
+            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Category']", 'through': "orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
325
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
326
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
327
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
328
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
329
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': "orm['catalogue.Product']"}),
330
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductClass']", 'null': 'True'}),
331
+            'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
332
+            'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Product']", 'symmetrical': 'False', 'through': "orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
333
+            'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': "orm['catalogue.Product']"}),
334
+            'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}),
335
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
336
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
337
+            'upc': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'})
338
+        },
339
+        'catalogue.productattribute': {
340
+            'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
341
+            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128', 'db_index': 'True'}),
342
+            'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
343
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
344
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
345
+            'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
346
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': "orm['catalogue.ProductClass']"}),
347
+            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
348
+            'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
349
+        },
350
+        'catalogue.productattributevalue': {
351
+            'Meta': {'object_name': 'ProductAttributeValue'},
352
+            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductAttribute']"}),
353
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
354
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
355
+            'value_boolean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
356
+            'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
357
+            'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
358
+            'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
359
+            'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
360
+            'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
361
+            'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
362
+            'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
363
+        },
364
+        'catalogue.productcategory': {
365
+            'Meta': {'ordering': "['-is_canonical']", 'object_name': 'ProductCategory'},
366
+            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Category']"}),
367
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
368
+            'is_canonical': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
369
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
370
+        },
371
+        'catalogue.productclass': {
372
+            'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
373
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
374
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
375
+            'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
376
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'})
377
+        },
378
+        'catalogue.productcontributor': {
379
+            'Meta': {'object_name': 'ProductContributor'},
380
+            'contributor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Contributor']"}),
381
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
382
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}),
383
+            'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ContributorRole']"})
384
+        },
385
+        'catalogue.productimage': {
386
+            'Meta': {'ordering': "['display_order']", 'unique_together': "(('product', 'display_order'),)", 'object_name': 'ProductImage'},
387
+            'caption': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
388
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
389
+            'display_order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
390
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
391
+            'original': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
392
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'images'", 'to': "orm['catalogue.Product']"})
393
+        },
394
+        'catalogue.productrecommendation': {
395
+            'Meta': {'object_name': 'ProductRecommendation'},
396
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
397
+            'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': "orm['catalogue.Product']"}),
398
+            'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
399
+            'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"})
400
+        }
401
+    }
402
+
403
+    complete_apps = ['catalogue']

oscar/apps/catalogue/migrations/0002_auto__add_field_product_status__add_field_category_description__add_fi.py → oscar/apps/catalogue/south_migrations/0002_auto__add_field_product_status__add_field_category_description__add_fi.py View File


oscar/apps/catalogue/migrations/0003_auto__add_unique_product_upc__chg_field_productcontributor_role.py → oscar/apps/catalogue/south_migrations/0003_auto__add_unique_product_upc__chg_field_productcontributor_role.py View File


oscar/apps/catalogue/migrations/0004_auto__chg_field_productattributevalue_value_boolean.py → oscar/apps/catalogue/south_migrations/0004_auto__chg_field_productattributevalue_value_boolean.py View File


oscar/apps/catalogue/migrations/0005_auto__chg_field_productattributevalue_value_boolean__add_field_product.py → oscar/apps/catalogue/south_migrations/0005_auto__chg_field_productattributevalue_value_boolean__add_field_product.py View File


oscar/apps/catalogue/migrations/0006_auto__add_field_product_is_discountable.py → oscar/apps/catalogue/south_migrations/0006_auto__add_field_product_is_discountable.py View File


oscar/apps/catalogue/migrations/0007_auto__add_field_productclass_requires_shipping__add_field_productclass.py → oscar/apps/catalogue/south_migrations/0007_auto__add_field_productclass_requires_shipping__add_field_productclass.py View File


oscar/apps/catalogue/migrations/0008_auto__add_unique_option_code.py → oscar/apps/catalogue/south_migrations/0008_auto__add_unique_option_code.py View File


oscar/apps/catalogue/migrations/0009_auto__add_field_product_rating.py → oscar/apps/catalogue/south_migrations/0009_auto__add_field_product_rating.py View File


oscar/apps/catalogue/migrations/0010_call_update_product_ratings.py → oscar/apps/catalogue/south_migrations/0010_call_update_product_ratings.py View File


oscar/apps/catalogue/migrations/0011_auto__chg_field_productimage_original__chg_field_category_image.py → oscar/apps/catalogue/south_migrations/0011_auto__chg_field_productimage_original__chg_field_category_image.py View File


oscar/apps/catalogue/migrations/0012_auto__chg_field_productattributevalue_value_boolean.py → oscar/apps/catalogue/south_migrations/0012_auto__chg_field_productattributevalue_value_boolean.py View File


oscar/apps/catalogue/migrations/0013_add_file_attributes.py → oscar/apps/catalogue/south_migrations/0013_add_file_attributes.py View File


oscar/apps/catalogue/migrations/0014_auto__del_field_productcategory_is_canonical.py → oscar/apps/catalogue/south_migrations/0014_auto__del_field_productcategory_is_canonical.py View File


oscar/apps/catalogue/migrations/0015_auto__chg_field_product_upc.py → oscar/apps/catalogue/south_migrations/0015_auto__chg_field_product_upc.py View File


oscar/apps/catalogue/migrations/0016_customer.py → oscar/apps/catalogue/south_migrations/0016_customer.py View File


oscar/apps/catalogue/migrations/0017_auto__del_contributor__del_productcontributor__del_contributorrole__de.py → oscar/apps/catalogue/south_migrations/0017_auto__del_contributor__del_productcontributor__del_contributorrole__de.py View File


oscar/apps/catalogue/migrations/0018_auto__chg_field_product_product_class.py → oscar/apps/catalogue/south_migrations/0018_auto__chg_field_product_product_class.py View File


oscar/apps/catalogue/migrations/0019_no_null_in_charfields.py → oscar/apps/catalogue/south_migrations/0019_no_null_in_charfields.py View File


oscar/apps/catalogue/migrations/0020_auto__chg_field_productattributevalue_value_text__chg_field_productatt.py → oscar/apps/catalogue/south_migrations/0020_auto__chg_field_productattributevalue_value_text__chg_field_productatt.py View File


oscar/apps/catalogue/migrations/0021_auto__add_unique_productattributevalue_attribute_product__add_unique_p.py → oscar/apps/catalogue/south_migrations/0021_auto__add_unique_productattributevalue_attribute_product__add_unique_p.py View File


oscar/apps/catalogue/migrations/0022_auto__del_field_product_score.py → oscar/apps/catalogue/south_migrations/0022_auto__del_field_product_score.py View File


oscar/apps/catalogue/migrations/0023_auto.py → oscar/apps/catalogue/south_migrations/0023_auto.py View File


oscar/apps/catalogue/migrations/0024_auto__del_attributeentity__del_attributeentitytype__del_field_producta.py → oscar/apps/catalogue/south_migrations/0024_auto__del_attributeentity__del_attributeentitytype__del_field_producta.py View File


oscar/apps/catalogue/migrations/0025_auto__add_field_product_structure.py → oscar/apps/catalogue/south_migrations/0025_auto__add_field_product_structure.py View File


oscar/apps/catalogue/migrations/0026_determine_product_structure.py → oscar/apps/catalogue/south_migrations/0026_determine_product_structure.py View File


sites/us/apps/shipping/migrations/__init__.py → oscar/apps/catalogue/south_migrations/__init__.py View File


+ 10
- 7
oscar/apps/catalogue/views.py View File

23
     model = Product
23
     model = Product
24
     view_signal = product_viewed
24
     view_signal = product_viewed
25
     template_folder = "catalogue"
25
     template_folder = "catalogue"
26
+
27
+    # Whether to redirect to the URL with the right path
26
     enforce_paths = True
28
     enforce_paths = True
27
 
29
 
30
+    # Whether to redirect child products to their parent's URL
31
+    enforce_parent = True
32
+
28
     def get(self, request, **kwargs):
33
     def get(self, request, **kwargs):
29
         """
34
         """
30
         Ensures that the correct URL is used before rendering a response
35
         Ensures that the correct URL is used before rendering a response
47
             return super(ProductDetailView, self).get_object(queryset)
52
             return super(ProductDetailView, self).get_object(queryset)
48
 
53
 
49
     def redirect_if_necessary(self, current_path, product):
54
     def redirect_if_necessary(self, current_path, product):
50
-        if self.enforce_paths:
51
-            if product.is_child:
52
-                return HttpResponsePermanentRedirect(
53
-                    product.parent.get_absolute_url())
55
+        if self.enforce_parent and product.is_child:
56
+            return HttpResponsePermanentRedirect(
57
+                product.parent.get_absolute_url())
54
 
58
 
59
+        if self.enforce_paths:
55
             expected_path = product.get_absolute_url()
60
             expected_path = product.get_absolute_url()
56
             if expected_path != urlquote(current_path):
61
             if expected_path != urlquote(current_path):
57
                 return HttpResponsePermanentRedirect(expected_path)
62
                 return HttpResponsePermanentRedirect(expected_path)
58
 
63
 
59
     def get_context_data(self, **kwargs):
64
     def get_context_data(self, **kwargs):
60
         ctx = super(ProductDetailView, self).get_context_data(**kwargs)
65
         ctx = super(ProductDetailView, self).get_context_data(**kwargs)
61
-        reviews = self.get_reviews()
62
-        ctx['reviews'] = reviews
66
+        ctx['reviews'] = self.get_reviews()
63
         ctx['alert_form'] = self.get_alert_form()
67
         ctx['alert_form'] = self.get_alert_form()
64
         ctx['has_active_alert'] = self.get_alert_status()
68
         ctx['has_active_alert'] = self.get_alert_status()
65
-
66
         return ctx
69
         return ctx
67
 
70
 
68
     def get_alert_status(self):
71
     def get_alert_status(self):

+ 1
- 0
oscar/apps/checkout/__init__.py View File

1
+default_app_config = 'oscar.apps.checkout.config.CheckoutConfig'

+ 0
- 0
oscar/apps/checkout/config.py View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save