Compare commits

..

3 Commits

Author SHA1 Message Date
James Herbert
6fd8f17094 Merge pull request #629 from jamesatjaminit/master 2025-10-06 21:28:54 +01:00
James Cook
7f6b15c154 Merge branch 'master' of github.com:jamesatjaminit/PyRIGS 2025-10-06 21:18:07 +01:00
James Cook
bb84bbab77 remove async from select.js script 2025-10-06 21:17:55 +01:00
44 changed files with 552 additions and 745 deletions

View File

@@ -1,49 +0,0 @@
*.sqlite3
*.md
**/tests
conftest.py
pytest.ini
Dockerfile
node_modules
npm-debug.log
.git
.env
__pycache__
*.pyc
*.pyo
*.pyd
.Python
pip-log.txt
pip-delete-this-directory.txt
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.log
.git
.gitignore
.mypy_cache
.pytest_cache
.hypothesis
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.vscode
.idea
.DS_Store
*.swp
*.swo
*~
docs/
tests/
*.md
docker-compose*.yml
Dockerfile*
.dockerignore

1
.gitignore vendored
View File

@@ -104,4 +104,3 @@ screenshots/
# Virutal Environments # Virutal Environments
.venv/ .venv/
/.env

6
.slugignore Normal file
View File

@@ -0,0 +1,6 @@
*.sqlite3
*.md
**/tests
conftest.py
pytest.ini
Dockerfile

View File

@@ -1,41 +0,0 @@
# Stage 1: Base build stage
FROM combos/python_node:3.10_22 AS base
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
FROM base AS builder
# Set up environment
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy
# Create non-root user
RUN addgroup --system app && adduser --system --group app
WORKDIR /app
# Copy uv project files first (for better caching)
COPY pyproject.toml uv.lock ./
WORKDIR /app
# Install the project's dependencies using the lockfile and settings
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project --all-groups
# Then, add the rest of the project source code and install it
# Installing separately from its dependencies allows optimal layer caching
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --all-groups
FROM python:3.10-slim-trixie
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
COPY --from=builder /app /app
WORKDIR /app
ENV PATH="/app/.venv/bin:$PATH"
EXPOSE 8000
CMD ["uv", "run", "gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "PyRIGS.wsgi"]

2
Procfile Normal file
View File

@@ -0,0 +1,2 @@
release: python manage.py migrate
web: gunicorn PyRIGS.wsgi --log-file -

View File

@@ -26,23 +26,21 @@ DEBUG = env('DEBUG', cast=bool, default=True)
STAGING = env('STAGING', cast=bool, default=False) STAGING = env('STAGING', cast=bool, default=False)
CI = env('CI', cast=bool, default=False) CI = env('CI', cast=bool, default=False)
ALLOWED_HOSTS = env("DJANGO_ALLOWED_HOSTS", default="rigs.nottinghamtec.co.uk").split(",") ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
if STAGING:
ALLOWED_HOSTS.append('.herokuapp.com')
if DEBUG: if DEBUG:
CRSF_TRUSTED_ORIGINS = ALLOWED_HOSTS.copy() ALLOWED_HOSTS.append('localhost')
CRSF_TRUSTED_ORIGINS.append("http://localhost:8000") ALLOWED_HOSTS.append('example.com')
CRSF_TRUSTED_ORIGINS.append("http://localhost:8001") ALLOWED_HOSTS.append('127.0.0.1')
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS.append('.app.github.dev')
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
if not DEBUG: if not DEBUG:
SECURE_SSL_REDIRECT = True # Redirect all http requests to https SECURE_SSL_REDIRECT = True # Redirect all http requests to https
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
INTERNAL_IPS = ['127.0.0.1'] INTERNAL_IPS = ['127.0.0.1']
@@ -97,18 +95,20 @@ WSGI_APPLICATION = 'PyRIGS.wsgi.application'
# Database # Database
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.{}'.format( 'ENGINE': 'django.db.backends.sqlite3',
env('DATABASE_ENGINE', default='sqlite3') 'NAME': str(BASE_DIR / 'db.sqlite3'),
), }
'NAME': env('DATABASE_NAME', default='rigs'),
'USER': env('DATABASE_USERNAME', default='rigs'),
'PASSWORD': env('DATABASE_PASSWORD', default='rigs'),
'HOST': env('DATABASE_HOST', default='127.0.0.1'),
'PORT': env('DATABASE_PORT', 5432),
}
} }
if not DEBUG:
import dj_database_url
if env("FRANKENRIGS_DATABASE_URL") is not None:
DATABASES['default'] = dj_database_url.config(env="FRANKENRIGS_DATABASE_URL")
else:
DATABASES['default'] = dj_database_url.config()
# Logging # Logging
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
@@ -257,7 +257,6 @@ TEMPLATES = [
"django.template.context_processors.tz", "django.template.context_processors.tz",
"django.template.context_processors.request", "django.template.context_processors.request",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
"RIGS.views.is_ajax",
], ],
'debug': DEBUG 'debug': DEBUG
}, },
@@ -270,3 +269,10 @@ TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf"
AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk' AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' 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

View File

@@ -36,8 +36,8 @@ urlpatterns = [
if settings.DEBUG: if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns() urlpatterns += staticfiles_urlpatterns()
# import debug_toolbar import debug_toolbar
urlpatterns += [ urlpatterns += [
# path('__debug__/', include(debug_toolbar.urls)), path('__debug__/', include(debug_toolbar.urls)),
path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")), path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")),
] ]

