Skip to content

Commit

Permalink
Fixed #1142 -- Added multiple database support.
Browse files Browse the repository at this point in the history
This monster of a patch is the result of Alex Gaynor's 2009 Google Summer of Code project.
Congratulations to Alex for a job well done.

Big thanks also go to:
 * Justin Bronn for keeping GIS in line with the changes,
 * Karen Tracey and Jani Tiainen for their help testing Oracle support
 * Brett Hoerner, Jon Loyens, and Craig Kimmerer for their feedback.
 * Malcolm Treddinick for his guidance during the GSoC submission process.
 * Simon Willison for driving the original design process
 * Cal Henderson for complaining about ponies he wanted.

... and everyone else too numerous to mention that helped to bring this feature into fruition.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11952 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
russellm committed Dec 22, 2009
1 parent 8aa940f commit 836d297
Show file tree
Hide file tree
Showing 231 changed files with 7,874 additions and 5,682 deletions.
3 changes: 3 additions & 0 deletions django/conf/global_settings.py
Expand Up @@ -131,6 +131,9 @@
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
DATABASE_OPTIONS = {} # Set to empty dictionary for default.

DATABASES = {
}

# The email backend to use. For possible shortcuts see django.core.mail.
# The default is to use the SMTP backend.
# Third-party backends can be specified by providing a Python path
Expand Down
16 changes: 10 additions & 6 deletions django/conf/project_template/settings.py
Expand Up @@ -9,12 +9,16 @@

MANAGERS = ADMINS

DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = '' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': '', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
Expand Down
31 changes: 21 additions & 10 deletions django/contrib/admin/options.py
Expand Up @@ -141,8 +141,9 @@ def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
"""
Get a form Field for a ForeignKey.
"""
db = kwargs.get('using')
if db_field.name in self.raw_id_fields:
kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db)
elif db_field.name in self.radio_fields:
kwargs['widget'] = widgets.AdminRadioSelect(attrs={
'class': get_ul_class(self.radio_fields[db_field.name]),
Expand All @@ -159,9 +160,10 @@ def formfield_for_manytomany(self, db_field, request=None, **kwargs):
# a field in admin.
if not db_field.rel.through._meta.auto_created:
return None
db = kwargs.get('using')

if db_field.name in self.raw_id_fields:
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
kwargs['help_text'] = ''
elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
Expand Down Expand Up @@ -739,15 +741,15 @@ def add_view(self, request, form_url='', extra_context=None):
form_validated = False
new_object = self.model()
prefixes = {}
for FormSet in self.get_formsets(request):
for FormSet, inline in zip(self.get_formsets(request), self.inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new=request.POST.has_key("_saveasnew"),
prefix=prefix)
prefix=prefix, queryset=inline.queryset(request))
formsets.append(formset)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, change=False)
Expand All @@ -770,12 +772,14 @@ def add_view(self, request, form_url='', extra_context=None):
initial[k] = initial[k].split(",")
form = ModelForm(initial=initial)
prefixes = {}
for FormSet in self.get_formsets(request):
for FormSet, inline in zip(self.get_formsets(request),
self.inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=self.model(), prefix=prefix)
formset = FormSet(instance=self.model(), prefix=prefix,
queryset=inline.queryset(request))
formsets.append(formset)

adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
Expand Down Expand Up @@ -837,13 +841,16 @@ def change_view(self, request, object_id, extra_context=None):
form_validated = False
new_object = obj
prefixes = {}
for FormSet in self.get_formsets(request, new_object):
for FormSet, inline in zip(self.get_formsets(request, new_object),
self.inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(request.POST, request.FILES,
instance=new_object, prefix=prefix)
instance=new_object, prefix=prefix,
queryset=inline.queryset(request))

formsets.append(formset)

if all_valid(formsets) and form_validated:
Expand All @@ -859,12 +866,13 @@ def change_view(self, request, object_id, extra_context=None):
else:
form = ModelForm(instance=obj)
prefixes = {}
for FormSet in self.get_formsets(request, obj):
for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=obj, prefix=prefix)
formset = FormSet(instance=obj, prefix=prefix,
queryset=inline.queryset(request))
formsets.append(formset)

adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
Expand Down Expand Up @@ -1187,6 +1195,9 @@ def get_fieldsets(self, request, obj=None):
form = self.get_formset(request).form
return [(None, {'fields': form.base_fields.keys()})]

def queryset(self, request):
return self.model._default_manager.all()

class StackedInline(InlineModelAdmin):
template = 'admin/edit_inline/stacked.html'

