Nasıl yabancı anahtarlar için destek eksikliği gidermek için Django veritabanları üzerinde
Django, çok sayıda veritabanı arasında yabancı anahtarları desteklemiyor biliyorum: http://docs.djangoproject.com/en/1.3/topics/db/multi-db/#cross-database-relations
Ama geçici bir çözüm arıyorum.
Ne işe yaramazsa
Ayrı bir veritabanı üzerinde her iki model var.
routers.py:
class NewsRouter(object):
def db_for_read(self, model, **hints):
if model._meta.app_label == 'news_app':
return 'news_db'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label == 'news_app':
return 'news_db'
return None
def allow_relation(self, obj1, obj2, **hints):
if obj1._meta.app_label == 'news_app' or obj2._meta.app_label == 'news_app':
return True
return None
def allow_syncdb(self, db, model):
if db == 'news_db':
return model._meta.app_label == 'news_app'
elif model._meta.app_label == 'news_app':
return False
return None
Fruit_app/models.py Model 1:
from django.db import models
class Fruit(models.Model):
name = models.CharField(max_length=20)
News_app/models.py Model 2:
from django.db import models
class Article(models.Model):
fruit = models.ForeignKey('fruit_app.Fruit')
intro = models.TextField()
"Madde" yönetici, yanlış bir veritabanı üzerinde Fruit
modeli ('news_db'
) bakarak için aşağıdaki hata verir: . bir eklemeye
DatabaseError at /admin/news_app/article/add/
(1146, "Table 'fkad_news.fruit_app_fruit' doesn't exist")
Yöntem 1: alt İntegerField
İntegerField bir alt sınıfı olan bir özel alan, ForeignKeyAcrossDb oluşturdum. Github üzerinde kod: https://github.com/saltycrane/django-foreign-key-across-db-testproject/tree/integerfield_subclass
fields.py:
from django.db import models
class ForeignKeyAcrossDb(models.IntegerField):
'''
Exists because foreign keys do not work across databases
'''
def __init__(self, model_on_other_db, **kwargs):
self.model_on_other_db = model_on_other_db
super(ForeignKeyAcrossDb, self).__init__(**kwargs)
def to_python(self, value):
# TODO: this db lookup is duplicated in get_prep_lookup()
if isinstance(value, self.model_on_other_db):
return value
else:
return self.model_on_other_db._default_manager.get(pk=value)
def get_prep_value(self, value):
if isinstance(value, self.model_on_other_db):
value = value.pk
return super(ForeignKeyAcrossDb, self).get_prep_value(value)
def get_prep_lookup(self, lookup_type, value):
# TODO: this db lookup is duplicated in to_python()
if not isinstance(value, self.model_on_other_db):
value = self.model_on_other_db._default_manager.get(pk=value)
return super(ForeignKeyAcrossDb, self).get_prep_lookup(lookup_type, value)
Ve makalemi model olarak değiştirdim:
class Article(models.Model):
fruit = ForeignKeyAcrossDb(Fruit)
intro = models.TextField()
Sorun, ben Makale erişirken bazen.meyve, bir tamsayı, ve bazen Meyve nesnedir. Her zaman Meyve bir nesne olmak istiyorum. Benim için Makale erişim yapmak gerekir.her zaman Meyve bir nesneyi döndürmek meyve?
Benim çözüm için geçici bir çözüm olarak, fruit_obj
bir özellik ekledim, ama mümkünse bunu ortadan kaldırmak istiyorum:
class Article(models.Model):
fruit = ForeignKeyAcrossDb(Fruit)
intro = models.TextField()
# TODO: shouldn't need fruit_obj if ForeignKeyAcrossDb field worked properly
@property
def fruit_obj(self):
if not hasattr(self, '_fruit_obj'):
# TODO: why is it sometimes an int and sometimes a Fruit object?
if isinstance(self.fruit, int) or isinstance(self.fruit, long):
print 'self.fruit IS a number'
self._fruit_obj = Fruit.objects.get(pk=self.fruit)
else:
print 'self.fruit IS NOT a number'
self._fruit_obj = self.fruit
return self._fruit_obj
def fruit_name(self):
return self.fruit_obj.name
Yöntem 2: alt ForeignKey alan
İkinci bir girişim olarak, ForeignKey alan sınıflara çalıştım. ReverseSingleRelatedObjectDescriptor
veritabanı Fruit
model yöneticisi forced_using
ile belirtilen kullanmak için değiştirdim. Ben de ForeignKey
sınıfı validate()
yöntemi kaldırıldı. Bu yöntem 1 ile aynı sorunu yoktu. Github üzerinde kod: https://github.com/saltycrane/django-foreign-key-across-db-testproject/tree/foreignkey_subclass
fields.py:
from django.db import models
from django.db import router
from django.db.models.query import QuerySet
class ReverseSingleRelatedObjectDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
# a single "remote" value, on the class that defines the related field.
# In the example "choice.poll", the poll attribute is a
# ReverseSingleRelatedObjectDescriptor instance.
def __init__(self, field_with_rel):
self.field = field_with_rel
def __get__(self, instance, instance_type=None):
if instance is None:
return self
cache_name = self.field.get_cache_name()
try:
return getattr(instance, cache_name)
except AttributeError:
val = getattr(instance, self.field.attname)
if val is None:
# If NULL is an allowed value, return it.
if self.field.null:
return None
raise self.field.rel.to.DoesNotExist
other_field = self.field.rel.get_related_field()
if other_field.rel:
params = {'%s__pk' % self.field.rel.field_name: val}
else:
params = {'%s__exact' % self.field.rel.field_name: val}
# If the related manager indicates that it should be used for
# related fields, respect that.
rel_mgr = self.field.rel.to._default_manager
db = router.db_for_read(self.field.rel.to, instance=instance)
if getattr(rel_mgr, 'forced_using', False):
db = rel_mgr.forced_using
rel_obj = rel_mgr.using(db).get(**params)
elif getattr(rel_mgr, 'use_for_related_fields', False):
rel_obj = rel_mgr.using(db).get(**params)
else:
rel_obj = QuerySet(self.field.rel.to).using(db).get(**params)
setattr(instance, cache_name, rel_obj)
return rel_obj
def __set__(self, instance, value):
raise NotImplementedError()
class ForeignKeyAcrossDb(models.ForeignKey):
def contribute_to_class(self, cls, name):
models.ForeignKey.contribute_to_class(self, cls, name)
setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
if isinstance(self.rel.to, basestring):
target = self.rel.to
else:
target = self.rel.to._meta.db_table
cls._meta.duplicate_targets[self.column] = (target, "o2m")
def validate(self, value, model_instance):
pass
fruit_app/models.py:
from django.db import models
class FruitManager(models.Manager):
forced_using = 'default'
class Fruit(models.Model):
name = models.CharField(max_length=20)
objects = FruitManager()
news_app/models.py:
from django.db import models
from foreign_key_across_db_testproject.fields import ForeignKeyAcrossDb
from foreign_key_across_db_testproject.fruit_app.models import Fruit
class Article(models.Model):
fruit = ForeignKeyAcrossDb(Fruit)
intro = models.TextField()
def fruit_name(self):
return self.fruit.name
Yöntem 2a: fruit_app . bir yönlendirici Ekle
Bu çözüm fruit_app
için ek bir yönlendirici kullanır. Bu çözüm Yöntemi 2 gerekli idi ForeignKey
değişiklikler gerektirmez. Sonra bakıyorsun Django varsayılan yönlendirme davranış django.db.utils.ConnectionRouter
, bulduğumuz rağmen beklediğimizden fruit_app
olmak 'default'
veritabanı varsayılan olarak, instance
ipucu geçti db_for_read
yabancı anahtar aramalar koyun 'news_db'
veritabanı. fruit_app
modeller her zaman 'default'
veritabanından okundu sağlamak için ikinci bir yönlendirici ekledik. ** 42 sınıfı sadece "" ForeignKey.validate()
yöntemi. düzeltmek için kullanılır Eğer Django veritabanları arasında yabancı anahtarları destek olmak, Django bir hata olduğunu söyleyebilirim.)
Github üzerinde kod: https://github.com/saltycrane/django-foreign-key-across-db-testproject
routers.py:
class NewsRouter(object):
def db_for_read(self, model, **hints):
if model._meta.app_label == 'news_app':
return 'news_db'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label == 'news_app':
return 'news_db'
return None
def allow_relation(self, obj1, obj2, **hints):
if obj1._meta.app_label == 'news_app' or obj2._meta.app_label == 'news_app':
return True
return None
def allow_syncdb(self, db, model):
if db == 'news_db':
return model._meta.app_label == 'news_app'
elif model._meta.app_label == 'news_app':
return False
return None
class FruitRouter(object):
def db_for_read(self, model, **hints):
if model._meta.app_label == 'fruit_app':
return 'default'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label == 'fruit_app':
return 'default'
return None
def allow_relation(self, obj1, obj2, **hints):
if obj1._meta.app_label == 'fruit_app' or obj2._meta.app_label == 'fruit_app':
return True
return None
def allow_syncdb(self, db, model):
if db == 'default':
return model._meta.app_label == 'fruit_app'
elif model._meta.app_label == 'fruit_app':
return False
return None
fruit_app/models.py:
from django.db import models
class Fruit(models.Model):
name = models.CharField(max_length=20)
news_app/models.py:
from django.db import models
from foreign_key_across_db_testproject.fields import ForeignKeyAcrossDb
from foreign_key_across_db_testproject.fruit_app.models import Fruit
class Article(models.Model):
fruit = ForeignKeyAcrossDb(Fruit)
intro = models.TextField()
def fruit_name(self):
return self.fruit.name
fields.py:
from django.core import exceptions
from django.db import models
from django.db import router
class ForeignKeyAcrossDb(models.ForeignKey):
def validate(self, value, model_instance):
if self.rel.parent_link:
return
models.Field.validate(self, value, model_instance)
if value is None:
return
using = router.db_for_read(self.rel.to, instance=model_instance) # is this more correct than Django's 1.2.5 version?
qs = self.rel.to._default_manager.using(using).filter(
**{self.rel.field_name: value}
)
qs = qs.complex_filter(self.rel.limit_choices_to)
if not qs.exists():
raise exceptions.ValidationError(self.error_messages['invalid'] % {
'model': self.rel.to._meta.verbose_name, 'pk': value})
Ek bilgi
- Bir sürü bilgi olan django-users listesine konu: http://groups.google.com/group/django-users/browse_thread/thread/74bcd1afdeb2f0/0fdfce061124b915
- Multi-db belgelerine Revizyon Tarihi: http://code.djangoproject.com/log/django/trunk/docs/topics/db/multi-db.txt?verbose=on
Güncelleme
Bizim yönlendiriciler biraz daha sıkıyorum sonra son yöntem hayata geçirdik. Tüm uygulama, bizi yanlış yaptık ve bu yüzden de oldukça sancılı olmuştur. YAPILACAKLAR listesinde bulunan bu yazma birim testleri.
CEVAP
ForeignKeyAcrossDb
parçası olarak, belki __init__
iç sınıf için bazı ayarlamalar yapmak olamaz mı? Eğer uygun alan 50 ** değilse, veritabanından yüklemek, ya da gereken başka bir şey olup olmadığını kontrol edin. __class__
Python es fazla sorun olmadan çalışma zamanında değiştirilebilir.
Ben başvuru belirli bir tablo yabancı ...
Nasıl birden fazla dosya üzerinde djan...
Nasıl iPhone SDK üzerinde etkin bir İn...
Nasıl bir DOM düğüm üzerinde olay dinl...
Nasıl bir hizmet Android üzerinde çalı...