added api endpoints, updated location

This commit is contained in:
Alexander Karpov 2023-05-21 20:46:07 +03:00
parent 9c2beb8cd7
commit 3bf8042ec2
18 changed files with 2179549 additions and 30 deletions

View File

@ -1,11 +1,18 @@
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
router = DefaultRouter() router = DefaultRouter()
router.register('tinder', TinderView) router.register('tinder', TinderView)
router.register("recommendations", PersonalRecommendation) router.register("recommendations", PersonalRecommendation)
router.register("user", UserViewSet)
app_name = "api" app_name = "api"
urlpatterns = router.urls urlpatterns = [
path("", include("passfinder.events.api.urls")),
path("auth/", include("passfinder.users.api.urls")),
]
urlpatterns += router.urls

View File

@ -39,7 +39,11 @@
# DATABASES # DATABASES
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#databases # https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {"default": env.db("DATABASE_URL", "postgres://postgres:Ilvas2006@localhost:5432/passfinder")} DATABASES = {
"default": env.db(
"DATABASE_URL", "postgres://postgres:Ilvas2006@localhost:5432/passfinder"
)
}
DATABASES["default"]["ATOMIC_REQUESTS"] = True DATABASES["default"]["ATOMIC_REQUESTS"] = True
# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-DEFAULT_AUTO_FIELD # https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-DEFAULT_AUTO_FIELD
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
@ -74,11 +78,7 @@
"polymorphic", "polymorphic",
] ]
LOCAL_APPS = [ LOCAL_APPS = ["passfinder.users", "passfinder.events", "passfinder.recomendations"]
"passfinder.users",
"passfinder.events",
"passfinder.recomendations"
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
@ -316,8 +316,7 @@
# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/ # django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/
REST_FRAMEWORK = { REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ( "DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication", "rest_framework_simplejwt.authentication.JWTAuthentication",
"rest_framework.authentication.TokenAuthentication",
), ),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
@ -334,8 +333,8 @@
"VERSION": "1.0.0", "VERSION": "1.0.0",
"SERVE_PERMISSIONS": [], "SERVE_PERMISSIONS": [],
"SERVERS": [ "SERVERS": [
{"url": "https://dev2.akarpov.ru", "description": "Production server"},
{"url": "http://127.0.0.1:8000", "description": "Local Development server"}, {"url": "http://127.0.0.1:8000", "description": "Local Development server"},
{"url": "https://akarpov.ru", "description": "Production server"},
], ],
} }

View File

@ -11,7 +11,7 @@
default="dh0qndI7XExEDZIdMDh2VPHbW9VNq0jsjsI2ImR9EyDQfNMYudHUaJswMggfhotG", default="dh0qndI7XExEDZIdMDh2VPHbW9VNq0jsjsI2ImR9EyDQfNMYudHUaJswMggfhotG",
) )
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "dev2.akarpov.ru"]
CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_ALLOW_ALL = True
# CACHES # CACHES

View File

