diff --git a/.github/workflows/django_testing_ci.yml b/.github/workflows/django_testing_ci.yml new file mode 100644 index 000000000..f61c0a028 --- /dev/null +++ b/.github/workflows/django_testing_ci.yml @@ -0,0 +1,89 @@ +# Github Actions workflow that runs the test suite on the master branch +# every night, as well as tests new pull requests. + +name: Django Testing CI + +on: + schedule: # Run every night at 3 AM + - cron: '0 10 * * *' # 10 AM UTC = 3 AM PST + push: # Run when pushes are made on the master and develop branch + branches: [ "master", "develop" ] + pull_request: # Runs when pull request to develop or master is opened, + # reopened, or updated with a new commit. + branches: [ "master", "develop" ] + +jobs: + build: + + runs-on: ubuntu-latest # Default github actions environment for Linux + + services: + postgres: # Set up a database container with the following specifications + image: postgres:9.6.24-alpine3.15 + env: + POSTGRES_DB: cf_brc_db + POSTGRES_PASSWORD: test + POSTGRES_PORT: 5432 + POSTGRES_USER: test + ports: + - 5432:5432 + options: >- # Actions run continues when database health is asserted + --health-cmd pg_isready + --health-interval 2s + --health-timeout 3s + --health-retries 15 + + steps: # Steps to run to set up testing + - name: Checkout the current commit + uses: actions/checkout@v3 + + - name: Set up Python 3.6.8 + uses: actions/setup-python@v3 + with: + python-version: 3.6.8 + + - name: Cache and/or Install apache2-dev needed for testing suite + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: apache2-dev + + - name: Cache Python packages # Use a cached installation of Python packages + id: cache-python + uses: actions/cache@v3 + with: + path: ~/venv + key: ${{ runner.os }}-python-packages-${{ hashFiles('requirements.txt') }} + + - if: ${{ steps.cache-python.outputs.cache-hit != 'true' }} # If a cache is not found + name: Install Python packages + run: | + python3.6 -m venv ~/venv + source ~/venv/bin/activate + pip install -r requirements.txt + + - name: Create settings files from samples and create log files needed for testing + run: | + # Create log files + sudo mkdir -p /var/log/user_portals/cf_mybrc + sudo touch /var/log/user_portals/cf_mybrc/cf_mybrc_portal.log + sudo touch /var/log/user_portals/cf_mybrc/cf_mybrc_api.log + + # Modify log file permssions to allow testing to function properly + sudo chmod 775 /var/log/user_portals/cf_mybrc + sudo chmod 666 /var/log/user_portals/cf_mybrc/cf_mybrc_portal.log + sudo chmod 666 /var/log/user_portals/cf_mybrc/cf_mybrc_api.log + + # Give Apache permission to logs + sudo chown -R :www-data /var/log/user_portals/cf_mybrc + + # Get setting configuration from samples + cp coldfront/config/local_strings.py.sample coldfront/config/local_strings.py + cp coldfront/config/local_settings.py.sample coldfront/config/local_settings.py + cp coldfront/config/test_settings.py.sample coldfront/config/test_settings.py + + - name: Run Tests + run: | + source ~/venv/bin/activate + python manage.py migrate + python manage.py test + diff --git a/README.md b/README.md index bff43b546..397354eca 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ that quotes need not be provided, except in the list variable. redis_passwd: password_here from_email: you@email.com admin_email: you@email.com + email_admin_list: ["you@email.com"] request_approval_cc_list: ["you@email.com"] ``` 8. Provision the VM. This should run the Ansible playbook. Expect this to take diff --git a/bootstrap/ansible/main.copyme b/bootstrap/ansible/main.copyme index 4f92f96e8..cd0c670e8 100644 --- a/bootstrap/ansible/main.copyme +++ b/bootstrap/ansible/main.copyme @@ -1,3 +1,11 @@ +############################################################################### +# Ansible Settings +############################################################################### + +# Types of Ansible tasks to run by default. +provisioning_tasks: true +common_tasks: true + ############################################################################### # General Settings ############################################################################### @@ -36,6 +44,37 @@ wsgi_conf_file_name: cf_mybrc_wsgi.conf # TODO: For LRC, use the substring 'cf_lrc'. wsgi_conf_log_prefix: cf_brc +# LRC Cloudflare settings. +# Whether the web server is behind Cloudflare. +# TODO: For the LRC production deployment, enable Cloudflare, since LBL +# TODO: requires that web servers visible to the Internet be placed behind it. +# TODO: https://commons.lbl.gov/display/cpp/Open+Web+Server+Requirements +cloudflare_enabled: false +# A list of Cloudflare's IP ranges. +# TODO: Keep it up-to-date with: https://www.cloudflare.com/ips/. +cloudflare_ip_ranges: [ + 103.21.244.0/22, + 103.22.200.0/22, + 103.31.4.0/22, + 104.16.0.0/13, + 104.24.0.0/14, + 108.162.192.0/18, + 131.0.72.0/22, + 141.101.64.0/18, + 162.158.0.0/15, + 172.64.0.0/13, + 173.245.48.0/20, + 188.114.96.0/20, + 190.93.240.0/20, + 197.234.240.0/22, + 198.41.128.0/17 +] +# The name of the server, which should differ from the name of the website. +# Source: See Open Web Server Requirements link above. +# TODO: Set this to e.g., mylrc-local.lbl.gov for mylrc.lbl.gov if Cloudflare +# TODO: is enabled. +cloudflare_local_server_name: + # CILogon client settings. # TODO: Set these, needed only if SSO should be enabled. cilogon_app_client_id: "" @@ -60,7 +99,7 @@ portal_name: "MyBRC" program_name_long: "Berkeley Research Computing" program_name_short: "BRC" primary_cluster_name: "Savio" -# TODO: For MyLRC, use "https://it.lbl.gov/resource/hpc/for-users/". +# TODO: For MyLRC, use "https://it.lbl.gov/service/scienceit/high-performance-computing/lrc/". center_user_guide: "https://docs-research-it.berkeley.edu/services/high-performance-computing/user-guide/" # TODO: For MyLRC, use "https://it.lbl.gov/resource/hpc/for-users/getting-started/". center_login_guide: "https://docs-research-it.berkeley.edu/services/high-performance-computing/user-guide/logging-brc-clusters/#Logging-in" @@ -90,10 +129,6 @@ allow_all_jobs: false # The URL of the Sentry instance to send errors to. sentry_dsn: "" -# Types of Ansible tasks to run by default. -provisioning_tasks: True -common_tasks: True - ############################################################################### # staging_settings ############################################################################### @@ -120,10 +155,15 @@ common_tasks: True # ssl_enabled: false # ssl_certificate_file: /etc/ssl/ssl_certificate.file # ssl_certificate_key_file: /etc/ssl/ssl_certificate_key.file +# # An optional chain file. # ssl_certificate_chain_file: /etc/ssl/ssl_certification_chain.file -# # An IP range, in CIDR notation, to which the REST API is accessible. -# ip_range_with_api_access: 0.0.0.0/24 +# # Zero or more space-separated IP ranges, in CIDR notation, to which the REST +# # API is accessible. If none are given, API access is not restricted. +# ip_range_with_api_access: + +# # IP addresses other than 127.0.0.1 that can view the django debug toolbar. +# debug_toolbar_ips: [] # # Email settings. # email_port: 25 @@ -133,8 +173,12 @@ common_tasks: True # # TODO: For LRC, use the substring 'MyLRC'. # email_subject_prefix: '[MyBRC-User-Portal]' +# # A list of admin email addresses to be notified about new requests and other +# # events. +# # TODO: Set these addresses to yours. +# email_admin_list: [] # # A list of email addresses to CC when certain requests are processed. -# # TODO: Set this address to yours. +# # TODO: Set these addresses to yours. # request_approval_cc_list: [] ############################################################################### @@ -166,12 +210,16 @@ common_tasks: True # ssl_enabled: true # ssl_certificate_file: /etc/ssl/ssl_certificate.file # ssl_certificate_key_file: /etc/ssl/ssl_certificate_key.file +# # An optional chain file. # ssl_certificate_chain_file: /etc/ssl/ssl_certification_chain.file -# # One or more space-separated IP ranges, in CIDR notation, to which the REST -# # API is accessible. +# # Zero or more space-separated IP ranges, in CIDR notation, to which the REST +# # API is accessible. If none are given, API access is not restricted. # ip_range_with_api_access: 10.0.0.0/8 +# # IP addresses other than 127.0.0.1 that can view the django debug toolbar. +# debug_toolbar_ips: [] + # # Email settings. # email_port: 25 # # TODO: Set these addresses to yours. @@ -180,9 +228,13 @@ common_tasks: True # # TODO: For LRC, use the substring 'MyLRC'. # email_subject_prefix: '[MyBRC-User-Portal]' +# # A list of admin email addresses to be notified about new requests and other +# # events. +# # TODO: Set these addresses to yours. +# email_admin_list: [] # # A list of email addresses to CC when certain requests are processed. -# # TODO: Set this address to yours. -# request_approval_cc_list: ['wfeinstein@lbl.gov'] +# # TODO: Set these addresses to yours. +# request_approval_cc_list: [] ############################################################################### # dev_settings @@ -210,10 +262,15 @@ common_tasks: True # ssl_enabled: false # ssl_certificate_file: /etc/ssl/ssl_certificate.file # ssl_certificate_key_file: /etc/ssl/ssl_certificate_key.file +# # An optional chain file. # ssl_certificate_chain_file: /etc/ssl/ssl_certification_chain.file -# # An IP range, in CIDR notation, to which the REST API is accessible. -# ip_range_with_api_access: 0.0.0.0/0 +# # Zero or more space-separated IP ranges, in CIDR notation, to which the REST +# # API is accessible. If none are given, API access is not restricted. +# ip_range_with_api_access: + +# # IP addresses other than 127.0.0.1 that can view the django debug toolbar. +# debug_toolbar_ips: ['10.0.2.2'] # 10.0.2.2 is the vagrant host. # # Email settings. # email_port: 1025 @@ -223,6 +280,10 @@ common_tasks: True # # TODO: For LRC, use the substring 'MyLRC'. # email_subject_prefix: '[MyBRC-User-Portal]' +# # A list of admin email addresses to be notified about new requests and other +# # events. +# # TODO: Set these addresses to yours. +# email_admin_list: ['you@email.com'] # # A list of email addresses to CC when certain requests are processed. -# # TODO: Set this address to yours. +# # TODO: Set these addresses to yours. # request_approval_cc_list: ['you@email.com'] diff --git a/bootstrap/ansible/playbook.yml b/bootstrap/ansible/playbook.yml index 1b5b0d8a0..38adc5594 100644 --- a/bootstrap/ansible/playbook.yml +++ b/bootstrap/ansible/playbook.yml @@ -302,15 +302,76 @@ state: started enabled: yes - - name: Permit http and https traffic in public zone + - name: Permit http traffic in public zone ansible.posix.firewalld: zone: public - service: "{{ item }}" + service: http state: enabled permanent: yes - loop: - - http - - https + + - name: Run Cloudflare Tasks + block: + # 443/tcp should only be allowed in Cloudflare IP ranges. + # https://commons.lbl.gov/pages/viewpage.action?pageId=203489943 + - name: Do not permit https traffic in public zone + ansible.posix.firewalld: + zone: public + service: https + state: disabled + permanent: yes + + - name: Deny 443/tcp in public zone + ansible.posix.firewalld: + zone: public + port: 443/tcp + state: disabled + permanent: yes + + - name: Create firewalld zone for Cloudflare IP ranges + ansible.posix.firewalld: + zone: cloudflare + state: present + permanent: yes + + # Firewalld must be reloaded after zone transactions. + # https://docs.ansible.com/ansible/latest/collections/ansible/posix/firewalld_module.html#notes + - name: Reload firewalld service + service: + name: firewalld + state: reloaded + + - name: Add Cloudflare IP ranges to Cloudflare zone + ansible.posix.firewalld: + zone: cloudflare + source: "{{ item }}" + permanent: yes + state: enabled + loop: "{{ cloudflare_ip_ranges }}" + + - name: Permit http and https traffic in Cloudflare zone + ansible.posix.firewalld: + zone: cloudflare + service: "{{ item }}" + state: enabled + permanent: yes + loop: + - http + - https + + # Log the original client IP address of each request instead of the Cloudflare IP. + # Source: https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs#C5XWe97z77b3XZV + - name: Update httpd combined LogFormat in accordance with mod_remoteip + lineinfile: + path: /etc/httpd/conf/httpd.conf + regexp: '^ LogFormat .+ combined$' + line: ' LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined' + + when: cloudflare_enabled + + - name: Reload firewalld service + service: + name: firewalld + state: reloaded when: flag_lrc_enabled diff --git a/bootstrap/ansible/settings_template.tmpl b/bootstrap/ansible/settings_template.tmpl index 0ecad7695..21b2ddfb1 100644 --- a/bootstrap/ansible/settings_template.tmpl +++ b/bootstrap/ansible/settings_template.tmpl @@ -21,7 +21,9 @@ CENTER_PROJECT_RENEWAL_HELP_URL = CENTER_BASE_URL + '/help' EMAIL_PORT = {{ email_port }} EMAIL_SUBJECT_PREFIX = '{{ email_subject_prefix }}' -EMAIL_ADMIN_LIST = ['admin@{{ hostname }}'] +# A list of admin email addresses to be notified about new requests and other +# events. +EMAIL_ADMIN_LIST = {{ email_admin_list }} or ['admin@{{ hostname }}'] EMAIL_SENDER = '{{ from_email }}' EMAIL_TICKET_SYSTEM_ADDRESS = 'help@{{ hostname }}' EMAIL_DIRECTOR_EMAIL_ADDRESS = 'director@{{ hostname }}' @@ -139,6 +141,17 @@ CONSTANCE_REDIS_CONNECTION = { 'password': '{{ redis_passwd }}', } +#------------------------------------------------------------------------------ +# django debug toolbar settings +#------------------------------------------------------------------------------ + +# IP addresses other than 127.0.0.1 that can view the django debug toolbar. +EXTRA_INTERNAL_IPS = [ +{% for ip in debug_toolbar_ips %} + '{{ ip }}', +{% endfor %} +] + #------------------------------------------------------------------------------ # django-flags settings #------------------------------------------------------------------------------ diff --git a/bootstrap/ansible/wsgi_conf_ssl.tmpl b/bootstrap/ansible/wsgi_conf_ssl.tmpl index 843e80660..a09551a7e 100644 --- a/bootstrap/ansible/wsgi_conf_ssl.tmpl +++ b/bootstrap/ansible/wsgi_conf_ssl.tmpl @@ -2,14 +2,30 @@ WSGISocketPrefix /var/run/wsgi LoadModule auth_basic_module modules/mod_auth_basic.so LoadModule authz_user_module modules/mod_authz_user.so LoadModule headers_module modules/mod_headers.so +{% if cloudflare_enabled | bool %} +LoadModule remoteip_module modules/mod_remoteip.so +{% endif %} LoadModule ssl_module modules/mod_ssl.so WSGIPassAuthorization On + {% if cloudflare_enabled | bool %} + ServerName {{ cloudflare_local_server_name }} + ServerAlias {{ cloudflare_local_server_name }} + {% else %} ServerName {{ hostname }} ServerAlias {{ hostname }} + {% endif %} + + {% if cloudflare_enabled | bool %} + # Source: https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs#C5XWe97z77b3XZV + RemoteIPHeader CF-Connecting-IP + {% for cloudflare_ip_range in cloudflare_ip_ranges %} + RemoteIPTrustedProxy {{ cloudflare_ip_range }} + {% endfor %} + {% endif %} Alias /static/ {{ git_prefix }}/{{ reponame }}/static_root/ @@ -31,7 +47,9 @@ WSGIPassAuthorization On SSLProtocol all -SSLv2 -SSLv3 SSLCertificateFile {{ ssl_certificate_file }} SSLCertificateKeyFile {{ ssl_certificate_key_file }} + {% if ssl_certificate_chain_file %} SSLCertificateChainFile {{ ssl_certificate_chain_file }} + {% endif %} ErrorLog /var/log/httpd/{{ wsgi_conf_log_prefix }}.error.log CustomLog /var/log/httpd/{{ wsgi_conf_log_prefix }}.custom.log combined @@ -46,7 +64,12 @@ WSGIPassAuthorization On + {% if cloudflare_enabled | bool %} + ServerName {{ cloudflare_local_server_name }} + Redirect permanent "/" "https://{{ cloudflare_local_server_name }}" + {% else %} ServerName {{ hostname }} Redirect permanent "/" "{{ full_host_path }}" + {% endif %} diff --git a/coldfront/config/local_settings.py.sample b/coldfront/config/local_settings.py.sample index 2e34cac44..d5164bebb 100644 --- a/coldfront/config/local_settings.py.sample +++ b/coldfront/config/local_settings.py.sample @@ -369,6 +369,22 @@ REST_FRAMEWORK = { ], } +#------------------------------------------------------------------------------ +# Enable django debug toolbar +#------------------------------------------------------------------------------ + +EXTRA_APPS += [ + 'debug_toolbar' +] + +EXTRA_MIDDLEWARE += [ + 'debug_toolbar.middleware.DebugToolbarMiddleware' +] + +INTERNAL_IPS = [ + '127.0.0.1' +] + # The number of hours for which a newly created authentication token will be # valid. TOKEN_EXPIRATION_HOURS = 24 @@ -459,7 +475,13 @@ except ImportError: try: from .dev_settings import * except ImportError: - pass + try: + from .test_settings import * + except ImportError: + pass + +# Update internal IPs for debug toolbar based on potentially-updated variables. +INTERNAL_IPS += EXTRA_INTERNAL_IPS # Update logging settings based on potentially-updated variables. LOGGING['handlers']['file']['filename'] = LOG_PATH diff --git a/coldfront/config/test_settings.py.sample b/coldfront/config/test_settings.py.sample new file mode 100644 index 000000000..4d92b5511 --- /dev/null +++ b/coldfront/config/test_settings.py.sample @@ -0,0 +1,95 @@ +import os + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['0.0.0.0', 'localhost', 'localhost'] + +PORTAL_NAME = 'MyBRC' +PROGRAM_NAME_LONG = 'Berkeley Research Computing' +PROGRAM_NAME_SHORT = 'BRC' +PRIMARY_CLUSTER_NAME = 'Savio' + +CENTER_NAME = PROGRAM_NAME_SHORT + ' HPC Resources' +CENTER_USER_GUIDE = 'https://docs-research-it.berkeley.edu/services/high-performance-computing/user-guide/' +CENTER_LOGIN_GUIDE = 'https://docs-research-it.berkeley.edu/services/high-performance-computing/user-guide/logging-brc-clusters/#Logging-in' +CENTER_HELP_EMAIL = 'brc-hpc-help@berkeley.edu' + +CENTER_BASE_URL = 'http://localhost:8880' +CENTER_HELP_URL = CENTER_BASE_URL + '/help' +CENTER_PROJECT_RENEWAL_HELP_URL = CENTER_BASE_URL + '/help' + +EMAIL_PORT = 1025 +EMAIL_SUBJECT_PREFIX = '[MyBRC-User-Portal]' +# A list of admin email addresses to be notified about new requests and other +# events. +EMAIL_ADMIN_LIST = ['admin@localhost'] +EMAIL_SENDER = 'test@test.test' +EMAIL_TICKET_SYSTEM_ADDRESS = 'help@localhost' +EMAIL_DIRECTOR_EMAIL_ADDRESS = 'director@localhost' +EMAIL_PROJECT_REVIEW_CONTACT = 'review@localhost' +EMAIL_DEVELOPMENT_EMAIL_LIST = ['dev1@localhost', 'dev2@localhost'] +EMAIL_OPT_OUT_INSTRUCTION_URL = CENTER_BASE_URL + '/optout' +EMAIL_SIGNATURE = """ +MyBRC User Portal team +http://localhost:8880 +Email : test@test.test +""" + +EMAIL_FROM = 'test@test.test' +EMAIL_ADMIN = 'test@test.test' +DEFAULT_FROM_EMAIL = EMAIL_FROM + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'cf_brc_db', + 'USER': 'test', + 'PASSWORD': 'test', + 'HOST': 'localhost', + 'PORT': '5432', + }, +} + +LOG_PATH = '/var/log/user_portals/cf_mybrc/cf_mybrc_portal.log' +API_LOG_PATH = '/var/log/user_portals/cf_mybrc/cf_mybrc_api.log' + +# A list of admin email addresses to CC when certain requests are approved. +REQUEST_APPROVAL_CC_LIST = ['test@test.test'] + +#------------------------------------------------------------------------------ +# SSL settings +#------------------------------------------------------------------------------ + +# Use a secure cookie for the session cookie (HTTPS only). +SESSION_COOKIE_SECURE = False + +#------------------------------------------------------------------------------ +# Django All-Auth settings +#------------------------------------------------------------------------------ + +CILOGON_APP_CLIENT_ID = '' +CILOGON_APP_SECRET = '' + +#------------------------------------------------------------------------------ +# django debug toolbar settings +#------------------------------------------------------------------------------ + +# IP addresses other than 127.0.0.1 that can view the django debug toolbar. +EXTRA_INTERNAL_IPS = [] + +#------------------------------------------------------------------------------ +# django-flags settings +#------------------------------------------------------------------------------ + +FLAGS = { + 'ALLOCATION_RENEWAL_FOR_NEXT_PERIOD_REQUESTABLE': [ + {'condition': 'during month', 'value': '5'}, + ], + 'BASIC_AUTH_ENABLED': [{'condition': 'boolean', 'value': True}], + 'BRC_ONLY': [{'condition': 'boolean', 'value': True}], + 'LRC_ONLY': [{'condition': 'boolean', 'value': False}], + 'SECURE_DIRS_REQUESTABLE': [{'condition': 'boolean', 'value': True}], + 'SERVICE_UNITS_PURCHASABLE': [{'condition': 'boolean', 'value': True}], + 'SSO_ENABLED': [{'condition': 'boolean', 'value': False}], +} diff --git a/coldfront/config/urls.py b/coldfront/config/urls.py index 5f97e4d67..6922d2390 100644 --- a/coldfront/config/urls.py +++ b/coldfront/config/urls.py @@ -28,6 +28,8 @@ path('help', TemplateView.as_view(template_name='portal/help.html'), name='help'), ] +if 'debug_toolbar' in settings.EXTRA_APPS: + urlpatterns.append(path('__debug__/', include('debug_toolbar.urls'))) if 'coldfront.api' in settings.EXTRA_APPS: urlpatterns.append(path('api/', include('coldfront.api.urls'))) diff --git a/coldfront/core/allocation/tests/test_commands/test_start_allocation_period.py b/coldfront/core/allocation/tests/test_commands/test_start_allocation_period.py index 3ee894660..6dbb889e6 100644 --- a/coldfront/core/allocation/tests/test_commands/test_start_allocation_period.py +++ b/coldfront/core/allocation/tests/test_commands/test_start_allocation_period.py @@ -278,8 +278,9 @@ def assert_output(self, output, expected_num_lines, project_names_by_id, number of lines, and that the Projects and requests included in the output are exactly the expected ones.""" # Remove newlines and ANSI color codes from the output. + ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') output_lines = [ - line.strip()[7:] for line in output.split('\n') if line.strip()] + ansi_escape.sub('', line.strip()) for line in output.split('\n') if line.strip()] self.assertEqual(expected_num_lines, len(output_lines)) num_new_project_requests = len(num_sus_by_new_project_request_id) diff --git a/coldfront/core/statistics/urls.py b/coldfront/core/statistics/urls.py index 67cd193e9..c6fb8b4cf 100644 --- a/coldfront/core/statistics/urls.py +++ b/coldfront/core/statistics/urls.py @@ -4,6 +4,8 @@ urlpatterns = [ path('', statistics_views.SlurmJobListView.as_view(), name='slurm-job-list'), - path('/', statistics_views.SlurmJobDetailView.as_view(), name='slurm-job-detail'), - path('export/', statistics_views.ExportJobListView.as_view(), name='export-job-list') -] \ No newline at end of file + path('export/', statistics_views.ExportJobListView.as_view(), name='export-job-list'), + # All URLs must come before slurm-job-detail, since it takes a + # string as a primary key. + path('/', statistics_views.SlurmJobDetailView.as_view(), name='slurm-job-detail'), +] diff --git a/coldfront/core/utils/tests/test_export_data.py b/coldfront/core/utils/tests/test_export_data.py index faaec1d5a..961d3b4a1 100644 --- a/coldfront/core/utils/tests/test_export_data.py +++ b/coldfront/core/utils/tests/test_export_data.py @@ -322,7 +322,7 @@ def test_csv_no_date(self): '--format=csv') output = self.convert_output(output, 'csv') - post_time = utc_now_offset_aware().replace(tzinfo=None, microsecond=0) + post_time = utc_now_offset_aware().replace(tzinfo=None, microsecond=999999) for index, item in enumerate(output): if index == 0: self.assertEqual(item, ['username', 'date_created']) diff --git a/requirements.txt b/requirements.txt index f29ce3c4f..20420398e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ Django==3.2.5 django-allauth==0.46.0 django-constance==2.9.0 django-crispy-forms==1.11.1 +django-debug-toolbar==3.2.4 django-durationwidget==1.0.5 django-filter==2.3.0 django-flags==5.0.8