ob-dj-store 0.0.11.3__py3-none-any.whl → 0.0.11.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. ob_dj_store/apis/stores/filters.py +33 -18
  2. ob_dj_store/apis/stores/rest/serializers/serializers.py +129 -53
  3. ob_dj_store/apis/stores/urls.py +3 -3
  4. ob_dj_store/apis/stores/views.py +182 -42
  5. ob_dj_store/core/stores/admin.py +8 -3
  6. ob_dj_store/core/stores/admin_inlines.py +4 -4
  7. ob_dj_store/core/stores/gateway/tap/models.py +1 -1
  8. ob_dj_store/core/stores/gateway/tap/utils.py +4 -4
  9. ob_dj_store/core/stores/managers.py +8 -6
  10. ob_dj_store/core/stores/migrations/0056_auto_20230213_2224.py +33 -0
  11. ob_dj_store/core/stores/migrations/0057_auto_20230214_1724.py +37 -0
  12. ob_dj_store/core/stores/migrations/0058_attributechoice_is_default.py +18 -0
  13. ob_dj_store/core/stores/migrations/0059_auto_20230217_2006.py +41 -0
  14. ob_dj_store/core/stores/migrations/0060_alter_orderitem_product_variant.py +24 -0
  15. ob_dj_store/core/stores/migrations/0061_auto_20230223_1435.py +28 -0
  16. ob_dj_store/core/stores/migrations/0062_auto_20230226_2005.py +43 -0
  17. ob_dj_store/core/stores/migrations/0063_alter_store_payment_methods.py +23 -0
  18. ob_dj_store/core/stores/migrations/0064_auto_20230228_1814.py +24 -0
  19. ob_dj_store/core/stores/migrations/0065_auto_20230228_1932.py +34 -0
  20. ob_dj_store/core/stores/models/_favorite.py +4 -1
  21. ob_dj_store/core/stores/models/_order.py +4 -2
  22. ob_dj_store/core/stores/models/_payment.py +31 -11
  23. ob_dj_store/core/stores/models/_product.py +22 -13
  24. ob_dj_store/core/stores/models/_store.py +15 -6
  25. ob_dj_store/core/stores/models/_wallet.py +41 -8
  26. ob_dj_store/core/stores/receivers.py +79 -4
  27. ob_dj_store/core/stores/utils.py +12 -6
  28. ob_dj_store/utils/helpers.py +8 -2
  29. ob_dj_store/utils/utils.py +43 -3
  30. {ob_dj_store-0.0.11.3.dist-info → ob_dj_store-0.0.11.10.dist-info}/METADATA +1 -1
  31. {ob_dj_store-0.0.11.3.dist-info → ob_dj_store-0.0.11.10.dist-info}/RECORD +33 -23
  32. {ob_dj_store-0.0.11.3.dist-info → ob_dj_store-0.0.11.10.dist-info}/WHEEL +0 -0
  33. {ob_dj_store-0.0.11.3.dist-info → ob_dj_store-0.0.11.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,43 @@
1
+ # Generated by Django 3.2.8 on 2023-02-26 17:05
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations, models
5
+
6
+ import ob_dj_store.core.stores.utils
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+ ("stores", "0061_auto_20230223_1435"),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.AddField(
18
+ model_name="store",
19
+ name="currency",
20
+ field=models.CharField(
21
+ default="KWD",
22
+ max_length=3,
23
+ validators=[ob_dj_store.core.stores.utils.validate_currency],
24
+ ),
25
+ ),
26
+ migrations.AddField(
27
+ model_name="wallet",
28
+ name="currency",
29
+ field=models.CharField(
30
+ default="KWD",
31
+ max_length=3,
32
+ validators=[ob_dj_store.core.stores.utils.validate_currency],
33
+ ),
34
+ ),
35
+ migrations.AlterUniqueTogether(
36
+ name="wallet",
37
+ unique_together={("user", "currency")},
38
+ ),
39
+ migrations.RemoveField(
40
+ model_name="wallet",
41
+ name="country",
42
+ ),
43
+ ]
@@ -0,0 +1,23 @@
1
+ # Generated by Django 3.2.8 on 2023-02-27 11:15
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("stores", "0062_auto_20230226_2005"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="store",
15
+ name="payment_methods",
16
+ field=models.ManyToManyField(
17
+ blank=True,
18
+ help_text="Payment methods within the store",
19
+ related_name="stores",
20
+ to="stores.PaymentMethod",
21
+ ),
22
+ ),
23
+ ]
@@ -0,0 +1,24 @@
1
+ # Generated by Django 3.2.8 on 2023-02-28 15:14
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
11
+ ("stores", "0063_alter_store_payment_methods"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="favorite",
17
+ name="name",
18
+ field=models.CharField(blank=True, max_length=200, null=True),
19
+ ),
20
+ migrations.AlterUniqueTogether(
21
+ name="favorite",
22
+ unique_together={("name", "user")},
23
+ ),
24
+ ]
@@ -0,0 +1,34 @@
1
+ # Generated by Django 3.2.8 on 2023-02-28 16:32
2
+
3
+ from django.db import migrations, models
4
+
5
+ import ob_dj_store.utils.helpers
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ("stores", "0064_auto_20230228_1814"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="wallet",
17
+ name="image",
18
+ field=models.ImageField(
19
+ blank=True,
20
+ null=True,
21
+ upload_to=ob_dj_store.utils.helpers.wallet_media_upload_to,
22
+ ),
23
+ ),
24
+ migrations.AddField(
25
+ model_name="wallet",
26
+ name="image_thumbnail_medium",
27
+ field=models.ImageField(blank=True, null=True, upload_to="wallets/"),
28
+ ),
29
+ migrations.AddField(
30
+ model_name="wallet",
31
+ name="name",
32
+ field=models.CharField(blank=True, max_length=200, null=True),
33
+ ),
34
+ ]
@@ -16,6 +16,7 @@ class Favorite(DjangoModelCleanMixin, models.Model):
16
16
  user = models.ForeignKey(
17
17
  get_user_model(), related_name="favorites", on_delete=models.CASCADE
18
18
  )
