Compare commits

...

8 Commits

Author SHA1 Message Date
dependabot[bot]
33125578a9
Bump pytest-factoryboy from 2.3.1 to 2.6.0
Bumps [pytest-factoryboy](https://github.com/pytest-dev/pytest-factoryboy) from 2.3.1 to 2.6.0.
- [Changelog](https://github.com/pytest-dev/pytest-factoryboy/blob/master/CHANGES.rst)
- [Commits](https://github.com/pytest-dev/pytest-factoryboy/compare/2.3.1...2.6.0)

---
updated-dependencies:
- dependency-name: pytest-factoryboy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 10:56:13 +00:00
90f15db5e3 added uuid tools, updated celery 2023-10-31 13:20:42 +03:00
Alexander Karpov
29f78393f4
Update README.md 2023-10-25 13:49:02 +03:00
e4bfd5ca07 updated settings 2023-10-25 10:42:08 +03:00
59fc828097 added user's themes, minor fixes 2023-10-25 10:27:55 +03:00
403fb8ffa5 added full tests for user's api 2023-10-16 01:50:33 +03:00
f6f15d3979 added email settings to compose 2023-10-15 23:02:53 +03:00
f59df63dd4 updated requirements, added path api tool 2023-10-13 02:28:51 +03:00
41 changed files with 1695 additions and 1027 deletions

View File

@ -6,3 +6,6 @@ USE_DOCKER=no
EMAIL_HOST=127.0.0.1
EMAIL_PORT=1025
SENTRY_DSN=
EMAIL_PASSWORD=
EMAIL_USER=
EMAIL_USE_SSL=false

View File

@ -53,3 +53,4 @@ $ mypy --config-file setup.cfg akarpov
- short link generator
- about me app
- gallery
- notifications

View File

@ -10,8 +10,18 @@
app_name = "music"
urlpatterns = [
path("playlists/", ListCreatePlaylistAPIView.as_view()),
path("playlists/<str:slug>", RetrieveUpdateDestroyPlaylistAPIView.as_view()),
path("song/", ListCreateSongAPIView.as_view()),
path("song/<str:slug>", RetrieveUpdateDestroySongAPIView.as_view()),
path(
"playlists/", ListCreatePlaylistAPIView.as_view(), name="list_create_playlist"
),
path(
"playlists/<str:slug>",
RetrieveUpdateDestroyPlaylistAPIView.as_view(),
name="retrieve_update_delete_playlist",
),
path("song/", ListCreateSongAPIView.as_view(), name="list_create_song"),
path(
"song/<str:slug>",
RetrieveUpdateDestroySongAPIView.as_view(),
name="retrieve_update_delete_song",
),
]

View File

@ -21,6 +21,11 @@
<!-- Latest compiled and minified Bootstrap CSS -->
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
{% if request.user.is_authenticated %}
{% if request.user.theme %}
<link href="{{ request.user.theme.file.url }}" rel="stylesheet">
{% endif %}
{% endif %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css">
<script src="https://kit.fontawesome.com/32fd82c823.js" crossorigin="anonymous"></script>
<!-- Your stuff: Third-party CSS libraries go here -->
@ -71,6 +76,7 @@
</a>
<ul class="dropdown-menu dropdown-menu-dark text-small shadow" aria-labelledby="dropdown">
<li><a class="dropdown-item {% active_link 'tools:qr:create' %}" href="{% url 'tools:qr:create' %}">QR generator</a></li>
<li><a class="dropdown-item {% active_link 'tools:uuid:main' %}" href="{% url 'tools:uuid:main' %}">UUID tools</a></li>
<li><a class="dropdown-item {% active_link 'tools:shortener:create' %}" href="{% url 'tools:shortener:create' %}">URL shortcuter</a></li>
</ul>
</li>

View File

@ -0,0 +1,58 @@
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="row align-items-center justify-content-center d-flex">
<div class="col-lg-12 d-flex align-items-stretch m-3">
<div style="width: 100%" class="card text-center">
<form class="card-body row justify-content-end ml-5 mr-5" method="get">
<div class="col-lg-11 col-sm-10">
<label for="uuid">Lookup uuid info</label><input {% if uuid %}value="{{ uuid }}" {% endif %}name="uuid" class="form-control" id="uuid" type="text" placeholder="insert uuid" />
</div>
<div class="col-lg-1 col-sm-2">
<button class="btn btn-success mt-4" type="submit"><i class="bi-search"></i></button>
</div>
</form>
</div>
</div>
{% if data %}
<div class="col-lg-10">
<table class="table">
<thead>
<tr>
<th scope="col">name</th>
<th scope="col">value</th>
</tr>
</thead>
<tbody>
{% for key, val in data.items %}
<tr>
<th scope="row">{{ key }}</th>
<td>{{ val }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
{% for token, version in tokens %}
<div class="col-lg-5 d-flex align-items-stretch justify-content-center m-3">
<div class="card text-center ml-1">
<div class="card-header">
<h4 class="bg-gray-300 ">{{ token }}<button class="btn" data-clipboard-text="{{ token }}">
<i style="font-size: 0.8em" class="bi bi-clipboard ml-2"></i>
</button></h4>
</div>
<div class="card-body">
Generated: {{ now }}, Version: {{ version }}
</div>
</div>
</div>
{% endfor %}
</div>
<script src="{% static 'js/clipboard.min.js' %}"></script>
<script>
new ClipboardJS('.btn');
</script>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block title %}Create Theme{% endblock %}
{% block content %}
<form class="form-horizontal" enctype="multipart/form-data" method="post">
{% csrf_token %}
{{ form|crispy }}
<div class="control-group">
<div class="controls">
<button type="submit" class="btn btn-primary">Create</button>
</div>
</div>
</form>
{% endblock %}

View File

@ -8,10 +8,34 @@
<form class="form-horizontal" enctype="multipart/form-data" method="post" action="{% url 'users:update' %}">
{% csrf_token %}
{{ form|crispy }}
{# Themes block #}
<p class="mt-3 ml-3">Theme:</p>
<div class="row">
<label class="col-6 col-sm-4 col-md-3 gl-mb-5 text-center">
<div style="background-color: white; height: 48px; border-radius: 4px; min-width: 112px; margin-bottom: 8px;"></div>
<input {% if not request.user.theme %}checked{% endif %} type="radio" value="0" name="theme" id="user_theme_id_0">
Default
</label>
{% for theme in themes %}
<label class="col-6 col-sm-4 col-md-3 gl-mb-5 text-center">
<div style="background-color: {{ theme.color }}; height: 48px; border-radius: 4px; min-width: 112px; margin-bottom: 8px;"></div>
<input {% if request.user.theme_id == theme.id %}checked{% endif %} type="radio" value="{{ theme.id }}" name="theme" id="user_theme_id_{{ theme.id }}">
{{ theme.name }}
</label>
{% endfor %}
{% if request.user.is_superuser %}
<label class="col-6 col-sm-4 col-md-3 gl-mb-5 text-center">
<div style="background-color: white; height: 48px; border-radius: 4px; min-width: 112px; margin-bottom: 8px;"></div>
<a href="{% url 'users:themes:create' %}">Create new</a>
</label>
{% endif %}
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn btn-primary">Update</button>
</div>
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,6 @@
from rest_framework import serializers
class URLPathSerializer(serializers.Serializer):
path = serializers.URLField()
kwargs = serializers.DictField(help_text="{'slug': 'str', 'pk': 'int'}")

View File

@ -0,0 +1,68 @@
from functools import lru_cache
from config import urls as urls_conf
urls = None
def get_urls(urllist, name="") -> (list, list):
res = []
res_short = []
for entry in urllist:
if hasattr(entry, "url_patterns"):
if entry.namespace != "admin":
rres, rres_short = get_urls(
entry.url_patterns,
name + entry.namespace + ":" if entry.namespace else name,
)
res += rres
res_short += rres_short
else:
res.append(
(
name + entry.pattern.name if entry.pattern.name else "",
str(entry.pattern),
)
)
res_short.append(
(
entry.pattern.name,
str(entry.pattern),
)
)
return res, res_short
@lru_cache
def urlpattern_to_js(pattern: str) -> (str, dict):
if pattern.startswith("^"):
return pattern
res = ""
kwargs = {}
for p in pattern.split("<"):
if ">" in p:
rec = ""
pn = p.split(">")
k = pn[0].split(":")
if len(k) == 1:
rec = "{" + k[0] + "}"
kwargs[k[0]] = "any"
elif len(k) == 2:
rec = "{" + k[1] + "}"
kwargs[k[1]] = k[0]
res += rec + pn[-1]
else:
res += p
return res, kwargs
def get_api_path_by_url(name: str) -> tuple[str, dict] | None:
global urls
if not urls:
urls, urls_short = get_urls(urls_conf.urlpatterns)
urls = dict(urls_short) | dict(urls)
if name in urls:
return urlpattern_to_js(urls[name])
return None

View File

@ -1,7 +1,10 @@
from django.urls import include, path
from akarpov.tools.api.views import RetrieveAPIUrlAPIView
app_name = "tools"
urlpatterns = [
path("<str:path>", RetrieveAPIUrlAPIView.as_view(), name="path"),
path("qr/", include("akarpov.tools.qr.api.urls", namespace="qr")),
]

View File

@ -0,0 +1,18 @@
from rest_framework import generics
from rest_framework.exceptions import NotFound
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from akarpov.tools.api.serializers import URLPathSerializer
from akarpov.tools.api.services import get_api_path_by_url
class RetrieveAPIUrlAPIView(generics.GenericAPIView):
serializer_class = URLPathSerializer
permission_classes = [AllowAny]
def get(self, request, *args, **kwargs):
path, k_args = get_api_path_by_url(self.kwargs["path"])
if not path:
raise NotFound
return Response(data={"path": path, "kwargs": k_args})

View File

@ -3,6 +3,7 @@
app_name = "tools"
urlpatterns = [
path("qr/", include("akarpov.tools.qr.urls", namespace="qr")),
path("uuid/", include("akarpov.tools.uuidtools.urls", namespace="uuid")),
path(
"promocodes/", include("akarpov.tools.promocodes.urls", namespace="promocodes")
),

View File

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class UuidtoolsConfig(AppConfig):
name = "akarpov.tools.uuidtools"

View File

@ -0,0 +1,5 @@
from django import forms
class UUIDForm(forms.Form):
token = forms.UUIDField()

View File

@ -0,0 +1,56 @@
import datetime
from uuid import UUID
def uuid1_time_to_datetime(time: int):
return datetime.datetime(1582, 10, 15) + datetime.timedelta(microseconds=time // 10)
version_info = {
1: """UUID Version 1 (Time-Based): The current time and the specific MAC address of the computer generating the
UUID are used to construct IDs in UUID version 1. This means that even if they generate UUIDs simultaneously,
separate machines are probably going to produce different ones. If your clock also knew your computers unique
number, it would display a different number each time you glanced at it depending on the time and who you were.
When generating a UUID that is specific to a certain computer and linked to the moment it was generated,
this version is frequently utilised.""",
2: """V2 UUIDs include the MAC address of the generator, lossy timestamp, and an account ID such as user ID or
group ID on the local computer. Because of the information included in the UUID, there is limited space for
randomness. The clock section of the UUID only advances every 429.47 seconds (~7 minutes). During any 7 minute
period, there are only 64 available different UUIDs! """,
3: """UUID Version 3 (Name-Based, Using MD5):The UUID version 3 uses MD5 hashing to create IDs.
It uses the MD5 technique to combine the namespace UUID (a unique UUID) and the name you supply to get a unique
identification. Imagine it as a secret recipe book where you add a secret ingredient (namespace UUID) to a standard
ingredient (name), and when you combine them using a specific method (MD5), you get a unique dish (UUID).
When you need to consistently produce UUIDs based on particular names, this variant is frequently utilised.""",
4: """UUID Version 4 (Random): UUID version 4 uses random integers to create identifiers. It doesnt depend on
any particular details like names or times. Instead, it simply generates a slew of random numbers and characters.
Imagine shaking a dice-filled box and then examining the face-up numbers that came out. It resembles receiving an
unpredictable combination each time. When you just require a single unique identification without any kind of
pattern or order, this version is fantastic.""",
5: """UUID Version 5 (Name-Based, Using SHA-1): Similar to version 3, UUID version 5 generates identifiers using
the SHA-1 algorithm rather than the MD5 technique. Similar to version 3, you provide it a namespace UUID and a
name, and it uses the SHA-1 technique to combine them to get a unique identification. Consider it similar to
baking a cake: you need a special pan (namespace UUID), your recipe (name), and a particular baking technique (
SHA-1). No matter how many times you cook this recipe, you will always end up with a unique cake (UUID). Similar
to version 3, UUID version 5 is frequently used in situations where a consistent and distinctive identity based
on particular names is required. The better encryption capabilities of SHA-1 make its use preferred over MD5 in
terms of security.""",
}
def decode_uuid(token: str) -> dict:
data = {"token": token}
try:
uuid = UUID(token)
except ValueError:
return {"message": "not a valid UUID token"}
data["version"] = f"{uuid.version}, {uuid.variant}"
data["hex"] = uuid.hex
data["bytes"] = bin(uuid.int)[2:]
data["num"] = uuid.int
if uuid.version == 1:
data["time"] = uuid1_time_to_datetime(uuid.time)
if uuid.version in version_info:
data["info"] = version_info[uuid.version]
return data

View File

@ -0,0 +1,9 @@
from django.urls import path
from . import views
app_name = "uuidtools"
urlpatterns = [
path("", views.MainView.as_view(), name="main"),
]

View File

@ -0,0 +1,28 @@
import uuid
import uuid6
from django.utils.timezone import now
from django.views import generic
from akarpov.tools.uuidtools.services import decode_uuid
class MainView(generic.TemplateView):
template_name = "tools/uuid/main.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if "uuid" in self.request.GET:
data = decode_uuid(str(self.request.GET["uuid"]))
context["data"] = data
context["uuid"] = self.request.GET["uuid"]
context["tokens"] = [
(uuid.uuid4(), 4),
(uuid6.uuid6(), 6),
(uuid6.uuid8(), 8),
(uuid.uuid1(), 1),
]
context["now"] = now()
return context

View File

@ -25,7 +25,7 @@ def validate_token(self, token):
class UserPublicInfoSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="api:users:user_retrieve_username_api", lookup_field="username"
view_name="api:users:get", lookup_field="username"
)
class Meta:

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.6 on 2023-10-25 06:37
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("themes", "0001_initial"),
("users", "0011_alter_userhistory_options_userhistory_created"),
]
operations = [
migrations.AddField(
model_name="user",
name="theme",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="themes.theme",
),
),
]

View File

@ -27,6 +27,7 @@ class User(AbstractUser, BaseImageModel, ShortLinkModel):
left_file_upload = models.BigIntegerField(
"Left file upload(in bites)", default=0, validators=[MinValueValidator(0)]
)
theme = models.ForeignKey("themes.Theme", null=True, on_delete=models.SET_NULL)
def get_absolute_url(self):
"""Get url for user's detail view.

View File

@ -1,3 +1,4 @@
import pytest
from django.urls import reverse_lazy
from pytest_lambda import lambda_fixture, static_fixture
from rest_framework import status
@ -27,3 +28,74 @@ def test_return_err_if_data_is_invalid(
assert response.status_code == status.HTTP_400_BAD_REQUEST
user.refresh_from_db()
assert not user.check_password(new_password)
class TestUserListRetrieve:
url = static_fixture(reverse_lazy("api:users:list"))
url_retrieve = static_fixture(
reverse_lazy("api:users:get", kwargs={"username": "TestUser"})
)
user = lambda_fixture(
lambda user_factory: user_factory(password="P@ssw0rd", username="TestUser")
)
def test_user_list_site_users(self, api_user_client, url, user):
response = api_user_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert response.json()["count"] == 1
assert response.json()["results"][0]["username"] == user.username
def test_user_retrieve_by_username(self, api_user_client, url_retrieve, user):
response = api_user_client.get(url_retrieve)
assert response.status_code == status.HTTP_200_OK
assert response.json()["username"] == user.username
assert response.json()["id"] == user.id
def test_user_retrieve_by_id(self, api_user_client, user):
response = api_user_client.get(
reverse_lazy("api:users:get_by_id", kwargs={"pk": user.id})
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["username"] == user.username
assert response.json()["id"] == user.id
class TestUserSelfRetrieve:
url = static_fixture(reverse_lazy("api:users:self"))
user = lambda_fixture(lambda user_factory: user_factory(password="P@ssw0rd"))
def test_user_self_retrieve(self, api_user_client, url, user):
response = api_user_client.get(url)
assert response.status_code == status.HTTP_200_OK
assert response.json()["username"] == user.username
assert response.json()["id"] == user.id
def test_user_self_update_put(self, api_user_client, url, user):
response = api_user_client.put(url, {"username": "NewUsername"})
assert response.status_code == status.HTTP_200_OK
assert response.json()["username"] == "NewUsername"
assert response.json()["id"] == user.id
user.refresh_from_db()
assert user.username == "NewUsername"
def test_user_self_update_patch(self, api_user_client, url, user):
response = api_user_client.patch(url, {"username": "NewUsername"})
assert response.status_code == status.HTTP_200_OK
assert response.json()["username"] == "NewUsername"
assert response.json()["id"] == user.id
user.refresh_from_db()
assert user.username == "NewUsername"
def test_user_self_delete(self, api_user_client, url, user):
response = api_user_client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert response.content == b""
with pytest.raises(user.DoesNotExist):
user.refresh_from_db()

View File

View File

@ -0,0 +1,8 @@
from django.contrib import admin
from akarpov.users.themes.models import Theme
@admin.register(Theme)
class ThemeAdmin(admin.ModelAdmin):
...

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ThemesConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "akarpov.users.themes"

View File

@ -0,0 +1,9 @@
from django import forms
from akarpov.users.themes.models import Theme
class ThemeForm(forms.ModelForm):
class Meta:
model = Theme
fields = ["name", "file", "color"]

View File

@ -0,0 +1,35 @@
# Generated by Django 4.2.6 on 2023-10-25 06:37
import colorfield.fields
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Theme",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=250)),
("file", models.FileField(upload_to="themes/")),
(
"color",
colorfield.fields.ColorField(
default="#FFFFFF", image_field=None, max_length=18, samples=None
),
),
],
),
]

View File

@ -0,0 +1,11 @@
from colorfield.fields import ColorField
from django.db import models
class Theme(models.Model):
name = models.CharField(max_length=250)
file = models.FileField(upload_to="themes/")
color = ColorField()
def __str__(self):
return self.name

View File

View File

@ -0,0 +1,8 @@
from django.urls import path
from akarpov.users.themes.views import CreateFormView
app_name = "themes"
urlpatterns = [
path("create", CreateFormView.as_view(), name="create"),
]

View File

@ -0,0 +1,13 @@
from django.views import generic
from akarpov.common.views import SuperUserRequiredMixin
from akarpov.users.themes.models import Theme
class CreateFormView(generic.CreateView, SuperUserRequiredMixin):
model = Theme
fields = ["name", "file", "color"]
template_name = "users/themes/create.html"
def get_success_url(self):
return ""

View File

@ -1,4 +1,4 @@
from django.urls import path
from django.urls import include, path
from akarpov.users.views import (
user_detail_view,
@ -11,6 +11,7 @@
app_name = "users"
urlpatterns = [
path("redirect/", view=user_redirect_view, name="redirect"),
path("themes/", include("akarpov.users.themes.urls", namespace="themes")),
path("update/", view=user_update_view, name="update"),
path("history/", view=user_history_view, name="history"),
path("history/delete", view=user_history_delete_view, name="history_delete"),

View File

@ -7,6 +7,7 @@
from akarpov.users.models import UserHistory
from akarpov.users.services.history import create_history_warning_note
from akarpov.users.themes.models import Theme
User = get_user_model()
@ -26,11 +27,24 @@ class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
success_message = _("Information successfully updated")
def get_success_url(self):
assert (
self.request.user.is_authenticated
) # for mypy to know that the user is authenticated
return self.request.user.get_absolute_url()
def get_context_data(self, **kwargs):
kwargs["themes"] = Theme.objects.all()
return super().get_context_data(**kwargs)
def form_valid(self, form):
data = self.request.POST
if "theme" in data:
if data["theme"] == "0":
self.object.theme = None
else:
try:
self.object.theme = Theme.objects.get(id=data["theme"])
except Theme.DoesNotExist:
...
return super().form_valid(form)
def get_object(self):
return self.request.user

View File

@ -4,4 +4,4 @@ set -o errexit
set -o nounset
watchfiles celery.__main__.main --args '-A config.celery_app worker -l INFO'
celery -A config.celery_app worker --loglevel=info -c 5

View File

@ -11,10 +11,6 @@ entryPoints:
entryPoint:
to: web-secure
web-secure:
# https
address: ":443"
flower:
address: ":5555"
@ -29,27 +25,6 @@ certificatesResolvers:
entryPoint: web
http:
routers:
web-secure-router:
rule: "Host(`akarpov.ru`) || Host(`www.akarpov.ru`)"
entryPoints:
- web-secure
middlewares:
- csrf
service: django
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
certResolver: letsencrypt
flower-secure-router:
rule: "Host(`akarpov.ru`)"
entryPoints:
- flower
service: flower
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
certResolver: letsencrypt
middlewares:
csrf:
# https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders

View File

@ -5,6 +5,7 @@
import environ
import structlog
from sentry_sdk.integrations.celery import CeleryIntegration
ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
# akarpov/
@ -81,7 +82,7 @@
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
"hosts": [env("REDIS_URL")],
},
},
}
@ -157,6 +158,7 @@
"akarpov.gallery",
"akarpov.tools.qr",
"akarpov.pipeliner",
"akarpov.users.themes",
"akarpov.notifications",
"akarpov.test_platform",
"akarpov.tools.shortener",
@ -307,6 +309,18 @@
)
# https://docs.djangoproject.com/en/dev/ref/settings/#email-timeout
EMAIL_TIMEOUT = 5
EMAIL_HOST_PASSWORD = env(
"EMAIL_PASSWORD",
default="",
)
EMAIL_HOST_USER = env(
"EMAIL_USER",
default="",
)
EMAIL_USE_SSL = env(
"EMAIL_USE_SSL",
default=False,
)
# ADMIN
# ------------------------------------------------------------------------------
@ -470,7 +484,7 @@
"SERVE_INCLUDE_SCHEMA": False,
"SERVERS": [
{"url": "http://127.0.0.1:8000", "description": "Local Development server"},
{"url": "https://akarpov.ru", "description": "Production server"},
{"url": "https://new.akarpov.ru", "description": "Production server"},
],
}
@ -574,5 +588,6 @@
signals_spans=True,
cache_spans=True,
),
CeleryIntegration(monitor_beat_tasks=True, propagate_traces=True),
],
)

View File

@ -52,14 +52,9 @@
# ------------------------------------------------------------------------------
# https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration
INSTALLED_APPS += ["django_extensions"] # noqa F405
# Celery
# ------------------------------------------------------------------------------
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-eager-propagates
CELERY_TASK_EAGER_PROPAGATES = True
# SHORTENER
# ------------------------------------------------------------------------------
SHORTENER_REDIRECT_TO = "https://dev2.akarpov.ru"
SHORTENER_HOST = "https://dev.akarpov.ru"
SHORTENER_REDIRECT_TO = env("SHORTENER_REDIRECT_TO", default="http://127.0.0.1:8000")
SHORTENER_HOST = env("SHORTENER_HOST", default="http://127.0.0.1:8000")

2128
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -101,7 +101,7 @@ requests = ">=2.25"
spacy = {extras = ["lookups"], version = "^3.6.1"}
spacy-transformers = "^1.2.5"
extract-msg = "0.28.7"
pytest-factoryboy = "2.3.1"
pytest-factoryboy = "2.6.0"
pytest-xdist = "^3.3.1"
pytest-mock = "^3.11.1"
pytest-asyncio = "^0.21.1"
@ -109,6 +109,7 @@ pytest-lambda = "^2.2.0"
pgvector = "^0.2.2"
pycld2 = "^0.41"
textract = "^1.6.5"
uuid6 = "^2023.5.2"
[build-system]