From f194f74aa62727e4624b3e6e1e06fdbeebc4f3b5 Mon Sep 17 00:00:00 2001 From: Jason Pellerin Date: Mon, 3 Jul 2006 20:56:24 +0000 Subject: [PATCH] [multi-db] Added install() and other schema manipulation methods to Manager. Fixed bug in manager assignment for inherited classes (objects and _default_manager in child class were still those belonging to parent class). git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3266 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/manager.py | 40 +++++- .../othertests/manager_schema_manipulation.py | 131 ++++++++++++++++++ 2 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 tests/othertests/manager_schema_manipulation.py diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 46a1710c1c..71a3ea5403 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -20,7 +20,9 @@ def ensure_default_manager(sender): except FieldDoesNotExist: pass cls.add_to_class('objects', Manager()) - + elif cls._default_manager.model != cls: + # cls is an inherited model; don't want the parent manager + cls.add_to_class('objects', Manager()) dispatcher.connect(ensure_default_manager, signal=signals.class_prepared) class Manager(object): @@ -38,7 +40,9 @@ class Manager(object): # TODO: Use weakref because of possible memory leak / circular reference. self.model = model setattr(model, name, ManagerDescriptor(self)) - if not hasattr(model, '_default_manager') or self.creation_counter < model._default_manager.creation_counter: + if not hasattr(model, '_default_manager') \ + or self.creation_counter < model._default_manager.creation_counter \ + or model._default_manager.model != model: model._default_manager = self ####################### @@ -102,6 +106,38 @@ class Manager(object): def values(self, *args, **kwargs): return self.get_query_set().values(*args, **kwargs) + ####################### + # SCHEMA MANIPULATION # + ####################### + + def install(self, initial_data=False): + """Install my model's table, indexes and (if requested) initial data. + + Returns a 2-tuple of the lists of statements executed and + statements pending. Pending statements are those that could + not yet be executed, such as foreign key constraints for + tables that don't exist at install time. + """ + creator = self.model._meta.connection_info.get_creation_module() + run, pending = creator.builder.get_create_table(self.model) + run += creator.builder.get_create_indexes(self.model) + pending += creator.builder.get_create_many_to_many(self.model) + if initial_data: + run += creator.builder.get_initialdata(self.model) + + for statement in run: + statement.execute() + return pending + + def load_initial_data(self): + """Load initial data for my model into the database.""" + pass # FIXME + + def drop(self): + """Drop my model's table.""" + pass # FIXME + + class ManagerDescriptor(object): # This class ensures managers aren't accessible via model instances. # For example, Poll.objects works, but poll_obj.objects raises AttributeError. diff --git a/tests/othertests/manager_schema_manipulation.py b/tests/othertests/manager_schema_manipulation.py new file mode 100644 index 0000000000..e3c8fb37b9 --- /dev/null +++ b/tests/othertests/manager_schema_manipulation.py @@ -0,0 +1,131 @@ +""" +# Django uses a model's default manager to perform schema manipulations such as +# creating or dropping the model's table. + +>>> from django.db import models + +# default connection +>>> class DA(models.Model): +... name = models.CharField(maxlength=20) +... +... def __str__(self): +... return self.name + +# connection django_test_db_a +>>> class PA(models.Model): +... name = models.CharField(maxlength=20) +... # This creates a cycle in the dependency graph +... c = models.ForeignKey('PC', null=True) +... +... def __str__(self): +... return self.name +... +... class Meta: +... db_connection = 'django_test_db_a' + +>>> class PB(models.Model): +... name = models.CharField(maxlength=20) +... a = models.ForeignKey(PA) +... +... def __str__(self): +... return self.name +... +... class Meta: +... db_connection = 'django_test_db_a' + +>>> class PC(models.Model): +... name = models.CharField(maxlength=20) +... b = models.ForeignKey(PB) +... +... def __str__(self): +... return self.name +... +... class Meta: +... db_connection = 'django_test_db_a' + +# connection django_test_db_b +>>> class QA(models.Model): +... name = models.CharField(maxlength=20) +... +... def __str__(self): +... return self.name +... +... class Meta: +... db_connection = 'django_test_db_b' + +>>> class QB(models.Model): +... name = models.CharField(maxlength=20) +... a = models.ForeignKey(QA) +... +... def __str__(self): +... return self.name +... +... class Meta: +... db_connection = 'django_test_db_b' + +# many-many +>>> class QC(models.Model): +... name = models.CharField(maxlength=20) +... +... def __str__(self): +... return self.name +... +... class Meta: +... db_connection = 'django_test_db_b' + +>>> class QD(models.Model): +... name = models.CharField(maxlength=20) +... qcs = models.ManyToManyField(QC) +... +... def __str__(self): +... return self.name +... +... class Meta: +... db_connection = 'django_test_db_b' + +# Using the manager, models can be installed individually, whether they +# use the default connection or a named connection. + +>>> DA.objects.install() +[] +>>> QA.objects.install() +[] +>>> QB.objects.install() +[] +>>> DA.objects.all() +[] +>>> list(QA.objects.all()) +[] +>>> list(QB.objects.all()) +[] +>>> QA(name="something").save() +>>> QA.objects.all() +[] + +# The `install()` method returns a tuple, the first element of which is a +# list of statements that were executed, and the second, pending +# statements that could not be executed because (for instance) they are +# meant to establish foreign key relationships to tables that don't +# exist. These are bound to the model's connection and should +# be executed after all models in the app have been installed. + +# NOTE: pretend db supports constraints for this test +>>> real_cnst = PA._meta.connection_info.backend.supports_constraints +>>> PA._meta.connection_info.backend.supports_constraints = True +>>> result = PA.objects.install() +>>> result +[BoundStatement('ALTER TABLE "othertests_pa" ADD CONSTRAINT "c_id_referencing_othertests_pc_id" FOREIGN KEY ("c_id") REFERENCES "othertests_pc" ("id");')] + +# NOTE: restore real constraint flag +>>> PA._meta.connection_info.backend.supports_constraints = real_cnst + +# Models with many-many relationships will also have pending statement +# lists. Like other pending statements, these should be executed after +# all models in the app have been installed. + +>>> QC.objects.install() +[] +>>> QD.objects.install() +[BoundStatement('CREATE TABLE "othertests_qd_qcs" (...);')] + +"""