19
+ name = models.CharField(max_length=200, null=True, blank=True)
19
20
  content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
20
21
  content_object = GenericForeignKey("content_type", "object_id")
21
22
  object_id = models.PositiveIntegerField()
@@ -26,18 +27,20 @@ class Favorite(DjangoModelCleanMixin, models.Model):
26
27
  class Meta:
27
28
  verbose_name = _("favorite")
28
29
  verbose_name_plural = _("favorites")
30
+ unique_together = (("name", "user"),)
29
31
 
30
32
  def __str__(self):
31
33
  return f"{self.user} favorites {self.content_object}"
32
34
 
33
35
  @classmethod
34
- def add_favorite(cls, content_object, user, extras=[]):
36
+ def add_favorite(cls, content_object, user, name, extras=[]):
35
37
  content_type = ContentType.objects.get_for_model(type(content_object))
36
38
  favorite = Favorite(
37
39
  user=user,
38
40
  content_type=content_type,
39
41
  object_id=content_object.pk,
40
42
  content_object=content_object,
43
+ name=name,
41
44
  )
42
45
  favorite.save()
43
46
  for extra in extras:
@@ -119,6 +119,8 @@ class Order(DjangoModelCleanMixin, models.Model):
119
119
 
120
120
  @property
121
121
  def total_amount(self):
122
+ if self.type_of_order == Order.OrderType.WALLET.value:
123
+ return Decimal(self.extra_infos["amount"])
122
124
  amount = Decimal(
123
125
  sum(map(lambda item: Decimal(item.total_amount) or 0, self.items.all()))
124
126
  )
@@ -157,7 +159,7 @@ class OrderItem(DjangoModelCleanMixin, models.Model):
157
159
  product_variant = models.ForeignKey(
158
160
  "stores.ProductVariant",
159
161
  null=True,
160
- on_delete=models.SET_NULL,
162
+ on_delete=models.PROTECT,
161
163
  related_name="order_items",
162
164
  )
163
165
  # notes for special instructions, can be empty
@@ -181,7 +183,7 @@ class OrderItem(DjangoModelCleanMixin, models.Model):
181
183
  extra_infos = models.JSONField(null=True, blank=True)
182
184
 
183
185
  def __str__(self):
184
- return f"OrderItem - {self.quantity} {self.product_variant.name}"
186
+ return f"OrderItem - {self.quantity} {self.product_variant.product.name}{self.product_variant.name}"
185
187
 
