【问题标题】:Why does len() execute __set__ of my class?为什么 len() 执行我班级的 __set__ ?
【发布时间】:2014-07-26 22:25:09
【问题描述】:

我在 Django 中实现了custom model field。它是一个图像字段,除了直接分配文件外,还允许分配 URL 字符串以从中加载图像。

import uuid
import urllib.request

from django.core.files.base import ContentFile
from django.db import models
from django.db.models.fields.files import ImageFileDescriptor


class UrlImageFileDescriptor(ImageFileDescriptor):
    def __set__(self, instance, value):
        # If a string is used for assignment, it is used as URL
        # to fetch an image from and store it on the server.
        if isinstance(value, str):
            try:
                response = urllib.request.urlopen(value)
                image = response.read()
                name = str(uuid.uuid4()) + '.png'
                value = ContentFile(image, name)
            except:
                print('Error fetching', value)
                pass
        super().__set__(instance, value)


class UrlImageField(models.ImageField):
    descriptor_class = UrlImageFileDescriptor

一般来说,该字段有效。但出于某种原因,Django 本身在内部为其分配了字符串值。每次使用该字段的模型查询集被过滤时,都会使用字符串调用 __set__,以便在 except 子句中的打印语句触发 Error fetching upload/to/50e170bf-61b6-4670-90d1-0369a8f9bdb4.png

我可以缩小从 Django 1.7c1 到 django/db/models/query.py 的调用范围。

def get(self, *args, **kwargs):
    """
    Performs the query and returns a single object matching the given
    keyword arguments.
    """
    clone = self.filter(*args, **kwargs)
    if self.query.can_filter():
        clone = clone.order_by()
    clone = clone[:MAX_GET_RESULTS + 1]
    num = len(clone) # This line causes setting of my field
    if num == 1:
        return clone._result_cache[0]
    # ...

为什么导致我的字段的__set__ 被执行的行?我可以验证输入值是一个有效的 URL 来解决这个问题,但我想先知道原因。

【问题讨论】:

  • 您能否在错误发生的点(即在您的except 子句中)显示完整的回溯?
  • @BrenBarn 这是link to the full traceback

标签: python django python-3.x descriptor magic-function


【解决方案1】:

故事在您的回溯中。要获取查询的长度:

File "C:\repository\virtualenv\lib\site-packages\django\db\models\query.py" in get
  350.         num = len(clone)

它将所有查询结果提取到一个列表中:

File "C:\repository\virtualenv\lib\site-packages\django\db\models\query.py" in __len__
  122.         self._fetch_all()
File "C:\repository\virtualenv\lib\site-packages\django\db\models\query.py" in _fetch_all
  966.             self._result_cache = list(self.iterator())

对于每个查询结果,它使用数据库中的数据创建一个模型对象:

File "C:\repository\virtualenv\lib\site-packages\django\db\models\query.py" in iterator
  275.                     obj = model(*row_data)

要创建模型对象,它会设置模型的每个字段:

File "C:\repository\virtualenv\lib\site-packages\django\db\models\base.py" in __init__
  383.                 setattr(self, field.attname, val)

最终在您的自定义模型字段上调用 ​​__set__

File "C:\repository\invoicepad\apps\customer\fields.py" in __set__
  18.                           response = urllib.request.urlopen(value)

这背后的更大规模的原因很难说更多,因为我对 Django 了解不多,也因为我不知道你的 db 结构是什么样的。但是,基本上它看起来无论数据库字段填充您的UriImageField,其中的数据对于您实现描述符的方式实际上都是无效的。 (例如,根据您的错误判断,数据库有'upload/to/50e170bf-61b6-4670-90d1-0369a8f9bdb4.png',但实际上没有这样的文件。)

【讨论】:

  • 谢谢!路径是 Django 为文件字段存储在数据库中的内容。我不知道 Django 首先将纯字符串分配给文件字段。该文件存在于该路径中,但它相对于同一服务器上的媒体根目录,因此通过 URL 获取失败。
猜你喜欢
  • 1970-01-01
  • 2013-09-17
  • 1970-01-01
  • 1970-01-01
  • 2017-06-12
  • 2021-06-27
  • 2016-02-20
  • 2016-06-03
  • 2016-05-08
相关资源
最近更新 更多