SORU
30 Mart 2011, ÇARŞAMBA


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

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
14 NİSAN 2011, PERŞEMBE


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.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • jonathepianist

    jonathepiani

    31 Temmuz 2008
  • LiveForGodsKingdom

    LiveForGodsK

    6 NİSAN 2008
  • Matt Davis

    Matt Davis

    4 ŞUBAT 2006