Browse Source

Drop unneeded Product.score field

It just duplicates ProductRecord.score, makes ``oscar_calculate_scores``
slow and generally serves no useful purpose.

Also included docs for the previous changse to
``oscar_calculate_scores``.
master
Maik Hoepfel 11 years ago
parent
commit
dc3126280d

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

@@ -112,6 +112,15 @@ Backwards incompatible changes in 0.8
112 112
 =====================================
113 113
 
114 114
 * The ``shipping`` app saw a few renames; please see the section above.
115
+* The ``oscar_calculate_scores`` command has been `rewritten`_ to use the ORM
116
+  instead of raw SQL. That exposed a bug in the previous calculations,
117
+  where purchases got weighed less than any other event. When you upgrade,
118
+  your total scores will be change. If you rely on the old behaviour,
119
+  just extend the ``Calculator`` class and adjust the weights.
120
+* ``Product.score`` was just duplicating ``ProductRecord.score`` and has been
121
+  removed. Use ``Product.stats.score`` instead.
122
+
123
+.. _rewritten: https://github.com/tangentlabs/django-oscar/commit/d8b4dbfed17be90846ea4bc47b5f7b39ad944c24
115 124
 
116 125
 Basket line stockrecords
117 126
 ------------------------
@@ -156,6 +165,7 @@ Migrations
156 165
 
157 166
     - ``0021`` - Add ``unique_together`` to ``ProductAttributeValue``,
158 167
       ``ProductRecommendation`` and ``ProductCategory``
168
+    - ``0022`` - Remove ``Product.score`` field.
159 169
 
160 170
 * Order:
161 171
 

+ 0
- 9
oscar/apps/analytics/scores.py View File

@@ -19,7 +19,6 @@ class Calculator(object):
19 19
 
20 20
     def run(self):
21 21
         self.calculate_scores()
22
-        self.update_product_models()
23 22
 
24 23
     def calculate_scores(self):
25 24
         self.logger.info("Calculating product scores")
@@ -28,11 +27,3 @@ class Calculator(object):
28 27
             self.weights[name] * F(name) for name in self.weights.keys()]
29 28
         ProductRecord.objects.update(
30 29
             score=sum(weighted_fields)/total_weight)
31
-
32
-    def update_product_models(self):
33
-        self.logger.info("Updating product records")
34
-        records = ProductRecord.objects.select_related('product')
35
-        for record in records:
36
-            record.product.score = record.score
37
-            record.product.save()
38
-        self.logger.info("Updated scores for %d products" % len(records))

+ 0
- 3
oscar/apps/catalogue/abstract_models.py View File

@@ -252,9 +252,6 @@ class AbstractProduct(models.Model):
252 252
         'catalogue.Product', through='ProductRecommendation', blank=True,
253 253
         verbose_name=_("Recommended Products"))
254 254
 
255
-    # Product score - used by analytics app
256
-    score = models.FloatField(_('Score'), default=0.00, db_index=True)
257
-
258 255
     # Denormalised product rating - used by reviews app.
259 256
     # Product has no ratings if rating is None
260 257
     rating = models.FloatField(_('Rating'), null=True, editable=False)

+ 145
- 0
oscar/apps/catalogue/migrations/0022_auto__del_field_product_score.py View File

