Skip to content

Commit 9ad2984

Browse files
committed
Merge remote-tracking branch 'origin/production' into issue-6127
2 parents afe7be1 + 108eafe commit 9ad2984

File tree

13 files changed

+306
-23
lines changed

13 files changed

+306
-23
lines changed

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,35 @@ All notable changes to this project will be documented in this file.
88

99
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010

11+
## [7.10.2](https://github.com/specify/specify7/compare/v7.10.1...v7.10.2) (23 April 2025)
12+
13+
## Added
14+
15+
* **[Batch Editing](https://discourse.specifysoftware.org/t/batch-edit/2248)** for simple fields ([#5417](https://github.com/specify/specify7/pull/5417)*Requested by many, many members*)
16+
* [**Added a 'Download' button** to record set and query results attachments](https://discourse.specifysoftware.org/t/bulk-download-attachments-from-a-query-result-or-record-set/2456) ([#6052](https://github.com/specify/specify7/pull/6052)*Requested by The University of Michigan, Naturhistorisches Museum Bern, Beaty Biodiversity Museum, Agriculture and Agri-Food Canada, Muséum d'Histoire Naturelle Geneva, and Queensland Herbarium*)
17+
* The [Form Meta menu](https://discourse.specifysoftware.org/t/form-meta-menu/844) is now accessible when using "View in Forms" ([#5416](https://github.com/specify/specify7/pull/5416))
18+
* A default value can now be used for a query combo box ([#6037]([https://github.com/specify/specify7/pull/6037])*Requested by South African Institute for Aquatic Biodiversity and The Ohio State University Mollusk Division*)
19+
* Adds tooltip for required fields in bulk carry forward config ([#6202](https://github.com/specify/specify7/pull/6202))
20+
* Specify will use the default `TypeSearches` when a custom one is not defined for a table ([#6236](https://github.com/specify/specify7/pull/6236))
21+
* Adds a new `uniqueIdentifier` field to Storage ([#6249](https://github.com/specify/specify7/pull/6249))
22+
23+
## Changed
24+
25+
* Non-default Taxon trees can be deleted if they are not used ([#6186](https://github.com/specify/specify7/pull/6186))
26+
* Adds modern attachment placeholder icons for video, audio, text, and other attachment filetypes ([#6119](https://github.com/specify/specify7/pull/6119))
27+
28+
## Fixed
29+
30+
* Fixes an issue that caused some WorkBench data sets to match to the incorrect ranks ([#6322](https://github.com/specify/specify7/pull/6322))
31+
* Fixes an issue where the defaults for insect collections were missing ([#6383](https://github.com/specify/specify7/pull/6383))
32+
* Field formats in the Schema Config utility are selected automatically again ([#6255](https://github.com/specify/specify7/pull/6255))
33+
* Primary Collection Objects (COs) in a consolidated Collection Object Group (COG) can no longer be deleted unless another CO is marked as primary ([#6181](https://github.com/specify/specify7/pull/6181))
34+
* Fixes all cases where table aggregation separators were missing ([#6115](https://github.com/specify/specify7/pull/6115))
35+
* Fixes all cases where taxon tree structure is invalid due to accepted names not being designated as accepted ([#5366](https://github.com/specify/specify7/pull/5366))
36+
* Fixes all cases where coordinate text fields are empty but decimal coordinate fields contain values ([#5368](https://github.com/specify/specify7/pull/5368)*Reported by The University of Michigan, College of Idaho, and several others*)
37+
* Fixes an issue where the Data Entry screen does not appear in the 'Geology' discipline ([#6365](https://github.com/specify/specify7/pull/6365))
38+
* Fixes an issue where unsaved changes would be lost when toggling the "Use Localized Field Labels" setting in the Form Meta menu
39+
1140
## [7.10.1](https://github.com/specify/specify7/compare/v7.10.0...v7.10.1) (10 March 2025)
1241

1342
## Added

specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,35 @@ export const businessRuleDefs: MappedBusinessRuleDefs = {
659659
},
660660
},
661661
Preparation: {
662+
fieldChecks: {
663+
countAmt: async (prep): Promise<BusinessRuleResult | undefined> => {
664+
const loanPrep = await prep.rgetCollection('loanPreparations');
665+
const totalPrep = prep.get('countAmt') ?? 0;
666+
let totalPrepLoaned = 0;
667+
668+
loanPrep.models.forEach((loan) => {
669+
const quantity = loan.get('quantity') ?? 0;
670+
totalPrepLoaned += quantity;
671+
});
672+
673+
if (totalPrep < totalPrepLoaned) {
674+
setSaveBlockers(
675+
prep,
676+
prep.specifyTable.field.countAmt,
677+
[resourcesText.preparationUsedInLoan()],
678+
PREPARATION_LOANED_KEY
679+
);
680+
} else {
681+
setSaveBlockers(
682+
prep,
683+
prep.specifyTable.field.countAmt,
684+
[],
685+
PREPARATION_LOANED_KEY
686+
);
687+
}
688+
return undefined;
689+
},
690+
},
662691
onRemoved: (preparation, collection): void => {
663692
if (preparation.get('isOnLoan') === true) {
664693
setSaveBlockers(

specifyweb/frontend/js_src/lib/components/FormFields/Field.tsx

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,29 +128,56 @@ function Field({
128128

129129
const isNew = resource?.isNew();
130130
const isCO = resource?.specifyTable.name === 'CollectionObject';
131+
131132
const isPartOfCOG = isCO
132133
? resource?.get('cojo') !== null && resource?.get('cojo') !== undefined
133134
: false;
135+
136+
const hasParentCO = isCO
137+
? resource.get('parentCO') !== null &&
138+
resource.get('parentCO') !== undefined
139+
: false;
140+
134141
const isCatNumberField = field?.name === 'catalogNumber';
142+
135143
// Check if collection pref wants to inherit primary cat num for empty CO cat num sibilings inside of a COG
136144
const [displayPrimaryCatNumberPref] = collectionPreferences.use(
137145
'catalogNumberInheritance',
138146
'behavior',
139147
'inheritance'
140148
);
141-
const displayCatNumberPlaceHolder =
149+
150+
// Check if collection pref wants to inherit parent cat num for empty CO cat num children
151+
const [displayParentCatNumberPref] = collectionPreferences.use(
152+
'catalogNumberParentInheritance',
153+
'behavior',
154+
'inheritance'
155+
);
156+
157+
const displayPrimaryCatNumberPlaceHolder =
142158
isNew === false &&
143159
isCO &&
144160
isPartOfCOG &&
145161
isCatNumberField &&
146162
displayPrimaryCatNumberPref;
147163

164+
const displayParentCatNumberPlaceHolder =
165+
isNew === false &&
166+
isCO &&
167+
hasParentCO &&
168+
isCatNumberField &&
169+
displayParentCatNumberPref;
170+
148171
const [primaryCatalogNumber, setPrimaryCatalogNumber] = React.useState<
149172
string | null
150173
>(null);
151174

175+
const [parentCatalogNumber, setParentCatalogNumber] = React.useState<
176+
string | null
177+
>(null);
178+
152179
React.useEffect(() => {
153-
if (resource && displayCatNumberPlaceHolder) {
180+
if (resource && displayPrimaryCatNumberPlaceHolder) {
154181
ajax<string | null>('/api/specify/catalog_number_for_sibling/', {
155182
method: 'POST',
156183
headers: { Accept: 'application/json' },
@@ -162,18 +189,38 @@ function Field({
162189
.catch((error) => {
163190
console.error('Error fetching catalog number:', error);
164191
});
192+
} else if (resource && displayParentCatNumberPlaceHolder) {
193+
ajax<string | null>('/api/specify/catalog_number_from_parent/', {
194+
method: 'POST',
195+
headers: { Accept: 'application/json' },
196+
body: resource,
197+
})
198+
.then((response) => {
199+
setParentCatalogNumber(response.data);
200+
})
201+
.catch((error) => {
202+
console.error('Error fetching catalog number:', error);
203+
});
165204
}
166-
}, [resource, displayCatNumberPlaceHolder]);
205+
}, [
206+
resource,
207+
displayPrimaryCatNumberPlaceHolder,
208+
displayParentCatNumberPlaceHolder,
209+
]);
167210

168211
return (
169212
<Input.Generic
170213
forwardRef={validationRef}
171214
key={parser.title}
172215
name={name}
173216
placeholder={
174-
displayCatNumberPlaceHolder && typeof primaryCatalogNumber === 'string'
217+
displayPrimaryCatNumberPlaceHolder &&
218+
typeof primaryCatalogNumber === 'string'
175219
? primaryCatalogNumber
176-
: undefined
220+
: displayParentCatNumberPlaceHolder &&
221+
typeof parentCatalogNumber === 'string'
222+
? parentCatalogNumber
223+
: undefined
177224
}
178225
{...validationAttributes}
179226
className={

specifyweb/frontend/js_src/lib/components/Preferences/CollectionDefinitions.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,25 @@ export const collectionPreferenceDefinitions = {
108108
},
109109
},
110110
},
111+
catalogNumberParentInheritance: {
112+
title: queryText.catalogNumberParentCOInheritance(),
113+
subCategories: {
114+
behavior: {
115+
title: preferencesText.behavior(),
116+
items: {
117+
inheritance: definePref<boolean>({
118+
title: preferencesText.inheritanceCatNumberParentCOPref(),
119+
requiresReload: false,
120+
visible: false,
121+
defaultValue: false,
122+
renderer: f.never,
123+
container: 'label',
124+
type: 'java.lang.Boolean',
125+
}),
126+
},
127+
},
128+
},
129+
},
111130
} as const;
112131

113132
ensure<GenericPreferences>()(collectionPreferenceDefinitions);

specifyweb/frontend/js_src/lib/localization/preferences.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,4 +2022,9 @@ export const preferencesText = createDictionary({
20222022
'en-us':
20232023
'Enable the inheritance of the primary catalog number to its empty siblings.',
20242024
},
2025+
inheritanceCatNumberParentCOPref: {
2026+
// TODO: improve
2027+
'en-us':
2028+
'Enable the inheritance of the parent catalog number to its empty children.',
2029+
},
20252030
} as const);

specifyweb/frontend/js_src/lib/localization/query.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,9 @@ export const queryText = createDictionary({
905905
catalogNumberInheritance: {
906906
'en-us': 'Catalog Number Inheritance',
907907
},
908+
catalogNumberParentCOInheritance: {
909+
'en-us': 'Catalog Number Parent Collection Object Inheritance',
910+
},
908911
formatInputAs: {
909912
comment: `
910913
Used to indicate a text field will be formatted with a specific format.

specifyweb/frontend/js_src/lib/localization/resources.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,4 +871,7 @@ export const resourcesText = createDictionary({
871871
'en-us':
872872
'Determination does not belong to the taxon tree associated with the Collection Object Type',
873873
},
874+
preparationUsedInLoan: {
875+
'en-us': 'The preparation is used in a loan.',
876+
},
874877
} as const);

specifyweb/interactions/cog_preps.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,16 @@ def modify_update_of_loan_return_sibling_preps(original_interaction_obj, updated
530530
for loan_prep_idx in range(len(updated_interaction_data["loanpreparations"])):
531531
if type(updated_interaction_data["loanpreparations"]) is str:
532532
continue
533+
534+
loan_preps = updated_interaction_data.get("loanpreparations")
535+
# Skip if loan_preps is not a list or index is out of range
536+
if not isinstance(loan_preps, list) or loan_prep_idx >= len(loan_preps):
537+
continue
538+
539+
loan_prep = loan_preps[loan_prep_idx]
540+
if not isinstance(loan_prep, dict):
541+
continue
542+
533543
loan_return_data = (
534544
updated_interaction_data["loanpreparations"][loan_prep_idx][
535545
"loanreturnpreparations"

specifyweb/specify/urls.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
re_path(r'^specify/merge/abort/(?P<merge_id>[0-9a-fA-F-]+)/$', views.abort_merge_task),
1515

1616
# cat num for siblings
17-
re_path(r'^specify/catalog_number_for_sibling/(?P<model>\w+)/(?P<id>\d+)/$', views.catalog_number_for_sibling),
17+
re_path(r'^specify/catalog_number_for_sibling/$', views.catalog_number_for_sibling),
18+
19+
# cat num for parent
20+
re_path(r'^specify/catalog_number_from_parent/$', views.catalog_number_from_parent),
1821

1922
# the main business data API
2023
re_path(r'^specify_schema/openapi.json$', schema.openapi),

specifyweb/specify/utils.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,33 @@ def get_cat_num_inheritance_setting(collection, user) -> bool:
129129
except Exception as e:
130130
logger.warning(f"An unexpected error occurred: {e}")
131131

132-
return inheritance_enabled
132+
return inheritance_enabled
133+
134+
def get_parent_cat_num_inheritance_setting(collection, user) -> bool:
135+
import specifyweb.context.app_resource as app_resource
136+
137+
parent_inheritance_enabled: bool = False
138+
139+
try:
140+
collection_prefs_json, _, __ = app_resource.get_app_resource(collection, user, 'CollectionPreferences')
141+
142+
if collection_prefs_json is not None:
143+
collection_prefs_dict = json.loads(collection_prefs_json)
144+
145+
catalog_number_parent_inheritance = collection_prefs_dict.get('catalogNumberParentInheritance', {})
146+
behavior = catalog_number_parent_inheritance.get('behavior', {}) \
147+
if isinstance(catalog_number_parent_inheritance, dict) else {}
148+
parent_inheritance_enabled = behavior.get('inheritance', False) if isinstance(behavior, dict) else False
149+
150+
if not isinstance(parent_inheritance_enabled, bool):
151+
parent_inheritance_enabled = False
152+
153+
except json.JSONDecodeError:
154+
logger.warning(f"Error: Could not decode JSON for collection preferences")
155+
except TypeError as e:
156+
logger.warning(f"Error: Unexpected data structure in collection preferences: {e}")
157+
except Exception as e:
158+
logger.warning(f"An unexpected error occurred: {e}")
159+
160+
return parent_inheritance_enabled
161+

specifyweb/specify/views.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1412,4 +1412,40 @@ def catalog_number_for_sibling(request: http.HttpRequest):
14121412

14131413
except Exception as e:
14141414
print(f"Error processing request: {e}")
1415-
return http.JsonResponse({'error': 'An internal server error occurred.'}, status=500)
1415+
return http.JsonResponse({'error': 'An internal server error occurred.'}, status=500)
1416+
1417+
1418+
@login_maybe_required
1419+
@require_POST
1420+
def catalog_number_from_parent(request: http.HttpRequest):
1421+
"""
1422+
Returns the catalog number of the parent CO
1423+
"""
1424+
try:
1425+
request_data = json.loads(request.body)
1426+
object_id = request_data.get('id')
1427+
provided_catalog_number = request_data.get('catalognumber')
1428+
except json.JSONDecodeError:
1429+
return http.JsonResponse({'error': 'Invalid JSON body.'}, status=400)
1430+
1431+
if object_id is None:
1432+
return http.JsonResponse({'error': "'id' field is required."}, status=400)
1433+
1434+
if provided_catalog_number is not None:
1435+
return http.JsonResponse(None, safe=False)
1436+
1437+
try:
1438+
# Get the child CO
1439+
child = spmodels.Collectionobject.objects.get(id=object_id)
1440+
1441+
# Get the parent CO
1442+
parent = child.parentco
1443+
1444+
if parent and parent.catalognumber:
1445+
return http.JsonResponse(parent.catalognumber, safe=False)
1446+
else:
1447+
return http.JsonResponse({'error': 'Parent or parent catalog number not found.'}, status=404)
1448+
1449+
except Exception as e:
1450+
print(f"Error processing request: {e}")
1451+
return http.JsonResponse({'error': 'An internal server error occurred.'}, status=500)

0 commit comments

Comments
 (0)