Skip to content

Commit fbed6b4

Browse files
committed
Merge branch 'dev' into dependabot/pip/django-model-utils-4.5.1
2 parents bb67cb2 + c54b126 commit fbed6b4

File tree

11 files changed

+567
-42
lines changed

11 files changed

+567
-42
lines changed

sde_collections/admin.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from .models.candidate_url import CandidateURL, ResolvedTitle
77
from .models.collection import Collection, WorkflowHistory
8-
from .models.pattern import IncludePattern, TitlePattern
8+
from .models.pattern import DivisionPattern, IncludePattern, TitlePattern
99
from .tasks import import_candidate_urls_from_api
1010

1111

@@ -191,6 +191,7 @@ class CollectionAdmin(admin.ModelAdmin, ExportCsvMixin, UpdateConfigMixin):
191191
"update_frequency",
192192
"source",
193193
"turned_on",
194+
"is_multi_division",
194195
),
195196
},
196197
),
@@ -221,9 +222,10 @@ class CollectionAdmin(admin.ModelAdmin, ExportCsvMixin, UpdateConfigMixin):
221222
"url",
222223
"division",
223224
"new_collection",
225+
"is_multi_division",
224226
)
225227
readonly_fields = ("config_folder",)
226-
list_filter = ("division", "curation_status", "workflow_status", "turned_on")
228+
list_filter = ("division", "curation_status", "workflow_status", "turned_on", "is_multi_division")
227229
search_fields = ("name", "url", "config_folder")
228230
actions = [
229231
generate_deployment_message,
@@ -292,8 +294,14 @@ class ResolvedTitleAdmin(admin.ModelAdmin):
292294
list_display = ["title_pattern", "candidate_url", "resolved_title", "created_at"]
293295

294296

297+
class DivisionPatternAdmin(admin.ModelAdmin):
298+
list_display = ("collection", "match_pattern", "division")
299+
search_fields = ("match_pattern", "division")
300+
301+
295302
admin.site.register(WorkflowHistory, WorkflowHistoryAdmin)
296303
admin.site.register(CandidateURL, CandidateURLAdmin)
297304
admin.site.register(TitlePattern, TitlePatternAdmin)
298305
admin.site.register(IncludePattern)
299306
admin.site.register(ResolvedTitle, ResolvedTitleAdmin)
307+
admin.site.register(DivisionPattern, DivisionPatternAdmin)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Generated by Django 4.2.9 on 2024-08-26 02:17
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("sde_collections", "0057_alter_collection_workflow_status_and_more"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="candidateurl",
16+
name="division",
17+
field=models.IntegerField(
18+
choices=[
19+
(1, "Astrophysics"),
20+
(2, "Biological and Physical Sciences"),
21+
(3, "Earth Science"),
22+
(4, "Heliophysics"),
23+
(5, "Planetary Science"),
24+
(6, "General"),
25+
],
26+
null=True,
27+
),
28+
),
29+
migrations.AddField(
30+
model_name="collection",
31+
name="is_multi_division",
32+
field=models.BooleanField(default=False, verbose_name="Is Multi-Division?"),
33+
),
34+
migrations.CreateModel(
35+
name="DivisionPattern",
36+
fields=[
37+
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
38+
(
39+
"match_pattern",
40+
models.CharField(
41+
help_text="This pattern is compared against the URL of all the documents in the collection and matching documents will be returned",
42+
verbose_name="Pattern",
43+
),
44+
),
45+
(
46+
"match_pattern_type",
47+
models.IntegerField(choices=[(1, "Individual URL Pattern"), (2, "Multi-URL Pattern")], default=1),
48+
),
49+
(
50+
"division",
51+
models.IntegerField(
52+
choices=[
53+
(1, "Astrophysics"),
54+
(2, "Biological and Physical Sciences"),
55+
(3, "Earth Science"),
56+
(4, "Heliophysics"),
57+
(5, "Planetary Science"),
58+
(6, "General"),
59+
]
60+
),
61+
),
62+
(
63+
"candidate_urls",
64+
models.ManyToManyField(related_name="%(class)s_urls", to="sde_collections.candidateurl"),
65+
),
66+
(
67+
"collection",
68+
models.ForeignKey(
69+
on_delete=django.db.models.deletion.CASCADE,
70+
related_name="%(class)s",
71+
related_query_name="%(class)ss",
72+
to="sde_collections.collection",
73+
),
74+
),
75+
],
76+
options={
77+
"verbose_name": "Division Pattern",
78+
"verbose_name_plural": "Division Patterns",
79+
"unique_together": {("collection", "match_pattern")},
80+
},
81+
),
82+
]

sde_collections/models/candidate_url.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.db import models
66

77
from .collection import Collection
8-
from .collection_choice_fields import DocumentTypes
8+
from .collection_choice_fields import Divisions, DocumentTypes
99
from .pattern import ExcludePattern, TitlePattern
1010

1111

@@ -57,6 +57,7 @@ class CandidateURL(models.Model):
5757
visited = models.BooleanField(default=False)
5858
objects = CandidateURLManager()
5959
document_type = models.IntegerField(choices=DocumentTypes.choices, null=True)
60+
division = models.IntegerField(choices=Divisions.choices, null=True)
6061
inferenced_by = models.CharField(
6162
"Inferenced By",
6263
default="",

sde_collections/models/collection.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class Collection(models.Model):
4646
document_type = models.IntegerField(choices=DocumentTypes.choices, default=DocumentTypes.DOCUMENTATION)
4747
tree_root_deprecated = models.CharField("Tree Root", max_length=1024, default="", blank=True)
4848
delete = models.BooleanField(default=False)
49+
is_multi_division = models.BooleanField("Is Multi-Division?", default=False)
4950

5051
# audit columns for production
5152
audit_hierarchy = models.CharField("Audit Hierarchy", max_length=2048, default="", blank=True)

sde_collections/models/pattern.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
parse_title,
1111
resolve_title,
1212
)
13-
from .collection_choice_fields import DocumentTypes
13+
from .collection_choice_fields import Divisions, DocumentTypes
1414

1515

1616
class BaseMatchPattern(models.Model):
@@ -250,6 +250,29 @@ class Meta:
250250
unique_together = ("collection", "match_pattern")
251251

252252

253+
class DivisionPattern(BaseMatchPattern):
254+
division = models.IntegerField(choices=Divisions.choices)
255+
256+
def apply(self) -> None:
257+
matched_urls = self.matched_urls()
258+
matched_urls.update(division=self.division)
259+
candidate_url_ids = list(matched_urls.values_list("id", flat=True))
260+
self.candidate_urls.through.objects.bulk_create(
261+
objs=[
262+
DivisionPattern.candidate_urls.through(candidateurl_id=candidate_url_id, divisionpattern_id=self.id)
263+
for candidate_url_id in candidate_url_ids
264+
]
265+
)
266+
267+
def unapply(self) -> None:
268+
self.candidate_urls.update(division=None)
269+
270+
class Meta:
271+
verbose_name = "Division Pattern"
272+
verbose_name_plural = "Division Patterns"
273+
unique_together = ("collection", "match_pattern")
274+
275+
253276
# @receiver(post_save, sender=TitlePattern)
254277
# def post_save_handler(sender, instance, created, **kwargs):
255278
# if created:

sde_collections/serializers.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
from .models.candidate_url import CandidateURL
44
from .models.collection import Collection, WorkflowHistory
5-
from .models.collection_choice_fields import DocumentTypes
5+
from .models.collection_choice_fields import Divisions, DocumentTypes
66
from .models.pattern import (
7+
DivisionPattern,
78
DocumentTypePattern,
89
ExcludePattern,
910
IncludePattern,
@@ -56,6 +57,7 @@ class Meta:
5657
class CandidateURLSerializer(serializers.ModelSerializer):
5758
excluded = serializers.BooleanField(required=False)
5859
document_type_display = serializers.CharField(source="get_document_type_display", read_only=True)
60+
division_display = serializers.CharField(source="get_division_display", read_only=True)
5961
url = serializers.CharField(required=False)
6062
generated_title_id = serializers.SerializerMethodField(read_only=True)
6163
match_pattern_type = serializers.SerializerMethodField(read_only=True)
@@ -86,6 +88,8 @@ class Meta:
8688
"candidate_urls_count",
8789
"document_type",
8890
"document_type_display",
91+
"division",
92+
"division_display",
8993
"visited",
9094
"test_title",
9195
"production_title",
@@ -135,7 +139,13 @@ def get_file_extension(self, obj):
135139
return obj.fileext
136140

137141
def get_tree_root(self, obj):
138-
return obj.collection.tree_root
142+
if obj.collection.is_multi_division:
143+
if obj.division:
144+
return f"/{obj.get_division_display()}/{obj.collection.config_folder}"
145+
else:
146+
return f"/{obj.collection.get_division_display()}/{obj.collection.config_folder}"
147+
else:
148+
return obj.collection.tree_root
139149

140150

141151
class BasePatternSerializer(serializers.ModelSerializer):
@@ -212,3 +222,26 @@ def validate_match_pattern(self, value):
212222
except DocumentTypePattern.DoesNotExist:
213223
pass
214224
return value
225+
226+
227+
class DivisionPatternSerializer(BasePatternSerializer, serializers.ModelSerializer):
228+
division_display = serializers.CharField(source="get_division_display", read_only=True)
229+
division = serializers.ChoiceField(choices=Divisions.choices)
230+
231+
class Meta:
232+
model = DivisionPattern
233+
fields = BasePatternSerializer.Meta.fields + (
234+
"division",
235+
"division_display",
236+
)
237+
238+
def validate_match_pattern(self, value):
239+
try:
240+
division_pattern = DivisionPattern.objects.get(
241+
match_pattern=value,
242+
match_pattern_type=DivisionPattern.MatchPatternTypeChoices.INDIVIDUAL_URL,
243+
)
244+
division_pattern.delete()
245+
except DivisionPattern.DoesNotExist:
246+
pass
247+
return value

sde_collections/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
router.register(r"include-patterns", views.IncludePatternViewSet)
1414
router.register(r"title-patterns", views.TitlePatternViewSet)
1515
router.register(r"document-type-patterns", views.DocumentTypePatternViewSet)
16+
router.register(r"division-patterns", views.DivisionPatternViewSet)
1617
router.register(r"environmental-justice", EnvironmentalJusticeRowViewSet)
1718

1819
app_name = "sde_collections"
@@ -31,6 +32,7 @@
3132
views.IndexingInstructionsView.as_view(),
3233
name="indexing_instructions",
3334
),
35+
path("api/assign-division/<int:pk>/", views.CandidateURLViewSet.as_view({"post": "update_division"})),
3436
path(
3537
"delete-required-url/<int:pk>",
3638
view=views.RequiredUrlsDeleteView.as_view(),

sde_collections/views.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
WorkflowStatusChoices,
2929
)
3030
from .models.pattern import (
31+
DivisionPattern,
3132
DocumentTypePattern,
3233
ExcludePattern,
3334
IncludePattern,
@@ -39,11 +40,11 @@
3940
CandidateURLSerializer,
4041
CollectionReadSerializer,
4142
CollectionSerializer,
43+
DivisionPatternSerializer,
4244
DocumentTypePatternSerializer,
4345
ExcludePatternSerializer,
4446
IncludePatternSerializer,
4547
TitlePatternSerializer,
46-
WorkflowHistorySerializer,
4748
)
4849
from .tasks import push_to_github_task
4950
from .utils.health_check import generate_db_github_metadata_differences
@@ -156,15 +157,15 @@ def get_context_data(self, **kwargs):
156157
timeline_history[history.workflow_status] = history
157158