Expand Down
9 changes: 5 additions & 4 deletions django/contrib/admin/widgets.py
Expand Up @@ -102,8 +102,9 @@ class ForeignKeyRawIdWidget(forms.TextInput):
A Widget for displaying ForeignKeys in the "raw_id" interface rather than
in a <select> box.
"""
def __init__(self, rel, attrs=None):
def __init__(self, rel, attrs=None, using=None):
self.rel = rel
self.db = using
super(ForeignKeyRawIdWidget, self).__init__(attrs)

def render(self, name, value, attrs=None):
Expand Down Expand Up @@ -148,16 +149,16 @@ def url_parameters(self):

def label_for_value(self, value):
key = self.rel.get_related_field().name
obj = self.rel.to._default_manager.get(**{key: value})
obj = self.rel.to._default_manager.using(self.db).get(**{key: value})
return '&nbsp;<strong>%s</strong>' % escape(truncate_words(obj, 14))

class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
"""
A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
in a <select multiple> box.
"""
def __init__(self, rel, attrs=None):
super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
def __init__(self, rel, attrs=None, using=None):
super(ManyToManyRawIdWidget, self).__init__(rel, attrs, using=None)

def render(self, name, value, attrs=None):
attrs['class'] = 'vManyToManyRawIdAdminField'
Expand Down
14 changes: 5 additions & 9 deletions django/contrib/auth/models.py
Expand Up @@ -3,19 +3,15 @@

from django.contrib import auth
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db import models, DEFAULT_DB_ALIAS
from django.db.models.manager import EmptyManager
from django.contrib.contenttypes.models import ContentType
from django.utils.encoding import smart_str
from django.utils.hashcompat import md5_constructor, sha_constructor
from django.utils.translation import ugettext_lazy as _

UNUSABLE_PASSWORD = '!' # This will never be a valid hash

try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
UNUSABLE_PASSWORD = '!' # This will never be a valid hash

def get_hexdigest(algorithm, salt, raw_password):
"""
Expand Down Expand Up @@ -114,15 +110,15 @@ def create_user(self, username, email, password=None):
user.set_password(password)
else:
user.set_unusable_password()
user.save()
user.save(using=self.db)
return user

def create_superuser(self, username, email, password):
u = self.create_user(username, email, password)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save()
u.save(using=self.db)
return u

def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
Expand Down Expand Up @@ -319,7 +315,7 @@ def get_profile(self):
try:
app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
model = models.get_model(app_label, model_name)
self._profile_cache = model._default_manager.get(user__id__exact=self.id)
self._profile_cache = model._default_manager.using(self._state.db).get(user__id__exact=self.id)
self._profile_cache.user = self
except (ImportError, ImproperlyConfigured):
raise SiteProfileNotAvailable
Expand Down
18 changes: 10 additions & 8 deletions django/contrib/comments/forms.py
Expand Up @@ -28,7 +28,7 @@ def __init__(self, target_object, data=None, initial=None):
initial = {}
initial.update(self.generate_security_data())
super(CommentSecurityForm, self).__init__(data=data, initial=initial)

def security_errors(self):
"""Return just those errors associated with security"""
errors = ErrorDict()
Expand Down Expand Up @@ -107,21 +107,21 @@ def get_comment_object(self):
"""
if not self.is_valid():
raise ValueError("get_comment_object may only be called on valid forms")

CommentModel = self.get_comment_model()
new = CommentModel(**self.get_comment_create_data())
new = self.check_for_duplicate_comment(new)

return new

def get_comment_model(self):
"""
Get the comment model to create with this form. Subclasses in custom
comment apps should override this, get_comment_create_data, and perhaps
check_for_duplicate_comment to provide custom comment models.
"""
return Comment

def get_comment_create_data(self):
"""
Returns the dict of data to be used to create a comment. Subclasses in
Expand All @@ -140,13 +140,15 @@ def get_comment_create_data(self):
is_public = True,
is_removed = False,
)

def check_for_duplicate_comment(self, new):
"""
Check that a submitted comment isn't a duplicate. This might be caused
by someone posting a comment twice. If it is a dup, silently return the *previous* comment.
"""
possible_duplicates = self.get_comment_model()._default_manager.filter(
possible_duplicates = self.get_comment_model()._default_manager.using(
self.target_object._state.db
).filter(
content_type = new.content_type,
object_pk = new.object_pk,
user_name = new.user_name,
Expand All @@ -156,7 +158,7 @@ def check_for_duplicate_comment(self, new):
for old in possible_duplicates:
if old.submit_date.date() == new.submit_date.date() and old.comment == new.comment:
return old

return new

def clean_comment(self):
Expand Down
8 changes: 4 additions & 4 deletions django/contrib/comments/models.py
Expand Up @@ -79,10 +79,10 @@ class Meta:
def __unicode__(self):
return "%s: %s..." % (self.name, self.comment[:50])

def save(self, force_insert=False, force_update=False):
def save(self, *args, **kwargs):
if self.submit_date is None:
self.submit_date = datetime.datetime.now()
super(Comment, self).save(force_insert, force_update)
super(Comment, self).save(*args, **kwargs)

def _get_userinfo(self):
"""
Expand Down Expand Up @@ -185,7 +185,7 @@ def __unicode__(self):
return "%s flag of comment ID %s by %s" % \
(self.flag, self.comment_id, self.user.username)

def save(self, force_insert=False, force_update=False):
def save(self, *args, **kwargs):
if self.flag_date is None:
self.flag_date = datetime.datetime.now()
super(CommentFlag, self).save(force_insert, force_update)
super(CommentFlag, self).save(*args, **kwargs)
4 changes: 2 additions & 2 deletions django/contrib/comments/views/comments.py
Expand Up @@ -25,7 +25,7 @@ def __init__(self, why):

@csrf_protect
@require_POST
def post_comment(request, next=None):
def post_comment(request, next=None, using=None):
"""
Post a comment.
Expand All @@ -50,7 +50,7 @@ def post_comment(request, next=None):
return CommentPostBadRequest("Missing content_type or object_pk field.")
try:
model = models.get_model(*ctype.split(".", 1))
target = model._default_manager.get(pk=object_pk)
target = model._default_manager.using(using).get(pk=object_pk)
except TypeError:
return CommentPostBadRequest(
"Invalid content_type value: %r" % escape(ctype))
Expand Down

0 comments on commit 836d297

Please sign in to comment.