Browse Source

Merge branch 'master' of git://github.com/tangentlabs/django-oscar

master
Patryk Zawadzki 14 years ago
parent
commit
1a0be1f4a1

+ 9
- 9
oscar/apps/address/abstract_models.py View File

23
         (MS, _("Ms")),
23
         (MS, _("Ms")),
24
         (DR, _("Dr")),
24
         (DR, _("Dr")),
25
     )
25
     )
26
-    title = models.CharField(_("Title"), max_length=64, choices=TITLE_CHOICES, blank=True)
27
-    first_name = models.CharField(_("First name"), max_length=255, blank=True)
28
-    last_name = models.CharField(_("Last name"), max_length=255)
26
+    title = models.CharField(_("Title"), max_length=64, choices=TITLE_CHOICES, blank=True, null=True)
27
+    first_name = models.CharField(_("First name"), max_length=255, blank=True, null=True)
28
+    last_name = models.CharField(_("Last name"), max_length=255, blank=True)
29
     
29
     
30
     # We use quite a few lines of an address as they are often quite long and 
30
     # We use quite a few lines of an address as they are often quite long and 
31
     # it's easier to just hide the unnecessary ones than add extra ones.
31
     # it's easier to just hide the unnecessary ones than add extra ones.
32
     line1 = models.CharField(_("First line of address"), max_length=255)
32
     line1 = models.CharField(_("First line of address"), max_length=255)
33
-    line2 = models.CharField(_("Second line of address"), max_length=255, blank=True)
34
-    line3 = models.CharField(_("Third line of address"), max_length=255, blank=True)
35
-    line4 = models.CharField(_("City"), max_length=255, blank=True)
36
-    state = models.CharField(_("State/County"), max_length=255, blank=True)
33
+    line2 = models.CharField(_("Second line of address"), max_length=255, blank=True, null=True)
34
+    line3 = models.CharField(_("Third line of address"), max_length=255, blank=True, null=True)
35
+    line4 = models.CharField(_("City"), max_length=255, blank=True, null=True)
36
+    state = models.CharField(_("State/County"), max_length=255, blank=True, null=True)
37
     postcode = models.CharField(_("Post/Zip-code"), max_length=64)
37
     postcode = models.CharField(_("Post/Zip-code"), max_length=64)
38
     country = models.ForeignKey('address.Country')
38
     country = models.ForeignKey('address.Country')
39
     
39
     
52
         """
52
         """
53
         Clean up fields
53
         Clean up fields
54
         """
54
         """
55
-        self.first_name = self.first_name.strip()
56
         for field in ['first_name', 'last_name', 'line1', 'line2', 'line3', 'line4', 'postcode']:
55
         for field in ['first_name', 'last_name', 'line1', 'line2', 'line3', 'line4', 'postcode']:
57
-            self.__dict__[field] = self.__dict__[field].strip()
56
+            if self.__dict__[field]:
57
+               self.__dict__[field] = self.__dict__[field].strip()
58
         
58
         
59
         # Ensure postcodes are always uppercase
59
         # Ensure postcodes are always uppercase
60
         if self.postcode:
60
         if self.postcode:

+ 1
- 1
oscar/apps/catalogue/abstract_models.py View File

292
     def get_absolute_url(self):
292
     def get_absolute_url(self):
293
         u"""Return a product's absolute url"""
293
         u"""Return a product's absolute url"""
294
         return ('catalogue:detail', (), {
294
         return ('catalogue:detail', (), {
295
-            'item_slug': self.slug,
295
+            'product_slug': self.slug,
296
             'pk': self.id})
296
             'pk': self.id})
297
     
297
     
298
     def save(self, *args, **kwargs):
298
     def save(self, *args, **kwargs):

+ 5
- 1
oscar/apps/catalogue/admin.py View File

29
     
29
     
30
 class OptionAdmin(admin.ModelAdmin):
30
 class OptionAdmin(admin.ModelAdmin):
31
     exclude = ['code']
31
     exclude = ['code']
32
+    
33
+class ProductAttributeValueAdmin(admin.ModelAdmin):
34
+    list_display = ('product', 'type', 'value')
35
+    
32
 
36
 
33
 admin.site.register(product_models.ProductClass, ProductClassAdmin)
37
 admin.site.register(product_models.ProductClass, ProductClassAdmin)
34
 admin.site.register(product_models.Product, ProductAdmin)
38
 admin.site.register(product_models.Product, ProductAdmin)
35
 admin.site.register(product_models.AttributeType, AttributeTypeAdmin)
39
 admin.site.register(product_models.AttributeType, AttributeTypeAdmin)
36
-admin.site.register(product_models.ProductAttributeValue)
40
+admin.site.register(product_models.ProductAttributeValue, ProductAttributeValueAdmin)
37
 admin.site.register(product_models.Option, OptionAdmin)
41
 admin.site.register(product_models.Option, OptionAdmin)
38
 admin.site.register(product_models.ProductImage)
42
 admin.site.register(product_models.ProductImage)
39
 admin.site.register(product_models.Category)
43
 admin.site.register(product_models.Category)

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

15
         urlpatterns = super(BaseCatalogueApplication, self).get_urls()        
15
         urlpatterns = super(BaseCatalogueApplication, self).get_urls()        
16
         urlpatterns += patterns('',
16
         urlpatterns += patterns('',
17
             url(r'^$', self.index_view.as_view(), name='index'),       
17
             url(r'^$', self.index_view.as_view(), name='index'),       
18
-            url(r'^(?P<item_slug>[\w-]*)-(?P<pk>\d+)/$', self.detail_view.as_view(), name='detail'),
18
+            url(r'^(?P<product_slug>[\w-]*)-(?P<pk>\d+)/$', self.detail_view.as_view(), name='detail'),
19
             url(r'^(?P<category_slug>[\w-]+(/[\w-]+)*)/$', self.category_view.as_view(), name='category')
19
             url(r'^(?P<category_slug>[\w-]+(/[\w-]+)*)/$', self.category_view.as_view(), name='category')
20
         )
20
         )
21
         return urlpatterns
21
         return urlpatterns
27
     def get_urls(self):
27
     def get_urls(self):
28
         urlpatterns = super(ReviewsApplication, self).get_urls()
28
         urlpatterns = super(ReviewsApplication, self).get_urls()
29
         urlpatterns += patterns('',
29
         urlpatterns += patterns('',
30
-            url(r'^(?P<item_slug>[\w-]*)-(?P<item_pk>\d+)/reviews/', include(self.reviews_app.urls)),
30
+            url(r'^(?P<product_slug>[\w-]*)-(?P<product_pk>\d+)/reviews/', include(self.reviews_app.urls)),
31
         )
31
         )
32
         return urlpatterns
32
         return urlpatterns
33
 
33
 

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

65
     @models.permalink
65
     @models.permalink
66
     def get_absolute_url(self):
66
     def get_absolute_url(self):
67
         return ('products:reviews-detail', (), {
67
         return ('products:reviews-detail', (), {
68
-            'item_slug': self.product.slug,
69
-            'item_pk': self.product.id,
68
+            'product_slug': self.product.slug,
69
+            'product_pk': self.product.id,
70
             'pk': self.id})
70
             'pk': self.id})
71
 
71
 
72
     def __unicode__(self):
72
     def __unicode__(self):

+ 8
- 8
oscar/apps/catalogue/reviews/tests.py View File

20
         username = str(randint(0, maxint))
20
         username = str(randint(0, maxint))
21
         self.user = User.objects.create_user(username, '%s@users.com'%username, '%spass123'%username)
21
         self.user = User.objects.create_user(username, '%s@users.com'%username, '%spass123'%username)
22
         self.anon_user = AnonymousUser()
22
         self.anon_user = AnonymousUser()
23
-        self.item = create_product()
24
-        self.review = ProductReview.objects.create(product=self.item,
23
+        self.product = create_product()
24
+        self.review = ProductReview.objects.create(product=self.product,
25
                                                    title="Dummy review",
25
                                                    title="Dummy review",
26
                                                    score=3,
26
                                                    score=3,
27
                                                    user=self.user)
27
                                                    user=self.user)
28
 
28
 
29
     def test_top_level_reviews_must_have_titles_and_scores(self):
29
     def test_top_level_reviews_must_have_titles_and_scores(self):
30
-        self.assertRaises(ValidationError, ProductReview.objects.create, product=self.item,
30
+        self.assertRaises(ValidationError, ProductReview.objects.create, product=self.product,
31
                           user=self.user)
31
                           user=self.user)
32
 
32
 
33
     def test_top_level_anonymous_reviews_must_have_names_and_emails(self):
33
     def test_top_level_anonymous_reviews_must_have_names_and_emails(self):
34
-        self.assertRaises(ValidationError, ProductReview.objects.create, product=self.item,
34
+        self.assertRaises(ValidationError, ProductReview.objects.create, product=self.product,
35
                           user=None, title="Anonymous review", score=3)
35
                           user=None, title="Anonymous review", score=3)
36
 
36
 
37
 
37
 
57
         self.client = Client()
57
         self.client = Client()
58
         super(SingleProductReviewViewTest, self).setUp()
