mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-11-22 19:06:41 +03:00
add qr api. processing, minor bug fixes
This commit is contained in:
parent
19e72eaaf3
commit
5cc34d055e
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
from akarpov.users.models import User
|
from akarpov.users.models import User
|
||||||
from akarpov.utils.files import user_file_upload_mixin
|
from akarpov.utils.files import user_file_upload_mixin
|
||||||
|
from utils.string import cleanhtml
|
||||||
|
|
||||||
|
|
||||||
class Post(models.Model):
|
class Post(models.Model):
|
||||||
|
@ -39,6 +40,7 @@ def get_comments(self):
|
||||||
return self.comments.all()
|
return self.comments.all()
|
||||||
|
|
||||||
def h_tags(self):
|
def h_tags(self):
|
||||||
|
# TODO: add caching here
|
||||||
tags = (
|
tags = (
|
||||||
Tag.objects.all()
|
Tag.objects.all()
|
||||||
.annotate(num_posts=Count("posts"))
|
.annotate(num_posts=Count("posts"))
|
||||||
|
@ -50,6 +52,16 @@ def h_tags(self):
|
||||||
def h_tag(self):
|
def h_tag(self):
|
||||||
return self.h_tags().first()
|
return self.h_tags().first()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
# TODO: add caching here
|
||||||
|
return cleanhtml(self.body)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def summary(self):
|
||||||
|
body = self.text
|
||||||
|
return body[:100] + "..." if len(body) > 100 else ""
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("blog:post", kwargs={"slug": self.slug})
|
return reverse("blog:post", kwargs={"slug": self.slug})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load humanize %}
|
{% load humanize static %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}posts on akarpov{% endblock %}
|
{% block title %}posts on akarpov{% endblock %}
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@
|
||||||
<strong style="color: {{ post.h_tag.color }}" class="d-inline-block mb-2">{{ post.h_tag.name }}</strong>
|
<strong style="color: {{ post.h_tag.color }}" class="d-inline-block mb-2">{{ post.h_tag.name }}</strong>
|
||||||
<h3 class="mb-0">{{ post.title }}</h3>
|
<h3 class="mb-0">{{ post.title }}</h3>
|
||||||
<p class="card-text"><small class="text-muted">{{ post.edited | naturaltime }}</small></p>
|
<p class="card-text"><small class="text-muted">{{ post.edited | naturaltime }}</small></p>
|
||||||
<p class="card-text mb-auto">This is a wider card with supporting text below as a natural lead-in to additional content.</p>
|
<p class="card-text mb-auto">{{ post.summary }}</p>
|
||||||
<a href="{% url 'blog:post' post.slug %}" class="stretched-link"></a>
|
<a href="{% url 'blog:post' post.slug %}" class="stretched-link"></a>
|
||||||
<p class="card-text mt-4">{{ post.get_rating }}
|
<p class="card-text mt-4">{{ post.get_rating }}
|
||||||
<i class="bi bi-eye ms-3"></i>{{ post.post_views }}
|
<i class="bi bi-eye ms-3"></i>{{ post.post_views }}
|
||||||
|
|
20
akarpov/templates/tools/qr/create.html
Normal file
20
akarpov/templates/tools/qr/create.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block title %}Qr generator on akarpov.ru{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form class="pt-2" enctype="multipart/form-data" method="POST" id="designer-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.media }}
|
||||||
|
{% for field in form %}
|
||||||
|
{{ field|as_crispy_field }}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="mt-4 flex justify-end space-x-4">
|
||||||
|
<button class="btn btn-secondary" type="submit" id="submit">
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ToolsConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
name = "akarpov.tools"
|
|
0
akarpov/tools/qr/api/__init__.py
Normal file
0
akarpov/tools/qr/api/__init__.py
Normal file
20
akarpov/tools/qr/api/serializers.py
Normal file
20
akarpov/tools/qr/api/serializers.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from akarpov.tools.qr.models import QR
|
||||||
|
from akarpov.tools.qr.services import simple
|
||||||
|
|
||||||
|
|
||||||
|
class QRSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = QR
|
||||||
|
fields = ["id", "body", "image"]
|
||||||
|
extra_kwargs = {
|
||||||
|
"id": {"read_only": True},
|
||||||
|
"body": {"write_only": True},
|
||||||
|
"image": {"read_only": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
user = self.context["request"].user.is_authenticated if self.context["request"].user else None
|
||||||
|
qr = simple.run(words=validated_data["body"], user=user)
|
||||||
|
return qr
|
9
akarpov/tools/qr/api/urls.py
Normal file
9
akarpov/tools/qr/api/urls.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from rest_framework.routers import SimpleRouter
|
||||||
|
|
||||||
|
from .views import QRViewSet
|
||||||
|
|
||||||
|
router = SimpleRouter()
|
||||||
|
router.register(r"", QRViewSet, basename="")
|
||||||
|
|
||||||
|
app_name = "qr"
|
||||||
|
urlpatterns = router.urls
|
21
akarpov/tools/qr/api/views.py
Normal file
21
akarpov/tools/qr/api/views.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework.generics import get_object_or_404
|
||||||
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
|
||||||
|
from akarpov.tools.qr.models import QR
|
||||||
|
from .serializers import QRSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class QRViewSet(generics.ListCreateAPIView, generics.RetrieveAPIView, GenericViewSet):
|
||||||
|
serializer_class = QRSerializer
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
print(self.kwargs)
|
||||||
|
return get_object_or_404(QR, pk=self.kwargs["pk"])
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
return QR.objects.filter(user=self.request.user)
|
||||||
|
return QR.objects.none()
|
|
@ -1,6 +1,7 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class QRConfig(AppConfig):
|
class QRConfig(AppConfig):
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
verbose_name = _("QR generator")
|
||||||
name = "akarpov.tools.qr"
|
name = "akarpov.tools.qr"
|
||||||
|
|
11
akarpov/tools/qr/forms.py
Normal file
11
akarpov/tools/qr/forms.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from akarpov.tools.qr.models import QR
|
||||||
|
|
||||||
|
|
||||||
|
class QRForm(forms.ModelForm):
|
||||||
|
body = forms.CharField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = QR
|
||||||
|
fields = ["body"]
|
12
akarpov/tools/qr/migrations/0001_initial.py
Normal file
12
akarpov/tools/qr/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Generated by Django 4.1.5 on 2023-01-07 14:36
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
operations = []
|
44
akarpov/tools/qr/migrations/0002_initial.py
Normal file
44
akarpov/tools/qr/migrations/0002_initial.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# Generated by Django 4.1.5 on 2023-01-10 20:32
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("qr", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="QR",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("body", models.TextField()),
|
||||||
|
("image", models.ImageField(upload_to="")),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="generated_qrs",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
akarpov/tools/qr/migrations/__init__.py
Normal file
0
akarpov/tools/qr/migrations/__init__.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class QR(models.Model):
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
body = models.TextField()
|
||||||
|
image = models.ImageField()
|
||||||
|
user = models.ForeignKey(
|
||||||
|
"users.User", related_name="generated_qrs", blank=True, on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"qr {self.body}"
|
0
akarpov/tools/qr/services/__init__.py
Normal file
0
akarpov/tools/qr/services/__init__.py
Normal file
35
akarpov/tools/qr/services/simple.py
Normal file
35
akarpov/tools/qr/services/simple.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from amzqr import amzqr
|
||||||
|
from django.core.files import File
|
||||||
|
|
||||||
|
from akarpov.tools.qr.models import QR
|
||||||
|
from akarpov.utils.generators import generate_charset
|
||||||
|
from akarpov.users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
def run(words: str, path: str = "/tmp/", user: User = None) -> QR:
|
||||||
|
version, level, qr_name = amzqr.run(
|
||||||
|
words,
|
||||||
|
version=1,
|
||||||
|
level="H",
|
||||||
|
picture=None,
|
||||||
|
colorized=False,
|
||||||
|
contrast=1.0,
|
||||||
|
brightness=1.0,
|
||||||
|
save_name=generate_charset(4) + ".png",
|
||||||
|
save_dir=path,
|
||||||
|
)
|
||||||
|
qr = QR(body=words)
|
||||||
|
if user:
|
||||||
|
qr.user = user
|
||||||
|
|
||||||
|
path = qr_name
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
qr.image.save(
|
||||||
|
qr_name.split("/")[-1],
|
||||||
|
File(f),
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
os.remove(path)
|
||||||
|
return qr
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from akarpov.tools.qr.views import qr_create_view
|
||||||
|
|
||||||
|
app_name = "qr"
|
||||||
|
urlpatterns = [path("", qr_create_view, name="create")]
|
|
@ -0,0 +1,18 @@
|
||||||
|
from django.views.generic import CreateView
|
||||||
|
|
||||||
|
from akarpov.tools.qr.forms import QRForm
|
||||||
|
from akarpov.tools.qr.models import QR
|
||||||
|
|
||||||
|
|
||||||
|
class QRCreateView(CreateView):
|
||||||
|
model = QR
|
||||||
|
form_class = QRForm
|
||||||
|
|
||||||
|
template_name = "tools/qr/create.html"
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.user = self.request.user if self.request.user else None
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
qr_create_view = QRCreateView.as_view()
|
|
@ -0,0 +1,4 @@
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
app_name = "tools"
|
||||||
|
urlpatterns = [path("qr/", include("akarpov.tools.qr.urls", namespace="qr"))]
|
26
akarpov/users/api/urls.py
Normal file
26
akarpov/users/api/urls.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from django.urls import path
|
||||||
|
from .views import (
|
||||||
|
UserListViewSet,
|
||||||
|
UserRetireUpdateSelfViewSet,
|
||||||
|
UserRetrieveIdViewSet,
|
||||||
|
UserRetrieveViewSet,
|
||||||
|
)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", UserListViewSet.as_view(), name="user_list_api"),
|
||||||
|
path(
|
||||||
|
"self/",
|
||||||
|
UserRetireUpdateSelfViewSet.as_view(),
|
||||||
|
name="user_get_update_delete_self_api",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"id/<int:pk>",
|
||||||
|
UserRetrieveIdViewSet.as_view(),
|
||||||
|
name="user_retrieve_id_api",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"<str:username>",
|
||||||
|
UserRetrieveViewSet.as_view(),
|
||||||
|
name="user_retrieve_username_api",
|
||||||
|
),
|
||||||
|
]
|
|
@ -87,15 +87,3 @@ class UserRetireUpdateSelfViewSet(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return self.retrieve(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def put(self, request, *args, **kwargs):
|
|
||||||
return self.update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def patch(self, request, *args, **kwargs):
|
|
||||||
return self.partial_update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
|
||||||
return self.destroy(request, *args, **kwargs)
|
|
||||||
|
|
|
@ -1,7 +1,17 @@
|
||||||
from akarpov.users.models import User
|
import pytest
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
def test_user_create(user: User):
|
class TestUser:
|
||||||
password = "123"
|
@pytest.fixture
|
||||||
user.set_password(password)
|
def user_with_code(self, user_factory):
|
||||||
assert user.check_password(password)
|
return user_factory(user_code="1234")
|
||||||
|
|
||||||
|
def test_send_code(self, mailoutbox, user_factory):
|
||||||
|
user_with_code.send_code()
|
||||||
|
|
||||||
|
assert len(mailoutbox) == 1
|
||||||
|
m = mailoutbox[0]
|
||||||
|
assert list(m.to) == [user_with_code.email]
|
||||||
|
assert "1234" in m.body
|
||||||
|
|
8
akarpov/utils/string.py
Normal file
8
akarpov/utils/string.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
CLEANR = re.compile("<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});")
|
||||||
|
|
||||||
|
|
||||||
|
def cleanhtml(raw_html):
|
||||||
|
cleantext = re.sub(CLEANR, "", raw_html)
|
||||||
|
return cleantext
|
|
@ -2,11 +2,7 @@
|
||||||
from rest_framework.authtoken.views import obtain_auth_token
|
from rest_framework.authtoken.views import obtain_auth_token
|
||||||
|
|
||||||
from akarpov.users.api.views import (
|
from akarpov.users.api.views import (
|
||||||
UserListViewSet,
|
|
||||||
UserRegisterViewSet,
|
UserRegisterViewSet,
|
||||||
UserRetireUpdateSelfViewSet,
|
|
||||||
UserRetrieveIdViewSet,
|
|
||||||
UserRetrieveViewSet,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns_v1 = [
|
urlpatterns_v1 = [
|
||||||
|
@ -23,26 +19,11 @@
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"users/",
|
"users/",
|
||||||
include(
|
include("akarpov.users.api.urls"),
|
||||||
[
|
|
||||||
path("", UserListViewSet.as_view(), name="user_list_api"),
|
|
||||||
path(
|
|
||||||
"self/",
|
|
||||||
UserRetireUpdateSelfViewSet.as_view(),
|
|
||||||
name="user_get_update_delete_self_api",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"id/<int:pk>",
|
"tools/",
|
||||||
UserRetrieveIdViewSet.as_view(),
|
include([path("qr/", include("akarpov.tools.qr.api.urls"))]),
|
||||||
name="user_retrieve_id_api",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"<str:username>",
|
|
||||||
UserRetrieveViewSet.as_view(),
|
|
||||||
name="user_retrieve_username_api",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -135,12 +135,7 @@
|
||||||
# "allauth.socialaccount.providers.yandex",
|
# "allauth.socialaccount.providers.yandex",
|
||||||
]
|
]
|
||||||
|
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = ["akarpov.users", "akarpov.blog", "akarpov.pipeliner", "akarpov.tools.qr"]
|
||||||
"akarpov.users",
|
|
||||||
"akarpov.blog",
|
|
||||||
"akarpov.pipeliner"
|
|
||||||
# Your stuff: custom apps go here
|
|
||||||
]
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
DJANGO_APPS + LOCAL_APPS + THIRD_PARTY_APPS + HEALTH_CHECKS + ALL_AUTH_PROVIDERS
|
DJANGO_APPS + LOCAL_APPS + THIRD_PARTY_APPS + HEALTH_CHECKS + ALL_AUTH_PROVIDERS
|
||||||
|
@ -453,9 +448,10 @@
|
||||||
# See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings
|
# See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings
|
||||||
SPECTACULAR_SETTINGS = {
|
SPECTACULAR_SETTINGS = {
|
||||||
"TITLE": "akarpov API",
|
"TITLE": "akarpov API",
|
||||||
|
"SCHEMA_PATH_PREFIX": "/api/v[0-9]",
|
||||||
"DESCRIPTION": "Documentation of API endpoints of akarpov",
|
"DESCRIPTION": "Documentation of API endpoints of akarpov",
|
||||||
"VERSION": "1.0.0",
|
"VERSION": "1.0.0",
|
||||||
"SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"],
|
"SERVE_INCLUDE_SCHEMA": False,
|
||||||
"SERVERS": [
|
"SERVERS": [
|
||||||
{"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"},
|
{"url": "https://akarpov.ru", "description": "Production server"},
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
path(settings.ADMIN_URL, admin.site.urls),
|
path(settings.ADMIN_URL, admin.site.urls),
|
||||||
# User management
|
# User management
|
||||||
path("users/", include("akarpov.users.urls", namespace="users")),
|
path("users/", include("akarpov.users.urls", namespace="users")),
|
||||||
|
path("tools/", include("akarpov.tools.urls", namespace="tools")),
|
||||||
path("ckeditor/", include("ckeditor_uploader.urls")),
|
path("ckeditor/", include("ckeditor_uploader.urls")),
|
||||||
path("accounts/", include("allauth.urls")),
|
path("accounts/", include("allauth.urls")),
|
||||||
path("", include("akarpov.blog.urls", namespace="blog")),
|
path("", include("akarpov.blog.urls", namespace="blog")),
|
||||||
|
@ -32,8 +33,8 @@
|
||||||
# API base url
|
# API base url
|
||||||
path("api/", include("config.api_router")),
|
path("api/", include("config.api_router")),
|
||||||
# DRF auth token
|
# DRF auth token
|
||||||
path("api_schema/", SpectacularAPIView.as_view(), name="api-schema"),
|
path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"),
|
||||||
path("api_rschema/", SpectacularAPIView.as_view(), name="api-redoc-schema"),
|
path("api/schema/", SpectacularAPIView.as_view(), name="api-redoc-schema"),
|
||||||
path(
|
path(
|
||||||
"api/docs/",
|
"api/docs/",
|
||||||
SpectacularSwaggerView.as_view(url_name="api-schema"),
|
SpectacularSwaggerView.as_view(url_name="api-schema"),
|
||||||
|
|
4747
poetry.lock
generated
4747
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -65,6 +65,7 @@ django-extra-settings = "^0.7.0"
|
||||||
psycopg2-binary = "^2.9.5"
|
psycopg2-binary = "^2.9.5"
|
||||||
django-cms = "^3.11.1"
|
django-cms = "^3.11.1"
|
||||||
django-sekizai = "^4.0.0"
|
django-sekizai = "^4.0.0"
|
||||||
|
amzqr = "^0.0.1"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user