Files
links/pricemon/models.py
T
2026-05-23 10:15:09 +10:00

105 lines
3.6 KiB
Python

from decimal import Decimal, ROUND_HALF_UP
from django.db import models
class PriceWatcher(models.Model):
INTERVAL_CHOICES = [
(6, 'Every 6 hours'),
(12, 'Every 12 hours'),
(24, 'Every day'),
(48, 'Every 2 days'),
(72, 'Every 3 days'),
(168, 'Every 7 days'),
]
name = models.CharField(max_length=200)
url = models.URLField(max_length=2000)
css_selector = models.CharField(
max_length=500,
help_text='CSS selector for the price element, e.g. span.price or .product-price',
)
check_interval_hours = models.IntegerField(choices=INTERVAL_CHOICES, default=24)
alert_price_threshold = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
help_text='Send an alert when the detected price is at or below this value. Leave blank to disable alerts.',
)
recurring_notification = models.BooleanField(
default=True,
help_text=(
'Keep notifying on every check while the price meets the threshold. '
'Disable to notify only once (until you dismiss the alert).'
),
)
enabled = models.BooleanField(default=True)
last_checked_at = models.DateTimeField(null=True, blank=True)
initial_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
last_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['name']
def __str__(self):
return self.name
@property
def price_change_direction(self):
if self.initial_price is None or self.last_price is None:
return None
if self.last_price > self.initial_price:
return 'up'
if self.last_price < self.initial_price:
return 'down'
return 'flat'
@property
def price_change_pct(self):
if self.initial_price is None or self.last_price is None or self.initial_price == 0:
return None
pct = ((self.last_price - self.initial_price) / self.initial_price) * Decimal('100')
return abs(pct).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
@property
def price_change_symbol(self):
direction = self.price_change_direction
if direction == 'up':
return '↑'
if direction == 'down':
return '↓'
if direction == 'flat':
return '→'
return ''
class PriceSnapshot(models.Model):
watcher = models.ForeignKey(PriceWatcher, on_delete=models.CASCADE, related_name='snapshots')
price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
raw_text = models.CharField(max_length=500, blank=True)
error = models.CharField(max_length=500, blank=True)
checked_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-checked_at']
def __str__(self):
return f'{self.watcher.name} @ {self.checked_at:%Y-%m-%d %H:%M}'
class PriceAlert(models.Model):
watcher = models.ForeignKey(PriceWatcher, on_delete=models.CASCADE, related_name='alerts')
old_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
new_price = models.DecimalField(max_digits=12, decimal_places=2)
drop_pct = models.DecimalField(max_digits=5, decimal_places=2, default=0)
created_at = models.DateTimeField(auto_now_add=True)
dismissed = models.BooleanField(default=False)
class Meta:
ordering = ['-created_at']
def __str__(self):
return f'{self.watcher.name}: ${self.old_price} → ${self.new_price}'