FilePathField intialized with an object implementing __str__ works as model field but not the corresponding form field

118 views
Skip to first unread message

jgomo3

unread,
May 15, 2017, 5:02:17 PM5/15/17
to Django users
I set a models.FilePathField with the path attribute as an instance of an object implementing __str__. I works well until I needed to get the corresponding Form Field. It caused the following error:

    TypeError: listdir: path should be string, bytes, os.PathLike, integer or None, not StringSettingsReference

StringSettingsReference is my custom Class I mentioned implements the __str__ method.

listdir is complaining because there is some code trying to do the following:

    for f in sorted(os.listdir(self.path)):

This code is from `django/forms/fields.py` (Django 1.11).

What I want to point out is that Django allowed me to create a model where a FilePathField was initialized with a path attribute with type different from string, bytes, os.PathLike, integer or None; and then, only Form Field related code complains about that (I was using my app for some time and realized about this just because decided to reuse the admin site (yes, my application is not using form so much)).

Django should complain before (not allowing me to create a model like that) or what would be more convenience for me is to not complains about that.

So, I was thinking to fill a bug report, but it is write in the Django documentation that it is a good idea to ask here before.

Does this qualify as a Bug?


Addendum:

Why am I using an special class anyway?

Because I wanted the path to be relative to a setting variable, but writing anything but a string in the model definition is not allowed by the migration serializing mechanism. I mean, I wanted to do the following:

    class MyModel(...).
        .
        .
        .
        a_file_path = models.FilePathField(path=settings.SOMEVARIABLE, ...)

but I can't write SOMEVARIABLE. So I had to create a "deconstructible" class:

    @deconstructible
    class StringSettingsReference:
        def __init__(self, name):
            self.name = name
   
        def __str__(self):
            return getattr(settings, self.name)

and use it like:

    a_file_path = models.FilePathField(path=StringSettingsReference('SOMEVARIABLE'), ...)

James Schneider

unread,
May 16, 2017, 5:41:20 AM5/16/17
to django...@googlegroups.com


On May 15, 2017 2:02 PM, "jgomo3" <jgo...@gmail.com> wrote:
I set a models.FilePathField with the path attribute as an instance of an object implementing __str__. I works well until I needed to get the corresponding Form Field. It caused the following error:

    TypeError: listdir: path should be string, bytes, os.PathLike, integer or None, not StringSettingsReference

StringSettingsReference is my custom Class I mentioned implements the __str__ method.

listdir is complaining because there is some code trying to do the following:

    for f in sorted(os.listdir(self.path)):

This code is from `django/forms/fields.py` (Django 1.11).

What I want to point out is that Django allowed me to create a model where a FilePathField was initialized with a path attribute with type different from string, bytes, os.PathLike, integer or None; and then, only Form Field related code complains about that (I was using my app for some time and realized about this just because decided to reuse the admin site (yes, my application is not using form so much)).

Django should complain before (not allowing me to create a model like that) or what would be more convenience for me is to not complains about that.

Working as expected. In general, validation occurs at the form level, not at the model level. Any input from a user is generally passed through a form or other process to validate the input before it is attached to the model. 

It sounds like you are assigning a custom object to the model field manually, which Django does not know how to handle later down the road. If there is a for loop involved, I don't believe the __str__ method is invoked, hence the reason you are receiving the exception.



So, I was thinking to fill a bug report, but it is write in the Django documentation that it is a good idea to ask here before.

Does this qualify as a Bug?

I wouldn't think so. You're treading outside of the expected work flow.



Addendum:

Why am I using an special class anyway?

Because I wanted the path to be relative to a setting variable, but writing anything but a string in the model definition is not allowed by the migration serializing mechanism. I mean, 

Right, the reason being that the value stored in that field will end up in a static database field upon a save() call. 

TBH, you may want to rethink your approach to this problem. If you are generating paths relative to a variable, then simple concatenate the given path and variable accordingly. Keep in mind that the generated value is what is being stored, so any illusion of a dynamic path would be lost.

If it were me, I'd investigate only keeping the short path (to keep the original value as long as possible), and adding the variable prefix after the object is retrieved later, but that may not be possible. It would make it easy to switch all of your paths to another section of the file system by changing a single setting. Otherwise you'll be left with modifying the existing values in the DB in the event of a location change, which can get messy quickly.

-James

jgomo3

unread,
May 16, 2017, 7:26:51 PM5/16/17
to Django users

El martes, 16 de mayo de 2017, 5:41:20 (UTC-4), James Schneider escribió:

James. Thank you a lot for your guidance ...
 
TBH, you may want to rethink your approach to this problem. If you are generating paths relative to a variable, then simple concatenate the given path and variable accordingly. Keep in mind that the generated value is what is being stored, so any illusion of a dynamic path would be lost.


I want that. But if you do that, the migration being produced will:

 * Or Be not possible to create, because the migration mechanism will not be able to serialize an expression as simple as `settings.A_SETTING`.
 * Or Will have the value of your development environment
 
If it were me, I'd investigate only keeping the short path (to keep the original value as long as possible), and adding the variable prefix after the object is retrieved later, but that may not be possible. It would make it easy to switch all of your paths to another section of the file system by changing a single setting. Otherwise you'll be left with modifying the existing values in the DB in the event of a location change, which can get messy quickly.


Precisely this is the reason of why I'm in such a mess.
My objective is to "...make it easy to switch all of your paths to another section of the file system by changing a single setting".
That is why I tried to simply assign a setting variable to the path argument of a FilePathField constructor. But due the migration problem, is not as simple as it should be.
It is not so much about the prefixing. In fact, I must correct my words: I'm not trying to prefix anything as you can see in my code example: I just want to assign a settings variable as the value of the path argument of the FilePathField constructor. In other words,
...
I want to write:

models.FilePathField(path=settings.A_SETTING)

But I can't because migration don't understand that. So I had to write:

models.FilePathField(path=StringSettingsReference('A_SETTING'))

where StringSettingsReference is my custom class.

I got inspiration for this solution from:

* http://jakzaprogramowac.pl/pytanie/20808,django-migrations-and-customizable-reusable-apps
* https://docs.djangoproject.com/en/1.11/topics/migrations/#adding-a-deconstruct-method

Thank you a lot for your help.
Reply all
Reply to author
Forward
0 new messages