Friday, October 30, 2020

Re: Get datetime now, not at server initialisation

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,

--
Carles Pina i Estany
https://carles.pina.cat

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/20201030110035.GA22706%40pina.cat.

No comments:

Post a Comment