Skip to content

Commit 9b4e170

Browse files
committed
Revert temporary upgrade to Django 5.2a1
This reverts commits cf7ed27. and fabb4bc.
1 parent cf7ed27 commit 9b4e170

File tree

5 files changed

+99
-10
lines changed

5 files changed

+99
-10
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ repos:
3636
rev: "1.22.2"
3737
hooks:
3838
- id: django-upgrade
39-
# TODO: Update to 5.2 when django-upgrade supports it
4039
args: [--target-version, "5.1"]
4140
- repo: https://github.com/psf/black
4241
rev: 24.10.0

djangoproject/settings/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
SESSION_COOKIE_HTTPONLY = True
173173

174174
SILENCED_SYSTEM_CHECKS = [
175+
"fields.W342", # tracdb has ForeignKey(unique=True) in lieu of multi-col PKs
175176
"security.W008", # SSL redirect is handled by nginx
176177
"security.W009", # SECRET_KEY is setup through Ansible secrets
177178
]

requirements/common.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ django-push @ git+https://github.com/brutasse/django-push.git@22fda99641cfbd2f30
77
django-read-only==1.18.0
88
django-recaptcha==4.0.0
99
django-registration-redux==2.13
10-
Django==5.2a1
10+
Django==5.1.5
1111
docutils==0.21.2
1212
feedparser==6.0.11
1313
Jinja2==3.1.5

tracdb/models.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,38 @@
99
* All the session and permission tables: they're just not needed.
1010
* Enum: I don't know what this is or what it's for.
1111
* NodeChange: Ditto.
12+
13+
These models are far from perfect but are Good Enough(tm) to get some useful data out.
14+
15+
One important mismatch between these models and the Trac database has to do with
16+
composite primary keys. Trac uses them for several tables, but Django does not support
17+
them yet (ticket #373).
18+
19+
These are the tables that use them:
20+
21+
* ticket_custom (model TicketCustom)
22+
* ticket_change (model TicketChange)
23+
* wiki (model Wiki)
24+
* attachment (model Attachment)
25+
26+
To make these work with Django (for some definition of the word "work") we mark only
27+
one of their field as being the primary key (primary_key=True).
28+
This is obviously incorrect but — somewhat suprisingly — it doesn't break **everything**
29+
and the little that does actually work is good enough for what we're trying to do:
30+
31+
* Model.objects.create(...) correctly creates the object in the db
32+
* Most queryset/manager methods work (in particular filter(), exclude(), all()
33+
and count())
34+
35+
On the other hand, here's what absolutely DOES NOT work (the list is sadly not
36+
exhaustive):
37+
38+
* Updating a model instance with save() will update ALL ROWS that happen to share
39+
the value for the field used as the "fake" primary key if they exist (resulting
40+
in a DBError)
41+
* The admin won't work (the "pk" field shortcut can't be used reliably since it can
42+
return multiple rows)
43+
1244
"""
1345

1446
from datetime import date
@@ -129,11 +161,11 @@ def __str__(self):
129161

130162

131163
class TicketCustom(models.Model):
132-
pk = models.CompositePrimaryKey("ticket", "name")
133164
ticket = models.ForeignKey(
134165
Ticket,
135166
related_name="custom_fields",
136167
db_column="ticket",
168+
primary_key=True, # XXX See note at the top about composite pk
137169
on_delete=models.DO_NOTHING,
138170
)
139171
name = models.TextField()
@@ -148,11 +180,11 @@ def __str__(self):
148180

149181

150182
class TicketChange(models.Model):
151-
pk = models.CompositePrimaryKey("ticket", "_time", "field")
152183
ticket = models.ForeignKey(
153184
Ticket,
154185
related_name="changes",
155186
db_column="ticket",
187+
primary_key=True, # XXX See note at the top about composite pk
156188
on_delete=models.DO_NOTHING,
157189
)
158190
author = models.TextField()
@@ -260,8 +292,9 @@ def __str__(self):
260292

261293

262294
class Wiki(models.Model):
263-
pk = models.CompositePrimaryKey("name", "version")
264-
name = models.TextField()
295+
name = models.TextField(
296+
primary_key=True
297+
) # XXX See note at the top about composite pk
265298
version = models.IntegerField()
266299
_time = models.BigIntegerField(db_column="time")
267300
time = time_property("_time")
@@ -279,9 +312,10 @@ def __str__(self):
279312

280313

281314
class Attachment(models.Model):
282-
pk = models.CompositePrimaryKey("type", "id", "filename")
283315
type = models.TextField()
284-
id = models.TextField()
316+
id = models.TextField(
317+
primary_key=True
318+
) # XXX See note at the top about composite pk
285319
filename = models.TextField()
286320
size = models.IntegerField()
287321
_time = models.BigIntegerField(db_column="time")

tracdb/testutils.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,60 @@
1+
from copy import deepcopy
2+
13
from django.apps import apps
2-
from django.db import connections
4+
from django.db import connections, models
5+
6+
# There's more models with a fake composite pk, but we don't test them at the moment.
7+
_MODELS_WITH_FAKE_COMPOSITE_PK = {"ticketcustom"}
8+
9+
10+
def _get_pk_field(model):
11+
"""
12+
Return the pk field for the given model.
13+
Raise ValueError if none or more than one is found.
14+
"""
15+
pks = [field for field in model._meta.get_fields() if field.primary_key]
16+
if len(pks) == 0:
17+
raise ValueError(f"No primary key field found for model {model._meta.label}")
18+
elif len(pks) > 1:
19+
raise ValueError(
20+
f"Found more than one primary key field for model {model._meta.label}"
21+
)
22+
else:
23+
return pks[0]
24+
25+
26+
def _replace_primary_key_field_with_autofield(model, schema_editor):
27+
"""
28+
See section about composite pks in the docstring for models.py to get some context
29+
for this.
30+
31+
In short, some models define a field as `primary_key=True` but that field is not
32+
actually a primary key. In particular that field is not supposed to be unique, which
33+
interferes with our tests.
34+
35+
For those models, we remove the `primary_key` flag from the field, and we add a
36+
new `testid` autofield. This makes the models easier to manipulate in the tests.
37+
"""
38+
old_pk_field = _get_pk_field(model)
39+
del old_pk_field.unique
40+
new_pk_field = deepcopy(old_pk_field)
41+
new_pk_field.primary_key = False
42+
schema_editor.alter_field(
43+
model=model, old_field=old_pk_field, new_field=new_pk_field
44+
)
45+
46+
autofield = models.AutoField(primary_key=True)
47+
autofield.set_attributes_from_name("testid")
48+
schema_editor.add_field(model=model, field=autofield)
49+
50+
51+
def _create_db_table_for_model(model, schema_editor):
52+
"""
53+
Use the schema editor API to create the db table for the given (unmanaged) model.
54+
"""
55+
schema_editor.create_model(model)
56+
if model._meta.model_name in _MODELS_WITH_FAKE_COMPOSITE_PK:
57+
_replace_primary_key_field_with_autofield(model, schema_editor)
358

459

560
def create_db_tables_for_unmanaged_models(schema_editor):
@@ -10,7 +65,7 @@ def create_db_tables_for_unmanaged_models(schema_editor):
1065
for model in appconfig.get_models():
1166
if model._meta.managed:
1267
continue
13-
schema_editor.create_model(model)
68+
_create_db_table_for_model(model, schema_editor)
1469

1570

1671
def destroy_db_tables_for_unmanaged_models(schema_editor):

0 commit comments

Comments
 (0)