diff --git a/examples/recreate_project_tables.sh b/examples/recreate_project_tables.sh index 953b5e3ae4e..36d0d031612 100755 --- a/examples/recreate_project_tables.sh +++ b/examples/recreate_project_tables.sh @@ -14,7 +14,7 @@ then fi echo "Recreating all tables in $PROJECT_FOLDER" echo "Dropping tables" -$MANAGE_COMMAND sqlclear customer promotions analytics payment reviews offer shipping order basket partner image address product datacash | \ +$MANAGE_COMMAND sqlclear customer promotions analytics payment reviews offer shipping order basket partner address product datacash | \ awk 'BEGIN {print "set foreign_key_checks=0;"} {print $0}' | \ $MANAGE_COMMAND dbshell && \ $MANAGE_COMMAND syncdb diff --git a/examples/vanilla/settings.py b/examples/vanilla/settings.py index 5ca85be5ba4..6b8f8490f13 100644 --- a/examples/vanilla/settings.py +++ b/examples/vanilla/settings.py @@ -93,7 +93,7 @@ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware', + #'debug_toolbar.middleware.DebugToolbarMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.transaction.TransactionMiddleware', 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', @@ -181,7 +181,7 @@ 'django.contrib.admin', 'django.contrib.flatpages', # External apps - #'django_extensions', + 'django_extensions', 'haystack', 'debug_toolbar', # Apps from oscar @@ -205,7 +205,7 @@ 'oscar.apps.promotions', 'oscar.apps.reports', 'oscar.apps.search', - #'pyzen', + 'pyzen', ) AUTHENTICATION_BACKENDS = ( diff --git a/oscar/app.py b/oscar/app.py index e3d7e4fe9bd..c5b309362a6 100644 --- a/oscar/app.py +++ b/oscar/app.py @@ -1,4 +1,5 @@ from django.conf.urls.defaults import patterns, url, include + from oscar.core.application import Application from oscar.apps.product.app import application as product_app from oscar.apps.customer.app import application as customer_app diff --git a/oscar/apps/address/forms.py b/oscar/apps/address/forms.py index 77f4946f916..24d0d6e6be0 100644 --- a/oscar/apps/address/forms.py +++ b/oscar/apps/address/forms.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- from django.forms import ModelForm +from django.db.models import get_model -from oscar.core.loading import import_module -import_module('address.models', ['UserAddress'], locals()) +UserAddress = get_model('address', 'useraddress') class UserAddressForm(ModelForm): diff --git a/oscar/apps/basket/abstract_models.py b/oscar/apps/basket/abstract_models.py index e5ecb79a13c..84733f5c46c 100644 --- a/oscar/apps/basket/abstract_models.py +++ b/oscar/apps/basket/abstract_models.py @@ -15,7 +15,7 @@ class AbstractBasket(models.Model): - u"""Basket object""" + """Basket object""" # Baskets can be anonymously owned (which are merged if the user signs in) owner = models.ForeignKey('auth.User', related_name='baskets', null=True) STATUS_CHOICES = ( @@ -57,13 +57,13 @@ def all_lines(self): # ============ def flush(self): - u"""Remove all lines from basket.""" + """Remove all lines from basket.""" if self.status == FROZEN: raise PermissionDenied("A frozen basket cannot be flushed") self.lines_all().delete() def add_product(self, item, quantity=1, options=[]): - u""" + """ Convenience method for adding products to a basket The 'options' list should contains dicts with keys 'option' and 'value' @@ -82,7 +82,7 @@ def add_product(self, item, quantity=1, options=[]): line.attributes.create(line=line, option=option_dict['option'], value=option_dict['value']) def set_discounts(self, discounts): - u""" + """ Sets the discounts that apply to this basket. This should be a list of dictionaries @@ -90,7 +90,7 @@ def set_discounts(self, discounts): self.discounts = discounts def merge_line(self, line): - u""" + """ For transferring a line from another basket to this one. This is used with the "Saved" basket functionality. @@ -132,7 +132,7 @@ def thaw(self): self.save() def set_as_submitted(self): - u"""Mark this basket as submitted.""" + """Mark this basket as submitted.""" self.status = SUBMITTED self.date_submitted = datetime.datetime.now() self.save() @@ -142,7 +142,7 @@ def set_as_submitted(self): # ======= def _create_line_reference(self, item, options): - u""" + """ Returns a reference string for a line based on the item and its options. """ @@ -151,7 +151,7 @@ def _create_line_reference(self, item, options): return "%d_%s" % (item.id, zlib.crc32(str(options))) def _get_total(self, property): - u""" + """ For executing a named method on each line of the basket and returning the total. """ @@ -166,37 +166,37 @@ def _get_total(self, property): @property def is_empty(self): - u"""Return bool based on basket having 0 lines""" + """Return bool based on basket having 0 lines""" return self.num_lines == 0 @property def total_excl_tax(self): - u"""Return total line price excluding tax""" + """Return total line price excluding tax""" return self._get_total('line_price_excl_tax_and_discounts') @property def total_tax(self): - u"""Return total tax for a line""" + """Return total tax for a line""" return self._get_total('line_tax') @property def total_incl_tax(self): - u"""Return total price for a line including tax""" + """Return total price for a line including tax""" return self._get_total('line_price_incl_tax_and_discounts') @property def num_lines(self): - u"""Return number of lines""" + """Return number of lines""" return self.all_lines().count() @property def num_items(self): - u"""Return number of items""" + """Return number of items""" return reduce(lambda num,line: num+line.quantity, self.all_lines(), 0) @property def num_items_without_discount(self): - u"""Return number of items""" + """Return number of items""" num = 0 for line in self.all_lines(): num += line.quantity_without_discount @@ -216,7 +216,7 @@ def time_since_creation(self, test_datetime=None): class AbstractLine(models.Model): - u"""A line of a basket (product and a quantity)""" + """A line of a basket (product and a quantity)""" basket = models.ForeignKey('basket.Basket', related_name='lines') # This is to determine which products belong to the same line @@ -241,7 +241,7 @@ def __unicode__(self): return u"%s, Product '%s', quantity %d" % (self.basket, self.product, self.quantity) def save(self, *args, **kwargs): - u"""Saves a line or deletes if it's quanity is 0""" + """Saves a line or deletes if it's quanity is 0""" if self.basket.status not in (OPEN, SAVED): raise PermissionDenied("You cannot modify a %s basket" % self.basket.status.lower()) if self.quantity == 0: @@ -260,7 +260,7 @@ def consume(self, quantity): self._affected_quantity += quantity def get_price_breakdown(self): - u""" + """ Returns a breakdown of line prices after discounts have been applied. """ @@ -315,22 +315,22 @@ def discount_value(self): @property def unit_price_excl_tax(self): - u"""Return unit price excluding tax""" + """Return unit price excluding tax""" return self._get_stockrecord_property('price_excl_tax') @property def unit_tax(self): - u"""Return tax of a unit""" + """Return tax of a unit""" return self._get_stockrecord_property('price_tax') @property def unit_price_incl_tax(self): - u"""Return unit price including tax""" + """Return unit price including tax""" return self._get_stockrecord_property('price_incl_tax') @property def line_price_excl_tax(self): - u"""Return line price excluding tax""" + """Return line price excluding tax""" return self.quantity * self.unit_price_excl_tax @property @@ -339,12 +339,12 @@ def line_price_excl_tax_and_discounts(self): @property def line_tax(self): - u"""Return line tax""" + """Return line tax""" return self.quantity * self.unit_tax @property def line_price_incl_tax(self): - u"""Return line price including tax""" + """Return line price including tax""" return self.quantity * self.unit_price_incl_tax @property @@ -353,7 +353,7 @@ def line_price_incl_tax_and_discounts(self): @property def description(self): - u"""Return product description""" + """Return product description""" d = str(self.product) ops = [] for attribute in self.attributes.all(): @@ -364,7 +364,7 @@ def description(self): class AbstractLineAttribute(models.Model): - u"""An attribute of a basket line""" + """An attribute of a basket line""" line = models.ForeignKey('basket.Line', related_name='attributes') option = models.ForeignKey('product.Option') value = models.CharField(_("Value"), max_length=255) diff --git a/oscar/apps/basket/app.py b/oscar/apps/basket/app.py index 48e99cf25a0..fc2269cbf53 100644 --- a/oscar/apps/basket/app.py +++ b/oscar/apps/basket/app.py @@ -1,7 +1,9 @@ from django.conf.urls.defaults import patterns, url + from oscar.apps.basket.views import BasketView, SavedView, VoucherView, VoucherAddView, BasketAddView from oscar.core.application import Application + class BasketApplication(Application): name = 'basket' summary_view = BasketView diff --git a/oscar/apps/basket/forms.py b/oscar/apps/basket/forms.py index d94ba36f0c0..336e096be69 100644 --- a/oscar/apps/basket/forms.py +++ b/oscar/apps/basket/forms.py @@ -4,6 +4,7 @@ basketline_model = get_model('basket', 'line') basket_model = get_model('basket', 'basket') + class BasketLineForm(forms.ModelForm): save_for_later = forms.BooleanField(initial=False, required=False) @@ -26,10 +27,20 @@ class BasketVoucherForm(forms.Form): def __init__(self, *args, **kwargs): return super(BasketVoucherForm, self).__init__(*args,**kwargs) + class AddToBasketForm(forms.Form): product_id = forms.IntegerField(widget=forms.HiddenInput(), min_value=1) quantity = forms.IntegerField(initial=1, min_value=1) + def __init__(self, instance, *args, **kwargs): + super(AddToBasketForm, self).__init__(*args, **kwargs) + self.instance = instance + if instance: + if instance.is_group: + self._create_group_product_fields(instance) + else: + self._create_product_fields(instance) + def _create_group_product_fields(self, item): u""" Adds the fields for a "group"-type product (eg, a parent product with a @@ -57,11 +68,4 @@ def _add_option_field(self, item, option): """ self.fields[option.code] = forms.CharField() - def __init__(self, instance, *args, **kwargs): - self.instance = instance - if instance: - if instance.is_group: - self._create_group_product_fields(instance) - else: - self._create_product_fields(instance) - super(AddToBasketForm, self).__init__(*args, **kwargs) + diff --git a/oscar/apps/basket/tests.py b/oscar/apps/basket/tests.py index 4cf452b84ec..2cd8bacc069 100644 --- a/oscar/apps/basket/tests.py +++ b/oscar/apps/basket/tests.py @@ -9,7 +9,7 @@ class ViewTest(TwillTestCase): def test_for_smoke(self): - self.visit('oscar-basket') + self.visit('basket:summary') self.assertResponseCodeIs(200) self.assertPageContains('Basket') self.assertPageTitleMatches('Oscar') @@ -46,14 +46,14 @@ def setUp(self): self.client = Client() def test_empty_basket_view(self): - url = reverse('basket') + url = reverse('basket:summary') response = self.client.get(url) self.assertEquals(200, response.status_code) self.assertEquals(0, response.context['basket'].num_lines) def test_anonymous_add_to_basket_creates_cookie(self): dummy_product = create_product() - url = reverse('basket-add') + url = reverse('basket:add') post_params = {'product_id': dummy_product.id, 'action': 'add', 'quantity': 1} diff --git a/oscar/apps/basket/views.py b/oscar/apps/basket/views.py index 826b391778d..a7d40254607 100644 --- a/oscar/apps/basket/views.py +++ b/oscar/apps/basket/views.py @@ -3,20 +3,21 @@ from django.db.models import get_model from django.http import HttpResponseRedirect, Http404 from django.views.generic import ListView, FormView +from django.forms.models import modelformset_factory + from extra_views import ModelFormsetView from oscar.apps.basket.forms import BasketLineForm, AddToBasketForm, \ BasketVoucherForm, SavedLineForm -from django.forms.models import modelformset_factory + class BasketView(ModelFormsetView): - model = get_model('basket', 'line') - basket_model = get_model('basket', 'basket') + model = get_model('basket', 'Line') + basket_model = get_model('basket', 'Basket') form_class = BasketLineForm extra = 0 can_delete = True template_name='basket/basket.html' - def get_queryset(self): return self.request.basket.lines.all() @@ -160,10 +161,10 @@ def formset_valid(self, formset): messages.info(self.request, msg) real_basket = self.request.basket real_basket.merge_line(form.instance) - return HttpResponseRedirect(self.request.META.get('HTTP_REFERER',reverse('basket:summary'))) + return HttpResponseRedirect(self.request.META.get('HTTP_REFERER', reverse('basket:summary'))) def formset_invalid(self, formset): - return HttpResponseRedirect(self.request.META.get('HTTP_REFERER',reverse('basket:summary'))) + return HttpResponseRedirect(self.request.META.get('HTTP_REFERER', reverse('basket:summary'))) # def do_remove_voucher(self, basket): diff --git a/oscar/apps/checkout/tests.py b/oscar/apps/checkout/tests.py index fffd368d0d9..590b2b271c9 100644 --- a/oscar/apps/checkout/tests.py +++ b/oscar/apps/checkout/tests.py @@ -19,7 +19,7 @@ def test_anonymous_checkout(self): # Add a product to the basket p = create_product(price=D('10.00')) - response = self.client.post(reverse('oscar-basket'), {'action': 'add', + response = self.client.post(reverse('basket:add'), {'action': 'add', 'product_id': str(p.id), 'quantity': 1}) self.assertEqual(302, response.status_code) diff --git a/oscar/apps/customer/app.py b/oscar/apps/customer/app.py index e4eafffdc4c..00328c5f9e1 100644 --- a/oscar/apps/customer/app.py +++ b/oscar/apps/customer/app.py @@ -1,5 +1,6 @@ from django.conf.urls.defaults import patterns, url from django.contrib.auth.decorators import login_required + from oscar.apps.customer.views import AccountSummaryView, OrderHistoryView, \ OrderDetailView, OrderLineView, AddressListView, AddressCreateView, \ AddressUpdateView, AddressDeleteView, EmailHistoryView, EmailDetailView, \ diff --git a/oscar/apps/customer/auth_backends.py b/oscar/apps/customer/auth_backends.py index e7223d8d45b..bb92aef0e96 100644 --- a/oscar/apps/customer/auth_backends.py +++ b/oscar/apps/customer/auth_backends.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import User from django.contrib.auth.backends import ModelBackend + class Emailbackend(ModelBackend): def authenticate(self, email=None, password=None, *args, **kwargs): if not email: diff --git a/oscar/apps/customer/forms.py b/oscar/apps/customer/forms.py index b0ab2ee57a7..13bc6b65286 100644 --- a/oscar/apps/customer/forms.py +++ b/oscar/apps/customer/forms.py @@ -1,10 +1,12 @@ +import string +import random + from django.contrib.auth.forms import AuthenticationForm from django.utils.translation import ugettext_lazy as _ from django import forms from django.contrib.auth.models import User from django.contrib.auth import authenticate -import string -import random + def generate_username(): uname = ''.join([random.choice(string.letters + string.digits + '_') for i in range(30)]) diff --git a/oscar/apps/customer/views.py b/oscar/apps/customer/views.py index 10a6850cc45..1eedfb66e80 100644 --- a/oscar/apps/customer/views.py +++ b/oscar/apps/customer/views.py @@ -6,16 +6,15 @@ from django.http import HttpResponseRedirect from django.contrib import messages from django.utils.translation import ugettext as _ -from oscar.apps.address.forms import UserAddressForm -from oscar.views.generic import PostActionMixin -from oscar.apps.customer.forms import EmailAuthenticationForm, EmailUserCreationForm from django.contrib.auth import authenticate from django.contrib.auth import login as auth_login - from django.conf import settings - from django.db.models import get_model +from oscar.apps.address.forms import UserAddressForm +from oscar.views.generic import PostActionMixin +from oscar.apps.customer.forms import EmailAuthenticationForm, EmailUserCreationForm + order_model = get_model('order', 'Order') order_line_model = get_model('order', 'Line') basket_model = get_model('basket', 'Basket') @@ -24,14 +23,14 @@ class AccountSummaryView(ListView): - u"""Customer order history""" + """Customer order history""" context_object_name = "orders" template_name = 'customer/profile.html' paginate_by = 20 model = order_model def get_queryset(self): - u"""Return a customer's orders""" + """Return a customer's orders""" return self.model._default_manager.filter(user=self.request.user)[0:5] @@ -106,34 +105,34 @@ class EmailHistoryView(ListView): paginate_by = 20 def get_queryset(self): - u"""Return a customer's orders""" + """Return a customer's orders""" return email_model._default_manager.filter(user=self.request.user) class EmailDetailView(DetailView): - u"""Customer order details""" + """Customer order details""" template_name = "customer/email.html" context_object_name = 'email' def get_object(self): - u"""Return an order object or 404""" + """Return an order object or 404""" return get_object_or_404(email_model, user=self.request.user, id=self.kwargs['email_id']) class OrderHistoryView(ListView): - u"""Customer order history""" + """Customer order history""" context_object_name = "orders" template_name = 'customer/order-history.html' paginate_by = 20 model = order_model def get_queryset(self): - u"""Return a customer's orders""" + """Return a customer's orders""" return self.model._default_manager.filter(user=self.request.user) class OrderDetailView(DetailView): - u"""Customer order details""" + """Customer order details""" model = order_model def get_template_names(self): @@ -144,10 +143,10 @@ def get_object(self): class OrderLineView(DetailView, PostActionMixin): - u"""Customer order line""" + """Customer order line""" def get_object(self): - u"""Return an order object or 404""" + """Return an order object or 404""" order = get_object_or_404(order_model, user=self.request.user, number=self.kwargs['order_number']) return order.lines.get(id=self.kwargs['line_id']) @@ -171,13 +170,13 @@ def do_reorder(self, line): class AddressListView(ListView): - u"""Customer address book""" + """Customer address book""" context_object_name = "addresses" template_name = 'customer/address-book.html' paginate_by = 40 def get_queryset(self): - u"""Return a customer's addresses""" + """Return a customer's addresses""" return user_address_model._default_manager.filter(user=self.request.user) diff --git a/oscar/apps/partner/abstract_models.py b/oscar/apps/partner/abstract_models.py index 0431cf1c3de..0fa6efe5a61 100644 --- a/oscar/apps/partner/abstract_models.py +++ b/oscar/apps/partner/abstract_models.py @@ -11,7 +11,7 @@ class AbstractPartner(models.Model): # A partner can have users assigned to it. These can be used # to provide authentication for webservices etc. - users = models.ManyToManyField('auth.User', related_name="partners", null=True) + users = models.ManyToManyField('auth.User', related_name="partners", blank=True, null=True) class Meta: verbose_name_plural = 'Fulfillment partners' diff --git a/oscar/apps/product/app.py b/oscar/apps/product/app.py index 15fef68bc51..614017dad74 100644 --- a/oscar/apps/product/app.py +++ b/oscar/apps/product/app.py @@ -1,6 +1,7 @@ +from django.conf.urls.defaults import patterns, url, include + from oscar.core.application import Application from oscar.apps.product.views import ItemDetailView, ProductListView, CategoryView -from django.conf.urls.defaults import patterns, url, include from oscar.apps.product.reviews.app import application as reviews_app diff --git a/oscar/apps/product/reviews/tests.py b/oscar/apps/product/reviews/tests.py index e88393c40d3..d825b6b1f74 100644 --- a/oscar/apps/product/reviews/tests.py +++ b/oscar/apps/product/reviews/tests.py @@ -56,17 +56,20 @@ class SingleProductReviewViewTest(ProductReviewTests, TestCase): def setUp(self): self.client = Client() super(SingleProductReviewViewTest, self).setUp() - self.kwargs = {'item_class_slug': self.item.get_item_class().slug, + self.kwargs = { 'item_slug': self.item.slug, - 'item_id': str(self.item.id)} + 'pk': str(self.item.id)} def test_each_product_has_review(self): - url = reverse('oscar-product-item', kwargs=self.kwargs) + url = reverse('products:detail', kwargs=self.kwargs) response = self.client.get(url) self.assertEquals(200, response.status_code) def test_user_can_add_product_review(self): - url = reverse('oscar-product-review-add', kwargs=self.kwargs) + kwargs = { + 'item_slug': self.item.slug, + 'item_pk': str(self.item.id)} + url = reverse('products:reviews-add', kwargs=kwargs) self.client.login(username='testuser', password='secret') response = self.client.get(url) self.assertEquals(200, response.status_code) diff --git a/oscar/apps/product/templates/product/detail.html b/oscar/apps/product/templates/product/detail.html index 04ed9acf94a..5766af3133a 100644 --- a/oscar/apps/product/templates/product/detail.html +++ b/oscar/apps/product/templates/product/detail.html @@ -5,7 +5,7 @@ {% load basket_tags %} {% block header %} -