View File

@@ -9,7 +9,7 @@ from functools import reduce
from itertools import chain from itertools import chain
from io import BytesIO from io import BytesIO
from PyPDF2 import PdfMerger, PdfReader from PyPDF2 import PdfFileMerger, PdfFileReader
from z3c.rml import rml2pdf from z3c.rml import rml2pdf
from django.conf import settings from django.conf import settings
@@ -30,11 +30,9 @@ from RIGS import models
from assets import models as asset_models from assets import models as asset_models
from training import models as training_models from training import models as training_models
# Template context processor
def is_ajax(request): def is_ajax(request):
return {"is_ajax": request.headers.get('x-requested-with') == 'XMLHttpRequest'} return request.headers.get('x-requested-with') == 'XMLHttpRequest'
def get_related(form, context): # Get some other objects to include in the form. Used when there are errors but also nice and quick. def get_related(form, context): # Get some other objects to include in the form. Used when there are errors but also nice and quick.
@@ -185,7 +183,7 @@ class SecureAPIRequest(generic.View):
class ModalURLMixin: class ModalURLMixin:
def get_close_url(self, update, detail): def get_close_url(self, update, detail):
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
url = reverse_lazy('closemodal') url = reverse_lazy('closemodal')
update_url = str(reverse_lazy(update, kwargs={'pk': self.object.pk})) 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=" + serializers.serialize("json", [self.object]))
@@ -204,7 +202,7 @@ class GenericListView(generic.ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['page_title'] = self.model.__name__ + "s" context['page_title'] = self.model.__name__ + "s"
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
return context return context
@@ -223,7 +221,7 @@ class GenericDetailView(generic.DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['page_title'] = f"{self.model.__name__} | {self.object.name}" context['page_title'] = f"{self.model.__name__} | {self.object.name}"
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
return context return context
@@ -234,7 +232,7 @@ class GenericUpdateView(generic.UpdateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['page_title'] = f"Edit {self.model.__name__}" context['page_title'] = f"Edit {self.model.__name__}"
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
return context return context
@@ -245,7 +243,7 @@ class GenericCreateView(generic.CreateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['page_title'] = f"Create {self.model.__name__}" context['page_title'] = f"Create {self.model.__name__}"
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
return context return context
@@ -335,10 +333,10 @@ def get_info_string(user):
def render_pdf_response(template, context, append_terms): def render_pdf_response(template, context, append_terms):
merger = PdfMerger() merger = PdfFileMerger()
rml = template.render(context) rml = template.render(context)
buffer = rml2pdf.parseString(rml) buffer = rml2pdf.parseString(rml)
merger.append(PdfReader(buffer)) merger.append(PdfFileReader(buffer))
buffer.close() buffer.close()
if append_terms: if append_terms:

View File

@@ -39,8 +39,6 @@ class EventForm(forms.ModelForm):
@property @property
def _get_items_json(self): def _get_items_json(self):
items = {} items = {}
if self.instance.pk is None:
return items
for item in self.instance.items.all(): for item in self.instance.items.all():
data = serializers.serialize('json', [item]) data = serializers.serialize('json', [item])
struct = simplejson.loads(data) struct = simplejson.loads(data)

View File

@@ -13,7 +13,6 @@ from RIGS import models
class Command(BaseCommand): class Command(BaseCommand):
# FIXME This needs a different implementation when moved off heroku
help = 'Sends email reminders as required. Triggered daily through heroku-scheduler in production.' help = 'Sends email reminders as required. Triggered daily through heroku-scheduler in production.'
def handle(self, *args, **options): def handle(self, *args, **options):
@@ -34,6 +33,6 @@ class Command(BaseCommand):
reply_to=[f"h.s.manager@{settings.DOMAIN}"], reply_to=[f"h.s.manager@{settings.DOMAIN}"],
) )
css = finders.find('css/email.css') css = finders.find('css/email.css')
html = premailer.Premailer(get_template("email/ra_reminder.html").render(context), external_styles=css, allow_loading_external_files=True).transform() html = premailer.Premailer(get_template("email/ra_reminder.html").render(context), external_styles=css).transform()
msg.attach_alternative(html, 'text/html') msg.attach_alternative(html, 'text/html')
msg.send() msg.send()

View File

@@ -5,7 +5,7 @@ import urllib.request
from io import BytesIO from io import BytesIO
import datetime import datetime
from PyPDF2 import PdfReader, PdfMerger from PyPDF2 import PdfFileReader, PdfFileMerger
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
from django.core.cache import cache from django.core.cache import cache
@@ -31,12 +31,12 @@ def send_eventauthorisation_success_email(instance):
} }
template = get_template('event_print.xml') template = get_template('event_print.xml')
merger = PdfMerger() merger = PdfFileMerger()
rml = template.render(context) rml = template.render(context)
buffer = rml2pdf.parseString(rml) buffer = rml2pdf.parseString(rml)
merger.append(PdfReader(buffer)) merger.append(PdfFileReader(buffer))
buffer.close() buffer.close()
terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL) terms = urllib.request.urlopen(settings.TERMS_OF_HIRE_URL)
@@ -66,7 +66,7 @@ def send_eventauthorisation_success_email(instance):
css = finders.find('css/email.css') css = finders.find('css/email.css')
html = Premailer(get_template("email/eventauthorisation_client_success.html").render(context), html = Premailer(get_template("email/eventauthorisation_client_success.html").render(context),
external_styles=css, allow_loading_external_files=True).transform() external_styles=css).transform()
client_email.attach_alternative(html, 'text/html') client_email.attach_alternative(html, 'text/html')
escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', instance.event.name) escapedEventName = re.sub(r'[^a-zA-Z0-9 \n\.]', '', instance.event.name)
@@ -124,7 +124,7 @@ def send_admin_awaiting_approval_email(user, request, **kwargs):
) )
css = finders.find('css/email.css') css = finders.find('css/email.css')
html = Premailer(get_template("email/admin_awaiting_approval.html").render(context), html = Premailer(get_template("email/admin_awaiting_approval.html").render(context),
external_styles=css, allow_loading_external_files=True).transform() external_styles=css).transform()
email.attach_alternative(html, 'text/html') email.attach_alternative(html, 'text/html')
email.send() email.send()

