mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-02-13 10:09:43 +00:00
Compare commits
20 Commits
combine-pr
...
86c033ba97
| Author | SHA1 | Date | |
|---|---|---|---|
| 86c033ba97 | |||
| 52fd662340 | |||
| 9818ed995f | |||
| 5178614d71 | |||
| a7bf990666 | |||
|
a4a28a6130
|
|||
|
e3d8cf8978
|
|||
|
626779ef25
|
|||
| fa1dc31639 | |||
| d69543e309 | |||
|
|
a24e6d4495 | ||
|
|
fa5792914a | ||
| 0117091f3e | |||
|
37101d3340
|
|||
| de4bed92a4 | |||
|
|
3767923175 | ||
|
|
1d77cf95d3 | ||
|
|
1f21d0b265 | ||
| 7846a6d31e | |||
| d28b73a0b8 |
151
.github/workflows/combine-prs.yml
vendored
Normal file
151
.github/workflows/combine-prs.yml
vendored
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
name: 'Combine PRs'
|
||||||
|
|
||||||
|
# Controls when the action will run - in this case triggered manually
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branchPrefix:
|
||||||
|
description: 'Branch prefix to find combinable PRs based on'
|
||||||
|
required: true
|
||||||
|
default: 'dependabot'
|
||||||
|
mustBeGreen:
|
||||||
|
description: 'Only combine PRs that are green (status is success)'
|
||||||
|
required: true
|
||||||
|
default: true
|
||||||
|
combineBranchName:
|
||||||
|
description: 'Name of the branch to combine PRs into'
|
||||||
|
required: true
|
||||||
|
default: 'combine-prs-branch'
|
||||||
|
ignoreLabel:
|
||||||
|
description: 'Exclude PRs with this label'
|
||||||
|
required: true
|
||||||
|
default: 'nocombine'
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
# This workflow contains a single job called "combine-prs"
|
||||||
|
combine-prs:
|
||||||
|
# The type of runner that the job will run on
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v6
|
||||||
|
id: create-combined-pr
|
||||||
|
name: Create Combined PR
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
script: |
|
||||||
|
const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', {
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo
|
||||||
|
});
|
||||||
|
let branchesAndPRStrings = [];
|
||||||
|
let baseBranch = null;
|
||||||
|
let baseBranchSHA = null;
|
||||||
|
for (const pull of pulls) {
|
||||||
|
const branch = pull['head']['ref'];
|
||||||
|
console.log('Pull for branch: ' + branch);
|
||||||
|
if (branch.startsWith('${{ github.event.inputs.branchPrefix }}')) {
|
||||||
|
console.log('Branch matched prefix: ' + branch);
|
||||||
|
let statusOK = true;
|
||||||
|
if(${{ github.event.inputs.mustBeGreen }}) {
|
||||||
|
console.log('Checking green status: ' + branch);
|
||||||
|
const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) {
|
||||||
|
repository(owner: $owner, name: $repo) {
|
||||||
|
pullRequest(number:$pull_number) {
|
||||||
|
commits(last: 1) {
|
||||||
|
nodes {
|
||||||
|
commit {
|
||||||
|
statusCheckRollup {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
const vars = {
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
pull_number: pull['number']
|
||||||
|
};
|
||||||
|
const result = await github.graphql(stateQuery, vars);
|
||||||
|
const [{ commit }] = result.repository.pullRequest.commits.nodes;
|
||||||
|
const state = commit.statusCheckRollup.state
|
||||||
|
console.log('Validating status: ' + state);
|
||||||
|
if(state != 'SUCCESS') {
|
||||||
|
console.log('Discarding ' + branch + ' with status ' + state);
|
||||||
|
statusOK = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Checking labels: ' + branch);
|
||||||
|
const labels = pull['labels'];
|
||||||
|
for(const label of labels) {
|
||||||
|
const labelName = label['name'];
|
||||||
|
console.log('Checking label: ' + labelName);
|
||||||
|
if(labelName == '${{ github.event.inputs.ignoreLabel }}') {
|
||||||
|
console.log('Discarding ' + branch + ' with label ' + labelName);
|
||||||
|
statusOK = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (statusOK) {
|
||||||
|
console.log('Adding branch to array: ' + branch);
|
||||||
|
const prString = '#' + pull['number'] + ' ' + pull['title'];
|
||||||
|
branchesAndPRStrings.push({ branch, prString });
|
||||||
|
baseBranch = pull['base']['ref'];
|
||||||
|
baseBranchSHA = pull['base']['sha'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (branchesAndPRStrings.length == 0) {
|
||||||
|
core.setFailed('No PRs/branches matched criteria');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await github.rest.git.createRef({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}',
|
||||||
|
sha: baseBranchSHA
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let combinedPRs = [];
|
||||||
|
let mergeFailedPRs = [];
|
||||||
|
for(const { branch, prString } of branchesAndPRStrings) {
|
||||||
|
try {
|
||||||
|
await github.rest.repos.merge({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
base: '${{ github.event.inputs.combineBranchName }}',
|
||||||
|
head: branch,
|
||||||
|
});
|
||||||
|
console.log('Merged branch ' + branch);
|
||||||
|
combinedPRs.push(prString);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Failed to merge branch ' + branch);
|
||||||
|
mergeFailedPRs.push(prString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Creating combined PR');
|
||||||
|
const combinedPRsString = combinedPRs.join('\n');
|
||||||
|
let body = '✅ This PR was created by the Combine PRs action by combining the following PRs:\n' + combinedPRsString;
|
||||||
|
if(mergeFailedPRs.length > 0) {
|
||||||
|
const mergeFailedPRsString = mergeFailedPRs.join('\n');
|
||||||
|
body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString
|
||||||
|
}
|
||||||
|
await github.rest.pulls.create({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
title: 'Combined PR',
|
||||||
|
head: '${{ github.event.inputs.combineBranchName }}',
|
||||||
|
base: baseBranch,
|
||||||
|
body: body
|
||||||
|
});
|
||||||
24
.github/workflows/django.yml
vendored
24
.github/workflows/django.yml
vendored
@@ -13,26 +13,20 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.9.1
|
python-version: 3.9
|
||||||
- uses: actions/cache@v2
|
cache: 'pipenv'
|
||||||
id: pcache
|
|
||||||
with:
|
|
||||||
path: ~/.local/share/virtualenvs
|
|
||||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pipenv-
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip pipenv
|
python3 -m pip install --upgrade pip pipenv
|
||||||
pipenv install -d
|
pipenv install -d
|
||||||
# if: steps.pcache.outputs.cache-hit != 'true'
|
# if: steps.pcache.outputs.cache-hit != 'true'
|
||||||
- name: Cache Static Files
|
- name: Cache Static Files
|
||||||
id: static-cache
|
id: static-cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: 'pipeline/built_assets'
|
path: 'pipeline/built_assets'
|
||||||
key: ${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
|
key: ${{ hashFiles('package-lock.json') }}-${{ hashFiles('pipeline/source_assets') }}
|
||||||
@@ -43,9 +37,9 @@ jobs:
|
|||||||
- name: Basic Checks
|
- name: Basic Checks
|
||||||
run: |
|
run: |
|
||||||
pipenv run pycodestyle . --exclude=migrations,node_modules
|
pipenv run pycodestyle . --exclude=migrations,node_modules
|
||||||
pipenv run python manage.py check
|
pipenv run python3 manage.py check
|
||||||
pipenv run python manage.py makemigrations --check --dry-run
|
pipenv run python3 manage.py makemigrations --check --dry-run
|
||||||
pipenv run python manage.py collectstatic --noinput
|
pipenv run python3 manage.py collectstatic --noinput
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: pipenv run pytest -n auto -vv --cov
|
run: pipenv run pytest -n auto -vv --cov
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
|
|||||||
13
Pipfile
13
Pipfile
@@ -11,7 +11,6 @@ asgiref = "~=3.3.1"
|
|||||||
beautifulsoup4 = "~=4.9.3"
|
beautifulsoup4 = "~=4.9.3"
|
||||||
Brotli = "~=1.0.9"
|
Brotli = "~=1.0.9"
|
||||||
cachetools = "~=4.2.1"
|
cachetools = "~=4.2.1"
|
||||||
certifi = "~=2020.12.5"
|
|
||||||
chardet = "~=4.0.0"
|
chardet = "~=4.0.0"
|
||||||
configparser = "~=5.0.1"
|
configparser = "~=5.0.1"
|
||||||
contextlib2 = "~=0.6.0.post1"
|
contextlib2 = "~=0.6.0.post1"
|
||||||
@@ -26,18 +25,16 @@ django-ical = "~=1.7.1"
|
|||||||
django-recurrence = "~=1.10.3"
|
django-recurrence = "~=1.10.3"
|
||||||
django-registration-redux = "~=2.9"
|
django-registration-redux = "~=2.9"
|
||||||
django-reversion = "~=3.0.9"
|
django-reversion = "~=3.0.9"
|
||||||
django-toolbelt = "~=0.0.1"
|
|
||||||
django-widget-tweaks = "~=1.4.8"
|
django-widget-tweaks = "~=1.4.8"
|
||||||
django-htmlmin = "~=0.11.0"
|
django-htmlmin = "~=0.11.0"
|
||||||
envparse = "~=0.2.0"
|
envparse = "*"
|
||||||
gunicorn = "~=20.0.4"
|
gunicorn = "~=20.0.4"
|
||||||
icalendar = "~=4.0.7"
|
icalendar = "~=4.0.7"
|
||||||
idna = "~=2.10"
|
idna = "~=2.10"
|
||||||
lxml = "~=4.7.1"
|
|
||||||
Markdown = "~=3.3.3"
|
Markdown = "~=3.3.3"
|
||||||
msgpack = "~=1.0.2"
|
msgpack = "~=1.0.2"
|
||||||
pep517 = "~=0.9.1"
|
pep517 = "~=0.9.1"
|
||||||
Pillow = "~=9.0.0"
|
Pillow = "~=9.3.0"
|
||||||
premailer = "~=3.7.0"
|
premailer = "~=3.7.0"
|
||||||
progress = "~=1.5"
|
progress = "~=1.5"
|
||||||
psutil = "~=5.8.0"
|
psutil = "~=5.8.0"
|
||||||
@@ -45,7 +42,7 @@ psycopg2 = "~=2.8.6"
|
|||||||
Pygments = "~=2.7.4"
|
Pygments = "~=2.7.4"
|
||||||
pyparsing = "~=2.4.7"
|
pyparsing = "~=2.4.7"
|
||||||
PyPDF2 = "~=1.27.5"
|
PyPDF2 = "~=1.27.5"
|
||||||
PyPOM = "~=2.2.0"
|
PyPOM = "~=2.2.4"
|
||||||
python-dateutil = "~=2.8.1"
|
python-dateutil = "~=2.8.1"
|
||||||
pytoml = "~=0.1.21"
|
pytoml = "~=0.1.21"
|
||||||
pytz = "~=2020.5"
|
pytz = "~=2020.5"
|
||||||
@@ -82,10 +79,10 @@ django-hcaptcha = "*"
|
|||||||
pikepdf = "*"
|
pikepdf = "*"
|
||||||
django-queryable-properties = "*"
|
django-queryable-properties = "*"
|
||||||
django-mass-edit = "*"
|
django-mass-edit = "*"
|
||||||
|
selenium = "~=3.141.0"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
selenium = "~=3.141.0"
|
pycodestyle = "~=2.9.1"
|
||||||
pycodestyle = "*"
|
|
||||||
coveralls = "*"
|
coveralls = "*"
|
||||||
django-coverage-plugin = "*"
|
django-coverage-plugin = "*"
|
||||||
pytest-cov = "*"
|
pytest-cov = "*"
|
||||||
|
|||||||
1228
Pipfile.lock
generated
1228
Pipfile.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -42,8 +42,9 @@ if not DEBUG:
|
|||||||
|
|
||||||
INTERNAL_IPS = ['127.0.0.1']
|
INTERNAL_IPS = ['127.0.0.1']
|
||||||
|
|
||||||
ADMINS = [('Tom Price', 'tomtom5152@gmail.com'), ('IT Manager', 'it@nottinghamtec.co.uk'),
|
DOMAIN = env('DOMAIN', default='example.com')
|
||||||
('Arona Jones', 'arona.jones@nottinghamtec.co.uk')]
|
|
||||||
|
ADMINS = [('IT Manager', f'it@{DOMAIN}'), ('Arona Jones', f'arona.jones@{DOMAIN}')]
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
ADMINS.append(('Testing Superuser', 'superuser@example.com'))
|
ADMINS.append(('Testing Superuser', 'superuser@example.com'))
|
||||||
|
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ class EventChecklistForm(forms.ModelForm):
|
|||||||
for key in vehicles:
|
for key in vehicles:
|
||||||
pk = int(key.split('_')[1])
|
pk = int(key.split('_')[1])
|
||||||
driver_key = 'driver_' + str(pk)
|
driver_key = 'driver_' + str(pk)
|
||||||
if(self.data[driver_key] == ''):
|
if (self.data[driver_key] == ''):
|
||||||
raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch')
|
raise forms.ValidationError('Add a driver to vehicle ' + str(pk), code='vehicle_mismatch')
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
|||||||
38
RIGS/management/commands/send_reminders.py
Normal file
38
RIGS/management/commands/send_reminders.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import premailer
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.template.loader import get_template
|
||||||
|
from django.contrib.staticfiles import finders
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.core.mail import EmailMultiAlternatives
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from RIGS import models
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Sends email reminders as required. Triggered daily through heroku-scheduler in production.'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
events = models.Event.objects.current_events().select_related('riskassessment')
|
||||||
|
for event in events:
|
||||||
|
earliest_time = event.earliest_time if isinstance(event.earliest_time, datetime.datetime) else timezone.make_aware(datetime.datetime.combine(event.earliest_time, datetime.time(00, 00)))
|
||||||
|
# 48 hours = 172800 seconds
|
||||||
|
if event.is_rig and not event.cancelled and not event.dry_hire and (earliest_time - timezone.now()).total_seconds() <= 172800 and not hasattr(event, 'riskassessment'):
|
||||||
|
context = {
|
||||||
|
"event": event,
|
||||||
|
"url": "https://" + settings.DOMAIN + reverse('event_ra', kwargs={'pk': event.pk})
|
||||||
|
}
|
||||||
|
target = event.mic.email if event.mic else f"productions@{settings.DOMAIN}"
|
||||||
|
msg = EmailMultiAlternatives(
|
||||||
|
f"{event} - Risk Assessment Incomplete",
|
||||||
|
get_template("email/ra_reminder.txt").render(context),
|
||||||
|
to=[target],
|
||||||
|
reply_to=[f"h.s.manager@{settings.DOMAIN}"],
|
||||||
|
)
|
||||||
|
css = finders.find('css/email.css')
|
||||||
|
html = premailer.Premailer(get_template("email/ra_reminder.html").render(context), external_styles=css).transform()
|
||||||
|
msg.attach_alternative(html, 'text/html')
|
||||||
|
msg.send()
|
||||||
@@ -58,13 +58,13 @@ def send_eventauthorisation_success_email(instance):
|
|||||||
|
|
||||||
client_email = EmailMultiAlternatives(
|
client_email = EmailMultiAlternatives(
|
||||||
subject,
|
subject,
|
||||||
get_template("eventauthorisation_client_success.txt").render(context),
|
get_template("email/eventauthorisation_client_success.txt").render(context),
|
||||||
to=[instance.email],
|
to=[instance.email],
|
||||||
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
|
reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS],
|
||||||
)
|
)
|
||||||
|
|
||||||
css = finders.find('css/email.css')
|
css = finders.find('css/email.css')
|
||||||
html = Premailer(get_template("eventauthorisation_client_success.html").render(context),
|
html = Premailer(get_template("email/eventauthorisation_client_success.html").render(context),
|
||||||
external_styles=css).transform()
|
external_styles=css).transform()
|
||||||
client_email.attach_alternative(html, 'text/html')
|
client_email.attach_alternative(html, 'text/html')
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ def send_eventauthorisation_success_email(instance):
|
|||||||
|
|
||||||
mic_email = EmailMessage(
|
mic_email = EmailMessage(
|
||||||
subject,
|
subject,
|
||||||
get_template("eventauthorisation_mic_success.txt").render(context),
|
get_template("email/eventauthorisation_mic_success.txt").render(context),
|
||||||
to=[mic_email_address]
|
to=[mic_email_address]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -117,12 +117,12 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
|
|||||||
|
|
||||||
email = EmailMultiAlternatives(
|
email = EmailMultiAlternatives(
|
||||||
f"{context['number_of_users']} new users awaiting approval on RIGS",
|
f"{context['number_of_users']} new users awaiting approval on RIGS",
|
||||||
get_template("admin_awaiting_approval.txt").render(context),
|
get_template("email/admin_awaiting_approval.txt").render(context),
|
||||||
to=[admin.email],
|
to=[admin.email],
|
||||||
reply_to=[user.email],
|
reply_to=[user.email],
|
||||||
)
|
)
|
||||||
css = finders.find('css/email.css')
|
css = finders.find('css/email.css')
|
||||||
html = Premailer(get_template("admin_awaiting_approval.html").render(context),
|
html = Premailer(get_template("email/admin_awaiting_approval.html").render(context),
|
||||||
external_styles=css).transform()
|
external_styles=css).transform()
|
||||||
email.attach_alternative(html, 'text/html')
|
email.attach_alternative(html, 'text/html')
|
||||||
email.send()
|
email.send()
|
||||||
|
|||||||
5
RIGS/templates/email/eventauthorisation_mic_success.txt
Normal file
5
RIGS/templates/email/eventauthorisation_mic_success.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}},
|
||||||
|
|
||||||
|
Just to let you know your event N{{object.eventdisplay_id}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.event.last_edited_at}}.
|
||||||
|
|
||||||
|
The TEC Rig Information Gathering System
|
||||||
16
RIGS/templates/email/ra_reminder.html
Normal file
16
RIGS/templates/email/ra_reminder.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{% extends 'base_client_email.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>Hi {{event.mic.get_full_name|default_if_none:"Productions Manager"}},</p>
|
||||||
|
|
||||||
|
{% if event.mic %}
|
||||||
|
<p>Just to let you know your event {{event.display_id}} <em>requires<em> a pre-event risk assessment completing prior to the event. Please do so as soon as possible.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>This is a reminder that event {{event.display_id}} requires a MIC assigning and a risk assessment completing.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<p>Fill it out here:</p>
|
||||||
|
<a href="{{url}}" class="btn btn-info"><span class="fas fa-paperclip"></span> Create Risk Assessment</a>
|
||||||
|
|
||||||
|
<p>TEC PA & Lighting</p>
|
||||||
|
{% endblock %}
|
||||||
9
RIGS/templates/email/ra_reminder.txt
Normal file
9
RIGS/templates/email/ra_reminder.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Hi {{event.mic.get_full_name|default_if_none:"Productions Manager"}},
|
||||||
|
|
||||||
|
{% if event.mic %}
|
||||||
|
Just to let you know your event {{event.display_id}} requires a risk assessment completing prior to the event. Please do so as soon as possible.
|
||||||
|
{% else %}
|
||||||
|
This is a reminder that event {{event.display_id}} requires a MIC assigning and a risk assessment completing.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
The TEC Rig Information Gathering System
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}},
|
|
||||||
|
|
||||||
Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.event.last_edited_at}}.
|
|
||||||
|
|
||||||
The TEC Rig Information Gathering System
|
|
||||||
@@ -118,9 +118,9 @@ def orderby(request, field, attr):
|
|||||||
@register.filter(needs_autoescape=True) # Used for accessing outside of a form, i.e. in detail views of RiskAssessment and EventChecklist
|
@register.filter(needs_autoescape=True) # Used for accessing outside of a form, i.e. in detail views of RiskAssessment and EventChecklist
|
||||||
def get_field(obj, field, autoescape=True):
|
def get_field(obj, field, autoescape=True):
|
||||||
value = getattr(obj, field)
|
value = getattr(obj, field)
|
||||||
if(isinstance(value, bool)):
|
if (isinstance(value, bool)):
|
||||||
value = yesnoi(value, field in obj.inverted_fields)
|
value = yesnoi(value, field in obj.inverted_fields)
|
||||||
elif(isinstance(value, str)):
|
elif (isinstance(value, str)):
|
||||||
value = truncatewords(value, 20)
|
value = truncatewords(value, 20)
|
||||||
return mark_safe(value)
|
return mark_safe(value)
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ def get_list(dictionary, key):
|
|||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def profile_by_index(value):
|
def profile_by_index(value):
|
||||||
if(value):
|
if (value):
|
||||||
return models.Profile.objects.get(pk=int(value))
|
return models.Profile.objects.get(pk=int(value))
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
path('event/<int:pk>/ra/', permission_required_with_403('RIGS.add_riskassessment')(views.EventRiskAssessmentCreate.as_view()),
|
path('event/<int:pk>/ra/', permission_required_with_403('RIGS.add_riskassessment')(views.EventRiskAssessmentCreate.as_view()),
|
||||||
name='event_ra'),
|
name='event_ra'),
|
||||||
path('event/ra/<int:pk>/', permission_required_with_403('RIGS.view_riskassessment')(views.EventRiskAssessmentDetail.as_view()),
|
path('event/ra/<int:pk>/', login_required(views.EventRiskAssessmentDetail.as_view()),
|
||||||
name='ra_detail'),
|
name='ra_detail'),
|
||||||
path('event/ra/<int:pk>/edit/', permission_required_with_403('RIGS.change_riskassessment')(views.EventRiskAssessmentEdit.as_view()),
|
path('event/ra/<int:pk>/edit/', permission_required_with_403('RIGS.change_riskassessment')(views.EventRiskAssessmentEdit.as_view()),
|
||||||
name='ra_edit'),
|
name='ra_edit'),
|
||||||
@@ -87,7 +87,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
path('event/<int:pk>/checklist/', permission_required_with_403('RIGS.add_eventchecklist')(views.EventChecklistCreate.as_view()),
|
path('event/<int:pk>/checklist/', permission_required_with_403('RIGS.add_eventchecklist')(views.EventChecklistCreate.as_view()),
|
||||||
name='event_ec'),
|
name='event_ec'),
|
||||||
path('event/checklist/<int:pk>/', permission_required_with_403('RIGS.view_eventchecklist')(views.EventChecklistDetail.as_view()),
|
path('event/checklist/<int:pk>/', login_required(views.EventChecklistDetail.as_view()),
|
||||||
name='ec_detail'),
|
name='ec_detail'),
|
||||||
path('event/checklist/<int:pk>/edit/', permission_required_with_403('RIGS.change_eventchecklist')(views.EventChecklistEdit.as_view()),
|
path('event/checklist/<int:pk>/edit/', permission_required_with_403('RIGS.change_eventchecklist')(views.EventChecklistEdit.as_view()),
|
||||||
name='ec_edit'),
|
name='ec_edit'),
|
||||||
|
|||||||
@@ -342,12 +342,12 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
|||||||
|
|
||||||
msg = EmailMultiAlternatives(
|
msg = EmailMultiAlternatives(
|
||||||
f"{self.object.display_id} | {self.object.name} - Event Authorisation Request",
|
f"{self.object.display_id} | {self.object.name} - Event Authorisation Request",
|
||||||
get_template("eventauthorisation_client_request.txt").render(context),
|
get_template("email/eventauthorisation_client_request.txt").render(context),
|
||||||
to=[email],
|
to=[email],
|
||||||
reply_to=[self.request.user.email],
|
reply_to=[self.request.user.email],
|
||||||
)
|
)
|
||||||
css = finders.find('css/email.css')
|
css = finders.find('css/email.css')
|
||||||
html = premailer.Premailer(get_template("eventauthorisation_client_request.html").render(context),
|
html = premailer.Premailer(get_template("email/eventauthorisation_client_request.html").render(context),
|
||||||
external_styles=css).transform()
|
external_styles=css).transform()
|
||||||
msg.attach_alternative(html, 'text/html')
|
msg.attach_alternative(html, 'text/html')
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
|
|||||||
|
|
||||||
|
|
||||||
class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
class EventAuthoriseRequestEmailPreview(generic.DetailView):
|
||||||
template_name = "eventauthorisation_client_request.html"
|
template_name = "email/eventauthorisation_client_request.html"
|
||||||
model = models.Event
|
model = models.Event
|
||||||
|
|
||||||
def render_to_response(self, context, **response_kwargs):
|
def render_to_response(self, context, **response_kwargs):
|
||||||
|
|||||||
18
assets/migrations/0027_asset_nickname.py
Normal file
18
assets/migrations/0027_asset_nickname.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2022-12-11 00:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0026_auto_20220526_1623'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='nickname',
|
||||||
|
field=models.CharField(blank=True, max_length=120),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -95,7 +95,7 @@ class AssetManager(models.Manager):
|
|||||||
def search(self, query=None):
|
def search(self, query=None):
|
||||||
qs = self.get_queryset()
|
qs = self.get_queryset()
|
||||||
if query is not None:
|
if query is not None:
|
||||||
or_lookup = (Q(asset_id__exact=query.upper()) | Q(description__icontains=query) | Q(serial_number__exact=query))
|
or_lookup = (Q(asset_id__exact=query.upper()) | Q(description__icontains=query) | Q(serial_number__exact=query) | Q(nickname__icontains=query))
|
||||||
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
@@ -125,6 +125,7 @@ class Asset(models.Model, RevisionMixin):
|
|||||||
purchase_price = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10, validators=[validate_positive])
|
purchase_price = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10, validators=[validate_positive])
|
||||||
replacement_cost = models.DecimalField(null=True, decimal_places=2, max_digits=10, validators=[validate_positive])
|
replacement_cost = models.DecimalField(null=True, decimal_places=2, max_digits=10, validators=[validate_positive])
|
||||||
comments = models.TextField(blank=True)
|
comments = models.TextField(blank=True)
|
||||||
|
nickname = models.CharField(max_length=120, blank=True)
|
||||||
|
|
||||||
# Audit
|
# Audit
|
||||||
last_audited_at = models.DateTimeField(blank=True, null=True)
|
last_audited_at = models.DateTimeField(blank=True, null=True)
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
<label for="{{ form.description.id_for_label }}">Description</label>
|
<label for="{{ form.description.id_for_label }}">Description</label>
|
||||||
{% render_field form.description|add_class:'form-control' value=object.description %}
|
{% render_field form.description|add_class:'form-control' value=object.description %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ form.nickname.id_for_label }}">Nickname</label>
|
||||||
|
{% render_field form.nickname|add_class:'form-control' value=object.nickname %}
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.category.id_for_label }}" >Category</label>
|
<label for="{{ form.category.id_for_label }}" >Category</label>
|
||||||
{% render_field form.category|add_class:'form-control'%}
|
{% render_field form.category|add_class:'form-control'%}
|
||||||
@@ -45,7 +49,10 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<dt>Asset ID</dt>
|
<dt>Asset ID</dt>
|
||||||
<dd>{{ object.asset_id }}</dd>
|
<dd>{{ object.asset_id }}</dd>
|
||||||
|
{% if object.nickname %}
|
||||||
|
<dt>Nickname</dt>
|
||||||
|
<dd>"{{ object.nickname }}"</dd>
|
||||||
|
{% endif %}
|
||||||
<dt>Description</dt>
|
<dt>Description</dt>
|
||||||
<dd>{{ object.description }}</dd>
|
<dd>{{ object.description }}</dd>
|
||||||
|
|
||||||
|
|||||||
7080
package-lock.json
generated
7080
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -28,13 +28,13 @@
|
|||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"konami": "^1.6.3",
|
"konami": "^1.6.3",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"node-sass": "^7.0.0",
|
"node-sass": "^7.0.3",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.5",
|
||||||
"uglify-js": "^3.14.5"
|
"uglify-js": "^3.14.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"browser-sync": "^2.27.7"
|
"browser-sync": "^2.27.10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"gulp": "gulp",
|
"gulp": "gulp",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.urls import path
|
|||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from training.decorators import is_supervisor
|
from training.decorators import is_supervisor
|
||||||
from PyRIGS.decorators import permission_required_with_403
|
|
||||||
|
|
||||||
from training import views, models
|
from training import views, models
|
||||||
from versioning.views import VersionHistory
|
from versioning.views import VersionHistory
|
||||||
@@ -12,10 +11,9 @@ urlpatterns = [
|
|||||||
path('item/<int:pk>/qualified_users/', login_required(views.ItemQualifications.as_view()), name='item_qualification'),
|
path('item/<int:pk>/qualified_users/', login_required(views.ItemQualifications.as_view()), name='item_qualification'),
|
||||||
|
|
||||||
path('trainee/list/', login_required(views.TraineeList.as_view()), name='trainee_list'),
|
path('trainee/list/', login_required(views.TraineeList.as_view()), name='trainee_list'),
|
||||||
path('trainee/<int:pk>/',
|
path('trainee/<int:pk>/', login_required(views.TraineeDetail.as_view()),
|
||||||
permission_required_with_403('RIGS.view_profile')(views.TraineeDetail.as_view()),
|
|
||||||
name='trainee_detail'),
|
name='trainee_detail'),
|
||||||
path('trainee/<int:pk>/history', permission_required_with_403('RIGS.view_profile')(VersionHistory.as_view()), name='trainee_history', kwargs={'model': models.Trainee, 'app': 'training'}), # Not picked up automatically because proxy model (I think)
|
path('trainee/<int:pk>/history', login_required(VersionHistory.as_view()), name='trainee_history', kwargs={'model': models.Trainee, 'app': 'training'}), # Not picked up automatically because proxy model (I think)
|
||||||
path('trainee/<int:pk>/add_qualification/', is_supervisor()(views.AddQualification.as_view()),
|
path('trainee/<int:pk>/add_qualification/', is_supervisor()(views.AddQualification.as_view()),
|
||||||
name='add_qualification'),
|
name='add_qualification'),
|
||||||
path('trainee/edit_qualification/<int:pk>/', is_supervisor()(views.EditQualification.as_view()),
|
path('trainee/edit_qualification/<int:pk>/', is_supervisor()(views.EditQualification.as_view()),
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ class ModelComparison:
|
|||||||
def name(self):
|
def name(self):
|
||||||
obj = self.new if self.new else self.old
|
obj = self.new if self.new else self.old
|
||||||
|
|
||||||
if(hasattr(obj, 'activity_feed_string')):
|
if (hasattr(obj, 'activity_feed_string')):
|
||||||
return obj.activity_feed_string
|
return obj.activity_feed_string
|
||||||
else:
|
else:
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
|||||||
Reference in New Issue
Block a user