20101118¶
File uploads¶
Yesterday I started to learn about how Django and ExtJS handle file uploads.
Turned lino.utils.mixins
into a package lino.mixins
.
Started the new modules
lino.mixins.uploadable
which defines one abstract model Uploadable.
Started lino.modlib.uploads
which defines
a model Upload.
About the Lino modules hierarchy¶
All this is not yet stable-
lino.mixins.printable
might as well move to lino.modlib.printables.
The deciding question is: does a functionality
deserve to get a Django application label?
For ‘printable’ I have the feeling “no”,
for “uploads” I’d rather say “yes”.
For example, the report UploadsByPerson and UploadsByCompany will be used by every Lino application that decides to implement Uploads as a PartnerDocument.
Is it true that Django doesn’t allow models
to override a non-abstract model inherit from it without creating multi-table inheritance ?
to remove fields from their base model?
Exploring Django’s FileFields and FieldFiles¶
Django’s FileField means that a partial filename is stored in the database in a VARCHAR field.
FileField.path returns the complete path (settings.MEDIA_ROOT + partial filename)
FileField.url returns the URL (settings.MEDIA_URL FileField.name).
The constructor has a mandatory parameter upload_to which may be a string (possibly containing date format specifiers) or a callable. This string is used to generate the partial filename.
When upload_to is not a callable, it is used only in get_directory_name:
def get_directory_name(self):
return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
when it is a callable, then it simply replaces get_directory_name.
get_directory_name is used only in FieldFile.generate_filename:
def generate_filename(self, instance, filename):
return os.path.join(self.get_directory_name(), self.get_filename(filename))
which in turn is used only in FieldFile.save()
:
def save(self, name, content, save=True):
name = self.field.generate_filename(self.instance, name)
self.name = self.storage.save(name, content)
When upload_to is a callable, then it simply replaces the generate_filename method.
Uploadable files in Lino¶
Here is how Lino (currently) handles uploaded files.
New attribute lino.reports.Report.handle_uploaded_files()
.
Here is the handle_uploaded_files current code of
lino.mixins.uploadable.Uploadable
(inherited by lino.modlib.uploads.models.Upload
):
def handle_uploaded_files(self,request):
uf = request.FILES['file'] # an UploadedFile instance
self.size = uf.size
self.mimetype = uf.content_type
# Django magics:
self.file = uf.name # assign a string
ff = self.file # get back a FileField instance !
ff.save(uf.name,uf,save=False)
# print "Wrote file ", ff.path
This method is called from
lino.ui.extjs.ext_ui.ExtUI.form2obj_and_save()
after storing the normal form data:
def form2obj_and_save(self,request,rh,data,elem,**kw2save):
# store normal form data
try:
rh.store.form2obj(data,elem)
except exceptions.ValidationError,e:
return error_response(e)
# store uploaded files
if rh.report.handle_uploaded_files is not None:
rh.report.handle_uploaded_files(request,elem)
TODO:
Lino.submit_detail doesn’t send mk and mt when in a slave report.
possibility to show a previously uploaded file. Maybe I need a new widget for FileField: if the field’s current value is empty, it shows an upload button; if it is non-empty, it shows the filename and a link that opens this file. In case of Upload the value is always empty in an insert window and always non-empty in a detail window. But the most flexible would be to not rely on this. Most straightforward would be a TwinTrigger button. Maybe also a “delete uploaded file” button, though this should be diabled if another Upload instance refers to the same file.