Django多数据库:向多个数据库写入数据

6

使用Django multidb,编写一个运行主/从基础架构的路由器相当容易。但是否可能编写一个可以向多个数据库写入的路由器?我的用例是一组在同一域上运行的项目。为了使用户不必在每个站点上注册/登录,我想同步contrib.authcontrib.sessions表。这在Django multidb中是否可能,或者应该查看数据库系统(在我的情况下是MySQL)的复制功能?

4个回答

8

我认为你最好实现单点登录(SSO)或OAuth服务。

但是,如果你想要在两个数据库之间同步用户表,并且正在使用自己的UserModel,你可以像这样做:

class MyUser(models.Model):
    name = models.CharField(max_length=100)
    user = models.ForeignKey(User, unique=True)


    def save(self, ...): # ALL the signature
        super(MyUser, self).save(using='database_1')
        super(MyUser, self).save(using='database_2')

你还可以像这样使用装饰器,这样你也可以将其用于同步其他表格:

你还可以像这样使用装饰器,这样你也可以将其用于同步其他表格:

def save_multi_db(model_class):

    def save_wrapper(save_func): 
        def new_save(self, *args, **kws):
            super(model_class, self).save(using='database_1')
            super(model_class, self).save(using='database_1')
        return new_save

    func = getattr(model_class, 'save')
    setattr(model_class, 'save', save_wrapper(func)) 

    return save_wrapper

# and use it like this:

@save_multi_db
class MyUser(models.Model):
      ....

希望这能帮到你 :)

我必须更改Django的源代码才能使其工作(正如我在问题中所说,我想要复制的表来自contrib.authcontrib.sessions)。如果可能的话,我想避免干扰Django本身。 - Benjamin Wohlwend

4

我现在正在开发一个Django分片模式。

我看了Django路由器,但决定自己编写。

以下是一些关于你的问题的想法:

  1. 一个想法是使用一个数据库,然后通过在post-save上使用Django信号来复制适当的数据。

类似这样--

import settings.databases as dbs_list

def post_save_function(UserModel):
     for db in dbs_list:
          UserModel.save(using=db,force_insert=True)

在单个数据库模型中,保存用户对象似乎会通过django.contrib内部发生的各种魔法自动保存会话、认证等数据,因此您可能不必进入并找出所有这些数据库表的名称和类型。
支持这种可能性的是,我发誓最近在某个地方(可能是Alex Gaynor的博客文章之一)读到过,如果一个对象具有外键,Django将尝试使用相同的DB(考虑到Django通常的操作方式,这是很明显的原因)。
另一个想法:
从您引用的Django多DB页面上的示例中,我想知道以下代码是否可行:
def allow_syncdb(self, db, model):
        "Explicitly put all models on all databases."
        return True

一个可能的修改:

定义允许同步数据库函数(self, db, model):

    if isinstance(model,User):
         return True

    elif isinstance(model,Session):
         return True

    else:
         ''' something appropriate --
             whatever your sharding schema is for other objects '''

重新审视这段代码,它可能更适合作为“db_for_write”函数。但你已经有了想法。

毫无疑问,你需要添加其他类型的模型才能使其正常工作(所有广泛的身份验证内容)。

祝你好运!希望这对你有所帮助。

我对你的发现和评论很感兴趣!

jb


嗨,感谢您的评论。由于我们已经采用LDAP/Kerberos系统提供集中式认证和单点登录,因此我不再追求这个特定问题了。 - Benjamin Wohlwend

1

首先,我认为你需要的更多是一个SSO框架,比如在这篇文章中提到的

我尝试了mouad的答案,但我无法让类装饰器起作用...而且我觉得这个解决方案不允许在模型中有自定义的save()函数。

更适合我的需求,我定义了一个自定义通用类,并简单地重写了save()函数。

class MultiDbModel(models.Model):
    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        for dbname in settings.DATABASES:
            super(MultiDbModel, self).save(using=dbname)

然后:

class MyObject(MultiDbModel):
    [...]
    def save(self, *args, **kwargs):
        [custom save]
        super(MyObject, self).save(args, kwargs)

1
太糟糕了,db路由器中的db_for_write无法返回多个写入数据库..这将是很酷的。另一个问题是使用filter().update()不会触发save(),因此您只能使用save。 - radtek
如果在第二个数据库中不存在要保存的对象,这也不考虑保存相关对象(FKs)。 - radtek

0
这是我的multidb_model.py文件,它似乎也处理外键。如果有bug,我会更新它,但一旦你按照Stepahne的答案继承了这个类,它就会处理外键保存。
import logging

from django.db import models

DEFAULT_DB = 'default'  # main postgres host
MIRROR_COPY_DB = 'pg_mirror'  # a copy original db, i.e. if you want to move your data and keep it in sync


class MultiPgDbModel(models.Model):
    class Meta:
        abstract = True

    def save(self, *args, **kwarg):
        super(MultiPgDbModel, self).save(using=DEFAULT_DB)
        try:
            fields = [field for field in self._meta.fields if field.get_internal_type() == 'ForeignKey']

            for field in fields:
                getattr(self, field.name).save(using=MIRROR_COPY_DB)
            super(MultiPgDbModel, self).save(using=MIRROR_COPY_DB)
        except Exception as e:
            logging.exception(f"MultiPgDbModel.save unexpected error when saving to pg_mirror, object id "
                              f"{self.pk} of model {self._meta.model}. {e}")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接