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.
- ob_dj_store/apis/stores/filters.py +33 -18
- ob_dj_store/apis/stores/rest/serializers/serializers.py +129 -53
- ob_dj_store/apis/stores/urls.py +3 -3
- ob_dj_store/apis/stores/views.py +182 -42
- ob_dj_store/core/stores/admin.py +8 -3
- ob_dj_store/core/stores/admin_inlines.py +4 -4
- ob_dj_store/core/stores/gateway/tap/models.py +1 -1
- ob_dj_store/core/stores/gateway/tap/utils.py +4 -4
- ob_dj_store/core/stores/managers.py +8 -6
- ob_dj_store/core/stores/migrations/0056_auto_20230213_2224.py +33 -0
- ob_dj_store/core/stores/migrations/0057_auto_20230214_1724.py +37 -0
- ob_dj_store/core/stores/migrations/0058_attributechoice_is_default.py +18 -0
- ob_dj_store/core/stores/migrations/0059_auto_20230217_2006.py +41 -0
- ob_dj_store/core/stores/migrations/0060_alter_orderitem_product_variant.py +24 -0
- ob_dj_store/core/stores/migrations/0061_auto_20230223_1435.py +28 -0
- ob_dj_store/core/stores/migrations/0062_auto_20230226_2005.py +43 -0
- ob_dj_store/core/stores/migrations/0063_alter_store_payment_methods.py +23 -0
- ob_dj_store/core/stores/migrations/0064_auto_20230228_1814.py +24 -0
- ob_dj_store/core/stores/migrations/0065_auto_20230228_1932.py +34 -0
- ob_dj_store/core/stores/models/_favorite.py +4 -1
- ob_dj_store/core/stores/models/_order.py +4 -2
- ob_dj_store/core/stores/models/_payment.py +31 -11
- ob_dj_store/core/stores/models/_product.py +22 -13
- ob_dj_store/core/stores/models/_store.py +15 -6
- ob_dj_store/core/stores/models/_wallet.py +41 -8
- ob_dj_store/core/stores/receivers.py +79 -4
- ob_dj_store/core/stores/utils.py +12 -6
- ob_dj_store/utils/helpers.py +8 -2
- ob_dj_store/utils/utils.py +43 -3
- {ob_dj_store-0.0.11.3.dist-info → ob_dj_store-0.0.11.10.dist-info}/METADATA +1 -1
- {ob_dj_store-0.0.11.3.dist-info → ob_dj_store-0.0.11.10.dist-info}/RECORD +33 -23
- {ob_dj_store-0.0.11.3.dist-info → ob_dj_store-0.0.11.10.dist-info}/WHEEL +0 -0
- {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.
|
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 =
|
115
|
-
|
116
|
-
|
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
|
-
|
123
|
-
|
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,
|
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 +
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
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
|
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="
|
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
|
154
|
-
|
155
|
-
|
156
|
-
|
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.
|
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
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
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
|
-
|
19
|
-
|
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
|
+
)
|
ob_dj_store/core/stores/utils.py
CHANGED
@@ -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
|
-
|
59
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
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
|
+
)
|
ob_dj_store/utils/helpers.py
CHANGED
@@ -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.
|
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"
|
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}"
|
ob_dj_store/utils/utils.py
CHANGED
@@ -1,3 +1,43 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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
|