Merge pull request 'Return store latitude/longitude in every store response' (#7) from feature/lat-long into master

Reviewed-on: #7
This commit is contained in:
Ghasemi 2026-08-31 08:51:01 -04:00
commit dcf63952b5
5 changed files with 45 additions and 3 deletions

View file

@ -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.

View file

@ -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):

View file

@ -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()

View file

@ -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)

View file

@ -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)