View File

@@ -1,5 +1,5 @@
Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}}, 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}}. 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 The TEC Rig Information Gathering System

View File

@@ -11,7 +11,7 @@
{% block preload_js %} {% block preload_js %}
{{ block.super }} {{ block.super }}
<script src="{% static 'js/selects.js' %}" async></script> <script src="{% static 'js/selects.js' %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load markdown_tags %} {% load markdown_tags %}
{% load static %} {% load static %}
@@ -18,7 +18,7 @@
{% block content %} {% block content %}
<div class="row my-3 py-3"> <div class="row my-3 py-3">
{% if not is_ajax %} {% if not request.is_ajax %}
{% if perms.RIGS.view_event %} {% if perms.RIGS.view_event %}
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'partials/event_detail_buttons.html' %} {% include 'partials/event_detail_buttons.html' %}
@@ -49,7 +49,7 @@
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if not is_ajax and perms.RIGS.view_event %} {% if not request.is_ajax and perms.RIGS.view_event %}
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'partials/event_detail_buttons.html' %} {% include 'partials/event_detail_buttons.html' %}
</div> </div>
@@ -69,16 +69,16 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'partials/crew_list.html' %} {% include 'partials/crew_list.html' %}
{% if not is_ajax and perms.RIGS.view_event %} {% if not request.is_ajax and perms.RIGS.view_event %}
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'partials/event_detail_buttons.html' %} {% include 'partials/event_detail_buttons.html' %}
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if not is_ajax and perms.RIGS.view_event %} {% if not request.is_ajax and perms.RIGS.view_event %}
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% include 'partials/last_edited.html' with target="event_history" %} {% include 'partials/last_edited.html' with target="event_history" %}
</div> </div>
@@ -86,7 +86,7 @@
</div> </div>
{% endblock %} {% endblock %}
{% if is_ajax %} {% if request.is_ajax %}
{% block footer %} {% block footer %}
{% if perms.RIGS.view_event %} {% if perms.RIGS.view_event %}
{% include 'partials/last_edited.html' with target="event_history" %} {% include 'partials/last_edited.html' with target="event_history" %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}
{% load button from filters %} {% load button from filters %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% block title %}TEC Email Address Required{% endblock %} {% block title %}TEC Email Address Required{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load help_text from filters %} {% load help_text from filters %}
{% load profile_by_index from filters %} {% load profile_by_index from filters %}
{% load yesnoi from filters %} {% load yesnoi from filters %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}
{% load help_text from filters %} {% load help_text from filters %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}
{% load button from filters %} {% load button from filters %}
@@ -28,7 +28,7 @@
<form id="checkin" role="form" method="POST" action="{{ form.action|default:request.path }}"> <form id="checkin" role="form" method="POST" action="{{ form.action|default:request.path }}">
<input type="hidden" name="{{ form.event.name }}" id="{{ form.event.id_for_label }}" <input type="hidden" name="{{ form.event.name }}" id="{{ form.event.id_for_label }}"
value="{{event.pk}}"/> value="{{event.pk}}"/>
{% if not is_ajax and self.request.user.pk is form.event.mic.pk %} {% if not request.is_ajax and self.request.user.pk is form.event.mic.pk %}
<div class="form-group"> <div class="form-group">
<label for="{{ form.person.id_for_label }}" <label for="{{ form.person.id_for_label }}"
class="col-sm-4 col-form-label">{{ form.person.label }}</label> class="col-sm-4 col-form-label">{{ form.person.label }}</label>
@@ -86,7 +86,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if not is_ajax %} {% if not request.is_ajax %}
<div class="row mt-3"> <div class="row mt-3">
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% button 'submit' %} {% button 'submit' %}

View File