186
188
  class Meta:
187
189
  verbose_name = _("Order Item")
@@ -110,17 +110,36 @@ class Payment(models.Model):
110
110
  return f"Payment (PK={self.pk})"
111
111
 
112
112
  def mark_paid(self):
113
+ """
114
+ mark payment and orders as paid then perform post payment actions
115
+ if wallet fill up we add wallet transaction to the user
116
+ if physical order we decrease the quantity from inventory and delete items from cart
117
+ """
118
+ from ob_dj_store.core.stores.models._wallet import WalletTransaction
119
+
113
120
  self.status = self.PaymentStatus.SUCCESS
114
- orders = list(self.orders.all())
115
- cart = self.user.cart
116
- for order in orders:
121
+ orders = self.orders.all()
122
+ if orders[0].type_of_order == Order.OrderType.WALLET.value:
123
+ order = orders[0]
124
+ currency = order.extra_infos["currency"]
125
+ wallet = self.user.wallets.get(currency=currency)
126
+ WalletTransaction.objects.create(
127
+ wallet=wallet,
128
+ amount=self.amount, # TODO: cunfused about how we hundle if he want to fill up other currency
129
+ type=WalletTransaction.TYPE.CREDIT,
130
+ )
117
131
  order.status = Order.OrderStatus.PAID
118
- for item in order.items.all():
119
- if item.inventory:
120
- item.inventory.decrease(item.quantity)
121
132
  order.save()
122
- items = order.store.store_items.filter(cart=cart)
123
- items.delete()
133
+ else:
134
+ cart = self.user.cart
135
+ for order in orders:
136
+ order.status = Order.OrderStatus.PAID
137
+ for item in order.items.all():
138
+ if item.inventory:
139
+ item.inventory.decrease(item.quantity)
140
+ order.save()
141
+ items = order.store.store_items.filter(cart=cart)
142
+ items.delete()
124
143
  self.payment_post_at = timezone.now()
125
144
  self.save()
126
145
 
@@ -130,14 +149,15 @@ class Payment(models.Model):
130
149
 
131
150
  @property
132
151
  def total_payment(self):
152
+ orders = self.orders.all()
133
153
  sum_orders = Decimal(
134
- sum(map(lambda order: Decimal(order.total_amount) or 0, self.orders.all()))
154
+ sum(map(lambda order: Decimal(order.total_amount) or 0, orders))
135
155
  )
136
156
  if self.payment_tax.rate == Tax.Rates.PERCENTAGE:
137
157
  perc = Decimal(sum_orders * self.payment_tax.value / 100)
138
- return sum_orders + perc
158
+ return round(sum_orders + perc, 3)
139
159
 
140
- return sum_orders + Decimal(self.payment_tax.value)
160
+ return round(sum_orders + self.payment_tax.value, 3)
141
161
 
142
162
  @property
143
163
  def type_of_order(self):
@@ -2,8 +2,6 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
2
2
  from django.core.validators import RegexValidator
3
3
  from django.db import models
4
4
  from django.utils.translation import gettext_lazy as _
5
- from imagekit.models import ImageSpecField
6
- from imagekit.processors import ResizeToFill
7
5
 
8
6
  from ob_dj_store.core.stores.managers import (
9
7
  CategoryManager,
@@ -31,11 +29,11 @@ class Category(DjangoModelCleanMixin, models.Model):
31
29
  related_name="subcategories",
32
30
  )
33
31
  image = models.ImageField(upload_to=category_media_upload_to, null=True, blank=True)
