mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-11-24 19:23:44 +03:00
added promocodes, senetry
This commit is contained in:
parent
a9937fc88a
commit
1aa39d1d20
|
@ -5,3 +5,4 @@ REDIS_CACHE=rediscache://localhost:6379/1
|
||||||
USE_DOCKER=no
|
USE_DOCKER=no
|
||||||
EMAIL_HOST=127.0.0.1
|
EMAIL_HOST=127.0.0.1
|
||||||
EMAIL_PORT=1025
|
EMAIL_PORT=1025
|
||||||
|
SENTRY_DSN=
|
||||||
|
|
|
@ -4,6 +4,15 @@ My collection of apps and tools
|
||||||
|
|
||||||
Writen in Python 3.11 and Django 4.2
|
Writen in Python 3.11 and Django 4.2
|
||||||
|
|
||||||
|
### local run via docker
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ python3 manage.py migrate
|
||||||
|
$ python3 manage.py runserver
|
||||||
|
$ celery -A config.celery_app worker --loglevel=info
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### local run via docker
|
### local run via docker
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
TopFolderView,
|
TopFolderView,
|
||||||
delete_file_view,
|
delete_file_view,
|
||||||
delete_folder_view,
|
delete_folder_view,
|
||||||
|
file_download_view,
|
||||||
file_report_list,
|
file_report_list,
|
||||||
file_table,
|
file_table,
|
||||||
file_update,
|
file_update,
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
path("api/chunked_upload/", ChunkedUploadView.as_view(), name="api_chunked_upload"),
|
path("api/chunked_upload/", ChunkedUploadView.as_view(), name="api_chunked_upload"),
|
||||||
path("api/folder/create/", folder_create, name="folder_create"),
|
path("api/folder/create/", folder_create, name="folder_create"),
|
||||||
path("api/file/report/<str:slug>", report_file, name="file_report"),
|
path("api/file/report/<str:slug>", report_file, name="file_report"),
|
||||||
|
path("api/file/download/<str:slug>", file_download_view, name="file_download"),
|
||||||
path("api/file/delete/<str:slug>", delete_file_view, name="delete"),
|
path("api/file/delete/<str:slug>", delete_file_view, name="delete"),
|
||||||
path("api/folder/create/<str:slug>", folder_create, name="sub_folder_create"),
|
path("api/folder/create/<str:slug>", folder_create, name="sub_folder_create"),
|
||||||
path("api/folder/delete/<str:slug>", delete_folder_view, name="folder_delete"),
|
path("api/folder/delete/<str:slug>", delete_folder_view, name="folder_delete"),
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.views import View
|
||||||
from django.views.generic import (
|
from django.views.generic import (
|
||||||
CreateView,
|
CreateView,
|
||||||
DetailView,
|
DetailView,
|
||||||
|
@ -342,3 +343,14 @@ def get_redirect_url(self, *args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
delete_folder_view = DeleteFolderView.as_view()
|
delete_folder_view = DeleteFolderView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
class FileDownloadView(View):
|
||||||
|
def get(self, request, slug):
|
||||||
|
file = get_object_or_404(File, slug=slug)
|
||||||
|
file.downloads += 1
|
||||||
|
file.save(update_fields=["downloads"])
|
||||||
|
return HttpResponseRedirect(file.file.url)
|
||||||
|
|
||||||
|
|
||||||
|
file_download_view = FileDownloadView.as_view()
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
<ul class="dropdown-menu dropdown-menu-dark text-small shadow" aria-labelledby="dropdownUser1">
|
<ul class="dropdown-menu dropdown-menu-dark text-small shadow" aria-labelledby="dropdownUser1">
|
||||||
<li><a class="dropdown-item {% active_link 'users:update' %}" href="{% url 'users:update' %}">Settings</a></li>
|
<li><a class="dropdown-item {% active_link 'users:update' %}" href="{% url 'users:update' %}">Settings</a></li>
|
||||||
<li><a class="dropdown-item {% active_link 'users:detail' request.user.username %}" href="{% url 'users:detail' request.user.username %}">Profile</a></li>
|
<li><a class="dropdown-item {% active_link 'users:detail' request.user.username %}" href="{% url 'users:detail' request.user.username %}">Profile</a></li>
|
||||||
|
<li><a class="dropdown-item {% active_link 'tools:qr:create' %}" href="{% url 'tools:promocodes:activate' %}">Activate promocode</a></li>
|
||||||
<li>
|
<li>
|
||||||
<hr class="dropdown-divider">
|
<hr class="dropdown-divider">
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="text-danger mt-2" style="text-decoration: none" href="{% url 'files:file_report' slug=file.slug %}"><i class="bi bi-flag-fill"></i>report file</a>
|
<a class="text-danger mt-2" style="text-decoration: none" href="{% url 'files:file_report' slug=file.slug %}"><i class="bi bi-flag-fill"></i>report file</a>
|
||||||
<div class="mt-4 text-center justify-content-sm-evenly justify-content-md-start gap-3 align-items-md-start align-items-sm-center d-flex">
|
<div class="mt-4 text-center justify-content-sm-evenly justify-content-md-start gap-3 align-items-md-start align-items-sm-center d-flex">
|
||||||
<a class="btn btn-success fs-6" href="{{ file.file.url }}" download><i class="bi bi-download"></i> Download</a>
|
<a class="btn btn-success fs-6" href="{% url 'files:file_download' slug=file.slug %}" download><i class="bi bi-download"></i> Download</a>
|
||||||
{% if has_perm %}
|
{% if has_perm %}
|
||||||
<a class="btn btn-danger fs-6" href="{% url 'files:delete' slug=file.slug %}"><i class="bi bi-trash"></i> Delete</a>
|
<a class="btn btn-danger fs-6" href="{% url 'files:delete' slug=file.slug %}"><i class="bi bi-trash"></i> Delete</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
32
akarpov/templates/tools/promocodes/activate.html
Normal file
32
akarpov/templates/tools/promocodes/activate.html
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if message %}
|
||||||
|
{% if status %}
|
||||||
|
<div class="alert alert-dismissible alert-success">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-dismissible alert-error">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<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">
|
||||||
|
Activate
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
0
akarpov/tools/promocodes/__init__.py
Normal file
0
akarpov/tools/promocodes/__init__.py
Normal file
6
akarpov/tools/promocodes/admin.py
Normal file
6
akarpov/tools/promocodes/admin.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from akarpov.tools.promocodes.models import PromoCode, PromoCodeActivation
|
||||||
|
|
||||||
|
admin.site.register(PromoCode)
|
||||||
|
admin.site.register(PromoCodeActivation)
|
6
akarpov/tools/promocodes/apps.py
Normal file
6
akarpov/tools/promocodes/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PromocodesConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "akarpov.tools.promocodes"
|
5
akarpov/tools/promocodes/forms.py
Normal file
5
akarpov/tools/promocodes/forms.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class PromoCodeForm(forms.Form):
|
||||||
|
promocode = forms.CharField(max_length=250, required=True)
|
103
akarpov/tools/promocodes/migrations/0001_initial.py
Normal file
103
akarpov/tools/promocodes/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
# Generated by Django 4.2.1 on 2023-06-21 13:36
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PromoCode",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
model_utils.fields.AutoCreatedField(
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
editable=False,
|
||||||
|
verbose_name="created",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
model_utils.fields.AutoLastModifiedField(
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
editable=False,
|
||||||
|
verbose_name="modified",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("promo", models.CharField(max_length=250, unique=True)),
|
||||||
|
(
|
||||||
|
"type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("single", "can be activated only one time, by one user"),
|
||||||
|
(
|
||||||
|
"multiuser",
|
||||||
|
"can be activated many times, but only one time for one user",
|
||||||
|
),
|
||||||
|
("multiple", "can be activated multiple times"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=250)),
|
||||||
|
("app_name", models.CharField(max_length=250)),
|
||||||
|
("model", models.CharField(max_length=250)),
|
||||||
|
("field", models.CharField(max_length=250)),
|
||||||
|
("value", models.IntegerField()),
|
||||||
|
("message", models.CharField(max_length=250)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PromoCodeActivation",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("activated", models.DateTimeField(auto_now_add=True)),
|
||||||
|
(
|
||||||
|
"promocode",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="activations",
|
||||||
|
to="promocodes.promocode",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="promocode_activations",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
akarpov/tools/promocodes/migrations/__init__.py
Normal file
0
akarpov/tools/promocodes/migrations/__init__.py
Normal file
37
akarpov/tools/promocodes/models.py
Normal file
37
akarpov/tools/promocodes/models.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from django.db import models
|
||||||
|
from model_utils.models import TimeStampedModel
|
||||||
|
|
||||||
|
|
||||||
|
class PromoCode(TimeStampedModel):
|
||||||
|
class PromoCodeType(models.TextChoices):
|
||||||
|
single = "single", "can be activated only one time, by one user"
|
||||||
|
multiuser = (
|
||||||
|
"multiuser",
|
||||||
|
"can be activated many times, but only one time for one user",
|
||||||
|
)
|
||||||
|
multiple = "multiple", "can be activated multiple times"
|
||||||
|
|
||||||
|
promo = models.CharField(max_length=250, unique=True)
|
||||||
|
type = models.CharField(choices=PromoCodeType.choices)
|
||||||
|
name = models.CharField(max_length=250)
|
||||||
|
app_name = models.CharField(max_length=250)
|
||||||
|
model = models.CharField(max_length=250)
|
||||||
|
field = models.CharField(max_length=250)
|
||||||
|
value = models.IntegerField()
|
||||||
|
message = models.CharField(max_length=250)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class PromoCodeActivation(models.Model):
|
||||||
|
activated = models.DateTimeField(auto_now_add=True)
|
||||||
|
promocode = models.ForeignKey(
|
||||||
|
"PromoCode", related_name="activations", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
"users.User", related_name="promocode_activations", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.promocode} activation by {self.user}"
|
64
akarpov/tools/promocodes/services.py
Normal file
64
akarpov/tools/promocodes/services.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import structlog
|
||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
from akarpov.tools.promocodes.models import PromoCode, PromoCodeActivation
|
||||||
|
from akarpov.users.models import User
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def activate_promocode(code: str, user: User) -> (str, bool):
|
||||||
|
try:
|
||||||
|
promo = PromoCode.objects.get(promo=code)
|
||||||
|
except PromoCode.DoesNotExist:
|
||||||
|
return "Promocode doesn't exist", False
|
||||||
|
|
||||||
|
if promo.type == PromoCode.PromoCodeType.single:
|
||||||
|
if PromoCodeActivation.objects.filter(promocode=promo).exists():
|
||||||
|
return "Promocode is already activated", False
|
||||||
|
elif promo.type == PromoCode.PromoCodeType.multiuser:
|
||||||
|
if PromoCodeActivation.objects.filter(promocode=promo, user=user).exists():
|
||||||
|
return "Promocode is already activated", False
|
||||||
|
try:
|
||||||
|
model = apps.get_model(app_label=promo.app_name, model_name=promo.model)
|
||||||
|
except LookupError:
|
||||||
|
logger.error(
|
||||||
|
f"can't activate promocode {code} for {promo.model} {promo.app_name} {promo.field}"
|
||||||
|
)
|
||||||
|
return "Somthing went wrong, we are already working on it", False
|
||||||
|
|
||||||
|
if not hasattr(model, promo.field):
|
||||||
|
logger.error(
|
||||||
|
f"can't activate promocode {code} for {promo.model} {promo.app_name} {promo.field}"
|
||||||
|
)
|
||||||
|
return "Somthing went wrong, we are already working on it", False
|
||||||
|
if model is User:
|
||||||
|
try:
|
||||||
|
setattr(user, promo.field, getattr(user, promo.field) + promo.value)
|
||||||
|
user.save()
|
||||||
|
PromoCodeActivation.objects.create(promocode=promo, user=user)
|
||||||
|
return promo.message, True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"can't activate promocode {code} for {promo.model} {promo.app_name} {promo.field}, {e}"
|
||||||
|
)
|
||||||
|
return "Somthing went wrong, we are already working on it", False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
usr_field = ""
|
||||||
|
if hasattr(model, "user"):
|
||||||
|
usr_field = "user"
|
||||||
|
elif hasattr(model, "creator"):
|
||||||
|
usr_field = "creator"
|
||||||
|
elif hasattr(model, "owner"):
|
||||||
|
usr_field = "owner"
|
||||||
|
obj = model.objects.filter({usr_field: user}).last()
|
||||||
|
setattr(obj, promo.field, getattr(obj, promo.field) + promo.value)
|
||||||
|
obj.save()
|
||||||
|
PromoCodeActivation.objects.create(promocode=promo, user=user)
|
||||||
|
return promo.message, True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"can't activate promocode {code} for {promo.model} {promo.app_name} {promo.field}, {e}"
|
||||||
|
)
|
||||||
|
return "Somthing went wrong, we are already working on it", False
|
9
akarpov/tools/promocodes/urls.py
Normal file
9
akarpov/tools/promocodes/urls.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from akarpov.tools.promocodes.views import activate_promo_code
|
||||||
|
|
||||||
|
app_name = "promocodes"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", activate_promo_code, name="activate"),
|
||||||
|
]
|
26
akarpov/tools/promocodes/views.py
Normal file
26
akarpov/tools/promocodes/views.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.views import generic
|
||||||
|
|
||||||
|
from akarpov.tools.promocodes.forms import PromoCodeForm
|
||||||
|
from akarpov.tools.promocodes.services import activate_promocode
|
||||||
|
|
||||||
|
|
||||||
|
class ActivatePromoCodeView(LoginRequiredMixin, generic.FormView):
|
||||||
|
form_class = PromoCodeForm
|
||||||
|
template_name = "tools/promocodes/activate.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context["message"] = ""
|
||||||
|
context["status"] = False
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
msg, status = activate_promocode(form.data["promocode"], self.request.user)
|
||||||
|
context = self.get_context_data(form=form)
|
||||||
|
context["message"] = msg
|
||||||
|
context["status"] = status
|
||||||
|
return self.render_to_response(context=context)
|
||||||
|
|
||||||
|
|
||||||
|
activate_promo_code = ActivatePromoCodeView.as_view()
|
|
@ -3,5 +3,8 @@
|
||||||
app_name = "tools"
|
app_name = "tools"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("qr/", include("akarpov.tools.qr.urls", namespace="qr")),
|
path("qr/", include("akarpov.tools.qr.urls", namespace="qr")),
|
||||||
|
path(
|
||||||
|
"promocodes/", include("akarpov.tools.promocodes.urls", namespace="promocodes")
|
||||||
|
),
|
||||||
path("shortener/", include("akarpov.tools.shortener.urls", namespace="shortener")),
|
path("shortener/", include("akarpov.tools.shortener.urls", namespace="shortener")),
|
||||||
]
|
]
|
||||||
|
|
|
@ -149,6 +149,7 @@
|
||||||
"akarpov.test_platform",
|
"akarpov.test_platform",
|
||||||
"akarpov.tools.shortener",
|
"akarpov.tools.shortener",
|
||||||
"akarpov.tools.qr",
|
"akarpov.tools.qr",
|
||||||
|
"akarpov.tools.promocodes",
|
||||||
]
|
]
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
|
@ -441,7 +442,6 @@
|
||||||
|
|
||||||
# django-rest-framework
|
# django-rest-framework
|
||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
# 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.authentication.SessionAuthentication",
|
||||||
|
@ -450,12 +450,8 @@
|
||||||
"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",
|
||||||
}
|
}
|
||||||
|
|
||||||
# django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup
|
|
||||||
CORS_URLS_REGEX = r"^/api/.*$"
|
CORS_URLS_REGEX = r"^/api/.*$"
|
||||||
|
|
||||||
# By Default swagger ui is available only to admin user(s). You can change permission classes to change that
|
|
||||||
# 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]",
|
"SCHEMA_PATH_PREFIX": "/api/v[0-9]",
|
||||||
|
@ -467,6 +463,7 @@
|
||||||
{"url": "https://akarpov.ru", "description": "Production server"},
|
{"url": "https://akarpov.ru", "description": "Production server"},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# CKEDITOR
|
# CKEDITOR
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
CKEDITOR_UPLOAD_PATH = "uploads/"
|
CKEDITOR_UPLOAD_PATH = "uploads/"
|
||||||
|
@ -546,3 +543,23 @@
|
||||||
"map.provider": "openstreetmap",
|
"map.provider": "openstreetmap",
|
||||||
"search.provider": "nominatim",
|
"search.provider": "nominatim",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# SENTRY
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
dsn = env("SENTRY_DSN", default="")
|
||||||
|
if dsn:
|
||||||
|
import sentry_sdk
|
||||||
|
from sentry_sdk.integrations.django import DjangoIntegration
|
||||||
|
|
||||||
|
sentry_sdk.init(
|
||||||
|
dsn=dsn,
|
||||||
|
traces_sample_rate=1.0,
|
||||||
|
integrations=[
|
||||||
|
DjangoIntegration(
|
||||||
|
transaction_style="url",
|
||||||
|
middleware_spans=True,
|
||||||
|
signals_spans=True,
|
||||||
|
cache_spans=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
1095
poetry.lock
generated
1095
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user