|
|
@@ -162,7 +162,7 @@ class AbstractProductCategory(models.Model):
|
|
162
|
162
|
product = models.ForeignKey('catalogue.Product')
|
|
163
|
163
|
category = models.ForeignKey('catalogue.Category')
|
|
164
|
164
|
is_canonical = models.BooleanField(_('is cannonical'), default=False, db_index=True)
|
|
165
|
|
-
|
|
|
165
|
+
|
|
166
|
166
|
class Meta:
|
|
167
|
167
|
abstract = True
|
|
168
|
168
|
ordering = ['-is_canonical']
|
|
|
@@ -171,7 +171,7 @@ class AbstractProductCategory(models.Model):
|
|
171
|
171
|
|
|
172
|
172
|
def __unicode__(self):
|
|
173
|
173
|
return u"<productcategory for product '%s'>" % self.product
|
|
174
|
|
-
|
|
|
174
|
+
|
|
175
|
175
|
|
|
176
|
176
|
class AbstractContributorRole(models.Model):
|
|
177
|
177
|
"""
|
|
|
@@ -180,10 +180,10 @@ class AbstractContributorRole(models.Model):
|
|
180
|
180
|
name = models.CharField(_('name'), max_length=50)
|
|
181
|
181
|
name_plural = models.CharField(_('name plural'), max_length=50)
|
|
182
|
182
|
slug = models.SlugField()
|
|
183
|
|
-
|
|
|
183
|
+
|
|
184
|
184
|
def __unicode__(self):
|
|
185
|
185
|
return self.name
|
|
186
|
|
-
|
|
|
186
|
+
|
|
187
|
187
|
class Meta:
|
|
188
|
188
|
abstract = True
|
|
189
|
189
|
verbose_name = _('Contributor Role')
|
|
|
@@ -192,7 +192,7 @@ class AbstractContributorRole(models.Model):
|
|
192
|
192
|
def save(self, *args, **kwargs):
|
|
193
|
193
|
if not self.slug:
|
|
194
|
194
|
self.slug = slugify(self.name)
|
|
195
|
|
- super(AbstractContributorRole, self).save(*args, **kwargs)
|
|
|
195
|
+ super(AbstractContributorRole, self).save(*args, **kwargs)
|
|
196
|
196
|
|
|
197
|
197
|
|
|
198
|
198
|
class AbstractContributor(models.Model):
|
|
|
@@ -204,7 +204,7 @@ class AbstractContributor(models.Model):
|
|
204
|
204
|
|
|
205
|
205
|
def __unicode__(self):
|
|
206
|
206
|
return self.name
|
|
207
|
|
-
|
|
|
207
|
+
|
|
208
|
208
|
class Meta:
|
|
209
|
209
|
abstract = True
|
|
210
|
210
|
verbose_name = _('Contributor')
|
|
|
@@ -221,14 +221,14 @@ class AbstractProductContributor(models.Model):
|
|
221
|
221
|
product = models.ForeignKey('catalogue.Product')
|
|
222
|
222
|
contributor = models.ForeignKey('catalogue.Contributor')
|
|
223
|
223
|
role = models.ForeignKey('catalogue.ContributorRole', blank=True, null=True)
|
|
224
|
|
-
|
|
|
224
|
+
|
|
225
|
225
|
def __unicode__(self):
|
|
226
|
226
|
return '%s <- %s - %s' % (self.product, self.role, self.contributor)
|
|
227
|
|
-
|
|
|
227
|
+
|
|
228
|
228
|
class Meta:
|
|
229
|
229
|
abstract = True
|
|
230
|
|
- verbose_name = _('Product Contributor')
|
|
231
|
|
- verbose_name_plural = _('Product COntributors')
|
|
|
230
|
+ verbose_name = _('Product contributor')
|
|
|
231
|
+ verbose_name_plural = _('Product contributors')
|
|
232
|
232
|
|
|
233
|
233
|
|
|
234
|
234
|
class AbstractProduct(models.Model):
|
|
|
@@ -238,21 +238,21 @@ class AbstractProduct(models.Model):
|
|
238
|
238
|
# If an item has no parent, then it is the "canonical" or abstract version of a product
|
|
239
|
239
|
# which essentially represents a set of products. If a product has a parent
|
|
240
|
240
|
# then it is a specific version of a catalogue.
|
|
241
|
|
- #
|
|
242
|
|
- # For example, a canonical product would have a title like "Green fleece" while its
|
|
|
241
|
+ #
|
|
|
242
|
+ # For example, a canonical product would have a title like "Green fleece" while its
|
|
243
|
243
|
# children would be "Green fleece - size L".
|
|
244
|
|
-
|
|
|
244
|
+
|
|
245
|
245
|
# Universal product code
|
|
246
|
246
|
upc = models.CharField(_("UPC"), max_length=64, blank=True, null=True, unique=True,
|
|
247
|
247
|
help_text=_("""Universal Product Code (UPC) is an identifier for a product which is
|
|
248
|
248
|
not specific to a particular supplier. Eg an ISBN for a book."""))
|
|
249
|
|
-
|
|
|
249
|
+
|
|
250
|
250
|
# No canonical product should have a stock record as they cannot be bought.
|
|
251
|
251
|
parent = models.ForeignKey('self', null=True, blank=True, related_name='variants',
|
|
252
|
252
|
help_text=_("""Only choose a parent product if this is a 'variant' of a canonical catalogue. For example
|
|
253
|
|
- if this is a size 4 of a particular t-shirt. Leave blank if this is a CANONICAL PRODUCT (ie
|
|
|
253
|
+ if this is a size 4 of a particular t-shirt. Leave blank if this is a CANONICAL PRODUCT (ie
|
|
254
|
254
|
there is only one version of this product)."""))
|
|
255
|
|
-
|
|
|
255
|
+
|
|
256
|
256
|
# Title is mandatory for canonical products but optional for child products
|
|
257
|
257
|
title = models.CharField(_('Title'), max_length=255, blank=True, null=True)
|
|
258
|
258
|
slug = models.SlugField(_('Slug'), max_length=255, unique=False)
|
|
|
@@ -267,23 +267,23 @@ class AbstractProduct(models.Model):
|
|
267
|
267
|
product_options = models.ManyToManyField('catalogue.Option', blank=True,
|
|
268
|
268
|
help_text=_("""Options are values that can be associated with a item when it is added to
|
|
269
|
269
|
a customer's basket. This could be something like a personalised message to be
|
|
270
|
|
- printed on a T-shirt.<br/>"""))
|
|
271
|
|
-
|
|
|
270
|
+ printed on a T-shirt."""))
|
|
|
271
|
+
|
|
272
|
272
|
related_products = models.ManyToManyField('catalogue.Product', related_name='relations', blank=True,
|
|
273
|
273
|
help_text=_("""Related items are things like different formats of the same book. Grouping them together allows
|
|
274
|
|
- better linking betwen products on the site.<br/>"""))
|
|
275
|
|
-
|
|
|
274
|
+ better linking betwen products on the site."""))
|
|
|
275
|
+
|
|
276
|
276
|
# Recommended products
|
|
277
|
277
|
recommended_products = models.ManyToManyField('catalogue.Product', through='ProductRecommendation', blank=True)
|
|
278
|
|
-
|
|
|
278
|
+
|
|
279
|
279
|
# Product score
|
|
280
|
280
|
score = models.FloatField(_('Score'), default=0.00, db_index=True)
|
|
281
|
|
-
|
|
|
281
|
+
|
|
282
|
282
|
date_created = models.DateTimeField(auto_now_add=True)
|
|
283
|
283
|
|
|
284
|
284
|
# This field is used by Haystack to reindex search
|
|
285
|
285
|
date_updated = models.DateTimeField(auto_now=True, db_index=True)
|
|
286
|
|
-
|
|
|
286
|
+
|
|
287
|
287
|
categories = models.ManyToManyField('catalogue.Category', through='ProductCategory')
|
|
288
|
288
|
|
|
289
|
289
|
is_discountable = models.BooleanField(default=True)
|
|
|
@@ -304,15 +304,15 @@ class AbstractProduct(models.Model):
|
|
304
|
304
|
def is_top_level(self):
|
|
305
|
305
|
u"""Return True if this is a parent product"""
|
|
306
|
306
|
return self.parent_id == None
|
|
307
|
|
-
|
|
|
307
|
+
|
|
308
|
308
|
@property
|
|
309
|
309
|
def is_group(self):
|
|
310
|
310
|
u"""Return True if this is a top level product and has more than 0 variants"""
|
|
311
|
311
|
# use len() instead of count() in this specific instance
|
|
312
|
|
- # as variants are highly likely to be used after this
|
|
|
312
|
+ # as variants are highly likely to be used after this
|
|
313
|
313
|
# which reduces the amount of SQL queries required
|
|
314
|
314
|
return self.is_top_level and len(self.variants.all()) > 0
|
|
315
|
|
-
|
|
|
315
|
+
|
|
316
|
316
|
@property
|
|
317
|
317
|
def is_variant(self):
|
|
318
|
318
|
u"""Return True if a product is not a top level product"""
|
|
|
@@ -326,7 +326,7 @@ class AbstractProduct(models.Model):
|
|
326
|
326
|
def min_variant_price_incl_tax(self):
|
|
327
|
327
|
u"""Return minimum variant price including tax"""
|
|
328
|
328
|
return self._min_variant_price('price_incl_tax')
|
|
329
|
|
-
|
|
|
329
|
+
|
|
330
|
330
|
@property
|
|
331
|
331
|
def min_variant_price_excl_tax(self):
|
|
332
|
332
|
u"""Return minimum variant price excluding tax"""
|
|
|
@@ -352,7 +352,7 @@ class AbstractProduct(models.Model):
|
|
352
|
352
|
def add_category_from_breadcrumbs(self, breadcrumb):
|
|
353
|
353
|
from oscar.apps.catalogue.utils import breadcrumbs_to_category
|
|
354
|
354
|
category = breadcrumbs_to_category(breadcrumb)
|
|
355
|
|
-
|
|
|
355
|
+
|
|
356
|
356
|
temp = models.get_model('product', 'productcategory')(category=category, product=self)
|
|
357
|
357
|
temp.save()
|
|
358
|
358
|
|
|
|
@@ -366,7 +366,7 @@ class AbstractProduct(models.Model):
|
|
366
|
366
|
if not title and self.parent_id:
|
|
367
|
367
|
title = self.parent.title
|
|
368
|
368
|
return title
|
|
369
|
|
-
|
|
|
369
|
+
|
|
370
|
370
|
def get_product_class(self):
|
|
371
|
371
|
"""
|
|
372
|
372
|
Return a product's item class
|
|
|
@@ -388,7 +388,7 @@ class AbstractProduct(models.Model):
|
|
388
|
388
|
}
|
|
389
|
389
|
|
|
390
|
390
|
# Helpers
|
|
391
|
|
-
|
|
|
391
|
+
|
|
392
|
392
|
def _min_variant_price(self, property):
|
|
393
|
393
|
u"""Return minimum variant price"""
|
|
394
|
394
|
prices = []
|
|
|
@@ -410,30 +410,30 @@ class AbstractProduct(models.Model):
|
|
410
|
410
|
if self.is_variant:
|
|
411
|
411
|
return u"%s (%s)" % (self.get_title(), self.attribute_summary())
|
|
412
|
412
|
return self.get_title()
|
|
413
|
|
-
|
|
|
413
|
+
|
|
414
|
414
|
@models.permalink
|
|
415
|
415
|
def get_absolute_url(self):
|
|
416
|
416
|
u"""Return a product's absolute url"""
|
|
417
|
417
|
return ('catalogue:detail', (), {
|
|
418
|
418
|
'product_slug': self.slug,
|
|
419
|
419
|
'pk': self.id})
|
|
420
|
|
-
|
|
|
420
|
+
|
|
421
|
421
|
def __init__(self, *args, **kwargs):
|
|
422
|
422
|
super(AbstractProduct, self).__init__(*args, **kwargs)
|
|
423
|
423
|
self.attr = ProductAttributesContainer(product=self)
|
|
424
|
|
-
|
|
|
424
|
+
|
|
425
|
425
|
def save(self, *args, **kwargs):
|
|
426
|
426
|
if self.is_top_level and not self.title:
|
|
427
|
427
|
raise ValidationError(_("Canonical products must have a title"))
|
|
428
|
428
|
if not self.slug:
|
|
429
|
429
|
self.slug = slugify(self.get_title())
|
|
430
|
|
-
|
|
|
430
|
+
|
|
431
|
431
|
# Validate attributes if necessary
|
|
432
|
432
|
self.attr.validate_attributes()
|
|
433
|
|
-
|
|
|
433
|
+
|
|
434
|
434
|
# Save product
|
|
435
|
435
|
super(AbstractProduct, self).save(*args, **kwargs)
|
|
436
|
|
-
|
|
|
436
|
+
|
|
437
|
437
|
# Finally, save attributes
|
|
438
|
438
|
self.attr.save()
|
|
439
|
439
|
|
|
|
@@ -447,15 +447,15 @@ class ProductRecommendation(models.Model):
|
|
447
|
447
|
ranking = models.PositiveSmallIntegerField(_('Ranking'), default=0)
|
|
448
|
448
|
|
|
449
|
449
|
class Meta:
|
|
450
|
|
- verbose_name = _('Product Recommendation')
|
|
451
|
|
- verbose_name_plural = _('Product Recomendations')
|
|
|
450
|
+ verbose_name = _('Product recommendation')
|
|
|
451
|
+ verbose_name_plural = _('Product recomendations')
|
|
452
|
452
|
|
|
453
|
453
|
|
|
454
|
454
|
class ProductAttributesContainer(object):
|
|
455
|
455
|
"""
|
|
456
|
456
|
Stolen liberally from django-eav, but simplified to be product-specific
|
|
457
|
457
|
"""
|
|
458
|
|
-
|
|
|
458
|
+
|
|
459
|
459
|
def __init__(self, product):
|
|
460
|
460
|
self.product = product
|
|
461
|
461
|
self.initialised = False
|
|
|
@@ -472,9 +472,9 @@ class ProductAttributesContainer(object):
|
|
472
|
472
|
if result:
|
|
473
|
473
|
return result
|
|
474
|
474
|
raise AttributeError((_(u"%(obj)s has no attribute named " \
|
|
475
|
|
- u"'%(attr)s'") % \
|
|
476
|
|
- {'obj': self.product.product_class, 'attr': name}))
|
|
477
|
|
-
|
|
|
475
|
+ u"'%(attr)s'") % \
|
|
|
476
|
+ {'obj': self.product.product_class, 'attr': name}))
|
|
|
477
|
+
|
|
478
|
478
|
def validate_attributes(self):
|
|
479
|
479
|
for attribute in self.get_all_attributes():
|
|
480
|
480
|
value = getattr(self, attribute.code, None)
|
|
|
@@ -490,22 +490,22 @@ class ProductAttributesContainer(object):
|
|
490
|
490
|
raise ValidationError(_(u"%(attr)s attribute %(err)s") % \
|
|
491
|
491
|
{'attr': attribute.code,
|
|
492
|
492
|
'err': e})
|
|
493
|
|
-
|
|
|
493
|
+
|
|
494
|
494
|
def get_values(self):
|
|
495
|
495
|
return self.product.attribute_values.all()
|
|
496
|
|
-
|
|
|
496
|
+
|
|
497
|
497
|
def get_value_by_attribute(self, attribute):
|
|
498
|
|
- return self.get_values().get(attribute=attribute)
|
|
499
|
|
-
|
|
|
498
|
+ return self.get_values().get(attribute=attribute)
|
|
|
499
|
+
|
|
500
|
500
|
def get_all_attributes(self):
|
|
501
|
501
|
return self.product.get_product_class().attributes.all()
|
|
502
|
|
-
|
|
|
502
|
+
|
|
503
|
503
|
def get_attribute_by_code(self, code):
|
|
504
|
504
|
return self.get_all_attributes().get(code=code)
|
|
505
|
|
-
|
|
|
505
|
+
|
|
506
|
506
|
def __iter__(self):
|
|
507
|
507
|
return iter(self.get_values())
|
|
508
|
|
-
|
|
|
508
|
+
|
|
509
|
509
|
def save(self):
|
|
510
|
510
|
for attribute in self.get_all_attributes():
|
|
511
|
511
|
if hasattr(self, attribute.code):
|
|
|
@@ -514,7 +514,7 @@ class ProductAttributesContainer(object):
|
|
514
|
514
|
|
|
515
|
515
|
|
|
516
|
516
|
class AbstractProductAttribute(models.Model):
|
|
517
|
|
-
|
|
|
517
|
+
|
|
518
|
518
|
TYPE_CHOICES = (
|
|
519
|
519
|
("text", _("Text")),
|
|
520
|
520
|
("integer", _("Integer")),
|
|
|
@@ -525,7 +525,7 @@ class AbstractProductAttribute(models.Model):
|
|
525
|
525
|
("option", _("Option")),
|
|
526
|
526
|
("entity", _("Entity"))
|
|
527
|
527
|
)
|
|
528
|
|
-
|
|
|
528
|
+
|
|
529
|
529
|
"""
|
|
530
|
530
|
Defines an attribute for a product class. (For example, number_of_pages for a 'book' class)
|
|
531
|
531
|
"""
|
|
|
@@ -541,10 +541,10 @@ class AbstractProductAttribute(models.Model):
|
|
541
|
541
|
required = models.BooleanField(_('required'), default=False)
|
|
542
|
542
|
|
|
543
|
543
|
class Meta:
|
|
544
|
|
- abstract = True
|
|
|
544
|
+ abstract = True
|
|
545
|
545
|
ordering = ['code']
|
|
546
|
|
- verbose_name = _('Product Attribute')
|
|
547
|
|
- verbose_name_plural = _('Product Attributes')
|
|
|
546
|
+ verbose_name = _('Product attribute')
|
|
|
547
|
+ verbose_name_plural = _('Product attributes')
|
|
548
|
548
|
|
|
549
|
549
|
def _validate_text(self, value):
|
|
550
|
550
|
if not (type(value) == unicode or type(value) == str):
|
|
|
@@ -587,7 +587,7 @@ class AbstractProductAttribute(models.Model):
|
|
587
|
587
|
if value not in valid_values:
|
|
588
|
588
|
raise ValidationError(_(u"%(enum)s is not a valid choice "
|
|
589
|
589
|
u"for %(attr)s") % \
|
|
590
|
|
- {'enum': value, 'attr': self})
|
|
|
590
|
+ {'enum': value, 'attr': self})
|
|
591
|
591
|
|
|
592
|
592
|
def get_validator(self):
|
|
593
|
593
|
DATATYPE_VALIDATORS = {
|
|
|
@@ -601,14 +601,14 @@ class AbstractProductAttribute(models.Model):
|
|
601
|
601
|
'option': self._validate_option,
|
|
602
|
602
|
}
|
|
603
|
603
|
|
|
604
|
|
- return DATATYPE_VALIDATORS[self.type]
|
|
|
604
|
+ return DATATYPE_VALIDATORS[self.type]
|
|
605
|
605
|
|
|
606
|
606
|
def __unicode__(self):
|
|
607
|
607
|
return self.name
|
|
608
|
608
|
|
|
609
|
609
|
def save(self, *args, **kwargs):
|
|
610
|
610
|
super(AbstractProductAttribute, self).save(*args, **kwargs)
|
|
611
|
|
-
|
|
|
611
|
+
|
|
612
|
612
|
def save_value(self, product, value):
|
|
613
|
613
|
try:
|
|
614
|
614
|
value_obj = product.attribute_values.get(attribute=self)
|
|
|
@@ -622,10 +622,10 @@ class AbstractProductAttribute(models.Model):
|
|
622
|
622
|
if value != value_obj.value:
|
|
623
|
623
|
value_obj.value = value
|
|
624
|
624
|
value_obj.save()
|
|
625
|
|
-
|
|
|
625
|
+
|
|
626
|
626
|
def validate_value(self, value):
|
|
627
|
627
|
self.get_validator()(value)
|
|
628
|
|
-
|
|
|
628
|
+
|
|
629
|
629
|
def is_value_valid(self, value):
|
|
630
|
630
|
"""
|
|
631
|
631
|
Check whether the passed value is valid for this attribute
|
|
|
@@ -639,9 +639,9 @@ class AbstractProductAttribute(models.Model):
|
|
639
|
639
|
class AbstractProductAttributeValue(models.Model):
|
|
640
|
640
|
"""
|
|
641
|
641
|
The "through" model for the m2m relationship between catalogue.Product
|
|
642
|
|
- and catalogue.ProductAttribute.
|
|
|
642
|
+ and catalogue.ProductAttribute.
|
|
643
|
643
|
This specifies the value of the attribute for a particular product
|
|
644
|
|
-
|
|
|
644
|
+
|
|
645
|
645
|
For example: number_of_pages = 295
|
|
646
|
646
|
"""
|
|
647
|
647
|
attribute = models.ForeignKey('catalogue.ProductAttribute')
|
|
|
@@ -654,18 +654,18 @@ class AbstractProductAttributeValue(models.Model):
|
|
654
|
654
|
value_date = models.DateField(_('date'), blank=True, null=True)
|
|
655
|
655
|
value_option = models.ForeignKey('catalogue.AttributeOption', blank=True, null=True)
|
|
656
|
656
|
value_entity = models.ForeignKey('catalogue.AttributeEntity', blank=True, null=True)
|
|
657
|
|
-
|
|
|
657
|
+
|
|
658
|
658
|
def _get_value(self):
|
|
659
|
659
|
return getattr(self, 'value_%s' % self.attribute.type)
|
|
660
|
|
-
|
|
|
660
|
+
|
|
661
|
661
|
def _set_value(self, new_value):
|
|
662
|
662
|
if self.attribute.type == 'option' and isinstance(new_value, str):
|
|
663
|
663
|
# Need to look up instance of AttributeOption
|
|
664
|
664
|
new_value = self.attribute.option_group.options.get(option=new_value)
|
|
665
|
665
|
setattr(self, 'value_%s' % self.attribute.type, new_value)
|
|
666
|
|
-
|
|
|
666
|
+
|
|
667
|
667
|
value = property(_get_value, _set_value)
|
|
668
|
|
-
|
|
|
668
|
+
|
|
669
|
669
|
class Meta:
|
|
670
|
670
|
abstract = True
|
|
671
|
671
|
verbose_name = _('Product Attribute Value')
|
|
|
@@ -673,19 +673,19 @@ class AbstractProductAttributeValue(models.Model):
|
|
673
|
673
|
|
|
674
|
674
|
def __unicode__(self):
|
|
675
|
675
|
return u"%s: %s" % (self.attribute.name, self.value)
|
|
676
|
|
-
|
|
677
|
|
-
|
|
|
676
|
+
|
|
|
677
|
+
|
|
678
|
678
|
class AbstractAttributeOptionGroup(models.Model):
|
|
679
|
679
|
"""
|
|
680
|
|
- Defines a group of options that collectively may be used as an
|
|
|
680
|
+ Defines a group of options that collectively may be used as an
|
|
681
|
681
|
attribute type
|
|
682
|
682
|
For example, Language
|
|
683
|
683
|
"""
|
|
684
|
684
|
name = models.CharField(_('name'), max_length=128)
|
|
685
|
|
-
|
|
|
685
|
+
|
|
686
|
686
|
def __unicode__(self):
|
|
687
|
687
|
return self.name
|
|
688
|
|
-
|
|
|
688
|
+
|
|
689
|
689
|
class Meta:
|
|
690
|
690
|
abstract = True
|
|
691
|
691
|
verbose_name = _('Attribute Option Group')
|
|
|
@@ -699,14 +699,14 @@ class AbstractAttributeOption(models.Model):
|
|
699
|
699
|
"""
|
|
700
|
700
|
group = models.ForeignKey('catalogue.AttributeOptionGroup', related_name='options')
|
|
701
|
701
|
option = models.CharField(_('option'), max_length=255)
|
|
702
|
|
-
|
|
|
702
|
+
|
|
703
|
703
|
def __unicode__(self):
|
|
704
|
704
|
return self.option
|
|
705
|
|
-
|
|
|
705
|
+
|
|
706
|
706
|
class Meta:
|
|
707
|
707
|
abstract = True
|
|
708
|
|
- verbose_name = _('Attribute Option')
|
|
709
|
|
- verbose_name_plural = _('Attribute Options')
|
|
|
708
|
+ verbose_name = _('Attribute option')
|
|
|
709
|
+ verbose_name_plural = _('Attribute options')
|
|
710
|
710
|
|
|
711
|
711
|
|
|
712
|
712
|
class AbstractAttributeEntity(models.Model):
|
|
|
@@ -719,7 +719,7 @@ class AbstractAttributeEntity(models.Model):
|
|
719
|
719
|
|
|
720
|
720
|
def __unicode__(self):
|
|
721
|
721
|
return self.name
|
|
722
|
|
-
|
|
|
722
|
+
|
|
723
|
723
|
class Meta:
|
|
724
|
724
|
abstract = True
|
|
725
|
725
|
verbose_name = _('Attribute entity')
|
|
|
@@ -737,10 +737,10 @@ class AbstractAttributeEntityType(models.Model):
|
|
737
|
737
|
"""
|
|
738
|
738
|
name = models.CharField(_("Name"), max_length=255)
|
|
739
|
739
|
slug = models.SlugField(_("Slug"), max_length=255, unique=False, blank=True)
|
|
740
|
|
-
|
|
|
740
|
+
|
|
741
|
741
|
def __unicode__(self):
|
|
742
|
742
|
return self.name
|
|
743
|
|
-
|
|
|
743
|
+
|
|
744
|
744
|
class Meta:
|
|
745
|
745
|
abstract = True
|
|
746
|
746
|
verbose_name = _('Attribute Entity Type')
|
|
|
@@ -750,29 +750,29 @@ class AbstractAttributeEntityType(models.Model):
|
|
750
|
750
|
if not self.slug:
|
|
751
|
751
|
self.slug = slugify(self.name)
|
|
752
|
752
|
super(AbstractAttributeEntityType, self).save(*args, **kwargs)
|
|
753
|
|
-
|
|
754
|
|
-
|
|
|
753
|
+
|
|
|
754
|
+
|
|
755
|
755
|
class AbstractOption(models.Model):
|
|
756
|
756
|
u"""
|
|
757
|
757
|
An option that can be selected for a particular item when the product
|
|
758
|
|
- is added to the basket.
|
|
759
|
|
-
|
|
760
|
|
- Eg a list ID for an SMS message send, or a personalised message to
|
|
761
|
|
- print on a T-shirt.
|
|
762
|
|
-
|
|
763
|
|
- This is not the same as an attribute as options do not have a fixed value for
|
|
|
758
|
+ is added to the basket.
|
|
|
759
|
+
|
|
|
760
|
+ Eg a list ID for an SMS message send, or a personalised message to
|
|
|
761
|
+ print on a T-shirt.
|
|
|
762
|
+
|
|
|
763
|
+ This is not the same as an attribute as options do not have a fixed value for
|
|
764
|
764
|
a particular item - options, they need to be specified by the customer.
|
|
765
|
765
|
"""
|
|
766
|
766
|
name = models.CharField(_('name'), max_length=128)
|
|
767
|
767
|
code = models.SlugField(_('code'), max_length=128)
|
|
768
|
|
-
|
|
|
768
|
+
|
|
769
|
769
|
REQUIRED, OPTIONAL = ('Required', 'Optional')
|
|
770
|
770
|
TYPE_CHOICES = (
|
|
771
|
771
|
(REQUIRED, _("Required - a value for this option must be specified")),
|
|
772
|
772
|
(OPTIONAL, _("Optional - a value for this option can be omitted")),
|
|
773
|
773
|
)
|
|
774
|
774
|
type = models.CharField(_("Status"), max_length=128, default=REQUIRED, choices=TYPE_CHOICES)
|
|
775
|
|
-
|
|
|
775
|
+
|
|
776
|
776
|
class Meta:
|
|
777
|
777
|
abstract = True
|
|
778
|
778
|
verbose_name = _('Option')
|
|
|
@@ -780,7 +780,7 @@ class AbstractOption(models.Model):
|
|
780
|
780
|
|
|
781
|
781
|
def __unicode__(self):
|
|
782
|
782
|
return self.name
|
|
783
|
|
-
|
|
|
783
|
+
|
|
784
|
784
|
def save(self, *args, **kwargs):
|
|
785
|
785
|
if not self.code:
|
|
786
|
786
|
self.code = slugify(self.name)
|
|
|
@@ -800,13 +800,13 @@ class AbstractProductImage(models.Model):
|
|
800
|
800
|
product = models.ForeignKey('catalogue.Product', related_name='images')
|
|
801
|
801
|
original = models.ImageField(_("Original"), upload_to=settings.OSCAR_IMAGE_FOLDER)
|
|
802
|
802
|
caption = models.CharField(_("Caption"), max_length=200, blank=True, null=True)
|
|
803
|
|
-
|
|
|
803
|
+
|
|
804
|
804
|
# Use display_order to determine which is the "primary" image
|
|
805
|
805
|
display_order = models.PositiveIntegerField(_("Display Order"), default=0,
|
|
806
|
806
|
help_text=_("""An image with a display order of
|
|
807
|
807
|
zero will be the primary image for a product"""))
|
|
808
|
808
|
date_created = models.DateTimeField(auto_now_add=True)
|
|
809
|
|
-
|
|
|
809
|
+
|
|
810
|
810
|
class Meta:
|
|
811
|
811
|
abstract = True
|
|
812
|
812
|
unique_together = ("product", "display_order")
|
|
|
@@ -834,7 +834,7 @@ class AbstractProductImage(models.Model):
|
|
834
|
834
|
images in a specific way.
|
|
835
|
835
|
"""
|
|
836
|
836
|
return self.resized_image_url()
|
|
837
|
|
-
|
|
|
837
|
+
|
|
838
|
838
|
@property
|
|
839
|
839
|
def thumbnail_url(self):
|
|
840
|
840
|
return self.resized_image_url()
|