34
- image_thumbnail = ImageSpecField(
35
- source="image",
36
- processors=[ResizeToFill(200, 200)],
37
- format="JPEG",
38
- options={"quality": 60},
32
+ image_thumbnail_small = models.ImageField(
33
+ upload_to="category_media/", null=True, blank=True
34
+ )
35
+ image_thumbnail_medium = models.ImageField(
36
+ upload_to="category_media/", null=True, blank=True
39
37
  )
40
38
  order_value = models.PositiveSmallIntegerField(
41
39
  verbose_name=_("ordering"), default=1
@@ -105,7 +103,7 @@ class Product(DjangoModelCleanMixin, models.Model):
105
103
  PHYSICAL = "PHYSICAL", _("physical")
106
104
  DIGITAL = "DIGITAL", _("digital")
107
105
 
108
- name = models.CharField(max_length=200, help_text=_("Name"))
106
+ name = models.CharField(max_length=200, help_text=_("Name"), unique=True)
109
107
  slug = models.SlugField(max_length=255, unique=True)
110
108
  description = models.TextField(null=True, blank=True)
111
109
  # TODO: A product can be assigned to multiple categories
@@ -189,6 +187,8 @@ class AttributeChoice(DjangoModelCleanMixin, models.Model):
189
187
  order_value = models.PositiveSmallIntegerField(
190
188
  verbose_name=_("ordering"), default=1
191
189
  )
190
+ is_default = models.BooleanField(default=False)
191
+ label = models.CharField(max_length=200, null=True, blank=True)
192
192
  # Audit fields
193
193
  created_at = models.DateTimeField(auto_now_add=True)
194
194
  updated_at = models.DateTimeField(auto_now=True)
@@ -210,6 +210,8 @@ class ProductAttribute(DjangoModelCleanMixin, models.Model):
210
210
  class Type(models.TextChoices):
211
211
  ONE_CHOICE = "ONE_CHOICE", _("one choice")
212
212
  MULTIPLE_CHOICES = "MULTIPLE_CHOICES", _("multiple choices")
213
+ LIST_CHOICES = "LIST_CHOICES", _("list choices")
214
+ INCREMENT_CHOICE = "INCREMENT_CHOICE", _("increment choice")
213
215
 
214
216
  name = models.CharField(max_length=200, help_text=_("Name"))
215
217
  description = models.TextField(null=True, blank=True)
@@ -225,9 +227,12 @@ class ProductAttribute(DjangoModelCleanMixin, models.Model):
225
227
  order_value = models.PositiveSmallIntegerField(
226
228
  verbose_name=_("ordering"), default=1
227
229
  )
230
+ min = models.PositiveSmallIntegerField(default=1)
231
+ max = models.PositiveSmallIntegerField(default=1)
228
232
  is_mandatory = models.BooleanField(default=False)
229
233
  plu = models.CharField(max_length=40, unique=True, null=True, blank=True)
230
234
  external_id = models.CharField(max_length=40, unique=True, null=True, blank=True)
235
+ label = models.CharField(max_length=200, null=True, blank=True)
231
236
  # Audit fields
232
237
  created_at = models.DateTimeField(auto_now_add=True)
233
238
  updated_at = models.DateTimeField(auto_now=True)
@@ -288,11 +293,11 @@ class ProductMedia(DjangoModelCleanMixin, models.Model):
288
293
  )
289
294
  is_primary = models.BooleanField(default=False)
290
295
  image = models.ImageField(upload_to=product_media_upload_to)
291
- image_thumbnail = ImageSpecField(
292
- source="image",
293
- processors=[ResizeToFill(200, 200)],
294
- format="JPEG",
295
- options={"quality": 60},
296
+ image_thumbnail_small = models.ImageField(
297
+ upload_to="product_media/", null=True, blank=True
298
+ )
299
+ image_thumbnail_medium = models.ImageField(
300
+ upload_to="product_media/", null=True, blank=True
296
301
  )
297
302
  order_value = models.PositiveSmallIntegerField(
298
303
  verbose_name=_("ordering"), default=1
@@ -313,3 +318,7 @@ class ProductMedia(DjangoModelCleanMixin, models.Model):
313
318
  ordering = ("order_value",)
314
319
  verbose_name = _("product media")
315
320
  verbose_name_plural = _("Product medias")
321
+
322
+ @property
323
+ def name(self):
324
+ return f"{self.product.name}_{self.order_value}"
@@ -3,6 +3,7 @@ import typing
3
3
  from django.conf import settings
4
4
  from django.contrib.gis.db import models
5
5
  from django.core.exceptions import ValidationError
6
+ from django.utils.timezone import now
6
7
  from django.utils.translation import gettext_lazy as _
7
8
  from phonenumber_field.modelfields import PhoneNumberField
8
9
 
@@ -11,7 +12,7 @@ from ob_dj_store.core.stores.managers import (
11
12
  ShippingMethodManager,
12
13
  StoreManager,
13
14
  )
14
- from ob_dj_store.core.stores.utils import get_currency_by_country
15
+ from ob_dj_store.core.stores.utils import validate_currency
15
16
  from ob_dj_store.utils.helpers import store_media_upload_to
16
17
  from ob_dj_store.utils.model import DjangoModelCleanMixin
17
18
 
@@ -113,7 +114,7 @@ class Store(DjangoModelCleanMixin, models.Model):
113
114
  )
114
115
  payment_methods = models.ManyToManyField(
115
116
  PaymentMethod,
116
- related_name="payment_methods",
117
+ related_name="stores",
117
118
  blank=True,
118
119
  help_text=_("Payment methods within the store"),
119
120
  )
@@ -136,6 +137,13 @@ class Store(DjangoModelCleanMixin, models.Model):
136
137
  help_text=_("This is the min price to get a free delivery"),
137
138
  )
138
139
  image = models.ImageField(upload_to=store_media_upload_to, null=True, blank=True)
140
+ currency = models.CharField(
141
+ max_length=3,
142
+ default="KWD",
143
+ validators=[
144
+ validate_currency,
145
+ ],
146
+ )
139
147
 
140
148
  created_at = models.DateTimeField(auto_now_add=True)
141
149
  updated_at = models.DateTimeField(auto_now=True)
@@ -150,10 +158,11 @@ class Store(DjangoModelCleanMixin, models.Model):
150
158
  return f"Store {self.name} (PK={self.pk})"
151
159
 
152
160
  @property
153
- def currency(self):
154
- if self.address:
155
- return get_currency_by_country(self.address.country.name)
156
- return None
161
+ def current_opening_hours(self):
162
+ return next(
163
+ (x for x in self.opening_hours.all() if x.weekday == now().weekday() + 1),
164
+ None,
165
+ )
157
166
 
158
167
 
159
168
  class OpeningHours(DjangoModelCleanMixin, models.Model):
@@ -6,10 +6,11 @@ from django.conf import settings
6
6
  from django.db import models
7
7
  from django.db.models.functions import Coalesce
8
8
  from django.utils.translation import gettext_lazy as _
9
- from django_countries.fields import CountryField
10
9
 
11
10
  from ob_dj_store.core.stores.managers import WalletTransactionManager
12
- from ob_dj_store.core.stores.utils import get_currency_by_country
11
+ from ob_dj_store.core.stores.models._store import PaymentMethod
12
+ from ob_dj_store.core.stores.utils import validate_currency
13
+ from ob_dj_store.utils.helpers import wallet_media_upload_to
13
14
 
14
15
  logger = logging.getLogger(__name__)
15
16
 
@@ -20,7 +21,21 @@ class Wallet(models.Model):
20
21
  on_delete=models.CASCADE,
21
22
  related_name="wallets",
22
23
  )
