如何在 Django 中使用仿函数而不是函数?

How to use functors instead of functions in django?

我有一个名为 Story 的模型:

from django.db import models
from api import settings
from core.functions import UploadImage
from .settings import STORY_UPLOAD_PATH

get_story_upload_path = UploadImage(STORY_UPLOAD_PATH)


class Story(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    image = models.ImageField(upload_to=get_story_upload_path)
    caption = models.TextField()
    expiration_date = models.DateTimeField()

    import datetime
    import uuid
    import os
    

上传照片的行为对于多个模型是相同的,因此我决定制作一个仿函数来保存上传路径并将其传递给具有共同行为的模型。

仿函数:

import datetime
import uuid
import os


class UploadImage:

    def __init__(self, upload_path):
        self.upload_path = upload_path

    def __call__(self, instance, filename):
        filename = f'{datetime.datetime.now()}_{uuid.uuid4()}_{filename}'
        return os.path.join(self.upload_path, instance.username, filename)

序列化器class:

from rest_framework import serializers
from models import Story, ViewedStory


class StorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Story
        fields = ('id', 'user', 'image', 'caption')
        read_only_fields = ('id', )

当我尝试进行迁移时,出现此错误:

Migrations for 'stories':
  stories/migrations/0001_initial.py
    - Create model Story
    - Create model ViewedStory
Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    main()
  File "manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.8/dist-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.8/dist-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.8/dist-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.8/dist-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.8/dist-packages/django/core/management/base.py", line 89, in wrapped
    res = handle_func(*args, **kwargs)
  File "/usr/local/lib/python3.8/dist-packages/django/core/management/commands/makemigrations.py", line 190, in handle
    self.write_migration_files(changes)
  File "/usr/local/lib/python3.8/dist-packages/django/core/management/commands/makemigrations.py", line 227, in write_migration_files
    migration_string = writer.as_string()
  File "/usr/local/lib/python3.8/dist-packages/django/db/migrations/writer.py", line 141, in as_string
    operation_string, operation_imports = OperationWriter(operation).serialize()
  File "/usr/local/lib/python3.8/dist-packages/django/db/migrations/writer.py", line 99, in serialize
    _write(arg_name, arg_value)
  File "/usr/local/lib/python3.8/dist-packages/django/db/migrations/writer.py", line 51, in _write
    arg_string, arg_imports = MigrationWriter.serialize(item)
  File "/usr/local/lib/python3.8/dist-packages/django/db/migrations/writer.py", line 271, in serialize
    return serializer_factory(value).serialize()
  File "/usr/local/lib/python3.8/dist-packages/django/db/migrations/serializer.py", line 39, in serialize
    item_string, item_imports = serializer_factory(item).serialize()
  File "/usr/local/lib/python3.8/dist-packages/django/db/migrations/serializer.py", line 201, in serialize
    return self.serialize_deconstructed(path, args, kwargs)
  File "/usr/local/lib/python3.8/dist-packages/django/db/migrations/serializer.py", line 88, in serialize_deconstructed
    arg_string, arg_imports = serializer_factory(arg).serialize()
  File "/usr/local/lib/python3.8/dist-packages/django/db/migrations/serializer.py", line 353, in serializer_factory
    raise ValueError(
ValueError: Cannot serialize: <core.functions.UploadImage object at 0x7f369fbb27f0>
There are some values Django cannot serialize into migration files.
For more, see https://docs.djangoproject.com/en/3.2/topics/migrations/#migration-serializing

为什么会发生这种情况,我该如何解决?

根据 documentation:

Migrations are Python files containing the old definitions of your models - thus, to write them, Django must take the current state of your models and serialize them out into a file.

这意味着 Django 需要序列化模型中使用的所有东西。由于get_story_upload_pathUploadImage的一个实例,你自己弄的一个classDjango显然不知道如何序列化它。

Add a deconstruct() method的文档中也提到了解决方法。如果 class 的构造函数的所有参数本身都是可序列化的,我们可以简单地使用 deconstructible 装饰器。此外,我们还需要添加一个 __eq__ 方法,以便迁移系统可以知道何时需要进行新的迁移。因此,您可以按如下方式更改 class UploadImage 以便它可以序列化:

from django.utils.deconstruct import deconstructible

@deconstructible
class UploadImage:
    
    def __init__(self, upload_path):
        self.upload_path = upload_path
    
    def __call__(self, instance, filename):
        filename = f'{datetime.datetime.now()}_{uuid.uuid4()}_{filename}'
        return os.path.join(self.upload_path, instance.username, filename)
    
    def __eq__(self, other):
        return isinstance(other, self.__class__) and self.upload_path == other.upload_path