@ -2,10 +2,7 @@
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from django.views import defaults as default_views
from django.views.generic import TemplateView
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [ urlpatterns = [
# Django Admin, use {% url 'admin:index' %} # Django Admin, use {% url 'admin:index' %}
@ -17,8 +14,6 @@
urlpatterns += [ urlpatterns += [
# API base url # API base url
path("api/", include("config.api_router")), path("api/", include("config.api_router")),
# DRF auth token
path("api/auth/token/", obtain_auth_token),
path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"), path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"),
path( path(
"api/docs/", "api/docs/",

2179283
data.json

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from passfinder.events.models import Hotel, HotelPhone, City, Event from passfinder.events.models import Hotel, HotelPhone, City, Event, BasePoint
class HotelPhoneSerializer(serializers.ModelSerializer): class HotelPhoneSerializer(serializers.ModelSerializer):
@ -34,4 +34,14 @@ class Meta:
class EventSerializer(serializers.ModelSerializer): class EventSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Event model = Event
fields = ('type', 'title', 'description', 'city', 'oid') fields = ("type", "title", "description", "city", "oid")
class PointSerializer(serializers.ModelSerializer):
# location = serializers.ListSerializer(
# child=serializers.FloatField(), source="bare_location", max_length=2
# )
class Meta:
model = BasePoint
fields = ["title", "description", "location", "icon"]

View File

@ -0,0 +1,9 @@
from django.urls import path
from passfinder.events.api.views import BuildRouteApiView
app_name = "events"
urlpatterns = [
path("route/build", BuildRouteApiView.as_view(), name="build_route")
]

View File

@ -0,0 +1,20 @@
from django_filters import DateFilter
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from passfinder.events.api.serializers import PointSerializer
from passfinder.events.models import BasePoint
class BuildRouteApiView(GenericAPIView):
filter_backends = (DjangoFilterBackend,)
filterset_class = DateFilter
serializer_class = PointSerializer
def get(self, request):
return Response(
data=PointSerializer(many=True).to_representation(
BasePoint.objects.order_by("?")[:10]
)
)

View File

@ -0,0 +1,5 @@
from django_filters import rest_framework as filters
class DateFilter(filters.FilterSet):
date = filters.DateRangeFilter(field_name="date")

View File

@ -0,0 +1,43 @@
# Generated by Django 4.2.1 on 2023-05-21 17:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("events", "0014_remove_excursionroute_excursion_and_more"),
]
operations = [
migrations.AddField(
model_name="basepoint",
name="lat",
field=models.FloatField(db_index=True, default=0),
),
migrations.AddField(
model_name="basepoint",
name="lon",
field=models.FloatField(db_index=True, default=0),
),
migrations.AddField(
model_name="city",
name="lat",
field=models.FloatField(db_index=True, default=0),
),
migrations.AddField(
model_name="city",
name="lon",
field=models.FloatField(db_index=True, default=0),
),
migrations.AddField(
model_name="place",
name="lat",
field=models.FloatField(db_index=True, default=0),
),
migrations.AddField(
model_name="place",
name="lon",
field=models.FloatField(db_index=True, default=0),
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 4.2.1 on 2023-05-21 17:44
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("events", "0015_basepoint_lat_basepoint_lon_city_lat_city_lon_and_more"),
]
operations = [
migrations.RemoveField(
model_name="basepoint",
name="location",
),
migrations.RemoveField(
model_name="city",
name="location",
),
migrations.RemoveField(
model_name="place",
name="location",
),
]

View File

@ -1,6 +1,5 @@
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
from location_field.models.plain import PlainLocationField
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
from passfinder.utils.choices import count_max_length from passfinder.utils.choices import count_max_length
@ -38,7 +37,12 @@ class City(OIDModel):
region = models.ForeignKey( region = models.ForeignKey(
"Region", related_name="cities", null=True, on_delete=models.SET_NULL "Region", related_name="cities", null=True, on_delete=models.SET_NULL
) )
location = PlainLocationField(zoom=6) lon = models.FloatField(default=0, db_index=True)
lat = models.FloatField(default=0, db_index=True)
@property
def location(self):
return [self.lon, self.lat]
def __str__(self): def __str__(self):
return self.title return self.title
@ -55,11 +59,16 @@ class Place(OIDModel):
) )
title = models.CharField(max_length=250) title = models.CharField(max_length=250)
description = models.TextField() description = models.TextField()
location = PlainLocationField(zoom=6) lon = models.FloatField(default=0, db_index=True)
lat = models.FloatField(default=0, db_index=True)
sites = ArrayField(models.CharField(max_length=250), null=True) sites = ArrayField(models.CharField(max_length=250), null=True)
phones = ArrayField(models.CharField(max_length=250), null=True) phones = ArrayField(models.CharField(max_length=250), null=True)
working_time = models.JSONField(null=True) working_time = models.JSONField(null=True)
@property
def location(self):
return [self.lon, self.lat]
class Tag(OIDModel): class Tag(OIDModel):
name = models.CharField(max_length=250) name = models.CharField(max_length=250)
@ -82,10 +91,20 @@ class BasePoint(OIDModel, PolymorphicModel):
"Place", related_name="points", null=True, on_delete=models.SET_NULL "Place", related_name="points", null=True, on_delete=models.SET_NULL
) )
sort = models.IntegerField(default=0) sort = models.IntegerField(default=0)
location = PlainLocationField(zoom=6) lon = models.FloatField(default=0, db_index=True)
lat = models.FloatField(default=0, db_index=True)
can_buy = models.BooleanField(default=True) can_buy = models.BooleanField(default=True)
priority = models.BooleanField(default=False) priority = models.BooleanField(default=False)
@property
def location(self):
return [self.lon, self.lat]
@property
def icon(self):
# TODO: change to icon/first image
return "https://akarpov.ru/media/uploads/files/qMO4dDfIXP.webp"
def __str__(self): def __str__(self):
return self.title return self.title

View File

@ -0,0 +1,5 @@
from django.contrib import admin
from passfinder.users.models import User
admin.site.register(User)

View File

@ -7,8 +7,25 @@
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = ["username", "name", "url"] fields = ["username", "email"]
class UserRegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("username", "email", "password")
extra_kwargs = { extra_kwargs = {
"url": {"view_name": "api:user-detail", "lookup_field": "username"} "password": {"write_only": True},
"email": {"required": True},
} }
def create(self, validated_data):
user = User.objects.create(
username=validated_data["username"],
email=validated_data["email"],
)
user.set_password(validated_data["password"])
user.save()
return user

View File

@ -0,0 +1,10 @@
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from .views import RegisterApiView
urlpatterns = [
path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path("register/", RegisterApiView.as_view(), name="user_register"),
]

View File

@ -1,11 +1,12 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from rest_framework import status from drf_spectacular.utils import extend_schema
from rest_framework import status, generics, permissions
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from .serializers import UserSerializer from .serializers import UserSerializer, UserRegisterSerializer
User = get_user_model() User = get_user_model()
@ -23,3 +24,16 @@ def get_queryset(self, *args, **kwargs):
def me(self, request): def me(self, request):
serializer = UserSerializer(request.user, context={"request": request}) serializer = UserSerializer(request.user, context={"request": request})
return Response(status=status.HTTP_200_OK, data=serializer.data) return Response(status=status.HTTP_200_OK, data=serializer.data)
class RegisterApiView(generics.CreateAPIView):
"""Creates new user and sends verification email"""
serializer_class = UserRegisterSerializer
permission_classes = [permissions.AllowAny]
@extend_schema(
operation_id="auth_user_register",
)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)

