diff --git a/PyRIGS/tests/base.py b/PyRIGS/tests/base.py index 79005757..0d056978 100644 --- a/PyRIGS/tests/base.py +++ b/PyRIGS/tests/base.py @@ -22,21 +22,12 @@ def create_datetime(year, month, day, hour, minute): def create_browser(): - browser = env('BROWSER', default="chrome") - if browser == "firefox": - options = webdriver.FirefoxOptions() - options.headless = True - driver = webdriver.Firefox(options=options) - driver.set_window_position(0, 0) - # Firefox is pissy about out of bounds otherwise - driver.set_window_size(3840, 2160) - else: - options = webdriver.ChromeOptions() - options.add_argument("--window-size=1920,1080") - options.add_argument("--headless") - if settings.CI: - options.add_argument("--no-sandbox") - driver = webdriver.Chrome(options=options) + options = webdriver.ChromeOptions() + options.add_argument("--window-size=1920,1080") + options.add_argument("--headless") + if settings.CI: + options.add_argument("--no-sandbox") + driver = webdriver.Chrome(options=options) return driver diff --git a/RIGS/finance.py b/RIGS/finance.py index f88881fd..9486b8ee 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -10,7 +10,7 @@ from django.http import Http404, HttpResponseRedirect from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.template.loader import get_template -from django.urls import reverse_lazy +from django.urls import reverse from django.views import generic from z3c.rml import rml2pdf @@ -92,8 +92,8 @@ class InvoiceVoid(generic.View): object.save() if object.void: - return HttpResponseRedirect(reverse_lazy('invoice_list')) - return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk})) + return HttpResponseRedirect(reverse('invoice_list')) + return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': object.pk})) class InvoiceDelete(generic.DeleteView): @@ -104,14 +104,14 @@ class InvoiceDelete(generic.DeleteView): obj = self.get_object() if obj.payment_set.all().count() > 0: messages.info(self.request, 'To delete an invoice, delete the payments first.') - return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk})) + return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': obj.pk})) return super(InvoiceDelete, self).get(pk) def post(self, request, pk): obj = self.get_object() if obj.payment_set.all().count() > 0: messages.info(self.request, 'To delete an invoice, delete the payments first.') - return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk})) + return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': obj.pk})) return super(InvoiceDelete, self).post(pk) def get_success_url(self): @@ -210,7 +210,7 @@ class InvoiceEvent(generic.View): invoice.save() messages.warning(self.request, 'Invoice voided') - return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': invoice.pk})) + return HttpResponseRedirect(reverse('invoice_detail', kwargs={'pk': invoice.pk})) class PaymentCreate(generic.CreateView): @@ -236,7 +236,7 @@ class PaymentCreate(generic.CreateView): def get_success_url(self): messages.info(self.request, "location.reload()") - return reverse_lazy('closemodal') + return reverse('closemodal') class PaymentDelete(generic.DeleteView): diff --git a/RIGS/migrations/0037_approve_legacy.py b/RIGS/migrations/0037_approve_legacy.py index 72348b3f..d38c1b66 100644 --- a/RIGS/migrations/0037_approve_legacy.py +++ b/RIGS/migrations/0037_approve_legacy.py @@ -1,5 +1,5 @@ # Generated by Django 2.0.13 on 2020-01-11 18:29 -# This migration ensures that legacy Profiles from before approvals were implemented are automatically approved +# This migration ensures that legacy Profiles from before approvals were implemented are automatically approved from django.db import migrations def approve_legacy(apps, schema_editor): @@ -15,5 +15,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(approve_legacy) + migrations.RunPython(approve_legacy, migrations.RunPython.noop) ] diff --git a/RIGS/migrations/0041_auto_20210208_1603.py b/RIGS/migrations/0041_auto_20210208_1603.py new file mode 100644 index 00000000..94795ad2 --- /dev/null +++ b/RIGS/migrations/0041_auto_20210208_1603.py @@ -0,0 +1,199 @@ +# Generated by Django 3.1.5 on 2021-02-08 16:03 + +import RIGS.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0040_profile_dark_theme'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='auth_request_to', + field=models.EmailField(blank=True, default='', max_length=254), + ), + migrations.AlterField( + model_name='event', + name='collector', + field=models.CharField(blank=True, default='', max_length=255, verbose_name='collected by'), + ), + migrations.AlterField( + model_name='event', + name='description', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='event', + name='meet_info', + field=models.CharField(blank=True, default='', max_length=255), + ), + migrations.AlterField( + model_name='event', + name='notes', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='event', + name='payment_method', + field=models.CharField(blank=True, default='', max_length=255), + ), + migrations.AlterField( + model_name='event', + name='payment_received', + field=models.CharField(blank=True, default='', max_length=255), + ), + migrations.AlterField( + model_name='event', + name='purchase_order', + field=models.CharField(blank=True, default='', max_length=255, verbose_name='PO'), + ), + migrations.AlterField( + model_name='eventauthorisation', + name='account_code', + field=models.CharField(blank=True, default='', max_length=50), + ), + migrations.AlterField( + model_name='eventauthorisation', + name='uni_id', + field=models.CharField(blank=True, default='', max_length=10, verbose_name='University ID'), + ), + migrations.AlterField( + model_name='eventchecklist', + name='extinguishers_location', + field=models.CharField(blank=True, default='', help_text='Location of fire extinguishers', max_length=255), + ), + migrations.AlterField( + model_name='eventchecklist', + name='hs_location', + field=models.CharField(blank=True, default='', help_text='Location of Safety Bag/Box', max_length=255), + ), + migrations.AlterField( + model_name='eventchecklist', + name='w1_description', + field=models.CharField(blank=True, default='', help_text='Description', max_length=255), + ), + migrations.AlterField( + model_name='eventchecklist', + name='w2_description', + field=models.CharField(blank=True, default='', help_text='Description', max_length=255), + ), + migrations.AlterField( + model_name='eventchecklist', + name='w3_description', + field=models.CharField(blank=True, default='', help_text='Description', max_length=255), + ), + migrations.AlterField( + model_name='eventitem', + name='description', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='organisation', + name='address', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='organisation', + name='email', + field=models.EmailField(blank=True, default='', max_length=254), + ), + migrations.AlterField( + model_name='organisation', + name='notes', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='organisation', + name='phone', + field=models.CharField(blank=True, default='', max_length=15), + ), + migrations.AlterField( + model_name='payment', + name='method', + field=models.CharField(blank=True, choices=[('C', 'Cash'), ('I', 'Internal'), ('E', 'External'), ('SU', 'SU Core'), ('T', 'TEC Adjustment')], default='', max_length=2), + ), + migrations.AlterField( + model_name='person', + name='address', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='person', + name='email', + field=models.EmailField(blank=True, default='', max_length=254), + ), + migrations.AlterField( + model_name='person', + name='notes', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='person', + name='phone', + field=models.CharField(blank=True, default='', max_length=15), + ), + migrations.AlterField( + model_name='profile', + name='api_key', + field=models.CharField(blank=True, default='', editable=False, max_length=40), + ), + migrations.AlterField( + model_name='profile', + name='phone', + field=models.CharField(default='', max_length=13, null=True), + ), + migrations.AlterField( + model_name='riskassessment', + name='general_notes', + field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'), + ), + migrations.AlterField( + model_name='riskassessment', + name='persons_responsible_structures', + field=models.TextField(blank=True, default='', help_text='Who are the persons on site responsible for their use?'), + ), + migrations.AlterField( + model_name='riskassessment', + name='power_notes', + field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'), + ), + migrations.AlterField( + model_name='riskassessment', + name='power_plan', + field=models.URLField(blank=True, default='', help_text="Upload your power plan to the Sharepoint and submit a link", validators=[RIGS.models.validate_url]), + ), + migrations.AlterField( + model_name='riskassessment', + name='rigging_plan', + field=models.URLField(blank=True, default='', help_text="Upload your rigging plan to the Sharepoint and submit a link", validators=[RIGS.models.validate_url]), + ), + migrations.AlterField( + model_name='riskassessment', + name='sound_notes', + field=models.TextField(blank=True, default='', help_text='Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?'), + ), + migrations.AlterField( + model_name='venue', + name='address', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='venue', + name='email', + field=models.EmailField(blank=True, default='', max_length=254), + ), + migrations.AlterField( + model_name='venue', + name='notes', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='venue', + name='phone', + field=models.CharField(blank=True, default='', max_length=15), + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index 03a35c46..e7050947 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -12,7 +12,7 @@ from django.conf import settings from django.contrib.auth.models import AbstractUser from django.core.exceptions import ValidationError from django.db import models -from django.urls import reverse_lazy +from django.urls import reverse from django.utils import timezone from django.utils.functional import cached_property from reversion import revisions as reversion @@ -21,11 +21,11 @@ from reversion.models import Version class Profile(AbstractUser): initials = models.CharField(max_length=5, unique=True, null=True, blank=False) - phone = models.CharField(max_length=13, null=True, blank=True) - api_key = models.CharField(max_length=40, blank=True, editable=False, null=True) + phone = models.CharField(max_length=13, null=True, default='') + api_key = models.CharField(max_length=40, blank=True, editable=False, default='') is_approved = models.BooleanField(default=False) - last_emailed = models.DateTimeField(blank=True, - null=True) # Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that... + # Currently only populated by the admin approval email. TODO: Populate it each time we send any email, might need that... + last_emailed = models.DateTimeField(blank=True, null=True) dark_theme = models.BooleanField(default=False) @classmethod @@ -103,12 +103,12 @@ class RevisionMixin(object): class Person(models.Model, RevisionMixin): name = models.CharField(max_length=50) - phone = models.CharField(max_length=15, blank=True, null=True) - email = models.EmailField(blank=True, null=True) + phone = models.CharField(max_length=15, blank=True, default='') + email = models.EmailField(blank=True, default='') - address = models.TextField(blank=True, null=True) + address = models.TextField(blank=True, default='') - notes = models.TextField(blank=True, null=True) + notes = models.TextField(blank=True, default='') def __str__(self): string = self.name @@ -134,17 +134,17 @@ class Person(models.Model, RevisionMixin): return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic') def get_absolute_url(self): - return reverse_lazy('person_detail', kwargs={'pk': self.pk}) + return reverse('person_detail', kwargs={'pk': self.pk}) class Organisation(models.Model, RevisionMixin): name = models.CharField(max_length=50) - phone = models.CharField(max_length=15, blank=True, null=True) - email = models.EmailField(blank=True, null=True) + phone = models.CharField(max_length=15, blank=True, default='') + email = models.EmailField(blank=True, default='') - address = models.TextField(blank=True, null=True) + address = models.TextField(blank=True, default='') - notes = models.TextField(blank=True, null=True) + notes = models.TextField(blank=True, default='') union_account = models.BooleanField(default=False) def __str__(self): @@ -171,7 +171,7 @@ class Organisation(models.Model, RevisionMixin): return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic') def get_absolute_url(self): - return reverse_lazy('organisation_detail', kwargs={'pk': self.pk}) + return reverse('organisation_detail', kwargs={'pk': self.pk}) class VatManager(models.Manager): @@ -211,12 +211,12 @@ class VatRate(models.Model, RevisionMixin): class Venue(models.Model, RevisionMixin): name = models.CharField(max_length=255) - phone = models.CharField(max_length=15, blank=True, null=True) - email = models.EmailField(blank=True, null=True) + phone = models.CharField(max_length=15, blank=True, default='') + email = models.EmailField(blank=True, default='') three_phase_available = models.BooleanField(default=False) - notes = models.TextField(blank=True, null=True) + notes = models.TextField(blank=True, default='') - address = models.TextField(blank=True, null=True) + address = models.TextField(blank=True, default='') def __str__(self): string = self.name @@ -229,7 +229,7 @@ class Venue(models.Model, RevisionMixin): return self.event_set.order_by('-start_date').select_related('person', 'organisation', 'venue', 'mic') def get_absolute_url(self): - return reverse_lazy('venue_detail', kwargs={'pk': self.pk}) + return reverse('venue_detail', kwargs={'pk': self.pk}) class EventManager(models.Manager): @@ -298,8 +298,8 @@ class Event(models.Model, RevisionMixin): person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE) organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE) venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE) - description = models.TextField(blank=True, null=True) - notes = models.TextField(blank=True, null=True) + description = models.TextField(blank=True, default='') + notes = models.TextField(blank=True, default='') status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL) dry_hire = models.BooleanField(default=False) is_rig = models.BooleanField(default=True) @@ -313,7 +313,7 @@ class Event(models.Model, RevisionMixin): end_time = models.TimeField(blank=True, null=True) access_at = models.DateTimeField(blank=True, null=True) meet_at = models.DateTimeField(blank=True, null=True) - meet_info = models.CharField(max_length=255, blank=True, null=True) + meet_info = models.CharField(max_length=255, blank=True, default='') # Crew management checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True, @@ -322,15 +322,15 @@ class Event(models.Model, RevisionMixin): verbose_name="MIC", on_delete=models.CASCADE) # Monies - payment_method = models.CharField(max_length=255, blank=True, null=True) - payment_received = models.CharField(max_length=255, blank=True, null=True) - purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO') - collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by') + payment_method = models.CharField(max_length=255, blank=True, default='') + payment_received = models.CharField(max_length=255, blank=True, default='') + purchase_order = models.CharField(max_length=255, blank=True, default='', verbose_name='PO') + collector = models.CharField(max_length=255, blank=True, default='', verbose_name='collected by') # Authorisation request details auth_request_by = models.ForeignKey('Profile', null=True, blank=True, on_delete=models.CASCADE) auth_request_at = models.DateTimeField(null=True, blank=True) - auth_request_to = models.EmailField(null=True, blank=True) + auth_request_to = models.EmailField(blank=True, default='') @property def display_id(self): @@ -456,7 +456,7 @@ class Event(models.Model, RevisionMixin): objects = EventManager() def get_absolute_url(self): - return reverse_lazy('event_detail', kwargs={'pk': self.pk}) + return reverse('event_detail', kwargs={'pk': self.pk}) def __str__(self): return "{}: {}".format(self.display_id, self.name) @@ -490,7 +490,7 @@ class Event(models.Model, RevisionMixin): class EventItem(models.Model, RevisionMixin): event = models.ForeignKey('Event', related_name='items', blank=True, on_delete=models.CASCADE) name = models.CharField(max_length=255) - description = models.TextField(blank=True, null=True) + description = models.TextField(blank=True, default='') quantity = models.IntegerField() cost = models.DecimalField(max_digits=10, decimal_places=2) order = models.IntegerField() @@ -505,7 +505,7 @@ class EventItem(models.Model, RevisionMixin): ordering = ['order'] def __str__(self): - return str(self.event.pk) + "." + str(self.order) + ": " + self.event.name + " | " + self.name + return "{}.{}: {} | {}".format(self.event_id, self.order, self.event.name, self.name) @property def activity_feed_string(self): @@ -517,13 +517,13 @@ class EventAuthorisation(models.Model, RevisionMixin): event = models.OneToOneField('Event', related_name='authorisation', on_delete=models.CASCADE) email = models.EmailField() name = models.CharField(max_length=255) - uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID") - account_code = models.CharField(max_length=50, blank=True, null=True) + uni_id = models.CharField(max_length=10, blank=True, default='', verbose_name="University ID") + account_code = models.CharField(max_length=50, default='', blank=True) amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount") sent_by = models.ForeignKey('Profile', on_delete=models.CASCADE) def get_absolute_url(self): - return reverse_lazy('event_detail', kwargs={'pk': self.event.pk}) + return reverse('event_detail', kwargs={'pk': self.event_id}) @property def activity_feed_string(self): @@ -562,11 +562,11 @@ class Invoice(models.Model, RevisionMixin): return self.balance == 0 or self.void def get_absolute_url(self): - return reverse_lazy('invoice_detail', kwargs={'pk': self.pk}) + return reverse('invoice_detail', kwargs={'pk': self.pk}) @property def activity_feed_string(self): - return "#{} for Event {}".format(self.display_id, "N%05d" % self.event.pk) + return "#{} for Event {}".format(self.display_id, self.event.display_id) def __str__(self): return "%i: %s (%.2f)" % (self.pk, self.event, self.balance) @@ -597,7 +597,7 @@ class Payment(models.Model, RevisionMixin): invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE) date = models.DateField() amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT') - method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True) + method = models.CharField(max_length=2, choices=METHODS, default='', blank=True) reversion_hide = True @@ -632,10 +632,9 @@ class RiskAssessment(models.Model, RevisionMixin): contractors = models.BooleanField(help_text="Are you using any external contractors?
i.e. Freelancers/Crewing Companies") other_companies = models.BooleanField(help_text="Are TEC working with any other companies on site?
e.g. TEC is providing the lighting while another company does sound") crew_fatigue = models.BooleanField(help_text="Is crew fatigue likely to be a risk at any point during this event?") - general_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?") + general_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?") # Power - # event_size = models.IntegerField(blank=True, null=True, choices=SIZES) big_power = models.BooleanField(help_text="Does the event require larger power supplies than 13A or 16A single phase wall sockets, or draw more than 20A total current?") # If yes to the above two, you must answer... power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='power_mic', blank=True, null=True, @@ -645,12 +644,12 @@ class RiskAssessment(models.Model, RevisionMixin): other_companies_power = models.BooleanField(help_text="Will TEC be supplying power to any other companies?") nonstandard_equipment_power = models.BooleanField(help_text="Does the power plan require the use of any power equipment (distros, dimmers, motor controllers, etc.) that does not belong to TEC?") multiple_electrical_environments = models.BooleanField(help_text="Will the electrical installation occupy more than one electrical environment?") - power_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?") - power_plan = models.URLField(blank=True, null=True, help_text="Upload your power plan to the Sharepoint and submit a link", validators=[validate_url]) + power_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?") + power_plan = models.URLField(blank=True, default='', help_text="Upload your power plan to the Sharepoint and submit a link", validators=[validate_url]) # Sound noise_monitoring = models.BooleanField(help_text="Does the event require noise monitoring or any non-standard procedures in order to comply with health and safety legislation or site rules?") - sound_notes = models.TextField(blank=True, null=True, help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?") + sound_notes = models.TextField(blank=True, default='', help_text="Did you have to consult a supervisor about any of the above? If so who did you consult and what was the outcome?") # Site known_venue = models.BooleanField(help_text="Is this venue new to you (the MIC) or new to TEC?") @@ -663,8 +662,8 @@ class RiskAssessment(models.Model, RevisionMixin): # Structures special_structures = models.BooleanField(help_text="Does the event require use of winch stands, motors, MPT Towers, or staging?") suspended_structures = models.BooleanField(help_text="Are any structures (excluding projector screens and IWBs) being suspended from TEC's structures?") - persons_responsible_structures = models.TextField(blank=True, null=True, help_text="Who are the persons on site responsible for their use?") - rigging_plan = models.URLField(blank=True, null=True, help_text="Upload your rigging plan to the Sharepoint and submit a link", validators=[validate_url]) + persons_responsible_structures = models.TextField(blank=True, default='', help_text="Who are the persons on site responsible for their use?") + rigging_plan = models.URLField(blank=True, default='', help_text="Upload your rigging plan to the Sharepoint and submit a link", validators=[validate_url]) # Blimey that was a lot of options @@ -723,7 +722,7 @@ class RiskAssessment(models.Model, RevisionMixin): return str(self.event) def get_absolute_url(self): - return reverse_lazy('ra_detail', kwargs={'pk': self.pk}) + return reverse('ra_detail', kwargs={'pk': self.pk}) def __str__(self): return "%i - %s" % (self.pk, self.event) @@ -746,8 +745,8 @@ class EventChecklist(models.Model, RevisionMixin): trip_hazard = models.BooleanField(blank=True, null=True, help_text="Appropriate barriers around kit and cabling secured?") warning_signs = models.BooleanField(blank=True, help_text="Warning signs in place?
(strobe, smoke, power etc.)") ear_plugs = models.BooleanField(blank=True, null=True, help_text="Ear plugs issued to crew where needed?") - hs_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of Safety Bag/Box") - extinguishers_location = models.CharField(blank=True, null=True, max_length=255, help_text="Location of fire extinguishers") + hs_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of Safety Bag/Box") + extinguishers_location = models.CharField(blank=True, default='', max_length=255, help_text="Location of fire extinguishers") # Small Electrical Checks rcds = models.BooleanField(blank=True, null=True, help_text="RCDs installed where needed and tested?") @@ -768,15 +767,15 @@ class EventChecklist(models.Model, RevisionMixin): fd_earth_fault = models.IntegerField(blank=True, null=True, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (ZS)") fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text="Prospective Short Circuit Current") # Worst case points - w1_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description") + w1_description = models.CharField(blank=True, default='', max_length=255, help_text="Description") w1_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?") w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage") w1_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (ZS)") - w2_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description") + w2_description = models.CharField(blank=True, default='', max_length=255, help_text="Description") w2_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?") w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage") w2_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (ZS)") - w3_description = models.CharField(blank=True, null=True, max_length=255, help_text="Description") + w3_description = models.CharField(blank=True, default='', max_length=255, help_text="Description") w3_polarity = models.BooleanField(blank=True, null=True, help_text="Polarity Checked?") w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage") w3_earth_fault = models.IntegerField(blank=True, null=True, help_text="Earth Fault Loop Impedance (ZS)") @@ -801,7 +800,7 @@ class EventChecklist(models.Model, RevisionMixin): return str(self.event) def get_absolute_url(self): - return reverse_lazy('ec_detail', kwargs={'pk': self.pk}) + return reverse('ec_detail', kwargs={'pk': self.pk}) def __str__(self): return "%i - %s" % (self.pk, self.event) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 6d07e4f6..7e6cc388 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -153,7 +153,7 @@ class EventDuplicate(EventUpdate): new.checked_in_by = None # Remove all the authorisation information from the new event - new.auth_request_to = None + new.auth_request_to = '' new.auth_request_by = None new.auth_request_at = None diff --git a/RIGS/templatetags/filters.py b/RIGS/templatetags/filters.py index 5e2eca0a..097e4678 100644 --- a/RIGS/templatetags/filters.py +++ b/RIGS/templatetags/filters.py @@ -4,7 +4,7 @@ from django.forms.forms import NON_FIELD_ERRORS from django.forms.utils import ErrorDict from django.template.defaultfilters import stringfilter from django.template.defaultfilters import yesno, title, truncatewords -from django.urls import reverse_lazy +from django.urls import reverse from django.utils.html import escape from django.utils.safestring import SafeData, mark_safe from django.utils.text import normalize_newlines @@ -173,7 +173,7 @@ def title_spaced(string): @register.filter(needs_autoescape=True) def namewithnotes(obj, url, autoescape=True): if hasattr(obj, 'notes') and obj.notes is not None and len(obj.notes) > 0: - return mark_safe(obj.name + " ".format(reverse_lazy(url, kwargs={'pk': obj.pk}))) + return mark_safe(obj.name + " ".format(reverse(url, kwargs={'pk': obj.pk}))) else: return obj.name diff --git a/RIGS/tests/test_interaction.py b/RIGS/tests/test_interaction.py index d03d86f4..f67927c9 100644 --- a/RIGS/tests/test_interaction.py +++ b/RIGS/tests/test_interaction.py @@ -311,7 +311,7 @@ class TestEventDuplicate(BaseRigboardTest): # TODO Rewrite when EventDetail page is implemented newEvent = models.Event.objects.latest('pk') - self.assertEqual(newEvent.auth_request_to, None) + assert newEvent.auth_request_to == '' self.assertEqual(newEvent.auth_request_by, None) self.assertEqual(newEvent.auth_request_at, None) @@ -449,7 +449,7 @@ class TestEventDetail(BaseRigboardTest): self.assertIn("N%05d | %s" % (self.testEvent.pk, self.testEvent.name), self.page.event_name) self.assertEqual(self.client.name, self.page.name) self.assertEqual(self.client.email, self.page.email) - self.assertEqual(self.client.phone, None) + assert self.client.phone == '' @screenshot_failure_cls diff --git a/assets/migrations/0019_fix_cabletype.py b/assets/migrations/0019_fix_cabletype.py new file mode 100644 index 00000000..57e63976 --- /dev/null +++ b/assets/migrations/0019_fix_cabletype.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.5 on 2021-02-08 16:02 + +from django.db import migrations + + +def add_default(apps, schema_editor): + CableType = apps.get_model('assets', 'CableType') + Connector = apps.get_model('assets', 'Connector') + for cable_type in CableType.objects.all(): + if cable_type.plug is None: + cable_type.plug = Connector.first() + if cable_type.socket is None: + cable_type.socket = Connector.first() + cable_type.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0018_auto_20200415_1940'), + ] + + operations = [ + migrations.RunPython(add_default, migrations.RunPython.noop) + ] diff --git a/assets/migrations/0020_auto_20210208_1603.py b/assets/migrations/0020_auto_20210208_1603.py new file mode 100644 index 00000000..c040462a --- /dev/null +++ b/assets/migrations/0020_auto_20210208_1603.py @@ -0,0 +1,50 @@ +# Generated by Django 3.1.5 on 2021-02-08 16:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0019_fix_cabletype'), + ] + + operations = [ + migrations.AlterField( + model_name='assetstatus', + name='display_class', + field=models.CharField(blank=True, default='', help_text='HTML class to be appended to alter display of assets with this status, such as in the list.', max_length=80), + preserve_default=False, + ), + migrations.AlterField( + model_name='cabletype', + name='plug', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plug', to='assets.connector'), + ), + migrations.AlterField( + model_name='cabletype', + name='socket', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='socket', to='assets.connector'), + ), + migrations.AlterField( + model_name='supplier', + name='address', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='supplier', + name='email', + field=models.EmailField(blank=True, default='', max_length=254), + ), + migrations.AlterField( + model_name='supplier', + name='notes', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='supplier', + name='phone', + field=models.CharField(blank=True, default='', max_length=15), + ), + ] diff --git a/assets/models.py b/assets/models.py index cc532780..0b783865 100644 --- a/assets/models.py +++ b/assets/models.py @@ -10,44 +10,44 @@ from RIGS.models import RevisionMixin, Profile class AssetCategory(models.Model): + name = models.CharField(max_length=80) + class Meta: verbose_name = 'Asset Category' verbose_name_plural = 'Asset Categories' ordering = ['name'] - name = models.CharField(max_length=80) - def __str__(self): return self.name class AssetStatus(models.Model): + name = models.CharField(max_length=80) + should_show = models.BooleanField( + default=True, help_text="Should this be shown by default in the asset list.") + display_class = models.CharField(max_length=80, blank=True, help_text="HTML class to be appended to alter display of assets with this status, such as in the list.") + class Meta: verbose_name = 'Asset Status' verbose_name_plural = 'Asset Statuses' ordering = ['name'] - name = models.CharField(max_length=80) - should_show = models.BooleanField( - default=True, help_text="Should this be shown by default in the asset list.") - display_class = models.CharField(max_length=80, blank=True, null=True, help_text="HTML class to be appended to alter display of assets with this status, such as in the list.") - def __str__(self): return self.name @reversion.register class Supplier(models.Model, RevisionMixin): + name = models.CharField(max_length=80) + 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="") + class Meta: ordering = ['name'] - name = models.CharField(max_length=80) - phone = models.CharField(max_length=15, blank=True, null=True) - email = models.EmailField(blank=True, null=True) - address = models.TextField(blank=True, null=True) - - notes = models.TextField(blank=True, null=True) - def get_absolute_url(self): return reverse('supplier_list') @@ -65,17 +65,16 @@ class Connector(models.Model): return self.description -# Things are nullable that shouldn't be because I didn't properly fix the data structure when moving this to its own model... class CableType(models.Model): - class Meta: - ordering = ['plug', 'socket', '-circuits'] - circuits = models.IntegerField(default=1) cores = models.IntegerField(default=3) plug = models.ForeignKey(Connector, on_delete=models.CASCADE, - related_name='plug', null=True) + related_name='plug') socket = models.ForeignKey(Connector, on_delete=models.CASCADE, - related_name='socket', null=True) + related_name='socket') + + class Meta: + ordering = ['plug', 'socket', '-circuits'] def __str__(self): if self.plug and self.socket: @@ -104,12 +103,6 @@ def get_available_asset_id(wanted_prefix=""): @reversion.register class Asset(models.Model, RevisionMixin): - class Meta: - ordering = ['asset_id_prefix', 'asset_id_number'] - permissions = [ - ('asset_finance', 'Can see financial data for assets') - ] - parent = models.ForeignKey(to='self', related_name='asset_parent', blank=True, null=True, on_delete=models.SET_NULL) asset_id = models.CharField(max_length=15, unique=True) @@ -143,12 +136,18 @@ class Asset(models.Model, RevisionMixin): reversion_perm = 'assets.asset_finance' - def get_absolute_url(self): - return reverse('asset_detail', kwargs={'pk': self.asset_id}) + class Meta: + ordering = ['asset_id_prefix', 'asset_id_number'] + permissions = [ + ('asset_finance', 'Can see financial data for assets') + ] def __str__(self): return "{} | {}".format(self.asset_id, self.description) + def get_absolute_url(self): + return reverse('asset_detail', kwargs={'pk': self.asset_id}) + def clean(self): errdict = {} if self.date_sold and self.date_acquired > self.date_sold: diff --git a/versioning/tests/test_models.py b/versioning/tests/test_models.py index 09787c4b..453ad901 100644 --- a/versioning/tests/test_models.py +++ b/versioning/tests/test_models.py @@ -43,7 +43,7 @@ class RIGSVersionTestCase(TestCase): # Check the prev version is loaded correctly previousVersion = current_version.parent - self.assertEqual(previousVersion._object_version.object.notes, None) + assert previousVersion._object_version.object.notes == '' # Check that finding the parent of the first version fails gracefully self.assertFalse(previousVersion.parent)