def test_deconstruct():
    f1 = MoneyField(currency='EUR', default=EUR(0))
    name, path, args, kwargs = f1.deconstruct()
    f2 = MoneyField(*args, **kwargs)
    assert f1.currency_code == f2.currency_code
    assert f1.decimal_places == f2.decimal_places
    assert f1.default == f2.default
def test_get_default():
    OneEuro = EUR(1)
    f = MoneyField(currency='EUR', null=True)
    assert f.get_default() is None
    f = MoneyField(currency='EUR', null=True, default=EUR())
    assert f.get_default() == EUR()
    f = MoneyField(currency='EUR', null=False, default=OneEuro)
    assert f.get_default() == OneEuro
def test_to_python():
    f = MoneyField(currency='EUR', null=True)
    assert f.to_python(3) == EUR('3')
    assert f.to_python('3.14') == EUR('3.14')
    assert f.to_python(None) == EUR()
    assert f.to_python(EUR(3)) == EUR('3')
    with pytest.raises(ValidationError):
        f.to_python('abc')
示例#4
0
class OrderPayment(with_metaclass(deferred.ForeignKeyBuilder, models.Model)):
    """
    A model to hold received payments for a given order.
    """
    order = deferred.ForeignKey(
        BaseOrder,
        verbose_name=_("Order"),
    )

    amount = MoneyField(
        _("Amount paid"),
        help_text=_("How much was paid with this particular transfer."),
    )

    transaction_id = models.CharField(
        _("Transaction ID"),
        max_length=255,
        help_text=_("The transaction processor's reference"),
    )

    created_at = models.DateTimeField(
        _("Received at"),
        auto_now_add=True,
    )

    payment_method = models.CharField(
        _("Payment method"),
        max_length=50,
        help_text=_("The payment backend used to process the purchase"),
    )

    class Meta:
        verbose_name = pgettext_lazy('order_models', "Order payment")
        verbose_name_plural = pgettext_lazy('order_models', "Order payments")
示例#5
0
class SmartCard(Product):
    # common product fields
    unit_price = MoneyField(_("Unit price"), decimal_places=3,
        help_text=_("Net price for this product"))

    # product properties
    CARD_TYPE = (2 * ('{}{}'.format(s, t),)
                 for t in ('SD', 'SDXC', 'SDHC', 'SDHC II') for s in ('', 'micro '))
    card_type = models.CharField(_("Card Type"), choices=CARD_TYPE, max_length=15)
    SPEED = ((str(s), "{} MB/s".format(s)) for s in (4, 20, 30, 40, 48, 80, 95, 280))
    speed = models.CharField(_("Transfer Speed"), choices=SPEED, max_length=8)
    product_code = models.CharField(_("Product code"), max_length=255, unique=True)
    storage = models.PositiveIntegerField(_("Storage Capacity"),
        help_text=_("Storage capacity in GB"))
    multilingual = TranslatedFields(description=HTMLField(verbose_name=_("Description"),
        configuration='CKEDITOR_SETTINGS_DESCRIPTION',
        help_text=_("Full description used in the catalog's detail view of Smart Cards.")))

    class Meta:
        verbose_name = _("Smart Card")
        verbose_name_plural = _("Smart Cards")

    def get_price(self, request):
        return self.unit_price

    def get_product_variant(self, extra):
        """
        SmartCards do not have flavors, they are the product.
        """
        return self
示例#6
0
文件: models.py 项目: boooka/my-shop
class Commodity(Product):
    """
    This Commodity model inherits from polymorphic Product, and therefore has to be redefined.
    """
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    # controlling the catalog
    placeholder = PlaceholderField("Commodity Details")
    show_breadcrumb = True  # hard coded to always show the product's breadcrumb

    default_manager = TranslatableManager()

    class Meta:
        verbose_name = _("Commodity")
        verbose_name_plural = _("Commodities")

    def get_price(self, request):
        return self.unit_price
示例#7
0
class Membership(Product):
    """
    This Commodity model inherits from polymorphic Product, and therefore has to be redefined.
    """
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    signup_date = models.DateField(
        _("Signed Up"),
        default=timezone.now,
    )

    profile = models.ForeignKey(User, null=True, blank=True)

    # controlling the catalog
    placeholder = PlaceholderField("Membership Details")
    show_breadcrumb = True  # hard coded to always show the product's breadcrumb

    class Meta:
        verbose_name = _("Membership")

    def get_price(self, request):
        return self.unit_price
示例#8
0
class KhimageVariant(models.Model):
    product = models.ForeignKey(
        KhimageModel,
        verbose_name=_("Khimage Model"),
        related_name='variants',
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    storage = models.PositiveIntegerField(
        _("Internal Storage"),
        help_text=_("Internal storage in MB"),
    )

    def get_price(self, request):
        return self.unit_price
示例#9
0
class SmartCard(CMSPageReferenceMixin, TranslatableModelMixin, BaseProduct):
    product_name = models.CharField(max_length=255,
                                    verbose_name=_("Product Name"))
    slug = models.SlugField(verbose_name=_("Slug"))
    unit_price = MoneyField(_("Unit price"),
                            decimal_places=3,
                            help_text=_("Net price for this product"))
    description = TranslatedField()

    # product properties
    manufacturer = models.ForeignKey(Manufacturer,
                                     verbose_name=_("Manufacturer"))
    CARD_TYPE = (2 * ('{}{}'.format(s, t), )
                 for t in ('SD', 'SDXC', 'SDHC', 'SDHC II')
                 for s in ('', 'micro '))
    card_type = models.CharField(_("Card Type"),
                                 choices=CARD_TYPE,
                                 max_length=15)
    SPEED = ((str(s), "{} MB/s".format(s))
             for s in (4, 20, 30, 40, 48, 80, 95, 280))
    speed = models.CharField(_("Transfer Speed"), choices=SPEED, max_length=8)
    product_code = models.CharField(_("Product code"),
                                    max_length=255,
                                    unique=True)
    storage = models.PositiveIntegerField(
        _("Storage Capacity"), help_text=_("Storage capacity in GB"))

    # controlling the catalog
    order = models.PositiveIntegerField(verbose_name=_("Sort by"),
                                        db_index=True)
    cms_pages = models.ManyToManyField(
        'cms.Page',
        through=ProductPage,
        help_text=_("Choose list view this product shall appear on."))
    images = models.ManyToManyField('filer.Image', through=ProductImage)

    objects = ProductManager()

    # filter expression used to lookup for a product item using the Select2 widget
    lookup_fields = (
        'product_code__startswith',
        'product_name__icontains',
    )

    class Meta:
        verbose_name = _("Smart Card")
        verbose_name_plural = _("Smart Cards")
        ordering = ('order', )

    objects = ProductManager()

    def __str__(self):
        return self.product_name

    @property
    def sample_image(self):
        return self.images.first()

    def get_price(self, request):
        return self.unit_price
示例#10
0
class Commodity(AvailableProductMixin, Product):
    """
    This Commodity model inherits from polymorphic Product, and therefore has to be redefined.
    """
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    quantity = models.PositiveIntegerField(
        _("Quantity"),
        default=0,
        validators=[MinValueValidator(0)],
        help_text=_("Available quantity in stock"))

    # controlling the catalog
    placeholder = PlaceholderField("Commodity Details")
    show_breadcrumb = True  # hard coded to always show the product's breadcrumb

    class Meta:
        verbose_name = _("Commodity")
        verbose_name_plural = _("Commodities")

    def get_price(self, request):
        return self.unit_price
示例#11
0
class SmartCard(Product):
    # common product fields
    unit_price = MoneyField(_("Unit price"), decimal_places=3,
        help_text=_("Net price for this product"))

    # product properties
    CARD_TYPE = (2 * ('{}{}'.format(s, t),)
                 for t in ('SD', 'SDXC', 'SDHC', 'SDHC II') for s in ('', 'micro '))
    card_type = models.CharField(_("Card Type"), choices=CARD_TYPE, max_length=15)
    SPEED = ((str(s), "{} MB/s".format(s)) for s in (4, 20, 30, 40, 48, 80, 95, 280))
    speed = models.CharField(_("Transfer Speed"), choices=SPEED, max_length=8)
    product_code = models.CharField(_("Product code"), max_length=255, unique=True)
    storage = models.PositiveIntegerField(_("Storage Capacity"),
        help_text=_("Storage capacity in GB"))

    class Meta:
        verbose_name = _("Smart Card")
        verbose_name_plural = _("Smart Cards")

    def get_price(self, request):
        return self.unit_price

    def get_product_markedness(self, extra):
        """
        SmartCards do not have a markedness, they are the product.
        """
        return self
示例#12
0
class SmartCard(AvailableProductMixin, Product):
    multilingual = TranslatedFields(
        description=HTMLField(
            verbose_name=_("Description"),
            configuration='CKEDITOR_SETTINGS_DESCRIPTION',
            help_text=_(
                "Full description used in the catalog's detail view of Smart Cards."),
        ),
    )
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    card_type = models.CharField(
        _("Card Type"),
        choices=[2 * ('{}{}'.format(s, t),)
                 for t in ['SD', 'SDXC', 'SDHC', 'SDHC II'] for s in ['', 'micro ']],
        max_length=15,
    )

    speed = models.CharField(
        _("Transfer Speed"),
        choices=[(str(s), "{} MB/s".format(s))
                 for s in [4, 20, 30, 40, 48, 80, 95, 280]],
        max_length=8,
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    storage = models.PositiveIntegerField(
        _("Storage Capacity"),
        help_text=_("Storage capacity in GB"),
    )

    quantity = models.PositiveIntegerField(
        _("Quantity"),
        default=0,
        validators=[MinValueValidator(0)],
        help_text=_("Available quantity in stock")
    )

    class Meta:
        verbose_name = _("Smart Card")
        verbose_name_plural = _("Smart Cards")
        ordering = ['order']

    # filter expression used to lookup for a product item using the Select2 widget
    lookup_fields = ['product_code__startswith', 'product_name__icontains']

    def get_price(self, request):
        return self.unit_price

    default_manager = ProductManager()
示例#13
0
class SmartCard(Product):
    # common product fields
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    # product properties
    CARD_TYPE = (2 * ('{}{}'.format(s, t), )
                 for t in ('SD', 'SDXC', 'SDHC', 'SDHC II')
                 for s in ('', 'micro '))
    card_type = models.CharField(
        _("Card Type"),
        choices=CARD_TYPE,
        max_length=15,
    )

    SPEED = [(str(s), "{} MB/s".format(s))
             for s in (4, 20, 30, 40, 48, 80, 95, 280)]
    speed = models.CharField(
        _("Transfer Speed"),
        choices=SPEED,
        max_length=8,
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )
    manufacturer = models.ForeignKey(
        Manufacturer,
        default=1,
        verbose_name=_("Manufacturer"),
    )
    storage = models.PositiveIntegerField(
        _("Storage Capacity"),
        help_text=_("Storage capacity in GB"),
    )

    description = HTMLField(
        verbose_name=_("Description"),
        configuration='CKEDITOR_SETTINGS_DESCRIPTION',
        help_text=_(
            "Full description used in the catalog's detail view of Smart Cards."
        ),
    )

    default_manager = BaseProductManager()

    class Meta:
        verbose_name = _("Smart Card")
        verbose_name_plural = _("Smart Cards")

    def get_price(self, request):
        return self.unit_price
示例#14
0
    class Commodity(CMSPageReferenceMixin, TranslatableModelMixin,
                    BaseProduct):
        """
        Generic Product Commodity to be used whenever the merchant does not require product specific
        attributes and just required a placeholder field to add arbitrary data.
        """
        # common product fields
        product_code = models.CharField(_("Product code"),
                                        max_length=255,
                                        unique=True)
        unit_price = MoneyField(_("Unit price"),
                                decimal_places=3,
                                help_text=_("Net price for this product"))

        # controlling the catalog
        order = models.PositiveIntegerField(verbose_name=_("Sort by"),
                                            db_index=True)
        cms_pages = models.ManyToManyField(
            'cms.Page',
            through=ProductPage,
            help_text=_("Choose list view this product shall appear on."))
        sample_image = image.FilerImageField(
            verbose_name=_("Sample Image"),
            blank=True,
            null=True,
            help_text=_("Sample image used in the catalog's list view."))
        show_breadcrumb = models.BooleanField(
            _("Show Breadcrumb"),
            default=True,
            help_text=_(
                "Shall the detail page show the product's breadcrumb."))
        placeholder = PlaceholderField("Commodity Details")

        # translatable fields for the catalog's list- and detail views
        product_name = TranslatedField()
        slug = TranslatedField()
        caption = TranslatedField()

        # filter expression used to search for a product item using the Select2 widget
        lookup_fields = (
            'product_code__startswith',
            'product_name__icontains',
        )

        objects = ProductManager()

        class Meta:
            app_label = app_settings.APP_LABEL
            ordering = ('order', )
            verbose_name = _("Commodity")
            verbose_name_plural = _("Commodities")

        def __str__(self):
            return self.product_code

        def get_price(self, request):
            return self.unit_price
示例#15
0
class SmartPhone(models.Model):
    product = models.ForeignKey(SmartPhoneModel,
        verbose_name=_("Smart-Phone Model"))
    product_code = models.CharField(_("Product code"),
        max_length=255, unique=True)
    unit_price = MoneyField(_("Unit price"), decimal_places=3,
        help_text=_("Net price for this product"))
    storage = models.PositiveIntegerField(_("Internal Storage"),
        help_text=_("Internal storage in MB"))

    def get_price(self, request):
        return self.unit_price
示例#16
0
def test_to_python():
    f = MoneyDbField(currency='EUR', null=True)
    assert f.to_python(3) == EUR('3')
    assert f.to_python('3.14') == EUR('3.14')
    assert f.to_python(None) == EUR()
    with pytest.raises(ValidationError):
        f.to_python('abc')
示例#17
0
 def test_default(self):
     EUR = MoneyMaker("EUR")
     f = MoneyDbField(currency="EUR", null=False)
     self.assertEqual(f.get_default(), EUR())
     f = MoneyDbField(currency="EUR", null=True)
     self.assertEqual(f.get_default(), EUR())
     f = MoneyDbField(currency="EUR")
     self.assertEqual(f.get_default(), EUR())
示例#18
0
 def test_to_python(self):
     EUR = MoneyMaker("EUR")
     f = MoneyDbField(currency="EUR", null=True)
     self.assertEqual(f.to_python(3), EUR("3"))
     self.assertEqual(f.to_python("3.14"), EUR("3.14"))
     self.assertEqual(f.to_python(None), EUR())
     with self.assertRaises(ValidationError):
         f.to_python("abc")
示例#19
0
 def test_to_python(self):
     EUR = MoneyMaker('EUR')
     f = MoneyDbField(currency='EUR', null=True)
     self.assertEqual(f.to_python(3), EUR('3'))
     self.assertEqual(f.to_python('3.14'), EUR('3.14'))
     self.assertEqual(f.to_python(None), EUR())
     with self.assertRaises(ValidationError):
         f.to_python('abc')
示例#20
0
def test_default():
    OneEuro = EUR(1)
    f = MoneyDbField(currency='EUR', null=True)
    assert f.get_default() is None
    f = MoneyDbField(currency='EUR', null=True, default=EUR())
    assert f.get_default() == EUR()
    f = MoneyDbField(currency='EUR', null=False, default=OneEuro)
    assert f.get_default() == OneEuro
示例#21
0
 def test_default(self):
     EUR = MoneyMaker('EUR')
     f = MoneyDbField(currency='EUR', null=False)
     self.assertEqual(f.get_default(), EUR())
     f = MoneyDbField(currency='EUR', null=True)
     self.assertEqual(f.get_default(), EUR())
     f = MoneyDbField(currency='EUR')
     self.assertEqual(f.get_default(), EUR())
示例#22
0
 def test_to_python(self):
     EUR = MoneyMaker('EUR')
     f = MoneyDbField(currency='EUR', null=True)
     self.assertEqual(f.to_python(3), EUR('3'))
     self.assertEqual(f.to_python('3.14'), EUR('3.14'))
     self.assertEqual(f.to_python(None), EUR())
     with self.assertRaises(ValidationError):
         f.to_python('abc')
示例#23
0
class ShippingDestination(models.Model):
    shipping_method = models.ForeignKey(
        ShippingMethod,
        related_name='destinations',
    )

    country = models.CharField(max_length=3)

    price = MoneyField(currency='EUR')

    class Meta:
        verbose_name = _("Shipping Destination")
        verbose_name_plural = _("Shipping Destination")
        unique_together = ['country', 'shipping_method']
示例#24
0
 def test_default(self):
     EUR = MoneyMaker('EUR')
     OneEuro = EUR(1)
     f = MoneyDbField(currency='EUR', null=True)
     self.assertEqual(f.get_default(), None)
     f = MoneyDbField(currency='EUR', null=True, default=EUR())
     self.assertEqual(f.get_default(), EUR())
     f = MoneyDbField(currency='EUR', null=False, default=OneEuro)
     self.assertEqual(f.get_default(), OneEuro)
示例#25
0
 def test_default(self):
     EUR = MoneyMaker('EUR')
     OneEuro = EUR(1)
     f = MoneyDbField(currency='EUR', null=True)
     self.assertEqual(f.get_default(), None)
     f = MoneyDbField(currency='EUR', null=True, default=EUR())
     self.assertEqual(f.get_default(), EUR())
     f = MoneyDbField(currency='EUR', null=False, default=OneEuro)
     self.assertEqual(f.get_default(), OneEuro)
示例#26
0
class ScooterVariant(models.Model):
    product = models.ForeignKey(
        ScooterModel,
        verbose_name=_("Scooter Model"),
        related_name='variants',
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    #~ storage = models.PositiveIntegerField(
    #~ _("Internal Storage"),
    #~ help_text=_("Internal storage in MB"),
    #~ )

    #~ battery_capacity = models.DecimalField(
    #~ _("Battery capacity"),
    #~ max_digits=3,
    #~ decimal_places=1,
    #~ help_text=_("Battery capacity in Ah"),
    #~ )

    COLORS = (
        ("white", "white"),
        ("black", "black"),
    )

    color = models.CharField(
        _("Color"),
        max_length=255,
        choices=COLORS,
        default='white',
    )

    def get_price(self, request):
        return self.unit_price
示例#27
0
class Commodity(Product):
    # common product fields
    unit_price = MoneyField(_("Unit price"),
                            decimal_places=3,
                            help_text=_("Net price for this product"))
    product_code = models.CharField(_("Product code"),
                                    max_length=255,
                                    unique=True)

    # controlling the catalog
    placeholder = PlaceholderField("Commodity Details")

    class Meta:
        verbose_name = _("Commodity")
        verbose_name_plural = _("Commodities")

    def get_price(self, request):
        return self.unit_price
示例#28
0
文件: models.py 项目: bwrm/alby
class Commodity(AvailableProductMixin, Product, TranslatableModelMixin):
    """
    This Commodity model inherits from polymorphic Product, and therefore has to be redefined.
    """
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )
    product_code = models.ForeignKey(
        ProductList,
        on_delete=models.CASCADE,
    )
    multilingual = TranslatedFields(
        description=HTMLField(
            verbose_name=_("Description"),
            configuration='CKEDITOR_SETTINGS_DESCRIPTION',
            blank=True,
            help_text=
            _("Full description used in the catalog's detail view of Smart Cards."
              ),
        ),
        caption=HTMLField(
            verbose_name=_("Caption"),
            configuration='CKEDITOR_SETTINGS_DESCRIPTION',
            blank=True,
            help_text=
            _("Full description used in the catalog's detail view of Smart Cards."
              ),
        ),
    )
    # controlling the catalog
    placeholder = PlaceholderField("Commodity Details")
    show_breadcrumb = True  # hard coded to always show the product's breadcrumb

    default_manager = TranslatableManager()

    class Meta:
        verbose_name = _("Commodity")
        verbose_name_plural = _("Commodities")

    def get_price(self, request):
        return self.unit_price
示例#29
0
class SmartPhoneVariant(AvailableProductMixin, models.Model):
    product = models.ForeignKey(
        SmartPhoneModel,
        on_delete=models.CASCADE,
        verbose_name=_("Smartphone Model"),
        related_name='variants',
    )

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    storage = models.PositiveIntegerField(
        _("Internal Storage"),
        help_text=_("Internal storage in GB"),
    )

    quantity = models.PositiveIntegerField(
        _("Quantity"),
        default=0,
        validators=[MinValueValidator(0)],
        help_text=_("Available quantity in stock"))

    def __str__(self):
        return _("{product} with {storage} GB").format(product=self.product,
                                                       storage=self.storage)

    def get_price(self, request):
        return self.unit_price
示例#30
0
class Scoop(Product):
    # common product fields
    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Net price for this product"),
    )

    # product properties

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    time_duration = models.PositiveIntegerField(
        _("time duration"),
        help_text=_("time in days"),
    )

    description = HTMLField(
        verbose_name=_("Description"),
        configuration='CKEDITOR_SETTINGS_DESCRIPTION',
        help_text=_(
            "Full description used in the catalog's detail view of Scoop."),
    )

    default_manager = BaseProductManager()

    class Meta:
        verbose_name = _("Scoop")
        verbose_name_plural = _("Scoop")

    def get_price(self, request):
        return self.unit_price
示例#31
0
文件: models.py 项目: bwrm/alby
class SofaVariant(models.Model):
    class Meta:
        ordering = ('unit_price', )

    product_model = models.ForeignKey(
        SofaModel,
        related_name='variants',
        on_delete=models.CASCADE,
    )
    product_code = models.ForeignKey(
        ProductList,
        on_delete=models.CASCADE,
        blank=True,
    )
    images = models.ManyToManyField(
        'filer.Image',
        through='VariantImage',
    )

    unit_price = MoneyField(_("Unit price"), blank=True)

    fabric = models.ForeignKey(
        Fabric,
        on_delete=models.CASCADE,
        blank=True,
    )

    def get_availability(self, request, **kwargs):
        return True

    def __str__(self):
        return self.fabric.fabric_name

    def delete(self, using=None, keep_parents=False):
        ProductList.objects.filter(product_code=self.product_code).delete()
        super(SofaVariant, self).delete()
示例#32
0
def test_format():
    f = MoneyDbField(max_digits=5, decimal_places=3)
    assert f._format(f.to_python(2)) == '2.000'
    assert f._format(f.to_python('2.34567')) == '2.346'
    assert f._format(None) is None
示例#33
0
class WeltladenProduct(CMSPageReferenceMixin, TranslatableModelMixin,
                       BaseProduct):
    product_name = models.CharField(
        max_length=255,
        verbose_name=_("Product Name"),
    )

    slug = models.SlugField(verbose_name=_("Slug"))

    caption = TranslatedField()
    short_description = TranslatedField()
    description = TranslatedField()
    ingredients = TranslatedField()

    # product properties
    manufacturer = models.ForeignKey(
        Manufacturer,
        on_delete=models.CASCADE,
        verbose_name=_("Manufacturer"),
        blank=True,
        null=True,
    )

    additional_manufacturers = models.ManyToManyField(
        Manufacturer,
        blank=True,
        verbose_name=_("Additional Manufacturers"),
        related_name="additional_manufacturers",
    )

    display_manufacturer_as_raw_material_supplier = models.BooleanField(
        _("Display manufacturer as raw material supplier"), default=False)

    supplier = models.ForeignKey(Supplier,
                                 verbose_name=_("Supplier"),
                                 on_delete=models.CASCADE)

    quality_labels = models.ManyToManyField(QualityLabel,
                                            blank=True,
                                            verbose_name=_("Quality labels"),
                                            related_name="quality_labels")

    origin_countries = CountryField(
        verbose_name=_("Origin countries"),
        blank_label=_('Select one or many'),
        multiple=True,
        blank=True,
    )

    # controlling the catalog
    order = models.PositiveIntegerField(
        _("Sort by"),
        db_index=True,
    )

    cms_pages = models.ManyToManyField(
        'cms.Page',
        through=ProductPage,
        help_text=_("Choose list view this product shall appear on."),
    )

    images = models.ManyToManyField(
        'filer.Image',
        through=ProductImage,
    )

    unit_price = MoneyField(
        _("Unit price"),
        decimal_places=3,
        help_text=_("Gross price for this product"),
    )

    vegan = models.BooleanField(_("Vegan"), default=False)

    lactose_free = models.BooleanField(_("Lactose free"), default=False)

    gluten_free = models.BooleanField(_("Gluten free"), default=False)

    tax_switch = models.BooleanField(
        _("Switch Tax"),
        default=True,
        help_text=_(
            "If switched on, then 20% tax item, if off then 10% tax item"))

    product_code = models.CharField(
        _("Product code"),
        max_length=255,
        unique=True,
    )

    instagram_category = models.ForeignKey(InstagramCategory,
                                           on_delete=models.CASCADE,
                                           null=True,
                                           blank=True)

    weight = models.DecimalField(
        _("Weight"),
        help_text=_("Weight in kilograms (kg). max 99.99kg"),
        decimal_places=2,
        max_digits=4,
        default=0.0)

    class Meta:
        verbose_name = _("Product")
        verbose_name_plural = _("Products")
        ordering = ['order']

    # filter expression used to lookup for a product item using the Select2 widget
    lookup_fields = ['product_code__startswith', 'product_name__icontains']

    def get_price(self, request):
        return self.unit_price

    objects = ProductManager()

    def __str__(self):
        return self.product_name

    @property
    def ordered_quality_labels(self):
        return self.quality_labels.all().order_by('ordering')

    @property
    def sample_image(self):
        return self.images.first()

    def get_weight(self):
        return self.weight

    def invalidate_cache(self):
        """
        Method ``ProductCommonSerializer.render_html()`` caches the rendered HTML snippets.
        Invalidate this HTML snippet after changing relevant parts of the product.
        """
        shop_app = apps.get_app_config('shop')
        if shop_app.cache_supporting_wildcard:
            cache.delete('product:{}|*'.format(self.id))

    def clean_fields(self, exclude=None):
        super().clean_fields(exclude=exclude)
        if WeltladenProduct.objects.filter(slug=self.slug).exclude(
                id=self.id).exists():
            raise ValidationError(_('Product slug already exits'),
                                  code='invalid')
def test_from_db_value():
    f = MoneyField(currency='EUR', null=True)
    assert f.from_db_value(Decimal('3'), None, None) == EUR('3')
    assert f.from_db_value(3.45, None, None) == EUR('3.45')
    assert f.from_db_value(None, None, None) is None
示例#35
0
class Product(BaseProduct, TranslatableModel):
    """
    Product model.
    """
    SINGLE, GROUP, VARIANT = range(3)

    KINDS = (
        (SINGLE, _('Single')),
        (GROUP, _('Group')),
        (VARIANT, _('Variant')),
    )

    translations = TranslatedFields(
        name=models.CharField(
            _('Name'),
            max_length=128,
        ),
        slug=models.SlugField(
            _('Slug'),
            db_index=True,
            help_text=
            _("Part that's used in url to display this product. Needs to be unique."
              ),
        ),
        _caption=models.TextField(
            _('Caption'),
            max_length=255,
            blank=True,
            help_text=
            _("Short product caption, usually used in catalog's list view of products."
              ),
        ),
        _description=models.TextField(
            _('Description'),
            blank=True,
            help_text=
            _("Description of a product, usually used as lead text in product's detail view."
              ),
        ),
        meta={
            'unique_together': [('language_code', 'slug')],
        },
    )

    code = models.CharField(
        _('Code'),
        max_length=64,
        unique=True,
        help_text=_('Unique identifier for a product.'),
    )

    # Categorization
    _category = TreeForeignKey(
        Category,
        models.CASCADE,
        blank=True,
        null=True,
        verbose_name=_('Category'),
    )

    _brand = TreeForeignKey(
        Brand,
        models.CASCADE,
        blank=True,
        null=True,
        verbose_name=_('Brand'),
    )

    _manufacturer = TreeForeignKey(
        Manufacturer,
        models.CASCADE,
        blank=True,
        null=True,
        verbose_name=_('Manufacturer'),
    )

    # Pricing
    _unit_price = MoneyField(
        _('Unit price'),
        default=0,
        help_text=_("For variants leave empty to use the Group price."),
    )

    _discount = models.DecimalField(
        _('Discount %'),
        blank=True,
        null=True,
        max_digits=4,
        decimal_places=2,
        validators=[MinValueValidator(Decimal('0.00'))],
        help_text=_("For variants leave empty to use Group discount."),
    )

    _tax = models.ForeignKey(
        Tax,
        models.SET_NULL,
        blank=True,
        null=True,
        verbose_name=_('Tax'),
        help_text=_(
            "Tax to be applied to this product. Variants inherit tax percentage from their Group, and should "
            "leave this field empty."),
    )

    # Settings
    kind = models.PositiveSmallIntegerField(
        _('Kind'),
        choices=KINDS,
        default=SINGLE,
        help_text=
        _('Choose a product type. Single products are products without variations. Group products are base products '
          'that hold variants and their common info, they cannot be added to cart. Variants are variations of a '
          'product that must select a Group product, and set their unique set of attributes. '
          '(See "Variant" section below)'),
    )

    discountable = models.BooleanField(
        _('Discountable'),
        default=True,
        help_text=_('Can this product be used in an offer?'),
    )

    modifiers = models.ManyToManyField(
        Modifier,
        blank=True,
        verbose_name=_('Modifiers'),
        limit_choices_to={'kind__in': [Modifier.STANDARD, Modifier.DISCOUNT]},
    )

    flags = models.ManyToManyField(
        Flag,
        blank=True,
        verbose_name=_('Flags'),
        help_text=_('Check flags for this product.'),
    )

    # Measurements
    _width = MeasurementField(
        _('Width'),
        blank=True,
        null=True,
        measurement=Distance,
    )

    _height = MeasurementField(
        _('Height'),
        blank=True,
        null=True,
        measurement=Distance,
    )

    _depth = MeasurementField(
        _('Depth'),
        blank=True,
        null=True,
        measurement=Distance,
    )

    _weight = MeasurementField(
        _('Weight'),
        blank=True,
        null=True,
        measurement=Mass,
    )

    # Group
    available_attributes = models.ManyToManyField(
        'Attribute',
        blank=True,
        related_name='products_available_attributes',
        verbose_name=_('Attributes'),
        help_text=_(
            'Select attributes that can be used in a Variant for this product.'
        ),
    )

    # Variant
    group = models.ForeignKey(
        'self',
        models.CASCADE,
        blank=True,
        null=True,
        related_name='variants',
        verbose_name=_('Group'),
        help_text=_('Select a Group product for this variation.'),
    )

    attributes = models.ManyToManyField(
        'Attribute',
        through='AttributeValue',
        verbose_name=_('Attributes'),
    )

    published = models.DateTimeField(
        _('Published'),
        default=timezone.now,
    )

    quantity = models.IntegerField(
        _('Quantity'),
        blank=True,
        null=True,
        help_text=_(
            'Number of available products to ship. Leave empty if product is always available, or set to 0 if product '
            'is not available.'),
    )

    order = models.BigIntegerField(
        _('Sort'),
        default=0,
    )

    content = PlaceholderField('shopit_product_content')

    objects = ProductManager()

    lookup_fields = ['code__startswith', 'translations__name__icontains']

    class Meta:
        db_table = 'shopit_products'
        verbose_name = _('Product')
        verbose_name_plural = _('Products')
        ordering = ['-order']

    def __str__(self):
        return self.product_name

    def save(self, *args, **kwargs):
        """
        Clean and clear product.
        Set unique ordering value for product and it's variants based on
        published timestamp. Force Single groups to a Group kind.
        """
        self.clean()
        self.clear()
        if self.is_variant:
            self.group.clear('_variants', '_invalid_variants', '_variations',
                             '_attribute_choices', '_combinations')
            self.order = self.group.order
            if self.group.is_single:
                self.group.kind = Product.GROUP
                self.group.save(update_fields=['kind'])
            super(Product, self).save(*args, **kwargs)
        else:
            # Don't generate timestamp if published hasn't changed.
            if self.pk is not None:
                original = Product.objects.get(
                    pk=self.pk).published.strftime('%s')
                if original == self.published.strftime('%s'):
                    super(Product, self).save(*args, **kwargs)
                    return
            timestamp = int(self.published.strftime('%s'))
            same = Product.objects.filter(order__startswith=timestamp).first()
            timestamp = same.order + 1 if same else timestamp * 1000
            self.order = timestamp
            super(Product, self).save(*args, **kwargs)
            if self.is_group:
                for variant in self.get_variants():
                    variant.clear()
                    variant.order = timestamp
                    variant.save(update_fields=['order'])

    def get_absolute_url(self, language=None):
        if not language:
            language = get_current_language()

        with switch_language(self, language):
            try:
                return reverse('shopit-product-detail',
                               args=[self.safe_translation_getter('slug')])
            except NoReverseMatch:  # pragma: no cover
                pass

    @property
    def product_name(self):
        return self.safe_translation_getter(
            'name', any_language=True) if self.pk else ''

    @property
    def product_code(self):
        return self.code

    @property
    def is_single(self):
        return self.kind == self.SINGLE

    @property
    def is_group(self):
        return self.kind == self.GROUP

    @property
    def is_variant(self):
        return self.kind == self.VARIANT

    @property
    def caption(self):
        return self.get_attr('_caption', '', translated=True)

    @caption.setter
    def caption(self, value):
        self._caption = value

    @property
    def description(self):
        return self.get_attr('_description', '', translated=True)

    @description.setter
    def description(self, value):
        self._description = value

    @property
    def category(self):
        return self.get_attr('_category')

    @category.setter
    def category(self, value):
        self._category = value

    @property
    def brand(self):
        return self.get_attr('_brand')

    @brand.setter
    def brand(self, value):
        self._brand = value

    @property
    def manufacturer(self):
        return self.get_attr('_manufacturer')

    @manufacturer.setter
    def manufacturer(self, value):
        self._manufacturer = value

    @property
    def unit_price(self):
        return self.get_attr('_unit_price', 0)

    @unit_price.setter
    def unit_price(self, value):
        self._unit_price = value

    @property
    def discount(self):
        return self.get_attr('_discount')

    @discount.setter
    def discount(self, value):
        self._discount = value

    @property
    def tax(self):
        return self.get_attr('_tax') or getattr(self.category, 'tax', None)

    @tax.setter
    def tax(self, value):
        self._tax = value

    @property
    def width(self):
        return self.get_attr('_width')

    @width.setter
    def width(self, value):
        self._width = value if isinstance(value, Distance) else Distance(
            m=value)

    @property
    def height(self):
        return self.get_attr('_height')

    @height.setter
    def height(self, value):
        self._height = value if isinstance(value, Distance) else Distance(
            m=value)

    @property
    def depth(self):
        return self.get_attr('_depth')

    @depth.setter
    def depth(self, value):
        self._depth = value if isinstance(value, Distance) else Distance(
            m=value)

    @property
    def weight(self):
        return self.get_attr('_weight')

    @weight.setter
    def weight(self, value):
        self._weight = value if isinstance(value, Mass) else Mass(g=value)

    @property
    def price(self):
        return self.get_price()

    @property
    def is_discounted(self):
        return bool(self.discount_percent)

    @property
    def is_taxed(self):
        return bool(self.tax_percent)

    @property
    def discount_percent(self):
        return self.discount or Decimal('0.00')

    @property
    def tax_percent(self):
        return getattr(self.tax, 'percent', Decimal('0.00'))

    @property
    def discount_amount(self):
        return self.unit_price * self.discount_percent / 100

    @property
    def tax_amount(self):
        return (self.unit_price -
                self.discount_amount) * self.tax_percent / 100

    @property
    def primary_image(self):
        return self.images.first()

    @cached_property
    def images(self):
        images = self.attachments.filter(kind=Attachment.IMAGE)
        return images or (self.group.images if self.is_variant else images)

    @cached_property
    def videos(self):
        videos = self.attachments.filter(kind=Attachment.VIDEO)
        return videos or (self.group.videos if self.is_variant else videos)

    @cached_property
    def files(self):
        files = self.attachments.filter(kind=Attachment.FILE)
        return files or (self.group.files if self.is_variant else files)

    def get_price(self, request=None):
        """
        Returns price with discount and tax calculated.
        """
        return self.unit_price - self.discount_amount + self.tax_amount

    def get_availability(self, request=None):
        """
        Returns product availibility as list of tuples `(quantity, until)`
        Method is not yet implemented in django-shop.
        """
        availability = self.quantity if self.quantity is not None else True
        if self.is_group:
            availability = 0
        elif self.is_variant and self not in self.group.get_variants():
            availability = 0
        return [(availability, datetime.max)]

    def is_available(self, quantity=1, request=None):
        """
        Returns if product is available for the given quantity. If request
        is passed in, count items already in cart.
        """
        if request:
            cart = Cart.objects.get_or_create_from_request(request)
            cart_item = self.is_in_cart(cart)
            quantity += cart_item.quantity if cart_item else 0

        now = timezone.now().replace(tzinfo=None)
        number, until = self.get_availability(request)[0]
        number = 100000 if number is True else number
        available = number >= quantity and now < until
        return available, int(number - quantity)

    def get_modifiers(self, distinct=True):
        """
        Returns all modifiers for this product.
        Collects categorization and group modifiers.
        """
        mods = getattr(self, '_mods', None)
        if mods is None:
            mods = self.modifiers.active()
            if self.is_variant:
                mods = mods | self.group.get_modifiers(distinct=False)
            else:
                if self.category:
                    mods = mods | self.category.get_modifiers(distinct=False)
                if self.brand:
                    mods = mods | self.brand.get_modifiers(distinct=False)
                if self.manufacturer:
                    mods = mods | self.manufacturer.get_modifiers(
                        distinct=False)
            self.cache('_mods', mods)
        return mods.distinct() if distinct else mods

    def get_flags(self, distinct=True):
        """
        Returns all flags for this product.
        Collects categorization and group flags.
        """
        flags = getattr(self, '_flags', None)
        if flags is None:
            flags = self.flags.active()
            if self.is_variant:
                flags = flags | self.group.get_flags(distinct=False)
            if self.category:
                flags = flags | self.category.get_flags(distinct=False)
            if self.brand:
                flags = flags | self.brand.get_flags(distinct=False)
            if self.manufacturer:
                flags = flags | self.manufacturer.get_flags(distinct=False)
            self.cache('_flags', flags)
        return flags.distinct() if distinct else flags

    def get_available_attributes(self):
        """
        Returns list of available attributes for Group and
        Variant products.
        """
        if self.is_group:
            if not hasattr(self, '_available_attributes'):
                self.cache('_available_attributes',
                           self.available_attributes.active())
            return getattr(self, '_available_attributes')

    def get_attributes(self):
        """
        Returns a dictionary containing Variant attributes.
        """
        if self.is_variant:
            attrs = getattr(self, '_attributes', OrderedDict())
            if not attrs:
                for value in self.attribute_values.select_related('attribute'):
                    attrs[value.attribute.key] = value.as_dict
                self.cache('_attributes', attrs)
            return attrs

    def get_variants(self):
        """
        Returns valid variants of a Group product.
        """
        if self.is_group:
            variants = getattr(self, '_variants', None)
            if variants is None:
                variants = self.variants.all()
                invalid = [x.pk for x in self.get_invalid_variants()]
                variants = self.variants.exclude(pk__in=invalid)
                self.cache('_variants', variants)
            return variants

    def get_invalid_variants(self):
        """
        Returns variants that whose attributes don't match available
        attributes and they need to be re-configured or deleted.
        """
        if self.is_group:
            if not hasattr(self, '_invalid_variants'):
                invalid = []
                valid_attrs = [
                ]  # Keep track of valid attrs to check for duplicates.
                codes = sorted(self.get_available_attributes().values_list(
                    'code', flat=True))
                for variant in self.variants.all():
                    attrs = variant.get_attributes()
                    if (attrs in valid_attrs
                            or sorted(x['code']
                                      for x in attrs.values()) != codes
                            or True in [
                                not x['nullable'] and x['value'] == ''
                                for x in attrs.values()
                            ]):
                        invalid.append(variant)
                    else:
                        valid_attrs.append(attrs)
                self.cache('_invalid_variants', invalid)
            return getattr(self, '_invalid_variants')

    def get_variations(self):
        """
        Returns a list of tuples containing a variant id and it's attributes.
        """
        if self.is_group:
            if not hasattr(self, '_variations'):
                variations = [(x.pk, x.get_attributes())
                              for x in self.get_variants()]
                self.cache('_variations', variations)
            return getattr(self, '_variations')

    def get_attribute_choices(self):
        """
        Returns available attribute choices for a group product, filtering
        only the used ones. Used to display dropdown fields on a group
        product to select a variant.
        """
        if self.is_group:
            if not hasattr(self, '_attribute_choices'):
                used = [
                    tuple([y['code'], y['value']])
                    for x in self.get_variations() for y in x[1].values()
                ]
                attrs = OrderedDict()
                for attr in self.get_available_attributes():
                    data = attr.as_dict
                    data['choices'] = [
                        x.as_dict for x in attr.get_choices()
                        if (x.attribute.code, x.value) in used
                    ]
                    if data['choices']:
                        attrs[attr.code] = data
                self.cache('_attribute_choices', attrs)
            return getattr(self, '_attribute_choices')

    def get_combinations(self):
        """
        Returns all available Variant combinations for a Group product
        based on `Available attributes` field, replacing the existant
        variants with actual variant data. Variants with attributes
        missing or not specified in `Available attributes` will not be
        included. This is used to show possible combinations in admin,
        as well as creating them automatically.
        """
        if self.is_group:
            if not hasattr(self, '_combinations'):
                values = []
                for attr in self.get_available_attributes():
                    vals = [
                        AttributeValue(attribute=attr, choice=x)
                        for x in attr.get_choices()
                    ]
                    if vals:
                        values.append(vals)
                combinations = []
                if values:
                    for combo in itertools.product(*values):
                        attrs = OrderedDict([(x.attribute.code, x.value)
                                             for x in combo])
                        name = self.safe_translation_getter('name',
                                                            any_language=True)
                        name = '%s %s' % (name, ' '.join(
                            [x.label for x in combo if x.label != '-']))
                        name = name.rstrip()
                        slug = slugify(name)
                        languages = []
                        variant = self.get_variant(attrs)
                        if variant:
                            name = variant.safe_translation_getter(
                                'name', name)
                            slug = variant.safe_translation_getter(
                                'slug', slug)
                            languages = variant.get_available_languages()
                        combinations.append({
                            'pk':
                            variant.pk if variant else None,
                            'name':
                            name,
                            'slug':
                            slug,
                            'code':
                            variant.code if variant else None,
                            'price':
                            variant.get_price() if variant else None,
                            'quantity':
                            variant.quantity if variant else None,
                            'languages':
                            languages,
                            'attributes':
                            OrderedDict([(x.attribute.code, x.as_dict)
                                         for x in combo])
                        })
                self.cache('_combinations', combinations)
            return getattr(self, '_combinations')

    def get_attachments(self):
        """
        Returns all attachments as a dictionary.
        If Product is a Variant and has not attachments itself,
        group attachemts are inherited.
        """
        attachments = getattr(self, '_attachments', None)
        if attachments is None:
            attachments = {
                'images': [x.as_dict for x in self.images] or None,
                'videos': [x.as_dict for x in self.videos] or None,
                'files': [x.as_dict for x in self.files] or None,
            }
            self.cache('_attachments', attachments)
        return attachments

    def get_relations(self):
        """
        Returns relations for a Single or Group product.
        """
        if not self.is_variant:
            if not hasattr(self, '_relations'):
                self.cache('_relations', self.relations.all())
            return getattr(self, '_relations')

    def get_related_products(self, kind=None):
        """
        Returns related products with the given kind. Variants inherit their
        related products from a Group.
        """
        if self.is_variant:
            return self.group.get_related_products(kind)
        relations = self.get_relations()
        if kind is not None:
            relations = relations.filter(kind=kind)
        return [x.product for x in relations]

    def get_reviews(self, language=None, include_inactive=False):
        """
        Returns reviews for this product, uses the group product for varaints.
        """
        if not self.is_variant:
            reviews = getattr(self, '_reviews', None)
            if include_inactive:
                reviews = self.reviews.all()
            if reviews is None:
                reviews = self.reviews.active()
                self.cache('_reviews', reviews)
            if language is not None:
                return reviews.filter(language=language)
            return reviews

    def get_variant(self, attrs):
        """
        Returns a Variant with the given attribute values for this Group.
        eg. attrs = {'code': 'value', 'code2': 'value2'}
        """
        if self.is_group:
            for variant in self.get_variants():
                current = [(x['code'], x['value'])
                           for x in variant.get_attributes().values()]
                if (sorted(attrs.items()) == sorted(current)):
                    return variant

    def filter_variants(self, attrs):
        """
        Returns a list of Variant products for this Group that contain
        attribute values passed in as `attrs`.

        eg. attrs = {'code': 'value', 'code2': 'value2'}
        """
        if self.is_group:
            variants = []
            for variant in self.get_variants():
                valid = True
                current = [(x['code'], x['value'])
                           for x in variant.get_attributes().values()]
                for attr in attrs.items():
                    if attr not in current:
                        valid = False
                        break
                if valid:
                    variants.append(variant)
            return variants

    def create_variant(self, combo, language=None):
        """
        Create a variant with the given `combo` object from
        the `get_combinations` method.
        """
        if self.is_group:
            if not language:
                language = get_current_language()
            slug = combo['slug']
            code = combo['code'] or Product.objects.latest('pk').pk + 1
            num = 0
            while Product.objects.translated(slug=slug).exists():
                num = num + 1
                slug = '%s-%d' % (combo['slug'], num)
            while Product.objects.filter(
                    code=code, translations__language_code=language).exists():
                code = int(code) + 1
            variant, created = Product.objects.get_or_create(
                code=code, kind=Product.VARIANT, group=self)
            variant.set_current_language(language)
            variant.name = combo['name']
            variant.slug = slug
            variant.save()
            if created:
                for attr_value in combo['attributes'].values():
                    attr = Attribute.objects.get(code=attr_value['code'])
                    if attr_value['value'] == '' and attr.nullable:
                        choice = None
                    else:
                        choice = attr.choices.get(pk=attr_value['choice'])
                    AttributeValue.objects.create(attribute=attr,
                                                  product=variant,
                                                  choice=choice)
            return variant

    @method_decorator(transaction.atomic)
    def create_all_variants(self, language=None):
        """
        Creates all missing variants for the group.
        """
        if self.is_group:
            variants = []
            if not language:
                language = get_current_language()
            for combo in self.get_combinations():
                if not combo['pk'] or language not in combo['languages']:
                    variants.append(
                        self.create_variant(combo, language=language))
            return variants

    def get_attr(self, name, case=None, translated=False):
示例#36
0
 def test_format(self):
     f = MoneyDbField(max_digits=5, decimal_places=3)
     self.assertEqual(f._format(f.to_python(2)), '2.000')
     self.assertEqual(f._format(f.to_python('2.34567')), '2.346')
     self.assertEqual(f._format(None), None)
示例#37
0
 def test_format(self):
     f = MoneyDbField(max_digits=5, decimal_places=3)
     self.assertEqual(f._format(f.to_python(2)), '2.000')
     self.assertEqual(f._format(f.to_python('2.34567')), '2.346')
     self.assertEqual(f._format(None), None)
def test_get_prep_value():
    f = MoneyField(currency='EUR', null=True)
    assert f.get_prep_value(EUR('3')) == Decimal('3')