mirror of
https://github.com/pretix/pretix.git
synced 2026-05-13 16:33:59 +00:00
Add user interface for manual retry
This commit is contained in:
@@ -52,9 +52,9 @@ class OrderSyncQueue(models.Model):
|
||||
failed_attempts = models.PositiveIntegerField(default=0)
|
||||
not_before = models.DateTimeField(blank=False, null=False, db_index=True)
|
||||
need_manual_retry = models.CharField(blank=True, null=True, choices=[
|
||||
('recoverable', _('Temporary error, retry exceeded')),
|
||||
('unrecoverable', _('Misconfiguration')),
|
||||
('unhandled', _('Unhandled exception'))
|
||||
('recoverable', _('Temporary error, auto-retry limit exceeded')),
|
||||
('unrecoverable', _('Misconfiguration, please check provider settings')),
|
||||
('unhandled', _('System error, needs manual intervention'))
|
||||
])
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -451,6 +451,11 @@ def get_global_navigation(request):
|
||||
'url': reverse('control:global.sysreport'),
|
||||
'active': (url.url_name == 'global.sysreport'),
|
||||
},
|
||||
{
|
||||
'label': _('Data sync problems'),
|
||||
'url': reverse('control:global.datasync.failedjobs'),
|
||||
'active': (url.url_name == 'global.datasync.failedjobs'),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<form action="{% url "control:event.order.sync_job" organizer=event.organizer.slug event=event.slug code=order.code provider=identifier %}" method="post" class="form-inline pull-right">
|
||||
{% csrf_token %}
|
||||
{% if pending %}
|
||||
{% if pending.not_before > now %}
|
||||
{% if pending.not_before > now or pending.need_manual_retry %}
|
||||
<button type="submit" name="run_job_now" value="{{ pending.pk }}" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Retry now" %}</button>
|
||||
{% endif %}
|
||||
<button type="submit" name="cancel_job" value="{{ pending.pk }}" class="btn btn-danger"><i class="fa fa-times"></i> {% trans "Cancel" %}</button>
|
||||
@@ -26,7 +26,10 @@
|
||||
<p><b>{{ display_name }}</b></p>
|
||||
{% if pending %}
|
||||
<p>
|
||||
{% if pending.failed_attempts %}
|
||||
{% if pending.need_manual_retry %}
|
||||
<i class="fa fa-warning"></i>
|
||||
{% trans "Error" %}: {{ pending.get_need_manual_retry_display }}
|
||||
{% elif pending.failed_attempts %}
|
||||
<i class="fa fa-warning"></i>
|
||||
{% blocktrans trimmed with num=pending.failed_attempts max=pending.max_retry_attempts %}
|
||||
Error. Retry {{ num }} of {{ max }}.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Sync problems" %}</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% if queue_items %}
|
||||
<label aria-label="{% trans "select all rows for batch-operation" %}"
|
||||
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th>{% trans "Order" %}</th>
|
||||
<th>{% trans "Sync provider" %}</th>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "Failure mode" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in queue_items %}
|
||||
<tr>
|
||||
<td><input type="checkbox" name="idlist" value="{{ item.pk }}"></td>
|
||||
<td><a href="{% url "control:event.order" event=item.order.event.slug organizer=item.order.event.organizer.slug code=item.order.code %}">{{ item.order.full_code }}</a></td>
|
||||
<td>{{ item.sync_provider }}</td>
|
||||
<td>{{ item.triggered }}</td>
|
||||
<td>{{ item.get_need_manual_retry_display }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" align="center">{% trans "No problems." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% if queue_items %}
|
||||
<tfoot>
|
||||
<tr><td colspan="5">
|
||||
<button type="submit" name="action" value="retry" class="btn btn-primary"><i class="fa fa-refresh"></i> Retry selected</button>
|
||||
<button type="submit" name="action" value="cancel" class="btn btn-danger"><i class="fa fa-times"></i> Cancel selected</button>
|
||||
</td></tr>
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
</table>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -58,6 +58,7 @@ urlpatterns = [
|
||||
re_path(r'^global/license/$', global_settings.LicenseCheckView.as_view(), name='global.license'),
|
||||
re_path(r'^global/sysreport/$', global_settings.SysReportView.as_view(), name='global.sysreport'),
|
||||
re_path(r'^global/message/$', global_settings.MessageView.as_view(), name='global.message'),
|
||||
re_path(r'^global/datasync/failedjobs/$', datasync.GlobalFailedSyncJobsView.as_view(), name='global.datasync.failedjobs'),
|
||||
re_path(r'^logdetail/$', global_settings.LogDetailView.as_view(), name='global.logdetail'),
|
||||
re_path(r'^logdetail/payment/$', global_settings.PaymentDetailView.as_view(), name='global.paymentdetail'),
|
||||
re_path(r'^logdetail/refund/$', global_settings.RefundDetailView.as_view(), name='global.refunddetail'),
|
||||
@@ -248,6 +249,7 @@ urlpatterns = [
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/export/(?P<pk>[^/]+)/delete$', organizer.DeleteScheduledExportView.as_view(),
|
||||
name='organizer.export.scheduled.delete'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/ticket_select2$', typeahead.ticket_select2, name='organizer.ticket_select2'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/datasync/failedjobs/$', datasync.OrganizerFailedSyncJobsView.as_view(), name='organizer.datasync.failedjobs'),
|
||||
re_path(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
|
||||
re_path(r'^events/$', main.EventList.as_view(), name='events'),
|
||||
re_path(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
|
||||
|
||||
@@ -29,9 +29,12 @@ from django.shortcuts import redirect
|
||||
from django.template.loader import get_template
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import TemplateView, ListView
|
||||
|
||||
from pretix.base.datasync.datasync import sync_targets
|
||||
from pretix.base.models import Event, Order
|
||||
from pretix.base.models.datasync import OrderSyncQueue
|
||||
from pretix.control.permissions import AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin
|
||||
from pretix.control.signals import order_info
|
||||
from pretix.control.views.orders import OrderView
|
||||
|
||||
@@ -77,6 +80,7 @@ class ControlSyncJob(OrderView):
|
||||
elif self.request.POST.get("run_job_now"):
|
||||
job = self.order.queued_sync_jobs.get(pk=self.request.POST.get("run_job_now"))
|
||||
job.not_before = now()
|
||||
job.need_manual_retry = None
|
||||
job.save()
|
||||
messages.success(self.request, _('The sync job has been set to run as soon as possible.'))
|
||||
|
||||
@@ -84,3 +88,44 @@ class ControlSyncJob(OrderView):
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return HttpResponseNotAllowed(['POST'])
|
||||
|
||||
|
||||
class FailedSyncJobsView(ListView):
|
||||
template_name = 'pretixcontrol/datasync/failed_jobs.html'
|
||||
model = OrderSyncQueue
|
||||
context_object_name = 'queue_items'
|
||||
paginate_by = 20
|
||||
ordering = ('triggered',)
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(
|
||||
need_manual_retry__isnull=False,
|
||||
).select_related(
|
||||
'order'
|
||||
)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
items = self.get_queryset().filter(pk__in=request.POST.getlist('idlist'))
|
||||
|
||||
if self.request.POST.get("action") == "retry":
|
||||
for item in items:
|
||||
item.not_before = now()
|
||||
item.need_manual_retry = None
|
||||
item.save()
|
||||
messages.success(self.request, _('The selected jobs have been set to run as soon as possible.'))
|
||||
elif self.request.POST.get("action") == "cancel":
|
||||
items.delete()
|
||||
messages.success(self.request, _('The selected jobs have been canceled.'))
|
||||
|
||||
return redirect(request.get_full_path())
|
||||
|
||||
|
||||
class GlobalFailedSyncJobsView(AdministratorPermissionRequiredMixin, FailedSyncJobsView):
|
||||
pass
|
||||
|
||||
|
||||
class OrganizerFailedSyncJobsView(OrganizerPermissionRequiredMixin, FailedSyncJobsView):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(
|
||||
event__organizer=self.request.organizer
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user