Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
DJANGO_SETTINGS_MODULE = specifyweb.settings
10 changes: 8 additions & 2 deletions requirements-testing.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
django-stubs==1.12.0
# django-stubs==1.12.0
django-stubs==5.2.9
mypy==1.5.1
hypothesis>=6.131.9,<7
hypothesis-jsonschema==0.19.0
types-requests==2.28.5
lxml
lxml
debugpy==1.6.5
pytest==7.2.1
pytest-django==4.12.0
django-extensions==4.1
# django-debug-toolbar==4.4.6
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ wheel
kombu==5.5.2
redis==6.4.0
celery==5.5.1
Django==4.2.30
Django==5.2.13
mysqlclient==2.1.1
SQLAlchemy==1.4.54
sqlalchemy2-stubs
requests==2.32.4
pycryptodome==3.21.0
PyJWT==2.12.1
django-auth-ldap==1.2.17
django-auth-ldap==5.3.0
jsonschema==4.0.1
typing-extensions==4.12.2
stream-zip==0.0.83
Expand Down
8 changes: 4 additions & 4 deletions specifyweb/backend/batch_identify/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from django.urls import re_path
from django.urls import path

from . import views

urlpatterns = [
re_path(r'^batch_identify/resolve/$', views.batch_identify_resolve),
re_path(r'^batch_identify/validate_record_set/$', views.batch_identify_validate_record_set),
re_path(r'^batch_identify/$', views.batch_identify),
path('batch_identify/resolve/', views.batch_identify_resolve),
path('batch_identify/validate_record_set/', views.batch_identify_validate_record_set),
path('batch_identify/', views.batch_identify),
]
12 changes: 6 additions & 6 deletions specifyweb/backend/context/testurls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
Provides urls to access the mocked views
"""

from django.urls import path, re_path
from django.urls import path

from . import testsviews as views

urlpatterns = [
path('collection/', views.collection),
re_path(r'^domain.json$', views.domain),
re_path(r'^viewsets/(?P<level>\d+).xml$', views.viewsets),
re_path(r'^schema_localization.json$', views.schema_localization),
re_path(r'^app.resource$', views.app_resource),
re_path(r'^available_related_searches.json$', views.available_related_searches),
path('domain.json', views.domain),
path('viewsets/<int:level>.xml', views.viewsets),
path('schema_localization.json', views.schema_localization),
path('app.resource', views.app_resource),
path('available_related_searches.json', views.available_related_searches),
]
42 changes: 21 additions & 21 deletions specifyweb/backend/context/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Defines the urls for the app context subsystem
"""

from django.urls import path, re_path
from django.urls import path
from django.urls import path

from . import views, user_resources, collection_resources
Expand All @@ -16,26 +16,26 @@
path('language/', views.languages),
path('schema/language/', views.schema_language),

re_path(r'^api_endpoints.json$', views.api_endpoints),
re_path(r'^api_endpoints_all.json$', views.api_endpoints_all),
re_path(r'^user.json$', views.user),
re_path(r'^stats_counts.json$', views.stats_counts),
re_path(r'^system_info.json$', views.system_info),
re_path(r'^all_system_data.json$', views.all_system_data),
re_path(r'^all_system_config_data.json$', views.all_system_config_data),
re_path(r'^server_time.json$', views.get_server_time),
re_path(r'^domain.json$', views.domain),
re_path(r'^view.json$', views.view),
re_path(r'^views.json$', views.views),
re_path(r'^viewsets.json$', views.viewsets),
re_path(r'^datamodel.json$', views.datamodel),
re_path(r'^schema_localization.json$', views.schema_localization),
re_path(r'^app.resource$', views.app_resource),
re_path(r'^available_related_searches.json$', views.available_related_searches),
re_path(r'^remoteprefs.properties$', views.remote_prefs),