58
         super(SingleProductReviewViewTest, self).setUp()
59
         self.kwargs = {
59
         self.kwargs = {
60
-                'item_slug': self.item.slug,
61
-                'pk': str(self.item.id)}
60
+                'product_slug': self.product.slug,
61
+                'pk': str(self.product.id)}
62
         
62
         
63
     def test_each_product_has_review(self):
63
     def test_each_product_has_review(self):
64
         url = reverse('catalogue:detail', kwargs=self.kwargs)
64
         url = reverse('catalogue:detail', kwargs=self.kwargs)
67
     
67
     
68
     def test_user_can_add_product_review(self):
68
     def test_user_can_add_product_review(self):
69
         kwargs = {
69
         kwargs = {
70
-                'item_slug': self.item.slug,
71
-                'item_pk': str(self.item.id)}
70
+                'product_slug': self.product.slug,
71
+                'product_pk': str(self.product.id)}
72
         url = reverse('catalogue:reviews-add', kwargs=kwargs)
72
         url = reverse('catalogue:reviews-add', kwargs=kwargs)
73
         self.client.login(username='testuser', password='secret')
73
         self.client.login(username='testuser', password='secret')
74
         response = self.client.get(url)
74
         response = self.client.get(url)

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

29
     
29
     
30
     def get_context_data(self, **kwargs):
30
     def get_context_data(self, **kwargs):
31
         context = super(CreateProductReview, self).get_context_data(**kwargs)
31
         context = super(CreateProductReview, self).get_context_data(**kwargs)
32
-        context['item'] = self.get_product()
32
+        context['product'] = self.get_product()
33
         return context
33
         return context
34
     
34
     
35
     def get_product(self):
35
     def get_product(self):
36
-        return get_object_or_404(self.product_model, pk=self.kwargs['item_pk'])
36
+        return get_object_or_404(self.product_model, pk=self.kwargs['product_pk'])
37
     
37
     
38
     def get_form_class(self):
38
     def get_form_class(self):
39
         if not self.request.user.is_authenticated():
39
         if not self.request.user.is_authenticated():
56
     template_name = "reviews/add_review_complete.html"
56
     template_name = "reviews/add_review_complete.html"
57
     context_object_name = 'review'
57
     context_object_name = 'review'
58
     model = get_model('reviews', 'productreview')
58
     model = get_model('reviews', 'productreview')
59
-    product_model = get_model('product', 'item')
59
+    product_model = get_model('catalogue', 'product')
60
     
60
     
61
     def get_context_data(self, **kwargs):
61
     def get_context_data(self, **kwargs):
62
         context = super(CreateProductReviewComplete, self).get_context_data(**kwargs)
62
         context = super(CreateProductReviewComplete, self).get_context_data(**kwargs)
63
-        context['item'] = get_object_or_404(self.product_model, pk=self.kwargs['item_pk'])
63
+        context['product'] = get_object_or_404(self.product_model, pk=self.kwargs['product_pk'])
64
         return context    
64
         return context    
65
 
65
 
66
 
66
 
72
     template_name = "reviews/review.html"
72
     template_name = "reviews/review.html"
73
     context_object_name = 'review'
73
     context_object_name = 'review'
74
     model = get_model('reviews', 'productreview')
74
     model = get_model('reviews', 'productreview')
75
-    product_model = get_model('product', 'item')
75
+    product_model = get_model('catalogue', 'product')
76
     vote_model = vote_model
76
     vote_model = vote_model
77
     
77
     
78
     def get_context_data(self, **kwargs):
78
     def get_context_data(self, **kwargs):
79
         context = super(ProductReviewDetail, self).get_context_data(**kwargs)
79
         context = super(ProductReviewDetail, self).get_context_data(**kwargs)
80
-        context['item'] = get_object_or_404(self.product_model, pk=self.kwargs['item_pk'])
80
+        context['product'] = get_object_or_404(self.product_model, pk=self.kwargs['product_pk'])
81
         return context
81
         return context
82
     
82
     
83
     def post(self, request, *args, **kwargs ):
83
     def post(self, request, *args, **kwargs ):
103
     template_name = 'reviews/reviews.html'
103
     template_name = 'reviews/reviews.html'
104
     context_object_name = "reviews"
104
     context_object_name = "reviews"
105
     model = get_model('reviews', 'productreview')
105
     model = get_model('reviews', 'productreview')
106
-    product_model = get_model('product', 'item')    
106
+    product_model = get_model('catalogue', 'product')    
107
     paginate_by = 20
107
     paginate_by = 20
108
      
108
      
109
     def get_queryset(self):
