ob-dj-store 0.0.11.8__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.
@@ -5,10 +5,12 @@ from django.utils.timezone import now
5
5
  from django_filters import rest_framework as filters
6
6
  from rest_framework.exceptions import ValidationError
7
7
 
8
+ from config import settings as store_settings
8
9
  from ob_dj_store.core.stores.models import (
9
10
  Category,
10
11
  Favorite,
11
12
  Order,
13
+ PaymentMethod,
12
14
  Product,
13
15
  ProductVariant,
14
16
  Store,
@@ -128,6 +130,7 @@ class CategoryFilter(filters.FilterSet):
128
130
  is_active=True,
129
131
  ).distinct(),
130
132
  ),
133
+ "subcategories__products__images",
131
134
  )
132
135
  .distinct()
133
136
  )
@@ -174,3 +177,25 @@ class FavoriteFilter(filters.FilterSet):
174
177
  content_type=content_type,
175
178
  object_id__in=products_ids,
176
179
  )
180
+
181
+
182
+ class PaymentMethodFilter(filters.FilterSet):
183
+ store = filters.CharFilter(method="by_store")
184
+ is_digital = filters.BooleanFilter(method="by_digital")
185
+
186
+ class Meta:
187
+ models = PaymentMethod
188
+
189
+ def by_store(self, queryset, name, value):
190
+ return queryset.filter(stores=value)
191
+
192
+ def by_digital(self, queryset, name, value):
193
+ if value:
194
+ return queryset.filter(
195
+ payment_provider__in=[
196
+ store_settings.TAP_CREDIT_CARD,
197
+ store_settings.TAP_KNET,
198
+ store_settings.TAP_ALL,
199
+ ]
200
+ )
201
+ return queryset
@@ -39,6 +39,7 @@ from ob_dj_store.core.stores.models import (
39
39
  ShippingMethod,
40
40
  Store,
41
41
  Tax,
42
+ Wallet,
42
43
  )
43
44
  from ob_dj_store.core.stores.models._inventory import Inventory
44
45
  from ob_dj_store.core.stores.utils import distance
@@ -288,7 +289,7 @@ class OrderSerializer(serializers.ModelSerializer):
288
289
  if payment_method:
289
290
  if payment_method.payment_provider == store_settings.WALLET:
290
291
  try:
291
- wallet = user.wallets.get(country=attrs["store"].address.country)
292
+ wallet = user.wallets.get(currency=attrs["store"].currency)
292
293
  except ObjectDoesNotExist:
293
294
  raise serializers.ValidationError(
294
295
  {
@@ -603,7 +604,9 @@ class ProductSerializer(FavoriteMixin, serializers.ModelSerializer):
603
604
  return data
604
605
 
605
606
 
606
- class ProductListSerializer(ProductSerializer):
607
+ class ProductListSerializer(serializers.ModelSerializer):
608
+ product_images = ProductMediaSerializer(many=True, source="images")
609
+
607
610
  class Meta:
608
611
  model = Product
609
612
  fields = (
@@ -974,7 +977,11 @@ class FavoriteSerializer(serializers.ModelSerializer):
974
977
  "extras",
975
978
  "object_id",
976
979
  "object_type",
980
+ "name",
977
981
  )
982
+ extra_kwargs = {
983
+ "name": {"required": True},
984
+ }
978
985
 
979
986
  def _lookup_validation(self, data):
980
987
  content_type = ContentType.objects.get_for_model(type(data["content_object"]))
@@ -1001,6 +1008,7 @@ class FavoriteSerializer(serializers.ModelSerializer):
1001
1008
 
1002
1009
  def validate(self, attrs):
1003
1010
  validated_data = super().validate(attrs)
1011
+ name = validated_data["name"]
1004
1012
  object_type = validated_data["object_type"]
1005
1013
  if object_type not in store_settings.FAVORITE_TYPES and hasattr(
1006
1014
  sys.modules[__name__], object_type
@@ -1035,14 +1043,50 @@ class FavoriteSerializer(serializers.ModelSerializer):
1035
1043
  validated_data = {
1036
1044
  "content_object": object_instance,
1037
1045
  "extras": extras,
1046
+ "name": name,
1038
1047
  }
1039
1048
  self._lookup_validation(validated_data)
1040
1049
  return validated_data
1041
1050
 
1042
1051
  def create(self, validated_data):
1043
- favorite = Favorite.add_favorite(
1044
- validated_data["content_object"],
1045
- self.context["request"].user,
1046
- validated_data["extras"],
1047
- )
1052
+ try:
1053
+ favorite = Favorite.add_favorite(
1054
+ content_object=validated_data["content_object"],
1055
+ user=self.context["request"].user,
1056
+ name=validated_data["name"],
1057
+ extras=validated_data["extras"],
1058
+ )
1059
+ except ValidationError as e:
1060
+ raise serializers.ValidationError(detail=e.message_dict)
1048
1061
  return favorite
1062
+
1063
+
1064
+ class WalletSerializer(serializers.ModelSerializer):
1065
+ class Meta:
1066
+ model = Wallet
1067
+ fields = ["id", "user", "balance", "name", "image", "image_thumbnail_medium"]
1068
+ extra_kwargs = {
1069
+ "user": {"read_only": True},
1070
+ "image_thumbnail_medium": {"read_only": True},
1071
+ }
1072
+
1073
+
1074
+ class WalletTopUpSerializer(serializers.Serializer):
1075
+ amount = serializers.DecimalField(
1076
+ max_digits=10, decimal_places=2, min_value=1, required=True
1077
+ )
1078
+ payment_method = serializers.PrimaryKeyRelatedField(
1079
+ queryset=PaymentMethod.objects.filter(
1080
+ payment_provider__in=[
1081
+ store_settings.TAP_CREDIT_CARD,
1082
+ store_settings.TAP_KNET,
1083
+ store_settings.TAP_ALL,
1084
+ ]
1085
+ ),
1086
+ required=True,
1087
+ )
1088
+
1089
+ def top_up_wallet(self, wallet):
1090
+ amount = self.validated_data["amount"]
1091
+ payment_method = self.validated_data["payment_method"]
1092
+ return wallet.top_up_wallet(amount=amount, payment_method=payment_method)
@@ -16,6 +16,7 @@ from ob_dj_store.apis.stores.views import (
16
16
  TaxViewSet,
17
17
  TransactionsViewSet,
18
18
  VariantView,
19
+ WalletViewSet,
19
20
  )
20
21
 
21
22
  app_name = "stores"
@@ -28,9 +29,6 @@ stores_router.register(r"order", OrderView, basename="order")
28
29
  stores_router.register(r"product", ProductView, basename="product")
29
30
  stores_router.register(r"variant", VariantView, basename="variant")
30
31
  stores_router.register(r"inventory", InventoryView, basename="inventory")
31
- stores_router.register(
32
- r"payment-method", PaymentMethodViewSet, basename="payment-method"
33
- )
34
32
  stores_router.register(
35
33
  r"shipping-method", ShippingMethodViewSet, basename="shipping-method"
36
34
  )
@@ -40,6 +38,8 @@ router.register(r"transaction", TransactionsViewSet, basename="transaction")
40
38
  router.register(r"cart", CartView, basename="cart")
41
39
  router.register(r"cart-item", CartItemView, basename="cart-item")
42
40
  router.register(r"favorite", FavoriteViewSet, basename="favorite")
41
+ router.register(r"wallet", WalletViewSet, basename="wallet")
42
+ router.register(r"payment-method", PaymentMethodViewSet, basename="payment-method")
43
43
  urlpatterns = [
44
44
  path(r"", include(router.urls)),
45
45
  path(r"", include(stores_router.urls)),
@@ -26,6 +26,7 @@ from ob_dj_store.apis.stores.filters import (
26
26
  FavoriteFilter,
27
27
  InventoryFilter,
28
28
  OrderFilter,
29
+ PaymentMethodFilter,
29
30
  ProductFilter,
30
31
  StoreFilter,
31
32
  VariantFilter,
@@ -47,6 +48,8 @@ from ob_dj_store.apis.stores.rest.serializers.serializers import (
47
48
  ShippingMethodSerializer,
48
49
  StoreSerializer,
49
50
  TaxSerializer,
51
+ WalletSerializer,
52
+ WalletTopUpSerializer,
50
53
  )
51
54
  from ob_dj_store.core.stores.models import (
52
55
  Cart,
@@ -62,6 +65,7 @@ from ob_dj_store.core.stores.models import (
62
65
  ShippingMethod,
63
66
  Store,
64
67
  Tax,
68
+ Wallet,
65
69
  )
66
70
  from ob_dj_store.core.stores.models._inventory import Inventory
67
71
 
@@ -188,29 +192,6 @@ class StoreView(
188
192
  serializer = self.get_serializer(page, many=True)
189
193
  return self.get_paginated_response(serializer.data)
190
194
 
191
- @swagger_auto_schema(
192
- operation_summary="Add or Remove Store from Favorites",
193
- operation_description="""
194
- Add or Remove Store from Favorites
195
- """,
196
- tags=[
197
- "Store",
198
- ],
199
- )
200
- @action(
201
- detail=True,
202
- methods=["GET"],
203
- url_path="favorite",
204
- )
205
- def favorite(self, request, *args, **kwargs):
206
- instance = self.get_object()
207
- try:
208
- Favorite.objects.favorite_for_user(instance, request.user).delete()
209
- except Favorite.DoesNotExist:
210
- Favorite.add_favorite(instance, request.user)
211
- serializer = StoreSerializer(instance=instance, context={"request": request})
212
- return Response(serializer.data)
213
-
214
195
  @swagger_auto_schema(
215
196
  operation_summary="Retrieve count of store's products",
216
197
  operation_description="""
@@ -674,6 +655,9 @@ class CategoryViewSet(
674
655
  filterset_class = CategoryFilter
675
656
  lookup_value_regex = "[0-9]+"
676
657
 
658
+ def get_queryset(self):
659
+ return super().get_queryset().prefetch_related("products__images")
660
+
677
661
  def get_object(self):
678
662
  store_id = self.request.query_params.get("store", None)
679
663
  instance_pk = self.kwargs["pk"]
@@ -699,6 +683,7 @@ class CategoryViewSet(
699
683
  "products",
700
684
  queryset=product_queryset.distinct(),
701
685
  ),
686
+ "subcategories__products__images",
702
687
  ).get(pk=instance_pk)
703
688
  except Category.DoesNotExist:
704
689
  raise Http404("No Category matches the given query.")
@@ -800,14 +785,9 @@ class PaymentMethodViewSet(
800
785
  permission_classes = [
801
786
  permissions.IsAuthenticated,
802
787
  ]
803
- queryset = PaymentMethod.objects.all()
804
-
805
- def get_queryset(self):
806
- try:
807
- store = Store.objects.get(pk=self.kwargs["store_pk"])
808
- except ObjectDoesNotExist:
809
- raise ValidationError(_(f"Store does not Exist"))
810
- return store.payment_methods.filter(is_active=True)
788
+ queryset = PaymentMethod.objects.filter(is_active=True)
789
+ filterset_class = PaymentMethodFilter
790
+ lookup_value_regex = "[0-9]+"
811
791
 
812
792
  @swagger_auto_schema(
813
793
  operation_summary="List Payment Methods",
@@ -949,3 +929,84 @@ class FavoriteViewSet(
949
929
  )
950
930
  def destroy(self, request, *args, **kwargs):
951
931
  return super().destroy(request, *args, **kwargs)
932
+
933
+
934
+ class WalletViewSet(
935
+ mixins.ListModelMixin,
936
+ mixins.RetrieveModelMixin,
937
+ mixins.UpdateModelMixin,
938
+ viewsets.GenericViewSet,
939
+ ):
940
+ queryset = Wallet.objects.all()
941
+ serializer_class = WalletSerializer
942
+ permission_classes = [
943
+ permissions.IsAuthenticated,
944
+ ]
945
+
946
+ def get_queryset(self):
947
+ return Wallet.objects.filter(user=self.request.user)
948
+
949
+ @swagger_auto_schema(
950
+ operation_summary="Get User Wallet",
951
+ operation_description="""
952
+ Get User Wallet
953
+ """,
954
+ tags=[
955
+ "Wallet",
956
+ ],
957
+ )
958
+ def retrieve(
959
+ self, request: Request, *args: typing.Any, **kwargs: typing.Any
960
+ ) -> Response:
961
+ return super().retrieve(request=request, *args, **kwargs)
962
+
963
+ @swagger_auto_schema(
964
+ operation_summary="Update user wallet",
965
+ operation_description="""
966
+ Update a wallet
967
+ """,
968
+ tags=[
969
+ "Wallet",
970
+ ],
971
+ )
972
+ def partial_update(self, request: Request, *args: typing.Any, **kwargs: typing.Any):
973
+ return super().partial_update(request, *args, **kwargs)
974
+
975
+ @swagger_auto_schema(
976
+ operation_summary="List User Wallets",
977
+ operation_description="""
978
+ List User Wallets
979
+ """,
980
+ tags=[
981
+ "Wallet",
982
+ ],
983
+ )
984
+ def list(
985
+ self, request: Request, *args: typing.Any, **kwargs: typing.Any
986
+ ) -> Response:
987
+ return super().list(request=request, *args, **kwargs)
988
+
989
+ @swagger_auto_schema(
990
+ operation_summary="top up a wallet",
991
+ operation_description="""
992
+ top up a user wallet with tap payment
993
+ """,
994
+ tags=[
995
+ "Wallet",
996
+ ],
997
+ )
998
+ @action(
999
+ methods=["POST"],
1000
+ detail=True,
1001
+ url_path="top-up",
1002
+ url_name="top-up",
1003
+ serializer_class=WalletTopUpSerializer,
1004
+ )
1005
+ def top_up_wallet(
1006
+ self, request: Request, *args: typing.Any, **kwargs: typing.Any
1007
+ ) -> Response:
1008
+ serializer = self.get_serializer(data=request.data)
1009
+ serializer.is_valid(raise_exception=True)
1010
+ instance = self.get_object()
1011
+ payment_url = serializer.top_up_wallet(instance)
1012
+ return Response({"payment_url": payment_url}, status=status.HTTP_200_OK)
@@ -104,12 +104,6 @@ class CategoryAdmin(admin.ModelAdmin):
104
104
  "is_active",
105
105
  ]
106
106
 
107
- def save_model(self, request, obj, form, change) -> None:
108
- from ob_dj_store.core.stores.receivers import create_media_thumbnails
109
-
110
- create_media_thumbnails(None, obj, None)
111
- return super().save_model(request, obj, form, change)
112
-
113
107
 
114
108
  class ProductVariantAdmin(admin.ModelAdmin):
115
109
  inlines = [
@@ -122,6 +116,9 @@ class ProductVariantAdmin(admin.ModelAdmin):
122
116
  ]
123
117
  search_fields = ["name", "product__name", "sku"]
124
118
 
119
+ def get_queryset(self, request):
120
+ return super().get_queryset(request).prefetch_related("inventories")
121
+
125
122
 
126
123
  class ProductAdmin(admin.ModelAdmin):
127
124
  list_display = ["id", "name", "category", "type", "is_active"]
@@ -249,6 +246,9 @@ class InventoryAdmin(admin.ModelAdmin):
249
246
  "is_uncountable",
250
247
  ]
251
248
 
249
+ def get_queryset(self, request):
250
+ return super().get_queryset(request).prefetch_related("store")
251
+
252
252
 
253
253
  class TaxAdmin(admin.ModelAdmin):
254
254
  list_display = [
@@ -22,10 +22,10 @@ class InventoryInlineAdmin(admin.TabularInline):
22
22
  extra = 1
23
23
 
24
24
  def get_queryset(self, request):
25
- qs = super().get_queryset(request)
26
- return qs.select_related(
27
- "store",
28
- "variant",
25
+ return (
26
+ super()
27
+ .get_queryset(request)
28
+ .select_related("variant", "store", "variant__product")
29
29
  )
30
30
 
31
31
 
@@ -93,7 +93,7 @@ class TapPayment(models.Model):
93
93
 
94
94
  @property
95
95
  def amount(self):
96
- return self.payment.amount
96
+ return self.payment.total_payment
97
97
 
98
98
  def callback_update(self, tap_payload):
99
99
  if self.status == self.Status.INITIATED:
@@ -6,7 +6,6 @@ from django.db import models
6
6
  from django.utils.translation import gettext_lazy as _
7
7
 
8
8
  from config import settings
9
- from ob_dj_store.core.stores.utils import get_country_by_currency
10
9
 
11
10
 
12
11
  class ActiveMixin:
@@ -77,8 +76,7 @@ class PaymentManager(models.Manager):
77
76
  return instance
78
77
  elif gateway == settings.WALLET:
79
78
  try:
80
- country = get_country_by_currency(currency)
81
- wallet = kwargs["user"].wallets.get(country=country)
79
+ wallet = kwargs["user"].wallets.get(currency=currency)
82
80
  except ObjectDoesNotExist:
83
81
  raise ValidationError({"wallet": _("Wallet Not Found")})
84
82
  WalletTransaction.objects.create(
@@ -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
  )
@@ -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):
@@ -319,8 +319,6 @@ class ProductMedia(DjangoModelCleanMixin, models.Model):
319
319
  verbose_name = _("product media")
320
320
  verbose_name_plural = _("Product medias")
321
321
 
322
- def save(self, **kwargs):
323
- from ob_dj_store.core.stores.receivers import create_media_thumbnails
324
-
325
- create_media_thumbnails(None, self, None)
326
- return super().save(**kwargs)
322
+ @property
323
+ def name(self):
324
+ return f"{self.product.name}_{self.order_value}"
@@ -12,7 +12,7 @@ from ob_dj_store.core.stores.managers import (
12
12
  ShippingMethodManager,
13
13
  StoreManager,
14
14
  )
15
- from ob_dj_store.core.stores.utils import get_currency_by_country
15
+ from ob_dj_store.core.stores.utils import validate_currency
16
16
  from ob_dj_store.utils.helpers import store_media_upload_to
17
17
  from ob_dj_store.utils.model import DjangoModelCleanMixin
18
18
 
@@ -114,7 +114,7 @@ class Store(DjangoModelCleanMixin, models.Model):
114
114
  )
115
115
  payment_methods = models.ManyToManyField(
116
116
  PaymentMethod,
117
- related_name="payment_methods",
117
+ related_name="stores",
118
118
  blank=True,
119
119
  help_text=_("Payment methods within the store"),
120
120
  )
@@ -137,6 +137,13 @@ class Store(DjangoModelCleanMixin, models.Model):
137
137
  help_text=_("This is the min price to get a free delivery"),
138
138
  )
139
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
+ )
140
147
 
141
148
  created_at = models.DateTimeField(auto_now_add=True)
142
149
  updated_at = models.DateTimeField(auto_now=True)
@@ -150,12 +157,6 @@ class Store(DjangoModelCleanMixin, models.Model):
150
157
  def __str__(self) -> typing.Text:
151
158
  return f"Store {self.name} (PK={self.pk})"
152
159
 
153
- @property
154
- def currency(self):
155
- if self.address:
156
- return get_currency_by_country(self.address.country.name)
157
- return None
158
-
159
160
  @property
160
161
  def current_opening_hours(self):
161
162
  return next(
@@ -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,9 +1,16 @@
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
5
  from config import settings as store_settings
6
- from ob_dj_store.core.stores.models import Cart, Category, Order, OrderHistory, Wallet
6
+ from ob_dj_store.core.stores.models import (
7
+ Cart,
8
+ Category,
9
+ Order,
10
+ OrderHistory,
11
+ ProductMedia,
12
+ Wallet,
13
+ )
7
14
  from ob_dj_store.utils.utils import resize_image
8
15
 
9
16
 
@@ -17,8 +24,8 @@ def create_customer_cart_and_wallet_handler(sender, instance, created, **kwargs)
17
24
  return
18
25
  cart = Cart(customer=instance)
19
26
  cart.save()
20
- country = getattr(instance, "country", None)
21
- Wallet.objects.create(user=instance, country=country)
27
+ for currency in store_settings.WALLET_CURRENCIES:
28
+ instance.wallets.create(currency=currency)
22
29
 
23
30
 
24
31
  # add receiver to ProductVariant to create inventory
@@ -37,19 +44,66 @@ def create_order_history_handler(sender, instance, created, **kwargs):
37
44
 
38
45
 
39
46
  @receiver(
40
- post_save,
47
+ pre_save,
41
48
  sender=Category,
42
49
  dispatch_uid="create_category_thumbnails",
43
50
  )
44
- def create_media_thumbnails(sender, instance, created, **kwargs):
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):
45
77
  medium_dim = getattr(store_settings, "THUMBNAIL_MEDIUM_DIMENSIONS", None)
46
78
  small_dim = getattr(store_settings, "THUMBNAIL_SMALL_DIMENSIONS", None)
47
79
  if instance.image:
48
80
  if medium_dim:
49
81
  instance.image_thumbnail_medium = resize_image(
50
- instance.image, dim=medium_dim, size_name="medium"
82
+ instance.image,
83
+ dim=medium_dim,
84
+ size_name="medium",
85
+ image_name=instance.name,
51
86
  )
52
87
  if small_dim:
53
88
  instance.image_thumbnail_small = resize_image(
54
- instance.image, dim=small_dim, size_name="small"
89
+ instance.image,
90
+ dim=small_dim,
91
+ size_name="small",
92
+ image_name=instance.name,
55
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
+ )
@@ -1,6 +1,8 @@
1
1
  import math
2
2
 
3
3
  import pycountry
4
+ from django.core.exceptions import ValidationError
5
+ from django.utils.translation import gettext_lazy as _
4
6
 
5
7
 
6
8
  def get_data_dict(instance):
@@ -53,13 +55,19 @@ def distance(origin, destination):
53
55
 
54
56
  def get_currency_by_country(country):
55
57
  country = pycountry.countries.get(name=country)
56
- if not country:
57
- return None
58
- return pycountry.currencies.get(numeric=country.numeric).alpha_3
58
+ currency = pycountry.currencies.get(numeric=country.numeric)
59
+ return currency.alpha_3
59
60
 
60
61
 
61
62
  def get_country_by_currency(currency):
62
63
  currency = pycountry.currencies.get(alpha_3=currency)
63
- if not currency:
64
- return None
65
- 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):
@@ -32,3 +32,9 @@ def store_media_upload_to(instance, filename):
32
32
  ext = filename.split(".")[-1]
33
33
  if instance:
34
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}"
@@ -3,13 +3,14 @@ import os
3
3
  from io import BytesIO
4
4
 
5
5
  from django.core.files.uploadedfile import InMemoryUploadedFile
6
+ from django.utils.timezone import now
6
7
  from PIL import Image, UnidentifiedImageError
7
8
  from pilkit.processors import ResizeToFill
8
9
 
9
10
  logger = logging.getLogger(__name__)
10
11
 
11
12
 
12
- def resize_image(image, dim: dict, size_name: str):
13
+ def resize_image(image, dim: dict, size_name: str, image_name: str):
13
14
  try:
14
15
  img = Image.open(image)
15
16
  except UnidentifiedImageError as e:
@@ -34,7 +35,7 @@ def resize_image(image, dim: dict, size_name: str):
34
35
  new_image = InMemoryUploadedFile(
35
36
  output,
36
37
  "ImageField",
37
- f"{filename}_{size_name}.{img_format.lower()}",
38
+ f"{image_name}_{int(now().timestamp())}_{size_name}.{img_format.lower()}",
38
39
  content_type,
39
40
  output.__sizeof__(),
40
41
  None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ob-dj-store
3
- Version: 0.0.11.8
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
@@ -1,28 +1,28 @@
1
1
  ob_dj_store/apis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  ob_dj_store/apis/stores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- ob_dj_store/apis/stores/filters.py,sha256=Kx4szVD8ethRcOHyUE4k3qkWNq1jercGw1dgieug7Cc,4991
4
- ob_dj_store/apis/stores/urls.py,sha256=wwItq02d8xXiDaQs2p2CrhgOTW2jrMD75EFbMTZuspM,1526
5
- ob_dj_store/apis/stores/views.py,sha256=2v6oood8PDsoQrNb-qAEGtmEzp2yBsdBORiMQn6G4gI,28202
6
- ob_dj_store/apis/stores/rest/serializers/serializers.py,sha256=qUh0uFJ9YJQv8QspAWZxviQ1s_BcAXQa_vIkCE_NHmU,34908
3
+ ob_dj_store/apis/stores/filters.py,sha256=s4US_TysnIgdT8pue_1jlG3V33WhYmCymXK3DuC0rqY,5746
4
+ ob_dj_store/apis/stores/urls.py,sha256=cPForgFpOgOGCUVAk6DZcZ7qOooMf1zpDIr1BA0L_8A,1593
5
+ ob_dj_store/apis/stores/views.py,sha256=zoEgQg3qKVlhhHPj96_UoNE-B8Pf8NK4AuUEmTwOhgg,29833
6
+ ob_dj_store/apis/stores/rest/serializers/serializers.py,sha256=43c_0NyKVZDfmI32f2DR3m6i0G2y3G_yxZEuJBvdAL4,36402
7
7
  ob_dj_store/apis/tap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  ob_dj_store/apis/tap/serializers.py,sha256=KPrBK4h2-fWvEVf6vOj2ww5-USV9WqpyYicIqoHIiXI,1065
9
9
  ob_dj_store/apis/tap/urls.py,sha256=bnOTv6an11kxpo_FdqlhsizlGPLVpNxBjCyKcf3_C9M,367
10
10
  ob_dj_store/apis/tap/views.py,sha256=VnVquybTHlJquxsC0RNTy20dtLXalchO0SlGjSDaBng,2666
11
11
  ob_dj_store/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  ob_dj_store/core/stores/__init__.py,sha256=-izNGrxNn_nn3IQXd5pkuES9lSF-AHYb14yhNPozYCI,65
13
- ob_dj_store/core/stores/admin.py,sha256=ZgVudDVomfKNR_BEhUzk5cz2Exx8G0K5H-PsPEnA7Mk,7383
14
- ob_dj_store/core/stores/admin_inlines.py,sha256=8pnJRzU3Zc0Il-BHm92hj-JEIdFZFj-Sdf9BAa5ne9g,1538
13
+ ob_dj_store/core/stores/admin.py,sha256=4um-O_30xpnfcZHe_sNOyvSTnqLSbeExA2E1QppLEZQ,7354
14
+ ob_dj_store/core/stores/admin_inlines.py,sha256=SLj8mMa-Sc3oP693R0W9c3Ocyk7eb34IyXbC4E6LL8M,1557
15
15
  ob_dj_store/core/stores/apps.py,sha256=i6D2lcqlluP6FwJSBpekf7BQVzQURwSy_Y97SgqWTkM,434
16
- ob_dj_store/core/stores/managers.py,sha256=qAAas3VTmppPyLVrTdIEW_ilPujcvqq4EtAkmmH_Kyg,6483
17
- ob_dj_store/core/stores/receivers.py,sha256=DR5-qDsAXLitSkc4_4Ade7ziGCeHUR53e8jocmOIWsk,1685
16
+ ob_dj_store/core/stores/managers.py,sha256=cDr8TtPxayLwTFNgFs9I87ZXh7ssqM-yAmvEcN9a5LU,6359
17
+ ob_dj_store/core/stores/receivers.py,sha256=5AYMW8ul5htJPp4gBwRAH1VyT5ugZvDMJmed2sostBM,3194
18
18
  ob_dj_store/core/stores/settings_validation.py,sha256=s9BPEdyCL2aslZwZGLAWEvJBzXlUo0p2TbC1_0E_SDo,327
19
- ob_dj_store/core/stores/utils.py,sha256=P_aSRj8ap9z6m1ZsX8iNEpo57yWbVTkg93yT5oQwXgo,1571
19
+ ob_dj_store/core/stores/utils.py,sha256=_FwZEIwKdfj3CuYHCz3wKqq5TBb8xak7UiiCB1oggKc,1850
20
20
  ob_dj_store/core/stores/gateway/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  ob_dj_store/core/stores/gateway/tap/__init__.py,sha256=5Z6azpb6tmr1nRvKwQWzlYw9ruvw-9ZMBWRqEngDKTM,40
22
22
  ob_dj_store/core/stores/gateway/tap/admin.py,sha256=Jzt50VacsSaxJb-ly4sJ0F4NOujixlS4_3DgkdU0JSk,384
23
23
  ob_dj_store/core/stores/gateway/tap/apps.py,sha256=kWwPjuAJeEmEasVDzUbvRsGaQWL-aYe4JDHNLvCVXPs,212
24
24
  ob_dj_store/core/stores/gateway/tap/managers.py,sha256=nJkKPzd_bgTttmZERiWWHiQW0WWcfSHUxNkOKmyrjDY,830
25
- ob_dj_store/core/stores/gateway/tap/models.py,sha256=BGxHY_-X4kLFLePuHGezvTAyBot3iQndigXtyW0RChg,3449
25
+ ob_dj_store/core/stores/gateway/tap/models.py,sha256=11SYWK-I7NyrJUQAJeTp3jzO94jwWNm_y5QZXd4UFkA,3456
26
26
  ob_dj_store/core/stores/gateway/tap/utils.py,sha256=DB-UVxQT3Sdb17g5PF5a9Ghy6GgKADxcJWt1MxPmlt4,2493
27
27
  ob_dj_store/core/stores/gateway/tap/migrations/0001_initial.py,sha256=_303R1R2tVVdPSMtwpuLUxvWbxQ2BML1Gsd-VhfnZEM,4808
28
28
  ob_dj_store/core/stores/gateway/tap/migrations/0002_auto_20220815_1610.py,sha256=gRsekTbqUH4h5yg5d1zWuai7Wiv6-rfsmjnXundgin8,674
@@ -90,23 +90,27 @@ ob_dj_store/core/stores/migrations/0058_attributechoice_is_default.py,sha256=nv_
90
90
  ob_dj_store/core/stores/migrations/0059_auto_20230217_2006.py,sha256=ZaTZRC10L9uzMuya0rXHcsvJzsfF6H9-kcbzrrKVZMQ,1304
91
91
  ob_dj_store/core/stores/migrations/0060_alter_orderitem_product_variant.py,sha256=wyGWekRoR3XAGFfNScNV1jvqMz_HgWACCeiodUa_fGA,607
92
92
  ob_dj_store/core/stores/migrations/0061_auto_20230223_1435.py,sha256=Rqi0XMNw21mVlSzmWo132xaJfamUeyZ1l0_wdr6ipg0,796
93
+ ob_dj_store/core/stores/migrations/0062_auto_20230226_2005.py,sha256=ApzGG5teYcLd4horeCzxIWnVPTeRoMNlJProUhYHacY,1187
94
+ ob_dj_store/core/stores/migrations/0063_alter_store_payment_methods.py,sha256=QKIrNVtjttvYn4IO1GpGkA2gX4cyqjHNbFQ7q7cQqno,571
95
+ ob_dj_store/core/stores/migrations/0064_auto_20230228_1814.py,sha256=6fgusQrMykN_tlNBxiJqLNvrHBA0yI32JK0kQ4VfNpQ,644
96
+ ob_dj_store/core/stores/migrations/0065_auto_20230228_1932.py,sha256=r3-ThPhh_Lf4J3tYbCLiRbLSui5VSqwaVCtzvEZes_8,919
93
97
  ob_dj_store/core/stores/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
98
  ob_dj_store/core/stores/models/__init__.py,sha256=1lEOqmvQlQJZj9nnObTAQqgnPP1yVGvM8H9yjglZ0VI,1568
95
99
  ob_dj_store/core/stores/models/_address.py,sha256=qS5TQ9Z12zx_4CrrHvG8PoYVkdiOq_MtbKR14WKh3Hw,1661
96
100
  ob_dj_store/core/stores/models/_cart.py,sha256=qgjg5MXTMRIZRJJDx5BQ1pa9__ylfrnYMMRGbB2R53c,4555
97
- ob_dj_store/core/stores/models/_favorite.py,sha256=k5WlwSiYsLFICh4cvfkuo63yiVmyU8HbZ9X60_c26WI,2419
101
+ ob_dj_store/core/stores/models/_favorite.py,sha256=bpXlFBNK8jsQkZG1RiDAbNTNSoVP0NOUUxZI8c46QzI,2561
98
102
  ob_dj_store/core/stores/models/_feedback.py,sha256=eCUVgprNK5hSRKOS4M_pdR7QH2-rqhoYevlpykhCOLg,1472
99
103
  ob_dj_store/core/stores/models/_inventory.py,sha256=vAXSpCyUdYDIWgUEUUObQfhAcCcQO6j6zATrHf5dPuQ,3928
100
- ob_dj_store/core/stores/models/_order.py,sha256=B1MpTal3nC3tgNzy8cDsjB2usMQMoN_WTvY3VUniwXM,7900
101
- ob_dj_store/core/stores/models/_payment.py,sha256=Rxo3vlpCzyqQjtCWjlQctgx15tuWf2e2zurURmBqGa0,5189
102
- ob_dj_store/core/stores/models/_product.py,sha256=R8ZW_12iofS8LrlVQ2zI-hDQE-gRsxXTTsQ_7gVIjQA,11034
103
- ob_dj_store/core/stores/models/_store.py,sha256=ysu_UQuJODj8JR9mnYmQPas5hi3kkUm9IRTBumNEX9s,7356
104
- ob_dj_store/core/stores/models/_wallet.py,sha256=cnLC7NgHk2dDwgwV-vLkiVN0cBP0LjsPwDVVK2wp0ro,2640
104
+ ob_dj_store/core/stores/models/_order.py,sha256=qtdUpG30dZeDGeh0Z8JhzyyqQIuxTdLfyzd0-xaoo3o,8018
105
+ ob_dj_store/core/stores/models/_payment.py,sha256=dCOXOYkgxOGJqp0nxEvzHmdZxR_srFUIZAhrB--SP1U,6143
106
+ ob_dj_store/core/stores/models/_product.py,sha256=yGVp3vl5rCReG4c8ogWy2NUcsTJeqCxP0tm6lQyYx7Y,10928
107
+ ob_dj_store/core/stores/models/_store.py,sha256=g7QXKbZI886Pdw6smuheVep7Vh1ke42PEhJhnxW4byA,7334
108
+ ob_dj_store/core/stores/models/_wallet.py,sha256=4LUyWPlgRedthWij-Nkn4diYvWHU755IOBLgpjZOyQ8,3679
105
109
  ob_dj_store/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
- ob_dj_store/utils/helpers.py,sha256=5zR395i-hTxEvgCscQEm2ueLB1ZTRpeY5faGPJI673M,1019
110
+ ob_dj_store/utils/helpers.py,sha256=02igVH2DSDTZYa6kFSTmBnJeXfTdgjRCRSXQ7mvmCGo,1224
107
111
  ob_dj_store/utils/model.py,sha256=DV7hOhTaZL3gh9sptts2jTUFlTArKG3i7oPioq9HLFE,303
108
- ob_dj_store/utils/utils.py,sha256=nHIa7oIIuZee3Vlf3UDcUU_myR3-UuFZ_zgo822Cy5I,1200
109
- ob_dj_store-0.0.11.8.dist-info/METADATA,sha256=DwF5WSq53xrMpj10Ir0-V14xZionUWPQhQartef6rIA,2827
110
- ob_dj_store-0.0.11.8.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
111
- ob_dj_store-0.0.11.8.dist-info/top_level.txt,sha256=CZG3G0ptTkzGnc0dFYN-ZD7YKdJBmm47bsmGwofD_lk,12
112
- ob_dj_store-0.0.11.8.dist-info/RECORD,,
112
+ ob_dj_store/utils/utils.py,sha256=euWeNI39P48jGNwKoBCC5AmwQUmgDeb92t0fQtPm-5g,1282
113
+ ob_dj_store-0.0.11.10.dist-info/METADATA,sha256=pjF7LcR9ltkOqvsxXXp5ZGjaJRR47kGrO_SyxVtGrZ4,2828
114
+ ob_dj_store-0.0.11.10.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
115
+ ob_dj_store-0.0.11.10.dist-info/top_level.txt,sha256=CZG3G0ptTkzGnc0dFYN-ZD7YKdJBmm47bsmGwofD_lk,12
116
+ ob_dj_store-0.0.11.10.dist-info/RECORD,,