23
- country = CountryField(_("the wallet's country"), null=True, blank=True)
24
+ name = models.CharField(max_length=200, null=True, blank=True)
25
+ image = models.ImageField(upload_to=wallet_media_upload_to, null=True, blank=True)
26
+ image_thumbnail_medium = models.ImageField(
27
+ upload_to="wallets/", null=True, blank=True
28
+ )
29
+ currency = models.CharField(
30
+ max_length=3,
31
+ default="KWD",
32
+ validators=[
33
+ validate_currency,
34
+ ],
35
+ )
36
+
37
+ class Meta:
38
+ unique_together = ("user", "currency")
24
39
 
25
40
  def __str__(self) -> typing.Text:
26
41
  return f"Wallet(PK={self.pk})"
@@ -48,11 +63,29 @@ class Wallet(models.Model):
48
63
  )
49
64
  return query["balance"]
50
65
 
51
- @property
52
- def currency(self):
53
- if self.country:
54
- return get_currency_by_country(self.country.name)
55
- return None
66
+ def top_up_wallet(self, amount: Decimal, payment_method: PaymentMethod):
67
+ from ob_dj_store.core.stores.models import Order, Payment
68
+
69
+ user = self.user
70
+ order = Order.objects.create(
71
+ customer=user,
72
+ payment_method=payment_method,
73
+ extra_infos={
74
+ "is_wallet_fill_up": True,
75
+ "amount": str(amount),
76
+ "currency": self.currency,
77
+ },
78
+ )
79
+ payment = Payment.objects.create(
80
+ orders=[
81
+ order,
82
+ ],
83
+ user=user,
84
+ currency=self.currency,
85
+ method=payment_method,
86
+ amount=amount,
87
+ )
88
+ return payment.payment_url
56
89
 
