checkpoint

⚒️ Generated with [Fabro](https://fabro.sh)
This commit is contained in:
Fabro 2026-03-16 08:08:59 -04:00
parent 9e1d96be6b
commit 1b8501b238
5 changed files with 79 additions and 0 deletions

57
checkpoint.json Normal file
View file

@ -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]: <FooBar: foo> 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]: <FooBar: foo>\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: <n> ## 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]: <FooBar: foo> 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]: <FooBar: foo>\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: <n> ## stdout fatal: destination path '.' already exists and is not an empty directory.": 1
},
"node_visits": {
"setup": 1,
"start": 1
}
}

View file

@ -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
}

View file

@ -0,0 +1,5 @@
{
"duration_ms": 25,
"exit_code": 128,
"timed_out": false
}

6
nodes/setup/status.json Normal file
View file

@ -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"
}

6
nodes/start/status.json Normal file
View file

@ -0,0 +1,6 @@
{
"status": "success",
"notes": null,
"failure_reason": null,
"timestamp": "2026-03-16T12:08:59.396185+00:00"
}