109
     def get_queryset(self):
110
-        qs = self.model.approved.filter(product=self.kwargs['item_pk'])
110
+        qs = self.model.approved.filter(product=self.kwargs['product_pk'])
111
         if 'sort_by' in self.request.GET and self.request.GET['sort_by'] == 'score':
111
         if 'sort_by' in self.request.GET and self.request.GET['sort_by'] == 'score':
112
             return qs.order_by('-score')
112
             return qs.order_by('-score')
113
         return qs.order_by('-date_created')
113
         return qs.order_by('-date_created')
114
      
114
      
115
     def get_context_data(self, **kwargs):
115
     def get_context_data(self, **kwargs):
116
         context = super(ProductReviewList, self).get_context_data(**kwargs)
116
         context = super(ProductReviewList, self).get_context_data(**kwargs)
117
-        context['item'] = get_object_or_404(self.product_model, pk=self.kwargs['item_pk'])  
117
+        context['product'] = get_object_or_404(self.product_model, pk=self.kwargs['product_pk'])  
118
         context['avg_score'] = self.object_list.aggregate(Avg('score'))           
118
         context['avg_score'] = self.object_list.aggregate(Avg('score'))           
119
         return context
119
         return context

+ 2
- 2
oscar/apps/catalogue/tests.py View File

82
         
82
         
83
     def test_canonical_urls_are_enforced(self):
83
     def test_canonical_urls_are_enforced(self):
84
         p = Product.objects.get(id=1)
84
         p = Product.objects.get(id=1)
85
-        args = {'item_slug': 'wrong-slug',
85
+        args = {'product_slug': 'wrong-slug',
86
                 'pk': p.id}
86
                 'pk': p.id}
87
         wrong_url = reverse('catalogue:detail', kwargs=args)
87
         wrong_url = reverse('catalogue:detail', kwargs=args)
88
         response = self.client.get(wrong_url)
88
         response = self.client.get(wrong_url)
89
-        self.assertEquals(301, response.status_code)
89
+        self.assertEquals(301, response.status_code)

+ 2
- 1
oscar/apps/checkout/views.py View File

88
                 basket = self.request.basket
88
                 basket = self.request.basket
89
             method.set_basket(basket)
89
             method.set_basket(basket)
90
         else:
90
         else:
91
+            # We default to using free shipping
91
             method = FreeShipping()
92
             method = FreeShipping()
92
         return method
93
         return method
93
     
94
     
605
     def get_object(self):
606
     def get_object(self):
606
         # We allow superusers to force an order thankyou page for testing
607
         # We allow superusers to force an order thankyou page for testing
607
         order = None
608
         order = None
608
-        if self.request.user.is_superuser():
609
+        if self.request.user.is_superuser:
609
             if 'order_number' in self.request.GET:
610
             if 'order_number' in self.request.GET:
610
                 order = Order._default_manager.get(number=self.request.GET['order_number'])
611
                 order = Order._default_manager.get(number=self.request.GET['order_number'])
611
             elif 'order_id' in self.request.GET:
612
             elif 'order_id' in self.request.GET:

+ 14
- 2
oscar/apps/order/abstract_models.py View File

110
 
110
 
111
 
111
 
112
 class AbstractOrderNote(models.Model):
112
 class AbstractOrderNote(models.Model):
113
-    u"""A note against an order."""
113
+    """
114
+    A note against an order.
115
+    
116
+    This are often used for audit purposes too.  IE, whenever an admin
117
+    makes a change to an order, we create a note to record what happened.
118
+    """
114
     order = models.ForeignKey('order.Order', related_name="notes")
119
     order = models.ForeignKey('order.Order', related_name="notes")
115
-    user = models.ForeignKey('auth.User')
120
+    
121
+    # These are sometimes programatically generated so don't need a 
122
+    # user everytime
123
+    user = models.ForeignKey('auth.User', null=True)
124
+    
125
+    # We allow notes to be classified although this isn't always needed
126
+    note_type = models.CharField(max_length=128, null=True)
127
+    
116
     message = models.TextField()
128
     message = models.TextField()
117
     date = models.DateTimeField(auto_now_add=True)
129
     date = models.DateTimeField(auto_now_add=True)
118
     
130
     

+ 7
- 0
oscar/apps/partner/abstract_models.py View File

106
         """
106
         """
107
         return get_partner_wrapper(self.partner.name).is_available_to_buy(self)
107
         return get_partner_wrapper(self.partner.name).is_available_to_buy(self)
108
     
108
     
109
+    @property
110
+    def net_stock_level(self):
111
+        """
112
+        Return the effective number in stock
113
+        """ 
114
+        return self.num_in_stock - self.num_allocated
115
+    
109
     @property