57
90
 
58
91
  class WalletTransaction(models.Model):
@@ -1,8 +1,17 @@
1
1
  from django.conf import settings
2
- from django.db.models.signals import post_save
2
+ from django.db.models.signals import post_save, pre_save
3
3
  from django.dispatch import receiver
4
4
 
5
- from ob_dj_store.core.stores.models import Cart, Order, OrderHistory, Wallet
5
+ from config import settings as store_settings
6
+ from ob_dj_store.core.stores.models import (
7
+ Cart,
8
+ Category,
9
+ Order,
10
+ OrderHistory,
11
+ ProductMedia,
12
+ Wallet,
13
+ )
14
+ from ob_dj_store.utils.utils import resize_image
6
15
 
7
16
 
8
17
  @receiver(
@@ -15,8 +24,8 @@ def create_customer_cart_and_wallet_handler(sender, instance, created, **kwargs)
15
24
  return
16
25
  cart = Cart(customer=instance)
17
26
  cart.save()
18
- country = getattr(instance, "country", None)
19
- Wallet.objects.create(user=instance, country=country)
27
+ for currency in store_settings.WALLET_CURRENCIES:
28
+ instance.wallets.create(currency=currency)
20
29
 
21
30
 
22
31
  # add receiver to ProductVariant to create inventory
@@ -32,3 +41,69 @@ def create_order_history_handler(sender, instance, created, **kwargs):
32
41
  order=instance,
33
42
  status=instance.status,
34
43
  )
44
+
45
+
46
+ @receiver(
47
+ pre_save,
48
+ sender=Category,
49
+ dispatch_uid="create_category_thumbnails",
50
+ )
51
+ def create_category_thumbnails(sender, instance, **kwargs):
52
+ medium_dim = getattr(store_settings, "THUMBNAIL_MEDIUM_DIMENSIONS", None)
53
+ small_dim = getattr(store_settings, "THUMBNAIL_SMALL_DIMENSIONS", None)
54
+ if instance.image:
55
+ if medium_dim:
56
+ instance.image_thumbnail_medium = resize_image(
57
+ instance.image,
58
+ dim=medium_dim,
59
+ size_name="medium",
60
+ image_name=instance.name,
61
+ )
62
+ if small_dim:
63
+ instance.image_thumbnail_small = resize_image(
64
+ instance.image,
65
+ dim=small_dim,
66
+ size_name="small",
67
+ image_name=instance.name,
68
+ )
69
+
70
+
71
+ @receiver(
72
+ pre_save,
73
+ sender=ProductMedia,
74
+ dispatch_uid="create_product_media_thumbnails",
75
+ )
76
+ def create_product_media_thumbnails(sender, instance, **kwargs):
77
+ medium_dim = getattr(store_settings, "THUMBNAIL_MEDIUM_DIMENSIONS", None)
78
+ small_dim = getattr(store_settings, "THUMBNAIL_SMALL_DIMENSIONS", None)
79
+ if instance.image:
80
+ if medium_dim:
81
+ instance.image_thumbnail_medium = resize_image(
82
+ instance.image,
83
+ dim=medium_dim,
84
+ size_name="medium",
85
+ image_name=instance.name,
86
+ )
87
+ if small_dim:
88
+ instance.image_thumbnail_small = resize_image(
89
+ instance.image,
90
+ dim=small_dim,
91
+ size_name="small",
92
+ image_name=instance.name,
93
+ )
94
+
95
+
96
+ @receiver(
97
+ pre_save,
98
+ sender=Wallet,
99
+ dispatch_uid="create_wallet_thumbnails",
100
+ )
101
+ def create_wallet_thumbnails(sender, instance, **kwargs):
102
+ medium_dim = getattr(store_settings, "THUMBNAIL_MEDIUM_DIMENSIONS", None)
103
+ if instance.image and medium_dim:
104
+ instance.image_thumbnail_medium = resize_image(
105
+ instance.image,
106
+ dim=medium_dim,
107
+ size_name="medium",
108
+ image_name=f"{instance.currency}_{instance.user.username}",
109
+ )
@@ -55,13 +55,19 @@ def distance(origin, destination):
55
55
 