@@ -1,59 +0,0 @@
{% extends 'base_rigs.html' %}
{% load paginator from filters %}
{% load help_text from filters %}
{% load verbose_name from filters %}
{% load get_field from filters %}
{% block title %}{{ title }} List{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<h2>{{title}} List</h2>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="table-responsive">
<table class="table mb-0 table-sm">
<thead>
<tr>
<th scope="col">Event</th>
{# mmm hax #}
{% if object_list.0 != None %}
{% for field in object_list.0.fieldz %}
<th scope="col">{{ object_list.0|verbose_name:field|title }}</th>
{% endfor %}
{% endif %}
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for object in object_list %}
<tr class="{% if object.reviewed_by %}table-success{%endif%}">
{# General #}
<th scope="row"><a href="{% url 'event_detail' object.event.pk %}">{{ object.event }}</a><br><small>{{ object.event.get_status_display }}</small></th>
{% for field in object_list.0.fieldz %}
<td>{{ object|get_field:field }}</td>
{% endfor %}
{# Buttons #}
<td>
{% include 'partials/hs_status.html' %}
</td>
</tr>
{% empty %}
<tr class="bg-warning">
<td colspan="6">Nothing found</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% if is_paginated %}
<div class="row justify-content-center">
{% paginator %}
</div>
{% endif %}
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load help_text from filters %} {% load help_text from filters %}
{% load profile_by_index from filters %} {% load profile_by_index from filters %}
{% load yesnoi from filters %} {% load yesnoi from filters %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}
{% load help_text from filters %} {% load help_text from filters %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load filters %} {% load filters %}
{% block content %} {% block content %}

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}
{% load help_text from filters %} {% load help_text from filters %}
@@ -9,7 +9,7 @@
{% endblock %} {% endblock %}
{% block preload_js %} {% block preload_js %}
<script src="{% static 'js/selects.js' %}" async></script> <script src="{% static 'js/selects.js' %}"></script>
{% endblock %} {% endblock %}
{% block js %} {% block js %}

View File

@@ -1,7 +1,7 @@
<div class="row"> <div class="row">
<label for="{{ formitem.0.id_for_label }}" <label for="{{ formitem.id_for_label }}"
class="col-8 control-label">{{ formitem.help_text|safe }}</label> class="col-8 control-label">{{ formitem.help_text|safe }}</label>
<div class="col-4 pb-3" id="{{ formitem.0.id_for_label|slice:'-2' }}"> <div class="col-4 pb-3" id="{{ formitem.id_for_label|slice:'-2' }}">
{% for radio in formitem %} {% for radio in formitem %}
<div class="custom-control custom-radio"> <div class="custom-control custom-radio">
{{ radio.tag }} {{ radio.tag }}

View File

@@ -360,7 +360,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix
) )
css = finders.find('css/email.css') css = finders.find('css/email.css')
html = premailer.Premailer(get_template("email/eventauthorisation_client_request.html").render(context), html = premailer.Premailer(get_template("email/eventauthorisation_client_request.html").render(context),
external_styles=css, allow_loading_external_files=True).transform() external_styles=css).transform()
msg.attach_alternative(html, 'text/html') msg.attach_alternative(html, 'text/html')
msg.send() msg.send()
@@ -376,7 +376,7 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView):
css = finders.find('css/email.css') css = finders.find('css/email.css')
response = super().render_to_response(context, **response_kwargs) response = super().render_to_response(context, **response_kwargs)
assert isinstance(response, HttpResponse) assert isinstance(response, HttpResponse)
response.content = premailer.Premailer(response.rendered_content, external_styles=css, allow_loading_external_files=True).transform() response.content = premailer.Premailer(response.rendered_content, external_styles=css).transform()
return response return response
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:'base_ajax.html,base_assets.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_assets.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% block content %} {% block content %}
@@ -79,7 +79,7 @@
</div> </div>
</div> </div>
</div> </div>
{% if not is_ajax %} {% if not request.is_ajax %}
<div class="form-group form-row pull-right"> <div class="form-group form-row pull-right">
<button class="btn btn-success" type="submit" form="asset_audit_form" id="id_mark_audited">Mark Audited</button> <button class="btn btn-success" type="submit" form="asset_audit_form" id="id_mark_audited">Mark Audited</button>
</div> </div>

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:"base_ajax.html,base_assets.html" %} {% extends 'base_assets.html' %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% load static %} {% load static %}

View File

@@ -52,10 +52,3 @@ def test_asset_2(db, category, test_status_2):
asset, created = models.Asset.objects.get_or_create(asset_id="10", description="Working Mic", status=test_status_2, category=category, date_acquired=datetime.date(2001, 10, 20), replacement_cost=1000) asset, created = models.Asset.objects.get_or_create(asset_id="10", description="Working Mic", status=test_status_2, category=category, date_acquired=datetime.date(2001, 10, 20), replacement_cost=1000)
yield asset yield asset
asset.delete() asset.delete()
@pytest.fixture
def test_supplier(db):
supplier, created = models.Supplier.objects.get_or_create(name="Fullmetal Heavy Industry")
yield supplier
supplier.delete()

View File

