Skip to content

Commit 5a30267

Browse files
authored
Merge branch 'main' into feat/advanced-collection-filters-2
2 parents 42d99a8 + 04ae3fa commit 5a30267

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Generated by Django 4.2.10 on 2025-07-08 16:51
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("main", "0059_alter_project_options"),
9+
]
10+
11+
operations = [
12+
migrations.AlterField(
13+
model_name="sourceimagecollection",
14+
name="method",
15+
field=models.CharField(
16+
choices=[
17+
("common_combined", "common_combined"),
18+
("random", "random"),
19+
("stratified_random", "stratified_random"),
20+
("interval", "interval"),
21+
("manual", "manual"),
22+
("starred", "starred"),
23+
("random_from_each_event", "random_from_each_event"),
24+
("last_and_random_from_each_event", "last_and_random_from_each_event"),
25+
("greatest_file_size_from_each_event", "greatest_file_size_from_each_event"),
26+
("detections_only", "detections_only"),
27+
("full", "full"),
28+
],
29+
default="common_combined",
30+
max_length=255,
31+
),
32+
),
33+
]

ami/main/models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,9 @@ def sync_captures(self, batch_size=1000, regroup_events_per_batch=False, job: "J
610610
job.progress.add_stage("Update deployment cache")
611611
job.update_progress()
612612

613+
# Regroup source images if needed
614+
if deployment_event_needs_update(deployment):
615+
group_images_into_events(deployment)
613616
self.save()
614617
self.update_calculated_fields(save=True)
615618

@@ -1091,6 +1094,23 @@ def group_images_into_events(
10911094
return events
10921095

10931096

1097+
def deployment_event_needs_update(deployment: Deployment) -> bool:
1098+
"""
1099+
Returns True if there are any SourceImages in the deployment
1100+
that haven't been assigned to an `Event`.
1101+
1102+
Note: This does not detect if images were deleted from the deployment
1103+
after being grouped. We currently have limited support for image deletion,
1104+
so handling that is out of scope for this check.
1105+
"""
1106+
1107+
ungrouped_images_exist = SourceImage.objects.filter(deployment=deployment, event__isnull=True).exists()
1108+
1109+
logger.debug(f"Deployment {deployment.pk}: ungrouped images exist = {ungrouped_images_exist}")
1110+
1111+
return ungrouped_images_exist
1112+
1113+
10941114
def delete_empty_events(deployment: Deployment, dry_run=False):
10951115
"""
10961116
Delete events that have no images, occurrences or other related records.

ami/main/tests.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
)
3131
from ami.ml.models.pipeline import Pipeline
3232
from ami.tests.fixtures.main import create_captures, create_occurrences, create_taxa, setup_test_project
33+
from ami.tests.fixtures.storage import populate_bucket
3334
from ami.users.models import User
3435
from ami.users.roles import BasicMember, Identifier, ProjectManager
3536

@@ -1572,3 +1573,56 @@ def test_project_manager_permissions_(self):
15721573
self._test_sourceimageupload_permissions(
15731574
user=self.project_manager, permission_map=self.PERMISSIONS_MAPS["project_manager"]["sourceimageupload"]
15741575
)
1576+
1577+
1578+
class TestDeploymentSyncCreatesEvents(TestCase):
1579+
def test_sync_creates_events_and_updates_counts(self):
1580+
# Set up a new project and deployment with test data
1581+
project, deployment = setup_test_project(reuse=False)
1582+
1583+
# Populate the object store with image data
1584+
assert deployment.data_source is not None
1585+
populate_bucket(
1586+
config=deployment.data_source.config,
1587+
subdir=f"deployment_{deployment.pk}",
1588+
skip_existing=False,
1589+
)
1590+
1591+
# Sync captures
1592+
deployment.sync_captures()
1593+
1594+
# Refresh and check results
1595+
deployment.refresh_from_db()
1596+
initial_events = Event.objects.filter(deployment=deployment)
1597+
initial_events_count = initial_events.count()
1598+
1599+
# Assertions
1600+
self.assertTrue(initial_events.exists(), "Expected events to be created")
1601+
self.assertEqual(
1602+
deployment.events_count, initial_events.count(), "Deployment events_count should match actual events"
1603+
)
1604+
# Simulate new images added to object store
1605+
populate_bucket(
1606+
config=deployment.data_source.config,
1607+
subdir=f"deployment_{deployment.pk}",
1608+
skip_existing=False,
1609+
num_nights=2,
1610+
images_per_day=5,
1611+
minutes_interval=120,
1612+
)
1613+
1614+
# Sync again
1615+
deployment.sync_captures()
1616+
deployment.refresh_from_db()
1617+
updated_events = Event.objects.filter(deployment=deployment)
1618+
1619+
# Assertions for second sync
1620+
self.assertGreater(
1621+
updated_events.count(), initial_events_count, "New events should be created after adding new images"
1622+
)
1623+
self.assertEqual(
1624+
deployment.events_count,
1625+
updated_events.count(),
1626+
"Deployment events_count should reflect updated event count",
1627+
)
1628+
logger.info(f"Initial events count: {initial_events_count}, Updated events count: {updated_events.count()}")

0 commit comments

Comments
 (0)