diff --git a/versions/models.py b/versions/models.py index 9fdea02..a40fc2c 100644 --- a/versions/models.py +++ b/versions/models.py @@ -309,7 +309,7 @@ def get_compiler(self, *args, **kwargs): (e.g. by adding a filter to the queryset) does not allow the caching of related object to work (they are attached to a queryset; filter() returns a new queryset). """ - if self.querytime.active: + if self.querytime.active and (not hasattr(self, '_querytime_filter_added') or not self._querytime_filter_added): time = self.querytime.time if time is None: self.add_q(Q(version_end_date__isnull=True)) @@ -318,6 +318,9 @@ def get_compiler(self, *args, **kwargs): (Q(version_end_date__gt=time) | Q(version_end_date__isnull=True)) & Q(version_start_date__lte=time) ) + # Ensure applying these filters happens only a single time (even if it doesn't falsify the query, it's + # just not very comfortable to read) + self._querytime_filter_added = True return super(VersionedQuery, self).get_compiler(*args, **kwargs) @@ -504,7 +507,6 @@ def get_joining_columns(self, reverse_join=False): return joining_columns - class VersionedManyToManyField(ManyToManyField): def __init__(self, *args, **kwargs): super(VersionedManyToManyField, self).__init__(*args, **kwargs) diff --git a/versions_tests/tests/test_models.py b/versions_tests/tests/test_models.py index 877fed0..628bc4a 100644 --- a/versions_tests/tests/test_models.py +++ b/versions_tests/tests/test_models.py @@ -678,6 +678,10 @@ def setUp(self): self.t1 = get_utc_now() sleep(0.1) + # State at t1 + # Players: [p1.v1, p2.v1] + # Teams: [t.v1] + # t.player_set = [p1, p2] team.player_set.remove(p2) @@ -687,6 +691,10 @@ def setUp(self): self.t2 = get_utc_now() sleep(0.1) + # State at t2 + # Players: [p1.v1, p2.v1, p2.v2] + # Teams: [t.v1] + # t.player_set = [p1] team.player_set.remove(p1) @@ -696,6 +704,10 @@ def setUp(self): self.t3 = get_utc_now() sleep(0.1) + # State at t3 + # Players: [p1.v1, p2.v1, p2.v2, p1.v2] + # Teams: [t.v1] + # t.player_set = [] # Let's get those players back into the game! team.player_set.add(p1) @@ -711,10 +723,18 @@ def setUp(self): self.t4 = get_utc_now() sleep(0.1) + # State at t4 + # Players: [p1.v1, p2.v1, p2.v2, p1.v2, p2.v3, p1.v3] + # Teams: [t.v1] + # t.player_set = [p1, p2] p1.delete() self.t5 = get_utc_now() + # State at t4 + # Players: [p1.v1, p2.v1, p2.v2, p1.v2, p2.v3, p1.v3] + # Teams: [t.v1] + # t.player_set = [p2] def test_filtering_on_the_other_side_of_the_relation(self): self.assertEqual(1, Team.objects.all().count()) @@ -774,6 +794,8 @@ def test_filtering_for_deleted_player_at_t5(self): @skipUnless(connection.vendor == 'sqlite', 'SQL is database specific, only sqlite is tested here.') def test_query_created_by_filtering_for_deleted_player_at_t5(self): team_none_queryset = Team.objects.as_of(self.t5).filter(player__name__startswith='p1') + # Validating the current query prior to analyzing the generated SQL + self.assertEqual([], list(team_none_queryset)) team_none_query = str(team_none_queryset.query) team_table = Team._meta.db_table @@ -1566,7 +1588,10 @@ def test_select_related(self): @skipUnless(connection.vendor == 'sqlite', 'SQL is database specific, only sqlite is tested here.') def test_select_related_query_sqlite(self): - select_related_query = str(Player.objects.as_of(self.t1).select_related('team').all().query) + select_related_queryset = Player.objects.as_of(self.t1).select_related('team').all() + # Validating the query before verifying the SQL string + self.assertEqual(['pl1.v1', 'pl2.v1'], [player.name for player in select_related_queryset]) + select_related_query = str(select_related_queryset.query) team_table = Team._meta.db_table player_table = Player._meta.db_table @@ -1625,7 +1650,7 @@ def test_select_related_query_postgresql(self): "{team_table}"."name", "{team_table}"."city_id" FROM "{player_table}" - LEFT OUTER JOIN "{team_table}" ON ("{player_table}"."team_id" = "{team_table}"."id" + LEFT OUTER JOIN "{team_table}" ON ("{player_table}"."team_id" = "{team_table}"."identity" AND (({team_table}.version_start_date <= {ts} AND ({team_table}.version_end_date > {ts} OR {team_table}.version_end_date IS NULL))))