@@ -7,12 +7,13 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from PyRIGS.tests.base import AutoLoginTest, assert_times_almost_equal from PyRIGS.tests.base import AutoLoginTest, screenshot_failure_cls, assert_times_almost_equal
from PyRIGS.tests.pages import animation_is_finished from PyRIGS.tests.pages import animation_is_finished
from assets import models from assets import models
from . import pages from . import pages
@screenshot_failure_cls
class TestAssetList(AutoLoginTest): class TestAssetList(AutoLoginTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@@ -143,6 +144,7 @@ def test_asset_duplicate(logged_in_browser, admin_user, live_server, test_asset)
assert models.Asset.objects.last().description == test_asset.description assert models.Asset.objects.last().description == test_asset.description
@screenshot_failure_cls
class TestAssetForm(AutoLoginTest): class TestAssetForm(AutoLoginTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@@ -209,6 +211,7 @@ class TestAssetForm(AutoLoginTest):
self.assertEqual(asset.date_acquired, acquired) self.assertEqual(asset.date_acquired, acquired)
@screenshot_failure_cls
class TestSupplierList(AutoLoginTest): class TestSupplierList(AutoLoginTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@@ -244,32 +247,37 @@ class TestSupplierList(AutoLoginTest):
time.sleep(1) time.sleep(1)
self.assertTrue(len(self.page.suppliers) == 7) self.assertTrue(len(self.page.suppliers) == 7)
self.page.set_query("NOTFOUND") self.page.set_query("This is not a supplier")
self.page.search() self.page.search()
self.assertTrue(len(self.page.suppliers) == 0) self.assertTrue(len(self.page.suppliers) == 0)
def test_supplier_create(logged_in_browser, live_server): @screenshot_failure_cls
page = pages.SupplierCreate(logged_in_browser.driver, live_server.url).open() class TestSupplierCreateAndEdit(AutoLoginTest):
def setUp(self):
super().setUp()
self.supplier = models.Supplier.objects.create(name="Fullmetal Heavy Industry")
page.remove_all_required() def test_supplier_create(self):
page.submit() self.page = pages.SupplierCreate(self.driver, self.live_server_url).open()
assert !self.page.success
assert "This field is required." in self.page.errors["Name"]
page.name = "Optican Health Supplies" self.page.remove_all_required()
page.submit() self.page.submit()
assert page.success self.assertFalse(self.page.success)
self.assertIn("This field is required.", self.page.errors["Name"])
self.page.name = "Optican Health Supplies"
self.page.submit()
self.assertTrue(self.page.success)
def test_supplier_edit(logged_in_browser, live_server, test_supplier): def test_supplier_edit(self):
page = pages.SupplierEdit(logged_in_browser.driver, live_server.url, supplier_id=test_supplier.pk).open() self.page = pages.SupplierEdit(self.driver, self.live_server_url, supplier_id=self.supplier.pk).open()
assert test_supplier.name == page.name self.assertEqual("Fullmetal Heavy Industry", self.page.name)
new_name = "Cyberdyne Systems" new_name = "Cyberdyne Systems"
page.name = new_name self.page.name = new_name
page.submit() self.page.submit()
assert page.success self.assertTrue(self.page.success)
def test_audit_search(logged_in_browser, live_server, test_asset): def test_audit_search(logged_in_browser, live_server, test_asset):
@@ -304,30 +312,47 @@ def test_audit_success(logged_in_browser, admin_user, live_server, test_asset):
assert test_asset.asset_id not in page.assets assert test_asset.asset_id not in page.assets
def test_audit_fail(logged_in_browser, admin_user, live_server, test_asset): @screenshot_failure_cls
page = pages.AssetAuditList(logged_in_browser.driver, live_server.url).open() class TestAssetAudit(AutoLoginTest):
wait = WebDriverWait(logged_in_browser.driver, 20) def setUp(self):
page.set_query(test_asset.asset_id) super().setUp()
page.search() self.category = models.AssetCategory.objects.create(name="Haulage")
wait.until(ec.visibility_of_element_located((By.ID, 'modal'))) self.status = models.AssetStatus.objects.create(name="Probably Fine", should_show=True)
# Do it wrong on purpose to check error display self.supplier = models.Supplier.objects.create(name="The Bazaar")
page.modal.remove_all_required() self.connector = models.Connector.objects.create(description="Trailer Socket", current_rating=1,
page.modal.description = "" voltage_rating=40, num_pins=13)
page.modal.submit() models.Asset.objects.create(asset_id="1", description="Trailer Cable", status=self.status,
wait.until(animation_is_finished()) category=self.category, date_acquired=datetime.date(2020, 2, 1), replacement_cost=10)
assert "This field is required." in self.page.modal.errors["Description"] models.Asset.objects.create(asset_id="11", description="Trailerboard", status=self.status,
category=self.category, date_acquired=datetime.date(2020, 2, 1), replacement_cost=10)
models.Asset.objects.create(asset_id="111", description="Erms", status=self.status, category=self.category,
date_acquired=datetime.date(2020, 2, 1), replacement_cost=10)
self.asset = models.Asset.objects.create(asset_id="1111", description="A hammer", status=self.status,
category=self.category,
date_acquired=datetime.date(2020, 2, 1), replacement_cost=10)
self.page = pages.AssetAuditList(self.driver, self.live_server_url).open()
self.wait = WebDriverWait(self.driver, 20)
def test_audit_fail(self):
self.page.set_query(self.asset.asset_id)
self.page.search()
self.wait.until(ec.visibility_of_element_located((By.ID, 'modal')))
# Do it wrong on purpose to check error display
self.page.modal.remove_all_required()
self.page.modal.description = ""
self.page.modal.submit()
self.wait.until(animation_is_finished())
self.driver.implicitly_wait(4)
self.assertIn("This field is required.", self.page.modal.errors["Description"])
def test_audit_list(logged_in_browser, admin_user, live_server, test_asset): def test_audit_list(self):
page = pages.AssetAuditList(logged_in_browser.driver, live_server.url).open() self.assertEqual(models.Asset.objects.filter(last_audited_at=None).count(), len(self.page.assets))
wait = WebDriverWait(logged_in_browser.driver, 20) asset_row = self.page.assets[0]
assert models.Asset.objects.filter(last_audited_at=None).count() == len(self.page.assets) self.driver.find_element(By.XPATH, "//a[contains(@class,'btn') and contains(., 'Audit')]").click()
asset_row = page.assets[0] self.wait.until(ec.visibility_of_element_located((By.ID, 'modal')))
logged_in_browser.driver.find_element(By.XPATH, "//a[contains(@class,'btn') and contains(., 'Audit')]").click() self.assertEqual(self.page.modal.asset_id, asset_row.id)
wait.until(ec.visibility_of_element_located((By.ID, 'modal'))) self.page.modal.close()
assert self.page.modal.asset_id == asset_row.id self.assertFalse(self.driver.find_element(By.ID, 'modal').is_displayed())
page.modal.close() # Make sure audit log was NOT filled out
assert !logged_in_browser.driver.find_element(By.ID, 'modal').is_displayed() audited = models.Asset.objects.get(asset_id=asset_row.id)
# Make sure audit log was NOT filled out assert audited.last_audited_by is None
audited = models.Asset.objects.get(asset_id=asset_row.id)
assert audited.last_audited_by is None

View File

@@ -16,7 +16,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template.loader import get_template from django.template.loader import get_template
from PyPDF2 import PdfMerger, PdfReader from PyPDF2 import PdfFileMerger, PdfFileReader
from PIL import Image, ImageDraw, ImageFont, ImageOps from PIL import Image, ImageDraw, ImageFont, ImageOps
from barcode import Code39 from barcode import Code39
from barcode.writer import ImageWriter from barcode.writer import ImageWriter
@@ -127,7 +127,7 @@ class AssetEdit(LoginRequiredMixin, AssetIDUrlMixin, generic.UpdateView):
return context return context
def get_success_url(self): def get_success_url(self):
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
url = reverse_lazy('closemodal') url = reverse_lazy('closemodal')
update_url = str(reverse_lazy('asset_update', kwargs={'pk': self.object.pk})) update_url = str(reverse_lazy('asset_update', kwargs={'pk': self.object.pk}))
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object])) messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
@@ -233,7 +233,7 @@ class SupplierList(GenericListView):
context['edit'] = 'supplier_update' context['edit'] = 'supplier_update'
context['can_edit'] = self.request.user.has_perm('assets.change_supplier') context['can_edit'] = self.request.user.has_perm('assets.change_supplier')
context['detail'] = 'supplier_detail' context['detail'] = 'supplier_detail'
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
else: else:
context['override'] = 'base_assets.html' context['override'] = 'base_assets.html'
@@ -250,7 +250,7 @@ class SupplierDetail(GenericDetailView):
context['detail_link'] = 'supplier_detail' context['detail_link'] = 'supplier_detail'
context['associated'] = 'partials/associated_assets.html' context['associated'] = 'partials/associated_assets.html'
context['associated2'] = '' context['associated2'] = ''
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
else: else:
context['override'] = 'base_assets.html' context['override'] = 'base_assets.html'
@@ -264,7 +264,7 @@ class SupplierCreate(GenericCreateView, ModalURLMixin):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
else: else:
context['override'] = 'base_assets.html' context['override'] = 'base_assets.html'
@@ -280,7 +280,7 @@ class SupplierUpdate(GenericUpdateView, ModalURLMixin):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
else: else:
context['override'] = 'base_assets.html' context['override'] = 'base_assets.html'
@@ -417,11 +417,11 @@ class GenerateLabels(generic.View):
# 'images3': images[3::4], # 'images3': images[3::4],
'filename': name 'filename': name
} }
merger = PdfMerger() merger = PdfFileMerger()
rml = template.render(context) rml = template.render(context)
buffer = rml2pdf.parseString(rml) buffer = rml2pdf.parseString(rml)
merger.append(PdfReader(buffer)) merger.append(PdfFileReader(buffer))
buffer.close() buffer.close()
merged = BytesIO() merged = BytesIO()

View File

@@ -1,52 +0,0 @@
services:
db:
image: postgres:17
environment:
POSTGRES_DB: ${DATABASE_NAME}
POSTGRES_USER: ${DATABASE_USERNAME}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
env_file:
- .env
pyrigs:
build: .
container_name: pyrigs
ports:
- "8000:8000"
depends_on:
- db
environment:
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
DEBUG: ${DEBUG}
DJANGO_LOGLEVEL: ${DJANGO_LOGLEVEL}
DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS}
DATABASE_ENGINE: ${DATABASE_ENGINE}
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
DATABASE_HOST: ${DATABASE_HOST}
DATABASE_PORT: ${DATABASE_PORT}
env_file:
- .env
develop:
# Create a `watch` configuration to update the app
#
watch:
# Sync the working directory with the `/app` directory in the container
- action: sync
path: .
target: /app
# Exclude the project virtual environment
ignore:
- .venv/
# Rebuild the image on changes to the `pyproject.toml`
- action: rebuild
path: ./pyproject.toml
volumes:
postgres_data:

View File

@@ -5,88 +5,95 @@ description = "A Django-based event booking system designed for use by TEC PA an
readme = "README.md" readme = "README.md"
requires-python = "~=3.10.0" requires-python = "~=3.10.0"
dependencies = [ dependencies = [
"ansicolors", "ansicolors~=1.1.8",
"asgiref", "asgiref~=3.3.1",
"beautifulsoup4", "backports.tempfile~=1.0",
"Brotli", "backports.weakref~=1.0.post1",
"cachetools", "beautifulsoup4~=4.9.3",
"chardet", "Brotli~=1.0.9",
"configparser", "cachetools~=4.2.1",
"contextlib2", "chardet~=4.0.0",
"cssselect", "configparser~=5.0.1",
"cssutils", "contextlib2~=0.6.0.post1",
"dj-database-url", "cssselect~=1.1.0",
"dj-static", "cssutils~=1.0.2",
"Django~=5.2", "dj-database-url~=0.5.0",
"django-filter", "dj-static~=0.0.6",
"django-ical", "Django~=3.2",
"django-recurrence", "django-debug-toolbar~=4.0.0",
"django-registration-redux", "django-filter~=2.4.0",
"django-reversion", "django-ical~=1.7.1",
"django-widget-tweaks", "django-recurrence~=1.10.3",
"django-htmlmin", "django-registration-redux~=2.9",
"django-reversion~=3.0.9",
"django-widget-tweaks~=1.4.8",
"django-htmlmin~=0.11.0",
"envparse", "envparse",
"gunicorn", "gunicorn~=22.0.0",
"icalendar", "icalendar~=4.0.7",
"idna", "idna~=3.7",
"Markdown", "Markdown~=3.3.3",
"msgpack", "msgpack~=1.0.2",
"pep517", "pep517~=0.9.1",
"Pillow", "Pillow~=10.0.1",
"premailer", "premailer~=3.7.0",
"progress", "progress~=1.5",
"psutil", "psutil~=5.8.0",
"psycopg2-binary", "psycopg2-binary",
"Pygments", "Pygments~=2.15.0",
"pyparsing", "pyparsing~=2.4.7",
"PyPDF2", "PyPDF2~=1.27.5",
"pytoml", "PyPOM~=2.2.4",
"pytz", "python-dateutil~=2.8.1",
"pytoml~=0.1.21",
"pytz~=2020.5",
"reportlab", "reportlab",
"retrying", "requests~=2.32.3",
"simplejson", "retrying~=1.3.3",
"soupsieve", "simplejson~=3.17.2",
"sqlparse", "six~=1.15.0",
"static3", "soupsieve~=2.1",
"svg2rlg", "sqlparse~=0.5.0",
"tornado", "static3~=0.7.0",
"urllib3", "svg2rlg~=0.3",
"whitenoise", "tini~=3.0.1",
"yolk", "tornado~=6.3",
"zipp", "urllib3~=1.26.19",
"zope.component", "whitenoise~=5.2.0",
"zope.deferredimport", "yolk~=0.4.3",
"zope.deprecation", "zipp~=3.4.0",
"zope.event", "zope.component~=4.6.2",
"zope.hookable", "zope.deferredimport~=4.3.1",
"zope.proxy", "zope.deprecation~=4.4.0",
"zope.schema", "zope.event~=4.5.0",
"zope.hookable~=5.0.1",
"zope.proxy~=4.3.5",
"zope.schema~=6.0.1",
"sentry-sdk", "sentry-sdk",
"diff-match-patch", "diff-match-patch",
"python-barcode", "python-barcode",
"django-hCaptcha", "django-hCaptcha",
"importlib-metadata",
"django-hcaptcha", "django-hcaptcha",
"z3c.rml", "z3c.rml",
"pikepdf", "pikepdf",
"django-queryable-properties", "django-queryable-properties",
"django-mass-edit", "django-mass-edit",
"selenium", "selenium~=4.9.1",
"zope.interface", "zope.interface",
] ]
[dependency-groups] [dependency-groups]
dev = [ dev = [
"PyPOM", "pycodestyle~=2.9.1",
"pycodestyle",
"coveralls", "coveralls",
"django-coverage-plugin", "django-coverage-plugin",
"pytest-cov", "pytest-cov",
"pytest-django", "pytest-django",
"pluggy~=1.2.0", "pluggy",
"pytest-splinter", "pytest-splinter",
"pytest", "pytest",
"pytest-reverse", "pytest-reverse",
"pytest-xdist[psutil]", "pytest-xdist[psutil]",
"PyPOM[splinter]", "PyPOM[splinter]",
"autopep8>=2.3.2",
] ]

View File

@@ -76,7 +76,7 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% if page_title and not is_ajax %} {% if page_title and not request.is_ajax %}
<h2>{{page_title|safe}}</h2> <h2>{{page_title|safe}}</h2>
{% endif %} {% endif %}
{% block content %}{% endblock %} {% block content %}{% endblock %}

View File

@@ -46,7 +46,7 @@
{% include associated2|safe %} {% include associated2|safe %}
{% endif %} {% endif %}
{% if not is_ajax %} {% if not request.is_ajax %}
<div class="row py-2"> <div class="row py-2">
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">
{% if can_edit %} {% if can_edit %}
@@ -59,7 +59,7 @@
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% if is_ajax %} {% if request.is_ajax %}
{% block footer %} {% block footer %}
<div class="row py-2"> <div class="row py-2">
<div class="col-sm-12 text-right"> <div class="col-sm-12 text-right">

View File

@@ -17,11 +17,11 @@
{% endif %} {% endif %}
{% for page in page_numbers %} {% for page in page_numbers %}
{% if page == page_obj.number %} {% ifequal page page_obj.number %}
<li class="page-item active"><a class="page-link" href="#">{{ page }}</a></li> <li class="page-item active"><a class="page-link" href="#">{{ page }}</a></li>
{% else %} {% else %}
<li class="page-item"><a class="page-link" href="?{% url_replace request 'page' page %}" class="page">{{ page }}</a></li> <li class="page-item"><a class="page-link" href="?{% url_replace request 'page' page %}" class="page">{{ page }}</a></li>
{% endif %} {% endifequal %}
{% endfor %} {% endfor %}
{% if show_last %} {% if show_last %}

