python 如何在Django中使用(db)覆盖'Model.__init__'和尊重'.?

ars1skjm  于 2022-10-30  发布在  Python
关注(0)|答案(1)|浏览(286)

下面的代码:

print(f"current database: {self.db}\ninfusate from database: {infusate._state.db}\ntracer from database: {tracer._state.db}")
FCirc.objects.using(self.db).get_or_create(
    serum_sample=sample,
    tracer=tracer,
    element=label.element,
)

生成以下输出和异常:

current database: validation
infusate from database: validation
tracer from database: validation
Validating FCirc updater: {'update_function': 'is_last_serum_peak_group', 'update_field': 'is_last', 'parent_field': 'serum_sample', 'child_fields': [], 'update_label': 'fcirc_calcs', 'generation': 2}
Traceback (most recent call last):
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 581, in get_or_create
    return self.get(**kwargs), False
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 435, in get
    raise self.model.DoesNotExist(
DataRepo.models.fcirc.FCirc.DoesNotExist: FCirc matching query does not exist.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/DataRepo/views/loading/validation.py", line 91, in validate_load_files
    call_command(
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/.venv/lib/python3.9/site-packages/django/core/management/__init__.py", line 181, in call_command
    return command.execute(*args,**defaults)
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/.venv/lib/python3.9/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args,**options)
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/DataRepo/management/commands/load_animals_and_samples.py", line 134, in handle
    loader.load_sample_table(
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/DataRepo/utils/sample_table_loader.py", line 426, in load_sample_table
    FCirc.objects.using(self.db).get_or_create(
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 588, in get_or_create
    return self.create(**params), True
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 451, in create
    obj = self.model(**kwargs)
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/DataRepo/models/maintained_model.py", line 430, in __init__
    super().__init__(*args,**kwargs)
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/.venv/lib/python3.9/site-packages/django/db/models/base.py", line 485, in __init__
    _setattr(self, field.name, rel_obj)
  File "/Users/rleach/PROJECT-local/TRACEBASE/tracebase/.venv/lib/python3.9/site-packages/django/db/models/fields/related_descriptors.py", line 229, in __set__
    raise ValueError('Cannot assign "%r": the current database router prevents relation between database "%s" and "%s".' % (value, instance._state.db, value._state.db))
ValueError: Cannot assign "<Tracer: lysine-[13C6]>": the current database router prevents this relation.
Cannot assign "<Tracer: lysine-[13C6]>": the current database router prevents this relation.

由于知道这个错误与不同数据库中记录之间的外部关系有关,所以作为一种健全性检查,我修改了related_descriptors.py的源代码以包含更多信息:

raise ValueError('Cannot assign "%r": the current database router prevents relations between database "%s" and "%s".' % (value, instance._state.db, value._state.db))

上面写着:

Cannot assign "<Tracer: lysine-[13C6]>": the current database router prevents relations between database "default" and "validation".

所以我要疯了。为什么它忽略了我的.using(self.db)呼叫?!
然后我意识到,“哦,是的--我在超类中覆盖了__init__到FCirc!我可能是在绕过using(db)。"

class FCirc(MaintainedModel, HierCachedModel):
    ...

class MaintainedModel(Model):
    ...

在两个超类混合中,MaintainedModel似乎是这个例子中的罪魁祸首。它是唯一一个覆盖__init__的超类。覆盖如下:

def __init__(self, *args,**kwargs):
        """
        This over-ride of the constructor is to prevent developers from explicitly setting values for automatically
        maintained fields.  It also performs a one-time validation check of the updater_dicts.
        """

        # ... about 80 lines of code that I'm very confident are unrelated to the problem.  See the docstring above.  Will paste upon request ...

        # vvv THIS LINE IS LINE 430 FROM maintained_model.py IN THE TRACE ABOVE
        super().__init__(*args,**kwargs)

如何在超级构造函数中沿着self.db?

798qvoo8

798qvoo81#

我发现了!另一个开发者在QuerySet类的派生类中为一些模型添加了对full_cleanwhich I have a separate question about)的调用。例如:

class TracerQuerySet(models.QuerySet):
    ...

class Tracer(MaintainedModel):
    objects = TracerQuerySet().as_manager()
    ...

这些对full_clean的调用 * 看起来 * 只能在默认数据库上工作。至少,我还不知道如何告诉full_clean在我们的验证数据库上操作。
如果当前数据库是默认数据库,我将找到的QuerySet派生类的3或4个调用更改为仅调用full_clean

if self._db == settings.DEFAULT_DB:
    tracer.full_clean()

在此之后,我不再得到FCircget_or_create调用上的异常,并且操作的数据库是验证数据库。
我很好奇是否有其他数据库操作被分配给了默认数据库,所以我在django.db.utils.py: _router_func中的另一段代码中添加了一个debug print:

def _router_func(action):
    def _route_db(self, model,**hints):
        chosen_db = None
        for router in self.routers:
            try:
                method = getattr(router, action)
            except AttributeError:
                # If the router doesn't have a method, skip to the next one.
                pass
            else:
                chosen_db = method(model,**hints)
                if chosen_db:
                    return chosen_db
        instance = hints.get('instance')
        if instance is not None and instance._state.db:
            return instance._state.db

        ###
        ### See what code is getting the default database
        ###
        print(f"Returning default database.  Trace:")
        traceback.print_stack()

        return DEFAULT_DB_ALIAS
    return _route_db

这显示了另外两个地方正在查询默认数据库,而我知道它应该只查询验证数据库。
这一个很有意义,因为它是一个新的数据库查询:

def tracer_labeled_elements(self):
    """
    This method returns a unique list of the labeled elements that exist among the tracers.
    """
    from DataRepo.models.tracer_label import TracerLabel
    return list(
        TracerLabel.objects.filter(tracer__infusates__id=self.id)
        .order_by("element")
        .distinct("element")
        .values_list("element", flat=True)
    )

所以我修改了它,改为使用当前数据库(基于示例):

def tracer_labeled_elements(self):
    """
    This method returns a unique list of the labeled elements that exist among the tracers.
    """
    from DataRepo.models.tracer_label import TracerLabel
    db = self.get_using_db()
    return list(
        TracerLabel.objects.using(db).filter(tracer__infusates__id=self.id)
        .order_by("element")
        .distinct("element")
        .values_list("element", flat=True)
    )

我将这个添加到我的MaintainedModel类中:

def get_using_db(self):
    """
    If an instance method makes an unrelated database query and a specific database is currently in use, this
    method will return that database to be used in the fresh query's `.using()` call.  Otherwise, django's code
    base will set the ModelState to the default database, which may differ from where the current model object came
    from.
    """
    db = settings.DEFAULT_DB
    if hasattr(self, "_state") and hasattr(self._state, "db"):
        db = self._state.db
    return db

这是可行的,但是我不喜欢它要求开发人员在派生类中包含特定于超类的代码。另外,settings.DEFAULT_DB是特定于项目的,所以它创建了一个相互依赖关系。
我真正需要做的是改变django基础代码在没有明确指定数据库时设置的默认数据库...
我敢打赌,这很容易做到。我只是从来没有这样做过。我会开始研究,我可能会修改这个答案很快。

上一个“答案”

好吧,这并不是一个真正的答案,因为我不希望开发人员不得不跳过这些可笑的障碍,在一个继承自MaintainedModel的模型上使用get_or_create,但它解决了这个问题。它防止了异常,并且所有内容都被应用到正确的数据库中。
也许这会给予其他人一个提示,告诉他们如何正确地解决MaintainedModel中__init__构造函数的重写内部的问题:

from django.db.models.base import ModelState
ms = ModelState
setattr(ms, "db", self.db)
print(f"current database: {self.db}\ninfusate from database: {infusate._state.db}\ntracer from database: {tracer._state.db}\nsample from database: {sample._state.db}\n_state type: {type(tracer._state)} db type: {type(tracer._state.db)}")
using_obj = FCirc.objects.using(self.db)
setattr(using_obj, "_state", ms)
print(f"using_obj database: {using_obj._state.db}")
using_obj.get_or_create(
    serum_sample=sample,
    tracer=tracer,
    element=label.element,
)

输出为:

current database: validation
infusate from database: validation
tracer from database: validation
sample from database: validation
_state type: <class 'django.db.models.base.ModelState'> db type: <class 'str'>
using_obj database: validation

相关问题