gpt4 book ai didi

python - 使用组合模式会导致 Django Factory Boy 的无限递归?

转载 作者:太空宇宙 更新时间:2023-11-04 02:21:00 27 4
gpt4 key购买 nike

我有一个模型 Profile,它有一个与 User 一对一的字段:

from django.db import models


class Profile(models.Model):
user = models.OneToOneField(User, related_name='user_profile', on_delete=models.CASCADE)

我正在尝试使用委托(delegate)给 Profile 模型的 __getattr__ 方法更新 User 模型,类似于 http://blog.thedigitalcatonline.com/blog/2014/08/20/python-3-oop-part-3-delegation-composition-and-inheritance/#enter-the-composition :

from django.contrib.auth.models import User


def user__getattr__(self, attr):
return getattr(self.user_profile, attr)


User.add_to_class('__getattr__', user__getattr__)

这似乎按预期工作。例如,在 Profile 模型上定义的字段之一是 timezone,现在我可以访问它了

In [4]: from lucy_web.models import *

In [5]: User.objects.first().timezone
Out[5]: 'America/Los_Angeles'

当我尝试使用 factory_boy 生成用户时出现问题.我有一个 UserFactory 和一个 RelatedFactory 引用 ProfileFactory:

import factory
from lucy_web.models import User
from .profile_factory import ProfileFactory



class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User

user_profile = factory.RelatedFactory(ProfileFactory, 'user')

@classmethod
def _create(cls, model_class, *args, **kwargs):
"""Override the default ``_create`` with create_user."""
manager = cls._get_manager(model_class)
# The default would use ``manager.create(*args, **kwargs)``
return manager.create_user(*args, **kwargs)

ProfileFactory 非常简单,类似于

class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = Profile

问题是,如果我现在尝试使用 UserFactory 创建用户,我会得到一个无限递归错误:

(lucy-web-CVxkrCFK) bash-3.2$ python manage.py shell
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from lucy_web.test_factories import *

In [2]: UserFactory()
(0.034) SELECT t.oid, typarray FROM pg_type t JOIN pg_namespace ns ON typnamespace = ns.oid WHERE typname = 'hstore'; args=None
(0.001) SELECT typarray FROM pg_type WHERE typname = 'citext'; args=None
(0.073) INSERT INTO "auth_user" ("password", "last_login", "is_superuser", "username", "first_name", "last_name", "email", "is_staff", "is_active", "date_joined") VALUES ('pbkdf2_sha256$100000$QuDq3QCL8zp7$Ru1O4K6I/KaMZZXDj2WVY8TV8/7rQNIL9OYL+1hWvTI=', NULL, false, 'ypearson-0@davis.biz', 'Jeffrey', 'Kim', 'ashleypham-0@scott.info', false, true, '2018-07-31T21:43:35.374902+00:00'::timestamptz) RETURNING "auth_user"."id"; args=('pbkdf2_sha256$100000$QuDq3QCL8zp7$Ru1O4K6I/KaMZZXDj2WVY8TV8/7rQNIL9OYL+1hWvTI=', None, False, 'ypearson-0@davis.biz', 'Jeffrey', 'Kim', 'ashleypham-0@scott.info', False, True, datetime.datetime(2018, 7, 31, 21, 43, 35, 374902, tzinfo=<UTC>))
(0.002) SELECT "lucy_web_profile"."id", "lucy_web_profile"."created_at", "lucy_web_profile"."updated_at", "lucy_web_profile"."user_id", "lucy_web_profile"."using_app", "lucy_web_profile"."phone", "lucy_web_profile"."phone_country", "lucy_web_profile"."street", "lucy_web_profile"."street2", "lucy_web_profile"."city", "lucy_web_profile"."state", "lucy_web_profile"."country", "lucy_web_profile"."zip_code", "lucy_web_profile"."timezone", "lucy_web_profile"."phone_type", "lucy_web_profile"."alternate_email", "lucy_web_profile"."activation_code" FROM "lucy_web_profile" WHERE "lucy_web_profile"."user_id" = 2159; args=(2159,)
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
<ipython-input-2-96e87501585e> in <module>()
----> 1 UserFactory()

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/factory/base.py in __call__(cls, **kwargs)
44 return cls.build(**kwargs)
45 elif cls._meta.strategy == enums.CREATE_STRATEGY:
---> 46 return cls.create(**kwargs)
47 elif cls._meta.strategy == enums.STUB_STRATEGY:
48 return cls.stub(**kwargs)

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/factory/base.py in create(cls, **kwargs)
561 def create(cls, **kwargs):
562 """Create an instance of the associated class, with overriden attrs."""
--> 563 return cls._generate(enums.CREATE_STRATEGY, kwargs)
564
565 @classmethod

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/factory/base.py in _generate(cls, strategy, params)
498
499 step = builder.StepBuilder(cls._meta, params, strategy)
--> 500 return step.build()
501
502 @classmethod

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/factory/builder.py in build(self, parent_step, force_sequence)
277 step=step,
278 args=args,
--> 279 kwargs=kwargs,
280 )
281

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/factory/base.py in instantiate(self, step, args, kwargs)
312 return self.factory._build(model, *args, **kwargs)
313 elif step.builder.strategy == enums.CREATE_STRATEGY:
--> 314 return self.factory._create(model, *args, **kwargs)
315 else:
316 assert step.builder.strategy == enums.STUB_STRATEGY

