class ProductType(ModelWithMetadata): name = models.CharField(max_length=250) slug = models.SlugField(max_length=255, unique=True, allow_unicode=True) has_variants = models.BooleanField(default=True) is_shipping_required = models.BooleanField(default=True) is_digital = models.BooleanField(default=False) weight = MeasurementField( measurement=Weight, unit_choices=WeightUnits.CHOICES, default=zero_weight ) class Meta(ModelWithMetadata.Meta): ordering = ("slug",) app_label = "product" permissions = ( ( ProductTypePermissions.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES.codename, "Manage product types and attributes.", ), ) def __str__(self) -> str: return self.name def __repr__(self) -> str: class_ = type(self) return "<%s.%s(pk=%r, name=%r)>" % ( class_.__module__, class_.__name__, self.pk, self.name, )
class ProductType(models.Model): name = models.CharField(max_length=128) has_variants = models.BooleanField(default=True) is_shipping_required = models.BooleanField(default=True) tax_rate = models.CharField(max_length=128, default=DEFAULT_TAX_RATE_NAME, choices=TaxRateType.CHOICES) store = models.ForeignKey(Store, null=True, blank=True, related_name='product_types', on_delete=models.CASCADE) weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, default=zero_weight) class Meta: app_label = 'product' def __str__(self): return self.name def __repr__(self): class_ = type(self) return '<%s.%s(pk=%r, name=%r)>' % (class_.__module__, class_.__name__, self.pk, self.name)
class ProductType(ModelWithMetadata): name = models.CharField(max_length=250) slug = models.SlugField(max_length=255, unique=True, allow_unicode=True) has_variants = models.BooleanField(default=True) is_shipping_required = models.BooleanField(default=True) is_digital = models.BooleanField(default=False) weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, default=zero_weight) class Meta: ordering = ("slug", ) app_label = "product" def __str__(self) -> str: return self.name def __repr__(self) -> str: class_ = type(self) return "<%s.%s(pk=%r, name=%r)>" % ( class_.__module__, class_.__name__, self.pk, self.name, )
class ShippingMethod(ModelWithMetadata): store = models.ForeignKey( Store, related_name="shipping_methods", on_delete=models.SET_NULL, null=True, blank=True, ) tenant_id = 'store_id' name = models.CharField(max_length=100) type = models.CharField(max_length=30, choices=ShippingMethodType.CHOICES) shipping_zone = models.ForeignKey(ShippingZone, related_name="shipping_methods", on_delete=models.CASCADE) minimum_order_weight = MeasurementField( measurement=Weight, unit_choices=WeightUnits.CHOICES, default=zero_weight, blank=True, null=True, ) maximum_order_weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, blank=True, null=True) excluded_products = models.ManyToManyField("product.Product", blank=True) # type: ignore maximum_delivery_days = models.PositiveIntegerField(null=True, blank=True) minimum_delivery_days = models.PositiveIntegerField(null=True, blank=True) objects = ShippingMethodQueryset.as_manager() translated = TranslationProxy() class Meta(ModelWithMetadata.Meta): ordering = ("pk", ) def __str__(self): return self.name def __repr__(self): if self.type == ShippingMethodType.PRICE_BASED: return "ShippingMethod(type=%s)" % (self.type, ) return "ShippingMethod(type=%s weight_range=(%s)" % ( self.type, _get_weight_type_display(self.minimum_order_weight, self.maximum_order_weight), )
class ShippingMethod(BaseModel): class ShippingMethodType(models.TextChoices): PRICE_BASED = "price", _("Price") WEIGHT_BASED = "weight", _("Weight") name = models.CharField(max_length=100) type = models.CharField(max_length=30, choices=ShippingMethodType.choices) price_amount = models.DecimalField( max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, default=0, ) shipping_zone = models.ForeignKey(ShippingZone, related_name="shipping_methods", on_delete=models.CASCADE) minimum_order_price = MoneyField( max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, default_currency=settings.DEFAULT_CURRENCY, ) maximum_order_price = MoneyField( max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, default_currency=settings.DEFAULT_CURRENCY, ) minimum_order_weight = MeasurementField( measurement=Weight, unit_choices=WeightUnits.CHOICES, default=0, ) maximum_order_weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, blank=True, null=True) meta = models.JSONField(blank=True, default=dict) class Meta: verbose_name = _("Shipping Method") verbose_name_plural = _("Shipping Methods") def __str__(self): return self.name def get_total(self): return self.price_amount
class delivery_product(models.Model): to_location = models.CharField(max_length=100) from_location = models.CharField(max_length=100) product_type = models.ForeignKey(types_of_product, on_delete=models.CASCADE) weight = MeasurementField(measurement=Weight) Date = models.DateTimeField(default=timezone.now) image = models.ImageField(upload_to='images/')
class ParentsHeight(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) fathers_height = MeasurementField(measurement=Distance, unit_choices=[("cm", "cm")]) mothers_height = MeasurementField(measurement=Distance, unit_choices=[("cm", "cm")]) def __str__(self): return self.player.user.username def value_club_unit(self): measurement_system = self.player.club.measurement_system if measurement_system == 'SI': return round(self.fathers_height.cm, 1), round(self.mothers_height.cm, 0), 'cm' elif measurement_system == 'Imp': return round(self.fathers_height.inch, 1), round(self.mothers_height.inch, 0), 'inch'
class ProductMeasurements(models.Model): product_measurements = models.ForeignKey( Product, related_name="product_measurements_id", on_delete=models.CASCADE) product_volume = MeasurementField(measurement=Volume, null=True, blank=True) product_area = MeasurementField(measurement=Area, null=True, blank=True) product_mass = MeasurementField(measurement=Mass, null=True, blank=True) product_weight = MeasurementField(measurement=Weight, null=True, blank=True) product_time = MeasurementField(measurement=Time, null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "product_measurements"
class DnaHeight(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) date = models.DateTimeField() # Date in DNA file predicted_height = MeasurementField(measurement=Distance, unit_choices=[("cm", "cm")]) meta = JSONField(default=dict()) original_filename = models.CharField(max_length=2000) def __str__(self): return self.player.user.username
class Log(DateTimeFields, SafeDeleteModel): """ - Date - Weight - Calories In - Calories Out - Activity LVL - progress pic """ class Meta: unique_together = ("user", "date") user = models.ForeignKey( get_user_model(), blank=False, null=False, on_delete=models.CASCADE, ) date = models.DateField(blank=False) # Log the date weight = MeasurementField(measurement=Weight, null=True, blank=False) calories_in = models.IntegerField( blank=False, help_text="Total calories consumed", ) # Calories consumed in kcal calories_out = models.IntegerField( blank=True, null=True, help_text="If you have a fitness tracker, total calories burned", ) # From fitness tracker e.g. apple watch, fitbit etc. choices = [ ("L", "Low"), ("M", "Moderate"), ("H", "High"), ] activity_lvl = models.CharField( max_length=1, choices=choices, blank=True, null=True, help_text="Estimate your relative activity level", ) front_progress_pic = CloudinaryField("image", null=True, blank=True) side_progress_pic = CloudinaryField("image", null=True, blank=True) back_progress_pic = CloudinaryField("image", null=True, blank=True) def __str__(self): return f"Log from {self.user}"
class delivery_product(models.Model): from_location = models.CharField(max_length=100) to_location = models.CharField(max_length=100) product_type = models.ForeignKey(types_of_product, on_delete=models.CASCADE) weight = MeasurementField(measurement=Weight, null=True, blank=True) Date = models.DateTimeField(default=timezone.now) image = models.ImageField(upload_to='images/', null=True, blank=True) def __str__(self): return self.from_location
class ProductMeasurements(models.Model): product_measurements = models.ForeignKey(Product, on_delete=models.CASCADE) selection_details = MultiSelectField(choices=PRODUCT_MEASUREMENTS_CHOICES, max_choices=4, max_length=255, null=True, blank=True) product_volume = MeasurementField(measurement=Volume, null=True, blank=True) product_area = MeasurementField(measurement=Area, null=True, blank=True) product_mass = MeasurementField(measurement=Mass, null=True, blank=True) product_weight = MeasurementField(measurement=Weight, null=True, blank=True) product_time = MeasurementField(measurement=Time, null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "db_measurements"
class Stock(models.Model): ingredient = models.ForeignKey('Ingredient', on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('Название ингредиента')) weight = MeasurementField(blank=True, null=True, measurement=Weight, unit_choices=WeightUnits.CHOICES, verbose_name=_('Вес')) volume = MeasurementField(blank=True, null=True, measurement=Volume, unit_choices=VolumeUnits.CHOICES, verbose_name=_('Обьем')) quantity = SmallIntegerField(blank=True, null=True, verbose_name=_('Количество')) class Meta: verbose_name_plural = _('Склад') verbose_name = _('Ингредиент') def __str__(self): value = 0 unit = '' if self.volume and self.volume.value: value, unit = self.volume.value, self.volume.unit if self.weight and self.weight.value: value, unit = self.weight.value, self.weight.unit if self.quantity: value, unit = self.quantity, 'шт' return f'{self.ingredient.name} - {value} {unit}' def save(self, *args, **kwargs): qs = Stock.objects.filter(ingredient=self.ingredient) if not qs: super().save(*args, **kwargs)
class KhamisRoche(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) date = models.DateField() # Median Date between Height and Weight record predicted_height = MeasurementField(measurement=Distance, unit_choices=[("cm", "cm")]) current_height = models.ForeignKey('profile.Height', on_delete=models.CASCADE) current_weight = models.ForeignKey('profile.Weight', on_delete=models.CASCADE) parents_height = models.ForeignKey('profile.ParentsHeight', on_delete=models.CASCADE) meta = JSONField(default=dict())
class Product(SeoModel, ModelWithMetadata, PublishableModel): product_type = models.ForeignKey(ProductType, related_name="products", on_delete=models.CASCADE) name = models.CharField(max_length=250) slug = models.SlugField(max_length=255, unique=True, allow_unicode=True) description = models.TextField(blank=True) description_json = SanitizedJSONField(blank=True, default=dict, sanitizer=clean_draft_js) category = models.ForeignKey( Category, related_name="products", on_delete=models.SET_NULL, null=True, blank=True, ) currency = models.CharField( max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH, default=settings.DEFAULT_CURRENCY, ) minimal_variant_price_amount = models.DecimalField( max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, blank=True, null=True, ) minimal_variant_price = MoneyField( amount_field="minimal_variant_price_amount", currency_field="currency") updated_at = models.DateTimeField(auto_now=True, null=True) weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, blank=True, null=True) objects = ProductsQueryset.as_manager() translated = TranslationProxy() class Meta: app_label = "product" ordering = ("slug", ) permissions = ((ProductPermissions.MANAGE_PRODUCTS.codename, "Manage products."), ) def __str__(self) -> str: return self.name @staticmethod def sort_by_attribute_fields() -> list: return ["concatenated_values_order", "concatenated_values", "name"]
class MeasurementTestModel(models.Model): measurement_distance = MeasurementField( measurement=measures.Distance, validators=[ MinValueValidator(measures.Distance(mi=1.0)), MaxValueValidator(measures.Distance(mi=3.0)) ], blank=True, null=True, ) measurement_distance_km = MeasurementField( measurement=measures.Distance, unit_choices=(('km', 'km'),), validators=[ MinValueValidator(measures.Distance(km=1.0)), MaxValueValidator(measures.Distance(km=3.0)) ], blank=True, null=True, ) measurement_weight = MeasurementField( measurement=measures.Weight, validators=[ MinValueValidator(measures.Weight(kg=1.0)), MaxValueValidator(measures.Weight(kg=3.0)) ], blank=True, null=True, ) measurement_speed = MeasurementField( measurement=measures.Speed, validators=[ MinValueValidator(measures.Speed(mph=1.0)), MaxValueValidator(measures.Speed(mph=3.0)) ], blank=True, null=True, ) measurement_temperature = MeasurementField( measurement=measures.Temperature, validators=[ MinValueValidator(measures.Temperature(1.0)), MaxValueValidator(measures.Temperature(3.0)) ], blank=True, null=True, ) measurement_speed_mph = MeasurementField( measurement=measures.Speed, unit_choices=(('mi__hr', 'mph'),), validators=[ MinValueValidator(measures.Speed(mph=1.0)), MaxValueValidator(measures.Speed(mph=3.0)) ], blank=True, null=True, ) def __unicode__(self): return self.measurement
class Weight(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) date = models.DateField() weight = MeasurementField(measurement=WeightMeasurement, unit_choices=[("kg", "kg")]) def __str__(self): return self.player.user.username def value_club_unit(self): measurement_system = self.player.club.measurement_system if measurement_system == 'SI': return round(self.weight.kg, 1), 'kg' elif measurement_system == 'Imp': return round(self.weight.lb, 1), 'lb'
class ProductType(models.Model): name = models.CharField(max_length=128) has_variants = models.BooleanField(default=True) is_shipping_required = models.BooleanField(default=True) tax_rate = models.CharField(max_length=128, choices=TaxRateType.CHOICES) weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, default=zero_weight) class Meta: app_label = 'product' def __str__(self): return self.name def __repr__(self): class_ = type(self) return '<%s.%s(pk=%r, name=%r)>' % (class_.__module__, class_.__name__, self.pk, self.name)
class ProductType(ModelWithMetadata): name = models.CharField(max_length=250) slug = models.SlugField(max_length=255, unique=True, allow_unicode=True) kind = models.CharField(max_length=32, choices=ProductTypeKind.CHOICES) has_variants = models.BooleanField(default=True) is_shipping_required = models.BooleanField(default=True) is_digital = models.BooleanField(default=False) weight = MeasurementField( measurement=Weight, unit_choices=WeightUnits.CHOICES, # type: ignore default=zero_weight, ) class Meta(ModelWithMetadata.Meta): ordering = ("slug",) app_label = "product" permissions = ( ( ProductTypePermissions.MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES.codename, "Manage product types and attributes.", ), ) indexes = [ *ModelWithMetadata.Meta.indexes, GinIndex( name="product_type_search_gin", # `opclasses` and `fields` should be the same length fields=["name", "slug"], opclasses=["gin_trgm_ops"] * 2, ), ] def __str__(self) -> str: return self.name def __repr__(self) -> str: class_ = type(self) return "<%s.%s(pk=%r, name=%r)>" % ( class_.__module__, class_.__name__, self.pk, self.name, )
class DeliveryProduct(models.Model): # order_id = models.AutoField(primary_key=True) from_location = models.ForeignKey(Industry, on_delete=models.CASCADE, related_name='from_location') to_location = models.ForeignKey(Industry, on_delete=models.CASCADE, related_name='to_location') PresentAddress = models.CharField(max_length=255, choices=OPTIONS3) product_type = models.ForeignKey(TypesOfProduct, on_delete=models.CASCADE) weight = MeasurementField(measurement=Weight, null=True, blank=True) Date = models.DateTimeField(default=timezone.now) image = models.ImageField(upload_to='images/', null=True, blank=True) phone = models.CharField(max_length=20) Bill = models.IntegerField(null=True, blank=True) order_status = models.CharField(max_length=100, choices=OPTIONS2, default="0") def __str__(self): return f"From Location :{self.from_location} and To location : {self.to_location} and Phone Number :{self.phone} and Product Type :{self.product_type}"
class ProductType(index.Indexed, BaseModel): """Think about product types as templates for your products. Multiple products can use the same product type.""" name = models.CharField( verbose_name=_("Name"), max_length=255, help_text=_("Required, Maximum of 255 characters."), ) slug = models.SlugField(max_length=255, unique=True, allow_unicode=True) weight = MeasurementField( measurement=Weight, unit_choices=WeightUnits.CHOICES, blank=True, null=True, ) class Meta: verbose_name = _("Product Type") verbose_name_plural = _("Products Types") def __str__(self) -> str: return f"{self.name}"
class ShipmentPiece(BaseModel): """Represents the physical shipped item as box or envelope etc...""" shipment = ParentalKey(Shipment, related_name="pieces", on_delete=models.CASCADE) weight = MeasurementField( measurement=Weight, unit_choices=WeightUnits.CHOICES, default=0, ) height = models.DecimalField(verbose_name=_("Height"), max_digits=20, decimal_places=4, null=True, blank=True) depth = models.DecimalField(verbose_name=_("Depth"), max_digits=20, decimal_places=4, null=True, blank=True) width = models.DecimalField(verbose_name=_("Width"), max_digits=20, decimal_places=4, null=True, blank=True) declared_value = models.DecimalField(verbose_name=_("Declared Value"), max_digits=20, decimal_places=4, null=True, blank=True) class Meta: verbose_name = _("Shipment Piece") verbose_name_plural = _("Shipments Pieces") def __str__(self): return f"{self.order.number}"
class PredictedHeight(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) date = models.DateField() predicted_height = MeasurementField(measurement=Distance, unit_choices=[("cm", "cm")]) METHOD_CHOICES = ( ('dna', 'DNA Test'), # DNA Test has always priority ('khr', 'Khamis Roche'), ) method = models.CharField(max_length=10, choices=METHOD_CHOICES) khamis_roche = models.ForeignKey(KhamisRoche, blank=True, null=True) dna_height = models.ForeignKey(DnaHeight, blank=True, null=True) def __str__(self): return self.player.user.username def value_club_unit(self): measurement_system = self.player.club.measurement_system if measurement_system == 'SI': return round(self.predicted_height.cm, 1), 'cm' elif measurement_system == 'Imp': return round(self.predicted_height.inch, 1), 'inch'
class Profile(models.Model): """Profile model. Proxy model that extends the base data with other information. """ class UnitMeasurement(models.TextChoices): """ Class to deal with enum unit measures Two posibles values: 0: METRIC, 1:IMPERIAL """ METRIC = "METRIC" IMPERIAL = "IMPERIAL" user = models.OneToOneField(User, on_delete=models.CASCADE) picture = models.ImageField(upload_to='users/pictures', blank=True, null=True) height = MeasurementField(measurement=Distance, unit_choices=(('ft', 'ft'), ('m', 'm'))) measurement_system = models.CharField(blank=True, choices=[(tag, tag.value) for tag in UnitMeasurement], default=UnitMeasurement.METRIC, max_length=8) country_code = models.CharField(blank=True, max_length=2) active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): """Return username.""" return self.user.username
class ProductType(models.Model): name = models.CharField(max_length=128) has_variants = models.BooleanField(default=True) product_attributes = models.ManyToManyField( 'ProductAttribute', related_name='product_types', blank=True) variant_attributes = models.ManyToManyField( 'ProductAttribute', related_name='product_variant_types', blank=True) is_shipping_required = models.BooleanField(default=False) tax_rate = models.CharField( max_length=128, default=DEFAULT_TAX_RATE_NAME, blank=True) weight = MeasurementField( measurement=Weight, unit_choices=WeightUnits.CHOICES, default=zero_weight) class Meta: app_label = 'product' def __str__(self): return self.name def __repr__(self): class_ = type(self) return '<%s.%s(pk=%r, name=%r)>' % ( class_.__module__, class_.__name__, self.pk, self.name)
class ProductType(models.Model): name = models.CharField(max_length=128) has_variants = models.BooleanField(default=True) is_shipping_required = models.BooleanField(default=True) is_digital = models.BooleanField(default=False) weight = MeasurementField( measurement=Weight, unit_choices=WeightUnits.CHOICES, default=zero_weight ) meta = JSONField(blank=True, null=True, default=dict, encoder=CustomJsonEncoder) class Meta: app_label = "product" def __str__(self): return self.name def __repr__(self): class_ = type(self) return "<%s.%s(pk=%r, name=%r)>" % ( class_.__module__, class_.__name__, self.pk, self.name, )
class ProductType(ModelWithMetadata): name = models.CharField(max_length=128) has_variants = models.BooleanField(default=True) is_shipping_required = models.BooleanField(default=True) is_digital = models.BooleanField(default=False) weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, default=zero_weight) objects = models.Manager() class Meta: app_label = "product" def __str__(self): return self.name def __repr__(self): class_ = type(self) return "<%s.%s(pk=%r, name=%r)>" % ( class_.__module__, class_.__name__, self.pk, self.name, )
class ProductVariant(ModelWithMetadata): sku = models.CharField(max_length=32, unique=True) name = models.CharField(max_length=255, blank=True) price_override = MoneyField( currency=settings.DEFAULT_CURRENCY, max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, blank=True, null=True, ) product = models.ForeignKey(Product, related_name="variants", on_delete=models.CASCADE) attributes = FilterableJSONBField(default=dict, blank=True, validators=[validate_attribute_json]) images = models.ManyToManyField("ProductImage", through="VariantImage") track_inventory = models.BooleanField(default=True) quantity = models.IntegerField(validators=[MinValueValidator(0)], default=Decimal(1)) quantity_allocated = models.IntegerField(validators=[MinValueValidator(0)], default=Decimal(0)) cost_price = MoneyField( currency=settings.DEFAULT_CURRENCY, max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, blank=True, null=True, ) weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, blank=True, null=True) objects = ProductVariantQueryset.as_manager() translated = TranslationProxy() class Meta: app_label = "product" def __str__(self): return self.name or self.sku @property def quantity_available(self): return max(self.quantity - self.quantity_allocated, 0) @property def is_visible(self): return self.product.is_visible @property def is_available(self): return self.product.is_available def check_quantity(self, quantity): """Check if there is at least the given quantity in stock. If stock handling is disabled, it simply run no check. """ if self.track_inventory and quantity > self.quantity_available: raise InsufficientStock(self) @property def base_price(self): return (self.price_override if self.price_override is not None else self.product.price) def get_price(self, discounts: Iterable[DiscountInfo] = None): return calculate_discounted_price(self.product, self.base_price, discounts) def get_weight(self): return self.weight or self.product.weight or self.product.product_type.weight def get_absolute_url(self): slug = self.product.get_slug() product_id = self.product.id return reverse("product:details", kwargs={ "slug": slug, "product_id": product_id }) def is_shipping_required(self): return self.product.product_type.is_shipping_required def is_digital(self): is_digital = self.product.product_type.is_digital return not self.is_shipping_required() and is_digital def is_in_stock(self): return self.quantity_available > 0 def display_product(self, translated=False): if translated: product = self.product.translated variant_display = str(self.translated) else: variant_display = str(self) product = self.product product_display = ("%s (%s)" % (product, variant_display) if variant_display else str(product)) return smart_text(product_display) def get_first_image(self): images = list(self.images.all()) return images[0] if images else self.product.get_first_image() def get_ajax_label(self, discounts=None): price = self.get_price(discounts) return "%s, %s, %s" % ( self.sku, self.display_product(), prices_i18n.amount(price), )
class Product(SeoModel, ModelWithMetadata, PublishableModel): product_type = models.ForeignKey(ProductType, related_name="products", on_delete=models.CASCADE) name = models.CharField(max_length=128) description = models.TextField(blank=True) description_json = SanitizedJSONField(blank=True, default=dict, sanitizer=clean_draft_js) category = models.ForeignKey(Category, related_name="products", on_delete=models.CASCADE) price = MoneyField( currency=settings.DEFAULT_CURRENCY, max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, ) minimal_variant_price = MoneyField( currency=settings.DEFAULT_CURRENCY, max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, ) attributes = FilterableJSONBField(default=dict, blank=True, validators=[validate_attribute_json]) updated_at = models.DateTimeField(auto_now=True, null=True) charge_taxes = models.BooleanField(default=True) weight = MeasurementField(measurement=Weight, unit_choices=WeightUnits.CHOICES, blank=True, null=True) objects = ProductsQueryset.as_manager() translated = TranslationProxy() class Meta: app_label = "product" ordering = ("name", ) permissions = (( "manage_products", pgettext_lazy("Permission description", "Manage products."), ), ) def __iter__(self): if not hasattr(self, "__variants"): setattr(self, "__variants", self.variants.all()) return iter(getattr(self, "__variants")) def __repr__(self): class_ = type(self) return "<%s.%s(pk=%r, name=%r)>" % ( class_.__module__, class_.__name__, self.pk, self.name, ) def __str__(self): return self.name def save(self, force_insert=False, force_update=False, using=None, update_fields=None): # Make sure the "minimal_variant_price" is set if self.minimal_variant_price is None: self.minimal_variant_price = self.price return super().save(force_insert, force_update, using, update_fields) @property def plain_text_description(self): if settings.USE_JSON_CONTENT: return json_content_to_raw_text(self.description_json) return strip_tags(self.description) @property def is_available(self): return self.is_visible and self.is_in_stock() def get_absolute_url(self): return reverse("product:details", kwargs={ "slug": self.get_slug(), "product_id": self.id }) def get_slug(self): return slugify(smart_text(unidecode(self.name))) def is_in_stock(self): return any(variant.is_in_stock() for variant in self) def get_first_image(self): images = list(self.images.all()) return images[0] if images else None def get_price_range(self, discounts: Iterable[DiscountInfo] = None): if self.variants.all(): prices = [variant.get_price(discounts) for variant in self] return MoneyRange(min(prices), max(prices)) price = calculate_discounted_price(self, self.price, discounts) return MoneyRange(start=price, stop=price)
class ProductVariant(SortableModel, ModelWithMetadata): sku = models.CharField(max_length=255, unique=True) name = models.CharField(max_length=255, blank=True) product = models.ForeignKey( Product, related_name="variants", on_delete=models.CASCADE ) media = models.ManyToManyField("ProductMedia", through="VariantMedia") track_inventory = models.BooleanField(default=True) weight = MeasurementField( measurement=Weight, unit_choices=WeightUnits.CHOICES, # type: ignore blank=True, null=True, ) objects = models.Manager.from_queryset(ProductVariantQueryset)() translated = TranslationProxy() class Meta(ModelWithMetadata.Meta): ordering = ("sort_order", "sku") app_label = "product" def __str__(self) -> str: return self.name or self.sku def get_price( self, product: Product, collections: Iterable["Collection"], channel: Channel, channel_listing: "ProductVariantChannelListing", discounts: Optional[Iterable[DiscountInfo]] = None, ) -> "Money": return calculate_discounted_price( product=product, price=channel_listing.price, discounts=discounts, collections=collections, channel=channel, ) def get_weight(self): return self.weight or self.product.weight or self.product.product_type.weight def is_shipping_required(self) -> bool: return self.product.product_type.is_shipping_required def is_digital(self) -> bool: is_digital = self.product.product_type.is_digital return not self.is_shipping_required() and is_digital def display_product(self, translated: bool = False) -> str: if translated: product = self.product.translated variant_display = str(self.translated) else: variant_display = str(self) product = self.product product_display = ( f"{product} ({variant_display})" if variant_display else str(product) ) return smart_text(product_display) def get_ordering_queryset(self): return self.product.variants.all()