Compare commits
No commits in common. "ea9474633b9d699615b3b446596a6e247e14735a" and "dcf63952b57dca3857b69a0b178b8b82ddc7df8d" have entirely different histories.
ea9474633b
...
dcf63952b5
8 changed files with 0 additions and 61 deletions
|
|
@ -15,8 +15,6 @@
|
||||||
- Test coverage for `StoreViewSet` list filtering (`apps/stores/tests/test_store_list.py`) — previously untested.
|
- Test coverage for `StoreViewSet` list filtering (`apps/stores/tests/test_store_list.py`) — previously untested.
|
||||||
- Test coverage for `ProductViewSet` list filtering (`apps/catalog/tests/test_product_list.py`) — previously untested.
|
- Test coverage for `ProductViewSet` list filtering (`apps/catalog/tests/test_product_list.py`) — previously untested.
|
||||||
- Test coverage for `CityViewSet`/`NeighborhoodViewSet` list filtering (`apps/locations/tests/test_location_list.py`) — previously untested.
|
- Test coverage for `CityViewSet`/`NeighborhoodViewSet` list filtering (`apps/locations/tests/test_location_list.py`) — previously untested.
|
||||||
- New `Store.max_delivery_time_minutes` field, set by the seller at registration (default 60), returned wherever a store appears in a response — store list/detail, the seller's own store, cart groups (`GET /api/v1/cart/`), and order/order-group serializers — so the checkout page's estimated-delivery-time UI no longer needs a frontend mock (`apps/stores/models.py`, `apps/stores/serializers.py`, migration `0003_store_max_delivery_time_minutes`).
|
|
||||||
- Documented `CartSerializer`'s `groups`/`grand_total` fields for swagger (`apps/orders/serializers.py`) — they were `SerializerMethodField`s with no schema, showing as opaque strings instead of the actual nested structure.
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Replaced hand-rolled `get_queryset` filtering with `django-filter` `FilterSet` classes (`apps/{stores,catalog,locations,reviews}/filters.py`) and `filterset_fields` (`apps/orders` status). `django-filter` was already installed and set as `DEFAULT_FILTER_BACKENDS` but unused everywhere; params are now auto-documented in swagger by drf-spectacular's django-filter integration, so the manual `OpenApiParameter` declarations for those fields were removed (stores keeps `lat`/`lng` manual since geo-distance isn't a plain filter).
|
- Replaced hand-rolled `get_queryset` filtering with `django-filter` `FilterSet` classes (`apps/{stores,catalog,locations,reviews}/filters.py`) and `filterset_fields` (`apps/orders` status). `django-filter` was already installed and set as `DEFAULT_FILTER_BACKENDS` but unused everywhere; params are now auto-documented in swagger by drf-spectacular's django-filter integration, so the manual `OpenApiParameter` declarations for those fields were removed (stores keeps `lat`/`lng` manual since geo-distance isn't a plain filter).
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from drf_spectacular.utils import extend_schema_field
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from apps.catalog.models import Product
|
from apps.catalog.models import Product
|
||||||
|
|
@ -48,11 +47,9 @@ class CartSerializer(serializers.Serializer):
|
||||||
self._groups_cache = build_cart_groups(cart)
|
self._groups_cache = build_cart_groups(cart)
|
||||||
return self._groups_cache
|
return self._groups_cache
|
||||||
|
|
||||||
@extend_schema_field(StoreCartGroupSerializer(many=True))
|
|
||||||
def get_groups(self, cart):
|
def get_groups(self, cart):
|
||||||
return StoreCartGroupSerializer(self._build_groups(cart), many=True, context=self.context).data
|
return StoreCartGroupSerializer(self._build_groups(cart), many=True, context=self.context).data
|
||||||
|
|
||||||
@extend_schema_field(serializers.IntegerField())
|
|
||||||
def get_grand_total(self, cart):
|
def get_grand_total(self, cart):
|
||||||
return sum(group['total'] for group in self._build_groups(cart))
|
return sum(group['total'] for group in self._build_groups(cart))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,6 @@ class CartTests(OrdersTestCase):
|
||||||
self.assertEqual(len(response.data['groups']), 2)
|
self.assertEqual(len(response.data['groups']), 2)
|
||||||
self.assertEqual(response.data['grand_total'], (28_500 * 2 + 15_000) + (85_000 + 20_000))
|
self.assertEqual(response.data['grand_total'], (28_500 * 2 + 15_000) + (85_000 + 20_000))
|
||||||
|
|
||||||
def test_cart_group_includes_store_max_delivery_time(self):
|
|
||||||
self.store1.max_delivery_time_minutes = 35
|
|
||||||
self.store1.save(update_fields=['max_delivery_time_minutes'])
|
|
||||||
self.add_to_cart(self.product1, quantity=1)
|
|
||||||
|
|
||||||
response = self.client.get('/api/v1/cart/')
|
|
||||||
|
|
||||||
self.assertEqual(response.data['groups'][0]['store']['max_delivery_time_minutes'], 35)
|
|
||||||
|
|
||||||
def test_adding_same_product_twice_increments_quantity(self):
|
def test_adding_same_product_twice_increments_quantity(self):
|
||||||
self.add_to_cart(self.product1, quantity=1)
|
self.add_to_cart(self.product1, quantity=1)
|
||||||
self.add_to_cart(self.product1, quantity=2)
|
self.add_to_cart(self.product1, quantity=2)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 6.0.2 on 2026-09-02 10:40
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stores', '0002_alter_store_cover_image_alter_store_logo_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='store',
|
|
||||||
name='max_delivery_time_minutes',
|
|
||||||
field=models.PositiveSmallIntegerField(default=60, help_text='حداکثر زمان ارسال (دقیقه)، هنگام ثبت فروشگاه توسط فروشنده مشخص می\u200cشود.'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -43,9 +43,6 @@ class Store(BaseModel):
|
||||||
help_text='محلههایی که این فروشگاه به آنها ارسال دارد.',
|
help_text='محلههایی که این فروشگاه به آنها ارسال دارد.',
|
||||||
)
|
)
|
||||||
delivery_radius_km = models.DecimalField(max_digits=5, decimal_places=2, default=5)
|
delivery_radius_km = models.DecimalField(max_digits=5, decimal_places=2, default=5)
|
||||||
max_delivery_time_minutes = models.PositiveSmallIntegerField(
|
|
||||||
default=60, help_text='حداکثر زمان ارسال (دقیقه)، هنگام ثبت فروشگاه توسط فروشنده مشخص میشود.',
|
|
||||||
)
|
|
||||||
|
|
||||||
min_order_amount = models.PositiveBigIntegerField(default=0)
|
min_order_amount = models.PositiveBigIntegerField(default=0)
|
||||||
delivery_fee = models.PositiveBigIntegerField(default=0)
|
delivery_fee = models.PositiveBigIntegerField(default=0)
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ class StoreListSerializer(serializers.ModelSerializer):
|
||||||
'uuid', 'name', 'category', 'logo', 'logo_url', 'cover_image', 'cover_image_url',
|
'uuid', 'name', 'category', 'logo', 'logo_url', 'cover_image', 'cover_image_url',
|
||||||
'phone_number', 'rating_avg', 'rating_count', 'min_order_amount', 'delivery_fee',
|
'phone_number', 'rating_avg', 'rating_count', 'min_order_amount', 'delivery_fee',
|
||||||
'free_delivery_threshold', 'is_open', 'distance_km', 'latitude', 'longitude',
|
'free_delivery_threshold', 'is_open', 'distance_km', 'latitude', 'longitude',
|
||||||
'max_delivery_time_minutes',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@extend_schema_field(serializers.URLField(allow_null=True))
|
@extend_schema_field(serializers.URLField(allow_null=True))
|
||||||
|
|
@ -125,7 +124,6 @@ class SellerStoreSerializer(serializers.ModelSerializer):
|
||||||
'cover_image', 'cover_image_url',
|
'cover_image', 'cover_image_url',
|
||||||
'phone_number', 'city', 'city_uuid', 'address', 'latitude', 'longitude',
|
'phone_number', 'city', 'city_uuid', 'address', 'latitude', 'longitude',
|
||||||
'service_neighborhoods', 'service_neighborhood_uuids', 'delivery_radius_km',
|
'service_neighborhoods', 'service_neighborhood_uuids', 'delivery_radius_km',
|
||||||
'max_delivery_time_minutes',
|
|
||||||
'min_order_amount', 'delivery_fee', 'free_delivery_threshold',
|
'min_order_amount', 'delivery_fee', 'free_delivery_threshold',
|
||||||
'accepts_wallet', 'accepts_online', 'accepts_cash_on_delivery',
|
'accepts_wallet', 'accepts_online', 'accepts_cash_on_delivery',
|
||||||
'rating_avg', 'rating_count', 'status', 'is_open', 'bank_account', 'created_at',
|
'rating_avg', 'rating_count', 'status', 'is_open', 'bank_account', 'created_at',
|
||||||
|
|
|
||||||
|
|
@ -21,24 +21,11 @@ class SellerStoreTests(OrdersTestCase):
|
||||||
'address': 'خیابان ولیعصر',
|
'address': 'خیابان ولیعصر',
|
||||||
'latitude': 35.75,
|
'latitude': 35.75,
|
||||||
'longitude': 51.4,
|
'longitude': 51.4,
|
||||||
'max_delivery_time_minutes': 40,
|
|
||||||
})
|
})
|
||||||
self.assertEqual(response.status_code, 201, response.data)
|
self.assertEqual(response.status_code, 201, response.data)
|
||||||
self.assertTrue(Store.objects.filter(owner=self.new_seller).exists())
|
self.assertTrue(Store.objects.filter(owner=self.new_seller).exists())
|
||||||
self.assertAlmostEqual(response.data['latitude'], 35.75)
|
self.assertAlmostEqual(response.data['latitude'], 35.75)
|
||||||
self.assertAlmostEqual(response.data['longitude'], 51.4)
|
self.assertAlmostEqual(response.data['longitude'], 51.4)
|
||||||
self.assertEqual(response.data['max_delivery_time_minutes'], 40)
|
|
||||||
|
|
||||||
def test_max_delivery_time_minutes_defaults_when_not_provided(self):
|
|
||||||
self.client.force_authenticate(user=self.new_seller)
|
|
||||||
response = self.client.post('/api/v1/seller/store/', {
|
|
||||||
'name': 'کافه من',
|
|
||||||
'category_uuid': str(self.category.uuid),
|
|
||||||
'city_uuid': str(self.city.uuid),
|
|
||||||
'address': 'خیابان ولیعصر',
|
|
||||||
})
|
|
||||||
self.assertEqual(response.status_code, 201, response.data)
|
|
||||||
self.assertEqual(response.data['max_delivery_time_minutes'], 60)
|
|
||||||
|
|
||||||
def test_seller_cannot_create_a_second_store(self):
|
def test_seller_cannot_create_a_second_store(self):
|
||||||
self.client.force_authenticate(user=self.seller1)
|
self.client.force_authenticate(user=self.seller1)
|
||||||
|
|
|
||||||
|
|
@ -75,14 +75,3 @@ class StoreListFilterTests(OrdersTestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertAlmostEqual(response.data['latitude'], 35.75)
|
self.assertAlmostEqual(response.data['latitude'], 35.75)
|
||||||
self.assertAlmostEqual(response.data['longitude'], 51.4)
|
self.assertAlmostEqual(response.data['longitude'], 51.4)
|
||||||
|
|
||||||
def test_max_delivery_time_minutes_included_in_list_and_retrieve(self):
|
|
||||||
self.store1.max_delivery_time_minutes = 45
|
|
||||||
self.store1.save(update_fields=['max_delivery_time_minutes'])
|
|
||||||
|
|
||||||
list_response = self.client.get('/api/v1/stores/')
|
|
||||||
store = next(s for s in list_response.data['results'] if s['uuid'] == str(self.store1.uuid))
|
|
||||||
self.assertEqual(store['max_delivery_time_minutes'], 45)
|
|
||||||
|
|
||||||
detail_response = self.client.get(f'/api/v1/stores/{self.store1.uuid}/')
|
|
||||||
self.assertEqual(detail_response.data['max_delivery_time_minutes'], 45)
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue