mirror of
https://github.com/task-17-lct/backend.git
synced 2024-11-24 01:43:45 +03:00
added attractions info, route save, user favorites
This commit is contained in:
parent
4f7185fd6b
commit
19134f6cd8
|
@ -1,7 +1,11 @@
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from passfinder.recomendations.api.views import TinderView, PersonalRecommendation
|
from passfinder.recomendations.api.views import TinderView, PersonalRecommendation
|
||||||
from passfinder.users.api.views import UserViewSet, CreateUserPreferenceApiView
|
from passfinder.users.api.views import (
|
||||||
|
UserViewSet,
|
||||||
|
CreateUserPreferenceApiView,
|
||||||
|
ListUserFavoritePointsApiView,
|
||||||
|
)
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
|
|
||||||
|
@ -14,5 +18,6 @@
|
||||||
path("", include("passfinder.events.api.urls")),
|
path("", include("passfinder.events.api.urls")),
|
||||||
path("auth/", include("passfinder.users.api.urls")),
|
path("auth/", include("passfinder.users.api.urls")),
|
||||||
path("user/preference", CreateUserPreferenceApiView.as_view()),
|
path("user/preference", CreateUserPreferenceApiView.as_view()),
|
||||||
|
path("user/favorite", ListUserFavoritePointsApiView.as_view()),
|
||||||
]
|
]
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls
|
||||||
|
|
|
@ -291,7 +291,7 @@
|
||||||
CELERY_REDIS_DB = env("CELERY_REDIS_DB", default=0)
|
CELERY_REDIS_DB = env("CELERY_REDIS_DB", default=0)
|
||||||
|
|
||||||
CELERY_REDIS_SSL = env.bool("CELERY_REDIS_SSL", default=False)
|
CELERY_REDIS_SSL = env.bool("CELERY_REDIS_SSL", default=False)
|
||||||
CELERY_BROKER_URL = env("CELERY_BROKER_URL", default='')
|
CELERY_BROKER_URL = env("CELERY_BROKER_URL", default="")
|
||||||
CELERY_TASK_SERIALIZER = "json"
|
CELERY_TASK_SERIALIZER = "json"
|
||||||
CELERY_ACCEPT_CONTENT = ["application/json"]
|
CELERY_ACCEPT_CONTENT = ["application/json"]
|
||||||
CELERY_ENABLE_UTC = True
|
CELERY_ENABLE_UTC = True
|
||||||
|
@ -301,7 +301,6 @@
|
||||||
"task": "django_clickhouse.tasks.clickhouse_auto_sync",
|
"task": "django_clickhouse.tasks.clickhouse_auto_sync",
|
||||||
"schedule": timedelta(seconds=5),
|
"schedule": timedelta(seconds=5),
|
||||||
"options": {"expires": 1},
|
"options": {"expires": 1},
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
# DRF
|
# DRF
|
||||||
|
@ -346,7 +345,7 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
CLICKHOUSE_DATABASES = {
|
CLICKHOUSE_DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"db_url": env("CLICKHOUSE_URL", default="http://akarpov.ru:1337"),
|
"db_url": env("CLICKHOUSE_URL", default="http://localhost:8123"),
|
||||||
"db_name": env("CLICKHOUSE_DB", default="default"),
|
"db_name": env("CLICKHOUSE_DB", default="default"),
|
||||||
"username": env("CLICKHOUSE_USER", default="default"),
|
"username": env("CLICKHOUSE_USER", default="default"),
|
||||||
"password": env("CLICKHOUSE_PASSWORD", default="default"),
|
"password": env("CLICKHOUSE_PASSWORD", default="default"),
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import json
|
import json
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
from passfinder.events.models import Region
|
from passfinder.events.models import Region, Event
|
||||||
|
|
||||||
with open("data/pos-attr.json") as f:
|
with open("data/pos-attr.json") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
|
|
||||||
reggg = {
|
reggg = {
|
||||||
"г. Санкт-Петербург": "Санкт-Петербург",
|
"г. Санкт-Петербург": "Санкт-Петербург",
|
||||||
"г. Москва": "Москва",
|
"г. Москва": "Москва",
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
"Республика Северная Осетия - Алания": "Республика Северная Осетия – Алания",
|
"Республика Северная Осетия - Алания": "Республика Северная Осетия – Алания",
|
||||||
"Ханты-Мансийский автономный округ - Югра": "Ханты-Мансийский автономный округ — Югра",
|
"Ханты-Мансийский автономный округ - Югра": "Ханты-Мансийский автономный округ — Югра",
|
||||||
}
|
}
|
||||||
|
rett = []
|
||||||
ret = []
|
ret = []
|
||||||
for infff in data:
|
for infff in data:
|
||||||
info = infff["general"]
|
info = infff["general"]
|
||||||
|
@ -38,6 +39,21 @@
|
||||||
}
|
}
|
||||||
if "typologies" in info:
|
if "typologies" in info:
|
||||||
res["extra_kwargs"]["typologies"] = [x["value"] for x in info["typologies"]]
|
res["extra_kwargs"]["typologies"] = [x["value"] for x in info["typologies"]]
|
||||||
|
if "securityInfo" in info or "borderInfo" in info:
|
||||||
|
for ev in Event.objects.filter(
|
||||||
|
title=info["name"],
|
||||||
|
address=res["address"],
|
||||||
|
lat=res["lat"],
|
||||||
|
lon=res["lon"],
|
||||||
|
):
|
||||||
|
ddd = [
|
||||||
|
info["securityInfo"] if "securityInfo" in info else "",
|
||||||
|
info["borderInfo"] if "borderInfo" in info else "",
|
||||||
|
]
|
||||||
|
ddd = [x for x in ddd if x]
|
||||||
|
ev.description = " ".join(ddd)
|
||||||
|
ev.save()
|
||||||
|
|
||||||
ret.append(res)
|
ret.append(res)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,6 @@ class Meta:
|
||||||
exclude = "hotel"
|
exclude = "hotel"
|
||||||
|
|
||||||
|
|
||||||
class CitySerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = City
|
|
||||||
|
|
||||||
|
|
||||||
class HotelSerializer(serializers.ModelSerializer):
|
class HotelSerializer(serializers.ModelSerializer):
|
||||||
phones = HotelPhoneSerializer(many=True)
|
phones = HotelPhoneSerializer(many=True)
|
||||||
source = serializers.CharField(source="parser_source")
|
source = serializers.CharField(source="parser_source")
|
||||||
|
@ -41,11 +36,12 @@ class Meta:
|
||||||
class PointSerializer(serializers.ModelSerializer):
|
class PointSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BasePoint
|
model = BasePoint
|
||||||
fields = ["title", "description", "location", "icon"]
|
fields = ["oid", "title", "description", "location", "icon"]
|
||||||
|
|
||||||
|
|
||||||
class RouteSerializer(serializers.Serializer):
|
class RouteSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField()
|
name = serializers.CharField()
|
||||||
|
date = serializers.DateField(allow_null=True)
|
||||||
description = serializers.CharField()
|
description = serializers.CharField()
|
||||||
points = serializers.ListSerializer(child=PointSerializer())
|
points = serializers.ListSerializer(child=PointSerializer())
|
||||||
|
|
||||||
|
@ -54,11 +50,59 @@ class RouteInputSerializer(serializers.Serializer):
|
||||||
date_from = serializers.DateField(required=False, allow_null=True)
|
date_from = serializers.DateField(required=False, allow_null=True)
|
||||||
date_to = serializers.DateField(required=False, allow_null=True)
|
date_to = serializers.DateField(required=False, allow_null=True)
|
||||||
region = serializers.CharField(
|
region = serializers.CharField(
|
||||||
min_length=24, max_length=24, required=False, allow_blank=True
|
min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CitySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = City
|
||||||
|
fields = ["oid", "title"]
|
||||||
|
|
||||||
|
|
||||||
class RegionSerializer(serializers.ModelSerializer):
|
class RegionSerializer(serializers.ModelSerializer):
|
||||||
|
cities = CitySerializer(many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Region
|
model = Region
|
||||||
fields = ["oid", "title", "description_short"]
|
fields = ["oid", "title", "description_short", "cities"]
|
||||||
|
|
||||||
|
|
||||||
|
class InputRoutePointSerializer(serializers.Serializer):
|
||||||
|
type = serializers.ChoiceField(choices=["point", "transition"])
|
||||||
|
time = serializers.IntegerField(min_value=0, required=True)
|
||||||
|
|
||||||
|
# point
|
||||||
|
point = serializers.CharField(
|
||||||
|
min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# transition
|
||||||
|
point_from = serializers.CharField(
|
||||||
|
min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True
|
||||||
|
)
|
||||||
|
point_to = serializers.CharField(
|
||||||
|
min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True
|
||||||
|
)
|
||||||
|
distance = serializers.FloatField(min_value=0, required=False, allow_null=True)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
if data["type"] == "point":
|
||||||
|
if "point" not in data or not data["point"]:
|
||||||
|
raise serializers.ValidationError("Point id is required")
|
||||||
|
get_object_or_404(BasePoint, oid=data["point"])
|
||||||
|
else:
|
||||||
|
if "point_to" not in data or not data["point_to"]:
|
||||||
|
raise serializers.ValidationError("Point to id is required")
|
||||||
|
get_object_or_404(BasePoint, oid=data["point_to"])
|
||||||
|
if "point_from" not in data or not data["point_from"]:
|
||||||
|
raise serializers.ValidationError("Point from id is required")
|
||||||
|
get_object_or_404(BasePoint, oid=data["point_from"])
|
||||||
|
if "distance" not in data or not data["distance"]:
|
||||||
|
raise serializers.ValidationError("Distance is required")
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class InputRouteSerializer(serializers.Serializer):
|
||||||
|
points = serializers.ListSerializer(child=InputRoutePointSerializer())
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from passfinder.events.api.views import BuildRouteApiView, ListRegionApiView
|
from passfinder.events.api.views import (
|
||||||
|
BuildRouteApiView,
|
||||||
|
ListRegionApiView,
|
||||||
|
ListCityApiView,
|
||||||
|
SaveRouteSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
app_name = "events"
|
app_name = "events"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("route/build", BuildRouteApiView.as_view(), name="build_route"),
|
path("route/build", BuildRouteApiView.as_view(), name="build_route"),
|
||||||
path("regions", ListRegionApiView.as_view(), name="regions"),
|
path("route/save", SaveRouteSerializer.as_view(), name="save_route"),
|
||||||
|
path("data/regions", ListRegionApiView.as_view(), name="regions"),
|
||||||
|
path("data/cities", ListCityApiView.as_view(), name="cities"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,8 +7,17 @@
|
||||||
RouteSerializer,
|
RouteSerializer,
|
||||||
RegionSerializer,
|
RegionSerializer,
|
||||||
RouteInputSerializer,
|
RouteInputSerializer,
|
||||||
|
CitySerializer,
|
||||||
|
InputRouteSerializer,
|
||||||
|
)
|
||||||
|
from passfinder.events.models import (
|
||||||
|
BasePoint,
|
||||||
|
Region,
|
||||||
|
City,
|
||||||
|
UserRoute,
|
||||||
|
UserRoutePoint,
|
||||||
|
UserRouteTransaction,
|
||||||
)
|
)
|
||||||
from passfinder.events.models import BasePoint, Region
|
|
||||||
|
|
||||||
|
|
||||||
class BuildRouteApiView(GenericAPIView):
|
class BuildRouteApiView(GenericAPIView):
|
||||||
|
@ -21,6 +30,7 @@ def get(self, request):
|
||||||
routes.append(
|
routes.append(
|
||||||
{
|
{
|
||||||
"name": "bebra",
|
"name": "bebra",
|
||||||
|
"date": None,
|
||||||
"description": "bebra bebra bebra",
|
"description": "bebra bebra bebra",
|
||||||
"points": PointSerializer(many=True).to_representation(
|
"points": PointSerializer(many=True).to_representation(
|
||||||
BasePoint.objects.order_by("?")[:10]
|
BasePoint.objects.order_by("?")[:10]
|
||||||
|
@ -35,7 +45,8 @@ def get(self, request):
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = RouteInputSerializer(data=request.data)
|
serializer = RouteInputSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
region = serializer.data["region"]
|
data = serializer.data
|
||||||
|
region = data["region"]
|
||||||
routes = []
|
routes = []
|
||||||
if region:
|
if region:
|
||||||
region = get_object_or_404(Region, oid=region)
|
region = get_object_or_404(Region, oid=region)
|
||||||
|
@ -43,6 +54,7 @@ def post(self, request):
|
||||||
routes.append(
|
routes.append(
|
||||||
{
|
{
|
||||||
"name": "bebra",
|
"name": "bebra",
|
||||||
|
"date": data["date_from"],
|
||||||
"description": "bebra bebra bebra",
|
"description": "bebra bebra bebra",
|
||||||
"points": PointSerializer(many=True).to_representation(
|
"points": PointSerializer(many=True).to_representation(
|
||||||
region.points.all().order_by("?")[:10]
|
region.points.all().order_by("?")[:10]
|
||||||
|
@ -54,6 +66,7 @@ def post(self, request):
|
||||||
routes.append(
|
routes.append(
|
||||||
{
|
{
|
||||||
"name": "bebra",
|
"name": "bebra",
|
||||||
|
"date": data["date_from"],
|
||||||
"description": "bebra bebra bebra",
|
"description": "bebra bebra bebra",
|
||||||
"points": PointSerializer(many=True).to_representation(
|
"points": PointSerializer(many=True).to_representation(
|
||||||
BasePoint.objects.order_by("?")[:10]
|
BasePoint.objects.order_by("?")[:10]
|
||||||
|
@ -66,3 +79,31 @@ def post(self, request):
|
||||||
class ListRegionApiView(ListAPIView):
|
class ListRegionApiView(ListAPIView):
|
||||||
serializer_class = RegionSerializer
|
serializer_class = RegionSerializer
|
||||||
queryset = Region.objects.all()
|
queryset = Region.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ListCityApiView(ListAPIView):
|
||||||
|
serializer_class = CitySerializer
|
||||||
|
queryset = City.objects.all().order_by("title")
|
||||||
|
|
||||||
|
|
||||||
|
class SaveRouteSerializer(GenericAPIView):
|
||||||
|
serializer_class = InputRouteSerializer
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
serializer = InputRouteSerializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
data = serializer.data
|
||||||
|
route = UserRoute.objects.create(user=self.request.user)
|
||||||
|
for point in data["points"]:
|
||||||
|
if point["type"] == "point":
|
||||||
|
UserRoutePoint.objects.create(
|
||||||
|
route=route, point=BasePoint.objects.get(oid=point["point"])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
UserRouteTransaction.objects.create(
|
||||||
|
route=route,
|
||||||
|
point_from=BasePoint.objects.get(oid=point["point_from"]),
|
||||||
|
point_to=BasePoint.objects.get(oid=point["point_to"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(data=data)
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
# Generated by Django 4.2.1 on 2023-05-23 20:34
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("events", "0017_event_address_event_extra_kwargs_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="UserRoute",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="routes",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="BaseUserRoutePoint",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"polymorphic_ctype",
|
||||||
|
models.ForeignKey(
|
||||||
|
editable=False,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="polymorphic_%(app_label)s.%(class)s_set+",
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"route",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="points",
|
||||||
|
to="events.userroute",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
"base_manager_name": "objects",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="UserRouteTransaction",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"baseuserroutepoint_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="events.baseuserroutepoint",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("distance", models.FloatField()),
|
||||||
|
(
|
||||||
|
"point_from",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="user_route_point_from",
|
||||||
|
to="events.basepoint",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"point_to",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="user_route_point_to",
|
||||||
|
to="events.basepoint",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
"base_manager_name": "objects",
|
||||||
|
},
|
||||||
|
bases=("events.baseuserroutepoint",),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="UserRoutePoint",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"baseuserroutepoint_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="events.baseuserroutepoint",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"point",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="events.basepoint",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
"base_manager_name": "objects",
|
||||||
|
},
|
||||||
|
bases=("events.baseuserroutepoint",),
|
||||||
|
),
|
||||||
|
]
|
|
@ -175,3 +175,35 @@ class Restaurant(BasePoint):
|
||||||
avg_time_visit = models.IntegerField(null=True)
|
avg_time_visit = models.IntegerField(null=True)
|
||||||
working_time = models.JSONField(null=True)
|
working_time = models.JSONField(null=True)
|
||||||
phones = ArrayField(models.CharField(max_length=18), null=True)
|
phones = ArrayField(models.CharField(max_length=18), null=True)
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoute(models.Model):
|
||||||
|
user = models.ForeignKey(
|
||||||
|
"users.User", related_name="routes", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user}'s route"
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserRoutePoint(PolymorphicModel):
|
||||||
|
route = models.ForeignKey(
|
||||||
|
"UserRoute", related_name="points", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoutePoint(BaseUserRoutePoint):
|
||||||
|
type = "point"
|
||||||
|
point = models.ForeignKey("BasePoint", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
class UserRouteTransaction(BaseUserRoutePoint):
|
||||||
|
type = "transition"
|
||||||
|
point_from = models.ForeignKey(
|
||||||
|
"BasePoint", related_name="user_route_point_from", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
point_to = models.ForeignKey(
|
||||||
|
"BasePoint", related_name="user_route_point_to", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
distance = models.FloatField()
|
||||||
|
|
|
@ -22,109 +22,146 @@ def get_nearest_(instance_model, model_type, mapping, rev_mapping, nearest_n, ml
|
||||||
for i in range(how_many):
|
for i in range(how_many):
|
||||||
try:
|
try:
|
||||||
res.append(Event.objects.get(oid=mapping[nearest[i]]))
|
res.append(Event.objects.get(oid=mapping[nearest[i]]))
|
||||||
except Event.DoesNotExist: ...
|
except Event.DoesNotExist:
|
||||||
if len(res) == nearest_n: break
|
...
|
||||||
|
if len(res) == nearest_n:
|
||||||
|
break
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def nearest_attraction(attraction, nearest_n):
|
def nearest_attraction(attraction, nearest_n):
|
||||||
return get_nearest_(attraction, 'attraction', attraction_mapping, rev_attraction_mapping, nearest_n, attracion_model)
|
return get_nearest_(
|
||||||
|
attraction,
|
||||||
|
"attraction",
|
||||||
|
attraction_mapping,
|
||||||
|
rev_attraction_mapping,
|
||||||
|
nearest_n,
|
||||||
|
attracion_model,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def nearest_movie(movie, nearest_n):
|
def nearest_movie(movie, nearest_n):
|
||||||
return get_nearest_(movie, 'movie', cinema_mapping, rev_cinema_mapping, nearest_n, cinema_model)
|
return get_nearest_(
|
||||||
|
movie, "movie", cinema_mapping, rev_cinema_mapping, nearest_n, cinema_model
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def nearest_plays(play, nearest_n):
|
def nearest_plays(play, nearest_n):
|
||||||
return get_nearest_(play, 'plays', plays_mapping, rev_plays_mapping, nearest_n, plays_model)
|
return get_nearest_(
|
||||||
|
play, "plays", plays_mapping, rev_plays_mapping, nearest_n, plays_model
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def nearest_excursion(excursion, nearest_n):
|
def nearest_excursion(excursion, nearest_n):
|
||||||
return get_nearest_(excursion, 'excursion', excursion_mapping, rev_excursion_mapping, nearest_n, excursion_model)
|
return get_nearest_(
|
||||||
|
excursion,
|
||||||
|
"excursion",
|
||||||
|
excursion_mapping,
|
||||||
|
rev_excursion_mapping,
|
||||||
|
nearest_n,
|
||||||
|
excursion_model,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def nearest_concert(concert, nearest_n):
|
def nearest_concert(concert, nearest_n):
|
||||||
return get_nearest_(concert, 'concert', concert_mapping, rev_concert_mapping, nearest_n, concert_model)
|
return get_nearest_(
|
||||||
|
concert,
|
||||||
|
"concert",
|
||||||
|
concert_mapping,
|
||||||
|
rev_concert_mapping,
|
||||||
|
nearest_n,
|
||||||
|
concert_model,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_nearest_event(event, nearest_n):
|
def get_nearest_event(event, nearest_n):
|
||||||
if event.type == 'plays':
|
if event.type == "plays":
|
||||||
return nearest_plays(event, nearest_n)
|
return nearest_plays(event, nearest_n)
|
||||||
if event.type == 'concert':
|
if event.type == "concert":
|
||||||
return nearest_concert(event, nearest_n)
|
return nearest_concert(event, nearest_n)
|
||||||
if event.type == 'movie':
|
if event.type == "movie":
|
||||||
return nearest_movie(event, nearest_n)
|
return nearest_movie(event, nearest_n)
|
||||||
|
|
||||||
|
|
||||||
def update_preferences_state(user, event, direction):
|
def update_preferences_state(user, event, direction):
|
||||||
pref = UserPreferences.objects.get(user=user)
|
pref = UserPreferences.objects.get(user=user)
|
||||||
|
|
||||||
if direction == 'left':
|
if direction == "left":
|
||||||
if event.type == 'plays':
|
if event.type == "plays":
|
||||||
pref.unpreffered_plays.add(event)
|
pref.unpreffered_plays.add(event)
|
||||||
if event.type == 'movie':
|
if event.type == "movie":
|
||||||
pref.unpreffered_movies.add(event)
|
pref.unpreffered_movies.add(event)
|
||||||
if event.type == 'concert':
|
if event.type == "concert":
|
||||||
pref.unpreferred_concerts.add(event)
|
pref.unpreferred_concerts.add(event)
|
||||||
else:
|
else:
|
||||||
if event.type == 'plays':
|
if event.type == "plays":
|
||||||
pref.preffered_plays.add(event)
|
pref.preffered_plays.add(event)
|
||||||
if event.type == 'movie':
|
if event.type == "movie":
|
||||||
pref.preffered_movies.add(event)
|
pref.preffered_movies.add(event)
|
||||||
if event.type == 'concert':
|
if event.type == "concert":
|
||||||
pref.preferred_concerts.add(event)
|
pref.preferred_concerts.add(event)
|
||||||
pref.save()
|
pref.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_next_tinder(user, prev_event, prev_direction):
|
def get_next_tinder(user, prev_event, prev_direction):
|
||||||
pref = UserPreferences.objects.get(user=user)
|
pref = UserPreferences.objects.get(user=user)
|
||||||
print(prev_event.type, len(pref.preferred_concerts.all()))
|
print(prev_event.type, len(pref.preferred_concerts.all()))
|
||||||
if prev_direction == 'left':
|
if prev_direction == "left":
|
||||||
if prev_event.type == 'plays' and len(pref.unpreffered_plays.all()) <= 2:
|
if prev_event.type == "plays" and len(pref.unpreffered_plays.all()) <= 2:
|
||||||
candidates = nearest_plays(prev_event, 100)
|
candidates = nearest_plays(prev_event, 100)
|
||||||
# print(candidates, type(candidates), len(Event.objects.filter(type='plays')))
|
# print(candidates, type(candidates), len(Event.objects.filter(type='plays')))
|
||||||
return candidates[-1]
|
return candidates[-1]
|
||||||
if prev_event.type == 'movie' and len(pref.unpreffered_movies.all()) <= 2:
|
if prev_event.type == "movie" and len(pref.unpreffered_movies.all()) <= 2:
|
||||||
candidates = nearest_movie(prev_event, 100)
|
candidates = nearest_movie(prev_event, 100)
|
||||||
return candidates[-1]
|
return candidates[-1]
|
||||||
if prev_event.type == 'concert' and len(pref.unpreferred_concerts.all()) <= 2:
|
if prev_event.type == "concert" and len(pref.unpreferred_concerts.all()) <= 2:
|
||||||
candidates = nearest_concert(prev_event, 100)
|
candidates = nearest_concert(prev_event, 100)
|
||||||
return candidates[-1]
|
return candidates[-1]
|
||||||
|
|
||||||
if prev_direction == 'right':
|
if prev_direction == "right":
|
||||||
if prev_event.type == 'plays' and len(pref.preffered_plays.all()) < 2:
|
if prev_event.type == "plays" and len(pref.preffered_plays.all()) < 2:
|
||||||
candidates = nearest_plays(prev_event, 2)
|
candidates = nearest_plays(prev_event, 2)
|
||||||
return candidates[1]
|
return candidates[1]
|
||||||
if prev_event.type == 'movie' and len(pref.preffered_movies.all()) < 2:
|
if prev_event.type == "movie" and len(pref.preffered_movies.all()) < 2:
|
||||||
candidates = nearest_movie(prev_event, 2)
|
candidates = nearest_movie(prev_event, 2)
|
||||||
return candidates[1]
|
return candidates[1]
|
||||||
if prev_event.type == 'concert' and len(pref.preferred_concerts.all()) < 2:
|
if prev_event.type == "concert" and len(pref.preferred_concerts.all()) < 2:
|
||||||
candidates = nearest_concert(prev_event, 2)
|
candidates = nearest_concert(prev_event, 2)
|
||||||
return candidates[1]
|
return candidates[1]
|
||||||
|
|
||||||
if prev_event.type == 'plays':
|
if prev_event.type == "plays":
|
||||||
if not len(pref.preffered_movies.all()) and not len(pref.unpreffered_movies.all()):
|
if not len(pref.preffered_movies.all()) and not len(
|
||||||
return choice(Event.objects.filter(type='movie'))
|
pref.unpreffered_movies.all()
|
||||||
if not len(pref.preferred_concerts.all()) and not len(pref.unpreferred_concerts.all()):
|
):
|
||||||
return choice(Event.objects.filter(type='concert'))
|
return choice(Event.objects.filter(type="movie"))
|
||||||
|
if not len(pref.preferred_concerts.all()) and not len(
|
||||||
|
pref.unpreferred_concerts.all()
|
||||||
|
):
|
||||||
|
return choice(Event.objects.filter(type="concert"))
|
||||||
|
|
||||||
if prev_event.type == 'movie':
|
if prev_event.type == "movie":
|
||||||
if not len(pref.preffered_plays.all()) and not len(pref.unpreffered_plays.all()):
|
if not len(pref.preffered_plays.all()) and not len(
|
||||||
return choice(Event.objects.filter(type='plays'))
|
pref.unpreffered_plays.all()
|
||||||
if not len(pref.preferred_concerts.all()) and not len(pref.unpreferred_concerts.all()):
|
):
|
||||||
return choice(Event.objects.filter(type='concert'))
|
return choice(Event.objects.filter(type="plays"))
|
||||||
|
if not len(pref.preferred_concerts.all()) and not len(
|
||||||
|
pref.unpreferred_concerts.all()
|
||||||
|
):
|
||||||
|
return choice(Event.objects.filter(type="concert"))
|
||||||
|
|
||||||
if prev_event.type == 'concert':
|
if prev_event.type == "concert":
|
||||||
if not len(pref.preffered_plays.all()) and not len(pref.unpreffered_plays.all()):
|
if not len(pref.preffered_plays.all()) and not len(
|
||||||
return choice(Event.objects.filter(type='plays'))
|
pref.unpreffered_plays.all()
|
||||||
if not len(pref.preffered_movies.all()) and not len(pref.unpreffered_movies.all()):
|
):
|
||||||
return choice(Event.objects.filter(type='movie'))
|
return choice(Event.objects.filter(type="plays"))
|
||||||
|
if not len(pref.preffered_movies.all()) and not len(
|
||||||
|
pref.unpreffered_movies.all()
|
||||||
|
):
|
||||||
|
return choice(Event.objects.filter(type="movie"))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def rank_candidates(candidates_list, negative_candidates_list):
|
def rank_candidates(candidates_list, negative_candidates_list):
|
||||||
flatten_c_list = []
|
flatten_c_list = []
|
||||||
ranks = {}
|
ranks = {}
|
||||||
|
@ -137,17 +174,23 @@ def rank_candidates(candidates_list, negative_candidates_list):
|
||||||
for lst in candidates_list:
|
for lst in candidates_list:
|
||||||
flatten_c_list.extend(lst)
|
flatten_c_list.extend(lst)
|
||||||
for cand in lst:
|
for cand in lst:
|
||||||
ranks.update({cand: {'rank': 0, 'lst': lst}})
|
ranks.update({cand: {"rank": 0, "lst": lst}})
|
||||||
|
|
||||||
cnt = Counter(flatten_c_list)
|
cnt = Counter(flatten_c_list)
|
||||||
|
|
||||||
for candidate, how_many in cnt.most_common(len(flatten_c_list)):
|
for candidate, how_many in cnt.most_common(len(flatten_c_list)):
|
||||||
ranks[candidate]['rank'] = how_many * (len(ranks[candidate]['lst']) - ranks[candidate]['lst'].index(candidate))
|
ranks[candidate]["rank"] = how_many * (
|
||||||
|
len(ranks[candidate]["lst"]) - ranks[candidate]["lst"].index(candidate)
|
||||||
|
)
|
||||||
|
|
||||||
res = []
|
res = []
|
||||||
for cand in ranks.keys():
|
for cand in ranks.keys():
|
||||||
res.append((ranks[cand]['rank'], cand))
|
res.append((ranks[cand]["rank"], cand))
|
||||||
return list(filter(lambda x: x[1] not in flatten_negatives, sorted(res, key=lambda x: -x[0])))
|
return list(
|
||||||
|
filter(
|
||||||
|
lambda x: x[1] not in flatten_negatives, sorted(res, key=lambda x: -x[0])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_personal_recommendation(prefer, unprefer):
|
def get_personal_recommendation(prefer, unprefer):
|
||||||
|
@ -158,7 +201,9 @@ def get_personal_recommendation(prefer, unprefer):
|
||||||
candidates.append(list(map(lambda x: x.oid, get_nearest_event(rec, 10)[1:])))
|
candidates.append(list(map(lambda x: x.oid, get_nearest_event(rec, 10)[1:])))
|
||||||
|
|
||||||
for neg in unprefer:
|
for neg in unprefer:
|
||||||
negative_candidates.append(list(map(lambda x: x.oid, get_nearest_event(neg, 10)[1:])))
|
negative_candidates.append(
|
||||||
|
list(map(lambda x: x.oid, get_nearest_event(neg, 10)[1:]))
|
||||||
|
)
|
||||||
|
|
||||||
ranked = rank_candidates(candidates, negative_candidates)
|
ranked = rank_candidates(candidates, negative_candidates)
|
||||||
|
|
||||||
|
@ -189,7 +234,6 @@ def get_personal_movies_recommendation(user):
|
||||||
return get_personal_recommendation(prefer, unprefer)
|
return get_personal_recommendation(prefer, unprefer)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def dist_func(event1: Event, event2: Event):
|
def dist_func(event1: Event, event2: Event):
|
||||||
# cords1 = [event1.lat, event1.lon]
|
# cords1 = [event1.lat, event1.lon]
|
||||||
# cords2 = [event2.lat, event2.lon]
|
# cords2 = [event2.lat, event2.lon]
|
||||||
|
@ -205,7 +249,9 @@ def generate_nearest():
|
||||||
NearestEvent.objects.all().delete()
|
NearestEvent.objects.all().delete()
|
||||||
all_events = list(Event.objects.all())
|
all_events = list(Event.objects.all())
|
||||||
for i, event in enumerate(Event.objects.all()):
|
for i, event in enumerate(Event.objects.all()):
|
||||||
event_all_events = list(sorted(all_events.copy(), key=lambda x: dist_func(event, x)))
|
event_all_events = list(
|
||||||
|
sorted(all_events.copy(), key=lambda x: dist_func(event, x))
|
||||||
|
)
|
||||||
nearest = NearestEvent.objects.create(event=event)
|
nearest = NearestEvent.objects.create(event=event)
|
||||||
nearest.nearest.set(event_all_events[0:100])
|
nearest.nearest.set(event_all_events[0:100])
|
||||||
nearest.save()
|
nearest.save()
|
||||||
|
@ -218,12 +264,15 @@ def generate_hotel_nearest():
|
||||||
all_events = list(Event.objects.all())
|
all_events = list(Event.objects.all())
|
||||||
hotels = list(Hotel.objects.all())
|
hotels = list(Hotel.objects.all())
|
||||||
for i, hotel in enumerate(hotels):
|
for i, hotel in enumerate(hotels):
|
||||||
event_all_events = list(sorted(all_events.copy(), key=lambda x: dist_func(hotel, x)))
|
event_all_events = list(
|
||||||
|
sorted(all_events.copy(), key=lambda x: dist_func(hotel, x))
|
||||||
|
)
|
||||||
nearest = NearestHotel.objects.create(hotel=hotel)
|
nearest = NearestHotel.objects.create(hotel=hotel)
|
||||||
nearest.nearest_events.set(event_all_events[0:100])
|
nearest.nearest_events.set(event_all_events[0:100])
|
||||||
if i % 100 == 0:
|
if i % 100 == 0:
|
||||||
print(i)
|
print(i)
|
||||||
|
|
||||||
|
|
||||||
def match_points():
|
def match_points():
|
||||||
regions = list(City.objects.all())
|
regions = list(City.objects.all())
|
||||||
for i, point in enumerate(Event.objects.all()):
|
for i, point in enumerate(Event.objects.all()):
|
||||||
|
@ -234,8 +283,12 @@ def match_points():
|
||||||
print(i)
|
print(i)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_mean_metric(
|
||||||
def calculate_mean_metric(favorite_events: Iterable[Event], target_event: Event, model: AnnoyIndex, rev_mapping):
|
favorite_events: Iterable[Event],
|
||||||
|
target_event: Event,
|
||||||
|
model: AnnoyIndex,
|
||||||
|
rev_mapping,
|
||||||
|
):
|
||||||
if not len(favorite_events):
|
if not len(favorite_events):
|
||||||
return 100000
|
return 100000
|
||||||
|
|
||||||
|
@ -248,35 +301,23 @@ def calculate_mean_metric(favorite_events: Iterable[Event], target_event: Event,
|
||||||
|
|
||||||
def calculate_favorite_metric(event: Event, user: User):
|
def calculate_favorite_metric(event: Event, user: User):
|
||||||
pref = UserPreferences.objects.get(user=user)
|
pref = UserPreferences.objects.get(user=user)
|
||||||
if event.type == 'plays':
|
if event.type == "plays":
|
||||||
preferred = pref.preffered_plays.all()
|
preferred = pref.preffered_plays.all()
|
||||||
return calculate_mean_metric(
|
return calculate_mean_metric(preferred, event, plays_model, rev_plays_mapping)
|
||||||
preferred,
|
if event.type == "concert":
|
||||||
event,
|
|
||||||
plays_model,
|
|
||||||
rev_plays_mapping
|
|
||||||
)
|
|
||||||
if event.type == 'concert':
|
|
||||||
preferred = pref.preferred_concerts.all()
|
preferred = pref.preferred_concerts.all()
|
||||||
return calculate_mean_metric(
|
return calculate_mean_metric(
|
||||||
preferred,
|
preferred, event, concert_model, rev_concert_mapping
|
||||||
event,
|
|
||||||
concert_model,
|
|
||||||
rev_concert_mapping
|
|
||||||
)
|
)
|
||||||
if event.type == 'movie':
|
if event.type == "movie":
|
||||||
preferred = pref.preffered_movies.all()
|
preferred = pref.preffered_movies.all()
|
||||||
return calculate_mean_metric(
|
return calculate_mean_metric(preferred, event, cinema_model, rev_cinema_mapping)
|
||||||
preferred,
|
|
||||||
event,
|
|
||||||
cinema_model,
|
|
||||||
rev_cinema_mapping
|
|
||||||
)
|
|
||||||
return 1000000
|
return 1000000
|
||||||
|
|
||||||
|
|
||||||
def get_nearest_favorite(events: Iterable[Event], user: User, exclude_events: Iterable[Event]=[]):
|
def get_nearest_favorite(
|
||||||
|
events: Iterable[Event], user: User, exclude_events: Iterable[Event] = []
|
||||||
|
):
|
||||||
first_event = None
|
first_event = None
|
||||||
for candidate in events:
|
for candidate in events:
|
||||||
if candidate not in exclude_events:
|
if candidate not in exclude_events:
|
||||||
|
@ -286,7 +327,8 @@ def get_nearest_favorite(events: Iterable[Event], user: User, exclude_events: It
|
||||||
result = first_event
|
result = first_event
|
||||||
result_min = calculate_favorite_metric(result, user)
|
result_min = calculate_favorite_metric(result, user)
|
||||||
for event in events:
|
for event in events:
|
||||||
if event in exclude_events: continue
|
if event in exclude_events:
|
||||||
|
continue
|
||||||
local_min_metric = calculate_favorite_metric(event, user)
|
local_min_metric = calculate_favorite_metric(event, user)
|
||||||
if local_min_metric < result_min:
|
if local_min_metric < result_min:
|
||||||
result_min = local_min_metric
|
result_min = local_min_metric
|
||||||
|
@ -312,7 +354,7 @@ def generate_route(point1: BasePoint, point2: BasePoint):
|
||||||
"from": point1,
|
"from": point1,
|
||||||
"to": point2,
|
"to": point2,
|
||||||
"distance": distance,
|
"distance": distance,
|
||||||
"time": time
|
"time": time,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -321,7 +363,7 @@ def generate_point(point: BasePoint):
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"point": point,
|
"point": point,
|
||||||
"point_type": "",
|
"point_type": "",
|
||||||
"time": timedelta(minutes=90+choice(range(-80, 90, 10)))
|
"time": timedelta(minutes=90 + choice(range(-80, 90, 10))),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -347,10 +389,10 @@ def generate_path(region: Region, user: User):
|
||||||
points.append(get_nearest_favorite(candidates, user, points))
|
points.append(get_nearest_favorite(candidates, user, points))
|
||||||
|
|
||||||
transition_route = generate_route(points[-1], points[-2])
|
transition_route = generate_route(points[-1], points[-2])
|
||||||
start_time += transition_route['time']
|
start_time += transition_route["time"]
|
||||||
|
|
||||||
point_route = generate_point(points[-1])
|
point_route = generate_point(points[-1])
|
||||||
start_time += point_route['time']
|
start_time += point_route["time"]
|
||||||
path.extend([transition_route, point_route])
|
path.extend([transition_route, point_route])
|
||||||
|
|
||||||
return hotel, points, path
|
return hotel, points, path
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
UserRegisterSerializer,
|
UserRegisterSerializer,
|
||||||
UserPreferenceSerializer,
|
UserPreferenceSerializer,
|
||||||
)
|
)
|
||||||
|
from ..models import UserPreference
|
||||||
|
from ...events.api.serializers import PointSerializer
|
||||||
|
from ...events.models import BasePoint
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
@ -45,3 +48,12 @@ def post(self, request, *args, **kwargs):
|
||||||
|
|
||||||
class CreateUserPreferenceApiView(generics.CreateAPIView):
|
class CreateUserPreferenceApiView(generics.CreateAPIView):
|
||||||
serializer_class = UserPreferenceSerializer
|
serializer_class = UserPreferenceSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ListUserFavoritePointsApiView(generics.ListAPIView):
|
||||||
|
serializer_class = PointSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return BasePoint.objects.filter(
|
||||||
|
user_preferences__user=self.request.user, user_preferences__type="favorite"
|
||||||
|
)
|
||||||
|
|
38
poetry.lock
generated
38
poetry.lock
generated
|
@ -1274,6 +1274,42 @@ prometheus-client = ">=0.8.0"
|
||||||
pytz = "*"
|
pytz = "*"
|
||||||
tornado = ">=5.0.0,<7.0.0"
|
tornado = ">=5.0.0,<7.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "geographiclib"
|
||||||
|
version = "2.0"
|
||||||
|
description = "The geodesic routines from GeographicLib"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "geographiclib-2.0-py3-none-any.whl", hash = "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734"},
|
||||||
|
{file = "geographiclib-2.0.tar.gz", hash = "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "geopy"
|
||||||
|
version = "2.3.0"
|
||||||
|
description = "Python Geocoding Toolbox"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "geopy-2.3.0-py3-none-any.whl", hash = "sha256:4a29a16d41d8e56ba8e07310802a1cbdf098eeb6069cc3d6d3068fc770629ffc"},
|
||||||
|
{file = "geopy-2.3.0.tar.gz", hash = "sha256:228cd53b6eef699b2289d1172e462a90d5057779a10388a7366291812601187f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
geographiclib = ">=1.52,<3"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
aiohttp = ["aiohttp"]
|
||||||
|
dev = ["coverage", "flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"]
|
||||||
|
dev-docs = ["readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"]
|
||||||
|
dev-lint = ["flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)"]
|
||||||
|
dev-test = ["coverage", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "sphinx (<=4.3.2)"]
|
||||||
|
requests = ["requests (>=2.16.2)", "urllib3 (>=1.24.2)"]
|
||||||
|
timezone = ["pytz"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humanize"
|
name = "humanize"
|
||||||
version = "4.6.0"
|
version = "4.6.0"
|
||||||
|
@ -3079,4 +3115,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
content-hash = "c715e551faec63d192743332fc0fd568147d4f164ff58a2ad843a7c10a5f4799"
|
content-hash = "2f8d96eb5b506fcf8f61c7ba8bfd68a0b740e9b06b5f32d9a22afb84fd480f2f"
|
||||||
|
|
|
@ -53,6 +53,7 @@ django-filter = "^23.2"
|
||||||
djangorestframework-simplejwt = "^5.2.2"
|
djangorestframework-simplejwt = "^5.2.2"
|
||||||
beautifulsoup4 = "^4.12.2"
|
beautifulsoup4 = "^4.12.2"
|
||||||
django-clickhouse = "^1.2.1"
|
django-clickhouse = "^1.2.1"
|
||||||
|
geopy = "^2.3.0"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user