feat: Add Twitch Drops Tracker application with campaign management
- Implemented models for DropCampaign, Game, Organization, DropBenefit, TimeBasedDrop, and DropBenefitEdge. - Created views for listing and detailing drop campaigns. - Added templates for dashboard, campaign list, and campaign detail. - Developed management command to import drop campaigns from JSON files. - Configured admin interface for managing campaigns and related models. - Updated URL routing for the application. - Enhanced README with installation instructions and project structure.
This commit is contained in:
parent
0c7c1c3f30
commit
5c482c1729
15 changed files with 1145 additions and 10 deletions
0
twitch/management/commands/__init__.py
Normal file
0
twitch/management/commands/__init__.py
Normal file
151
twitch/management/commands/import_drop_campaign.py
Normal file
151
twitch/management/commands/import_drop_campaign.py
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError, CommandParser
|
||||
|
||||
from twitch.models import DropBenefit, DropBenefitEdge, DropCampaign, Game, Organization, TimeBasedDrop
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Import Twitch drop campaign data from a JSON file."""
|
||||
|
||||
help = "Import Twitch drop campaign data from a JSON file"
|
||||
|
||||
def add_arguments(self, parser: CommandParser) -> None:
|
||||
"""Add command arguments.
|
||||
|
||||
Args:
|
||||
parser: The command argument parser.
|
||||
"""
|
||||
parser.add_argument(
|
||||
"json_file",
|
||||
type=str,
|
||||
help="Path to the JSON file containing the drop campaign data",
|
||||
)
|
||||
|
||||
def handle(self, **options: str) -> None:
|
||||
"""Execute the command.
|
||||
|
||||
Args:
|
||||
**options: Arbitrary keyword arguments.
|
||||
|
||||
Raises:
|
||||
CommandError: If the file doesn't exist, isn't a JSON file,
|
||||
or has an invalid JSON structure.
|
||||
"""
|
||||
json_file_path: str = options["json_file"]
|
||||
file_path = Path(json_file_path)
|
||||
|
||||
# Validate file exists and is a JSON file
|
||||
if not file_path.exists():
|
||||
msg = f"File {json_file_path} does not exist"
|
||||
raise CommandError(msg)
|
||||
|
||||
if not json_file_path.endswith(".json"):
|
||||
msg = f"File {json_file_path} is not a JSON file"
|
||||
raise CommandError(msg)
|
||||
|
||||
# Load JSON data
|
||||
try:
|
||||
with file_path.open(encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
msg = f"Error decoding JSON: {e}"
|
||||
raise CommandError(msg) from e
|
||||
|
||||
# Check if the JSON has the expected structure
|
||||
if "data" not in data or "user" not in data["data"] or "dropCampaign" not in data["data"]["user"]:
|
||||
msg = "Invalid JSON structure: Missing data.user.dropCampaign"
|
||||
raise CommandError(msg)
|
||||
|
||||
# Extract drop campaign data
|
||||
drop_campaign_data = data["data"]["user"]["dropCampaign"]
|
||||
|
||||
# Process the data
|
||||
self._import_drop_campaign(drop_campaign_data)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Successfully imported drop campaign: {drop_campaign_data['name']}"))
|
||||
|
||||
def _import_drop_campaign(self, campaign_data: dict[str, Any]) -> None:
|
||||
"""Import drop campaign data into the database.
|
||||
|
||||
Args:
|
||||
campaign_data: The drop campaign data to import.
|
||||
"""
|
||||
# First, create or update the game
|
||||
game_data = campaign_data["game"]
|
||||
game, _ = Game.objects.update_or_create(
|
||||
id=game_data["id"],
|
||||
defaults={
|
||||
"slug": game_data.get("slug", ""),
|
||||
"display_name": game_data["displayName"],
|
||||
},
|
||||
)
|
||||
|
||||
# Create or update the organization
|
||||
org_data = campaign_data["owner"]
|
||||
organization, _ = Organization.objects.update_or_create(
|
||||
id=org_data["id"],
|
||||
defaults={"name": org_data["name"]},
|
||||
)
|
||||
|
||||
# Create or update the drop campaign
|
||||
drop_campaign, _ = DropCampaign.objects.update_or_create(
|
||||
id=campaign_data["id"],
|
||||
defaults={
|
||||
"name": campaign_data["name"],
|
||||
"description": campaign_data["description"],
|
||||
"details_url": campaign_data.get("detailsURL", ""),
|
||||
"account_link_url": campaign_data.get("accountLinkURL", ""),
|
||||
"image_url": campaign_data.get("imageURL", ""),
|
||||
"start_at": campaign_data["startAt"],
|
||||
"end_at": campaign_data["endAt"],
|
||||
"status": campaign_data["status"],
|
||||
"is_account_connected": campaign_data["self"]["isAccountConnected"],
|
||||
"game": game,
|
||||
"owner": organization,
|
||||
},
|
||||
)
|
||||
|
||||
# Process time-based drops
|
||||
for drop_data in campaign_data.get("timeBasedDrops", []):
|
||||
time_based_drop, _ = TimeBasedDrop.objects.update_or_create(
|
||||
id=drop_data["id"],
|
||||
defaults={
|
||||
"name": drop_data["name"],
|
||||
"required_minutes_watched": drop_data["requiredMinutesWatched"],
|
||||
"required_subs": drop_data.get("requiredSubs", 0),
|
||||
"start_at": drop_data["startAt"],
|
||||
"end_at": drop_data["endAt"],
|
||||
"campaign": drop_campaign,
|
||||
},
|
||||
)
|
||||
|
||||
# Process benefits
|
||||
for benefit_edge in drop_data.get("benefitEdges", []):
|
||||
benefit_data = benefit_edge["benefit"]
|
||||
benefit, _ = DropBenefit.objects.update_or_create(
|
||||
id=benefit_data["id"],
|
||||
defaults={
|
||||
"name": benefit_data["name"],
|
||||
"image_asset_url": benefit_data.get("imageAssetURL", ""),
|
||||
"created_at": benefit_data["createdAt"],
|
||||
"entitlement_limit": benefit_data.get("entitlementLimit", 1),
|
||||
"is_ios_available": benefit_data.get("isIosAvailable", False),
|
||||
"distribution_type": benefit_data["distributionType"],
|
||||
"game": game,
|
||||
"owner_organization": organization,
|
||||
},
|
||||
)
|
||||
|
||||
# Create the relationship between drop and benefit
|
||||
DropBenefitEdge.objects.update_or_create(
|
||||
drop=time_based_drop,
|
||||
benefit=benefit,
|
||||
defaults={
|
||||
"entitlement_limit": benefit_edge.get("entitlementLimit", 1),
|
||||
},
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue