mirror of
https://github.com/pretix/pretix.git
synced 2026-08-05 09:57:49 +00:00
Log view: New filter form (#6412)
* Rework filter form for event log view * Log view: New filter form * isort * Update src/pretix/control/forms/filter.py Co-authored-by: luelista <weller@rami.io> --------- Co-authored-by: luelista <weller@rami.io>
This commit is contained in:
co-authored by
luelista
parent
2381af4267
commit
ca03a2556d
@@ -39,6 +39,7 @@ from urllib.parse import urlencode
|
||||
from django import forms
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import (
|
||||
Count, Exists, F, Max, Model, OrderBy, OuterRef, Q, QuerySet,
|
||||
)
|
||||
@@ -59,7 +60,7 @@ from pretix.base.models import (
|
||||
Gate, Invoice, InvoiceAddress, Item, Order, OrderPayment, OrderPosition,
|
||||
OrderRefund, Organizer, OutgoingMail, Question, QuestionAnswer, Quota,
|
||||
SalesChannel, SubEvent, SubEventMetaValue, Team, TeamAPIToken, TeamInvite,
|
||||
Voucher,
|
||||
User, Voucher,
|
||||
)
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.base.timeframes import (
|
||||
@@ -3007,3 +3008,91 @@ class OutgoingMailFilterForm(FilterForm):
|
||||
qs = qs.order_by("-created", "-pk")
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class LogFilterForm(FilterForm):
|
||||
source = forms.ChoiceField(
|
||||
label=_('Source'),
|
||||
choices=[
|
||||
('', _('All sources')),
|
||||
('team', _('Team actions')),
|
||||
('customer', _('Customer actions')),
|
||||
('device', _('Device actions')),
|
||||
],
|
||||
required=False
|
||||
)
|
||||
device = SafeModelChoiceField(
|
||||
label=_('Device'),
|
||||
empty_label=_('All devices'),
|
||||
queryset=Device.objects.none(),
|
||||
required=False
|
||||
)
|
||||
user_email = forms.EmailField(
|
||||
label=_('User email'),
|
||||
widget=forms.EmailInput(
|
||||
attrs={"placeholder": _('All users')}
|
||||
),
|
||||
required=False
|
||||
)
|
||||
action_type = forms.CharField(
|
||||
widget=forms.HiddenInput,
|
||||
required=False,
|
||||
)
|
||||
content_type = forms.ModelChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
widget=forms.HiddenInput,
|
||||
required=False,
|
||||
)
|
||||
object = forms.IntegerField(
|
||||
widget=forms.HiddenInput,
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.organizer = kwargs.pop('organizer')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['device'].queryset = self.organizer.devices.all().order_by('device_id')
|
||||
self.fields['device'].widget = Select2(
|
||||
attrs={
|
||||
'data-model-select2': 'generic',
|
||||
'data-select2-url': reverse('control:organizer.devices.select2', kwargs={
|
||||
'organizer': self.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': _('All devices'),
|
||||
}
|
||||
)
|
||||
self.fields['device'].widget.choices = self.fields['device'].choices
|
||||
self.fields['device'].label = _('Device')
|
||||
|
||||
def filter_qs(self, qs):
|
||||
fdata = self.cleaned_data
|
||||
if fdata.get('source') == 'team':
|
||||
qs = qs.filter(user__isnull=False)
|
||||
elif fdata.get('source') == 'device':
|
||||
qs = qs.filter(device__isnull=False)
|
||||
elif fdata.get('source') == 'customer':
|
||||
qs = qs.filter(user__isnull=True, device__isnull=True)
|
||||
|
||||
if fdata.get('device'):
|
||||
qs = qs.filter(device_id=fdata['device'].pk)
|
||||
|
||||
if fdata.get('action_type'):
|
||||
qs = qs.filter(action_type__in=fdata['action_type'].split(','))
|
||||
|
||||
if fdata.get('user_email'):
|
||||
try:
|
||||
user = User.objects.get(email=fdata['user_email'].lower())
|
||||
qs = qs.filter(user=user)
|
||||
except User.DoesNotExist:
|
||||
# Do not actively leak info that this user does not exist. Yes, this will likely have a
|
||||
# timing attack possibility, but with the magic that database query planners do,
|
||||
# it's probably impossible to avoid that.
|
||||
qs = qs.none()
|
||||
|
||||
if fdata.get('content_type'):
|
||||
qs = qs.filter(content_type=fdata.get('content_type'))
|
||||
|
||||
if fdata.get('object'):
|
||||
qs = qs.filter(object_id=fdata.get('object'))
|
||||
return qs
|
||||
|
||||
@@ -1,41 +1,12 @@
|
||||
{% extends "pretixcontrol/items/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load icon %}
|
||||
{% block title %}{% trans "Event logs" %}{% endblock %}
|
||||
{% block inside %}
|
||||
<h1>{% trans "Event logs" %}</h1>
|
||||
<form class="form-inline helper-display-inline" action="" method="get">
|
||||
<input type="hidden" name="content_type" value="{{ request.GET.content_type }}">
|
||||
<input type="hidden" name="object" value="{{ request.GET.object }}">
|
||||
<p>
|
||||
<select name="user" class="form-control">
|
||||
<option value="">{% trans "All actions" %}</option>
|
||||
<option value="yes" {% if request.GET.user == "yes" %}selected="selected"{% endif %}>
|
||||
{% trans "Team actions" %}
|
||||
</option>
|
||||
<option value="no" {% if request.GET.user == "no" %}selected="selected"{% endif %}>
|
||||
{% trans "Customer actions" %}
|
||||
</option>
|
||||
{% for up in userlist %}
|
||||
{% if up.user__id %}
|
||||
<option value="{{ up.user__id }}"
|
||||
{% if request.GET.user == up.user__id %}selected="selected"{% endif %}>
|
||||
{{ up.user__email }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for d in devicelist %}
|
||||
{% if d.device__id %}
|
||||
<option value="d-{{ d.device__id }}"
|
||||
{% if "d-" in request.GET.user and request.GET.user|slice:"2:" == d.device__id|slugify %}selected="selected"{% endif %}>
|
||||
{{ d.device__name }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
|
||||
</p>
|
||||
</form>
|
||||
{% include "pretixcontrol/fragment_log_filter_form.html" %}
|
||||
<ul class="list-group">
|
||||
{% for log in logs %}
|
||||
<li class="list-group-item logentry">
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load icon %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Filter" %}
|
||||
</h3>
|
||||
</div>
|
||||
<form class="panel-body filter-form" action="" method="get">
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-xs-6">
|
||||
{% bootstrap_field filter_form.source %}
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-6">
|
||||
{% bootstrap_field filter_form.device %}
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-6">
|
||||
{% bootstrap_field filter_form.user_email %}
|
||||
</div>
|
||||
</div>
|
||||
{{ filter_form.action_type }}
|
||||
{{ filter_form.content_type }}
|
||||
{{ filter_form.object }}
|
||||
{% if filter_form.is_valid and filter_form.cleaned_data.action_type %}
|
||||
{% icon "filter" %} <code>{{ filter_form.cleaned_data.action_type }}</code>
|
||||
{% endif %}
|
||||
{% if filter_form.is_valid and filter_form.cleaned_data.content_type and filter_form.cleaned_data.object %}
|
||||
{% icon "filter" %} {% trans "Specific object selected" %}
|
||||
{% endif %}
|
||||
<div class="text-right flip">
|
||||
<button class="btn btn-primary btn-lg" type="submit">
|
||||
<span class="fa fa-filter"></span>
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -4,24 +4,7 @@
|
||||
{% block title %}{% trans "Organizer logs" %}{% endblock %}
|
||||
{% block inside %}
|
||||
<h1>{% trans "Organizer logs" %}</h1>
|
||||
<form class="form-inline helper-display-inline" action="" method="get">
|
||||
<input type="hidden" name="content_type" value="{{ request.GET.content_type }}">
|
||||
<input type="hidden" name="object" value="{{ request.GET.object }}">
|
||||
<p>
|
||||
<select name="user" class="form-control">
|
||||
<option value="">{% trans "All actions" %}</option>
|
||||
{% for up in userlist %}
|
||||
{% if up.user__id %}
|
||||
<option value="{{ up.user__id }}"
|
||||
{% if request.GET.user == up.user__id %}selected="selected"{% endif %}>
|
||||
{{ up.user__email }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
|
||||
</p>
|
||||
</form>
|
||||
{% include "pretixcontrol/fragment_log_filter_form.html" %}
|
||||
<ul class="list-group">
|
||||
{% for log in logs %}
|
||||
<li class="list-group-item logentry">
|
||||
|
||||
@@ -60,7 +60,7 @@ from django.http import (
|
||||
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed,
|
||||
JsonResponse,
|
||||
)
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import conditional_escape, format_html
|
||||
@@ -119,6 +119,7 @@ from ...helpers.compat import CompatDeleteView
|
||||
from ...helpers.format import (
|
||||
PlainHtmlAlternativeString, SafeFormatter, format_map,
|
||||
)
|
||||
from ..forms.filter import LogFilterForm
|
||||
from ..logdisplay import OVERVIEW_BANLIST
|
||||
from . import CreateView, PaginationMixin, UpdateView
|
||||
|
||||
@@ -1282,32 +1283,20 @@ class EventLog(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
]
|
||||
qs = qs.filter(content_type__in=allowed_types)
|
||||
|
||||
if self.request.GET.get('user') == 'yes':
|
||||
qs = qs.filter(user__isnull=False)
|
||||
elif self.request.GET.get('user') == 'no':
|
||||
qs = qs.filter(user__isnull=True)
|
||||
elif self.request.GET.get('user', '').startswith('d-'):
|
||||
qs = qs.filter(device_id=self.request.GET.get('user')[2:])
|
||||
elif self.request.GET.get('user'):
|
||||
qs = qs.filter(user_id=self.request.GET.get('user'))
|
||||
|
||||
if self.request.GET.get('action_type'):
|
||||
qs = qs.filter(action_type=self.request.GET['action_type'])
|
||||
|
||||
if self.request.GET.get('content_type'):
|
||||
qs = qs.filter(content_type=get_object_or_404(ContentType, pk=self.request.GET.get('content_type')))
|
||||
|
||||
if self.request.GET.get('object'):
|
||||
qs = qs.filter(object_id=self.request.GET.get('object'))
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
ctx['userlist'] = self.request.event.logentry_set.order_by().distinct().values('user__id', 'user__email')
|
||||
ctx['devicelist'] = self.request.event.logentry_set.order_by('device__name').distinct().values('device__id', 'device__name')
|
||||
ctx['filter_form'] = self.filter_form
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
def filter_form(self):
|
||||
return LogFilterForm(data=self.request.GET, organizer=self.request.organizer)
|
||||
|
||||
|
||||
class EventComment(EventPermissionRequiredMixin, View):
|
||||
permission = 'event.settings.general:write'
|
||||
|
||||
@@ -113,7 +113,8 @@ from pretix.base.views.tasks import AsyncAction
|
||||
from pretix.control.forms.exports import ScheduledOrganizerExportForm
|
||||
from pretix.control.forms.filter import (
|
||||
CustomerFilterForm, DeviceFilterForm, EventFilterForm, GiftCardFilterForm,
|
||||
OrganizerFilterForm, ReusableMediaFilterForm, TeamFilterForm,
|
||||
LogFilterForm, OrganizerFilterForm, ReusableMediaFilterForm,
|
||||
TeamFilterForm,
|
||||
)
|
||||
from pretix.control.forms.orders import ExporterForm
|
||||
from pretix.control.forms.organizer import (
|
||||
@@ -2672,16 +2673,21 @@ class LogView(OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
'user', 'content_type', 'api_token', 'oauth_application', 'device'
|
||||
).order_by('-datetime')
|
||||
qs = qs.exclude(action_type__in=OVERVIEW_BANLIST)
|
||||
if self.request.GET.get('action_type'):
|
||||
qs = qs.filter(action_type=self.request.GET['action_type'])
|
||||
if self.request.GET.get('user'):
|
||||
qs = qs.filter(user_id=self.request.GET.get('user'))
|
||||
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
ctx['filter_form'] = self.filter_form
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
def filter_form(self):
|
||||
return LogFilterForm(data=self.request.GET, organizer=self.request.organizer)
|
||||
|
||||
|
||||
class MembershipTypeListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
|
||||
model = MembershipType
|
||||
|
||||
Reference in New Issue
Block a user