Skip to content

Commit b98ecce

Browse files
committed
Merge tag '25.05.4' into develop
New collection category
2 parents 5fe96bd + c1adb40 commit b98ecce

File tree

13 files changed

+130
-5
lines changed

13 files changed

+130
-5
lines changed

admin/collection_providers/forms.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class CollectionProviderForm(forms.ModelForm):
1717
study_design_choices = forms.CharField(widget=forms.HiddenInput(), required=False)
1818
data_type_choices = forms.CharField(widget=forms.HiddenInput(), required=False)
1919
disease_choices = forms.CharField(widget=forms.HiddenInput(), required=False)
20+
grade_levels_choices = forms.CharField(widget=forms.HiddenInput(), required=False)
2021
_id = forms.SlugField(
2122
required=True,
2223
help_text='URL Slug',
@@ -330,3 +331,33 @@ def clean_data_type_choices(self):
330331
if choices:
331332
added_choices = json.loads(choices)
332333
return {'added': added_choices, 'removed': removed_choices}
334+
335+
def clean_grade_levels_choices(self):
336+
if not self.data.get('grade_levels_choices'):
337+
return {'added': [], 'removed': []}
338+
339+
collection_provider = self.instance
340+
primary_collection = collection_provider.primary_collection
341+
if primary_collection: # Modifying an existing CollectionProvider
342+
old_choices = {c.strip(' ') for c in primary_collection.grade_levels_choices}
343+
updated_choices = {c.strip(' ') for c in json.loads(self.data.get('grade_levels_choices'))}
344+
added_choices = updated_choices - old_choices
345+
removed_choices = old_choices - updated_choices
346+
347+
active_removed_choices = set(
348+
primary_collection.collectionsubmission_set.filter(
349+
data_type__in=removed_choices
350+
).values_list('grade_levels', flat=True)
351+
)
352+
if active_removed_choices:
353+
raise forms.ValidationError(
354+
'Cannot remove the following choices for "grade_levels", as they are '
355+
f'currently in use: {active_removed_choices}'
356+
)
357+
else: # Creating a new CollectionProvider
358+
added_choices = set()
359+
removed_choices = set()
360+
choices = self.data.get('grade_levels_choices')
361+
if choices:
362+
added_choices = json.loads(choices)
363+
return {'added': added_choices, 'removed': removed_choices}

admin/collection_providers/views.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ def form_valid(self, form):
6262
self.object.primary_collection.data_type_choices.append(item)
6363
for item in form.cleaned_data['disease_choices']['added']:
6464
self.object.primary_collection.disease_choices.append(item)
65+
for item in form.cleaned_data['grade_levels_choices']['added']:
66+
self.object.primary_collection.grade_levels_choices.append(item)
6567
self.object.primary_collection.save()
6668
return super().form_valid(form)
6769

@@ -188,6 +190,11 @@ def get_context_data(self, *args, **kwargs):
188190
))
189191
kwargs['data_type_choices'] = data_type_choices_html
190192

193+
grade_levels_choices_html = '<ul>{choices}</ul>'.format(choices=''.join(
194+
f'<li>{choice}</li>' for choice in primary_collection.grade_levels_choices
195+
))
196+
kwargs['grade_levels_choices'] = grade_levels_choices_html
197+
191198
# get a dict of model fields so that we can set the initial value for the update form
192199
fields = model_to_dict(collection_provider)
193200
fields['collected_type_choices'] = json.dumps(primary_collection.collected_type_choices)
@@ -202,6 +209,7 @@ def get_context_data(self, *args, **kwargs):
202209
fields['study_design_choices'] = json.dumps(primary_collection.study_design_choices)
203210
fields['data_type_choices'] = json.dumps(primary_collection.data_type_choices)
204211
fields['disease_choices'] = json.dumps(primary_collection.disease_choices)
212+
fields['grade_levels_choices'] = json.dumps(primary_collection.grade_levels_choices)
205213

206214
# compile html list of collected_type_choices
207215
if collection_provider.primary_collection:
@@ -262,7 +270,7 @@ class CollectionProviderChangeForm(PermissionRequiredMixin, UpdateView):
262270

263271
def form_valid(self, form):
264272
if self.object.primary_collection:
265-
for choices_name in ['collected_type', 'status', 'issue', 'volume', 'program_area', 'school_type', 'study_design', 'data_type', 'disease']:
273+
for choices_name in ['collected_type', 'status', 'issue', 'volume', 'program_area', 'school_type', 'study_design', 'data_type', 'disease', 'grade_levels']:
266274
_process_collection_choices(self.object, choices_name, form)
267275
self.object.primary_collection.save()
268276
return super().form_valid(form)
@@ -402,6 +410,7 @@ def create_or_update_provider(self, provider_data):
402410
provider.primary_collection.study_design_choices = primary_collection['fields']['study_design_choices']
403411
provider.primary_collection.disease_choices = primary_collection['fields']['disease_choices']
404412
provider.primary_collection.data_type_choices = primary_collection['fields']['data_type_choices']
413+
provider.primary_collection.data_type_choices = primary_collection['fields']['grade_levels_choices']
405414
provider.primary_collection.save()
406415
if licenses:
407416
provider.licenses_acceptable.set(licenses)

admin/static/js/pages/collection-provider-page.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ $('#tags-input-disease').on('itemRemoved', function(event) {
7373
$('#id_disease_choices').val(JSON.stringify($('#tags-input-disease').tagsinput('items')));
7474
});
7575

76+
$('#tags-input-grade-levels').on('itemAdded', function(event) {
77+
$('#id_grade_levels_choices').val(JSON.stringify($('#tags-input-grade-levels').tagsinput('items')));
78+
});
79+
80+
$('#tags-input-grade-levels').on('itemRemoved', function(event) {
81+
$('#id_grade_levels_choices').val(JSON.stringify($('#tags-input-grade-levels').tagsinput('items')));
82+
});
83+
7684

7785
$(document).ready(function() {
7886
var collectedTypeItems = JSON.parse($('#id_collected_type_choices').val());
@@ -119,4 +127,9 @@ $(document).ready(function() {
119127
dataTypeItems.forEach(function(element){
120128
$('#tags-input-data-type').tagsinput('add', element)
121129
});
130+
131+
var dataTypeItems = JSON.parse($('#id_grade_levels_choices').val());
132+
dataTypeItems.forEach(function(element){
133+
$('#tags-input-grade-levels').tagsinput('add', element)
134+
});
122135
});

admin/templates/collection_providers/detail.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ <h2>{{ collection_provider.name }}</h2>
7474
<th>data_type_choices</th>
7575
<td>{{ data_type_choices | safe}}</td>
7676
</tr>
77+
<tr>
78+
<th>grade_levels_choices</th>
79+
<td>{{ grade_levels_choices | safe}}</td>
80+
</tr>
7781
</table>
7882
</div>
7983
</div>

admin/templates/collection_providers/update_collection_provider_form.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@
110110
<input id="tags-input-data-type" type="text" data-role="tagsinput"/>
111111
</div>
112112
</div>
113+
<div>
114+
<label>Grade Levels choices:</label>
115+
<div class=#bootstrap-tagsinput">
116+
<input id="tags-input-grade-levels" type="text" data-role="tagsinput"/>
117+
</div>
118+
</div>
113119
<input class="form-button" type="submit" value="Save" />
114120
</form>
115121
</div>

api/collections/serializers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ class CollectionSerializer(JSONAPISerializer):
7676
child=ser.CharField(max_length=127),
7777
default=list(),
7878
)
79+
grade_levels_choices = ser.ListField(
80+
child=ser.CharField(max_length=127),
81+
default=list(),
82+
)
7983

8084
links = LinksField({})
8185

@@ -251,6 +255,7 @@ def subjects_view_kwargs(self):
251255
study_design = ser.CharField(required=False)
252256
data_type = ser.CharField(required=False)
253257
disease = ser.CharField(required=False)
258+
grade_levels = ser.CharField(required=False)
254259

255260
def get_absolute_url(self, obj):
256261
return absolute_reverse(
@@ -286,6 +291,8 @@ def update(self, obj, validated_data):
286291
obj.data_type = validated_data.pop('data_type')
287292
if 'disease' in validated_data:
288293
obj.disease = validated_data.pop('disease')
294+
if 'grade_levels' in validated_data:
295+
obj.grade_levels = validated_data.pop('grade_levels')
289296

290297
obj.save()
291298
return obj
@@ -353,6 +360,7 @@ def subjects_view_kwargs(self):
353360
study_design = ser.CharField(required=False)
354361
date_type = ser.CharField(required=False)
355362
disease = ser.CharField(required=False)
363+
grade_levels = ser.CharField(required=False)
356364

357365
def get_absolute_url(self, obj):
358366
return absolute_reverse(
@@ -388,6 +396,8 @@ def update(self, obj, validated_data):
388396
obj.data_type = validated_data.pop('data_type')
389397
if 'disease' in validated_data:
390398
obj.disease = validated_data.pop('disease')
399+
if 'grade_levels' in validated_data:
400+
obj.grade_levels = validated_data.pop('grade_levels')
391401

392402
obj.save()
393403
return obj

api_tests/search/views/test_views.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def collection_public(self, user):
5151
status_choices=['', 'asdf', 'lkjh'], collected_type_choices=['', 'asdf', 'lkjh'],
5252
issue_choices=['', '0', '1', '2'], volume_choices=['', '0', '1', '2'],
5353
disease_choices=['illness'], data_type_choices=['realness'],
54-
program_area_choices=['', 'asdf', 'lkjh'])
54+
program_area_choices=['', 'asdf', 'lkjh'], grade_levels_choices=['super', 'cool'])
5555

5656
@pytest.fixture()
5757
def registration_collection(self, user):
@@ -908,7 +908,7 @@ def test_POST_search_collections(
908908
registration_two, registration_private, reg_with_abstract):
909909
collection_public.collect_object(node_one, user, status='asdf', issue='0', volume='1', program_area='asdf')
910910
collection_public.collect_object(node_two, user, collected_type='asdf', status='lkjh')
911-
collection_public.collect_object(node_with_abstract, user, status='asdf')
911+
collection_public.collect_object(node_with_abstract, user, status='asdf', grade_levels='super')
912912
collection_public.collect_object(node_private, user, status='asdf', collected_type='asdf')
913913

914914
registration_collection.collect_object(registration_one, user, status='asdf')
@@ -984,6 +984,11 @@ def test_POST_search_collections(
984984
actual_ids = self.get_ids(res.json['data'])
985985
assert node_one._id in actual_ids
986986

987+
payload = self.post_payload(gradeLevels='super')
988+
res = app.post_json_api(url_collection_search, payload)
989+
assert res.status_code == 200
990+
assert res.json['links']['meta']['total'] == 1
991+
987992
# test_search_abstract_keyword_and_filter
988993
payload = self.post_payload(q='Khadja', status='asdf')
989994
res = app.post_json_api(url_collection_search, payload)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 4.2.15 on 2025-03-27 11:54
2+
3+
import django.contrib.postgres.fields
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('osf', '0027_alter_guidversionsthrough_object_id'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='collection',
16+
name='grade_levels_choices',
17+
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=127), blank=True, default=list, size=None),
18+
),
19+
migrations.AddField(
20+
model_name='collectionsubmission',
21+
name='grade_levels',
22+
field=models.CharField(blank=True, max_length=127),
23+
),
24+
]

osf/models/collection.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class Meta:
5757
study_design_choices = ArrayField(models.CharField(max_length=127), blank=True, default=list)
5858
disease_choices = ArrayField(models.CharField(max_length=127), blank=True, default=list)
5959
data_type_choices = ArrayField(models.CharField(max_length=127), blank=True, default=list)
60+
grade_levels_choices = ArrayField(models.CharField(max_length=127), blank=True, default=list)
6061
is_public = models.BooleanField(default=False, db_index=True)
6162
is_promoted = models.BooleanField(default=False, db_index=True)
6263
is_bookmark_collection = models.BooleanField(default=False, db_index=True)
@@ -161,8 +162,9 @@ def has_permission(self, user, perm):
161162
return user.has_perms(self.groups[perm], self)
162163

