Search code examples
djangodjango-storage

Storing Django FileField with leading slash


I have a database used by two different systems and as a result I actually need the FileField value to have a leading slash, like so:

/dirs/filename.ext

In Django, however, FileField values can't have a leading slash since it breaks how they interact with MEDIA_ROOT.

So my suspicion is that I either have to create a custom storage class or somehow customize FileField so that the leading slash is stripped out when read and restored when saved.


In case anyone is wondering why I am doing this: I am mirroring the files on a separate non-Django server.

On the Django server, the files are relative to the media root. So supposing the media root was /path/to/myapp/media, a file with the path dirs/filename.ext would exist at /path/to/myapp/media/dirs/filename.ext.

Meanwhile, when mirrored on the other server, they are stored relative to the webroot. So the path is equivalent to the absolute URL of the file (e.g. the file dirs/filename.ext is stored in /path/to/example.com/dirs/filename.ext and accessed as http://example.com/dirs/filename.ext).

Both servers are using the same database.

I realize that one solution is prepending a slash everywhere the field is used on the other server, but that is across a number of different source files, whereas in Django thanks to the record model I should be able to make a change just in the models.py file and it will work across the entire Django site.


So far I have tried creating a custom version of FileField and it correctly prepends the / on lookup and saving but I cannot get it to remove the leading slash when used within the Django app.


Example

Imagine a record called Tool with a PDF file for its manual. On the Django server, it would be displayed in a template thusly:

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

Meanwhile on the other server it's more like (this is CF code):

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

In the second server example, it needs to be an absolute URL.

So, to be clear:

  • the second server is not a Django project
  • it would be much more time-consuming to change the code on the second server rather than the first
  • therefore the value in the FileField needs to be an absolute URL in order to be compatible with Django, but needs to be saved with a leading slash in order to be compatible with the second server.

Solution

  • Finally figured out how to do this. The trick was also subclassing FieldFile in addition to FileField:

    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
    

    This appears to work.