Skip to content

Commit

Permalink
Migrate admin panel to django
Browse files Browse the repository at this point in the history
  • Loading branch information
laggron42 committed Jan 6, 2025
1 parent 80a57bf commit ff6b42b
Show file tree
Hide file tree
Showing 21 changed files with 1,182 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ repos:
- git+https://github.com/fastapi-admin/fastapi-admin.git
- aerich==0.6.3
- redis
- django
- dj_database_url
- django-stubs
- repo: https://github.com/csachs/pyproject-flake8
rev: v7.0.0
hooks:
Expand Down
Empty file.
7 changes: 7 additions & 0 deletions admin_panel/admin_panel/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.contrib import admin


class BallsdexAdminSite(admin.AdminSite):
site_header = "Ballsdex administration" # TODO: use configured bot name
site_title = "Ballsdex admin panel"
site_url = None
5 changes: 5 additions & 0 deletions admin_panel/admin_panel/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib.admin.apps import AdminConfig


class BallsdexAdminConfig(AdminConfig):
default_site = "admin_panel.admin.BallsdexAdminSite"
7 changes: 7 additions & 0 deletions admin_panel/admin_panel/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "admin_panel.settings")

application = get_asgi_application()
117 changes: 117 additions & 0 deletions admin_panel/admin_panel/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from pathlib import Path

import dj_database_url

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-2ri6p%3d(df5+bdiiekb&v9cz9j=68j1%2xn)x494%-%+y#2nv"

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"admin_panel.apps.BallsdexAdminConfig",
"bd_models",
"preview",
]

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "admin_panel.urls"

TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]

WSGI_APPLICATION = "admin_panel.wsgi.application"


# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases

DATABASES = {"default": dj_database_url.config("BALLSDEXBOT_DB_URL")}


# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]


# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/

# TODO: put back normal static files
STATIC_URL = "staatic/"
MEDIA_URL = "/"

# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"


# TODO: set a proper static folder and move existing uploads
# Right now, this exposes all files, including config.yml and .env
MEDIA_ROOT = ".."
9 changes: 9 additions & 0 deletions admin_panel/admin_panel/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
path("admin/", admin.site.urls),
path("", include("preview.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
7 changes: 7 additions & 0 deletions admin_panel/admin_panel/wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "admin_panel.settings")

application = get_wsgi_application()
Empty file.
156 changes: 156 additions & 0 deletions admin_panel/bd_models/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from typing import TYPE_CHECKING, Any

from django.contrib import admin
from django.forms import Textarea
from django.utils.safestring import mark_safe

from .models import Ball, Economy, Regime, Special

if TYPE_CHECKING:
from django.db.models import Field
from django.http.request import HttpRequest


@admin.register(Regime)
class RegimeAdmin(admin.ModelAdmin):
pass


@admin.register(Economy)
class EconomyAdmin(admin.ModelAdmin):
pass


@admin.register(Ball)
class BallAdmin(admin.ModelAdmin):
raw_id_fields = ["regime", "economy"]
readonly_fields = ("collection_image", "spawn_image", "preview")
fieldsets = [
(
None,
{
"fields": [
"country",
"health",
"attack",
"rarity",
"emoji_id",
"economy",
"regime",
],
},
),
(
"Assets",
{
"description": "You must have permission from the copyright holder "
"to use the files you're uploading!",
"fields": [
"spawn_image",
"wild_card",
"collection_image",
"collection_card",
"credits",
],
},
),
(
"Ability",
{
"description": "The ability of the countryball",
"fields": ["capacity_name", "capacity_description"],
},
),
(
"Advanced",
{
"description": "Advanced settings",
"classes": ["collapse"],
"fields": ["enabled", "tradeable", "short_name", "catch_names", "translations"],
},
),
("Preview", {"description": "Generate previews", "fields": ["preview"]}),
]

list_display = ["country", "emoji", "rarity", "capacity_name", "health", "attack", "enabled"]
list_editable = ["enabled", "rarity"]
list_filter = ["enabled", "tradeable", "regime", "economy", "created_at"]
ordering = ["-created_at"]

search_fields = [
"country",
"capacity_name",
"capacity_description",
"catch_names",
"translations",
"credits",
]
search_help_text = (
"Search for countryball name, abilitie name/content, credits, catch names or translations"
)

@admin.display(description="Emoji")
def emoji(self, obj: Ball):
return mark_safe(
f'<img src="https://cdn.discordapp.com/emojis/{obj.emoji_id}.png?size=40" '
f'title="ID: {obj.emoji_id}" />'
)

def formfield_for_dbfield(
self, db_field: "Field[Any, Any]", request: "HttpRequest | None", **kwargs: Any
) -> "Field[Any, Any] | None":
if db_field.name == "capacity_description":
kwargs["widget"] = Textarea()
return super().formfield_for_dbfield(db_field, request, **kwargs) # type: ignore


@admin.register(Special)
class SpecialAdmin(admin.ModelAdmin):
fieldsets = [
(
None,
{
"fields": ["name", "catch_phrase", "rarity", "emoji", "background"],
},
),
(
"Time range",
{
"fields": ["start_date", "end_date"],
"description": "An optional time range to make the event limited in time. As soon "
"as the event is loaded in the bot's cache, it will automatically load and unload "
"at the specified time.",
},
),
(
"Advanced",
{
"fields": ["tradeable", "hidden"],
"classes": ["collapse"],
},
),
]

list_display = ["name", "emoji_display", "start_date", "end_date", "rarity", "hidden"]
list_editable = ["hidden", "rarity"]
list_filter = ["hidden", "tradeable"]

search_fields = ["name", "catch_phrase"]

@admin.display(description="Emoji")
def emoji_display(self, obj: Special):
return (
mark_safe(
f'<img src="https://cdn.discordapp.com/emojis/{obj.emoji}.png?size=40" '
f'title="ID: {obj.emoji}" />'
)
if obj.emoji and obj.emoji.isdigit()
else obj.emoji
)

def formfield_for_dbfield(
self, db_field: "Field[Any, Any]", request: "HttpRequest | None", **kwargs: Any
) -> "Field[Any, Any] | None":
if db_field.name == "catch_phrase":
kwargs["widget"] = Textarea()
return super().formfield_for_dbfield(db_field, request, **kwargs) # type: ignore
7 changes: 7 additions & 0 deletions admin_panel/bd_models/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig


class BdModelsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "bd_models"
verbose_name = "Ballsdex models"
Loading

0 comments on commit ff6b42b

Please sign in to comment.