Skip to content

Commit

Permalink
feat(Locations): new type & website_url fields to start allowing ONLI…
Browse files Browse the repository at this point in the history
…NE stores (#446)
  • Loading branch information
raphodn authored Oct 6, 2024
1 parent ffe5e5a commit ce50c74
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 72 deletions.
2 changes: 1 addition & 1 deletion open_prices/api/locations/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ class LocationFilter(django_filters.FilterSet):

class Meta:
model = Location
fields = ["price_count"]
fields = ["type", "price_count"]
12 changes: 12 additions & 0 deletions open_prices/api/locations/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,15 @@ class LocationCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = Location.CREATE_FIELDS

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# with UniqueConstraints, DRF sets some fields as required
for field in (
Location.TYPE_OSM_MANDATORY_FIELDS + Location.TYPE_ONLINE_MANDATORY_FIELDS
):
self.fields[field].required = False

# with UniqueConstraints, DRF wrongly validates.
# Leave it to the model's save()
validators = []
52 changes: 39 additions & 13 deletions open_prices/api/locations/tests.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
from django.test import TestCase
from django.urls import reverse

from open_prices.locations import constants as location_constants
from open_prices.locations.factories import LocationFactory

LOCATION_NODE_652825274 = {
LOCATION_OSM_NODE_652825274 = {
"type": location_constants.TYPE_OSM,
"osm_id": 652825274,
"osm_type": "NODE",
"osm_name": "Monoprix",
"osm_lat": "45.1805534",
"osm_lon": "5.7153387",
"price_count": 15,
}
LOCATION_NODE_6509705997 = {
LOCATION_OSM_NODE_6509705997 = {
"type": location_constants.TYPE_OSM,
"osm_id": 6509705997,
"osm_type": "NODE",
"osm_name": "Carrefour",
"price_count": 0,
}
LOCATION_WAY_872934393 = {
LOCATION_OSM_WAY_872934393 = {
"type": location_constants.TYPE_OSM,
"osm_id": 872934393,
"osm_type": "WAY",
"osm_name": "Auchan",
"price_count": 50,
}
LOCATION_ONLINE_DECATHLON = {
"type": location_constants.TYPE_ONLINE,
"website_url": "https://www.decathlon.fr/",
"price_count": 15,
}


class LocationListApiTest(TestCase):
Expand Down Expand Up @@ -61,21 +70,30 @@ class LocationListFilterApiTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.url = reverse("api:locations-list")
LocationFactory(**LOCATION_NODE_652825274)
LocationFactory(**LOCATION_NODE_6509705997)
LocationFactory(**LOCATION_WAY_872934393)
LocationFactory(**LOCATION_OSM_NODE_652825274)
LocationFactory(**LOCATION_OSM_NODE_6509705997)
LocationFactory(**LOCATION_OSM_WAY_872934393)
LocationFactory(**LOCATION_ONLINE_DECATHLON)

def test_location_list_filter_by_osm_name(self):
url = self.url + "?osm_name__like=monop"
response = self.client.get(url)
self.assertEqual(response.data["total"], 1)
self.assertEqual(response.data["items"][0]["osm_name"], "Monoprix")

def test_location_list_filter_by_type(self):
url = self.url + "?type=ONLINE"
response = self.client.get(url)
self.assertEqual(response.data["total"], 1)
self.assertEqual(
response.data["items"][0]["type"], location_constants.TYPE_ONLINE
)

def test_location_list_filter_by_price_count(self):
# exact price_count
url = self.url + "?price_count=15"
response = self.client.get(url)
self.assertEqual(response.data["total"], 1)
self.assertEqual(response.data["total"], 1 + 1)
self.assertEqual(response.data["items"][0]["price_count"], 15)
# lte / gte
url = self.url + "?price_count__gte=20"
Expand All @@ -84,14 +102,14 @@ def test_location_list_filter_by_price_count(self):
self.assertEqual(response.data["items"][0]["price_count"], 50)
url = self.url + "?price_count__lte=20"
response = self.client.get(url)
self.assertEqual(response.data["total"], 2)
self.assertEqual(response.data["total"], 3)
self.assertEqual(response.data["items"][0]["price_count"], 15)


class LocationDetailApiTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.location = LocationFactory(**LOCATION_NODE_652825274)
cls.location = LocationFactory(**LOCATION_OSM_NODE_652825274)
cls.url = reverse("api:locations-detail", args=[cls.location.id])

def test_location_detail(self):
Expand Down Expand Up @@ -131,9 +149,9 @@ class LocationCreateApiTest(TestCase):
def setUpTestData(cls):
cls.url = reverse("api:locations-list")

def test_location_create(self):
def test_location_create_osm(self):
response = self.client.post(
self.url, LOCATION_NODE_652825274, content_type="application/json"
self.url, LOCATION_OSM_NODE_652825274, content_type="application/json"
)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data["osm_id"], 652825274)
Expand All @@ -143,11 +161,19 @@ def test_location_create(self):
) # ignored (and post_save signal disabled)
self.assertEqual(response.data["price_count"], 0) # ignored

def test_location_create_online(self):
response = self.client.post(
self.url, LOCATION_ONLINE_DECATHLON, content_type="application/json"
)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data["website_url"], "https://www.decathlon.fr/")
self.assertEqual(response.data["price_count"], 0) # ignored


class LocationUpdateApiTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.location = LocationFactory(**LOCATION_NODE_652825274)
cls.location = LocationFactory(**LOCATION_OSM_NODE_652825274)
cls.url = reverse("api:locations-detail", args=[cls.location.id])

def test_location_update_not_allowed(self):
Expand All @@ -159,7 +185,7 @@ def test_location_update_not_allowed(self):
class LocationDeleteApiTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.location = LocationFactory(**LOCATION_NODE_652825274)
cls.location = LocationFactory(**LOCATION_OSM_NODE_652825274)
cls.url = reverse("api:locations-detail", args=[cls.location.id])

def test_location_delete_not_allowed(self):
Expand Down
12 changes: 12 additions & 0 deletions open_prices/locations/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
TYPE_OSM = "OSM"
TYPE_ONLINE = "ONLINE"
TYPE_LIST = [TYPE_OSM, TYPE_ONLINE]
TYPE_CHOICES = [(key, key) for key in TYPE_LIST]

OSM_TYPE_NODE = "NODE"
OSM_TYPE_WAY = "WAY"
OSM_TYPE_RELATION = "RELATION"
Expand All @@ -10,3 +15,10 @@
OSM_ID_NOT_OK_LIST = [-5, 0, "test", None, "None", True, "true", False, "false"]
OSM_TYPE_OK_LIST = [OSM_TYPE_NODE, OSM_TYPE_WAY]
OSM_TYPE_NOT_OK_LIST = ["way", "W", "test", None, "None"]

WEBSITE_URL_OK_LIST = [
"https://www.decathlon.fr/",
"https://www.alltricks.fr",
"www.ekosport.fr/",
"www.auvieuxcampeur.fr",
]
38 changes: 33 additions & 5 deletions open_prices/locations/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,39 @@ class LocationFactory(DjangoModelFactory):
class Meta:
model = Location

osm_id = factory.Sequence(lambda x: random.randrange(0, 10000000000))
osm_type = factory.fuzzy.FuzzyChoice(location_constants.OSM_TYPE_LIST)
osm_name = factory.Faker("name")
osm_address_country = factory.Faker("country")
# price_count = factory.LazyAttribute(lambda x: random.randrange(0, 100))
class Params:
osm_name_faker = factory.Faker("name")
osm_address_country_faker = factory.Faker("country")
website_url_faker = factory.Faker("uri")

type = location_constants.TYPE_OSM # random.choice(location_constants.TYPE_LIST)

osm_id = factory.LazyAttributeSequence(
lambda x, y: random.randrange(0, 10000000000)
if x.type == location_constants.TYPE_OSM
else None
)
osm_type = factory.LazyAttribute(
lambda x: random.choice(location_constants.OSM_TYPE_LIST)
if x.type == location_constants.TYPE_OSM
else None
)
osm_name = factory.LazyAttribute(
lambda x: x.osm_name_faker if x.type == location_constants.TYPE_OSM else None
)
osm_address_country = factory.LazyAttribute(
lambda x: x.osm_address_country_faker
if x.type == location_constants.TYPE_OSM
else None
)

website_url = factory.LazyAttribute(
lambda x: x.website_url_faker
if x.type == location_constants.TYPE_ONLINE
else None
)

# price_count = factory.LazyAttribute(lambda l: random.randrange(0, 100))
# user_count = factory.LazyAttribute(lambda x: random.randrange(0, 100))
# product_count = factory.LazyAttribute(lambda x: random.randrange(0, 100))
# proof_count = factory.LazyAttribute(lambda x: random.randrange(0, 100))
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Generated by Django 5.1 on 2024-09-13 14:26

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("locations", "0004_location_user_count"),
]

operations = [
migrations.AddField(
model_name="location",
name="type",
field=models.CharField(
choices=[("OSM", "OSM"), ("ONLINE", "ONLINE")],
default="OSM",
max_length=20,
),
preserve_default=False,
),
migrations.AddField(
model_name="location",
name="website_url",
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name="location",
name="osm_id",
field=models.PositiveBigIntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name="location",
name="osm_type",
field=models.CharField(
blank=True,
choices=[("NODE", "NODE"), ("WAY", "WAY"), ("RELATION", "RELATION")],
max_length=10,
null=True,
),
),
migrations.AlterUniqueTogether(
name="location",
unique_together=set(),
),
migrations.AddConstraint(
model_name="location",
constraint=models.UniqueConstraint(
condition=models.Q(("type", "OSM")),
fields=("osm_id", "osm_type"),
name="unique_osm_constraint",
),
),
migrations.AddConstraint(
model_name="location",
constraint=models.UniqueConstraint(
condition=models.Q(("type", "ONLINE")),
fields=("website_url",),
name="unique_online_constraint",
),
),
]
Loading

0 comments on commit ce50c74

Please sign in to comment.