Code

Ticket #5805: index_together.diff

File index_together.diff, 7.1 KB (added by jgelens, 3 years ago)

Updated patch to match trunk

Line 
1diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
2--- a/django/db/backends/creation.py
3+++ b/django/db/backends/creation.py
4@@ -133,21 +133,39 @@
5                     (qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())),
6                     qn(r_col), qn(table), qn(col),
7                     self.connection.ops.deferrable_sql()))
8             del pending_references[model]
9         return final_output
10 
11     def sql_indexes_for_model(self, model, style):
12         "Returns the CREATE INDEX SQL statements for a single model"
13+        from django.db.backends.util import truncate_name
14+
15         if not model._meta.managed or model._meta.proxy:
16             return []
17+
18+        opts = model._meta
19+        qn = self.connection.ops.quote_name
20         output = []
21+
22         for f in model._meta.local_fields:
23             output.extend(self.sql_indexes_for_field(model, f, style))
24+
25+        for field_constraints in opts.index_together:
26+            i_name = '%s_%s' % (opts.db_table, self._digest(field_constraints))
27+
28+            output.append(
29+                style.SQL_KEYWORD('CREATE INDEX') + ' ' +
30+                style.SQL_TABLE(qn(truncate_name(i_name, self.connection.ops.max_name_length()))) + ' ' +
31+                style.SQL_KEYWORD('ON') + ' ' +
32+                style.SQL_TABLE(qn(opts.db_table)) + ' ' +
33+                "(%s);" % ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints])
34+            )
35+
36         return output
37 
38     def sql_indexes_for_field(self, model, f, style):
39         "Return the CREATE INDEX SQL statements for a single model field"
40         from django.db.backends.util import truncate_name
41 
42         if f.db_index and not f.unique:
43             qn = self.connection.ops.quote_name
44diff --git a/django/db/models/options.py b/django/db/models/options.py
45--- a/django/db/models/options.py
46+++ b/django/db/models/options.py
47@@ -12,27 +12,28 @@
48 from django.utils.datastructures import SortedDict
49 
50 # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
51 get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
52 
53 DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
54                  'unique_together', 'permissions', 'get_latest_by',
55                  'order_with_respect_to', 'app_label', 'db_tablespace',
56-                 'abstract', 'managed', 'proxy', 'auto_created')
57+                 'abstract', 'managed', 'proxy', 'auto_created', 'index_together')
58 
59 class Options(object):
60     def __init__(self, meta, app_label=None):
61         self.local_fields, self.local_many_to_many = [], []
62         self.virtual_fields = []
63         self.module_name, self.verbose_name = None, None
64         self.verbose_name_plural = None
65         self.db_table = ''
66         self.ordering = []
67         self.unique_together =  []
68+        self.index_together =  []
69         self.permissions =  []
70         self.object_name, self.app_label = None, app_label
71         self.get_latest_by = None
72         self.order_with_respect_to = None
73         self.db_tablespace = settings.DEFAULT_TABLESPACE
74         self.admin = None
75         self.meta = meta
76         self.pk = None
77@@ -75,24 +76,29 @@
78                 if name.startswith('_'):
79                     del meta_attrs[name]
80             for attr_name in DEFAULT_NAMES:
81                 if attr_name in meta_attrs:
82                     setattr(self, attr_name, meta_attrs.pop(attr_name))
83                 elif hasattr(self.meta, attr_name):
84                     setattr(self, attr_name, getattr(self.meta, attr_name))
85 
86-            # unique_together can be either a tuple of tuples, or a single
87+            # unique_together and index_together can be either a tuple of tuples, or a single
88             # tuple of two strings. Normalize it to a tuple of tuples, so that
89             # calling code can uniformly expect that.
90             ut = meta_attrs.pop('unique_together', self.unique_together)
91             if ut and not isinstance(ut[0], (tuple, list)):
92                 ut = (ut,)
93             self.unique_together = ut
94 
95+            it = meta_attrs.pop('index_together', self.index_together)
96+            if it and not isinstance(it[0], (tuple, list)):
97+                it = (it,)
98+            self.index_together = it
99+
100             # verbose_name_plural is a special case because it uses a 's'
101             # by default.
102             if self.verbose_name_plural is None:
103                 self.verbose_name_plural = string_concat(self.verbose_name, 's')
104 
105             # Any leftover attributes must be invalid.
106             if meta_attrs != {}:
107                 raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()))
108diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
109--- a/docs/ref/models/options.txt
110+++ b/docs/ref/models/options.txt
111@@ -242,16 +242,35 @@
112     appropriate ``UNIQUE`` statements are included in the ``CREATE TABLE``
113     statement).
114 
115     For convenience, unique_together can be a single list when dealing with a single
116     set of fields::
117 
118         unique_together = ("driver", "restaurant")
119 
120+``index_together``
121+-------------------
122+
123+.. versionadded:: 1.4
124+
125+.. attribute:: Options.index_together
126+
127+    Sets of field names that, taken together, will be indexed::
128+
129+        index_together = (("driver", "restaurant"),)
130+
131+    This is a list of lists of fields that will indexed together. (i.e., the
132+    appropriate ``CREATE INDEX`` statements will be created for this table.
133+
134+    For convenience, index_together can be a single list when dealing with a single
135+    set of fields::
136+
137+        index_together = ("driver", "restaurant")
138+
139 ``verbose_name``
140 ----------------
141 
142 .. attribute:: Options.verbose_name
143 
144     A human-readable name for the object, singular::
145 
146         verbose_name = "pizza"
147diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py
148--- a/tests/modeltests/basic/models.py
149+++ b/tests/modeltests/basic/models.py
150@@ -8,11 +8,12 @@
151 
152 
153 class Article(models.Model):
154     headline = models.CharField(max_length=100, default='Default headline')
155     pub_date = models.DateTimeField()
156 
157     class Meta:
158         ordering = ('pub_date','headline')
159+        index_together = ('headline', 'pub_date')
160 
161     def __unicode__(self):
162         return self.headline
163diff --git a/tests/modeltests/get_or_create/models.py b/tests/modeltests/get_or_create/models.py
164--- a/tests/modeltests/get_or_create/models.py
165+++ b/tests/modeltests/get_or_create/models.py
166@@ -9,14 +9,17 @@
167 from django.db import models
168 
169 
170 class Person(models.Model):
171     first_name = models.CharField(max_length=100)
172     last_name = models.CharField(max_length=100)
173     birthday = models.DateField()
174 
175+    class Meta:
176+        index_together = (('first_name', 'last_name'), ('first_name', 'birthday'))
177+
178     def __unicode__(self):
179         return u'%s %s' % (self.first_name, self.last_name)
180 
181 class ManualPrimaryKeyTest(models.Model):
182     id = models.IntegerField(primary_key=True)
183     data = models.CharField(max_length=100)