56
56
  def get_currency_by_country(country):
57
57
  country = pycountry.countries.get(name=country)
58
- if not country:
59
- raise ValidationError(_("Invalid Country!"))
60
- return pycountry.currencies.get(numeric=country.numeric).alpha_3
58
+ currency = pycountry.currencies.get(numeric=country.numeric)
59
+ return currency.alpha_3
61
60
 
62
61
 
63
62
  def get_country_by_currency(currency):
64
63
  currency = pycountry.currencies.get(alpha_3=currency)
65
- if not currency:
66
- raise ValidationError(_("Invalid Currency!"))
67
- return pycountry.countries.get(numeric=currency.numeric).alpha_2
64
+ country = pycountry.countries.get(numeric=currency.numeric)
65
+ return country.alpha_2
66
+
67
+
68
+ def validate_currency(value):
69
+ if not pycountry.currencies.get(alpha_3=value):
70
+ raise ValidationError(
71
+ _("%(value)s is not a currency"),
72
+ params={"value": value},
73
+ )
@@ -19,7 +19,7 @@ def import_class_from_string(serializer_class_name):
19
19
 
20
20
  def product_media_upload_to(instance, filename):
21
21
  ext = filename.split(".")[-1]
22
- return f"product_media/{instance.product.id}_{instance.order_value}_{int(now().timestamp())}.{ext}"
22
+ return f"product_media/{instance.product.name}_{instance.order_value}_{int(now().timestamp())}.{ext}"
23
23
 
24
24
 
25
25
  def category_media_upload_to(instance, filename):
@@ -31,4 +31,10 @@ def category_media_upload_to(instance, filename):
31
31
  def store_media_upload_to(instance, filename):
32
32
  ext = filename.split(".")[-1]
33
33
  if instance:
34
- return f"category_media/{instance.name}_{int(now().timestamp())}.{ext}"
34
+ return f"store_media/{instance.name}_{int(now().timestamp())}.{ext}"
35
+
36
+
37
+ def wallet_media_upload_to(instance, filename):
38
+ ext = filename.split(".")[-1]
39
+ if instance:
40
+ return f"wallets/{instance.currency}_{instance.user.username}_{int(now().timestamp())}.{ext}"
@@ -1,3 +1,43 @@
1
- class JustOnSave(object):
2
- def on_source_saved(self, file):
3
- file.generate()
1
+ import logging
2
+ import os
3
+ from io import BytesIO
4
+
5
+ from django.core.files.uploadedfile import InMemoryUploadedFile
6
+ from django.utils.timezone import now
7
+ from PIL import Image, UnidentifiedImageError
8
+ from pilkit.processors import ResizeToFill
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def resize_image(image, dim: dict, size_name: str, image_name: str):
14
+ try:
15
+ img = Image.open(image)
16
+ except UnidentifiedImageError as e:
17
+ logger.error(e)
18
+ return None
19
+ width, height = dim.values()
20
+ processor = ResizeToFill(width=width, height=height)
21
+ new_img = processor.process(img)
22
+ filename, extension = os.path.splitext(image.name)
23
+ extension = extension.lower()
24
+ if extension == ".png":
25
+ content_type = "image/png"
26
+ img_format = "PNG"
27
+ else:
28
+ content_type = "image/jpeg"
29
+ img_format = "JPEG"
30
+ output = BytesIO()
31
+ if new_img.mode in ("RGBA", "P"):
32
+ new_img = new_img.convert("RGB")
33
+ new_img.save(output, format=img_format, quality=70)
34
+ output.seek(0)
35
+ new_image = InMemoryUploadedFile(
36
+ output,
37
+ "ImageField",
38
+ f"{image_name}_{int(now().timestamp())}_{size_name}.{img_format.lower()}",
39
+ content_type,
40
+ output.__sizeof__(),
41
+ None,
42
+ )
43
+ return new_image
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ob-dj-store
3
- Version: 0.0.11.3
3
+ Version: 0.0.11.10
4
4
  Summary: OBytes django application for managing ecommerce stores.
5
5
  Home-page: https://www.obytes.com/
6
6
  Author: OBytes