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
128
twitch/models.py
128
twitch/models.py
|
|
@ -1 +1,127 @@
|
|||
# Create your models here.
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Game(models.Model):
|
||||
"""Represents a game on Twitch."""
|
||||
|
||||
id = models.TextField(primary_key=True)
|
||||
slug = models.TextField(blank=True, default="")
|
||||
display_name = models.TextField()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return a string representation of the game."""
|
||||
return self.display_name
|
||||
|
||||
|
||||
class Organization(models.Model):
|
||||
"""Represents an organization on Twitch that can own drop campaigns."""
|
||||
|
||||
id = models.TextField(primary_key=True)
|
||||
name = models.TextField()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return a string representation of the organization."""
|
||||
return self.name
|
||||
|
||||
|
||||
class DropCampaign(models.Model):
|
||||
"""Represents a Twitch drop campaign."""
|
||||
|
||||
STATUS_CHOICES: ClassVar[list[tuple[str, str]]] = [
|
||||
("ACTIVE", "Active"),
|
||||
("UPCOMING", "Upcoming"),
|
||||
("EXPIRED", "Expired"),
|
||||
]
|
||||
|
||||
id = models.TextField(primary_key=True)
|
||||
name = models.TextField()
|
||||
description = models.TextField(blank=True)
|
||||
details_url = models.URLField(max_length=500, blank=True, default="")
|
||||
account_link_url = models.URLField(max_length=500, blank=True, default="")
|
||||
image_url = models.URLField(max_length=500, blank=True, default="")
|
||||
start_at = models.DateTimeField()
|
||||
end_at = models.DateTimeField()
|
||||
status = models.TextField(choices=STATUS_CHOICES)
|
||||
is_account_connected = models.BooleanField(default=False)
|
||||
|
||||
# Foreign keys
|
||||
game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="drop_campaigns")
|
||||
owner = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="drop_campaigns")
|
||||
|
||||
# Tracking fields
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return a string representation of the drop campaign."""
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def is_active(self) -> bool:
|
||||
"""Check if the campaign is currently active."""
|
||||
now = timezone.now()
|
||||
return self.start_at <= now <= self.end_at and self.status == "ACTIVE"
|
||||
|
||||
|
||||
class DropBenefit(models.Model):
|
||||
"""Represents a benefit that can be earned from a drop."""
|
||||
|
||||
DISTRIBUTION_TYPES: ClassVar[list[tuple[str, str]]] = [
|
||||
("DIRECT_ENTITLEMENT", "Direct Entitlement"),
|
||||
("CODE", "Code"),
|
||||
]
|
||||
|
||||
id = models.TextField(primary_key=True)
|
||||
name = models.TextField()
|
||||
image_asset_url = models.URLField(max_length=500, blank=True, default="")
|
||||
created_at = models.DateTimeField()
|
||||
entitlement_limit = models.PositiveIntegerField(default=1)
|
||||
is_ios_available = models.BooleanField(default=False)
|
||||
distribution_type = models.TextField(choices=DISTRIBUTION_TYPES)
|
||||
|
||||
# Foreign keys
|
||||
game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="drop_benefits")
|
||||
owner_organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="drop_benefits")
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return a string representation of the drop benefit."""
|
||||
return self.name
|
||||
|
||||
|
||||
class TimeBasedDrop(models.Model):
|
||||
"""Represents a time-based drop in a drop campaign."""
|
||||
|
||||
id = models.TextField(primary_key=True)
|
||||
name = models.TextField()
|
||||
required_minutes_watched = models.PositiveIntegerField()
|
||||
required_subs = models.PositiveIntegerField(default=0)
|
||||
start_at = models.DateTimeField()
|
||||
end_at = models.DateTimeField()
|
||||
|
||||
# Foreign keys
|
||||
campaign = models.ForeignKey(DropCampaign, on_delete=models.CASCADE, related_name="time_based_drops")
|
||||
benefits = models.ManyToManyField(DropBenefit, through="DropBenefitEdge", related_name="drops")
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return a string representation of the time-based drop."""
|
||||
return self.name
|
||||
|
||||
|
||||
class DropBenefitEdge(models.Model):
|
||||
"""Represents the relationship between a TimeBasedDrop and a DropBenefit."""
|
||||
|
||||
drop = models.ForeignKey(TimeBasedDrop, on_delete=models.CASCADE)
|
||||
benefit = models.ForeignKey(DropBenefit, on_delete=models.CASCADE)
|
||||
entitlement_limit = models.PositiveIntegerField(default=1)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("drop", "benefit")
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return a string representation of the drop benefit edge."""
|
||||
return f"{self.drop.name} - {self.benefit.name}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue