Features: 1) Add tab support for inline admin classes; 2) Introduce new settings for taskboard URL and support contact;
Fixes: 1) Remove redundant imports from admin.py; Extra: 1) Update inline classes to inherit from TabularInline; 2) Add unfold.contrib modules to INSTALLED_APPS; 3) Reorder imports in admin.py for consistency.
This commit is contained in:
parent
43dc556063
commit
376c73ba26
6 changed files with 148 additions and 57 deletions
|
|
@ -1,10 +1,10 @@
|
||||||
from django.contrib.admin import ModelAdmin, register
|
from django.contrib.admin import register
|
||||||
from django_summernote.admin import SummernoteModelAdminMixin
|
from django_summernote.admin import SummernoteModelAdminMixin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
|
from engine.blog.models import Post, PostTag
|
||||||
from engine.core.admin import ActivationActionsMixin, FieldsetsMixin
|
from engine.core.admin import ActivationActionsMixin, FieldsetsMixin
|
||||||
|
|
||||||
from .models import Post, PostTag
|
|
||||||
|
|
||||||
|
|
||||||
@register(Post)
|
@register(Post)
|
||||||
class PostAdmin(SummernoteModelAdminMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg]
|
class PostAdmin(SummernoteModelAdminMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from constance.admin import Config
|
||||||
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
|
from constance.admin import ConstanceAdmin as BaseConstanceAdmin
|
||||||
from django.apps import AppConfig, apps
|
from django.apps import AppConfig, apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.admin import ModelAdmin, TabularInline, action, register, site
|
from django.contrib.admin import register, site
|
||||||
from django.contrib.gis.admin import GISModelAdmin
|
from django.contrib.gis.admin import GISModelAdmin
|
||||||
from django.contrib.messages import constants as messages
|
from django.contrib.messages import constants as messages
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
|
@ -15,6 +15,8 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from modeltranslation.translator import NotRegistered, translator
|
from modeltranslation.translator import NotRegistered, translator
|
||||||
from modeltranslation.utils import get_translation_fields
|
from modeltranslation.utils import get_translation_fields
|
||||||
from mptt.admin import DraggableMPTTAdmin
|
from mptt.admin import DraggableMPTTAdmin
|
||||||
|
from unfold.admin import ModelAdmin, TabularInline
|
||||||
|
from unfold.decorators import action
|
||||||
|
|
||||||
from engine.core.forms import CRMForm, OrderForm, OrderProductForm, StockForm, VendorForm
|
from engine.core.forms import CRMForm, OrderForm, OrderProductForm, StockForm, VendorForm
|
||||||
from engine.core.models import (
|
from engine.core.models import (
|
||||||
|
|
@ -65,15 +67,15 @@ class FieldsetsMixin:
|
||||||
for orig in transoptions.local_fields:
|
for orig in transoptions.local_fields:
|
||||||
translation_fields += get_translation_fields(orig)
|
translation_fields += get_translation_fields(orig)
|
||||||
if translation_fields:
|
if translation_fields:
|
||||||
fss = list(fss) + [(_("translations"), {"fields": translation_fields})] # type: ignore [list-item]
|
fss = list(fss) + [(_("translations"), {"classes": ["tab"], "fields": translation_fields})] # type: ignore [list-item]
|
||||||
return fss
|
return fss
|
||||||
|
|
||||||
if self.general_fields:
|
if self.general_fields:
|
||||||
fieldsets.append((_("general"), {"fields": self.general_fields}))
|
fieldsets.append((_("general"), {"classes": ["tab"], "fields": self.general_fields}))
|
||||||
if self.relation_fields:
|
if self.relation_fields:
|
||||||
fieldsets.append((_("relations"), {"fields": self.relation_fields}))
|
fieldsets.append((_("relations"), {"classes": ["tab"], "fields": self.relation_fields}))
|
||||||
if self.additional_fields:
|
if self.additional_fields:
|
||||||
fieldsets.append((_("additional info"), {"fields": self.additional_fields}))
|
fieldsets.append((_("additional info"), {"classes": ["tab"], "fields": self.additional_fields}))
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
|
|
||||||
meta_fields = []
|
meta_fields = []
|
||||||
|
|
@ -91,14 +93,14 @@ class FieldsetsMixin:
|
||||||
meta_fields.append("human_readable_id")
|
meta_fields.append("human_readable_id")
|
||||||
|
|
||||||
if meta_fields:
|
if meta_fields:
|
||||||
fieldsets.append((_("metadata"), {"fields": meta_fields}))
|
fieldsets.append((_("metadata"), {"classes": ["tab"], "fields": meta_fields}))
|
||||||
|
|
||||||
ts = []
|
ts = []
|
||||||
for name in ("created", "modified"):
|
for name in ("created", "modified"):
|
||||||
if any(f.name == name for f in opts.fields):
|
if any(f.name == name for f in opts.fields):
|
||||||
ts.append(name)
|
ts.append(name)
|
||||||
if ts:
|
if ts:
|
||||||
fieldsets.append((_("timestamps"), {"fields": ts, "classes": ["collapse"]}))
|
fieldsets.append((_("timestamps"), {"classes": ["tab"], "fields": ts}))
|
||||||
fieldsets = add_translations_fieldset(fieldsets) # type: ignore [arg-type, assignment]
|
fieldsets = add_translations_fieldset(fieldsets) # type: ignore [arg-type, assignment]
|
||||||
return fieldsets # type: ignore [return-value]
|
return fieldsets # type: ignore [return-value]
|
||||||
|
|
||||||
|
|
@ -140,10 +142,9 @@ class AttributeValueInline(TabularInline): # type: ignore [type-arg]
|
||||||
model = AttributeValue
|
model = AttributeValue
|
||||||
extra = 0
|
extra = 0
|
||||||
autocomplete_fields = ["attribute"]
|
autocomplete_fields = ["attribute"]
|
||||||
is_navtab = True
|
|
||||||
verbose_name = _("attribute value")
|
verbose_name = _("attribute value")
|
||||||
verbose_name_plural = _("attribute values")
|
verbose_name_plural = _("attribute values")
|
||||||
icon = "fa-solid fa-list-ul"
|
tab = True
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return super().get_queryset(request).select_related("attribute", "product")
|
return super().get_queryset(request).select_related("attribute", "product")
|
||||||
|
|
@ -152,10 +153,9 @@ class AttributeValueInline(TabularInline): # type: ignore [type-arg]
|
||||||
class ProductImageInline(TabularInline): # type: ignore [type-arg]
|
class ProductImageInline(TabularInline): # type: ignore [type-arg]
|
||||||
model = ProductImage
|
model = ProductImage
|
||||||
extra = 0
|
extra = 0
|
||||||
is_navtab = True
|
tab = True
|
||||||
verbose_name = _("image")
|
verbose_name = _("image")
|
||||||
verbose_name_plural = _("images")
|
verbose_name_plural = _("images")
|
||||||
icon = "fa-regular fa-images"
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return super().get_queryset(request).select_related("product")
|
return super().get_queryset(request).select_related("product")
|
||||||
|
|
@ -165,10 +165,9 @@ class StockInline(TabularInline): # type: ignore [type-arg]
|
||||||
model = Stock
|
model = Stock
|
||||||
extra = 0
|
extra = 0
|
||||||
form = StockForm
|
form = StockForm
|
||||||
is_navtab = True
|
tab = True
|
||||||
verbose_name = _("stock")
|
verbose_name = _("stock")
|
||||||
verbose_name_plural = _("stocks")
|
verbose_name_plural = _("stocks")
|
||||||
icon = "fa-solid fa-boxes-stacked"
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return super().get_queryset(request).select_related("vendor", "product")
|
return super().get_queryset(request).select_related("vendor", "product")
|
||||||
|
|
@ -179,10 +178,9 @@ class OrderProductInline(TabularInline): # type: ignore [type-arg]
|
||||||
extra = 0
|
extra = 0
|
||||||
readonly_fields = ("product", "quantity", "buy_price")
|
readonly_fields = ("product", "quantity", "buy_price")
|
||||||
form = OrderProductForm
|
form = OrderProductForm
|
||||||
is_navtab = True
|
|
||||||
verbose_name = _("order product")
|
verbose_name = _("order product")
|
||||||
verbose_name_plural = _("order products")
|
verbose_name_plural = _("order products")
|
||||||
icon = "fa-solid fa-boxes-packing"
|
tab = True
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return super().get_queryset(request).select_related("product").only("product__name")
|
return super().get_queryset(request).select_related("product").only("product__name")
|
||||||
|
|
@ -193,10 +191,9 @@ class CategoryChildrenInline(TabularInline): # type: ignore [type-arg]
|
||||||
fk_name = "parent"
|
fk_name = "parent"
|
||||||
extra = 0
|
extra = 0
|
||||||
fields = ("name", "description", "is_active", "image", "markup_percent")
|
fields = ("name", "description", "is_active", "image", "markup_percent")
|
||||||
is_navtab = True
|
tab = True
|
||||||
verbose_name = _("children")
|
verbose_name = _("children")
|
||||||
verbose_name_plural = _("children")
|
verbose_name_plural = _("children")
|
||||||
icon = "fa-solid fa-leaf"
|
|
||||||
|
|
||||||
|
|
||||||
@register(AttributeGroup)
|
@register(AttributeGroup)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
from django.contrib import admin
|
from django.contrib.admin import register
|
||||||
from django.contrib.admin import ModelAdmin, register
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from unfold.admin import ModelAdmin, TabularInline
|
||||||
|
|
||||||
from engine.core.admin import ActivationActionsMixin
|
from engine.core.admin import ActivationActionsMixin
|
||||||
from engine.payments.forms import GatewayForm, TransactionForm
|
from engine.payments.forms import GatewayForm, TransactionForm
|
||||||
from engine.payments.models import Balance, Transaction, Gateway
|
from engine.payments.models import Balance, Gateway, Transaction
|
||||||
|
|
||||||
|
|
||||||
class TransactionInline(admin.TabularInline): # type: ignore [type-arg]
|
class TransactionInline(TabularInline): # type: ignore [type-arg]
|
||||||
model = Transaction
|
model = Transaction
|
||||||
form = TransactionForm
|
form = TransactionForm
|
||||||
extra = 1
|
extra = 1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin import register
|
||||||
from django.contrib.auth.admin import (
|
from django.contrib.auth.admin import (
|
||||||
GroupAdmin as BaseGroupAdmin,
|
GroupAdmin as BaseGroupAdmin,
|
||||||
)
|
)
|
||||||
|
|
@ -25,6 +26,7 @@ from rest_framework_simplejwt.token_blacklist.models import (
|
||||||
from rest_framework_simplejwt.token_blacklist.models import (
|
from rest_framework_simplejwt.token_blacklist.models import (
|
||||||
OutstandingToken as BaseOutstandingToken,
|
OutstandingToken as BaseOutstandingToken,
|
||||||
)
|
)
|
||||||
|
from unfold.admin import ModelAdmin, TabularInline
|
||||||
|
|
||||||
from engine.core.admin import ActivationActionsMixin
|
from engine.core.admin import ActivationActionsMixin
|
||||||
from engine.core.models import Order
|
from engine.core.models import Order
|
||||||
|
|
@ -32,16 +34,16 @@ from engine.payments.models import Balance
|
||||||
from engine.vibes_auth.forms import UserForm
|
from engine.vibes_auth.forms import UserForm
|
||||||
from engine.vibes_auth.models import (
|
from engine.vibes_auth.models import (
|
||||||
BlacklistedToken,
|
BlacklistedToken,
|
||||||
Group,
|
|
||||||
OutstandingToken,
|
|
||||||
User,
|
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
ChatThread,
|
ChatThread,
|
||||||
|
Group,
|
||||||
|
OutstandingToken,
|
||||||
ThreadStatus,
|
ThreadStatus,
|
||||||
|
User,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BalanceInline(admin.TabularInline): # type: ignore [type-arg]
|
class BalanceInline(TabularInline): # type: ignore [type-arg]
|
||||||
model = Balance
|
model = Balance
|
||||||
can_delete = False
|
can_delete = False
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
@ -51,7 +53,7 @@ class BalanceInline(admin.TabularInline): # type: ignore [type-arg]
|
||||||
icon = "fa-solid fa-wallet"
|
icon = "fa-solid fa-wallet"
|
||||||
|
|
||||||
|
|
||||||
class OrderInline(admin.TabularInline): # type: ignore [type-arg]
|
class OrderInline(TabularInline): # type: ignore [type-arg]
|
||||||
model = Order
|
model = Order
|
||||||
extra = 0
|
extra = 0
|
||||||
verbose_name = _("order")
|
verbose_name = _("order")
|
||||||
|
|
@ -60,7 +62,7 @@ class OrderInline(admin.TabularInline): # type: ignore [type-arg]
|
||||||
icon = "fa-solid fa-cart-shopping"
|
icon = "fa-solid fa-cart-shopping"
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(ActivationActionsMixin, BaseUserAdmin): # type: ignore [misc, type-arg]
|
class UserAdmin(ActivationActionsMixin, BaseUserAdmin, ModelAdmin): # type: ignore [misc, type-arg]
|
||||||
inlines = (BalanceInline, OrderInline)
|
inlines = (BalanceInline, OrderInline)
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {"fields": ("email", "password")}),
|
(None, {"fields": ("email", "password")}),
|
||||||
|
|
@ -125,8 +127,8 @@ class UserAdmin(ActivationActionsMixin, BaseUserAdmin): # type: ignore [misc, t
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
@admin.register(ChatThread)
|
@register(ChatThread)
|
||||||
class ChatThreadAdmin(admin.ModelAdmin):
|
class ChatThreadAdmin(ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"uuid",
|
"uuid",
|
||||||
"user",
|
"user",
|
||||||
|
|
@ -161,7 +163,7 @@ class ChatThreadAdmin(admin.ModelAdmin):
|
||||||
queryset.update(status=ThreadStatus.OPEN)
|
queryset.update(status=ThreadStatus.OPEN)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ChatMessage)
|
@register(ChatMessage)
|
||||||
class ChatMessageAdmin(admin.ModelAdmin):
|
class ChatMessageAdmin(admin.ModelAdmin):
|
||||||
list_display = ("uuid", "thread", "sender_type", "sender_user", "sent_at")
|
list_display = ("uuid", "thread", "sender_type", "sender_user", "sent_at")
|
||||||
list_filter = ("sender_type",)
|
list_filter = ("sender_type",)
|
||||||
|
|
@ -170,15 +172,15 @@ class ChatMessageAdmin(admin.ModelAdmin):
|
||||||
readonly_fields = ("created", "modified")
|
readonly_fields = ("created", "modified")
|
||||||
|
|
||||||
|
|
||||||
class GroupAdmin(BaseGroupAdmin):
|
class GroupAdmin(BaseGroupAdmin, ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BlacklistedTokenAdmin(BaseBlacklistedTokenAdmin):
|
class BlacklistedTokenAdmin(BaseBlacklistedTokenAdmin, ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OutstandingTokenAdmin(BaseOutstandingTokenAdmin):
|
class OutstandingTokenAdmin(BaseOutstandingTokenAdmin, ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ EVIBES_VERSION = "2025.4"
|
||||||
RELEASE_DATE = datetime(2025, 11, 9)
|
RELEASE_DATE = datetime(2025, 11, 9)
|
||||||
|
|
||||||
PROJECT_NAME = getenv("EVIBES_PROJECT_NAME", "eVibes")
|
PROJECT_NAME = getenv("EVIBES_PROJECT_NAME", "eVibes")
|
||||||
|
TASKBOARD_URL = getenv(
|
||||||
|
"EVIBES_TASKBOARD_URL", "https://plane.wiseless.xyz/spaces/issues/dd33cb0ab9b04ef08a10f7eefae6d90c/?board=kanban"
|
||||||
|
)
|
||||||
|
SUPPORT_CONTACT = getenv("EVIBES_SUPPORT_CONTACT", "https://t.me/fureunoir")
|
||||||
|
|
||||||
BASE_DIR: Path = Path(__file__).resolve().parent.parent.parent
|
BASE_DIR: Path = Path(__file__).resolve().parent.parent.parent
|
||||||
INITIALIZED: bool = (BASE_DIR / ".initialized").exists()
|
INITIALIZED: bool = (BASE_DIR / ".initialized").exists()
|
||||||
|
|
@ -108,6 +112,8 @@ INSTALLED_APPS: list[str] = [
|
||||||
"unfold",
|
"unfold",
|
||||||
"unfold.contrib.filters",
|
"unfold.contrib.filters",
|
||||||
"unfold.contrib.forms",
|
"unfold.contrib.forms",
|
||||||
|
"unfold.contrib.inlines",
|
||||||
|
"unfold.contrib.constance",
|
||||||
"modeltranslation",
|
"modeltranslation",
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
"django.contrib.admindocs",
|
"django.contrib.admindocs",
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,114 @@
|
||||||
"""django-unfold configuration.
|
from django.templatetags.static import static
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
This module defines branding for the Django admin using django-unfold.
|
from evibes.settings.base import PROJECT_NAME, SUPPORT_CONTACT, TASKBOARD_URL
|
||||||
It intentionally avoids database-backed configuration (e.g., Constance)
|
|
||||||
so that it is safe during initial migrations and in all environments.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from evibes.settings.base import PROJECT_NAME
|
|
||||||
|
|
||||||
# See django-unfold documentation for all available options.
|
|
||||||
# Only minimal, production-safe branding is configured here.
|
|
||||||
UNFOLD = {
|
UNFOLD = {
|
||||||
# Text shown in the browser title bar and in the admin header
|
"SITE_TITLE": f"{PROJECT_NAME} Dashboard",
|
||||||
"SITE_TITLE": f"{PROJECT_NAME} Admin",
|
|
||||||
"SITE_HEADER": PROJECT_NAME,
|
"SITE_HEADER": PROJECT_NAME,
|
||||||
# Optional URL the header/brand links to (leave default admin index)
|
|
||||||
# "SITE_URL": "/admin/",
|
|
||||||
# Logos and favicon served via Django staticfiles
|
|
||||||
# Files are expected at: engine/core/static/logo.png, favicon.ico, favicon.png
|
|
||||||
# Refer to them by their static URL path (relative), no leading slash.
|
|
||||||
"SITE_LOGO": "logo.png",
|
"SITE_LOGO": "logo.png",
|
||||||
# If you use a different logo for dark theme, set SITE_LOGO_DARK
|
|
||||||
# Otherwise Unfold will reuse SITE_LOGO
|
|
||||||
# "SITE_LOGO_DARK": "logo.png",
|
|
||||||
"SITE_ICON": "favicon.ico",
|
"SITE_ICON": "favicon.ico",
|
||||||
# Sidebar behavior, search etc. (keep defaults minimal)
|
"SHOW_LANGUAGES": True,
|
||||||
# Unfold automatically respects user OS light/dark theme; no forcing here.
|
"LOGIN": {
|
||||||
|
"image": lambda request: static("logo.png"),
|
||||||
|
},
|
||||||
|
"COMMAND": {
|
||||||
|
"search_models": True,
|
||||||
|
"show_history": True,
|
||||||
|
},
|
||||||
|
"EXTENSIONS": {
|
||||||
|
"modeltranslation": {
|
||||||
|
"flags": {
|
||||||
|
"ar-ar": "🇸🇦",
|
||||||
|
"cs-cz": "🇨🇿",
|
||||||
|
"da-dk": "🇩🇰",
|
||||||
|
"de-de": "🇩🇪",
|
||||||
|
"en-gb": "🇬🇧",
|
||||||
|
"en-us": "🇺🇸",
|
||||||
|
"es-es": "🇪🇸",
|
||||||
|
"fa-ir": "🇮🇷",
|
||||||
|
"fr-fr": "🇫🇷",
|
||||||
|
"he-il": "🇮🇱",
|
||||||
|
"hi-in": "🇮🇳",
|
||||||
|
"hr-hr": "🇭🇷",
|
||||||
|
"id-id": "🇮🇩",
|
||||||
|
"it-it": "🇮🇹",
|
||||||
|
"ja-jp": "🇯🇵",
|
||||||
|
"kk-kz": "🇰🇿",
|
||||||
|
"ko-kr": "🇰🇷",
|
||||||
|
"nl-nl": "🇳🇱",
|
||||||
|
"no-no": "🇳🇴",
|
||||||
|
"pl-pl": "🇵🇱",
|
||||||
|
"pt-br": "🇧🇷",
|
||||||
|
"ro-ro": "🇷🇴",
|
||||||
|
"ru-ru": "🇷🇺",
|
||||||
|
"sv-se": "🇸🇪",
|
||||||
|
"th-th": "🇹🇭",
|
||||||
|
"tr-tr": "🇹🇷",
|
||||||
|
"vi-vn": "🇻🇳",
|
||||||
|
"zh-hans": "🇨🇳",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"SIDEBAR": {
|
||||||
|
"show_search": True,
|
||||||
|
"navigation": [
|
||||||
|
{
|
||||||
|
"title": _("Menu"),
|
||||||
|
"separator": True,
|
||||||
|
"collapsible": True,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": _("Dashboard"),
|
||||||
|
"icon": "dashboard",
|
||||||
|
"link": reverse_lazy("admin:index"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": _("Health"),
|
||||||
|
"icon": "health_metrics",
|
||||||
|
"link": reverse_lazy("health_check:health_check_home"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": _("Swagger"),
|
||||||
|
"icon": "integration_instructions",
|
||||||
|
"link": reverse_lazy("swagger-ui-platform"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": _("Redoc"),
|
||||||
|
"icon": "integration_instructions",
|
||||||
|
"link": reverse_lazy("redoc-ui-platform"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": _("GraphQL"),
|
||||||
|
"icon": "graph_5",
|
||||||
|
"link": reverse_lazy("graphql-platform"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": _("Taskboard"),
|
||||||
|
"icon": "view_kanban",
|
||||||
|
"link": TASKBOARD_URL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": _("Support"),
|
||||||
|
"icon": "contact_support",
|
||||||
|
"link": SUPPORT_CONTACT,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"TABS": [
|
||||||
|
{
|
||||||
|
"models": [
|
||||||
|
"core.product",
|
||||||
|
],
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": _("Your custom title"),
|
||||||
|
"link": reverse_lazy("admin:core_product_changelist"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue