Просмотр исходного кода

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

master
Patryk Zawadzki 14 лет назад
Родитель
Сommit
1a0be1f4a1

+ 9
- 9
oscar/apps/address/abstract_models.py Просмотреть файл

@@ -23,17 +23,17 @@ class AbstractAddress(models.Model):
23 23
         (MS, _("Ms")),
24 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 30
     # We use quite a few lines of an address as they are often quite long and 
31 31
     # it's easier to just hide the unnecessary ones than add extra ones.
32 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 37
     postcode = models.CharField(_("Post/Zip-code"), max_length=64)
38 38
     country = models.ForeignKey('address.Country')
39 39
     
@@ -52,9 +52,9 @@ class AbstractAddress(models.Model):
52 52
         """
53 53
         Clean up fields
54 54
         """
55
-        self.first_name = self.first_name.strip()
56 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 59
         # Ensure postcodes are always uppercase
60 60
         if self.postcode:

+ 1
- 1
oscar/apps/catalogue/abstract_models.py Просмотреть файл

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

+ 5
- 1
oscar/apps/catalogue/admin.py Просмотреть файл

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

+ 2
- 2
oscar/apps/catalogue/app.py Просмотреть файл

@@ -15,7 +15,7 @@ class BaseCatalogueApplication(Application):
15 15
         urlpatterns = super(BaseCatalogueApplication, self).get_urls()        
16 16
         urlpatterns += patterns('',
17 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 19
             url(r'^(?P<category_slug>[\w-]+(/[\w-]+)*)/$', self.category_view.as_view(), name='category')
20 20
         )
21 21
         return urlpatterns
@@ -27,7 +27,7 @@ class ReviewsApplication(Application):
27 27
     def get_urls(self):
28 28
         urlpatterns = super(ReviewsApplication, self).get_urls()
29 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 32
         return urlpatterns
33 33
 

+ 2
- 2
oscar/apps/catalogue/reviews/abstract_models.py Просмотреть файл

@@ -65,8 +65,8 @@ class AbstractProductReview(models.Model):
65 65
     @models.permalink
66 66
     def get_absolute_url(self):
67 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 70
             'pk': self.id})
71 71
 
72 72
     def __unicode__(self):

+ 8
- 8
oscar/apps/catalogue/reviews/tests.py Просмотреть файл

@@ -20,18 +20,18 @@ class ProductReviewTests(unittest.TestCase):
20 20
         username = str(randint(0, maxint))
21 21
         self.user = User.objects.create_user(username, '%s@users.com'%username, '%spass123'%username)
22 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 25
                                                    title="Dummy review",
26 26
                                                    score=3,
27 27
                                                    user=self.user)
28 28
 
29 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 31
                           user=self.user)
32 32
 
33 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 35
                           user=None, title="Anonymous review", score=3)
36 36
 
37 37
 
@@ -57,8 +57,8 @@ class SingleProductReviewViewTest(ProductReviewTests, TestCase):
57 57
         self.client = Client()
58 58
         super(SingleProductReviewViewTest, self).setUp()
59 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 63
     def test_each_product_has_review(self):
64 64
         url = reverse('catalogue:detail', kwargs=self.kwargs)
@@ -67,8 +67,8 @@ class SingleProductReviewViewTest(ProductReviewTests, TestCase):
67 67
     
68 68
     def test_user_can_add_product_review(self):
69 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 72
         url = reverse('catalogue:reviews-add', kwargs=kwargs)
73 73
         self.client.login(username='testuser', password='secret')
74 74
         response = self.client.get(url)

+ 9
- 9
oscar/apps/catalogue/reviews/views.py Просмотреть файл

@@ -29,11 +29,11 @@ class CreateProductReview(CreateView):
29 29
     
30 30
     def get_context_data(self, **kwargs):
31 31
         context = super(CreateProductReview, self).get_context_data(**kwargs)
32
-        context['item'] = self.get_product()
32
+        context['product'] = self.get_product()
33 33
         return context
34 34
     
35 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 38
     def get_form_class(self):
39 39
         if not self.request.user.is_authenticated():
@@ -56,11 +56,11 @@ class CreateProductReviewComplete(DetailView):
56 56
     template_name = "reviews/add_review_complete.html"
57 57
     context_object_name = 'review'
58 58
     model = get_model('reviews', 'productreview')
59
-    product_model = get_model('product', 'item')
59
+    product_model = get_model('catalogue', 'product')
60 60
     
61 61
     def get_context_data(self, **kwargs):
62 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 64
         return context    
65 65
 
66 66
 
@@ -72,12 +72,12 @@ class ProductReviewDetail(DetailView):
72 72
     template_name = "reviews/review.html"
73 73
     context_object_name = 'review'
74 74
     model = get_model('reviews', 'productreview')
75
-    product_model = get_model('product', 'item')
75
+    product_model = get_model('catalogue', 'product')
76 76
     vote_model = vote_model
77 77
     
78 78
     def get_context_data(self, **kwargs):
79 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 81
         return context
82 82
     
83 83
     def post(self, request, *args, **kwargs ):
@@ -103,17 +103,17 @@ class ProductReviewList(ListView):
103 103
     template_name = 'reviews/reviews.html'
104 104
     context_object_name = "reviews"
105 105
     model = get_model('reviews', 'productreview')
106
-    product_model = get_model('product', 'item')    
106
+    product_model = get_model('catalogue', 'product')    
107 107
     paginate_by = 20
108 108
      
109 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 111
         if 'sort_by' in self.request.GET and self.request.GET['sort_by'] == 'score':
112 112
             return qs.order_by('-score')
113 113
         return qs.order_by('-date_created')
114 114
      
115 115
     def get_context_data(self, **kwargs):
116 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 118
         context['avg_score'] = self.object_list.aggregate(Avg('score'))           
119 119
         return context

+ 2
- 2
oscar/apps/catalogue/tests.py Просмотреть файл

@@ -82,8 +82,8 @@ class SingleProductViewTest(TestCase):
82 82
         
83 83
     def test_canonical_urls_are_enforced(self):
84 84
         p = Product.objects.get(id=1)
85
-        args = {'item_slug': 'wrong-slug',
85
+        args = {'product_slug': 'wrong-slug',
86 86
                 'pk': p.id}
87 87
         wrong_url = reverse('catalogue:detail', kwargs=args)
88 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 Просмотреть файл

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

+ 14
- 2
oscar/apps/order/abstract_models.py Просмотреть файл

@@ -110,9 +110,21 @@ class AbstractOrder(models.Model):
110 110
 
111 111
 
112 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 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 128
     message = models.TextField()
117 129
     date = models.DateTimeField(auto_now_add=True)
118 130
     

+ 7
- 0
oscar/apps/partner/abstract_models.py Просмотреть файл

@@ -106,6 +106,13 @@ class AbstractStockRecord(models.Model):
106 106
         """
107 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 116
     @property
110 117
     def availability(self):
111 118
         u"""Return an item's availability as a string"""

+ 1
- 1
oscar/apps/payment/abstract_models.py Просмотреть файл

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

+ 2
- 1
oscar/apps/payment/admin.py Просмотреть файл

@@ -4,7 +4,8 @@ from oscar.core.loading import import_module
4 4
 models = import_module('payment.models', ['Source', 'Transaction', 'SourceType'])
5 5
 
6 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 10
 admin.site.register(models.Source, SourceAdmin)
10 11
 admin.site.register(models.SourceType)

+ 4
- 0
oscar/apps/payment/models.py Просмотреть файл

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

+ 2
- 0
oscar/apps/promotions/context_processors.py Просмотреть файл

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

+ 1
- 0
oscar/apps/promotions/models.py Просмотреть файл

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

+ 1
- 0
oscar/forms/fields.py Просмотреть файл

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

Загрузка…
Отмена
Сохранить