60
poetry.lock generated
View File

@ -848,6 +848,21 @@ files = [
[package.dependencies] [package.dependencies]
Django = ">=3.2" Django = ">=3.2"
[[package]]
name = "django-filter"
version = "23.2"
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "django-filter-23.2.tar.gz", hash = "sha256:2fe15f78108475eda525692813205fa6f9e8c1caf1ae65daa5862d403c6dbf00"},
{file = "django_filter-23.2-py3-none-any.whl", hash = "sha256:d12d8e0fc6d3eb26641e553e5d53b191eb8cec611427d4bdce0becb1f7c172b5"},
]
[package.dependencies]
Django = ">=3.2"
[[package]] [[package]]
name = "django-ipware" name = "django-ipware"
version = "5.0.0" version = "5.0.0"
@ -1013,6 +1028,31 @@ files = [
django = ">=3.0" django = ">=3.0"
pytz = "*" pytz = "*"
[[package]]
name = "djangorestframework-simplejwt"
version = "5.2.2"
description = "A minimal JSON Web Token authentication plugin for Django REST Framework"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "djangorestframework_simplejwt-5.2.2-py3-none-any.whl", hash = "sha256:4c0d2e2513e12587d93501ac091781684a216c3ee614eb3b5a10586aef5ca845"},
{file = "djangorestframework_simplejwt-5.2.2.tar.gz", hash = "sha256:d27d4bcac2c6394f678dea8b4d0d511c6e18a7f2eb8aaeeb8a7de601aeb77c42"},
]
[package.dependencies]
django = "*"
djangorestframework = "*"
pyjwt = ">=1.7.1,<3"
[package.extras]
crypto = ["cryptography (>=3.3.1)"]
dev = ["Sphinx (>=1.6.5,<2)", "cryptography", "flake8", "ipython", "isort", "pep8", "pytest", "pytest-cov", "pytest-django", "pytest-watch", "pytest-xdist", "python-jose (==3.3.0)", "sphinx-rtd-theme (>=0.1.9)", "tox", "twine", "wheel"]
doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"]
lint = ["flake8", "isort", "pep8"]
python-jose = ["python-jose (==3.3.0)"]
test = ["cryptography", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "tox"]
[[package]] [[package]]
name = "djangorestframework-stubs" name = "djangorestframework-stubs"
version = "1.10.0" version = "1.10.0"
@ -2010,6 +2050,24 @@ files = [
[package.extras] [package.extras]
plugins = ["importlib-metadata"] plugins = ["importlib-metadata"]
[[package]]
name = "pyjwt"
version = "2.7.0"
description = "JSON Web Token implementation in Python"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "PyJWT-2.7.0-py3-none-any.whl", hash = "sha256:ba2b425b15ad5ef12f200dc67dd56af4e26de2331f965c5439994dad075876e1"},
{file = "PyJWT-2.7.0.tar.gz", hash = "sha256:bd6ca4a3c4285c1a2d4349e5a035fdf8fb94e04ccd0fcbe6ba289dae9cc3e074"},
]
[package.extras]
crypto = ["cryptography (>=3.4.0)"]
dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
[[package]] [[package]]
name = "pylint" name = "pylint"
version = "2.17.4" version = "2.17.4"
@ -2930,4 +2988,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 = "8461316ade8f4a6256022b829102583a19138c47a7d5c20ccb47d7ba50ec1a1f" content-hash = "9abf5a717109289c45bdc20e08a2b6ad6d3e0ef05dc56d194fa3a77450648a57"

View File

@ -49,6 +49,8 @@ sentry-sdk = "^1.12.0"
django-location-field = "^2.7.0" django-location-field = "^2.7.0"
django-polymorphic = "^3.1.0" django-polymorphic = "^3.1.0"
annoy = "^1.17.2" annoy = "^1.17.2"
django-filter = "^23.2"
djangorestframework-simplejwt = "^5.2.2"
[build-system] [build-system]