From 54b44404bab00d2c876a5c26b8792cb067698558 Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Mon, 29 May 2023 10:50:04 +0000 Subject: [PATCH] Fix various minor issues (#545) * Add absolute URL to power tests * Update to target Python 3.10 * Return user to current page when clicking 'mark reviewed' * Add units to power test record detail and form I'm a bad scientist (coz I'm an engineer) * Allow a higher value in PSSC fields * Default venue to event venue in EC/PT * Fix population of initial venue values for EC/PT * Add link to create power test from EC detail * Do not set power plan field to required on RA "This might be a problem if the risk assessment is being done by one person and the power plan by another." * Default power MIC to MIC * Implement some suggestions from the Doctor * Prevent checking in to cancelled events and dry hires Will close #539 * Exclude dry hires from H&S overview list * Add "ex VAT" tooltips to asset purchase price and replacement cost * Automagically clear and focus ID field when audit modal closes Closes #533 * Delete unused things * Allow two decimal places in cable length, show training item IDs in selectpicker Will close #540 * Fix #524 500 Error when viewing qualification list for items nobody is qualified in * Update README.md * Add a guard against nulls in recent changes Maybe fixes #537 I'm unable to replicate locally * Turn down verbosity of CI tests, fix tests, potential speedup * Squash migration * Add encoding to open * Update to v3 upload-artifact Resolves a deprecation warning --- .github/workflows/django.yml | 5 +- Pipfile | 2 +- Pipfile.lock | 104 ++++++++++++++---- PyRIGS/settings.py | 9 +- PyRIGS/tests/base.py | 2 +- PyRIGS/tests/test_unit.py | 8 +- PyRIGS/views.py | 7 +- README.md | 3 +- RIGS/forms.py | 11 +- RIGS/migrations/0049_auto_20230529_1123.py | 53 +++++++++ RIGS/models.py | 24 ++-- RIGS/templates/event_form.html | 2 +- RIGS/templates/hs/event_checklist_detail.html | 4 + RIGS/templates/hs/event_checklist_form.html | 4 +- RIGS/templates/hs/power_detail.html | 2 +- RIGS/templates/hs/power_form.html | 10 +- RIGS/templates/hs/risk_assessment_form.html | 2 +- RIGS/templates/partials/event_status.html | 2 +- RIGS/templatetags/filters.py | 4 +- RIGS/tests/pages.py | 2 +- RIGS/tests/test_unit.py | 5 +- RIGS/views/hs.py | 59 +++++----- assets/migrations/0028_alter_asset_length.py | 18 +++ assets/models.py | 4 +- assets/templates/asset_audit_list.html | 5 + assets/templates/asset_form.html | 6 +- .../templates/partials/asset_list_table.html | 2 +- .../partials/purchasedetails_form.html | 4 +- assets/tests/pages.py | 2 +- package-lock.json | 1 + pipeline/source_assets/js/interaction.js | 5 - templates/base_ajax.html | 1 - .../partials/form_field.html | 8 +- training/models.py | 6 +- training/templatetags/tags.py | 2 +- training/tests/test_interaction.py | 2 +- training/tests/test_unit.py | 2 +- training/views.py | 2 +- versioning/urls.py | 12 +- versioning/versioning.py | 5 +- versioning/views.py | 12 +- 41 files changed, 294 insertions(+), 129 deletions(-) create mode 100644 RIGS/migrations/0049_auto_20230529_1123.py create mode 100644 assets/migrations/0028_alter_asset_length.py rename {assets/templates => templates}/partials/form_field.html (52%) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 41aded15..84d49acd 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -12,6 +12,7 @@ jobs: runs-on: ubuntu-latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PYTHONDONTWRITEBYTECODE: 1 steps: - uses: actions/checkout@v3 - name: Set up Python @@ -41,8 +42,8 @@ jobs: pipenv run python3 manage.py makemigrations --check --dry-run pipenv run python3 manage.py collectstatic --noinput - name: Run Tests - run: pipenv run pytest -n auto -vv --cov - - uses: actions/upload-artifact@v2 + run: pipenv run pytest -n auto --cov + - uses: actions/upload-artifact@v3 if: failure() with: name: failure-screenshots ${{ matrix.test-group }} diff --git a/Pipfile b/Pipfile index 2cbc2f2c..23fecb62 100644 --- a/Pipfile +++ b/Pipfile @@ -93,7 +93,7 @@ pytest = "*" pytest-reverse = "*" [requires] -python_version = "3.9" +python_version = "3.10" [dev-packages.pytest-xdist] extras = [ "psutil",] diff --git a/Pipfile.lock b/Pipfile.lock index e5d41286..ce535346 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "9865ac4d5b85c2b9461dcfe9391f319bce4416b009ced82b8f8fe2ea5b7d9d7a" + "sha256": "71377846d2282aa2f5352819eb31260cbbb04f41c2c3462b5c3ca2c8bc602b34" }, "pipfile-spec": 6, "requires": { - "python_version": "3.9" + "python_version": "3.10" }, "sources": [ { @@ -263,7 +263,7 @@ "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==3.1.0" }, "configparser": { @@ -1006,11 +1006,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:5932c092c6e6035584eb74d77064e4bce3b7935dfc4a331349719a40db265840", - "sha256:cf89a5063ef84278d186aceaed6fb595bfe67d099298e537634a323664265669" + "sha256:0bbcecda9f51936904c1030e7fef0fe693e633888f02a14d1cb68646a50e83b3", + "sha256:56d6d9d194c898d853a7c1dd99bed92ce82334ee1282292c15bcc967ff1a49b5" ], "index": "pypi", - "version": "==1.22.2" + "version": "==1.24.0" }, "setuptools": { "hashes": [ @@ -1206,11 +1206,11 @@ }, "urllib3": { "hashes": [ - "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", - "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" + "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", + "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14" ], "index": "pypi", - "version": "==1.26.15" + "version": "==1.26.16" }, "webencodings": { "hashes": [ @@ -1232,7 +1232,7 @@ "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==1.2.0" }, "yolk": { @@ -1552,7 +1552,7 @@ "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==3.1.0" }, "coverage": { @@ -1765,11 +1765,11 @@ }, "pytest-cov": { "hashes": [ - "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b", - "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470" + "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6", + "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a" ], "index": "pypi", - "version": "==4.0.0" + "version": "==4.1.0" }, "pytest-django": { "hashes": [ @@ -1800,11 +1800,11 @@ "psutil" ], "hashes": [ - "sha256:1849bd98d8b242b948e472db7478e090bf3361912a8fed87992ed94085f54727", - "sha256:37290d161638a20b672401deef1cba812d110ac27e35d213f091d15b8beb40c9" + "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93", + "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2" ], "index": "pypi", - "version": "==3.2.1" + "version": "==3.3.1" }, "requests": { "hashes": [ @@ -1878,18 +1878,18 @@ }, "urllib3": { "hashes": [ - "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", - "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" + "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", + "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14" ], "index": "pypi", - "version": "==1.26.15" + "version": "==1.26.16" }, "wsproto": { "hashes": [ "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version >= '3.7.0'", "version": "==1.2.0" }, "zope.component": { @@ -1900,6 +1900,22 @@ "index": "pypi", "version": "==4.6.2" }, + "zope.deferredimport": { + "hashes": [ + "sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1", + "sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a" + ], + "index": "pypi", + "version": "==4.3.1" + }, + "zope.deprecation": { + "hashes": [ + "sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df", + "sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113" + ], + "index": "pypi", + "version": "==4.4.0" + }, "zope.event": { "hashes": [ "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42", @@ -2011,6 +2027,52 @@ ], "index": "pypi", "version": "==5.2.0" + }, + "zope.proxy": { + "hashes": [ + "sha256:00573dfa755d0703ab84bb23cb6ecf97bb683c34b340d4df76651f97b0bab068", + "sha256:092049280f2848d2ba1b57b71fe04881762a220a97b65288bcb0968bb199ec30", + "sha256:0cbd27b4d3718b5ec74fc65ffa53c78d34c65c6fd9411b8352d2a4f855220cf1", + "sha256:17fc7e16d0c81f833a138818a30f366696653d521febc8e892858041c4d88785", + "sha256:19577dfeb70e8a67249ba92c8ad20589a1a2d86a8d693647fa8385408a4c17b0", + "sha256:207aa914576b1181597a1516e1b90599dc690c095343ae281b0772e44945e6a4", + "sha256:219a7db5ed53e523eb4a4769f13105118b6d5b04ed169a283c9775af221e231f", + "sha256:2b50ea79849e46b5f4f2b0247a3687505d32d161eeb16a75f6f7e6cd81936e43", + "sha256:5903d38362b6c716e66bbe470f190579c530a5baf03dbc8500e5c2357aa569a5", + "sha256:5c24903675e271bd688c6e9e7df5775ac6b168feb87dbe0e4bcc90805f21b28f", + "sha256:5ef6bc5ed98139e084f4e91100f2b098a0cd3493d4e76f9d6b3f7b95d7ad0f06", + "sha256:61b55ae3c23a126a788b33ffb18f37d6668e79a05e756588d9e4d4be7246ab1c", + "sha256:63ddb992931a5e616c87d3d89f5a58db086e617548005c7f9059fac68c03a5cc", + "sha256:6943da9c09870490dcfd50c4909c0cc19f434fa6948f61282dc9cb07bcf08160", + "sha256:6ad40f85c1207803d581d5d75e9ea25327cd524925699a83dfc03bf8e4ba72b7", + "sha256:6b44433a79bdd7af0e3337bd7bbcf53dd1f9b0fa66bf21bcb756060ce32a96c1", + "sha256:6bbaa245015d933a4172395baad7874373f162955d73612f0b66b6c2c33b6366", + "sha256:7007227f4ea85b40a2f5e5a244479f6a6dfcf906db9b55e812a814a8f0e2c28d", + "sha256:74884a0aec1f1609190ec8b34b5d58fb3b5353cf22b96161e13e0e835f13518f", + "sha256:7d25fe5571ddb16369054f54cdd883f23de9941476d97f2b92eb6d7d83afe22d", + "sha256:7e162bdc5e3baad26b2262240be7d2bab36991d85a6a556e48b9dfb402370261", + "sha256:814d62678dc3a30f4aa081982d830b7c342cf230ffc9d030b020cb154eeebf9e", + "sha256:8878a34c5313ee52e20aa50b03138af8d472bae465710fb954d133a9bfd3c38d", + "sha256:a66a0d94e5b081d5d695e66d6667e91e74d79e273eee95c1747717ba9cb70792", + "sha256:a69f5cbf4addcfdf03dda564a671040127a6b7c34cf9fe4973582e68441b63fa", + "sha256:b00f9f0c334d07709d3f73a7cb8ae63c6ca1a90c790a63b5e7effa666ef96021", + "sha256:b6ed71e4a7b4690447b626f499d978aa13197a0e592950e5d7020308f6054698", + "sha256:bdf5041e5851526e885af579d2f455348dba68d74f14a32781933569a327fddf", + "sha256:be034360dd34e62608419f86e799c97d389c10a0e677a25f236a971b2f40dac9", + "sha256:cc8f590a5eed30b314ae6b0232d925519ade433f663de79cc3783e4b10d662ba", + "sha256:cd7a318a15fe6cc4584bf3c4426f092ed08c0fd012cf2a9173114234fe193e11", + "sha256:cf19b5f63a59c20306e034e691402b02055c8f4e38bf6792c23cad489162a642", + "sha256:cfc781ce442ec407c841e9aa51d0e1024f72b6ec34caa8fdb6ef9576d549acf2", + "sha256:dea9f6f8633571e18bc20cad83603072e697103a567f4b0738d52dd0211b4527", + "sha256:e4a86a1d5eb2cce83c5972b3930c7c1eac81ab3508464345e2b8e54f119d5505", + "sha256:e7106374d4a74ed9ff00c46cc00f0a9f06a0775f8868e423f85d4464d2333679", + "sha256:e98a8a585b5668aa9e34d10f7785abf9545fe72663b4bfc16c99a115185ae6a5", + "sha256:f64840e68483316eb58d82c376ad3585ca995e69e33b230436de0cdddf7363f9", + "sha256:f8f4b0a9e6683e43889852130595c8854d8ae237f2324a053cdd884de936aa9b", + "sha256:fc45a53219ed30a7f670a6d8c98527af0020e6fd4ee4c0a8fb59f147f06d816c" + ], + "index": "pypi", + "version": "==4.3.5" } } } diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index 7f371f3d..54a3d228 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -218,8 +218,6 @@ TIME_ZONE = 'Europe/London' FORMAT_MODULE_PATH = 'PyRIGS.formats' -USE_I18N = True - USE_L10N = True USE_TZ = True @@ -264,3 +262,10 @@ TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf" AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk' DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' + +SECURE_HSTS_SECONDS = 3600 +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_CONTENT_TYPE_NOSNIFF = True +SESSION_COOKIE_SECURE = env('SESSION_COOKIE_SECURE_ENABLED', True) +CSRF_COOKIE_SECURE = env('CSRF_COOKIE_SECURE_ENABLED', True) +SECURE_HSTS_PRELOAD = True diff --git a/PyRIGS/tests/base.py b/PyRIGS/tests/base.py index d1669d5e..bcf1824c 100644 --- a/PyRIGS/tests/base.py +++ b/PyRIGS/tests/base.py @@ -63,7 +63,7 @@ def screenshot_failure(func): if not pathlib.Path("screenshots").is_dir(): os.mkdir("screenshots") self.driver.save_screenshot(screenshot_file) - print("Error in test {} is at path {}".format(screenshot_name, screenshot_file), file=sys.stderr) + print(f"Error in test {screenshot_name} is at path {screenshot_file}", file=sys.stderr) raise e return wrapper_func diff --git a/PyRIGS/tests/test_unit.py b/PyRIGS/tests/test_unit.py index 3ab38567..12207d7d 100644 --- a/PyRIGS/tests/test_unit.py +++ b/PyRIGS/tests/test_unit.py @@ -59,8 +59,8 @@ class TestSampleDataGenerator(TestCase): assert Asset.objects.all().count() > 50 assert Event.objects.all().count() > 100 call_command('deleteSampleData') - assert Asset.objects.all().count() == 0 - assert Event.objects.all().count() == 0 + assert not Asset.objects.all().exists() + assert not Event.objects.all().exists() @override_settings(DEBUG=True) @@ -76,9 +76,9 @@ def test_unauthenticated(client): # Nothing should be available to the unauthen assertTemplateUsed(response, 'login_redirect.html') else: if "embed" in str(url): - expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url) + expected_url = f"{reverse('login_embed')}?next={request_url}" else: - expected_url = "{0}?next={1}".format(reverse('login'), request_url) + expected_url = f"{reverse('login')}?next={request_url}" assertRedirects(response, expected_url) call_command('deleteSampleData') diff --git a/PyRIGS/views.py b/PyRIGS/views.py index 372e1af0..c34f014e 100644 --- a/PyRIGS/views.py +++ b/PyRIGS/views.py @@ -48,7 +48,7 @@ class Index(generic.TemplateView): # Displays the current rig count along with def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['rig_count'] = models.Event.objects.rig_count() - context['now'] = models.Event.objects.events_in_bounds(timezone.now(), timezone.now()) + context['now'] = models.Event.objects.events_in_bounds(timezone.now(), timezone.now()).exclude(dry_hire=True).exclude(status=models.Event.CANCELLED) return context @@ -135,10 +135,11 @@ class SecureAPIRequest(generic.View): query = reduce(operator.and_, queries) objects = self.models[model].objects.filter(query) for o in objects: + name = o.display_name if hasattr(o, 'display_name') else o.name data = { 'pk': o.pk, 'value': o.pk, - 'text': o.name, + 'text': name, } try: # See if there is a valid update URL data['update'] = reverse(f"{model}_update", kwargs={'pk': o.pk}) @@ -183,7 +184,7 @@ class ModalURLMixin: url = reverse_lazy('closemodal') update_url = str(reverse_lazy(update, kwargs={'pk': self.object.pk})) messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object])) - messages.info(self.request, "modalobject[0]['update_url']='" + update_url + "'") + messages.info(self.request, f"modalobject[0]['update_url']='{update_url}'") else: url = reverse_lazy(detail, kwargs={ 'pk': self.object.pk, diff --git a/README.md b/README.md index cd6a443c..b54c9c95 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,9 @@ For setup information and other such helpful stuff check the [Wiki](https://gith - PyRIGS: Base app, stores 'global' information - RIGS: Rigboard stuff - event calendar etc - assets: Database of our kit, testing data etc +- training: Logs in-house training within various "departments" (sound, lighting etc). - versioning: Our custom logic built on top of django-reversion. Semi-modular. - users: Our custom logic for registration and profiles. Semi-modular. -- training: SoonTM + [![forthebadge](https://forthebadge.com/images/badges/built-with-resentment.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/contains-technical-debt.svg)](https://forthebadge.com) diff --git a/RIGS/forms.py b/RIGS/forms.py index c5e5de66..9a5f60b6 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -131,7 +131,7 @@ class BaseClientEventAuthorisationForm(forms.ModelForm): def clean(self): if self.cleaned_data.get('amount') != self.instance.event.total: self.add_error('amount', 'The amount authorised must equal the total for the event (inc VAT).') - return super(BaseClientEventAuthorisationForm, self).clean() + return super().clean() class Meta: abstract = True @@ -179,7 +179,7 @@ class EventRiskAssessmentForm(forms.ModelForm): unexpected_values.append(f"
  • {self._meta.model._meta.get_field(field).help_text}
  • ") if len(unexpected_values) > 0 and not self.cleaned_data.get('supervisor_consulted'): raise forms.ValidationError(f"Your answers to these questions: require consulting with a supervisor.", code='unusual_answers') - return super(EventRiskAssessmentForm, self).clean() + return super().clean() class Meta: model = models.RiskAssessment @@ -195,8 +195,6 @@ class EventChecklistForm(forms.ModelForm): if field.__class__ == forms.NullBooleanField: # Only display yes/no to user, the 'none' is only ever set in the background field.widget = forms.CheckboxInput() - # Parsed from incoming form data by clean, then saved into models when the form is saved - items = {} related_models = { 'venue': models.Venue, @@ -216,6 +214,11 @@ class PowerTestRecordForm(forms.ModelForm): # Only display yes/no to user, the 'none' is only ever set in the background field.widget = forms.CheckboxInput() + related_models = { + 'venue': models.Venue, + 'power_mic': models.Profile, + } + class Meta: model = models.PowerTestRecord fields = '__all__' diff --git a/RIGS/migrations/0049_auto_20230529_1123.py b/RIGS/migrations/0049_auto_20230529_1123.py new file mode 100644 index 00000000..9b3fdcda --- /dev/null +++ b/RIGS/migrations/0049_auto_20230529_1123.py @@ -0,0 +1,53 @@ +# Generated by Django 3.2.19 on 2023-05-29 10:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0048_auto_20230518_1256'), + ] + + operations = [ + migrations.AlterField( + model_name='powertestrecord', + name='fd_earth_fault', + field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (ZS) / Ω', max_digits=6, null=True, verbose_name='Earth Fault Loop Impedance'), + ), + migrations.AlterField( + model_name='powertestrecord', + name='fd_pssc', + field=models.IntegerField(blank=True, help_text='Prospective Short Circuit Current / A', null=True, verbose_name='PSCC'), + ), + migrations.AlterField( + model_name='powertestrecord', + name='w1_earth_fault', + field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (ZS) / Ω', max_digits=6, null=True, verbose_name='Earth Fault Loop Impedance'), + ), + migrations.AlterField( + model_name='powertestrecord', + name='w1_voltage', + field=models.IntegerField(blank=True, help_text='Voltage / V', null=True), + ), + migrations.AlterField( + model_name='powertestrecord', + name='w2_earth_fault', + field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (ZS) / Ω', max_digits=6, null=True, verbose_name='Earth Fault Loop Impedance'), + ), + migrations.AlterField( + model_name='powertestrecord', + name='w2_voltage', + field=models.IntegerField(blank=True, help_text='Voltage / V', null=True), + ), + migrations.AlterField( + model_name='powertestrecord', + name='w3_earth_fault', + field=models.DecimalField(blank=True, decimal_places=2, help_text='Earth Fault Loop Impedance (ZS) / Ω', max_digits=6, null=True, verbose_name='Earth Fault Loop Impedance'), + ), + migrations.AlterField( + model_name='powertestrecord', + name='w3_voltage', + field=models.IntegerField(blank=True, help_text='Voltage / V', null=True), + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index 550b4df5..ea18e95e 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -497,7 +497,7 @@ class Event(models.Model, RevisionMixin): earliest = datetime.datetime.combine(self.start_date, datetime.time(00, 00)) tz = pytz.timezone(settings.TIME_ZONE) earliest = tz.localize(earliest) - return not self.dry_hire and earliest <= timezone.now() + return not self.dry_hire and not self.status == Event.CANCELLED and earliest <= timezone.now() objects = EventManager() @@ -875,6 +875,9 @@ class EventChecklist(ReviewableModel, RevisionMixin): @reversion.register class PowerTestRecord(ReviewableModel, RevisionMixin): + earth_fault_text = "Earth Fault Loop Impedance (ZS) / Ω" + pssc_text = "Prospective Short Circuit Current / A" + event = models.ForeignKey('Event', related_name='power_tests', on_delete=models.CASCADE) power_mic = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='checklists', verbose_name="Power MIC", on_delete=models.CASCADE, help_text="Who is the Power MIC?") @@ -896,21 +899,21 @@ class PowerTestRecord(ReviewableModel, RevisionMixin): fd_voltage_l2 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L2-N", help_text="L2 - N") fd_voltage_l3 = models.IntegerField(blank=True, null=True, verbose_name="First Distro Voltage L3-N", help_text="L3 - N") fd_phase_rotation = models.BooleanField(blank=True, null=True, verbose_name="Phase Rotation", help_text="Phase Rotation
    (if required)") - fd_earth_fault = models.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, 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") + fd_earth_fault = models.DecimalField(blank=True, null=True, max_digits=6, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text=earth_fault_text) + fd_pssc = models.IntegerField(blank=True, null=True, verbose_name="PSCC", help_text=pssc_text) # Worst case points 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.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (ZS)") + w1_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage / V") + w1_earth_fault = models.DecimalField(blank=True, null=True, max_digits=6, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text=earth_fault_text) 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.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (ZS)") + w2_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage / V") + w2_earth_fault = models.DecimalField(blank=True, null=True, max_digits=6, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text=earth_fault_text) 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.DecimalField(blank=True, null=True, max_digits=5, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text="Earth Fault Loop Impedance (ZS)") + w3_voltage = models.IntegerField(blank=True, null=True, help_text="Voltage / V") + w3_earth_fault = models.DecimalField(blank=True, null=True, max_digits=6, decimal_places=2, verbose_name="Earth Fault Loop Impedance", help_text=earth_fault_text) all_rcds_tested = models.BooleanField(blank=True, null=True, help_text="All circuit RCDs tested?
    (using test button)") public_sockets_tested = models.BooleanField(blank=True, null=True, help_text="Public/Performer accessible circuits tested?
    (using socket tester)") @@ -924,6 +927,9 @@ class PowerTestRecord(ReviewableModel, RevisionMixin): def __str__(self): return f"{self.pk} - {self.event}" + def get_absolute_url(self): + return reverse('pt_detail', kwargs={'pk': self.pk}) + @property def activity_feed_string(self): return str(self.event) diff --git a/RIGS/templates/event_form.html b/RIGS/templates/event_form.html index afa8d5b7..108ed4e5 100644 --- a/RIGS/templates/event_form.html +++ b/RIGS/templates/event_form.html @@ -209,7 +209,7 @@
    diff --git a/RIGS/templates/hs/event_checklist_detail.html b/RIGS/templates/hs/event_checklist_detail.html index 705e0209..82e20020 100644 --- a/RIGS/templates/hs/event_checklist_detail.html +++ b/RIGS/templates/hs/event_checklist_detail.html @@ -9,6 +9,8 @@
    {% button 'edit' url='ec_edit' pk=object.pk %} {% button 'view' url='event_detail' pk=object.event.pk text="Event" %} + {% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
    @@ -71,6 +73,8 @@
    {% button 'edit' url='ec_edit' pk=object.pk %} {% button 'view' url='event_detail' pk=object.pk text="Event" %} + {% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
    diff --git a/RIGS/templates/hs/event_checklist_form.html b/RIGS/templates/hs/event_checklist_form.html index ea8183a1..373ad306 100644 --- a/RIGS/templates/hs/event_checklist_form.html +++ b/RIGS/templates/hs/event_checklist_form.html @@ -24,7 +24,7 @@ {% block content %}
    {% include 'form_errors.html' %} - +
    @@ -59,8 +59,6 @@
    diff --git a/RIGS/templates/hs/power_detail.html b/RIGS/templates/hs/power_detail.html index d8253248..958a1d25 100644 --- a/RIGS/templates/hs/power_detail.html +++ b/RIGS/templates/hs/power_detail.html @@ -86,7 +86,7 @@ - Voltage
    (cube meter) + Voltage
    (cube meter) / V {{ object|help_text:'fd_voltage_l1' }} {{ object|help_text:'fd_voltage_l2' }} {{ object|help_text:'fd_voltage_l3' }} diff --git a/RIGS/templates/hs/power_form.html b/RIGS/templates/hs/power_form.html index 315175b7..d77ead1b 100644 --- a/RIGS/templates/hs/power_form.html +++ b/RIGS/templates/hs/power_form.html @@ -51,22 +51,18 @@
    - {% if power_mic %} - {% elif event.riskassessment.power_mic %} - {% endif %}
    - {% if venue %} - {% elif event.venue %} - {% endif %}
    @@ -119,7 +115,7 @@ - Voltage
    (cube meter) + Voltage
    (cube meter) / V {{ form.fd_voltage_l1.help_text }} {{ form.fd_voltage_l2.help_text }} {{ form.fd_voltage_l3.help_text }} diff --git a/RIGS/templates/hs/risk_assessment_form.html b/RIGS/templates/hs/risk_assessment_form.html index 28184cd3..0fcef1be 100644 --- a/RIGS/templates/hs/risk_assessment_form.html +++ b/RIGS/templates/hs/risk_assessment_form.html @@ -25,7 +25,7 @@ }); $('input[type=radio][name=outside], input[type=radio][name=generators], input[type=radio][name=other_companies_power], input[type=radio][name=nonstandard_equipment_power], input[type=radio][name=multiple_electrical_environments]').change(function() { $('#{{ form.power_notes.id_for_label }}').prop('required', parseBool(this.value)); - $('#{{ form.power_plan.id_for_label }}').prop('required', parseBool(this.value)); + //$('#{{ form.power_plan.id_for_label }}').prop('required', parseBool(this.value)); }); $('input[type=radio][name=special_structures]').change(function() { $('#{{ form.persons_responsible_structures.id_for_label }}').prop('hidden', !parseBool(this.value)).prop('required', parseBool(this.value)); diff --git a/RIGS/templates/partials/event_status.html b/RIGS/templates/partials/event_status.html index e4f1ad53..7c521de1 100644 --- a/RIGS/templates/partials/event_status.html +++ b/RIGS/templates/partials/event_status.html @@ -15,7 +15,7 @@ {% endif %} {% endif %} {% if not event.dry_hire %} - {% if event.has_checklist %} + {% if event.riskassessment %} RA: {% else %} RA: diff --git a/RIGS/templatetags/filters.py b/RIGS/templatetags/filters.py index b2480d40..73a8c148 100644 --- a/RIGS/templatetags/filters.py +++ b/RIGS/templatetags/filters.py @@ -171,7 +171,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(url, kwargs={'pk': obj.pk}))) + return mark_safe(obj.name + f" ") else: return obj.name @@ -183,7 +183,7 @@ def linkornone(target, namespace=None, autoescape=True): link = namespace + "://" + target else: link = target - return mark_safe("{}".format(link, str(target))) + return mark_safe(f"{target}") else: return "None" diff --git a/RIGS/tests/pages.py b/RIGS/tests/pages.py index 7745c0c1..a2765830 100644 --- a/RIGS/tests/pages.py +++ b/RIGS/tests/pages.py @@ -114,7 +114,7 @@ class CreateEvent(FormPage): } def select_event_type(self, type_name): - self.find_element(By.XPATH, '//button[.="{}"]'.format(type_name)).click() + self.find_element(By.XPATH, f'//button[.="{type_name}"]').click() def item_row(self, ID): return rigs_regions.ItemRow(self, self.find_element(By.ID, "item-" + ID)) diff --git a/RIGS/tests/test_unit.py b/RIGS/tests/test_unit.py index 3c038e46..2426a59a 100644 --- a/RIGS/tests/test_unit.py +++ b/RIGS/tests/test_unit.py @@ -259,7 +259,7 @@ class TestPrintPaperwork(TestCase): def test_login_redirect(client, django_user_model): request_url = reverse('event_embed', kwargs={'pk': 1}) - expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url) + expected_url = f"{reverse('login_embed')}?next={request_url}" # Request the page and check it redirects response = client.get(request_url, follow=True) @@ -372,7 +372,8 @@ def test_ra_redirect(admin_client, admin_user, ra): class TestMarkdownTemplateTags(TestCase): - markdown = open(os.path.join(settings.BASE_DIR, "RIGS/tests/sample.md")).read() + with open(os.path.join(settings.BASE_DIR, "RIGS/tests/sample.md"), encoding="utf-8") as f: + markdown = f.read() def test_html_safe(self): html = markdown_filter(self.markdown) diff --git a/RIGS/views/hs.py b/RIGS/views/hs.py index a2ac9786..a4a53cb6 100644 --- a/RIGS/views/hs.py +++ b/RIGS/views/hs.py @@ -13,31 +13,24 @@ from django.shortcuts import redirect class HSCreateView(generic.CreateView): - def get_form(self, **kwargs): - form = super().get_form(**kwargs) - epk = self.kwargs.get('pk') - event = models.Event.objects.get(pk=epk) - form.instance.event = event - return form - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - epk = self.kwargs.get('pk') - event = models.Event.objects.get(pk=epk) + event = models.Event.objects.get(pk=self.kwargs.get('pk')) context['event'] = event context['page_title'] = f'Create {self.model.__name__} for Event {event.display_id}' + get_related(context['form'], context) return context -class MarkReviewed(generic.View): - def get(self, *args, **kwargs): +class MarkReviewed(generic.RedirectView): + def get_redirect_url(self, *args, **kwargs): obj = apps.get_model('RIGS', kwargs.get('model')).objects.get(pk=kwargs.get('pk')) with reversion.create_revision(): reversion.set_user(self.request.user) obj.reviewed_by = self.request.user obj.reviewed_at = timezone.now() obj.save() - return HttpResponseRedirect(reverse('hs_list')) + return self.request.META.get('HTTP_REFERER', reverse('hs_list')) class EventRiskAssessmentCreate(HSCreateView): @@ -55,11 +48,17 @@ class EventRiskAssessmentCreate(HSCreateView): if ra is not None: return HttpResponseRedirect(reverse('ra_edit', kwargs={'pk': ra.pk})) - return super(EventRiskAssessmentCreate, self).get(self) + return super().get(self) def get_success_url(self): return reverse('ra_detail', kwargs={'pk': self.object.pk}) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + if context['event'].mic: + context['power_mic'] = context['event'].mic + return context + class EventRiskAssessmentEdit(generic.UpdateView): model = models.RiskAssessment @@ -74,7 +73,7 @@ class EventRiskAssessmentEdit(generic.UpdateView): return reverse('ra_detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): - context = super(EventRiskAssessmentEdit, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) rpk = self.kwargs.get('pk') ra = models.RiskAssessment.objects.get(pk=rpk) context['event'] = ra.event @@ -89,7 +88,7 @@ class EventRiskAssessmentDetail(generic.DetailView): template_name = 'hs/risk_assessment_detail.html' def get_context_data(self, **kwargs): - context = super(EventRiskAssessmentDetail, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['page_title'] = f"Risk Assessment for Event {self.object.event.display_id} {self.object.event.name}" return context @@ -99,7 +98,7 @@ class EventChecklistDetail(generic.DetailView): template_name = 'hs/event_checklist_detail.html' def get_context_data(self, **kwargs): - context = super(EventChecklistDetail, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['page_title'] = f"Event Checklist for Event {self.object.event.display_id} {self.object.event.name}" return context @@ -117,7 +116,7 @@ class EventChecklistEdit(generic.UpdateView): return reverse('ec_detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): - context = super(EventChecklistEdit, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) pk = self.kwargs.get('pk') ec = models.EventChecklist.objects.get(pk=pk) context['event'] = ec.event @@ -136,19 +135,22 @@ class EventChecklistCreate(HSCreateView): def get(self, *args, **kwargs): epk = kwargs.get('pk') event = models.Event.objects.get(pk=epk) - # Check if RA exists ra = models.RiskAssessment.objects.filter(event=event).first() - if ra is None: 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('event_ra', kwargs={'pk': epk})) - - return super(EventChecklistCreate, self).get(self) + return super().get(self) def get_success_url(self): return reverse('ec_detail', kwargs={'pk': self.object.pk}) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + if context['event'].venue: + context['venue'] = context['event'].venue + return context + class PowerTestDetail(generic.DetailView): model = models.PowerTestRecord @@ -179,7 +181,7 @@ class PowerTestEdit(generic.UpdateView): context['event'] = ec.event context['edit'] = True context['page_title'] = f'Edit Power Test Record for Event {ec.event.display_id}' - # get_related(context['form'], context) + get_related(context['form'], context) return context @@ -191,7 +193,6 @@ class PowerTestCreate(HSCreateView): def get(self, *args, **kwargs): epk = kwargs.get('pk') event = models.Event.objects.get(pk=epk) - # Check if RA exists ra = models.RiskAssessment.objects.filter(event=event).first() @@ -204,6 +205,14 @@ class PowerTestCreate(HSCreateView): def get_success_url(self): return reverse('pt_detail', kwargs={'pk': self.object.pk}) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + if context['event'].venue: + context['venue'] = context['event'].venue + if context['event'].riskassessment.power_mic: + context['power_mic'] = context['event'].riskassessment.power_mic + return context + class HSList(generic.ListView): paginate_by = 20 @@ -211,10 +220,10 @@ class HSList(generic.ListView): template_name = 'hs/hs_list.html' def get_queryset(self): - return models.Event.objects.all().exclude(status=models.Event.CANCELLED).order_by('-start_date').select_related('riskassessment').prefetch_related('checklists') + return models.Event.objects.all().exclude(status=models.Event.CANCELLED).exclude(dry_hire=True).order_by('-start_date').select_related('riskassessment').prefetch_related('checklists') def get_context_data(self, **kwargs): - context = super(HSList, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['page_title'] = 'H&S Overview' return context diff --git a/assets/migrations/0028_alter_asset_length.py b/assets/migrations/0028_alter_asset_length.py new file mode 100644 index 00000000..78fadbdf --- /dev/null +++ b/assets/migrations/0028_alter_asset_length.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-05-24 22:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0027_asset_nickname'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='length', + field=models.DecimalField(blank=True, decimal_places=2, help_text='m', max_digits=10, null=True), + ), + ] diff --git a/assets/models.py b/assets/models.py index 32331029..3d8f70c8 100644 --- a/assets/models.py +++ b/assets/models.py @@ -135,7 +135,7 @@ class Asset(models.Model, RevisionMixin): # Cable assets is_cable = models.BooleanField(default=False) cable_type = models.ForeignKey(to=CableType, blank=True, null=True, on_delete=models.SET_NULL) - length = models.DecimalField(decimal_places=1, max_digits=10, + length = models.DecimalField(decimal_places=2, max_digits=10, blank=True, null=True, help_text='m') csa = models.DecimalField(decimal_places=2, max_digits=10, blank=True, null=True, help_text='mm²') @@ -192,5 +192,5 @@ class Asset(models.Model, RevisionMixin): return str(self.asset_id) @property - def name(self): + def display_name(self): return f"{self.display_id} | {self.description}" diff --git a/assets/templates/asset_audit_list.html b/assets/templates/asset_audit_list.html index 057d2dae..73711dbb 100644 --- a/assets/templates/asset_audit_list.html +++ b/assets/templates/asset_audit_list.html @@ -35,6 +35,11 @@ function onAuditClick(assetID) { $('#' + assetID).remove(); } + $('#modal').on('hidden.bs.modal', function (e) { + searchbar = document.getElementById('id_q'); + searchbar.value = ""; + setTimeout(searchbar.focus(), 2000); + }) {% endblock %} diff --git a/assets/templates/asset_form.html b/assets/templates/asset_form.html index 7496875b..af61c8e4 100644 --- a/assets/templates/asset_form.html +++ b/assets/templates/asset_form.html @@ -12,7 +12,6 @@ {{ block.super }} - {% endblock %} {% block js %} @@ -35,9 +34,10 @@ $(document).find(".selectpicker").selectpicker().each(function(){initPicker($(this))}); }); + {% endblock %} diff --git a/assets/templates/partials/asset_list_table.html b/assets/templates/partials/asset_list_table.html index d5963b00..e5136eb4 100644 --- a/assets/templates/partials/asset_list_table.html +++ b/assets/templates/partials/asset_list_table.html @@ -12,7 +12,7 @@ {% for item in object_list %} - + {{ item.asset_id }} {{ item.description }} {{ item.category }} diff --git a/assets/templates/partials/purchasedetails_form.html b/assets/templates/partials/purchasedetails_form.html index 9fa226b9..dc627fc9 100644 --- a/assets/templates/partials/purchasedetails_form.html +++ b/assets/templates/partials/purchasedetails_form.html @@ -34,7 +34,7 @@
    £
    - {% render_field form.purchase_price|add_class:'form-control' value=object.purchase_price %} + {% render_field form.purchase_price|add_class:'form-control'|set_data:"toggle:tooltip" value=object.purchase_price title="Ex. VAT" %}
    @@ -42,7 +42,7 @@
    £
    - {% render_field form.replacement_cost|add_class:'form-control' value=object.replacement_cost %} + {% render_field form.replacement_cost|add_class:'form-control'|set_data:"toggle:tooltip" value=object.replacement_cost title="Ex. VAT" %}
    diff --git a/assets/tests/pages.py b/assets/tests/pages.py index a9815f31..9f5afa32 100644 --- a/assets/tests/pages.py +++ b/assets/tests/pages.py @@ -77,7 +77,7 @@ class AssetForm(FormPage): 'description': (regions.TextBox, (By.ID, 'id_description')), 'is_cable': (regions.CheckBox, (By.ID, 'id_is_cable')), 'serial_number': (regions.TextBox, (By.ID, 'id_serial_number')), - 'comments': (regions.SimpleMDETextArea, (By.ID, 'id_comments')), + 'comments': (regions.TextBox, (By.ID, 'id_comments')), 'purchase_price': (regions.TextBox, (By.ID, 'id_purchase_price')), 'replacement_cost': (regions.TextBox, (By.ID, 'id_replacement_cost')), 'date_acquired': (regions.DatePicker, (By.ID, 'id_date_acquired')), diff --git a/package-lock.json b/package-lock.json index 9180f996..0a2350ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "PyRIGS", "version": "1.0.0", "license": "Custom", "dependencies": { diff --git a/pipeline/source_assets/js/interaction.js b/pipeline/source_assets/js/interaction.js index 308fe5af..f18bb317 100644 --- a/pipeline/source_assets/js/interaction.js +++ b/pipeline/source_assets/js/interaction.js @@ -6,11 +6,6 @@ function setupItemTable(items_json) { newitem = -1; } -function nl2br(str, is_xhtml) { - var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '
    ' : '
    '; - return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2'); -} - function escapeHtml(str) { return $('
    ').text(str).html(); } diff --git a/templates/base_ajax.html b/templates/base_ajax.html index 435b7607..1fd3fa46 100644 --- a/templates/base_ajax.html +++ b/templates/base_ajax.html @@ -17,7 +17,6 @@ e.preventDefault(); data = $(this).serialize(); action = $(this).attr('action'); - console.log(action) $.post(action, data, function(resp) { $('#modal').html(resp); }); diff --git a/assets/templates/partials/form_field.html b/templates/partials/form_field.html similarity index 52% rename from assets/templates/partials/form_field.html rename to templates/partials/form_field.html index 027a2cce..b7b75418 100644 --- a/assets/templates/partials/form_field.html +++ b/templates/partials/form_field.html @@ -1,15 +1,15 @@ {% load widget_tweaks %} {% load title_spaced from filters %} {% spaceless %} - +{% if not nolabel %}{%endif%} {% if append or prepend %} -
    +
    {% if prepend %}
    {{ prepend }}
    {% endif %} - {% render_field field|add_class:'form-control' %} + {% render_field field|add_class:'form-control' style=style %} {% if append %}
    {{ append }} @@ -17,6 +17,6 @@ {% endif %}
    {% else %} -{% render_field field|add_class:'form-control' class+=col %} +{% render_field field|add_class:'form-control' class+=col style=style %} {% endif %} {% endspaceless %} diff --git a/training/models.py b/training/models.py index 9f6fddc8..c107890c 100644 --- a/training/models.py +++ b/training/models.py @@ -105,6 +105,10 @@ class TrainingItem(models.Model): def display_id(self): return f"{self.category.reference_number}.{self.reference_number}" + @property + def display_name(self): + return f"{self.display_id} | {self.name}" + @display_id.filter @classmethod def display_id(cls, lookup, value): @@ -369,7 +373,7 @@ class TrainingLevelQualification(models.Model, RevisionMixin): return str(self) def get_absolute_url(self): - return reverse('trainee_detail', kwargs={'pk': self.trainee.pk}) + return reverse('trainee_detail', kwargs={'pk': self.trainee_id}) class Meta: unique_together = ["trainee", "level"] diff --git a/training/templatetags/tags.py b/training/templatetags/tags.py index a43ee8fe..30c18fbc 100644 --- a/training/templatetags/tags.py +++ b/training/templatetags/tags.py @@ -43,7 +43,7 @@ def confirm_button(user, trainee, level): if level.user_has_requirements(trainee): string = "Awaiting Confirmation" if models.Trainee.objects.get(pk=user.pk).is_supervisor or user.has_perm('training.add_traininglevelqualification'): - string += "Confirm".format(reverse('confirm_level', kwargs={'pk': trainee.pk, 'level_pk': level.pk})) + string += f"Confirm" return mark_safe(string) else: return "" diff --git a/training/tests/test_interaction.py b/training/tests/test_interaction.py index bf5e2b13..570e7243 100644 --- a/training/tests/test_interaction.py +++ b/training/tests/test_interaction.py @@ -44,7 +44,7 @@ def test_add_qualification(logged_in_browser, live_server, trainee, supervisor, page.submit() assert page.success qualification = models.TrainingItemQualification.objects.get(trainee=trainee, item=training_item) - assert qualification.supervisor.pk == supervisor.pk + assert qualification.supervisor_id == supervisor.pk assert qualification.date == date assert qualification.notes == "A note" assert qualification.depth == models.TrainingItemQualification.STARTED diff --git a/training/tests/test_unit.py b/training/tests/test_unit.py index 4660c141..ed3154ef 100644 --- a/training/tests/test_unit.py +++ b/training/tests/test_unit.py @@ -29,7 +29,7 @@ def test_add_qualification_reversion(admin_client, trainee, training_item, super assert response.status_code == 302 qual = models.TrainingItemQualification.objects.last() assert qual is not None - assert training_item.pk == qual.item.pk + assert training_item.pk == qual.item_id # Ensure only one revision has been created assert Revision.objects.count() == 1 response = admin_client.post(url, {'date': date, 'supervisor': supervisor.pk, 'trainee': trainee.pk, 'item': training_item.pk, 'depth': 1}) diff --git a/training/views.py b/training/views.py index de018faf..eb9c2870 100644 --- a/training/views.py +++ b/training/views.py @@ -265,5 +265,5 @@ class ItemQualifications(generic.ListView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["page_title"] = f"People Qualified In {self.object_list[0].item}" + context["page_title"] = f"People Qualified In {models.TrainingItem.objects.get(pk=self.kwargs['pk'])}" return context diff --git a/versioning/urls.py b/versioning/urls.py index f0551eb0..34f46fa1 100644 --- a/versioning/urls.py +++ b/versioning/urls.py @@ -30,15 +30,15 @@ for app in [apps.get_app_config(label) for label in ("RIGS", "assets", "training modelname = model.__name__.lower() if appname == 'rigboard': urlpatterns += [ - path('{}//history/'.format(modelname), - permission_required_with_403('{}.change_{}'.format(app.label, modelname))( + path(f'{modelname}//history/', + permission_required_with_403(f'{app.label}.change_{modelname}')( views.VersionHistory.as_view()), - name='{}_history'.format(modelname), kwargs={'model': model, 'app': appname, }), + name=f'{modelname}_history', kwargs={'model': model, 'app': appname, }), ] else: urlpatterns += [ - path('{}/{}//history/'.format(appname, modelname), - permission_required_with_403('{}.change_{}'.format(app.label, modelname))( + path(f'{appname}/{modelname}//history/', + permission_required_with_403(f'{app.label}.change_{modelname}')( views.VersionHistory.as_view()), - name='{}_history'.format(modelname), kwargs={'model': model, 'app': appname, }), + name=f'{modelname}_history', kwargs={'model': model, 'app': appname, }), ] diff --git a/versioning/versioning.py b/versioning/versioning.py index 3a9f5931..47ee7c53 100644 --- a/versioning/versioning.py +++ b/versioning/versioning.py @@ -160,7 +160,10 @@ class ModelComparison: # Build some dicts of what we have item_dict = {} # build a list of items, key is the item_pk for version in old_item_versions: # put all the old versions in a list - compare = ModelComparison(old=version._object_version.object, **comparisonParams) + old = version._object_version.object + if old is None: + pass + compare = ModelComparison(old=old, **comparisonParams) item_dict[version.object_id] = compare for version in new_item_versions: # go through the new versions diff --git a/versioning/views.py b/versioning/views.py index d9353971..354d7ccd 100644 --- a/versioning/views.py +++ b/versioning/views.py @@ -27,10 +27,10 @@ class VersionHistory(generic.ListView): return get_object_or_404(self.kwargs['model'], pk=self.kwargs['pk']) def get_context_data(self, **kwargs): - context = super(VersionHistory, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['object'] = self.get_object() if self.kwargs['app'] != 'rigboard': - context['override'] = 'base_{}.html'.format(self.kwargs['app']) + context['override'] = f'base_{self.kwargs["app"]}.html' return context @@ -59,10 +59,10 @@ class ActivityTable(generic.ListView): return RIGSVersion.objects.get_for_multiple_models(filter_models(self.kwargs.get('models'), self.request.user)).order_by("-revision__date_created") def get_context_data(self, **kwargs): - context = super(ActivityTable, self).get_context_data(**kwargs) - context['page_title'] = "{} Activity Stream".format(title(self.kwargs['app'])) + context = super().get_context_data(**kwargs) + context['page_title'] = f"{title(self.kwargs['app'])} Activity Stream" if self.kwargs['app'] != 'rigboard': - context['override'] = 'base_{}.html'.format(self.kwargs['app']) + context['override'] = f'base_{self.kwargs["app"]}.html' return context @@ -77,7 +77,7 @@ class ActivityFeed(generic.ListView): # Appears on homepage def get_context_data(self, **kwargs): # Call the base implementation first to get a context - context = super(ActivityFeed, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['page_title'] = "Activity Feed" maxTimeDelta = datetime.timedelta(hours=1)