")
if len(unexpected_values) > 0 and not self.cleaned_data.get('supervisor_consulted'):
- raise forms.ValidationError("Your answers to these questions:
{}
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:
{''.join([str(elem) for elem in unexpected_values])}
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)
diff --git a/RIGS/models.py b/RIGS/models.py
index 378bac0a..4d307bf2 100644
--- a/RIGS/models.py
+++ b/RIGS/models.py
@@ -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})"
diff --git a/RIGS/signals.py b/RIGS/signals.py
index e1fc37b0..1a9aee51 100644
--- a/RIGS/signals.py
+++ b/RIGS/signals.py
@@ -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],
diff --git a/RIGS/views/finance.py b/RIGS/views/finance.py
index cab74a56..405de166 100644
--- a/RIGS/views/finance.py
+++ b/RIGS/views/finance.py
@@ -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'] += "VOID"
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):
diff --git a/RIGS/views/hs.py b/RIGS/views/hs.py
index 33fa5d80..515ccf73 100644
--- a/RIGS/views/hs.py
+++ b/RIGS/views/hs.py
@@ -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 {} {}".format(self.object.event.get_absolute_url(), self.object.event.display_id, self.object.event.name)
+ context['page_title'] = f"Risk Assessment for Event {self.object.event.display_id} {self.object.event.name}"
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 {} {}".format(self.object.event.get_absolute_url(), self.object.event.display_id, self.object.event.name)
+ context['page_title'] = f"Event Checklist for Event {self.object.event.display_id} {self.object.event.name}"
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):
diff --git a/RIGS/views/rigboard.py b/RIGS/views/rigboard.py
index 1adbfd57..d3ed5a2e 100644
--- a/RIGS/views/rigboard.py
+++ b/RIGS/views/rigboard.py
@@ -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],
diff --git a/assets/models.py b/assets/models.py
index b235b810..b1bc11d1 100644
--- a/assets/models.py
+++ b/assets/models.py
@@ -2,11 +2,12 @@ import re
from django.core.exceptions import ValidationError
from django.db import models, connection
+from django.db.models import Q
from django.urls import reverse
from reversion import revisions as reversion
from reversion.models import Version
-from RIGS.models import Profile
+from RIGS.models import Profile, ContactableManager
from versioning.versioning import RevisionMixin
@@ -46,6 +47,8 @@ class Supplier(models.Model, RevisionMixin):
notes = models.TextField(blank=True, default="")
+ objects = ContactableManager()
+
class Meta:
ordering = ['name']
@@ -107,6 +110,15 @@ def get_available_asset_id(wanted_prefix=""):
cursor.close()
+class AssetManager(models.Manager):
+ def search(self, query=None):
+ qs = self.get_queryset()
+ if query is not None:
+ or_lookup = (Q(asset_id__exact=query.upper()) | Q(description__icontains=query) | Q(serial_number__exact=query))
+ qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
+ return qs
+
+
@reversion.register
class Asset(models.Model, RevisionMixin):
parent = models.ForeignKey(to='self', related_name='asset_parent',
@@ -142,6 +154,8 @@ class Asset(models.Model, RevisionMixin):
reversion_perm = 'assets.asset_finance'
+ objects = AssetManager()
+
class Meta:
ordering = ['asset_id_prefix', 'asset_id_number']
permissions = [
diff --git a/assets/templates/asset_list.html b/assets/templates/asset_list.html
index 5ddae31c..d3ddb2a5 100644
--- a/assets/templates/asset_list.html
+++ b/assets/templates/asset_list.html
@@ -75,13 +75,13 @@