View File

@@ -1,10 +1,10 @@
{% extends is_ajax|yesno:"base_ajax.html,base.html" %} {% extends request.is_ajax|yesno:"base_ajax.html,base.html" %}
{% block title %}Search Help{% endblock %} {% block title %}Search Help{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
{% if not is_ajax %} {% if not request.is_ajax %}
<div class="col-sm-12"> <div class="col-sm-12">
<h1>Search Help</h1> <h1>Search Help</h1>
</div> </div>

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:'base_ajax.html,base_training.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_training.html' %}
{% load static %} {% load static %}
{% load widget_tweaks %} {% load widget_tweaks %}
@@ -37,7 +37,7 @@
<label for="depth" class="col-sm-2 col-form-label">Depth</label> <label for="depth" class="col-sm-2 col-form-label">Depth</label>
{% render_field form.depth|add_class:'form-control col-sm'|attr:'required' %} {% render_field form.depth|add_class:'form-control col-sm'|attr:'required' %}
</div> </div>
{% if not is_ajax %} {% if not request.is_ajax %}
<button type="submit" class="btn btn-primary">Save</button> <button type="submit" class="btn btn-primary">Save</button>
{% endif %} {% endif %}
</form> </form>

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:'base_ajax.html,base_training.html' %} {% extends request.is_ajax|yesno:'base_ajax.html,base_training.html' %}
{% load static %} {% load static %}
{% load widget_tweaks %} {% load widget_tweaks %}
@@ -51,7 +51,7 @@
{% render_field form.notes|add_class:'form-control' rows=3 %} {% render_field form.notes|add_class:'form-control' rows=3 %}
</div> </div>
</div> </div>
{% if not is_ajax %} {% if not request.is_ajax %}
<div class="col-sm-12 text-right pr-0"> <div class="col-sm-12 text-right pr-0">
{% button 'submit' %} {% button 'submit' %}
</div> </div>

View File

@@ -126,7 +126,7 @@ class AddQualification(generic.CreateView, ModalURLMixin):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["depths"] = models.TrainingItemQualification.CHOICES context["depths"] = models.TrainingItemQualification.CHOICES
if is_ajax(self.request).get('is_ajax'): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
else: else:
context['override'] = 'base_training.html' context['override'] = 'base_training.html'

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %}
{% load static %} {% load static %}
{% load linkornone from filters %} {% load linkornone from filters %}
@@ -41,7 +41,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if not is_ajax and object.pk == user.pk %} {% if not request.is_ajax and object.pk == user.pk %}
<div class="row py-3"> <div class="row py-3">
<div class="col text-right"> <div class="col text-right">
<div class="btn-group"> <div class="btn-group">
@@ -85,7 +85,7 @@
</div> </div>
</div> </div>
</div> </div>
{% if not is_ajax and object.pk == user.pk %} {% if not request.is_ajax and object.pk == user.pk %}
<div class="col-lg-8 col-12"> <div class="col-lg-8 col-12">
<div class="card"> <div class="card">
<div class="card-header">Personal iCal Details</div> <div class="card-header">Personal iCal Details</div>

View File

@@ -1,10 +1,10 @@
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView
from django.contrib.auth import get_user_model
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views import generic from django.views import generic
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from RIGS import models
# This view should be exempt from requiring CSRF token. # This view should be exempt from requiring CSRF token.
@@ -28,7 +28,7 @@ class LoginEmbed(LoginView):
class ProfileDetail(generic.DetailView): class ProfileDetail(generic.DetailView):
template_name = "profile_detail.html" template_name = "profile_detail.html"
model = get_user_model() model = models.Profile
def get_queryset(self): def get_queryset(self):
try: try:
@@ -48,7 +48,7 @@ class ProfileDetail(generic.DetailView):
class ProfileUpdateSelf(generic.UpdateView): class ProfileUpdateSelf(generic.UpdateView):
template_name = "profile_form.html" template_name = "profile_form.html"
model = get_user_model() model = models.Profile
fields = ['first_name', 'last_name', 'email', 'initials', 'phone', 'dark_theme'] fields = ['first_name', 'last_name', 'email', 'initials', 'phone', 'dark_theme']
def get_queryset(self): def get_queryset(self):

645
uv.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
{% extends is_ajax|yesno:"base_ajax_nomodal.html,base_rigs.html" %} {% extends request.is_ajax|yesno:"base_ajax_nomodal.html,base_rigs.html" %}
{% load static %} {% load static %}
{% load humanize %} {% load humanize %}