Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3b0b36e
Initial Django 5.2 upgrade
acwhite211 May 1, 2026
eef1949
Remove old USE_L10N, not needed in new version
acwhite211 May 1, 2026
ed2cf1d
Fix ManyToManyField through_fields for Django upgrade
acwhite211 May 1, 2026
0033c8d
Replace the removed models.NullBooleanField inheritance with a local …
acwhite211 May 1, 2026
530e6a9
auto-update from django-update
g1rly-c0d3r Jul 9, 2026
02206b4
expose django settings to pytest
g1rly-c0d3r Jul 9, 2026
0f3c25a
Merge branch 'main' into issue-8046
g1rly-c0d3r Jul 14, 2026
ffecf67
Merge branch 'main' into issue-8046
g1rly-c0d3r Jul 15, 2026
aed5838
Lint code with ESLint and Prettier
g1rly-c0d3r Jul 15, 2026
686e165
fix: make logging out a `POST` request rather than a `GET` request
g1rly-c0d3r Jul 16, 2026
31c2acb
chore: error handling for when the logout request fails
g1rly-c0d3r Jul 17, 2026
6ef25c4
fix: revert unintended change to specify_settings.py
g1rly-c0d3r Jul 17, 2026
e2b0eee
fix: revert unintended change to specify_settings.py
g1rly-c0d3r Jul 17, 2026
a65c822
fix: remove unused import from `inheritence/urls.py`
g1rly-c0d3r Jul 17, 2026
b602879
Merge branch 'main' into issue-8046
g1rly-c0d3r Jul 17, 2026
82ee2fe
fix: update Missing agent to point to new logout endpoint
g1rly-c0d3r Jul 17, 2026
a963629
fix: update `ChooseCollection` to use the new api for logging out
g1rly-c0d3r Jul 17, 2026
15c82a2
fix: fix 421 error code caused by race condition from two `.then()` c…
g1rly-c0d3r Jul 20, 2026
3246bbd
Sync localization strings with Weblate
Jul 18, 2026
614994b
Sync localization strings with Weblate
Jul 18, 2026
ac18076
Sync localization strings with Weblate
Maxime-Griveau Jul 20, 2026
1dc34ca
SQUASH LATER: broken test
g1rly-c0d3r Jul 21, 2026
435c163
test: adding unit tests
g1rly-c0d3r Jul 22, 2026
219e60c
test: remove front-end logout tests
g1rly-c0d3r Jul 22, 2026
6a6d62d
Merge branch 'main' into issue-8046
g1rly-c0d3r Jul 22, 2026
6729ea7
fix: correctly call `userText.logOut()` for label
g1rly-c0d3r Jul 22, 2026
a684c35
chore: address coderabbit comments:
g1rly-c0d3r Jul 24, 2026
e2b7af4
fix: move network request into an effect
g1rly-c0d3r Jul 24, 2026
4da3228
fix:state LoadingContext as an effect dependency for loading
g1rly-c0d3r Jul 28, 2026
2476cf4
fix: accept a `nextUrl` in `Logout` so that `ChooseCollection`'s beha…
g1rly-c0d3r Jul 28, 2026
d68dac5
Merge branch 'main' into issue-8046
g1rly-c0d3r Jul 28, 2026
75026ea
Lint code with ESLint and Prettier
g1rly-c0d3r Jul 28, 2026
85d7ac2
fix: re-name migrations to not have multiple leaf nodes
g1rly-c0d3r Jul 28, 2026
783568d
Merge branch 'issue-8046' of github.com:specify/specify7 into issue-8046
g1rly-c0d3r Jul 28, 2026
45575d7
Merge branch 'main' into issue-8046
g1rly-c0d3r Jul 31, 2026
a6f6020
fix: set `USE_TZ` in our django settings to match pre-5.0 behavior
g1rly-c0d3r Jul 31, 2026
d339542
merge main into issue-8046
g1rly-c0d3r Aug 12, 2026
da80d86
Merge branch 'main' into issue-8046
g1rly-c0d3r Aug 12, 2026
8026fbc
fix: merge conflict
g1rly-c0d3r Aug 12, 2026
0d67b9c
Merge branch 'main' into issue-8046
g1rly-c0d3r Aug 13, 2026
1da6d88
fix: re-order migrations to ensure no migration errors occur compared…
g1rly-c0d3r Aug 13, 2026
1c8724f
Merge branch 'main' into issue-8046
CarolineDenis Aug 18, 2026
b0b0ca2
fix: explicitly cast return value of `Spdataset.was_uploaded` to `boo…
g1rly-c0d3r Aug 19, 2026
69b35c7
fix: remove type annotations for children of `django.db.models.Model`…
g1rly-c0d3r Aug 19, 2026
b8b93bb
fix: cast `specifyuser` to `Specifyuser` instead of `AbstractBaseUser…
g1rly-c0d3r Aug 19, 2026
c3189c5
fix: add `objects` field to `ModelWithTable` class to suppress `mypy`…
g1rly-c0d3r Aug 19, 2026
9d7e4de
fix: disable type checking for 1 (one) line in our tests because `myp…
g1rly-c0d3r Aug 19, 2026
4b274c8
Merge branch 'main' into issue-8046
g1rly-c0d3r Aug 19, 2026
87d6731
Merge branch 'main' into issue-8046
CarolineDenis Aug 26, 2026
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
12 changes: 8 additions & 4 deletions requirements-testing.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
django-stubs==1.12.0
mypy==1.5.1
# django-stubs==1.12.0
django-stubs==5.2.9
mypy==1.13.0
hypothesis>=6.131.9,<7
hypothesis-jsonschema==0.19.0
types-requests==2.28.5
lxml
coverage
pytest-django
debugpy==1.6.5
pytest==7.2.1
pytest-django==4.12.0
django-extensions==4.1
coverage==7.15.2
6 changes: 3 additions & 3 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
PyJWT==2.13.0
django-auth-ldap==5.3.0
jsonschema==4.0.1
typing-extensions==4.12.2
stream-zip==0.0.83
Expand Down
2 changes: 1 addition & 1 deletion specifyweb/backend/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def oic_callback(request: http.HttpRequest) -> http.HttpResponse:
spuserexternalid.save()

login(request,
cast(AbstractBaseUser, spuserexternalid.specifyuser),
cast(Specifyuser, spuserexternalid.specifyuser),
backend='django.contrib.auth.backends.ModelBackend')
return http.HttpResponseRedirect('/accounts/choose_collection')

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/<str: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
8 changes: 4 additions & 4 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 path

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),
]
15 changes: 7 additions & 8 deletions specifyweb/backend/setup_tool/urls.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@

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_setup_progress),
path('setup_progress/', views.get_setup_progress),
path('config_progress/', views.get_setup_progress),
path('setup_database/create/', views.setup_database_view),

re_path(r'^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 All @@ -22,4 +21,4 @@
# re_path(f'^taxontreedef/create/$', views.create_taxon_tree_view),
# re_path(f'^collection/create/$', views.create_collection_view),
# re_path(f'^specifyuser/create/$', views.create_specifyuser_view),
]
]
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),
]
4 changes: 2 additions & 2 deletions specifyweb/backend/workbench/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def get_dataset_as_dict(self):
return ds_dict

def was_uploaded(self) -> bool:
return self.uploadresult and self.uploadresult["success"]
return bool(self.uploadresult and self.uploadresult["success"])

class Spdatasetattachment(models.Model):
specify_model = datamodel.get_table_strict('spdatasetattachment')
Expand Down Expand Up @@ -202,4 +202,4 @@ class Meta:
]


# save = partialmethod(custom_save)
# save = partialmethod(custom_save)
9 changes: 6 additions & 3 deletions specifyweb/backend/workbench/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,11 @@ def test_create_record_set(self) -> None:
no_commit=False,
allow_partial=False,
)

self.assertTrue(dataset.uploadresult["success"])

# ignoring this mympy type error becuase mypy deduces that the type of `uploadresult` is
# Any | None. we know it is a JSONField, but since that is a field of the parent of Spdataset, mypy can't find it.
# TODO (mypy): figure out how to tell mypy that uploadresult is iterable
self.assertTrue(dataset.uploadresult["success"]) #type: ignore

response = c.post(
f"/api/workbench/create_recordset/{datasetid}/",
Expand Down Expand Up @@ -337,4 +340,4 @@ def test_transfer_dataset_rejects_invalid_user(self) -> None:
self.assertEqual(response.status_code, 400)
self.dataset.refresh_from_db()
self.assertEqual(self.dataset.specifyuser, self.specifyuser)
self.assertFalse(Message.objects.exists())
self.assertFalse(Message.objects.exists())
6 changes: 3 additions & 3 deletions specifyweb/backend/workbench/upload/predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def canonicalize_remove_node(node: ToRemoveNode) -> Q:


def _map_matchee(matchee: list[ToRemoveMatchee], model_name: str) -> Exists:
model: Model = get_model(model_name)
model = get_model(model_name)
qs = [Q(**match["filter_on"]) for match in matchee]
qs_or = Func.make_ors(qs)
query = model.objects.filter(qs_or)
Expand All @@ -309,7 +309,7 @@ class ContetRef(Exception):
]


def safe_fetch(model: Model, filters, version):
def safe_fetch(model, filters, version):
if filters is None:
return None
try:
Expand Down Expand Up @@ -354,4 +354,4 @@ def resolve_reference_attributes(

clone_attrs = {field: getattr(reference_record, field) for field in all_fields}

return clone_attrs
return clone_attrs
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,9 @@ function Wrapped({
<Button.BorderedGray
onClick={(): void =>
loading(
ping('/accounts/logout/').then(() =>
ping('/accounts/logout/', {method: 'POST'}).then(() =>
globalThis.location.assign(
formatUrl('/accounts/logout/', { next: nextUrl })
formatUrl('/specify/command/logout/', { next: nextUrl })
)
)
)
Expand Down
2 changes: 1 addition & 1 deletion specifyweb/frontend/js_src/lib/components/Core/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function MissingAgent(): JSX.Element {
}}
forceToTop
header={userText.noAgent()}
onClose={(): void => globalThis.location.assign('/accounts/logout/')}
onClose={(): void => globalThis.location.assign('/specify/command/logout/')}
>
{userText.noAgentDescription()}
</Dialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const rawUserTools = ensure<IR<IR<Omit<MenuItem, 'name'>>>>()({
[userText.userAccount()]: {
logOut: {
title: userText.logOut(),
url: '/accounts/logout/',
url: '/specify/command/logout/',
icon: icons.logout,
enabled: () => userInformation.isauthenticated,
onClick: async () =>
Expand Down
28 changes: 28 additions & 0 deletions specifyweb/frontend/js_src/lib/components/Logout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { useSearchParameter } from '../../hooks/navigation';
import { ajax } from '../../utils/ajax';
import { LoadingScreen } from '../Molecules/Dialog';
import { formatUrl } from '../Router/queryString';
import { LoadingContext } from '../Core/Contexts';
import { softError } from '../Errors/assert';

export function Logout(): JSX.Element {
const loading = React.useContext(LoadingContext);
const [nextUrl = '/specify/'] = useSearchParameter('nextUrl');

console.log('next => ' + nextUrl);

React.useEffect(() => {
loading(
ajax<string>('/accounts/logout/', { method: 'POST', headers: {} })
.catch((error) => softError(error))
.finally(() =>
globalThis.location.assign(
formatUrl('/accounts/login/', { next: nextUrl })
)
)
);
}, [loading]);

return <LoadingScreen />;
}
8 changes: 8 additions & 0 deletions specifyweb/frontend/js_src/lib/components/Router/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,14 @@ export const routes: RA<EnhancedRoute> = [
({ CacheBuster }) => CacheBuster
),
},
{
path: 'logout',
title: userText.logOut(),
element: () =>
import('../Logout').then(
({ Logout }) => Logout
)
},
],
},
{
Expand Down
6 changes: 2 additions & 4 deletions specifyweb/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,12 @@ def get_sa_db_url(db_name):

SITE_ID = 1

USE_TZ=False

# If you set this to False, Django will make some optimizations so as not
# 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
2 changes: 1 addition & 1 deletion specifyweb/specify/migrations/0045_add_indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
class Migration(migrations.Migration):

dependencies = [
('specify', '0044_alter_deletion_cascade'),
('specify', '0044_alter_deletion_cascade')
]

operations = [
Expand Down
Loading
Loading