~/Documents/Dev/lucy2/lucy-web/lucy_web/test_factories/user_factory.py in _create(cls, model_class, *args, **kwargs)
48 manager = cls._get_manager(model_class)
49 # The default would use ``manager.create(*args, **kwargs)``
---> 50 return manager.create_user(*args, **kwargs)

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/django/contrib/auth/models.py in create_user(self, username, email, password, **extra_fields)
148 extra_fields.setdefault('is_staff', False)
149 extra_fields.setdefault('is_superuser', False)
--> 150 return self._create_user(username, email, password, **extra_fields)
151
152 def create_superuser(self, username, email, password, **extra_fields):

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/django/contrib/auth/models.py in _create_user(self, username, email, password, **extra_fields)
142 user = self.model(username=username, email=email, **extra_fields)
143 user.set_password(password)
--> 144 user.save(using=self._db)
145 return user
146

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/django/contrib/auth/base_user.py in save(self, *args, **kwargs)
71
72 def save(self, *args, **kwargs):
---> 73 super().save(*args, **kwargs)
74 if self._password is not None:
75 password_validation.password_changed(self._password, self)

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/django/db/models/base.py in save(self, force_insert, force_update, using, update_fields)
727
728 self.save_base(using=using, force_insert=force_insert,
--> 729 force_update=force_update, update_fields=update_fields)
730 save.alters_data = True
731

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/django/db/models/base.py in save_base(self, raw, force_insert, force_update, using, update_fields)
767 post_save.send(
768 sender=origin, instance=self, created=(not updated),
--> 769 update_fields=update_fields, raw=raw, using=using,
770 )
771

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/django/dispatch/dispatcher.py in send(self, sender, **named)
176 return [
177 (receiver, receiver(signal=self, sender=sender, **named))
--> 178 for receiver in self._live_receivers(sender)
179 ]
180

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/django/dispatch/dispatcher.py in <listcomp>(.0)
176 return [
177 (receiver, receiver(signal=self, sender=sender, **named))
--> 178 for receiver in self._live_receivers(sender)
179 ]
180

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/auditlog/receivers.py in log_create(sender, instance, created, **kwargs)
14 """
15 if created:
---> 16 changes = model_instance_diff(None, instance)
17
18 log_entry = LogEntry.objects.log_create(

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/auditlog/diff.py in model_instance_diff(old, new)
133 for field in fields:
134 old_value = get_field_value(old, field)
--> 135 new_value = get_field_value(new, field)
136
137 if old_value != new_value:

~/.local/share/virtualenvs/lucy-web-CVxkrCFK/lib/python3.7/site-packages/auditlog/diff.py in get_field_value(obj, field)
76 else:
77 try:
---> 78 value = smart_text(getattr(obj, field.name, None))
79 except ObjectDoesNotExist:
80 value = field.default if field.default is not NOT_PROVIDED else None

~/Documents/Dev/lucy2/lucy-web/lucy_web/models/user.py in user__getattr__(self, attr)
86 In accordance with PEP 562, we cannot name it '__getattr__' here as that refers to the module __getattr__ method.
87 """
---> 88 return getattr(self.user_profile, attr)
89 # if self.user_profile:
90 # return self.user_profile.__getattribute__(attr)

... last 1 frames repeated, from the frame below ...

~/Documents/Dev/lucy2/lucy-web/lucy_web/models/user.py in user__getattr__(self, attr)
86 In accordance with PEP 562, we cannot name it '__getattr__' here as that refers to the module __getattr__ method.
87 """
---> 88 return getattr(self.user_profile, attr)
89 # if self.user_profile:
90 # return self.user_profile.__getattribute__(attr)

RecursionError: maximum recursion depth exceeded

我有点困惑为什么只有 UserFactory 会导致此错误,而“正常”属性查找似乎工作正常。关于如何解决此问题的任何想法?

最佳答案

这似乎与 factory_boy 无关:对于您的工厂,代码严格等同于:

user = User.objects.create_user()
profile = Profile.objects.create(user=user)

但是,从您的堆栈跟踪来看,您似乎已经安装了一个信号处理程序并将其连接到您的 post_save() 信号(在 auditlog/receivers.py 中)。

该信号处理程序似乎正在计算更新字段的列表;并因此尝试访问 User 对象的某些字段。但是,由于 Profile 尚未创建,因此对自定义 __getattr__ 的调用失败。

解决此问题的正确方法是更改​​您的 user__getattr__ 以在尝试读取它之前正确检查是否定义了 self.user_profile;以下是适用于您的情况的代码示例:

def user__getattr__(user, attr):
try:
profile = user.user_profile
except ObjectDoesNotExist: # Generated by Django if the lookup fails
raise
return getattr(profile, attr)

注意:虽然您也可以使用 factory_boy 助手“静音”信号,但这只会隐藏问题:从数据库的角度来看,不能保证 UserProfile 存在于给定的 User 对象——您的代码应该准备好处理这种情况。

关于python - 使用组合模式会导致 Django Factory Boy 的无限递归?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51622768/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com