diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ee26a..fdb69be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - `GET /api/v1/cities/?search=` — search cities by name (`apps/locations/views.py`); previously no text search existed. - `GET /api/v1/neighborhoods/?search=` — search neighborhoods by neighborhood name or city name, alongside the existing `city` UUID filter (`apps/locations/views.py`). - `GET /api/v1/stores/` now returns `phone_number` in the list (previously only on the single-store detail view) and a `distance_km` field, populated whenever `lat`/`lng` are passed (`apps/stores/serializers.py`). +- Store `latitude`/`longitude` now come back on every store response — list, retrieve, and the seller's own store (`GET`/`PATCH`/`POST /api/v1/seller/store/`) — derived from `Store.location` via new `Store.latitude`/`Store.longitude` properties (`apps/stores/models.py`, `apps/stores/serializers.py`). Previously they were write-only on the seller serializer and absent everywhere else. - 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 `CityViewSet`/`NeighborhoodViewSet` list filtering (`apps/locations/tests/test_location_list.py`) — previously untested. diff --git a/apps/stores/models.py b/apps/stores/models.py index 686483e..9014527 100644 --- a/apps/stores/models.py +++ b/apps/stores/models.py @@ -77,6 +77,14 @@ class Store(BaseModel): from django.conf import settings as dj_settings return dj_settings.DEFAULT_COMMISSION_PERCENT + @property + def latitude(self): + return self.location.y if self.location else None + + @property + def longitude(self): + return self.location.x if self.location else None + class StoreWorkingHours(BaseModel): class Weekday(models.IntegerChoices): diff --git a/apps/stores/serializers.py b/apps/stores/serializers.py index 26b2af7..4f1169f 100644 --- a/apps/stores/serializers.py +++ b/apps/stores/serializers.py @@ -32,13 +32,15 @@ class StoreListSerializer(serializers.ModelSerializer): logo_url = serializers.SerializerMethodField() cover_image_url = serializers.SerializerMethodField() distance_km = serializers.SerializerMethodField() + latitude = serializers.FloatField(read_only=True) + longitude = serializers.FloatField(read_only=True) class Meta: model = Store fields = ( 'uuid', 'name', 'category', 'logo', 'logo_url', 'cover_image', 'cover_image_url', 'phone_number', 'rating_avg', 'rating_count', 'min_order_amount', 'delivery_fee', - 'free_delivery_threshold', 'is_open', 'distance_km', + 'free_delivery_threshold', 'is_open', 'distance_km', 'latitude', 'longitude', ) @extend_schema_field(serializers.URLField(allow_null=True)) @@ -109,8 +111,8 @@ class SellerStoreSerializer(serializers.ModelSerializer): source='service_neighborhoods', queryset=Neighborhood.objects.filter(is_active=True), write_only=True, many=True, required=False, ) - latitude = serializers.FloatField(write_only=True, required=False) - longitude = serializers.FloatField(write_only=True, required=False) + latitude = serializers.FloatField(required=False) + longitude = serializers.FloatField(required=False) bank_account = StoreBankAccountSerializer(read_only=True) logo_url = serializers.SerializerMethodField() cover_image_url = serializers.SerializerMethodField() diff --git a/apps/stores/tests/test_seller_store.py b/apps/stores/tests/test_seller_store.py index c877dc6..7f7cf38 100644 --- a/apps/stores/tests/test_seller_store.py +++ b/apps/stores/tests/test_seller_store.py @@ -19,9 +19,13 @@ class SellerStoreTests(OrdersTestCase): 'category_uuid': str(self.category.uuid), 'city_uuid': str(self.city.uuid), 'address': 'خیابان ولیعصر', + 'latitude': 35.75, + 'longitude': 51.4, }) self.assertEqual(response.status_code, 201, response.data) self.assertTrue(Store.objects.filter(owner=self.new_seller).exists()) + self.assertAlmostEqual(response.data['latitude'], 35.75) + self.assertAlmostEqual(response.data['longitude'], 51.4) def test_seller_cannot_create_a_second_store(self): self.client.force_authenticate(user=self.seller1) diff --git a/apps/stores/tests/test_store_list.py b/apps/stores/tests/test_store_list.py index 11b61cf..bc1e325 100644 --- a/apps/stores/tests/test_store_list.py +++ b/apps/stores/tests/test_store_list.py @@ -48,3 +48,30 @@ class StoreListFilterTests(OrdersTestCase): store = next(s for s in response.data['results'] if s['uuid'] == str(self.store1.uuid)) self.assertIsNone(store['distance_km']) + + def test_latitude_longitude_included_in_list(self): + self.store1.location = Point(51.4, 35.75, srid=4326) + self.store1.save(update_fields=['location']) + + response = self.client.get('/api/v1/stores/') + + store = next(s for s in response.data['results'] if s['uuid'] == str(self.store1.uuid)) + self.assertAlmostEqual(store['latitude'], 35.75) + self.assertAlmostEqual(store['longitude'], 51.4) + + def test_latitude_longitude_null_without_location(self): + response = self.client.get('/api/v1/stores/') + + store = next(s for s in response.data['results'] if s['uuid'] == str(self.store1.uuid)) + self.assertIsNone(store['latitude']) + self.assertIsNone(store['longitude']) + + def test_latitude_longitude_included_in_retrieve(self): + self.store1.location = Point(51.4, 35.75, srid=4326) + self.store1.save(update_fields=['location']) + + response = self.client.get(f'/api/v1/stores/{self.store1.uuid}/') + + self.assertEqual(response.status_code, 200) + self.assertAlmostEqual(response.data['latitude'], 35.75) + self.assertAlmostEqual(response.data['longitude'], 51.4)