re_path(r'^attachment_settings.json$', attachment_settings),
re_path(r'^report_runner_status.json$', report_runner_status),
path('api_endpoints.json', views.api_endpoints),
path('api_endpoints_all.json', views.api_endpoints_all),
path('user.json', views.user),
path('stats_counts.json', views.stats_counts),
path('system_info.json', views.system_info),
path('all_system_data.json', views.all_system_data),
path('all_system_config_data.json', views.all_system_config_data),
path('server_time.json', views.get_server_time),
path('domain.json', views.domain),
path('view.json', views.view),
path('views.json', views.views),
path('viewsets.json', views.viewsets),
path('datamodel.json', views.datamodel),
path('schema_localization.json', views.schema_localization),
path('app.resource', views.app_resource),
path('available_related_searches.json', views.available_related_searches),
path('remoteprefs.properties', views.remote_prefs),

path('attachment_settings.json', attachment_settings),
path('report_runner_status.json', report_runner_status),

path('user_resource/', user_resources.user_resources),
path('user_resource/<int:resourceid>/', user_resources.user_resource),
Expand Down
6 changes: 3 additions & 3 deletions specifyweb/backend/inheritance/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@

from django.urls import include, path, re_path
from django.urls import include, path

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'include' is not used.

from specifyweb.backend.inheritance import views

urlpatterns = [
# cat num for siblings
re_path(r'^catalog_number_for_sibling/$', views.catalog_number_for_sibling),
path('catalog_number_for_sibling/', views.catalog_number_for_sibling),

# cat num for parent
re_path(r'^catalog_number_from_parent/$', views.catalog_number_from_parent),
path('catalog_number_from_parent/', views.catalog_number_from_parent),
]
12 changes: 6 additions & 6 deletions specifyweb/backend/setup_tool/urls.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@

from django.urls import re_path
from django.urls import path

from . import views

urlpatterns = [
# check if the db is new at login
re_path(r'^setup_progress/$', views.get_setup_progress),
re_path(r'^config_progress/$', views.get_config_progress),
path('setup_progress/', views.get_setup_progress),
path('config_progress/', views.get_config_progress),

re_path(r'^setup_database/create/$', views.setup_database_view),
path('setup_database/create/', views.setup_database_view),

re_path(r'^discipline_and_trees/create/$', views.create_discipline_and_trees),
re_path(r'^running_worker_tasks/$', views.get_running_worker_tasks),
path('discipline_and_trees/create/', views.create_discipline_and_trees),
path('running_worker_tasks/', views.get_running_worker_tasks),

# These urls are functional but unused by the setup process. The API can be used instead.
# re_path(r'^institution/create/$', views.create_institution_view),
Expand Down
4 changes: 2 additions & 2 deletions specifyweb/backend/trees/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@

# Create new trees
path('create_default_tree/', views.create_default_tree_view),
re_path(r'^create_default_tree/status/(?P<task_id>[^/]+)/$', views.default_tree_upload_status),
re_path(r'^create_default_tree/abort/(?P<task_id>[^/]+)/$', views.abort_default_tree_creation),
path('create_default_tree/status/<str:task_id>/', views.default_tree_upload_status),
path('create_default_tree/abort/<str:task_id>/', views.abort_default_tree_creation),
path('default_tree_mapping/', views.default_tree_mapping),
path('db_encoding/', views.get_db_encoding),
]
6 changes: 1 addition & 5 deletions specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,7 @@ export const dialogIcons: RR<
> = {
none: undefined,
error: <span className="text-red-500">{icons.exclamationCircle}</span>,
warning: (
<span className="text-red-500">
{icons.exclamation}
</span>
),
warning: <span className="text-red-500">{icons.exclamation}</span>,
success: <span className="text-green-500">{icons.checkCircle}</span>,
info: icons.informationCircle,
question: icons.questionCircle,
Expand Down
4 changes: 0 additions & 4 deletions specifyweb/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,6 @@ def get_sa_db_url(db_name):
# to load the internationalization machinery.
USE_I18N = True

# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
USE_L10N = True

LANGUAGE_COOKIE_NAME='language'

# Absolute filesystem path to the directory that will hold user-uploaded files.
Expand Down
32 changes: 16 additions & 16 deletions specifyweb/settings/specify_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,31 @@
# want to access which must be a Specify database already
# be initialized from a backup or by using the Specify Wizard
# from a Specify 6 installation.
DATABASE_NAME = 'SpecifyDB'
DATABASE_NAME = os.environ.get("DATABASE_NAME", "")

# Database hostname or IP. Will use localhost:3306 by default.
DATABASE_HOST = ''
DATABASE_PORT = ''
DATABASE_HOST = os.environ.get("DATABASE_HOST", "")
DATABASE_PORT = os.environ.get("DATABASE_PORT", "")

# Any extra options for the database connection
# https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-OPTIONS
DATABASE_OPTIONS = {}

# The master user login. This is the MySQL user used to connect to the
# database. This can be the same as the Specify 6 master user.
MASTER_NAME = 'MasterUser'
MASTER_PASSWORD = 'MasterPassword'
MIGRATOR_NAME = 'MasterUser'
MIGRATOR_PASSWORD = 'MasterPassword'
APP_USER_NAME = 'MasterUser'
APP_USER_PASSWORD = 'MasterPassword'

# MASTER_NAME = os.environ.get('MASTER_NAME', 'root')
# MASTER_PASSWORD = os.environ.get('MASTER_NAME', 'password')
# MIGRATOR_NAME = os.environ.get('MIGRATOR_NAME', MASTER_NAME)
# MIGRATOR_PASSWORD = os.environ.get('MIGRATOR_PASSWORD', MASTER_PASSWORD)
# APP_USER_NAME = os.environ.get('APP_USER_NAME', MIGRATOR_NAME)
# APP_USER_PASSWORD = os.environ.get('APP_USER_PASSWORD', MIGRATOR_PASSWORD)
MASTER_NAME = os.environ.get("MASTER_NAME", "")
MASTER_PASSWORD = os.environ.get("MASTER_PASSWORD", "")
MIGRATOR_NAME = os.environ.get("MIGRATOR_NAME", "")
MIGRATOR_PASSWORD = os.environ.get("MIGRATOR_PASSWORD", "")
APP_USER_NAME = os.environ.get("APP_USER_NAME", "")
APP_USER_PASSWORD = os.environ.get("APP_USER_PASSWORD", "")

# MASTER_NAME = os.environ.get("MASTER_NAME", "")
# MASTER_PASSWORD = os.environ.get("MASTER_PASSWORD", "")
# MIGRATOR_NAME = os.environ.get("MIGRATOR_NAME", "")
# MIGRATOR_PASSWORD = os.environ.get("MIGRATOR_PASSWORD", "")
# APP_USER_NAME = os.environ.get("APP_USER_NAME", "")
# APP_USER_PASSWORD = os.environ.get("APP_USER_PASSWORD", "")

# The Specify web attachment server URL.
WEB_ATTACHMENT_URL = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Generated by Django 5.2.13 on 2026-04-30

from django.db import migrations, models

class Migration(migrations.Migration):
dependencies = [
('specify', '0044_alter_deletion_cascade'),
]

operations = [
migrations.AlterField(
model_name='autonumberingscheme',
name='collections',
field=models.ManyToManyField(related_name='numberingschemes', through='specify.Autonumschcoll', through_fields=('autonumberingscheme', 'collection'), to='specify.collection'),
),
migrations.AlterField(
model_name='autonumberingscheme',
name='disciplines',
field=models.ManyToManyField(related_name='numberingschemes', through='specify.Autonumschdsp', through_fields=('autonumberingscheme', 'discipline'), to='specify.discipline'),
),
migrations.AlterField(
model_name='autonumberingscheme',
name='divisions',
field=models.ManyToManyField(related_name='numberingschemes', through='specify.Autonumschdiv', through_fields=('autonumberingscheme', 'division'), to='specify.division'),
),
migrations.AlterField(
model_name='project',
name='collectionobjects',
field=models.ManyToManyField(related_name='projects', through='specify.Project_colobj', through_fields=('project', 'collectionobject'), to='specify.collectionobject'),
),
migrations.AlterField(
model_name='specifyuser',
name='spprincipals',
field=models.ManyToManyField(related_name='specifyusers', through='specify.Specifyuser_spprincipal', through_fields=('specifyuser', 'spprincipal'), to='specify.spprincipal'),
),
migrations.AlterField(
model_name='spexportschema',
name='mappings',
field=models.ManyToManyField(related_name='spexportschemas', through='specify.Spexportschema_exportmapping', through_fields=('spexportschema', 'spexportschemamapping'), to='specify.spexportschemamapping'),
),
migrations.AlterField(
model_name='spprincipal',
name='sppermissions',
field=models.ManyToManyField(related_name='spprincipals', through='specify.Spprincipal_sppermission', through_fields=('spprincipal', 'sppermission'), to='specify.sppermission'),
),
]
23 changes: 12 additions & 11 deletions specifyweb/specify/urls.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# Entrypoint for the routing of the app

from django.urls import path
from django.urls import include, re_path

from specifyweb.specify import views
from specifyweb.specify.models_utils import schema

urlpatterns = [
# the main business data API
re_path(r'^specify_schema/openapi.json$', schema.openapi),
path('specify_schema/openapi.json', schema.openapi),
re_path(r'^specify_schema/(?P<model>\w+)/$', schema.view),
re_path(r'^specify/', include('specifyweb.backend.batch_identify.urls')), # batch identify
path('specify/', include('specifyweb.backend.batch_identify.urls')), # batch identify
re_path(r'^specify/(?P<model>\w+)/(?P<id>\d+)/$', views.resource), # permissions added
re_path(r'^specify/(?P<model>\w+)/$', views.collection), # permissions added

Expand All @@ -19,29 +20,29 @@
# === Backwards compatibility ===

# Merge endpoints
re_path(r'^specify/merge/', include('specifyweb.backend.merge.urls')),
path('specify/merge/', include('specifyweb.backend.merge.urls')),

# Inheritance (catalog number endpoints)
re_path(r'^specify/', include('specifyweb.backend.inheritance.urls')),
path('specify/', include('specifyweb.backend.inheritance.urls')),

# Series endpoints
re_path(r'^specify', include('specifyweb.backend.series.urls')),
path('specify', include('specifyweb.backend.series.urls')),

# Table rows
re_path(r'^', include('specifyweb.backend.table_rows.urls')),
path('', include('specifyweb.backend.table_rows.urls')),

# Delete blockers
re_path(r'^', include('specifyweb.backend.delete_blockers.urls')),
path('', include('specifyweb.backend.delete_blockers.urls')),

## Bulk copy
re_path(r'^specify', include('specifyweb.backend.bulk_copy.urls')),
path('specify', include('specifyweb.backend.bulk_copy.urls')),

# Trees
re_path(r'^', include('specifyweb.backend.trees.urls')),
path('', include('specifyweb.backend.trees.urls')),

# Locality update tool
re_path(r'^', include('specifyweb.backend.locality_update_tool.urls')),
path('', include('specifyweb.backend.locality_update_tool.urls')),

# Master key + User management
re_path(r'^', include('specifyweb.backend.accounts.urls')),
path('', include('specifyweb.backend.accounts.urls')),
]
7 changes: 6 additions & 1 deletion specifyweb/specify/utils/case_insensitive_bool.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ def to_python(self, value):
return False
return super().to_python(value)

class NullBooleanField(models.NullBooleanField):
class NullBooleanField(BooleanField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('null', True)
kwargs.setdefault('blank', True)
super().__init__(*args, **kwargs)

def to_python(self, value):
if value == 'true':
return True
Expand Down
4 changes: 2 additions & 2 deletions specifyweb/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
# just redirect root url to the main specify view
path('', skip_collection_access_check(RedirectView.as_view(url='/specify/'))),

re_path(r'^opensearch.xml$', search_view),
path('opensearch.xml', search_view),

# This is the main specify view.
# Every URL beginning with '/specify/' is handled
Expand All @@ -53,7 +53,7 @@
path('api/', include(api_urls)),
path('api/backup/', include(backup_urls)),
path('images/<path:path>', images),
re_path(r'^properties/(?P<name>.+).properties$', properties), # Note fully supported since remmoving dependence on specify.jar
path('properties/<path:name>.properties', properties), # Note fully supported since remmoving dependence on specify.jar

path('documentation/', include(doc_urls)),

Expand Down
Loading