158159
# Add placeholders for stages with no workflow history
159-
for status in WorkflowStatusChoices:
160-
if status not in timeline_history:
161-
timeline_history[status] = {
162-
"workflow_status": status,
160+
for workflow_status in WorkflowStatusChoices:
161+
if workflow_status not in timeline_history:
162+
timeline_history[workflow_status] = {
163+
"workflow_status": workflow_status,
163164
"created_at": None,
164-
"label": WorkflowStatusChoices(status).label,
165+
"label": WorkflowStatusChoices(workflow_status).label,
165166
}
166167

167-
context["timeline_history"] = [timeline_history[status] for status in WorkflowStatusChoices]
168+
context["timeline_history"] = [timeline_history[workflow_status] for workflow_status in WorkflowStatusChoices]
168169
context["required_urls"] = RequiredUrls.objects.filter(collection=self.get_object())
169170
context["segment"] = "collection-detail"
170171
context["comments"] = Comments.objects.filter(collection=self.get_object()).order_by("-created_at")
@@ -220,6 +221,7 @@ def get_context_data(self, **kwargs):
220221
) # 2=regex patterns
221222
context["title_patterns"] = self.collection.titlepattern.all()
222223
context["workflow_status_choices"] = WorkflowStatusChoices
224+
context["is_multi_division"] = self.collection.is_multi_division
223225

224226
return context
225227

@@ -272,6 +274,15 @@ def get_queryset(self):
272274
queryset = self._filter_by_is_excluded(queryset, is_excluded)
273275
return queryset.order_by("url")
274276

277+
def update_division(self, request, pk=None):
278+
candidate_url = get_object_or_404(CandidateURL, pk=pk)
279+
division = request.data.get("division")
280+
if division:
281+
candidate_url.division = division
282+
candidate_url.save()
283+
return Response(status=status.HTTP_200_OK)
284+
return Response(status=status.HTTP_400_BAD_REQUEST, data={"error": "Division is required."})
285+
275286

276287
class CandidateURLBulkCreateView(generics.ListCreateAPIView):
277288
queryset = CandidateURL.objects.all()
@@ -386,6 +397,21 @@ def create(self, request, *args, **kwargs):
386397
return Response(status=status.HTTP_204_NO_CONTENT)
387398

388399

400+
class DivisionPatternViewSet(CollectionFilterMixin, viewsets.ModelViewSet):
401+
queryset = DivisionPattern.objects.all()
402+
serializer_class = DivisionPatternSerializer
403+
404+
def get_queryset(self):
405+
return super().get_queryset().order_by("match_pattern")
406+
407+
def create(self, request, *args, **kwargs):
408+
division = request.POST.get("division")
409+
if division:
410+
return super().create(request, *args, **kwargs)
411+
else:
412+
return Response(status=status.HTTP_400_BAD_REQUEST, data={"error": "Division is required."})
413+
414+
389415
class CollectionViewSet(viewsets.ModelViewSet):
390416
queryset = Collection.objects.all()
391417
serializer_class = CollectionSerializer

sde_indexing_helper/static/css/candidate_url_list.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ div.dt-buttons .btn.processing:after {
334334
-webkit-animation: dtb-spinner 1500ms infinite linear;
335335
}
336336

337-
.document_type_dropdown, .dropdown-toggle {
337+
.document_type_dropdown, .division_dropdown, .dropdown-toggle {
338338
width: 100%;
339339
display: flex;
340340
justify-content: center;

0 commit comments

Comments
 (0)