@@ -0,0 +1,145 @@
1
+# -*- coding: utf-8 -*-
2
+from south.utils import datetime_utils as datetime
3
+from south.db import db
4
+from south.v2 import SchemaMigration
5
+from django.db import models
6
+
7
+
8
+class Migration(SchemaMigration):
9
+
10
+    def forwards(self, orm):
11
+        # Deleting field 'Product.score'
12
+        db.delete_column(u'catalogue_product', 'score')
13
+
14
+
15
+    def backwards(self, orm):
16
+        # Adding field 'Product.score'
17
+        db.add_column(u'catalogue_product', 'score',
18
+                      self.gf('django.db.models.fields.FloatField')(default=0.0, db_index=True),
19
+                      keep_default=False)
20
+
21
+
22
+    models = {
23
+        u'catalogue.attributeentity': {
24
+            'Meta': {'object_name': 'AttributeEntity'},
25
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
26
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
27
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'}),
28
+            'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': u"orm['catalogue.AttributeEntityType']"})
29
+        },
30
+        u'catalogue.attributeentitytype': {
31
+            'Meta': {'object_name': 'AttributeEntityType'},
32
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
33
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
34
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'})
35
+        },
36
+        u'catalogue.attributeoption': {
37
+            'Meta': {'object_name': 'AttributeOption'},
38
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': u"orm['catalogue.AttributeOptionGroup']"}),
39
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
40
+            'option': ('django.db.models.fields.CharField', [], {'max_length': '255'})
41
+        },
42
+        u'catalogue.attributeoptiongroup': {
43
+            'Meta': {'object_name': 'AttributeOptionGroup'},
44
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
45
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
46
+        },
47
+        u'catalogue.category': {
48
+            'Meta': {'ordering': "['full_name']", 'object_name': 'Category'},
49
+            'depth': ('django.db.models.fields.PositiveIntegerField', [], {}),
50
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
51
+            'full_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
52
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
53
+            'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
54
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
55
+            'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
56
+            'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
57
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'})
58
+        },
59
+        u'catalogue.option': {
60
+            'Meta': {'object_name': 'Option'},
61
+            'code': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}),
62
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
63
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
64
+            'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'})
65
+        },
66
+        u'catalogue.product': {
67
+            'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'},
68
+            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.ProductAttribute']", 'through': u"orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}),
69
+            'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Category']", 'through': u"orm['catalogue.ProductCategory']", 'symmetrical': 'False'}),
70
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
71
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
72
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
73
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
74
+            'is_discountable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
75
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': u"orm['catalogue.Product']"}),
76
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'products'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['catalogue.ProductClass']"}),
77
+            'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
78
+            'rating': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
79
+            'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Product']", 'symmetrical': 'False', 'through': u"orm['catalogue.ProductRecommendation']", 'blank': 'True'}),
80
+            'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': u"orm['catalogue.Product']"}),
81
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
82
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
83
+            'upc': ('oscar.models.fields.NullCharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
84
+        },
85
+        u'catalogue.productattribute': {
86
+            'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'},
87
+            'code': ('django.db.models.fields.SlugField', [], {'max_length': '128'}),
88
+            'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}),
89
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
90
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
91
+            'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}),
92
+            'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': u"orm['catalogue.ProductClass']"}),
93
+            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
94
+            'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'})
95
+        },
96
+        u'catalogue.productattributevalue': {
97
+            'Meta': {'unique_together': "(('attribute', 'product'),)", 'object_name': 'ProductAttributeValue'},
98
+            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.ProductAttribute']"}),
99
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
100
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_values'", 'to': u"orm['catalogue.Product']"}),
101
+            'value_boolean': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
102
+            'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
103
+            'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}),
104
+            'value_file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
105
+            'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
106
+            'value_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
107
+            'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
108
+            'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}),
109
+            'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
110
+            'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
111
+        },
112
+        u'catalogue.productcategory': {
113
+            'Meta': {'ordering': "['product', 'category']", 'unique_together': "(('product', 'category'),)", 'object_name': 'ProductCategory'},
114
+            'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Category']"}),
115
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
116
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"})
117
+        },
118
+        u'catalogue.productclass': {
119
+            'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'},
120
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
121
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
122
+            'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}),
123
+            'requires_shipping': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
124
+            'slug': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}),
125
+            'track_stock': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
126
+        },
127
+        u'catalogue.productimage': {
128
+            'Meta': {'ordering': "['display_order']", 'unique_together': "(('product', 'display_order'),)", 'object_name': 'ProductImage'},
129
+            'caption': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
130
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
131
+            'display_order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
132
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
133
+            'original': ('django.db.models.fields.files.ImageField', [], {'max_length': '255'}),
134
+            'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'images'", 'to': u"orm['catalogue.Product']"})
135
+        },
136
+        u'catalogue.productrecommendation': {
137
+            'Meta': {'ordering': "['primary', '-ranking']", 'unique_together': "(('primary', 'recommendation'),)", 'object_name': 'ProductRecommendation'},
138
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
139
+            'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': u"orm['catalogue.Product']"}),
140
+            'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
141
+            'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"})
142
+        }
143
+    }
144
+
145
+    complete_apps = ['catalogue']

+ 1
- 1
oscar/apps/dashboard/catalogue/forms.py View File

@@ -273,7 +273,7 @@ class ProductForm(forms.ModelForm):
273 273
 
274 274
     class Meta:
275 275
         model = Product
276
-        exclude = ('slug', 'score', 'product_class',
276
+        exclude = ('slug', 'product_class',
277 277
                    'recommended_products', 'product_options',
278 278
                    'attributes', 'categories')
279 279
         widgets = {

+ 2
- 2
oscar/apps/promotions/models.py View File

@@ -297,9 +297,9 @@ class AutomaticProductList(AbstractProductList):
297 297
 
298 298
     def get_queryset(self):
299 299
         Product = get_model('catalogue', 'Product')
300
-        qs = Product.browsable.base_queryset()
300
+        qs = Product.browsable.base_queryset().select_related('stats')
301 301
         if self.method == self.BESTSELLING:
302
-            return qs.order_by('-score')
302
+            return qs.order_by('-stats__score')
303 303
         return qs.order_by('-date_created')
304 304
 
305 305
     def get_products(self):

Loading…
Cancel
Save