From 1b8501b238107521d31744fa5654e3c400a891bf Mon Sep 17 00:00:00 2001 From: Fabro Date: Mon, 16 Mar 2026 08:08:59 -0400 Subject: [PATCH] checkpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⚒️ Generated with [Fabro](https://fabro.sh) --- checkpoint.json | 57 ++++++++++++++++++++++++++++++ nodes/setup/script_invocation.json | 5 +++ nodes/setup/script_timing.json | 5 +++ nodes/setup/status.json | 6 ++++ nodes/start/status.json | 6 ++++ 5 files changed, 79 insertions(+) create mode 100644 checkpoint.json create mode 100644 nodes/setup/script_invocation.json create mode 100644 nodes/setup/script_timing.json create mode 100644 nodes/setup/status.json create mode 100644 nodes/start/status.json diff --git a/checkpoint.json b/checkpoint.json new file mode 100644 index 000000000..5f2dceef3 --- /dev/null +++ b/checkpoint.json @@ -0,0 +1,57 @@ +{ + "timestamp": "2026-03-16T12:08:59.423744Z", + "current_node": "setup", + "completed_nodes": [ + "start", + "setup" + ], + "node_retries": { + "start": 1, + "setup": 1 + }, + "context_values": { + "graph.rankdir": "LR", + "graph.goal": "Cannot override get_FOO_display() in Django 2.2+.\nDescription\n\t\nI cannot override the get_FIELD_display function on models since version 2.2. It works in version 2.1.\nExample:\nclass FooBar(models.Model):\n\tfoo_bar = models.CharField(_(\"foo\"), choices=[(1, 'foo'), (2, 'bar')])\n\tdef __str__(self):\n\t\treturn self.get_foo_bar_display() # This returns 'foo' or 'bar' in 2.2, but 'something' in 2.1\n\tdef get_foo_bar_display(self):\n\t\treturn \"something\"\nWhat I expect is that I should be able to override this function.\n\n\n\n## Additional Context\n\nThanks for this report. Regression in a68ea231012434b522ce45c513d84add516afa60. Reproduced at 54a7b021125d23a248e70ba17bf8b10bc8619234.\nOK, I have a lead on this. Not at all happy about how it looks at first pass, but I'll a proof of concept PR together for it tomorrow AM.\nI don't think it should be marked as blocker since it looks like it was never supported, because it depends on the order of attrs passed in ModelBase.__new__(). So on Django 2.1 and Python 3.7: In [1]: import django ...: django.VERSION In [2]: from django.db import models ...: ...: class FooBar(models.Model): ...: def get_foo_bar_display(self): ...: return \"something\" ...: ...: foo_bar = models.CharField(\"foo\", choices=[(1, 'foo'), (2, 'bar')]) ...: ...: def __str__(self): ...: return self.get_foo_bar_display() # This returns 'foo' or 'bar' in 2.2, but 'something' in 2.1 ...: ...: class Meta: ...: app_label = 'test' ...: ...: FooBar(foo_bar=1) Out[2]: Before ​Python 3.6 the order of attrs wasn't defined at all.\nSergey, an example from the ticket description works for me with Django 2.1 and Python 3.6, 3.7 and 3.8.\nIn [2]: import django ...: django.VERSION Out[2]: (2, 1, 13, 'final', 0) In [3]: import sys ...: sys.version Out[3]: '3.5.7 (default, Oct 17 2019, 07:04:41) \\n[GCC 8.3.0]' In [4]: from django.db import models ...: ...: class FooBar(models.Model): ...: foo_bar = models.CharField(\"foo\", choices=[(1, 'foo'), (2, 'bar')]) ...: ...: def __str__(self): ...: return self.get_foo_bar_display() # This returns 'foo' or 'bar' in 2.2, but 'something' in 2.1 ...: ...: def get_foo_bar_display(self): ...: return \"something\" ...: ...: class Meta: ...: app_label = 'test' ...: ...: FooBar(foo_bar=1) Out[4]: \nOK, so there is a behaviour change here, but Sergey is correct that it does depend on attr order, so it's hard to say that this can be said to ever have been thought of as supported, with the exact example provided. This example produces the opposite result on 2.1 (even on >=PY36): def test_overriding_display_backwards(self): class FooBar2(models.Model): def get_foo_bar_display(self): return \"something\" foo_bar = models.CharField(\"foo\", choices=[(1, 'foo'), (2, 'bar')]) f = FooBar2(foo_bar=1) # This returns 'foo' or 'bar' in both 2.2 and 2.1 self.assertEqual(f.get_foo_bar_display(), \"foo\") Because get_foo_bar_display() is defined before foo_bar is gets replaced in the the add_to_class() step. Semantically order shouldn't make a difference. Given that it does, I can't see that we're bound to maintain that behaviour. (There's a possible fix in Field.contribute_to_class() but implementing that just reverses the pass/fail behaviour depending on order...) Rather, the correct way to implement this on 2.2+ is: def test_overriding_display(self): class FooBar(models.Model): foo_bar = models.CharField(\"foo\", choices=[(1, 'foo'), (2, 'bar')]) def _get_FIELD_display(self, field): if field.attname == 'foo_bar': return \"something\" return super()._get_FIELD_display(field) f = FooBar(foo_bar=1) self.assertEqual(f.get_foo_bar_display(), \"something\") This is stable for declaration order on version 2.2+. This approach requires overriding _get_FIELD_display() before declaring fields on 2.1, because otherwise Model._get_FIELD_display() is picked up during Field.contribute_to_class(). This ordering dependency is, ultimately, the same issue that was addressed in a68ea231012434b522ce45c513d84add516afa60, and the follow-up in #30254. The behaviour in 2.1 (and before) was incorrect. Yes, there's a behaviour change here but it's a bugfix, and all bugfixes are breaking changes if you're depending on the broken behaviour. I'm going to downgrade this from Release Blocker accordingly. I'll reclassify this as a Documentation issue and provide the working example, as overriding _get_FIELD_display() is a legitimate use-case I'd guess.\nReplying to Carlton Gibson: (There's a possible fix in Field.contribute_to_class() but implementing that just reverses the pass/fail behaviour depending on order...) Doesn't this fix it? if not hasattr(cls, 'get_%s_display' % self.name): setattr(cls, 'get_%s_display' % self.name, partialmethod(cls._get_FIELD_display, field=self))", + "failure_signature": "setup|deterministic|script failed with exit code: ## stdout fatal: destination path '.' already exists and is not an empty directory.", + "command.output": "fatal: destination path '.' already exists and is not an empty directory.\n", + "command.stderr": "", + "thread.start.current_node": "setup", + "internal.retry_count.start": 1, + "failure_class": "deterministic", + "internal.thread_id": "start", + "current.preamble": "Goal: Cannot override get_FOO_display() in Django 2.2+.\nDescription\n\t\nI cannot override the get_FIELD_display function on models since version 2.2. It works in version 2.1.\nExample:\nclass FooBar(models.Model):\n\tfoo_bar = models.CharField(_(\"foo\"), choices=[(1, 'foo'), (2, 'bar')])\n\tdef __str__(self):\n\t\treturn self.get_foo_bar_display() # This returns 'foo' or 'bar' in 2.2, but 'something' in 2.1\n\tdef get_foo_bar_display(self):\n\t\treturn \"something\"\nWhat I expect is that I should be able to override this function.\n\n\n\n## Additional Context\n\nThanks for this report. Regression in a68ea231012434b522ce45c513d84add516afa60. Reproduced at 54a7b021125d23a248e70ba17bf8b10bc8619234.\nOK, I have a lead on this. Not at all happy about how it looks at first pass, but I'll a proof of concept PR together for it tomorrow AM.\nI don't think it should be marked as blocker since it looks like it was never supported, because it depends on the order of attrs passed in ModelBase.__new__(). So on Django 2.1 and Python 3.7: In [1]: import django ...: django.VERSION In [2]: from django.db import models ...: ...: class FooBar(models.Model): ...: def get_foo_bar_display(self): ...: return \"something\" ...: ...: foo_bar = models.CharField(\"foo\", choices=[(1, 'foo'), (2, 'bar')]) ...: ...: def __str__(self): ...: return self.get_foo_bar_display() # This returns 'foo' or 'bar' in 2.2, but 'something' in 2.1 ...: ...: class Meta: ...: app_label = 'test' ...: ...: FooBar(foo_bar=1) Out[2]: Before ​Python 3.6 the order of attrs wasn't defined at all.\nSergey, an example from the ticket description works for me with Django 2.1 and Python 3.6, 3.7 and 3.8.\nIn [2]: import django ...: django.VERSION Out[2]: (2, 1, 13, 'final', 0) In [3]: import sys ...: sys.version Out[3]: '3.5.7 (default, Oct 17 2019, 07:04:41) \\n[GCC 8.3.0]' In [4]: from django.db import models ...: ...: class FooBar(models.Model): ...: foo_bar = models.CharField(\"foo\", choices=[(1, 'foo'), (2, 'bar')]) ...: ...: def __str__(self): ...: return self.get_foo_bar_display() # This returns 'foo' or 'bar' in 2.2, but 'something' in 2.1 ...: ...: def get_foo_bar_display(self): ...: return \"something\" ...: ...: class Meta: ...: app_label = 'test' ...: ...: FooBar(foo_bar=1) Out[4]: \nOK, so there is a behaviour change here, but Sergey is correct that it does depend on attr order, so it's hard to say that this can be said to ever have been thought of as supported, with the exact example provided. This example produces the opposite result on 2.1 (even on >=PY36): def test_overriding_display_backwards(self): class FooBar2(models.Model): def get_foo_bar_display(self): return \"something\" foo_bar = models.CharField(\"foo\", choices=[(1, 'foo'), (2, 'bar')]) f = FooBar2(foo_bar=1) # This returns 'foo' or 'bar' in both 2.2 and 2.1 self.assertEqual(f.get_foo_bar_display(), \"foo\") Because get_foo_bar_display() is defined before foo_bar is gets replaced in the the add_to_class() step. Semantically order shouldn't make a difference. Given that it does, I can't see that we're bound to maintain that behaviour. (There's a possible fix in Field.contribute_to_class() but implementing that just reverses the pass/fail behaviour depending on order...) Rather, the correct way to implement this on 2.2+ is: def test_overriding_display(self): class FooBar(models.Model): foo_bar = models.CharField(\"foo\", choices=[(1, 'foo'), (2, 'bar')]) def _get_FIELD_display(self, field): if field.attname == 'foo_bar': return \"something\" return super()._get_FIELD_display(field) f = FooBar(foo_bar=1) self.assertEqual(f.get_foo_bar_display(), \"something\") This is stable for declaration order on version 2.2+. This approach requires overriding _get_FIELD_display() before declaring fields on 2.1, because otherwise Model._get_FIELD_display() is picked up during Field.contribute_to_class(). This ordering dependency is, ultimately, the same issue that was addressed in a68ea231012434b522ce45c513d84add516afa60, and the follow-up in #30254. The behaviour in 2.1 (and before) was incorrect. Yes, there's a behaviour change here but it's a bugfix, and all bugfixes are breaking changes if you're depending on the broken behaviour. I'm going to downgrade this from Release Blocker accordingly. I'll reclassify this as a Documentation issue and provide the working example, as overriding _get_FIELD_display() is a legitimate use-case I'd guess.\nReplying to Carlton Gibson: (There's a possible fix in Field.contribute_to_class() but implementing that just reverses the pass/fail behaviour depending on order...) Doesn't this fix it? if not hasattr(cls, 'get_%s_display' % self.name): setattr(cls, 'get_%s_display' % self.name, partialmethod(cls._get_FIELD_display, field=self))\n", + "internal.run_id": "01KKV8S49GE5JF2G0BF2X413E3", + "current_node": "setup", + "outcome": "fail", + "internal.retry_count.setup": 1, + "internal.fidelity": "compact", + "internal.node_visit_count": 1 + }, + "logs": [], + "node_outcomes": { + "setup": { + "status": "fail", + "context_updates": { + "command.output": "fatal: destination path '.' already exists and is not an empty directory.\n", + "command.stderr": "" + }, + "failure": { + "message": "Script failed with exit code: 128\n\n## stdout\nfatal: destination path '.' already exists and is not an empty directory.\n", + "failure_class": "deterministic" + }, + "duration_ms": 26 + }, + "start": { + "status": "success", + "duration_ms": 0 + } + }, + "next_node_id": "solve", + "loop_failure_signatures": { + "setup|deterministic|script failed with exit code: ## stdout fatal: destination path '.' already exists and is not an empty directory.": 1 + }, + "node_visits": { + "setup": 1, + "start": 1 + } +} \ No newline at end of file diff --git a/nodes/setup/script_invocation.json b/nodes/setup/script_invocation.json new file mode 100644 index 000000000..ed1fa78fd --- /dev/null +++ b/nodes/setup/script_invocation.json @@ -0,0 +1,5 @@ +{ + "command": "git clone https://github.com/django/django.git . && git checkout 84633905273fc916e3d17883810d9969c03f73c2 && python -m pip install -e .", + "language": "shell", + "timeout_ms": null +} \ No newline at end of file diff --git a/nodes/setup/script_timing.json b/nodes/setup/script_timing.json new file mode 100644 index 000000000..5a1d8ff69 --- /dev/null +++ b/nodes/setup/script_timing.json @@ -0,0 +1,5 @@ +{ + "duration_ms": 25, + "exit_code": 128, + "timed_out": false +} \ No newline at end of file diff --git a/nodes/setup/status.json b/nodes/setup/status.json new file mode 100644 index 000000000..e95b75540 --- /dev/null +++ b/nodes/setup/status.json @@ -0,0 +1,6 @@ +{ + "status": "fail", + "notes": null, + "failure_reason": "Script failed with exit code: 128\n\n## stdout\nfatal: destination path '.' already exists and is not an empty directory.\n", + "timestamp": "2026-03-16T12:08:59.423559+00:00" +} \ No newline at end of file diff --git a/nodes/start/status.json b/nodes/start/status.json new file mode 100644 index 000000000..39c724473 --- /dev/null +++ b/nodes/start/status.json @@ -0,0 +1,6 @@ +{ + "status": "success", + "notes": null, + "failure_reason": null, + "timestamp": "2026-03-16T12:08:59.396185+00:00" +} \ No newline at end of file