{{ item.get_title }}

+

{{ item.get_title }}

{% endblock header %} @@ -13,9 +13,9 @@

{{ item.get_title }}

{% for image in item.images.all %} - {% if image.caption %} -
{{ image.caption }}
- {% endif %} + {% if image.caption %} +
{{ image.caption }}
+ {% endif %} {% endfor %}
diff --git a/oscar/apps/product/tests.py b/oscar/apps/product/tests.py index f17feaf5beb..7aa08dc2036 100644 --- a/oscar/apps/product/tests.py +++ b/oscar/apps/product/tests.py @@ -1,13 +1,13 @@ -import unittest from django.test import TestCase, Client from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse -from oscar.apps.product.models import Item, ItemClass, Category +from oscar.apps.product.models import Item, ItemClass, Category from oscar.apps.product.utils import breadcrumbs_to_category -class CategoryTests(unittest.TestCase): + +class CategoryTests(TestCase): def test_create_category_root(self): trail = 'Books' @@ -41,7 +41,7 @@ def test_subsubcategory(self): self.assertEquals(category.slug, 'books/factual/popular-science', ) -class ItemTests(unittest.TestCase): +class ItemTests(TestCase): def setUp(self): self.item_class,_ = ItemClass.objects.get_or_create(name='Clothing') diff --git a/oscar/apps/product/views.py b/oscar/apps/product/views.py index 257f647c5ba..7afb8e7923f 100644 --- a/oscar/apps/product/views.py +++ b/oscar/apps/product/views.py @@ -1,14 +1,14 @@ from django.http import HttpResponsePermanentRedirect, Http404 from django.views.generic import ListView, DetailView -from oscar.apps.product.signals import product_viewed, product_search - from django.db.models import get_model +from oscar.apps.product.signals import product_viewed, product_search + item_model = get_model('product','item') category_model = get_model('product', 'category') + class ItemDetailView(DetailView): - #template_name = "oscar/product/item.html" model = item_model view_signal = product_viewed template_folder = "product" @@ -28,12 +28,11 @@ def get(self, request, **kwargs): self.view_signal.send(sender=self, product=item, user=request.user, request=request, response=response) return response; - def get_template_names(self): """ Returns a list of possible templates. - We try 2 options before defaulting to oscar/product/detail.html: + We try 2 options before defaulting to product/detail.html: 1). detail-for-upc-.html 2). detail-for-class-.html diff --git a/oscar/core/application.py b/oscar/core/application.py index 1899634a260..7e6ec13b052 100644 --- a/oscar/core/application.py +++ b/oscar/core/application.py @@ -1,15 +1,17 @@ from django.conf.urls.defaults import patterns + class Application(object): name = None def __init__(self, app_name=None, **kwargs): self.app_name = app_name + # Set all kwargs as object attributes for key, value in kwargs.iteritems(): setattr(self, key, value) def get_urls(self): - u""" + """ Return the url patterns for this app, MUST be implemented in the subclass """ return patterns('') diff --git a/requirements-dev.txt b/requirements-dev.txt index 1410b9b8a38..0b74d298360 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,4 +10,4 @@ pysolr==2.0.13 PyZen==0.3.1 mock==0.7.0 twill==0.9 --e git+https://AndrewIngram@github.com/AndrewIngram/django-extra-views.git#egg=django-extra-views \ No newline at end of file +-e git://github.com/AndrewIngram/django-extra-views.git@b2290b2ca1bc5ee4c08af2b7a9f268d48d3cefd7#egg=django_extra_views-0.1.0-py2.6-dev \ No newline at end of file