Hi Clive,
On Oct/28/2020, Clive Bruton wrote:
> Thanks, that was very helpful. I had another pointer on this and found out
> that the easiest way to do this is just to change the ```file``` line in the
> class to:
>
> ```
> file = ImageField(_('image'), upload_to='images/items/%Y/%m/%d/%H/%M/%S')
> ````
I haven't used this method. I always pass a callable (passing a
function).
(I'm deleting a part of your message...)
> So, given that ```upload_path``` returns a string, I don't understand why I
> also have to concatenate the ```filename```:
>
> ```
> def upload_path(instance, filename):
> dtnow = datetime.now(timezone.utc)
> dtstr = '{:%Y/%m/%d}'.format(dtnow)
> dtstr = 'images/items/' + dtstr + '/' + filename
> return dtstr
> ```
> In this case, if I do not concatenate ```filename``` then the uploaded file
> gets named with the last element of the date formatting and without a file
> extension, ie potentially like this:
>
> ```images/items/2020/10/28/20/59/55```
>
> rather than:
>
> ```images/items/2020/10/28/20/59/55/image.jpg```
I see...
> The other thing I don't understand is how ```instance``` and ```filename```
> are passed to the function ```upload_path```. I would expect to do something
> like:
>
> ``` file = ImageField(_('image'), upload_to=upload_path(instance,
> filename))```
>
> But if I do that I get:
>
> ```NameError: name 'instance' is not defined```
>
> Sorry for so many more questions!
I see...
I'll explain two things that put together might help you.
What Django is doing (for now believe me, but keep reading to see it
yourself :-) ) is similar to this:
------------
def print_name(name):
print(f'The name is {name}')
def print_short_name(name):
print(f'Short name: {name[0:2]}')
def call_with_upper_parameter(function_to_call, param):
param = param.upper()
function_to_call(param)
for name in ['Clive', 'Jannett']:
call_with_upper_parameter(print_name, name)
call_with_upper_parameter(print_short_name, name)
------------
The output is:
The name is CLIVE
Short name: CL
The name is JANNETT
Short name: JA
Note that "call_with_upper_parameter" gets two parameters: a callable
and a parameter. It converts the parameter to upper case and
then calls the function_to_call with this parameter.
If you would do "print(function_to_call)" you would see "<function ... at
0x......>"
There is the Python function "callable" to know if a variable can be
called (so if it can be executed: pass parameter -or not- and with () )
Or in another way:
------------------
In [1]: def f():
...: pass
...:
In [2]: name = 'Clive'
In [3]: callable(f)
Out[3]: True
In [4]: callable(name)
Out[4]: False
In [5]: f()
In [6]: name()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-8afa4fbf817e> in <module>()
----> 1 name()
TypeError: 'str' object is not callable
In [7]:
------------------
In my opinion: it takes a bit of effort the first times to understand
them. After this it stays for you for any language.
And now the Django part:
You are using ImageField. ImageField is a FileField:
https://github.com/django/django/blob/master/django/db/models/fields/files.py#L370
FileField in the init saves the upload_to into self.upload_to:
https://github.com/django/django/blob/966b5b49b6521483f1c90b4499c4c80e80136de3/django/db/models/fields/files.py#L240
Side note: if self.upload_to is a str at some point forces to be relative:
https://github.com/django/django/blob/966b5b49b6521483f1c90b4499c4c80e80136de3/django/db/models/fields/files.py#L265
But here the interesting code:
https://github.com/django/django/blob/966b5b49b6521483f1c90b4499c4c80e80136de3/django/db/models/fields/files.py#L308
In "generate_filename" is doing:
if it's a callable: it calls self.upload_to with the instance and the
filename
If it's not a callable (a str in your case): it executes
datetime.datetime.now().strftime(str(self.upload_to)) : so it's
interpolating the %Y, %m, etc. that you had used and uses it as a base
directory and then adds the filename (posixpath.join(dirname, filename))
Does this explain how it works? I think that your questions might be
answered with the callable code and the Django code.
(I think that you had read the documentation but here it is:
https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.FileField.upload_to
, it doesn't explain how it is implemented internally).
It might help you to use a Python debugger and see the steps as they
happen, variables, etc. (adding a breakpoint in the Django code and
inspect variables).
Let me know if it's not clear or if I explained something that you
didn't ask!
Cheers,