Code

Ticket #6148: generic-db_schema-r8319.diff

File generic-db_schema-r8319.diff, 9.5 KB (added by crippledcanary@…, 6 years ago)

remade some stuff and added testing and doc. Tested with mysql and sqlite so far

Line 
1Index: django/core/management/commands/syncdb.py
2===================================================================
3--- django/core/management/commands/syncdb.py   (revision 8319)
4+++ django/core/management/commands/syncdb.py   (working copy)
5@@ -61,6 +61,9 @@
6             app_name = app.__name__.split('.')[-2]
7             model_list = models.get_models(app)
8             for model in model_list:
9+                # Add model defined schema tables if anny
10+                if model._meta.db_schema:
11+                    tables += connection.introspection.schema_table_names(model._meta.db_schema)
12                 # Create the model's database table, if it doesn't already exist.
13                 if verbosity >= 2:
14                     print "Processing %s.%s model" % (app_name, model._meta.object_name)
15Index: django/core/management/sql.py
16===================================================================
17--- django/core/management/sql.py       (revision 8319)
18+++ django/core/management/sql.py       (working copy)
19@@ -84,6 +84,9 @@
20     references_to_delete = {}
21     app_models = models.get_models(app)
22     for model in app_models:
23+        # Find aditional tables in model defined schemas
24+        if model._meta.db_schema:
25+            table_names += connection.introspection.get_schema_table_list(cursor, model._meta.db_schema)
26         if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
27             # The table exists, so it needs to be dropped
28             opts = model._meta
29Index: django/db/backends/__init__.py
30===================================================================
31--- django/db/backends/__init__.py      (revision 8319)
32+++ django/db/backends/__init__.py      (working copy)
33@@ -225,6 +225,13 @@
34         """
35         raise NotImplementedError()
36 
37+    def prep_db_table(self, db_schema, db_table):
38+        """
39+        Prepares and formats the table name if neccesary.
40+        Just returns the db_table if not supported
41+        """
42+        return db_table
43+
44     def random_function_sql(self):
45         """
46         Returns a SQL expression that returns a random value.
47@@ -381,6 +388,14 @@
48         "Returns a list of names of all tables that exist in the database."
49         cursor = self.connection.cursor()
50         return self.get_table_list(cursor)
51+   
52+    def get_schema_table_list(self, cursor, schema):
53+        return []
54+   
55+    def schema_table_names(self, schema):
56+        "Returns a list of names of all tables that exist in the database schema."
57+        cursor = self.connection.cursor()
58+        return self.get_schema_table_list(cursor, schema)
59 
60     def django_table_names(self, only_existing=False):
61         """
62Index: django/db/backends/creation.py
63===================================================================
64--- django/db/backends/creation.py      (revision 8319)
65+++ django/db/backends/creation.py      (working copy)
66@@ -243,8 +243,14 @@
67                     tablespace_sql = ''
68             else:
69                 tablespace_sql = ''
70+            # Use original db_table in index name if schema is provided
71+            if model._meta.db_schema:
72+                index_table_name = model._meta._db_table
73+            else:
74+                index_table_name = model._meta.db_table
75+               
76             output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
77-                style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +
78+                style.SQL_TABLE(qn('%s_%s' % (index_table_name, f.column))) + ' ' +
79                 style.SQL_KEYWORD('ON') + ' ' +
80                 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
81                 "(%s)" % style.SQL_FIELD(qn(f.column)) +
82Index: django/db/backends/mysql/base.py
83===================================================================
84--- django/db/backends/mysql/base.py    (revision 8319)
85+++ django/db/backends/mysql/base.py    (working copy)
86@@ -106,8 +106,12 @@
87     def quote_name(self, name):
88         if name.startswith("`") and name.endswith("`"):
89             return name # Quoting once is enough.
90-        return "`%s`" % name
91+        # add support for tablenames passed that also have their schema in their name
92+        return "`%s`" % name.replace('.','`.`')
93 
94+    def prep_db_table(self, db_schema, db_table):
95+        return "%s.%s" % (db_schema, db_table)
96+
97     def random_function_sql(self):
98         return 'RAND()'
99 
100Index: django/db/backends/mysql/introspection.py
101===================================================================
102--- django/db/backends/mysql/introspection.py   (revision 8319)
103+++ django/db/backends/mysql/introspection.py   (working copy)
104@@ -32,6 +32,10 @@
105         cursor.execute("SHOW TABLES")
106         return [row[0] for row in cursor.fetchall()]
107 
108+    def get_schema_table_list(self, cursor, schema):
109+        cursor.execute("SHOW TABLES FROM %s" % self.connection.ops.quote_name(schema))
110+        return [schema + "." + row[0] for row in cursor.fetchall()]
111+   
112     def get_table_description(self, cursor, table_name):
113         "Returns a description of the table, with the DB-API cursor.description interface."
114         cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
115Index: django/db/models/options.py
116===================================================================
117--- django/db/models/options.py (revision 8319)
118+++ django/db/models/options.py (working copy)
119@@ -21,7 +21,7 @@
120 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
121                  'unique_together', 'permissions', 'get_latest_by',
122                  'order_with_respect_to', 'app_label', 'db_tablespace',
123-                 'abstract')
124+                 'abstract', 'db_schema')
125 
126 class Options(object):
127     def __init__(self, meta, app_label=None):
128@@ -29,6 +29,7 @@
129         self.module_name, self.verbose_name = None, None
130         self.verbose_name_plural = None
131         self.db_table = ''
132+        self.db_schema = ''
133         self.ordering = []
134         self.unique_together =  []
135         self.permissions =  []
136@@ -95,6 +96,14 @@
137             self.db_table = "%s_%s" % (self.app_label, self.module_name)
138             self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
139 
140+        # Patch db_table with the schema if provided and allowed
141+        if self.db_schema:
142+            # Store original db_table in a save place first
143+            self._db_table = self.db_table
144+            self.db_table = connection.ops.prep_db_table(self.db_schema, self.db_table)
145+            # If no changes were done then backend don't support schemas
146+            if self._db_table == self.db_table:
147+                self.db_schema = ''
148 
149     def _prepare(self, model):
150         if self.order_with_respect_to:
151Index: docs/model-api.txt
152===================================================================
153--- docs/model-api.txt  (revision 8319)
154+++ docs/model-api.txt  (working copy)
155@@ -1207,6 +1207,18 @@
156 that aren't allowed in Python variable names -- notably, the hyphen --
157 that's OK. Django quotes column and table names behind the scenes.
158 
159+``db_schema``
160+-----------------
161+
162+**New in Django development version**
163+
164+The name of the database schema to use for the model. If the backend
165+doesn't support multiple schemas, this options is ignored.
166+
167+If this is used Django will prefix any table names with the schema name.
168+For MySQL Django would use ``db_schema + '.' + db_table``.
169+
170+
171 ``db_tablespace``
172 -----------------
173 
174Index: tests/modeltests/schemas/__init__.py
175===================================================================
176Index: tests/modeltests/schemas/models.py
177===================================================================
178--- tests/modeltests/schemas/models.py  (revision 0)
179+++ tests/modeltests/schemas/models.py  (revision 0)
180@@ -0,0 +1,74 @@
181+# coding: utf-8
182+
183+from django.db import models
184+
185+
186+class Blog(models.Model):
187+    "Model in default schema"
188+    name = models.CharField(max_length=50)
189+
190+   
191+class Entry(models.Model):
192+    "Model in custom schema that reference the default"
193+    blog = models.ForeignKey(Blog)   
194+    title = models.CharField(max_length=50)
195+   
196+    class Meta:
197+        "using custom db_table as well"
198+        db_table='schema_blog_entries'
199+        db_schema = 'test_django'
200+       
201+
202+class Comment(models.Model):
203+    "Model in the default schema that references the custom"
204+    entry = models.ForeignKey(Entry)
205+    text = models.CharField(max_length=50)
206+
207+__test__ = {'API_TESTS': """
208+# Nothing in there yet
209+>>> Blog.objects.all()
210+[]
211+
212+# Create a blog
213+>>> b = Blog(name='Test')
214+>>> b.save()
215+
216+# Verify that we got an ID
217+>>> b.id
218+1
219+
220+# Create entry
221+>>> e = Entry(blog=b, title='Test entry')
222+>>> e.save()
223+>>> e.id
224+1
225+
226+# Create Comments
227+>>> c1 = Comment(entry=e, text='nice entry')
228+>>> c1.save()
229+>>> c2 = Comment(entry=e, text='really like it')
230+>>> c2.save()
231+
232+
233+>>> from django.conf import settings
234+>>> from django.db import connection, models
235+
236+# Test if we support schemas and can find the table if so
237+>>> if e._meta.db_schema:
238+...     tables = connection.introspection.schema_table_names(e._meta.db_schema)
239+... else:
240+...     tables = connection.introspection.table_names()
241+>>> if connection.introspection.table_name_converter(e._meta.db_table) in tables:
242+...     print "ok"
243+... else:
244+...     print "schema=" + e._meta.db_schema
245+ok
246+
247+# Test that mysql backend doesn't drop the schema
248+>>> if settings.DATABASE_ENGINE == 'mysql':
249+...     if e._meta.db_schema != 'test_django':
250+...         print "shouldn't drop or modify schema"
251+
252+#Done
253+"""
254+}