Update price monitor settings

This commit is contained in:
2026-05-23 10:15:09 +10:00
parent 4721eec917
commit 227bc9b1de
5 changed files with 138 additions and 3 deletions
+9 -1
View File
@@ -4,7 +4,15 @@ from pricemon.models import PriceAlert, PriceSnapshot, PriceWatcher
@admin.register(PriceWatcher)
class PriceWatcherAdmin(admin.ModelAdmin):
list_display = ['name', 'url', 'last_price', 'last_checked_at', 'enabled', 'check_interval_hours']
list_display = [
'name',
'url',
'initial_price',
'last_price',
'last_checked_at',
'enabled',
'check_interval_hours',
]
list_filter = ['enabled', 'check_interval_hours']
search_fields = ['name', 'url']
@@ -0,0 +1,41 @@
from django.db import migrations, models
def backfill_initial_price(apps, schema_editor):
PriceWatcher = apps.get_model('pricemon', 'PriceWatcher')
PriceSnapshot = apps.get_model('pricemon', 'PriceSnapshot')
for watcher in PriceWatcher.objects.all():
if watcher.initial_price is not None:
continue
snapshot = (
PriceSnapshot.objects.filter(watcher_id=watcher.pk, price__isnull=False)
.order_by('checked_at', 'pk')
.first()
)
if snapshot is None:
continue
watcher.initial_price = snapshot.price
watcher.save(update_fields=['initial_price'])
def noop_reverse(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('pricemon', '0004_alert_price_threshold_recurring'),
]
operations = [
migrations.AddField(
model_name='pricewatcher',
name='initial_price',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True),
),
migrations.RunPython(backfill_initial_price, noop_reverse),
]
+39 -2
View File
@@ -1,3 +1,5 @@
from decimal import Decimal, ROUND_HALF_UP
from django.db import models
@@ -19,15 +21,22 @@ class PriceWatcher(models.Model):
)
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,
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).',
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)
@@ -37,6 +46,34 @@ class PriceWatcher(models.Model):
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')
+3
View File
@@ -53,6 +53,9 @@ def check_watcher(watcher_pk: int) -> None:
update_fields = ['last_checked_at']
if price is not None:
if watcher.initial_price is None:
watcher.initial_price = price
update_fields.append('initial_price')
watcher.last_price = price
update_fields.append('last_price')
watcher.last_checked_at = now()
+46
View File
@@ -92,6 +92,29 @@
{% else %}
<span class="text-gray-400 text-lg"></span>
{% endif %}
<div class="mt-1 text-xs text-gray-500">
{% if watcher.initial_price %}
<span class="block">{% trans "Initial" %} ${{ watcher.initial_price }}</span>
{% endif %}
{% if watcher.price_change_direction %}
{% if watcher.price_change_direction == 'up' %}
<span class="block text-red-600">
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
{% trans "up" %} {{ watcher.price_change_pct }}%
</span>
{% elif watcher.price_change_direction == 'down' %}
<span class="block text-green-600">
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
{% trans "down" %} {{ watcher.price_change_pct }}%
</span>
{% else %}
<span class="block text-gray-500">
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
0.00%
</span>
{% endif %}
{% endif %}
</div>
</div>
</div>
<!-- Actions row -->
@@ -168,6 +191,29 @@
{% else %}
<span class="text-gray-400"></span>
{% endif %}
<div class="mt-1 space-y-0.5 text-xs text-gray-500">
{% if watcher.initial_price %}
<div>{% trans "Initial" %} ${{ watcher.initial_price }}</div>
{% endif %}
{% if watcher.price_change_direction %}
{% if watcher.price_change_direction == 'up' %}
<div class="font-medium text-red-600">
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
{% trans "up" %} {{ watcher.price_change_pct }}%
</div>
{% elif watcher.price_change_direction == 'down' %}
<div class="font-medium text-green-600">
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
{% trans "down" %} {{ watcher.price_change_pct }}%
</div>
{% else %}
<div class="font-medium text-gray-500">
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
0.00%
</div>
{% endif %}
{% endif %}
</div>
</td>
<td class="px-4 py-3 text-gray-500">
{% if watcher.last_checked_at %}