mirror of
https://github.com/wahyd4/links.git
synced 2026-08-08 21:04:53 +10:00
Add price monitor toggle
This commit is contained in:
@@ -5,6 +5,11 @@ urlpatterns = [
|
||||
path('', views.DashboardView.as_view(), name='pricemon-dashboard'),
|
||||
path('add/', views.WatcherCreateView.as_view(), name='pricemon-add'),
|
||||
path('<int:pk>/edit/', views.WatcherEditView.as_view(), name='pricemon-edit'),
|
||||
path(
|
||||
'<int:pk>/toggle-enabled/',
|
||||
views.WatcherToggleEnabledView.as_view(),
|
||||
name='pricemon-toggle-enabled',
|
||||
),
|
||||
path('<int:pk>/delete/', views.WatcherDeleteView.as_view(), name='pricemon-delete'),
|
||||
path('<int:pk>/history/', views.WatcherHistoryView.as_view(), name='pricemon-history'),
|
||||
path('<int:pk>/check/', views.CheckNowView.as_view(), name='pricemon-check-now'),
|
||||
|
||||
@@ -21,6 +21,7 @@ class DashboardView(TemplateView):
|
||||
active_alerts = PriceAlert.objects.filter(dismissed=False).select_related('watcher')
|
||||
ctx['watchers'] = watchers
|
||||
ctx['total_watchers'] = watchers.count()
|
||||
ctx['enabled_watchers_count'] = watchers.filter(enabled=True).count()
|
||||
ctx['active_alerts_count'] = active_alerts.count()
|
||||
ctx['active_alerts'] = active_alerts[:10]
|
||||
return ctx
|
||||
@@ -75,6 +76,16 @@ class WatcherDeleteView(View):
|
||||
return redirect('pricemon-dashboard')
|
||||
|
||||
|
||||
class WatcherToggleEnabledView(View):
|
||||
def post(self, request, pk):
|
||||
watcher = get_object_or_404(PriceWatcher, pk=pk)
|
||||
watcher.enabled = not watcher.enabled
|
||||
watcher.save(update_fields=['enabled'])
|
||||
state = 'enabled' if watcher.enabled else 'disabled'
|
||||
messages.success(request, f'Watcher "{watcher.name}" {state}.')
|
||||
return redirect('pricemon-dashboard')
|
||||
|
||||
|
||||
class WatcherHistoryView(TemplateView):
|
||||
template_name = 'pricemon/history.html'
|
||||
|
||||
|
||||
@@ -35,12 +35,7 @@
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-100 p-4">
|
||||
<div class="text-xs text-gray-400 font-medium uppercase tracking-wide mb-1">{% trans "Enabled" %}</div>
|
||||
<div class="text-2xl font-bold text-gray-900">
|
||||
{% with watchers|length as total %}
|
||||
{% for w in watchers %}{% if w.enabled %}{% endif %}{% endfor %}
|
||||
{{ watchers|length }}
|
||||
{% endwith %}
|
||||
</div>
|
||||
<div class="text-2xl font-bold text-gray-900">{{ enabled_watchers_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -147,6 +142,23 @@
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<form method="post" action="{% url 'pricemon-toggle-enabled' watcher.pk %}" class="inline"
|
||||
{% if watcher.enabled %}onsubmit="return confirm('Disable price monitoring for this product?')"{% endif %}>
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
title="{% if watcher.enabled %}{% trans 'Disable monitoring' %}{% else %}{% trans 'Enable monitoring' %}{% endif %}"
|
||||
class="p-2 rounded text-gray-400 {% if watcher.enabled %}hover:text-amber-600 hover:bg-amber-50{% else %}hover:text-green-600 hover:bg-green-50{% endif %} transition">
|
||||
{% if watcher.enabled %}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m5-3a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
{% else %}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5v14l11-7L8 5z"/>
|
||||
</svg>
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'pricemon-delete' watcher.pk %}" class="inline"
|
||||
onsubmit="return confirm('Delete watcher {{ watcher.name }}?')">
|
||||
{% csrf_token %}
|
||||
@@ -262,6 +274,23 @@
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<form method="post" action="{% url 'pricemon-toggle-enabled' watcher.pk %}" class="inline"
|
||||
{% if watcher.enabled %}onsubmit="return confirm('Disable price monitoring for this product?')"{% endif %}>
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
title="{% if watcher.enabled %}{% trans 'Disable monitoring' %}{% else %}{% trans 'Enable monitoring' %}{% endif %}"
|
||||
class="p-1.5 rounded text-gray-400 {% if watcher.enabled %}hover:text-amber-600 hover:bg-amber-50{% else %}hover:text-green-600 hover:bg-green-50{% endif %} transition">
|
||||
{% if watcher.enabled %}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m5-3a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
{% else %}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5v14l11-7L8 5z"/>
|
||||
</svg>
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'pricemon-delete' watcher.pk %}" class="inline"
|
||||
onsubmit="return confirm('Delete watcher {{ watcher.name }}?')">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
|
||||
from pricemon.models import PriceWatcher
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def watcher():
|
||||
with patch('pricemon.tasks.schedule_watcher'):
|
||||
return PriceWatcher.objects.create(
|
||||
name='Test product',
|
||||
url='https://example.com/product',
|
||||
css_selector='.price',
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestPriceMonitorToggle:
|
||||
def test_dashboard_shows_disable_action_for_enabled_watcher(self, client, watcher):
|
||||
response = client.get(reverse('pricemon-dashboard'))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert reverse('pricemon-toggle-enabled', args=[watcher.pk]) in response.content.decode()
|
||||
assert 'Disable monitoring' in response.content.decode()
|
||||
assert response.context['enabled_watchers_count'] == 1
|
||||
|
||||
def test_toggle_disables_watcher_and_unschedules_job(self, client, watcher):
|
||||
with patch('pricemon.tasks.unschedule_watcher') as unschedule_watcher:
|
||||
response = client.post(reverse('pricemon-toggle-enabled', args=[watcher.pk]))
|
||||
|
||||
watcher.refresh_from_db()
|
||||
assert response.status_code == 302
|
||||
assert response.url == reverse('pricemon-dashboard')
|
||||
assert watcher.enabled is False
|
||||
unschedule_watcher.assert_called_once()
|
||||
|
||||
def test_toggle_enables_paused_watcher_and_schedules_job(self, client, watcher):
|
||||
with patch('pricemon.tasks.unschedule_watcher'):
|
||||
watcher.enabled = False
|
||||
watcher.save(update_fields=['enabled'])
|
||||
|
||||
with patch('pricemon.tasks.schedule_watcher') as schedule_watcher:
|
||||
response = client.post(reverse('pricemon-toggle-enabled', args=[watcher.pk]))
|
||||
|
||||
watcher.refresh_from_db()
|
||||
assert response.status_code == 302
|
||||
assert watcher.enabled is True
|
||||
schedule_watcher.assert_called_once()
|
||||
|
||||
def test_toggle_rejects_get_requests(self, client, watcher):
|
||||
response = client.get(reverse('pricemon-toggle-enabled', args=[watcher.pk]))
|
||||
|
||||
assert response.status_code == 405
|
||||
Reference in New Issue
Block a user