Improve admin page

This commit is contained in:
2024-07-01 17:41:51 +02:00
parent 219aee31af
commit 40587fa24f
11 changed files with 633 additions and 32 deletions

View File

@ -76,6 +76,7 @@ INSTALLED_APPS: list[str] = [
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.twitch",
"simple_history",
]
MIDDLEWARE: list[str] = [
@ -88,6 +89,7 @@ MIDDLEWARE: list[str] = [
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"allauth.account.middleware.AccountMiddleware",
"simple_history.middleware.HistoryRequestMiddleware",
]
@ -166,9 +168,16 @@ LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"
SOCIALACCOUNT_ONLY = True
ACCOUNT_EMAIL_VERIFICATION = "none"
SOCIALACCOUNT_STORE_TOKENS = True
AUTHENTICATION_BACKENDS: list[str] = [
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
]
SOCIALACCOUNT_PROVIDERS = {
"twitch": {
"SCOPE": ["user:read:email"],
"AUTH_PARAMS": {"force_verify": True},
},
}

View File

@ -1,6 +1,8 @@
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import DiscordSetting, Subscription
admin.site.register(DiscordSetting)
admin.site.register(Subscription)
# https://django-simple-history.readthedocs.io/en/latest/admin.html
admin.site.register(DiscordSetting, SimpleHistoryAdmin)
admin.site.register(Subscription, SimpleHistoryAdmin)

View File

@ -0,0 +1,146 @@
# Generated by Django 5.0.6 on 2024-07-01 15:17
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("core", "0003_subscription"),
("twitch_app", "0003_historicaldropbenefit_historicaldropcampaign_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations: list[Operation] = [
migrations.CreateModel(
name="HistoricalDiscordSetting",
fields=[
(
"id",
models.BigIntegerField(
auto_created=True,
blank=True,
db_index=True,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("webhook_url", models.URLField()),
("disabled", models.BooleanField(default=False)),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"user",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical discord setting",
"verbose_name_plural": "historical discord settings",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalSubscription",
fields=[
(
"id",
models.BigIntegerField(
auto_created=True,
blank=True,
db_index=True,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(blank=True, editable=False)),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"discord_webhook",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="core.discordsetting",
),
),
(
"game",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.game",
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"user",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical subscription",
"verbose_name_plural": "historical subscriptions",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]

View File

@ -1,5 +1,6 @@
from django.contrib.auth.models import User
from django.db import models
from simple_history.models import HistoricalRecords
from twitch_app.models import Game
@ -8,6 +9,7 @@ class DiscordSetting(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
webhook_url = models.URLField()
history = HistoricalRecords()
disabled = models.BooleanField(default=False)
def __str__(self) -> str:
@ -18,6 +20,7 @@ class Subscription(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
game = models.ForeignKey(Game, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
history = HistoricalRecords()
discord_webhook = models.ForeignKey(DiscordSetting, on_delete=models.CASCADE)
def __str__(self) -> str:

View File

@ -9,8 +9,5 @@ playwright
playwright
psycopg[binary]
python-dotenv
redis[hiredis]
selectolax
sentry-sdk[django]
twitchAPI
whitenoise[brotli]

View File

@ -1,9 +1,11 @@
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import DropBenefit, DropCampaign, Game, Organization, TimeBasedDrop
admin.site.register(DropBenefit)
admin.site.register(DropCampaign)
admin.site.register(Game)
admin.site.register(Organization)
admin.site.register(TimeBasedDrop)
# https://django-simple-history.readthedocs.io/en/latest/admin.html
admin.site.register(DropBenefit, SimpleHistoryAdmin)
admin.site.register(DropCampaign, SimpleHistoryAdmin)
admin.site.register(Game, SimpleHistoryAdmin)
admin.site.register(Organization, SimpleHistoryAdmin)
admin.site.register(TimeBasedDrop, SimpleHistoryAdmin)

View File

@ -16,7 +16,6 @@ from twitch_app.models import (
Game,
Organization,
TimeBasedDrop,
User,
)
if TYPE_CHECKING:
@ -50,7 +49,7 @@ async def insert_data(data: dict) -> None: # noqa: PLR0914, C901
logger.debug("No user data found")
return
user_id = user_data["id"]
user_data["id"]
drop_campaign_data = user_data["dropCampaign"]
if not drop_campaign_data:
return
@ -155,12 +154,6 @@ async def insert_data(data: dict) -> None: # noqa: PLR0914, C901
if created:
logger.debug("Time-based drop created: %s", time_based_drop)
# Create or get the user
user, created = await sync_to_async(User.objects.get_or_create)(id=user_id)
await sync_to_async(user.drop_campaigns.add)(drop_campaign)
if created:
logger.debug("User created: %s", user)
class Command(BaseCommand):
help = "Scrape Twitch Drops Campaigns with login using Firefox"

View File

@ -0,0 +1,308 @@
# Generated by Django 5.0.6 on 2024-07-01 15:17
import django.db.models.deletion
import django.db.models.functions.text
import simple_history.models
from django.conf import settings
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("twitch_app", "0002_game_image_url"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations: list[Operation] = [
migrations.CreateModel(
name="HistoricalDropBenefit",
fields=[
("id", models.TextField(db_index=True)),
("created_at", models.DateTimeField(blank=True, null=True)),
("entitlement_limit", models.IntegerField(blank=True, null=True)),
("image_asset_url", models.URLField(blank=True, null=True)),
("is_ios_available", models.BooleanField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"game",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.game",
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"owner_organization",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.organization",
),
),
],
options={
"verbose_name": "historical drop benefit",
"verbose_name_plural": "historical drop benefits",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalDropCampaign",
fields=[
("id", models.TextField(db_index=True)),
("account_link_url", models.URLField(blank=True, null=True)),
("description", models.TextField(blank=True, null=True)),
("details_url", models.URLField(blank=True, null=True)),
("end_at", models.DateTimeField(blank=True, null=True)),
("image_url", models.URLField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
("start_at", models.DateTimeField(blank=True, null=True)),
("status", models.TextField(blank=True, null=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"game",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.game",
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"owner",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.organization",
),
),
],
options={
"verbose_name": "historical drop campaign",
"verbose_name_plural": "historical drop campaigns",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalGame",
fields=[
("id", models.TextField(db_index=True)),
("slug", models.TextField(blank=True, null=True)),
(
"twitch_url",
models.GeneratedField( # type: ignore # noqa: PGH003
db_persist=True,
expression=django.db.models.functions.text.Concat(
models.Value("https://www.twitch.tv/directory/category/"),
"slug",
),
output_field=models.TextField(),
),
),
(
"image_url",
models.GeneratedField( # type: ignore # noqa: PGH003
db_persist=True,
expression=django.db.models.functions.text.Concat(
models.Value("https://static-cdn.jtvnw.net/ttv-boxart/"),
"id",
models.Value("_IGDB.jpg"),
),
output_field=models.URLField(),
),
),
("display_name", models.TextField(blank=True, null=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical game",
"verbose_name_plural": "historical games",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalTimeBasedDrop",
fields=[
("id", models.TextField(db_index=True)),
("required_subs", models.IntegerField(blank=True, null=True)),
("end_at", models.DateTimeField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
(
"required_minutes_watched",
models.IntegerField(blank=True, null=True),
),
("start_at", models.DateTimeField(blank=True, null=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical time based drop",
"verbose_name_plural": "historical time based drops",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalUser",
fields=[
("id", models.TextField(db_index=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical user",
"verbose_name_plural": "historical users",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.0.6 on 2024-07-01 15:23
from django.db import migrations
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("twitch_app", "0003_historicaldropbenefit_historicaldropcampaign_and_more"),
]
operations: list[Operation] = [
migrations.RemoveField(
model_name="user",
name="drop_campaigns",
),
migrations.DeleteModel(
name="HistoricalUser",
),
migrations.DeleteModel(
name="User",
),
]

View File

@ -0,0 +1,92 @@
# Generated by Django 5.0.6 on 2024-07-01 15:40
from django.db import migrations
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
(
"twitch_app",
"0004_remove_user_drop_campaigns_delete_historicaluser_and_more",
),
]
operations: list[Operation] = [
migrations.AlterModelOptions(
name="dropbenefit",
options={
"ordering": ("name",),
"verbose_name": "Drop Benefit",
"verbose_name_plural": "Drop Benefits",
},
),
migrations.AlterModelOptions(
name="dropcampaign",
options={
"ordering": ("name",),
"verbose_name": "Drop Campaign",
"verbose_name_plural": "Drop Campaigns",
},
),
migrations.AlterModelOptions(
name="game",
options={
"ordering": ("display_name",),
"verbose_name": "Game",
"verbose_name_plural": "Games",
},
),
migrations.AlterModelOptions(
name="historicaldropbenefit",
options={
"get_latest_by": ("history_date", "history_id"),
"ordering": ("-history_date", "-history_id"),
"verbose_name": "historical Drop Benefit",
"verbose_name_plural": "historical Drop Benefits",
},
),
migrations.AlterModelOptions(
name="historicaldropcampaign",
options={
"get_latest_by": ("history_date", "history_id"),
"ordering": ("-history_date", "-history_id"),
"verbose_name": "historical Drop Campaign",
"verbose_name_plural": "historical Drop Campaigns",
},
),
migrations.AlterModelOptions(
name="historicalgame",
options={
"get_latest_by": ("history_date", "history_id"),
"ordering": ("-history_date", "-history_id"),
"verbose_name": "historical Game",
"verbose_name_plural": "historical Games",
},
),
migrations.AlterModelOptions(
name="historicaltimebaseddrop",
options={
"get_latest_by": ("history_date", "history_id"),
"ordering": ("-history_date", "-history_id"),
"verbose_name": "historical Time-Based Drop",
"verbose_name_plural": "historical Time-Based Drops",
},
),
migrations.AlterModelOptions(
name="organization",
options={
"ordering": ("name",),
"verbose_name": "Organization",
"verbose_name_plural": "Organizations",
},
),
migrations.AlterModelOptions(
name="timebaseddrop",
options={
"ordering": ("name",),
"verbose_name": "Time-Based Drop",
"verbose_name_plural": "Time-Based Drops",
},
),
]

View File

@ -1,8 +1,11 @@
from django.contrib.humanize.templatetags.humanize import naturaltime
from django.db import models
from django.db.models import Value
from django.db.models.functions import (
Concat,
)
from django.utils import timezone
from simple_history.models import HistoricalRecords
class Organization(models.Model):
@ -11,6 +14,11 @@ class Organization(models.Model):
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
class Meta:
verbose_name = "Organization"
verbose_name_plural = "Organizations"
ordering = ("name",)
def __str__(self) -> str:
return self.name or self.id
@ -35,6 +43,12 @@ class Game(models.Model):
display_name = models.TextField(blank=True, null=True)
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords()
class Meta:
verbose_name = "Game"
verbose_name_plural = "Games"
ordering = ("display_name",)
def __str__(self) -> str:
return self.display_name or self.slug or self.id
@ -51,9 +65,15 @@ class DropBenefit(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE)
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords()
class Meta:
verbose_name = "Drop Benefit"
verbose_name_plural = "Drop Benefits"
ordering = ("name",)
def __str__(self) -> str:
return self.name or self.id
return f"{self.owner_organization.name} - {self.game.display_name} - {self.name}"
class TimeBasedDrop(models.Model):
@ -66,9 +86,19 @@ class TimeBasedDrop(models.Model):
benefits = models.ManyToManyField(DropBenefit)
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords()
class Meta:
verbose_name = "Time-Based Drop"
verbose_name_plural = "Time-Based Drops"
ordering = ("name",)
def __str__(self) -> str:
return self.name or self.id
if self.end_at:
if self.end_at < timezone.now():
return f"{self.benefits.first()} - {self.name} - Ended {naturaltime(self.end_at)}"
return f"{self.benefits.first()} - {self.name} - Ends in {naturaltime(self.end_at)}"
return f"{self.benefits.first()} - {self.name}"
class DropCampaign(models.Model):
@ -94,16 +124,12 @@ class DropCampaign(models.Model):
time_based_drops = models.ManyToManyField(TimeBasedDrop)
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords()
class Meta:
verbose_name = "Drop Campaign"
verbose_name_plural = "Drop Campaigns"
ordering = ("name",)
def __str__(self) -> str:
return self.name or self.id
class User(models.Model):
id = models.TextField(primary_key=True)
drop_campaigns = models.ManyToManyField(DropCampaign)
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
def __str__(self) -> str:
return self.id
return f"{self.owner.name} - {self.game.display_name} - {self.name}"