+ {% block object-tools %}
+ {% if change and not is_popup %}
+
+ {% endif %}
+ {% endblock %}
+
+
+ {% include "generate_ball_preview.html" %}
+
+
+{% else %}
+ {{ block.super }}
+{% endif %}
+{% endblock %}
\ No newline at end of file
diff --git a/admin_panel/templates/admin/bd_models/ballinstance/change_form.html b/admin_panel/templates/admin/bd_models/ballinstance/change_form.html
new file mode 100644
index 00000000..4a42adb9
--- /dev/null
+++ b/admin_panel/templates/admin/bd_models/ballinstance/change_form.html
@@ -0,0 +1,45 @@
+{% extends "admin/change_form.html" %}
+
+{% block after_related_objects %}
+
+ {% block object-tools %}
+ {% if change and not is_popup %}
+
+ {% endif %}
+ {% endblock %}
+
+
+ {% include "generate_ball_preview.html" %}
+
+
+{% else %}
+ {{ block.super }}
+{% endif %}
+{% endblock %}
\ No newline at end of file
diff --git a/admin_panel/templates/admin/bd_models/trade/change_form.html b/admin_panel/templates/admin/bd_models/trade/change_form.html
new file mode 100644
index 00000000..17823511
--- /dev/null
+++ b/admin_panel/templates/admin/bd_models/trade/change_form.html
@@ -0,0 +1,42 @@
+{% extends "admin/change_form.html" %}
+
+{% block after_related_objects %}
+
+{% blocktranslate count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %}
+
+{% endif %}
+
+{% if form.non_field_errors %}
+{% for error in form.non_field_errors %}
+
+
+{% if user.is_authenticated %}
+
+{% blocktranslate trimmed %}
+ You are authenticated as {{ username }}, but are not authorized to
+ access this page. Would you like to login to a different account?
+{% endblocktranslate %}
+
+{% endif %}
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/ballsdex/__main__.py b/ballsdex/__main__.py
index 3e182616..e2c3bbc3 100755
--- a/ballsdex/__main__.py
+++ b/ballsdex/__main__.py
@@ -237,10 +237,13 @@ def filter(self, record):
return True
-async def init_tortoise(db_url: str):
+async def init_tortoise(db_url: str, *, skip_migrations: bool = False):
log.debug(f"Database URL: {db_url}")
await Tortoise.init(config=TORTOISE_ORM)
+ if skip_migrations:
+ return
+
# migrations
command = Command(TORTOISE_ORM, app="models")
await command.init()
diff --git a/ballsdex/core/admin/__init__.py b/ballsdex/core/admin/__init__.py
deleted file mode 100644
index 9cae801d..00000000
--- a/ballsdex/core/admin/__init__.py
+++ /dev/null
@@ -1,91 +0,0 @@
-import os
-import pathlib
-
-from fastapi import FastAPI
-from fastapi_admin.app import app as admin_app
-from fastapi_admin.exceptions import (
- forbidden_error_exception,
- not_found_error_exception,
- server_error_exception,
- unauthorized_error_exception,
-)
-from fastapi_admin.providers.login import UsernamePasswordProvider
-from redis import asyncio as aioredis
-from starlette.middleware.cors import CORSMiddleware
-from starlette.responses import RedirectResponse
-from starlette.staticfiles import StaticFiles
-from starlette.status import (
- HTTP_401_UNAUTHORIZED,
- HTTP_403_FORBIDDEN,
- HTTP_404_NOT_FOUND,
- HTTP_500_INTERNAL_SERVER_ERROR,
-)
-from tortoise.contrib.fastapi import register_tortoise
-
-from ballsdex.__main__ import TORTOISE_ORM
-from ballsdex.core.admin import resources, routes # noqa: F401
-from ballsdex.core.admin.resources import User
-
-BASE_DIR = pathlib.Path(".")
-
-
-def init_fastapi_app() -> FastAPI:
- app = FastAPI()
- app.mount(
- "/static",
- StaticFiles(directory=BASE_DIR / "static"),
- name="static",
- )
- app.mount(
- "/ballsdex/core/image_generator/src",
- StaticFiles(directory=BASE_DIR / "ballsdex/core/image_generator/src"),
- name="image_gen",
- )
-
- @app.get("/")
- async def index():
- return RedirectResponse(url="/admin")
-
- admin_app.add_exception_handler(
- HTTP_500_INTERNAL_SERVER_ERROR, server_error_exception # type: ignore
- )
- admin_app.add_exception_handler(HTTP_404_NOT_FOUND, not_found_error_exception) # type: ignore
- admin_app.add_exception_handler(HTTP_403_FORBIDDEN, forbidden_error_exception) # type: ignore
- admin_app.add_exception_handler(
- HTTP_401_UNAUTHORIZED, unauthorized_error_exception # type: ignore
- )
-
- @app.on_event("startup")
- async def startup():
- redis = aioredis.from_url(
- os.environ["BALLSDEXBOT_REDIS_URL"], decode_responses=True, encoding="utf8"
- )
- await admin_app.configure(
- logo_url="https://i.imgur.com/HwNKi5a.png",
- template_folders=[os.path.join(BASE_DIR, "ballsdex", "templates")],
- favicon_url="https://raw.githubusercontent.com/fastapi-admin/" # type: ignore
- "fastapi-admin/dev/images/favicon.png",
- providers=[
- UsernamePasswordProvider(
- login_logo_url="https://preview.tabler.io/static/logo.svg",
- admin_model=User,
- )
- ],
- redis=redis,
- )
-
- app.mount("/admin", admin_app)
- app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- expose_headers=["*"],
- )
- register_tortoise(app, config=TORTOISE_ORM)
-
- return app
-
-
-_app = init_fastapi_app()
diff --git a/ballsdex/core/admin/resources.py b/ballsdex/core/admin/resources.py
deleted file mode 100755
index 87dc3442..00000000
--- a/ballsdex/core/admin/resources.py
+++ /dev/null
@@ -1,389 +0,0 @@
-import os
-from typing import Any, List
-
-from fastapi_admin.app import app
-from fastapi_admin.enums import Method
-from fastapi_admin.file_upload import FileUpload
-from fastapi_admin.resources import Action, Field, Link, Model
-from fastapi_admin.widgets import displays, filters, inputs
-from starlette.requests import Request
-
-from ballsdex.core.models import (
- Ball,
- BallInstance,
- BlacklistedGuild,
- BlacklistedID,
- Economy,
- GuildConfig,
- Player,
- Regime,
- Special,
- User,
-)
-
-
-@app.register
-class Home(Link):
- label = "Home"
- icon = "fas fa-home"
- url = "/admin"
-
-
-upload = FileUpload(uploads_dir=os.path.join(".", "static", "uploads"))
-
-
-@app.register
-class AdminResource(Model):
- label = "Admin"
- model = User
- icon = "fas fa-user"
- page_pre_title = "admin list"
- page_title = "Admins"
- filters = [
- filters.Search(
- name="username",
- label="Name",
- search_mode="contains",
- placeholder="Search for username",
- ),
- ]
- fields = [
- "id",
- "username",
- Field(
- name="password",
- label="Password",
- display=displays.InputOnly(),
- input_=inputs.Password(),
- ),
- Field(
- name="avatar",
- label="Avatar",
- display=displays.Image(width="40"),
- input_=inputs.Image(null=True, upload=upload),
- ),
- "created_at",
- ]
-
- async def cell_attributes(self, request: Request, obj: dict, field: Field) -> dict:
- if field.name == "id":
- return {"class": "bg-danger text-white"}
- return await super().cell_attributes(request, obj, field)
-
-
-@app.register
-class SpecialResource(Model):
- label = "Special events"
- model = Special
- icon = "fas fa-star"
- page_pre_title = "special list"
- page_title = "Special events list"
- filters = [
- filters.Search(
- name="name", label="Name", search_mode="icontains", placeholder="Search for events"
- ),
- filters.Boolean(name="hidden", label="Hidden"),
- ]
- fields = [
- "name",
- "catch_phrase",
- Field(
- name="start_date",
- label="Start date of the event",
- display=displays.DateDisplay(),
- input_=inputs.Date(help_text="Date when special balls will start spawning"),
- ),
- Field(
- name="end_date",
- label="End date of the event",
- display=displays.DateDisplay(),
- input_=inputs.Date(help_text="Date when special balls will stop spawning"),
- ),
- "rarity",
- Field(
- name="background",
- label="Special background (1428x2000)",
- display=displays.Image(width="40"),
- input_=inputs.Image(upload=upload, null=True),
- ),
- "emoji",
- "tradeable",
- "hidden",
- "credits",
- ]
-
- async def get_actions(self, request: Request) -> List[Action]:
- actions = await super().get_actions(request)
- actions.append(
- Action(
- icon="fas fa-upload",
- label="Generate card",
- name="generate",
- method=Method.GET,
- ajax=False,
- )
- )
- return actions
-
-
-@app.register
-class RegimeResource(Model):
- label = "Regime"
- model = Regime
- icon = "fas fa-flag"
- page_pre_title = "regime list"
- page_title = "Regimes"
- fields = [
- "name",
- Field(
- name="background",
- label="Background (1428x2000)",
- display=displays.Image(width="40"),
- input_=inputs.Image(upload=upload, null=True),
- ),
- ]
-
-
-@app.register
-class EconomyResource(Model):
- label = "Economy"
- model = Economy
- icon = "fas fa-coins"
- page_pre_title = "economy list"
- page_title = "Economies"
- fields = [
- "name",
- Field(
- name="icon",
- label="Icon (512x512)",
- display=displays.Image(width="40"),
- input_=inputs.Image(upload=upload, null=True),
- ),
- ]
-
-
-class Emoji(displays.Display):
- async def render(self, request: Request, value: Any):
- return (
- f'