2025.4 BETA
This commit is contained in:
parent
c09c0d8753
commit
3fbe6883c7
189 changed files with 3768 additions and 3905 deletions
|
|
@ -75,7 +75,7 @@ static/
|
|||
media/
|
||||
!engine/core/static
|
||||
!engine/blog/static
|
||||
!engine/authv/static
|
||||
!engine/vibes_auth/static
|
||||
!engine/payments/static
|
||||
|
||||
# Environment file
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -111,7 +111,7 @@ media/
|
|||
# Allow checked-in static from apps
|
||||
!engine/core/static/
|
||||
!engine/payments/static/
|
||||
!engine/authv/static/
|
||||
!engine/vibes_auth/static/
|
||||
!engine/blog/static/
|
||||
|
||||
# Webassets
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from engine.authv.messaging.models import ChatMessage, ChatThread, ThreadStatus
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@admin.register(ChatThread)
|
||||
class ChatThreadAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"uuid",
|
||||
"user",
|
||||
"email",
|
||||
"assigned_to",
|
||||
"status",
|
||||
"last_message_at",
|
||||
"is_active",
|
||||
"created",
|
||||
"modified",
|
||||
)
|
||||
list_filter = (
|
||||
"status",
|
||||
"is_active",
|
||||
("assigned_to", admin.EmptyFieldListFilter),
|
||||
)
|
||||
search_fields = ("uuid", "email", "user__email", "user__username")
|
||||
autocomplete_fields = ("user", "assigned_to")
|
||||
actions = (
|
||||
"close_threads",
|
||||
"open_threads",
|
||||
"delete_selected",
|
||||
)
|
||||
readonly_fields = ("created", "modified")
|
||||
|
||||
@admin.action(description=_("Close selected threads"))
|
||||
def close_threads(self, request, queryset): # type: ignore[no-untyped-def]
|
||||
queryset.update(status=ThreadStatus.CLOSED)
|
||||
|
||||
@admin.action(description=_("Open selected threads"))
|
||||
def open_threads(self, request, queryset): # type: ignore[no-untyped-def]
|
||||
queryset.update(status=ThreadStatus.OPEN)
|
||||
|
||||
|
||||
@admin.register(ChatMessage)
|
||||
class ChatMessageAdmin(admin.ModelAdmin):
|
||||
list_display = ("uuid", "thread", "sender_type", "sender_user", "sent_at")
|
||||
list_filter = ("sender_type",)
|
||||
search_fields = ("uuid", "thread__uuid", "sender_user__email")
|
||||
autocomplete_fields = ("thread", "sender_user")
|
||||
readonly_fields = ("created", "modified")
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-28 11:56
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-28 12:07
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-28 12:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-29 13:09
|
||||
|
||||
import markdown_field.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-09-19 11:59
|
||||
|
||||
import markdown_field.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-10-07 12:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-10-21 09:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from constance import config
|
||||
|
|
@ -165,7 +164,7 @@ class AmoCRM:
|
|||
def process_order_changes(self, order: Order) -> str:
|
||||
with transaction.atomic():
|
||||
try:
|
||||
link: Optional[OrderCrmLink] = OrderCrmLink.objects.get(order=order)
|
||||
link: OrderCrmLink | None = OrderCrmLink.objects.get(order=order)
|
||||
except OrderCrmLink.MultipleObjectsReturned:
|
||||
link = OrderCrmLink.objects.filter(order=order).first()
|
||||
except OrderCrmLink.DoesNotExist:
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@ from engine.core.utils import get_project_parameters
|
|||
from engine.core.utils.languages import get_flag_by_language
|
||||
from engine.core.utils.messages import permission_denied_message
|
||||
from engine.payments.graphene.mutations import Deposit
|
||||
from engine.authv.filters import UserFilter
|
||||
from engine.authv.graphene.mutations import (
|
||||
from engine.vibes_auth.filters import UserFilter
|
||||
from engine.vibes_auth.graphene.mutations import (
|
||||
ActivateUser,
|
||||
ConfirmResetPassword,
|
||||
CreateUser,
|
||||
|
|
@ -99,8 +99,8 @@ from engine.authv.graphene.mutations import (
|
|||
UploadAvatar,
|
||||
VerifyJSONWebToken,
|
||||
)
|
||||
from engine.authv.graphene.object_types import UserType
|
||||
from engine.authv.models import User
|
||||
from engine.vibes_auth.graphene.object_types import UserType
|
||||
from engine.vibes_auth.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ class Query(ObjectType):
|
|||
|
||||
@staticmethod
|
||||
def resolve_users(_parent, info, **_kwargs):
|
||||
if info.context.user.has_perm("authv.view_user"):
|
||||
if info.context.user.has_perm("vibes_auth.view_user"):
|
||||
return User.objects.all()
|
||||
users = User.objects.filter(uuid=info.context.user.pk)
|
||||
return users if users.exists() else User.objects.none()
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -662,7 +662,7 @@ msgstr ""
|
|||
"deepl_translate -l en-gb -l ar-ar -l cs-cz -l da-dk -l de-de-de -l en-us -l "
|
||||
"es-es -l fr-fr -l hi-in -l it-it -l ja-jp -l kk-kz -l nl-nl -l nl-nl -l pl-"
|
||||
"pl -l pt-br -l ro-ro -l ru-ru -l zh-hans -l zh-ans -a core -a geo -a geo -a "
|
||||
"payments -a authv -a blog"
|
||||
"payments -a vibes_auth -a blog"
|
||||
|
||||
#: engine/core/docs/drf/viewsets.py:592
|
||||
msgid "limit the results amount, 1 < limit < 10, default: 5"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -711,7 +711,7 @@ msgstr ""
|
|||
"docker compose exec app poetry run python manage.py deepl_translate -l en-gb"
|
||||
" -l ar-ar -l cs-cz -l da-dk -l de-de -l en-us -l es-es -l fr-fr -l hi-in -l "
|
||||
"it-it -l ja-jp -l kk-kz -l nl-nl -l pl -l pt-br -l ro-ro -l ru-ru -l zh-hans"
|
||||
" -a core -a geo -a payments -a authv -a blog"
|
||||
" -a core -a geo -a payments -a vibes_auth -a blog"
|
||||
|
||||
#: engine/core/docs/drf/viewsets.py:592
|
||||
msgid "limit the results amount, 1 < limit < 10, default: 5"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -647,7 +647,7 @@ msgstr ""
|
|||
"docker compose exec app poetry run python manage.py deepl_translate -l en-gb"
|
||||
" -l ar-ar -l cs-cz -l da-dk -l de-de -l en-us -l es-es -l fr-fr -l hi-in -l "
|
||||
"it-it -l ja-jp -l kk-kz -l n-nl -l pl-pl -l pt-br -l ro-ro -l ru-ru -l zh-"
|
||||
"hans -a core -a geo -a payments -a authv -a blog"
|
||||
"hans -a core -a geo -a payments -a vibes_auth -a blog"
|
||||
|
||||
#: engine/core/docs/drf/viewsets.py:592
|
||||
msgid "limit the results amount, 1 < limit < 10, default: 5"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -700,7 +700,7 @@ msgstr ""
|
|||
"docker compose exec app poetry run python manage.py deepl_translate -l en-gb"
|
||||
" -l ar-ar -l cs-cz -l da-dk -l de-de -l en-us -l es-es -l fr-fr -l hi-in -l "
|
||||
"it-it -l ja-jp -l kk-kz -l nl-nl -l pl-pl -l pt-br -l ro-ro -l ru-ru -l zh-"
|
||||
"hans -a core -a geo -a plăți -a authv -a blog"
|
||||
"hans -a core -a geo -a plăți -a vibes_auth -a blog"
|
||||
|
||||
#: engine/core/docs/drf/viewsets.py:592
|
||||
msgid "limit the results amount, 1 < limit < 10, default: 5"
|
||||
|
|
|
|||
|
|
@ -39,4 +39,4 @@ DEEPL_TARGET_LANGUAGES_MAPPING = {
|
|||
"zh-hans": "ZH-HANS",
|
||||
}
|
||||
|
||||
TRANSLATABLE_APPS = ["core", "authv", "blog", "payments", "root"]
|
||||
TRANSLATABLE_APPS = ["core", "vibes_auth", "blog", "payments", "root"]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from typing import Any
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from engine.core.models import Vendor
|
||||
from engine.authv.models import Group
|
||||
from engine.vibes_auth.models import Group
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-10 11:38
|
||||
|
||||
import uuid
|
||||
|
||||
import django.core.validators
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-10 11:38
|
||||
|
||||
import django.contrib.postgres.indexes
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-10 12:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-10 20:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-16 12:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-20 15:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-24 14:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import engine.core.validators
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-04-10 15:55
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-04-15 09:15
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-17 14:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-18 11:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import engine.core.validators
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-28 11:56
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-30 13:29
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-30 13:42
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-30 14:03
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-05-05 12:56
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-05-06 13:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import engine.core.utils
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-05-20 04:57
|
||||
|
||||
import uuid
|
||||
|
||||
import django.contrib.gis.db.models.fields
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-05-20 19:06
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-05-21 09:35
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-05-28 19:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-10 02:42
|
||||
|
||||
import uuid
|
||||
|
||||
import django_extensions.db.fields
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-17 08:25
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-18 19:21
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-20 02:25
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-21 16:04
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-21 16:29
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-21 16:34
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-21 16:40
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-21 17:14
|
||||
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-21 17:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
import engine.core.utils.db
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-21 21:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-06-29 13:09
|
||||
|
||||
import engine.core.utils.db
|
||||
import django_extensions.db.fields
|
||||
from django.db import migrations
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-07-28 08:55
|
||||
|
||||
import engine.core.utils
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-09-01 17:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-09-01 17:36
|
||||
|
||||
import engine.core.utils
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-09-06 22:05
|
||||
|
||||
import django.db.models.deletion
|
||||
import django_extensions.db.fields
|
||||
import django_prometheus.models
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-09-06 22:16
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-09-19 11:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-09-22 11:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-10-13 12:15
|
||||
|
||||
import engine.core.utils
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-10-17 11:27
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-10-18 19:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-10-18 21:16
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-10-21 09:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2.7 on 2025-10-24 23:17
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2.7 on 2025-10-26 14:10
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2.7 on 2025-10-26 16:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2.7 on 2025-11-01 23:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import datetime
|
|||
import json
|
||||
import logging
|
||||
from contextlib import suppress
|
||||
from typing import Any, Optional, Self, Iterable
|
||||
from typing import Any, Iterable, Self
|
||||
|
||||
from constance import config
|
||||
from django.conf import settings
|
||||
|
|
@ -36,8 +36,8 @@ from django.db.models import (
|
|||
TextField,
|
||||
URLField,
|
||||
)
|
||||
from django.db.models.indexes import Index
|
||||
from django.db.models.functions import Length
|
||||
from django.db.models.indexes import Index
|
||||
from django.http import Http404
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import force_bytes
|
||||
|
|
@ -64,8 +64,8 @@ from engine.core.utils import (
|
|||
from engine.core.utils.db import TweakedAutoSlugField, unicode_slugify_function
|
||||
from engine.core.utils.lists import FAILED_STATUSES
|
||||
from engine.core.validators import validate_category_image_dimensions
|
||||
from evibes.utils.misc import create_object
|
||||
from engine.payments.models import Transaction
|
||||
from evibes.utils.misc import create_object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -369,10 +369,10 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel): #
|
|||
.distinct()
|
||||
)
|
||||
|
||||
per_cat: dict[int, dict[int, dict[str, Any]]] = {}
|
||||
per_cat: dict[Any, Any] = {}
|
||||
for cat_id, attr_id, attr_name, value_type, value in rows:
|
||||
cat_bucket = per_cat.get(cat_id)
|
||||
if cat_bucket is None:
|
||||
cat_bucket = per_cat.get(cat_id, "")
|
||||
if not cat_bucket:
|
||||
cat_bucket = {}
|
||||
per_cat[cat_id] = cat_bucket
|
||||
bucket = cat_bucket.get(attr_id)
|
||||
|
|
@ -405,7 +405,7 @@ class Category(ExportModelOperationsMixin("category"), NiceModel, MPTTModel): #
|
|||
.distinct()
|
||||
)
|
||||
|
||||
by_attr: dict[int, dict] = {}
|
||||
by_attr: dict[Any, Any] = {}
|
||||
for attr_id, attr_name, value_type, value in rows:
|
||||
bucket = by_attr.get(attr_id)
|
||||
if bucket is None:
|
||||
|
|
@ -913,7 +913,7 @@ class Wishlist(ExportModelOperationsMixin("wishlist"), NiceModel): # type: igno
|
|||
verbose_name=_("wishlisted products"),
|
||||
)
|
||||
user = OneToOneField(
|
||||
"authv.User",
|
||||
"vibes_auth.User",
|
||||
on_delete=CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
|
|
@ -1107,7 +1107,7 @@ class PromoCode(ExportModelOperationsMixin("promocode"), NiceModel): # type: ig
|
|||
verbose_name=_("usage timestamp"),
|
||||
)
|
||||
user = ForeignKey(
|
||||
"authv.User",
|
||||
"vibes_auth.User",
|
||||
on_delete=CASCADE,
|
||||
help_text=_("user assigned to this promocode if applicable"),
|
||||
verbose_name=_("assigned user"),
|
||||
|
|
@ -1236,7 +1236,7 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
|||
verbose_name=_("attributes"),
|
||||
)
|
||||
user = ForeignKey(
|
||||
"authv.User",
|
||||
"vibes_auth.User",
|
||||
on_delete=CASCADE,
|
||||
help_text=_("the user who placed the order"),
|
||||
verbose_name=_("user"),
|
||||
|
|
@ -1680,6 +1680,49 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
|
|||
return None
|
||||
|
||||
|
||||
class Feedback(ExportModelOperationsMixin("feedback"), NiceModel): # type: ignore [misc]
|
||||
__doc__ = _( # type: ignore
|
||||
"Manages user feedback for products. "
|
||||
"This class is designed to capture and store user feedback for specific products "
|
||||
"that they have purchased. It contains attributes to store user comments, "
|
||||
"a reference to the related product in the order, and a user-assigned rating. The "
|
||||
"class uses database fields to effectively model and manage feedback data."
|
||||
)
|
||||
|
||||
is_publicly_visible = True
|
||||
|
||||
comment = TextField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_("user-provided comments about their experience with the product"),
|
||||
verbose_name=_("feedback comments"),
|
||||
)
|
||||
order_product = OneToOneField(
|
||||
"core.OrderProduct",
|
||||
on_delete=CASCADE,
|
||||
blank=False,
|
||||
null=False,
|
||||
help_text=_("references the specific product in an order that this feedback is about"),
|
||||
verbose_name=_("related order product"),
|
||||
)
|
||||
rating = FloatField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_("user-assigned rating for the product"),
|
||||
verbose_name=_("product rating"),
|
||||
validators=[MinValueValidator(0), MaxValueValidator(10)],
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.order_product and self.order_product.order and self.order_product.order.user:
|
||||
return f"{self.rating} by {self.order_product.order.user.email}"
|
||||
return f"{self.rating} | {self.uuid}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("feedback")
|
||||
verbose_name_plural = _("feedbacks")
|
||||
|
||||
|
||||
class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # type: ignore [misc]
|
||||
__doc__ = _( # type: ignore
|
||||
"Represents products associated with orders and their attributes. "
|
||||
|
|
@ -1810,7 +1853,7 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # t
|
|||
return DigitalAssetDownload.objects.create(order_product=self).url
|
||||
return ""
|
||||
|
||||
def do_feedback(self, rating=10, comment="", action="add") -> Optional["Feedback"] | int:
|
||||
def do_feedback(self, rating=10, comment="", action="add") -> Feedback | int | None:
|
||||
if not self.order:
|
||||
raise ValueError(_("order product must have an order"))
|
||||
if action not in ["add", "remove"]:
|
||||
|
|
@ -1912,46 +1955,3 @@ class DigitalAssetDownload(ExportModelOperationsMixin("attribute_group"), NiceMo
|
|||
return (
|
||||
f"https://api.{config.BASE_DOMAIN}/download/{urlsafe_base64_encode(force_bytes(self.order_product.uuid))}"
|
||||
)
|
||||
|
||||
|
||||
class Feedback(ExportModelOperationsMixin("feedback"), NiceModel): # type: ignore [misc]
|
||||
__doc__ = _( # type: ignore
|
||||
"Manages user feedback for products. "
|
||||
"This class is designed to capture and store user feedback for specific products "
|
||||
"that they have purchased. It contains attributes to store user comments, "
|
||||
"a reference to the related product in the order, and a user-assigned rating. The "
|
||||
"class uses database fields to effectively model and manage feedback data."
|
||||
)
|
||||
|
||||
is_publicly_visible = True
|
||||
|
||||
comment = TextField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_("user-provided comments about their experience with the product"),
|
||||
verbose_name=_("feedback comments"),
|
||||
)
|
||||
order_product = OneToOneField(
|
||||
"core.OrderProduct",
|
||||
on_delete=CASCADE,
|
||||
blank=False,
|
||||
null=False,
|
||||
help_text=_("references the specific product in an order that this feedback is about"),
|
||||
verbose_name=_("related order product"),
|
||||
)
|
||||
rating = FloatField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_("user-assigned rating for the product"),
|
||||
verbose_name=_("product rating"),
|
||||
validators=[MinValueValidator(0), MaxValueValidator(10)],
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.order_product and self.order_product.order and self.order_product.order.user:
|
||||
return f"{self.rating} by {self.order_product.order.user.email}"
|
||||
return f"{self.rating} | {self.uuid}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("feedback")
|
||||
verbose_name_plural = _("feedbacks")
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class CategoryDetailSerializer(ModelSerializer):
|
|||
else:
|
||||
children = obj.children.filter(is_active=True)
|
||||
|
||||
return CategorySimpleSerializer(children, many=True, context=self.context).data if obj.children.exists() else []
|
||||
return CategorySimpleSerializer(children, many=True, context=self.context).data if obj.children.exists() else [] # type: ignore [return-value]
|
||||
|
||||
|
||||
class BrandDetailSerializer(ModelSerializer):
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class CategorySimpleSerializer(ModelSerializer): # type: ignore [type-arg]
|
|||
else:
|
||||
children = obj.children.filter(is_active=True)
|
||||
|
||||
return CategorySimpleSerializer(children, many=True, context=self.context).data if obj.children.exists() else []
|
||||
return CategorySimpleSerializer(children, many=True, context=self.context).data if obj.children.exists() else [] # type: ignore [return-value]
|
||||
|
||||
|
||||
class BrandSimpleSerializer(ModelSerializer): # type: ignore [type-arg]
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from engine.core.utils import (
|
|||
)
|
||||
from engine.core.utils.emailing import send_order_created_email, send_order_finished_email, send_promocode_created_email
|
||||
from evibes.utils.misc import create_object
|
||||
from engine.authv.models import User
|
||||
from engine.vibes_auth.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import shutil
|
|||
import uuid
|
||||
from datetime import date, timedelta
|
||||
from time import sleep
|
||||
from typing import Any, Type
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from celery.app import shared_task
|
||||
|
|
@ -40,10 +40,11 @@ def update_products_task() -> tuple[bool, str]:
|
|||
|
||||
if not update_products_task_running:
|
||||
cache.set("update_products_task_running", True, 86400)
|
||||
vendors: list[Type[AbstractVendor]] = get_vendors_integrations()
|
||||
vendors: list[AbstractVendor] = get_vendors_integrations()
|
||||
|
||||
for vendor in vendors:
|
||||
try:
|
||||
# noinspection PyArgumentList
|
||||
vendor.update_stock()
|
||||
except VendorInactiveError:
|
||||
logger.info("Skipping %s due to inactivity", str(vendor))
|
||||
|
|
@ -70,9 +71,10 @@ def update_orderproducts_task() -> tuple[bool, str]:
|
|||
message confirming the successful execution of the task.
|
||||
:rtype: Tuple[bool, str]
|
||||
"""
|
||||
vendors: list[Type[AbstractVendor]] = get_vendors_integrations()
|
||||
vendors: list[AbstractVendor] = get_vendors_integrations()
|
||||
|
||||
for vendor in vendors:
|
||||
# noinspection PyArgumentList
|
||||
vendor.update_order_products_statuses()
|
||||
|
||||
return True, "Success"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from graphene import Context
|
||||
from rest_framework.request import Request
|
||||
|
||||
from engine.authv.models import User
|
||||
from engine.vibes_auth.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import logging
|
||||
from typing import Type
|
||||
|
||||
from engine.core.models import Vendor
|
||||
from engine.core.vendors import AbstractVendor
|
||||
|
|
@ -8,8 +7,8 @@ from evibes.utils.misc import create_object
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_vendors_integrations(name: str | None = None) -> list[Type[AbstractVendor]]:
|
||||
vendors_integrations: list[Type[AbstractVendor]] = []
|
||||
def get_vendors_integrations(name: str | None = None) -> list[AbstractVendor]:
|
||||
vendors_integrations: list[AbstractVendor] = []
|
||||
|
||||
vendors = Vendor.objects.filter(is_active=True, integration_path__isnull=False)
|
||||
if name:
|
||||
|
|
@ -17,7 +16,7 @@ def get_vendors_integrations(name: str | None = None) -> list[Type[AbstractVendo
|
|||
|
||||
for vendor in vendors:
|
||||
try:
|
||||
module_name, class_name = vendor.integration_path.rsplit(".", 1)
|
||||
module_name, class_name = vendor.integration_path.rsplit(".", 1) # type: ignore [union-attr]
|
||||
vendors_integrations.append(create_object(module_name, class_name))
|
||||
except Exception as e:
|
||||
logger.warning("Couldn't load integration for vendor %s: %s", vendor.name, e)
|
||||
|
|
|
|||
2
engine/core/vendors/__init__.py
vendored
2
engine/core/vendors/__init__.py
vendored
|
|
@ -95,7 +95,7 @@ class AbstractVendor:
|
|||
deletions on inactive objects.
|
||||
"""
|
||||
|
||||
def __init__(self, vendor_name: str | None = None, currency: str = "USD") -> None:
|
||||
def __init__(self, vendor_name: str = "", currency: str = "USD") -> None:
|
||||
self.vendor_name = vendor_name
|
||||
self.currency = currency
|
||||
self.blocked_attributes: list[Any] = []
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-10 11:38
|
||||
|
||||
import uuid
|
||||
|
||||
import django_extensions.db.fields
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.5 on 2025-03-10 11:38
|
||||
|
||||
import django.contrib.postgres.indexes
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-29 11:32
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.1.8 on 2025-04-30 13:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2 on 2025-10-21 09:24
|
||||
|
||||
import django.db.models.deletion
|
||||
import django_extensions.db.fields
|
||||
import uuid
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Generated by Django 5.2.7 on 2025-10-24 23:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from datetime import datetime, time
|
||||
from typing import Type
|
||||
|
||||
from constance import config
|
||||
from django.conf import settings
|
||||
|
|
@ -9,20 +8,20 @@ from django.db.models import (
|
|||
CharField,
|
||||
FloatField,
|
||||
ForeignKey,
|
||||
Index,
|
||||
JSONField,
|
||||
OneToOneField,
|
||||
PositiveIntegerField,
|
||||
QuerySet,
|
||||
Sum,
|
||||
Index,
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from engine.core.abstract import NiceModel
|
||||
from evibes.utils.misc import create_object
|
||||
from engine.payments.gateways import AbstractGateway
|
||||
from engine.payments.managers import GatewayManager
|
||||
from evibes.utils.misc import create_object
|
||||
|
||||
|
||||
class Transaction(NiceModel):
|
||||
|
|
@ -172,7 +171,7 @@ class Gateway(NiceModel):
|
|||
def can_be_used(self, value: bool):
|
||||
self.__dict__["can_be_used"] = value
|
||||
|
||||
def get_integration_class_object(self, raise_exc: bool = True) -> Type[AbstractGateway] | None:
|
||||
def get_integration_class_object(self, raise_exc: bool = True) -> AbstractGateway | None:
|
||||
if not self.integration_path:
|
||||
if raise_exc:
|
||||
raise ValueError(_("gateway integration path is not set"))
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from django.dispatch import receiver
|
|||
|
||||
from engine.payments.models import Balance, Transaction, Gateway
|
||||
from engine.payments.utils.emailing import balance_deposit_email
|
||||
from engine.authv.models import User
|
||||
from engine.vibes_auth.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ def process_transaction_changes(instance: Transaction, created: bool, **kwargs:
|
|||
instance.gateway = Gateway.objects.can_be_used().first()
|
||||
try:
|
||||
gateway = instance.gateway.get_integration_class_object()
|
||||
gateway.process_transaction(instance)
|
||||
gateway.process_transaction(instance) # type: ignore [union-attr]
|
||||
except Exception as e:
|
||||
instance.process = {"status": "ERRORED", "error": str(e)}
|
||||
logger.error(f"Error processing transaction {instance.uuid}: {e}\n{traceback.format_exc()}")
|
||||
|
|
|
|||
|
|
@ -28,8 +28,9 @@ from rest_framework_simplejwt.token_blacklist.models import (
|
|||
from engine.core.admin import ActivationActionsMixin
|
||||
from engine.core.models import Order
|
||||
from engine.payments.models import Balance
|
||||
from engine.authv.forms import UserForm
|
||||
from engine.authv.models import BlacklistedToken, Group, OutstandingToken, User
|
||||
from engine.vibes_auth.forms import UserForm
|
||||
from engine.vibes_auth.messaging.models import ChatMessage, ChatThread, ThreadStatus
|
||||
from engine.vibes_auth.models import BlacklistedToken, Group, OutstandingToken, User
|
||||
|
||||
|
||||
class BalanceInline(admin.TabularInline): # type: ignore [type-arg]
|
||||
|
|
@ -113,6 +114,52 @@ class UserAdmin(ActivationActionsMixin, BaseUserAdmin): # type: ignore [misc, t
|
|||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@admin.register(ChatThread)
|
||||
class ChatThreadAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"uuid",
|
||||
"user",
|
||||
"email",
|
||||
"assigned_to",
|
||||
"status",
|
||||
"last_message_at",
|
||||
"is_active",
|
||||
"created",
|
||||
"modified",
|
||||
)
|
||||
list_filter = (
|
||||
"status",
|
||||
"is_active",
|
||||
("assigned_to", admin.EmptyFieldListFilter),
|
||||
)
|
||||
search_fields = ("uuid", "email", "user__email", "user__username")
|
||||
autocomplete_fields = ("user", "assigned_to")
|
||||
actions = (
|
||||
"close_threads",
|
||||
"open_threads",
|
||||
"delete_selected",
|
||||
)
|
||||
readonly_fields = ("created", "modified")
|
||||
|
||||
@admin.action(description=_("Close selected threads"))
|
||||
def close_threads(self, request, queryset): # type: ignore[no-untyped-def]
|
||||
queryset.update(status=ThreadStatus.CLOSED)
|
||||
|
||||
@admin.action(description=_("Open selected threads"))
|
||||
def open_threads(self, request, queryset): # type: ignore[no-untyped-def]
|
||||
queryset.update(status=ThreadStatus.OPEN)
|
||||
|
||||
|
||||
@admin.register(ChatMessage)
|
||||
class ChatMessageAdmin(admin.ModelAdmin):
|
||||
list_display = ("uuid", "thread", "sender_type", "sender_user", "sent_at")
|
||||
list_filter = ("sender_type",)
|
||||
search_fields = ("uuid", "thread__uuid", "sender_user__email")
|
||||
autocomplete_fields = ("thread", "sender_user")
|
||||
readonly_fields = ("created", "modified")
|
||||
|
||||
|
||||
class GroupAdmin(BaseGroupAdmin):
|
||||
pass
|
||||
|
||||
|
|
@ -4,11 +4,11 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
class VibesAuthConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "engine.authv"
|
||||
name = "engine.vibes_auth"
|
||||
verbose_name = _("authentication")
|
||||
icon = "fa fa-solid fa-user"
|
||||
priority = 89
|
||||
hide = False
|
||||
|
||||
def ready(self) -> None:
|
||||
import engine.authv.signals # noqa: F401
|
||||
import engine.vibes_auth.signals # noqa: F401
|
||||
|
|
@ -3,7 +3,7 @@ from drf_spectacular.utils import extend_schema, inline_serializer
|
|||
from rest_framework import serializers, status
|
||||
|
||||
from engine.core.docs.drf import error
|
||||
from engine.authv.serializers import (
|
||||
from engine.vibes_auth.serializers import (
|
||||
TokenObtainPairSerializer,
|
||||
TokenRefreshSerializer,
|
||||
TokenVerifySerializer,
|
||||
|
|
@ -3,7 +3,7 @@ from drf_spectacular.utils import extend_schema
|
|||
from rest_framework import status
|
||||
|
||||
from engine.core.docs.drf import BASE_ERRORS
|
||||
from engine.authv.serializers import (
|
||||
from engine.vibes_auth.serializers import (
|
||||
ActivateEmailSerializer,
|
||||
ConfirmPasswordResetSerializer,
|
||||
MergeRecentlyViewedSerializer,
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import django_filters
|
||||
|
||||
from engine.authv.models import User
|
||||
from engine.vibes_auth.models import User
|
||||
|
||||
|
||||
class UserFilter(django_filters.FilterSet):
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from django.forms import ModelForm
|
||||
|
||||
from engine.core.widgets import JSONTableWidget
|
||||
from engine.authv.models import User
|
||||
from engine.vibes_auth.models import User
|
||||
|
||||
|
||||
class UserForm(ModelForm): # type: ignore [type-arg]
|
||||
|
|
@ -15,15 +15,15 @@ from graphene_file_upload.scalars import Upload
|
|||
|
||||
from engine.core.graphene import BaseMutation
|
||||
from engine.core.utils.messages import permission_denied_message
|
||||
from engine.authv.graphene.object_types import UserType
|
||||
from engine.authv.models import User
|
||||
from engine.authv.serializers import (
|
||||
from engine.vibes_auth.graphene.object_types import UserType
|
||||
from engine.vibes_auth.models import User
|
||||
from engine.vibes_auth.serializers import (
|
||||
TokenObtainPairSerializer,
|
||||
TokenRefreshSerializer,
|
||||
TokenVerifySerializer,
|
||||
)
|
||||
from engine.authv.utils.emailing import send_reset_password_email_task
|
||||
from engine.authv.validators import is_valid_email, is_valid_phone_number
|
||||
from engine.vibes_auth.utils.emailing import send_reset_password_email_task
|
||||
from engine.vibes_auth.validators import is_valid_email, is_valid_phone_number
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ class UpdateUser(BaseMutation):
|
|||
name = "User"
|
||||
raise Http404(_(f"{name} does not exist: {uuid}")) from dne
|
||||
|
||||
if not (info.context.user.has_perm("authv.change_user") or info.context.user == user):
|
||||
if not (info.context.user.has_perm("vibes_auth.change_user") or info.context.user == user):
|
||||
raise PermissionDenied(permission_denied_message)
|
||||
|
||||
email = kwargs.get("email")
|
||||
|
|
@ -158,7 +158,7 @@ class UpdateUser(BaseMutation):
|
|||
"is_staff",
|
||||
"is_active",
|
||||
"is_superuser",
|
||||
] or info.context.user.has_perm("authv.change_user"):
|
||||
] or info.context.user.has_perm("vibes_auth.change_user"):
|
||||
setattr(user, attr, value)
|
||||
|
||||
user.save()
|
||||
|
|
@ -174,7 +174,7 @@ class DeleteUser(BaseMutation):
|
|||
success = Boolean()
|
||||
|
||||
def mutate(self, info, uuid=None, email=None):
|
||||
if info.context.user.has_perm("authv.delete_user"):
|
||||
if info.context.user.has_perm("vibes_auth.delete_user"):
|
||||
try:
|
||||
if uuid is not None:
|
||||
User.objects.get(uuid=uuid).delete()
|
||||
|
|
@ -10,7 +10,7 @@ from engine.core.graphene.object_types import OrderType, ProductType, WishlistTy
|
|||
from engine.core.models import Product, Wishlist
|
||||
from engine.payments.graphene.object_types import BalanceType
|
||||
from engine.payments.models import Balance
|
||||
from engine.authv.models import User
|
||||
from engine.vibes_auth.models import User
|
||||
|
||||
|
||||
class GroupType(DjangoObjectType):
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue