mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-24 08:52:15 +00:00
Refactor search logic to a create an 'omnisearch' (#484)
This commit is contained in:
@@ -172,9 +172,9 @@ class EventRiskAssessmentForm(forms.ModelForm):
|
||||
unexpected_values = []
|
||||
for field, value in models.RiskAssessment.expected_values.items():
|
||||
if self.cleaned_data.get(field) != value:
|
||||
unexpected_values.append("<li>{}</li>".format(self._meta.model._meta.get_field(field).help_text))
|
||||
unexpected_values.append(f"<li>{self._meta.model._meta.get_field(field).help_text}</li>")
|
||||
if len(unexpected_values) > 0 and not self.cleaned_data.get('supervisor_consulted'):
|
||||
raise forms.ValidationError("Your answers to these questions: <ul>{}</ul> require consulting with a supervisor.".format(''.join([str(elem) for elem in unexpected_values])), code='unusual_answers')
|
||||
raise forms.ValidationError(f"Your answers to these questions: <ul>{''.join([str(elem) for elem in unexpected_values])}</ul> require consulting with a supervisor.", code='unusual_answers')
|
||||
return super(EventRiskAssessmentForm, self).clean()
|
||||
|
||||
class Meta:
|
||||
@@ -235,9 +235,9 @@ class EventChecklistForm(forms.ModelForm):
|
||||
pk = int(key.split('_')[1])
|
||||
|
||||
for field in other_fields:
|
||||
value = self.data['{}_{}'.format(field, pk)]
|
||||
value = self.data[f'{field}_{pk}']
|
||||
if value == '':
|
||||
raise forms.ValidationError('Add a {} to crewmember {}'.format(field, pk), code='{}_mismatch'.format(field))
|
||||
raise forms.ValidationError(f'Add a {field} to crewmember {pk}', code=f'{field}_mismatch')
|
||||
|
||||
try:
|
||||
item = models.EventChecklistCrew.objects.get(pk=pk)
|
||||
|
||||
@@ -8,6 +8,7 @@ from urllib.parse import urlparse
|
||||
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.db.models import Q, F
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -20,6 +21,17 @@ from reversion.models import Version
|
||||
from versioning.versioning import RevisionMixin
|
||||
|
||||
|
||||
def filter_by_pk(filt, query):
|
||||
# try and parse an int
|
||||
try:
|
||||
val = int(query)
|
||||
filt = filt | Q(pk=val)
|
||||
except: # noqa
|
||||
# not an integer
|
||||
pass
|
||||
return filt
|
||||
|
||||
|
||||
class Profile(AbstractUser):
|
||||
initials = models.CharField(max_length=5, null=True, blank=False)
|
||||
phone = models.CharField(max_length=13, blank=True, default='')
|
||||
@@ -51,7 +63,7 @@ class Profile(AbstractUser):
|
||||
def name(self):
|
||||
name = self.get_full_name()
|
||||
if self.initials:
|
||||
name += ' "{}"'.format(self.initials)
|
||||
name += f' "{self.initials}"'
|
||||
return name
|
||||
|
||||
@property
|
||||
@@ -70,15 +82,28 @@ class Profile(AbstractUser):
|
||||
return self.name
|
||||
|
||||
|
||||
class ContactableManager(models.Manager):
|
||||
def search(self, query=None):
|
||||
qs = self.get_queryset()
|
||||
if query is not None:
|
||||
or_lookup = Q(name__icontains=query) | Q(email__icontains=query) | Q(address__icontains=query) | Q(notes__icontains=query) | Q(
|
||||
phone__startswith=query) | Q(phone__endswith=query)
|
||||
|
||||
or_lookup = filter_by_pk(or_lookup, query)
|
||||
|
||||
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
||||
return qs
|
||||
|
||||
|
||||
class Person(models.Model, RevisionMixin):
|
||||
name = models.CharField(max_length=50)
|
||||
phone = models.CharField(max_length=15, blank=True, default='')
|
||||
email = models.EmailField(blank=True, default='')
|
||||
|
||||
address = models.TextField(blank=True, default='')
|
||||
|
||||
notes = models.TextField(blank=True, default='')
|
||||
|
||||
objects = ContactableManager()
|
||||
|
||||
def __str__(self):
|
||||
string = self.name
|
||||
if self.notes is not None:
|
||||
@@ -110,12 +135,12 @@ class Organisation(models.Model, RevisionMixin):
|
||||
name = models.CharField(max_length=50)
|
||||
phone = models.CharField(max_length=15, blank=True, default='')
|
||||
email = models.EmailField(blank=True, default='')
|
||||
|
||||
address = models.TextField(blank=True, default='')
|
||||
|
||||
notes = models.TextField(blank=True, default='')
|
||||
union_account = models.BooleanField(default=False)
|
||||
|
||||
objects = ContactableManager()
|
||||
|
||||
def __str__(self):
|
||||
string = self.name
|
||||
if self.notes is not None:
|
||||
@@ -184,9 +209,10 @@ class Venue(models.Model, RevisionMixin):
|
||||
email = models.EmailField(blank=True, default='')
|
||||
three_phase_available = models.BooleanField(default=False)
|
||||
notes = models.TextField(blank=True, default='')
|
||||
|
||||
address = models.TextField(blank=True, default='')
|
||||
|
||||
objects = ContactableManager()
|
||||
|
||||
def __str__(self):
|
||||
string = self.name
|
||||
if self.notes and len(self.notes) > 0:
|
||||
@@ -260,6 +286,23 @@ class EventManager(models.Manager):
|
||||
|
||||
return events
|
||||
|
||||
def search(self, query=None):
|
||||
qs = self.get_queryset()
|
||||
if query is not None:
|
||||
or_lookup = Q(name__icontains=query) | Q(description__icontains=query) | Q(notes__icontains=query)
|
||||
|
||||
or_lookup = filter_by_pk(or_lookup, query)
|
||||
|
||||
try:
|
||||
if query[0] == "N":
|
||||
val = int(query[1:])
|
||||
or_lookup = Q(pk=val) # If string is N###### then do a simple PK filter
|
||||
except: # noqa
|
||||
pass
|
||||
|
||||
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
||||
return qs
|
||||
|
||||
|
||||
@reversion.register(follow=['items'])
|
||||
class Event(models.Model, RevisionMixin):
|
||||
@@ -314,10 +357,8 @@ class Event(models.Model, RevisionMixin):
|
||||
def display_id(self):
|
||||
if self.pk:
|
||||
if self.is_rig:
|
||||
return str("N%05d" % self.pk)
|
||||
|
||||
return f"N{self.pk:05d}"
|
||||
return self.pk
|
||||
|
||||
return "????"
|
||||
|
||||
# Calculated values
|
||||
@@ -530,6 +571,34 @@ class InvoiceManager(models.Manager):
|
||||
query = self.raw(sql)
|
||||
return query
|
||||
|
||||
def search(self, query=None):
|
||||
qs = self.get_queryset()
|
||||
if query is not None:
|
||||
or_lookup = Q(event__name__icontains=query)
|
||||
|
||||
or_lookup = filter_by_pk(or_lookup, query)
|
||||
|
||||
# try and parse an int
|
||||
try:
|
||||
val = int(query)
|
||||
or_lookup = or_lookup | Q(event__pk=val)
|
||||
except: # noqa
|
||||
# not an integer
|
||||
pass
|
||||
|
||||
try:
|
||||
if query[0] == "N":
|
||||
val = int(query[1:])
|
||||
or_lookup = Q(event__pk=val) # If string is Nxxxxx then filter by event number
|
||||
elif query[0] == "#":
|
||||
val = int(query[1:])
|
||||
or_lookup = Q(pk=val) # If string is #xxxxx then filter by invoice number
|
||||
except: # noqa
|
||||
pass
|
||||
|
||||
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
||||
return qs
|
||||
|
||||
|
||||
@reversion.register(follow=['payment_set'])
|
||||
class Invoice(models.Model, RevisionMixin):
|
||||
@@ -572,11 +641,11 @@ class Invoice(models.Model, RevisionMixin):
|
||||
return f"#{self.display_id} for Event {self.event.display_id}"
|
||||
|
||||
def __str__(self):
|
||||
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
|
||||
return f"{self.display_id}: {self.event} (£{self.balance:.2f})"
|
||||
|
||||
@property
|
||||
def display_id(self):
|
||||
return "{:05d}".format(self.pk)
|
||||
return f"#{self.pk:05d}"
|
||||
|
||||
class Meta:
|
||||
ordering = ['-invoice_date']
|
||||
@@ -731,7 +800,7 @@ class RiskAssessment(models.Model, RevisionMixin):
|
||||
return reverse('ra_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return "%i - %s" % (self.pk, self.event)
|
||||
return f"{self.pk} - {self.event}"
|
||||
|
||||
|
||||
@reversion.register(follow=['vehicles', 'crew'])
|
||||
@@ -813,7 +882,7 @@ class EventChecklist(models.Model, RevisionMixin):
|
||||
return reverse('ec_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return "%i - %s" % (self.pk, self.event)
|
||||
return f"{self.pk} - {self.event}"
|
||||
|
||||
|
||||
@reversion.register
|
||||
@@ -825,7 +894,7 @@ class EventChecklistVehicle(models.Model, RevisionMixin):
|
||||
reversion_hide = True
|
||||
|
||||
def __str__(self):
|
||||
return "{} driven by {}".format(self.vehicle, str(self.driver))
|
||||
return f"{self.vehicle} driven by {self.driver}"
|
||||
|
||||
|
||||
@reversion.register
|
||||
@@ -843,4 +912,4 @@ class EventChecklistCrew(models.Model, RevisionMixin):
|
||||
raise ValidationError('Unless you\'ve invented time travel, crew can\'t finish before they have started.')
|
||||
|
||||
def __str__(self):
|
||||
return "{} ({})".format(str(self.crewmember), self.role)
|
||||
return f"{self.crewmember} ({self.role})"
|
||||
|
||||
@@ -54,7 +54,7 @@ def send_eventauthorisation_success_email(instance):
|
||||
elif instance.event.organisation is not None and instance.email == instance.event.organisation.email:
|
||||
context['to_name'] = instance.event.organisation.name
|
||||
|
||||
subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name)
|
||||
subject = f"{instance.event.display_id} | {instance.event.name} - Event Authorised"
|
||||
|
||||
client_email = EmailMultiAlternatives(
|
||||
subject,
|
||||
@@ -70,7 +70,7 @@ def send_eventauthorisation_success_email(instance):
|
||||
|
||||
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', instance.event.name)
|
||||
|
||||
client_email.attach('N%05d - %s - CONFIRMATION.pdf' % (instance.event.pk, escapedEventName),
|
||||
client_email.attach(f'{instance.event.display_id} - {escapedEventName} - CONFIRMATION.pdf',
|
||||
merged.getvalue(),
|
||||
'application/pdf'
|
||||
)
|
||||
@@ -116,7 +116,7 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
|
||||
}
|
||||
|
||||
email = EmailMultiAlternatives(
|
||||
"%s new users awaiting approval on RIGS" % (context['number_of_users']),
|
||||
f"{context['number_of_users']} new users awaiting approval on RIGS",
|
||||
get_template("admin_awaiting_approval.txt").render(context),
|
||||
to=[admin.email],
|
||||
reply_to=[user.email],
|
||||
|
||||
@@ -28,7 +28,8 @@ class InvoiceIndex(generic.ListView):
|
||||
total = 0
|
||||
for i in context['object_list']:
|
||||
total += i.balance
|
||||
context['page_title'] = "Outstanding Invoices ({} Events, £{:.2f})".format(len(list(context['object_list'])), total)
|
||||
event_count = len(list(context['object_list']))
|
||||
context['page_title'] = f"Outstanding Invoices ({event_count} Events, £{total:.2f})"
|
||||
context['description'] = "Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger"
|
||||
return context
|
||||
|
||||
@@ -43,7 +44,7 @@ class InvoiceDetail(generic.DetailView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
invoice_date = self.object.invoice_date.strftime("%d/%m/%Y")
|
||||
context['page_title'] = f"Invoice {self.object.display_id} ({invoice_date}) "
|
||||
context['page_title'] = f"Invoice {self.object.display_id} ({invoice_date})"
|
||||
if self.object.void:
|
||||
context['page_title'] += "<span class='badge badge-warning float-right'>VOID</span>"
|
||||
elif self.object.is_closed:
|
||||
@@ -59,11 +60,14 @@ class InvoicePrint(generic.View):
|
||||
object = invoice.event
|
||||
template = get_template('event_print.xml')
|
||||
|
||||
name = re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name)
|
||||
filename = f"Invoice {invoice.display_id} for {object.display_id} {name}.pdf"
|
||||
|
||||
context = {
|
||||
'object': object,
|
||||
'invoice': invoice,
|
||||
'current_user': request.user,
|
||||
'filename': 'Invoice {} for {} {}.pdf'.format(invoice.display_id, object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name))
|
||||
'filename': filename
|
||||
}
|
||||
|
||||
rml = template.render(context)
|
||||
@@ -73,7 +77,7 @@ class InvoicePrint(generic.View):
|
||||
pdfData = buffer.read()
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
|
||||
response['Content-Disposition'] = f'filename="{filename}"'
|
||||
response.write(pdfData)
|
||||
return response
|
||||
|
||||
@@ -124,32 +128,7 @@ class InvoiceArchive(generic.ListView):
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
q = self.request.GET.get('q', "")
|
||||
|
||||
filter = Q(event__name__icontains=q)
|
||||
|
||||
# try and parse an int
|
||||
try:
|
||||
val = int(q)
|
||||
filter = filter | Q(pk=val)
|
||||
filter = filter | Q(event__pk=val)
|
||||
except: # noqa
|
||||
# not an integer
|
||||
pass
|
||||
|
||||
try:
|
||||
if q[0] == "N":
|
||||
val = int(q[1:])
|
||||
filter = Q(event__pk=val) # If string is Nxxxxx then filter by event number
|
||||
elif q[0] == "#":
|
||||
val = int(q[1:])
|
||||
filter = Q(pk=val) # If string is #xxxxx then filter by invoice number
|
||||
except: # noqa
|
||||
pass
|
||||
|
||||
object_list = self.model.objects.filter(filter).order_by('-invoice_date')
|
||||
|
||||
return object_list
|
||||
return self.model.objects.search(self.request.GET.get('q')).order_by('-invoice_date')
|
||||
|
||||
|
||||
class InvoiceWaiting(generic.ListView):
|
||||
@@ -163,7 +142,7 @@ class InvoiceWaiting(generic.ListView):
|
||||
objects = self.get_queryset()
|
||||
for obj in objects:
|
||||
total += obj.sum_total
|
||||
context['page_title'] = "Events for Invoice ({} Events, £{:.2f})".format(len(objects), total)
|
||||
context['page_title'] = f"Events for Invoice ({len(objects)} Events, £{total:.2f})"
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
@@ -37,7 +37,7 @@ class EventRiskAssessmentCreate(generic.CreateView):
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
context['event'] = event
|
||||
context['page_title'] = 'Create Risk Assessment for Event {}'.format(event.display_id)
|
||||
context['page_title'] = f'Create Risk Assessment for Event {event.display_id}'
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
@@ -62,7 +62,7 @@ class EventRiskAssessmentEdit(generic.UpdateView):
|
||||
ra = models.RiskAssessment.objects.get(pk=rpk)
|
||||
context['event'] = ra.event
|
||||
context['edit'] = True
|
||||
context['page_title'] = 'Edit Risk Assessment for Event {}'.format(ra.event.display_id)
|
||||
context['page_title'] = f'Edit Risk Assessment for Event {ra.event.display_id}'
|
||||
return context
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class EventRiskAssessmentDetail(generic.DetailView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventRiskAssessmentDetail, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Risk Assessment for Event <a href='{}'>{} {}</a>".format(self.object.event.get_absolute_url(), self.object.event.display_id, self.object.event.name)
|
||||
context['page_title'] = f"Risk Assessment for Event <a href='{self.object.event.get_absolute_url()}'>{self.object.event.display_id} {self.object.event.name}</a>"
|
||||
return context
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ class EventChecklistDetail(generic.DetailView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EventChecklistDetail, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Event Checklist for Event <a href='{}'>{} {}</a>".format(self.object.event.get_absolute_url(), self.object.event.display_id, self.object.event.name)
|
||||
context['page_title'] = f"Event Checklist for Event <a href='{self.object.event.get_absolute_url()}'>{self.object.event.display_id} {self.object.event.name}</a>"
|
||||
return context
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ class EventChecklistEdit(generic.UpdateView):
|
||||
ec = models.EventChecklist.objects.get(pk=pk)
|
||||
context['event'] = ec.event
|
||||
context['edit'] = True
|
||||
context['page_title'] = 'Edit Event Checklist for Event {}'.format(ec.event.display_id)
|
||||
context['page_title'] = f'Edit Event Checklist for Event {ec.event.display_id}'
|
||||
form = context['form']
|
||||
# Get some other objects to include in the form. Used when there are errors but also nice and quick.
|
||||
for field, model in form.related_models.items():
|
||||
@@ -158,7 +158,7 @@ class EventChecklistCreate(generic.CreateView):
|
||||
ra = models.RiskAssessment.objects.filter(event=event).first()
|
||||
|
||||
if ra is None:
|
||||
messages.error(self.request, 'A Risk Assessment must exist prior to creating any Event Checklists for {}! Please create one now.'.format(event))
|
||||
messages.error(self.request, f'A Risk Assessment must exist prior to creating any Event Checklists for {event}! Please create one now.')
|
||||
return HttpResponseRedirect(reverse_lazy('event_ra', kwargs={'pk': epk}))
|
||||
|
||||
return super(EventChecklistCreate, self).get(self)
|
||||
@@ -175,7 +175,7 @@ class EventChecklistCreate(generic.CreateView):
|
||||
epk = self.kwargs.get('pk')
|
||||
event = models.Event.objects.get(pk=epk)
|
||||
context['event'] = event
|
||||
context['page_title'] = 'Create Event Checklist for Event {}'.format(event.display_id)
|
||||
context['page_title'] = f'Create Event Checklist for Event {event.display_id}'
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
|
||||
@@ -188,11 +188,14 @@ class EventPrint(generic.View):
|
||||
user_str = f"by {request.user.name} " if request.user is not None else ""
|
||||
time = timezone.now().strftime('%d/%m/%Y %H:%I')
|
||||
|
||||
name = re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name)
|
||||
filename = f"Event_{object.display_id}_{name}_{object.start_date}.pdf"
|
||||
|
||||
context = {
|
||||
'object': object,
|
||||
'quote': True,
|
||||
'current_user': request.user,
|
||||
'filename': 'Event_{}_{}_{}.pdf'.format(object.display_id, re.sub(r'[^a-zA-Z0-9 \n\.]', '', object.name), object.start_date),
|
||||
'filename': filename,
|
||||
'info_string': f"[Paperwork generated {user_str}on {time} - {object.current_version_id}]",
|
||||
}
|
||||
|
||||
@@ -208,7 +211,7 @@ class EventPrint(generic.View):
|
||||
merger.write(merged)
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
response['Content-Disposition'] = 'filename="{}"'.format(context['filename'])
|
||||
response['Content-Disposition'] = f'filename="{filename}"'
|
||||
response.write(merged.getvalue())
|
||||
return response
|
||||
|
||||
@@ -244,32 +247,17 @@ class EventArchive(generic.ListView):
|
||||
filter &= Q(start_date__gte=start)
|
||||
|
||||
q = self.request.GET.get('q', "")
|
||||
objects = self.model.objects.all()
|
||||
|
||||
if q != "":
|
||||
qfilter = Q(name__icontains=q) | Q(description__icontains=q) | Q(notes__icontains=q)
|
||||
|
||||
# try and parse an int
|
||||
try:
|
||||
val = int(q)
|
||||
qfilter = qfilter | Q(pk=val)
|
||||
except: # noqa not an integer
|
||||
pass
|
||||
|
||||
try:
|
||||
if q[0] == "N":
|
||||
val = int(q[1:])
|
||||
qfilter = Q(pk=val) # If string is N###### then do a simple PK filter
|
||||
except: # noqa
|
||||
pass
|
||||
|
||||
filter &= qfilter
|
||||
if q:
|
||||
objects = self.model.objects.search(q)
|
||||
|
||||
status = self.request.GET.getlist('status', "")
|
||||
|
||||
if len(status) > 0:
|
||||
filter &= Q(status__in=status)
|
||||
|
||||
qs = self.model.objects.filter(filter).order_by('-start_date')
|
||||
qs = objects.filter(filter).order_by('-start_date')
|
||||
|
||||
# Preselect related for efficiency
|
||||
qs.select_related('person', 'organisation', 'venue', 'mic')
|
||||
@@ -393,7 +381,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
||||
context['to_name'] = event.organisation.name
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
"N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name),
|
||||
f"{self.object.display_id} | {self.object.name} - Event Authorisation Request",
|
||||
get_template("eventauthorisation_client_request.txt").render(context),
|
||||
to=[email],
|
||||
reply_to=[self.request.user.email],
|
||||
|
||||
Reference in New Issue
Block a user