使用前导斜杠存储Django FileField

时间:2013-03-28 21:21:17

标签: django django-storage

我有一个由两个不同系统使用的数据库,因此我实际上需要FileField值有一个前导斜杠,如下所示:

/dirs/filename.ext

然而,在Django中,FileField值不能具有前导斜杠,因为它会破坏它们与MEDIA_ROOT的交互方式。

所以我怀疑我要么必须创建自定义存储类,要么以某种方式自定义FileField,以便在读取时删除前导斜杠并在保存时恢复。


如果有人想知道我为什么这样做:我在一个单独的 -Django 服务器上镜像文件。

在Django服务器上,文件是相对于媒体根目录的。因此,假设媒体根目录为/path/to/myapp/mediadirs/filename.ext处将存在路径为/path/to/myapp/media/dirs/filename.ext的文件。

同时,当镜像在另一台服务器上时,它们相对于webroot存储。因此,路径等同于文件的绝对网址(例如,文件dirs/filename.ext存储在/path/to/example.com/dirs/filename.ext中,并作为http://example.com/dirs/filename.ext访问。)

两台服务器都使用相同的数据库。

我意识到一个解决方案是在其他服务器上使用该字段的前提下放置一个斜杠,但这是在多个不同的源文件中,而在Django中,由于记录模型,我应该能够在models.py文件中进行更改,它将在整个Django站点上运行。


到目前为止,我已尝试创建FileField的自定义版本,并且正确地在/上查找并保存,但我无法将其添加到 remove < / em>在Django应用程序中使用时的前导斜杠。


实施例

想象一下名为Tool的记录,其手册包含PDF文件。在Django服务器上,它将在模板中显示:

<h1>{{ tool.name }}</h1>
<p>{{ tool.description }}</p>
<p><a href="{{ MEDIA_URL }}{{ tool.file.url }}">Link to Manual</a></p>

同时在另一台服务器上,它更像是(这是CF代码):

<h1>#GetTool.tool_name#</h1>
<p>#GetTool.tool_description#</p>
<p><a href="#GetTool.tool_file#">Link to Manual</a></p>

在第二个服务器示例中,它必须是绝对URL。

所以,要明确:

  • 第二台服务器不是Django项目
  • 更改第二台服务器上的代码而非第一台
  • 的代码会更耗时
  • 因此,FileField中的值必须是绝对URL才能与Django兼容,但需要使用前导斜杠保存,以便与第二个服务器兼容。

4 个答案:

答案 0 :(得分:0)

您可以尝试创建自定义字段。就像是:

class MyFileField(models.Field):
    attr_class = MyFieldFile

    def get_directory_name(self):
        return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))

    def get_filename(self, filename):
        return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename)))

    def generate_filename(self, instance, filename):
        return os.path.join(self.get_directory_name(), self.get_filename(filename))

    def get_prep_lookup(self, lookup_type, value):
        if hasattr(value, 'name'):
            value = value.name
        return super(FileField, self).get_prep_lookup(lookup_type, value)

    def get_prep_value(self, value):
        "Returns field's value prepared for saving into a database."
        # Need to convert File objects provided via a form to unicode for database insertion
        if value is None:
            return None
        return unicode(value)

这是默认的django-1.4代码。你应该修改它以在需要的地方添加斜杠

您还应该继承django.db.models.fields.files.FieldFile(您可以在示例中看到MyFieldFile),因为当您访问字段值时,您实际上会获得此类。并且这个类有保存修改路径的保存方法。所以删除该类中的斜杠。

这两个classws的方法调用存储类的方法,所以另一种选择是创建替代存储

from django.core.files.storage import FileSystemStorage

class MyStorage(FileSystemStorage):
    def path(self, name):
        try:
            path = safe_join(self.location, name)
        except ValueError:
            raise SuspiciousOperation("Attempted access to '%s' denied." % name)
        return os.path.normpath(path)

    def _save(self, name, content):
        full_path = self.path(name)
        # many lines of code
        return name  # this is used as filename in database

在django设置中配置默认​​存储:

DEFAULT_FILE_STORAGE = 'yourapp.import.path.MyStorage'

答案 1 :(得分:0)

如果我理解你,为什么你不能只做一个方法来追加或删除字段的斜杠而不是做一个特殊的字段?

这有点hackish,但我想它确实有效。

class MyModelWithAppendingSlash(models.Model):
    my_file = FileField(options_go_here)

    def my_file_appended(self):
        return path = '/' + self.my_file.filename

    def my_file_slash_removed(self):
        return my_file.filename[1:]

然后,当您在django app中引用该模型时,请执行myModelWithAppendingSlashInstance.my_file_appended().my_file_slash_removed()

我剪掉了很多代码,你必须修改它,但你得到了我想要在这里实现的目标的要点。这对你来说可能是完全错误的,但这就是我解释你的问题的方式。

答案 2 :(得分:0)

如果没有一行代码,您可以使用数据库中的VIEW来解决此问题。只需创建一个连接'/'(或'http://example.com/',如果您希望网址真的是绝对的)和路径的视图。

CREATE VIEW CF_appname_tool AS
SELECT [other attributes], '/' + file as file
FROM appname_tool;

我需要知道你use SQL server,因为字符串连接运算符因DBMS而不同。

我怀疑更改CF代码以从CF_appname_tool中选择太麻烦了。您可以将数据从appname_tool移至some_other_name并在Django Tool模型上设置db_table选项:

class Tool(model.Model):
    class Meta:
        db_table = 'some_other_name'

并创建appname_tool VIEW作为some_other_name的选择:

CREATE VIEW appname_tool AS
SELECT [other attributes], '/' + file as file
FROM some_other_name;

如果要在制作中以编程方式创建VIEW,可以收听post_syncdb信号。

答案 3 :(得分:0)

最后想出了如何做到这一点。除了FileField之外,诀窍还是继承FieldFile:

class LeadingSlashFieldFile(files.FieldFile):
    def __init__(self, instance, field, name):
        name = re.sub(r'^/', '', name)
        super(LeadingSlashFieldFile, self).__init__(instance, field, name)

class LeadingSlashFileField(models.FileField):
    attr_class = LeadingSlashFieldFile

    def get_prep_lookup(self, lookup_type, value):
        if hasattr(value, 'name'):
            value = value.name
        if value[0] <> '/':
            value = "/" + value
        return super(LeadingSlashFileField, self).get_prep_lookup(lookup_type, value)

    def get_prep_value(self, value):
        value = super(LeadingSlashFileField, self).get_prep_value(value)
        if value is None:
            return None
        value = unicode(value)
        if value[0] <> '/':
            value = "/" + value
        return value

这似乎有效。