Skip to content

Commit 4b53081

Browse files
committed
Used composite PKs for tracdb instead of hacks
1 parent 4257af9 commit 4b53081

File tree

3 files changed

+8
-98
lines changed

3 files changed

+8
-98
lines changed

djangoproject/settings/common.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@
175175
SESSION_COOKIE_HTTPONLY = True
176176

177177
SILENCED_SYSTEM_CHECKS = [
178-
"fields.W342", # tracdb has ForeignKey(unique=True) in lieu of multi-col PKs
179178
"security.W008", # SSL redirect is handled by nginx
180179
"security.W009", # SECRET_KEY is setup through Ansible secrets
181180
]

tracdb/models.py

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,6 @@
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-
4412
"""
4513

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

162130

163131
class TicketCustom(models.Model):
132+
pk = models.CompositePrimaryKey("ticket", "name")
164133
ticket = models.ForeignKey(
165134
Ticket,
166135
related_name="custom_fields",
167136
db_column="ticket",
168-
primary_key=True, # XXX See note at the top about composite pk
169137
on_delete=models.DO_NOTHING,
170138
)
171139
name = models.TextField()
@@ -180,11 +148,11 @@ def __str__(self):
180148

181149

182150
class TicketChange(models.Model):
151+
pk = models.CompositePrimaryKey("ticket", "_time", "field")
183152
ticket = models.ForeignKey(
184153
Ticket,
185154
related_name="changes",
186155
db_column="ticket",
187-
primary_key=True, # XXX See note at the top about composite pk
188156
on_delete=models.DO_NOTHING,
189157
)
190158
author = models.TextField()
@@ -292,9 +260,8 @@ def __str__(self):
292260

293261

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

313280

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

tracdb/testutils.py

Lines changed: 2 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,5 @@
1-
from copy import deepcopy
2-
31
from django.apps import apps
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)
2+
from django.db import connections
583

594

605
def create_db_tables_for_unmanaged_models(schema_editor):
@@ -65,7 +10,7 @@ def create_db_tables_for_unmanaged_models(schema_editor):
6510
for model in appconfig.get_models():
6611
if model._meta.managed:
6712
continue
68-
_create_db_table_for_model(model, schema_editor)
13+
schema_editor.create_model(model)
6914

7015

7116
def destroy_db_tables_for_unmanaged_models(schema_editor):

0 commit comments

Comments
 (0)