116
     @property
110
     def availability(self):
117
     def availability(self):
111
         u"""Return an item's availability as a string"""
118
         u"""Return an item's availability as a string"""

+ 1
- 1
oscar/apps/payment/abstract_models.py View File

16
     source will have its own entry.
16
     source will have its own entry.
17
     """
17
     """
18
     order = models.ForeignKey('order.Order', related_name='sources')
18
     order = models.ForeignKey('order.Order', related_name='sources')
19
-    type = models.ForeignKey('payment.SourceType')
19
+    source_type = models.ForeignKey('payment.SourceType')
20
     currency = models.CharField(max_length=12, default=settings.OSCAR_DEFAULT_CURRENCY)
20
     currency = models.CharField(max_length=12, default=settings.OSCAR_DEFAULT_CURRENCY)
21
     amount_allocated = models.DecimalField(decimal_places=2, max_digits=12)
21
     amount_allocated = models.DecimalField(decimal_places=2, max_digits=12)
22
     amount_debited = models.DecimalField(decimal_places=2, max_digits=12, default=Decimal('0.00'))
22
     amount_debited = models.DecimalField(decimal_places=2, max_digits=12, default=Decimal('0.00'))

+ 2
- 1
oscar/apps/payment/admin.py View File

4
 models = import_module('payment.models', ['Source', 'Transaction', 'SourceType'])
4
 models = import_module('payment.models', ['Source', 'Transaction', 'SourceType'])
5
 
5
 
6
 class SourceAdmin(admin.ModelAdmin):
6
 class SourceAdmin(admin.ModelAdmin):
7
-    list_display = ('order', 'type', 'amount_allocated', 'amount_debited', 'balance', 'reference')
7
+    list_display = ('order', 'source_type', 'amount_allocated', 'amount_debited', 'balance', 'reference')
8
+
8
 
9
 
9
 admin.site.register(models.Source, SourceAdmin)
10
 admin.site.register(models.Source, SourceAdmin)
10
 admin.site.register(models.SourceType)
11
 admin.site.register(models.SourceType)

+ 4
- 0
oscar/apps/payment/models.py View File

1
 from oscar.apps.payment.abstract_models import *
1
 from oscar.apps.payment.abstract_models import *
2
 
2
 
3
+
3
 class Source(AbstractSource):
4
 class Source(AbstractSource):
4
     pass
5
     pass
5
 
6
 
7
+
6
 class SourceType(AbstractSourceType):
8
 class SourceType(AbstractSourceType):
7
     pass
9
     pass
8
 
10
 
11
+
9
 class Transaction(AbstractTransaction):
12
 class Transaction(AbstractTransaction):
10
     pass
13
     pass
11
 
14
 
15
+
12
 class Bankcard(AbstractBankcard):
16
 class Bankcard(AbstractBankcard):
13
     pass
17
     pass
14
 
18
 

+ 2
- 0
oscar/apps/promotions/context_processors.py View File

38
     by position, and write these lists to the context dict.
38
     by position, and write these lists to the context dict.
39
     """
39
     """
40
     for linked_promotion in linked_promotions:
40
     for linked_promotion in linked_promotions:
41
+        if not linked_promotion.content_object:
42
+            continue
41
         key = 'promotions_%s' % linked_promotion.position.lower()
43
         key = 'promotions_%s' % linked_promotion.position.lower()
42
         if key not in context:
44
         if key not in context:
43
             context[key] = []
45
             context[key] = []

+ 1
- 0
oscar/apps/promotions/models.py View File

150
     _type = 'Multi-image'
150
     _type = 'Multi-image'
151
     name = models.CharField(_("Name"), max_length=128)
151
     name = models.CharField(_("Name"), max_length=128)
152
     images = models.ManyToManyField('promotions.Image', null=True, blank=True)
152
     images = models.ManyToManyField('promotions.Image', null=True, blank=True)
153
+    date_created = models.DateTimeField(auto_now_add=True)
153
     
154
     
154
     def __unicode__(self):
155
     def __unicode__(self):
155
         return self.name
156
         return self.name

+ 1
- 0
oscar/forms/fields.py View File

4
 from django.db import models
4
 from django.db import models
5
 from django.http import Http404
5
 from django.http import Http404
6
 
6
 
7
+
7
 class ExtendedURLField(models.CharField):
8
 class ExtendedURLField(models.CharField):
8
     u"""
9
     u"""
9
     Custom field similar to URLField type field, however also accepting 
10
     Custom field similar to URLField type field, however also accepting 

Loading…
Cancel
Save