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 unfold.admin import ModelAdmin
|
||||
|
||||
from engine.blog.models import Post, PostTag
|
||||
from engine.core.admin import ActivationActionsMixin, FieldsetsMixin
|
||||
|
||||
from .models import Post, PostTag
|
||||
|
||||
|
||||
@register(Post)
|
||||
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 django.apps import AppConfig, apps
|
||||
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.messages import constants as messages
|
||||
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.utils import get_translation_fields
|
||||
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.models import (
|
||||
|
|
@ -65,15 +67,15 @@ class FieldsetsMixin:
|
|||
for orig in transoptions.local_fields:
|
||||
translation_fields += get_translation_fields(orig)
|
||||
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
|
||||
|
||||
if self.general_fields:
|
||||
fieldsets.append((_("general"), {"fields": self.general_fields}))
|
||||
fieldsets.append((_("general"), {"classes": ["tab"], "fields": self.general_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:
|
||||
fieldsets.append((_("additional info"), {"fields": self.additional_fields}))
|
||||
fieldsets.append((_("additional info"), {"classes": ["tab"], "fields": self.additional_fields}))
|
||||
opts = self.model._meta
|
||||
|
||||
meta_fields = []
|
||||
|
|
@ -91,14 +93,14 @@ class FieldsetsMixin:
|
|||
meta_fields.append("human_readable_id")
|
||||
|
||||
if meta_fields:
|
||||
fieldsets.append((_("metadata"), {"fields": meta_fields}))
|
||||
fieldsets.append((_("metadata"), {"classes": ["tab"], "fields": meta_fields}))
|
||||
|
||||
ts = []
|
||||
for name in ("created", "modified"):
|
||||
if any(f.name == name for f in opts.fields):
|
||||
ts.append(name)
|
||||
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]
|
||||
return fieldsets # type: ignore [return-value]
|
||||
|
||||
|
|
@ -140,10 +142,9 @@ class AttributeValueInline(TabularInline): # type: ignore [type-arg]
|
|||
model = AttributeValue
|
||||
extra = 0
|
||||
autocomplete_fields = ["attribute"]
|
||||
is_navtab = True
|
||||
verbose_name = _("attribute value")
|
||||
verbose_name_plural = _("attribute values")
|
||||
icon = "fa-solid fa-list-ul"
|
||||
tab = True
|
||||
|
||||
def get_queryset(self, request):
|
||||
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]
|
||||
model = ProductImage
|
||||
extra = 0
|
||||
is_navtab = True
|
||||
tab = True
|
||||
verbose_name = _("image")
|
||||
verbose_name_plural = _("images")
|
||||
icon = "fa-regular fa-images"
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).select_related("product")
|
||||
|
|
@ -165,10 +165,9 @@ class StockInline(TabularInline): # type: ignore [type-arg]
|
|||
model = Stock
|
||||
extra = 0
|
||||
form = StockForm
|
||||
is_navtab = True
|
||||
tab = True
|
||||
verbose_name = _("stock")
|
||||
verbose_name_plural = _("stocks")
|
||||
icon = "fa-solid fa-boxes-stacked"
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).select_related("vendor", "product")
|
||||
|
|
@ -179,10 +178,9 @@ class OrderProductInline(TabularInline): # type: ignore [type-arg]
|
|||
extra = 0
|
||||
readonly_fields = ("product", "quantity", "buy_price")
|
||||
form = OrderProductForm
|
||||
is_navtab = True
|
||||
verbose_name = _("order product")
|
||||
verbose_name_plural = _("order products")
|
||||
icon = "fa-solid fa-boxes-packing"
|
||||
tab = True
|
||||
|
||||
def get_queryset(self, request):
|
||||
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"
|
||||
extra = 0
|
||||
fields = ("name", "description", "is_active", "image", "markup_percent")
|
||||
is_navtab = True
|
||||
tab = True
|
||||
verbose_name = _("children")
|
||||
verbose_name_plural = _("children")
|
||||
icon = "fa-solid fa-leaf"
|
||||
|
||||
|
||||
@register(AttributeGroup)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.admin import ModelAdmin, register
|
||||
from django.contrib.admin import register
|
||||
from django.db.models import QuerySet
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from unfold.admin import ModelAdmin, TabularInline
|
||||
|
||||
from engine.core.admin import ActivationActionsMixin
|
||||
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
|
||||
form = TransactionForm
|
||||
extra = 1
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Any
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import register
|
||||
from django.contrib.auth.admin import (
|
||||
GroupAdmin as BaseGroupAdmin,
|
||||
)
|
||||
|
|
@ -25,6 +26,7 @@ from rest_framework_simplejwt.token_blacklist.models import (
|
|||
from rest_framework_simplejwt.token_blacklist.models import (
|
||||
OutstandingToken as BaseOutstandingToken,
|
||||
)
|
||||
from unfold.admin import ModelAdmin, TabularInline
|
||||
|
||||
from engine.core.admin import ActivationActionsMixin
|
||||
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.models import (
|
||||
BlacklistedToken,
|
||||
Group,
|
||||
OutstandingToken,
|
||||
User,
|
||||
ChatMessage,
|
||||
ChatThread,
|
||||
Group,
|
||||
OutstandingToken,
|
||||
ThreadStatus,
|
||||
User,
|
||||
)
|
||||
|
||||
|
||||
class BalanceInline(admin.TabularInline): # type: ignore [type-arg]
|
||||
class BalanceInline(TabularInline): # type: ignore [type-arg]
|
||||
model = Balance
|
||||
can_delete = False
|
||||
extra = 0
|
||||
|
|
@ -51,7 +53,7 @@ class BalanceInline(admin.TabularInline): # type: ignore [type-arg]
|
|||
icon = "fa-solid fa-wallet"
|
||||
|
||||
|
||||
class OrderInline(admin.TabularInline): # type: ignore [type-arg]
|
||||
class OrderInline(TabularInline): # type: ignore [type-arg]
|
||||
model = Order
|
||||
extra = 0
|
||||
verbose_name = _("order")
|
||||
|
|
@ -60,7 +62,7 @@ class OrderInline(admin.TabularInline): # type: ignore [type-arg]
|
|||
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)
|
||||
fieldsets = (
|
||||
(None, {"fields": ("email", "password")}),
|
||||
|
|
@ -125,8 +127,8 @@ class UserAdmin(ActivationActionsMixin, BaseUserAdmin): # type: ignore [misc, t
|
|||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@admin.register(ChatThread)
|
||||
class ChatThreadAdmin(admin.ModelAdmin):
|
||||
@register(ChatThread)
|
||||
class ChatThreadAdmin(ModelAdmin):
|
||||
list_display = (
|
||||
"uuid",
|
||||
"user",
|
||||
|
|
@ -161,7 +163,7 @@ class ChatThreadAdmin(admin.ModelAdmin):
|
|||
queryset.update(status=ThreadStatus.OPEN)
|
||||
|
||||
|
||||
@admin.register(ChatMessage)
|
||||
@register(ChatMessage)
|
||||
class ChatMessageAdmin(admin.ModelAdmin):
|
||||
list_display = ("uuid", "thread", "sender_type", "sender_user", "sent_at")
|
||||
list_filter = ("sender_type",)
|
||||
|
|
@ -170,15 +172,15 @@ class ChatMessageAdmin(admin.ModelAdmin):
|
|||
readonly_fields = ("created", "modified")
|
||||
|
||||
|
||||
class GroupAdmin(BaseGroupAdmin):
|
||||
class GroupAdmin(BaseGroupAdmin, ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class BlacklistedTokenAdmin(BaseBlacklistedTokenAdmin):
|
||||
class BlacklistedTokenAdmin(BaseBlacklistedTokenAdmin, ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class OutstandingTokenAdmin(BaseOutstandingTokenAdmin):
|
||||
class OutstandingTokenAdmin(BaseOutstandingTokenAdmin, ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ EVIBES_VERSION = "2025.4"
|
|||
RELEASE_DATE = datetime(2025, 11, 9)
|
||||
|
||||
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
|
||||
INITIALIZED: bool = (BASE_DIR / ".initialized").exists()
|
||||
|
|
@ -108,6 +112,8 @@ INSTALLED_APPS: list[str] = [
|
|||
"unfold",
|
||||
"unfold.contrib.filters",
|
||||
"unfold.contrib.forms",
|
||||
"unfold.contrib.inlines",
|
||||
"unfold.contrib.constance",
|
||||
"modeltranslation",
|
||||
"django.contrib.admin",
|
||||
"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.
|
||||
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, SUPPORT_CONTACT, TASKBOARD_URL
|
||||
|
||||
from evibes.settings.base import PROJECT_NAME
|
||||
|
||||
# See django-unfold documentation for all available options.
|
||||
# Only minimal, production-safe branding is configured here.
|
||||
UNFOLD = {
|
||||
# Text shown in the browser title bar and in the admin header
|
||||
"SITE_TITLE": f"{PROJECT_NAME} Admin",
|
||||
"SITE_TITLE": f"{PROJECT_NAME} Dashboard",
|
||||
"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",
|
||||
# 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",
|
||||
# Sidebar behavior, search etc. (keep defaults minimal)
|
||||
# Unfold automatically respects user OS light/dark theme; no forcing here.
|
||||
"SHOW_LANGUAGES": True,
|
||||
"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