163164
def collect_object(
164-
self, obj, collector, collected_type=None, status=None, volume=None, issue=None,
165-
program_area=None, school_type=None, study_design=None, data_type=None, disease=None):
165+
self, obj, collector, collected_type=None, status=None, volume=None,
166+
issue=None, program_area=None, school_type=None, study_design=None,
167+
data_type=None, disease=None, grade_levels=None):
166168
""" Adds object to collection, creates CollectionSubmission reference
167169
Performs type / metadata validation. User permissions checked in view.
168170
@@ -181,6 +183,7 @@ def collect_object(
181183
study_design = study_design or ''
182184
data_type = data_type or ''
183185
disease = disease or ''
186+
grade_levels = grade_levels or ''
184187

185188
if not self.collected_type_choices and collected_type:
186189
raise ValidationError('May not specify "type" for this collection')
@@ -236,6 +239,12 @@ def collect_object(
236239
elif data_type not in self.data_type_choices:
237240
raise ValidationError(f'"{data_type}" is not an acceptable "data_type" for this collection')
238241

242+
if grade_levels:
243+
if not self.grade_levels_choices:
244+
raise ValidationError('May not specify "grade_levels" for this collection')
245+
elif grade_levels not in self.grade_levels_choices:
246+
raise ValidationError(f'"{grade_levels}" is not an acceptable "grade_levels" for this collection')
247+
239248
if not any([isinstance(obj, t.model_class()) for t in self.collected_types.all()]):
240249
# Not all objects have a content_type_pk, have to look the other way.
241250
# Ideally, all objects would, and we could do:
@@ -266,6 +275,7 @@ def collect_object(
266275
collection_submission.study_design = study_design
267276
collection_submission.data_type = data_type
268277
collection_submission.disease = disease
278+
collection_submission.grade_levels = grade_levels
269279
collection_submission.save()
270280

271281
return collection_submission

osf/models/collection_submission.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class Meta:
4848
blank=True,
4949
max_length=127
5050
)
51+
grade_levels = models.CharField(blank=True, max_length=127)
5152
machine_state = models.IntegerField(
5253
choices=CollectionSubmissionStates.int_field_choices(),
5354
default=CollectionSubmissionStates.IN_PROGRESS,

website/project/views/node.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,7 @@ def serialize_collections(collection_submissions, auth):
956956
'program_area': collection_submission.program_area,
957957
'disease': collection_submission.disease,
958958
'data_type': collection_submission.data_type,
959+
'grade_levels': collection_submission.grade_levels,
959960
'state': collection_submission.state.db_name,
960961
'subjects': list(collection_submission.subjects.values_list('text', flat=True)),
961962
'is_public': collection_submission.collection.is_public,

website/search/elastic_search.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ def serialize_collection_submission(collection_submission):
602602
'studyDesign': collection_submission.study_design,
603603
'disease': collection_submission.disease,
604604
'dataType': collection_submission.data_type,
605+
'gradeLevels': collection_submission.grade_levels,
605606
'subjects': list(collection_submission.subjects.values_list('text', flat=True)),
606607
'title': getattr(obj, 'title', ''),
607608
'url': getattr(obj, 'url', ''),

website/templates/project/project.mako

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,11 @@
415415
</div>
416416
% endif
417417
<hr>
418+
% if collection['grade_levels']:
419+
<div style="padding-left: 30px;">
420+
Grade Levels: <i>${collection['grade_levels']}</i>
421+
</div>
422+
% endif
418423
% elif collection['state'] == 'pending' and user['is_contributor_or_group_member']:
419424
% if user['is_admin']:
420425
<a class="fa fa-close collections-cancel-icon pull-right" collection_id=${collection['collection_id']} node_id=${collection['node_id']} aria-label="Cancel Submission Request Button"></a>
@@ -475,6 +480,11 @@
475480
Data Type: <i>${collection['data_type']}</i>
476481
</div>
477482
% endif
483+
% if collection['grade_levels']:
484+
<div style="padding-left: 30px;">
485+
Grade Levels: <i>${collection['grade_levels']}</i>
486+
</div>
487+
% endif
478488
<hr>
479489
% elif collection['state'] == 'rejected' and user['is_contributor_or_group_member']:
480490
% if user['is_admin']:

0 commit comments

Comments
 (0)