Apologies for the late reply - I was at a conference all weekend, so I'm still catching up on mail.
On Thu, Jun 24, 2010 at 12:24 AM, Waldemar Kornewald <wkornew...@gmail.com> wrote: > On Wed, Jun 23, 2010 at 2:58 AM, Russell Keith-Magee > <russ...@keith-magee.com> wrote: >> On Wed, Jun 23, 2010 at 2:58 AM, Waldemar Kornewald >> <wkornew...@gmail.com> wrote: >>> On Tue, Jun 22, 2010 at 2:40 PM, Russell Keith-Magee >>> <russ...@keith-magee.com> wrote: >>>> On Tue, Jun 22, 2010 at 2:55 PM, Waldemar Kornewald >>>> <wkornew...@gmail.com> wrote: >>>> It also strikes me that a lot of this is being configured at the >>>> global level -- i.e., you have to nominate that your public upload >>>> backend will be S3, rather than nominating that a specific file field >>>> will be backed by S3. >>> >>> I'm repeating myself here, but anyway: The primary purpose of this API >>> is to allow for writing reusable Django apps that automatically work >>> with your project's file handling solution(s) without hard-coding >>> anything. This requires that you specify the upload/download backends >>> separately from FileField (otherwise it's hard-coded and not reusable) >>> and instead provide a mechanism for detecting which backend should be >>> chosen for the current request. Of course, this mechanism could take >>> the FileField into account. >> >> You may feel that you're repeating yourself, but this point certainly >> wasn't clear to me from your previous two posts. >> >> Making it possible to configure the file handling strategy of a >> reusable app is certainly a reasonable feature request. However: >> >> 1) Again, the file storage strategy isn't something that is constant >> across a deployment. I may want to use S3 for one type of file, but >> use local storage for another. >> >> 2) Handling this flexibility at the level of the request is >> completely the wrong approach. File storage doesn't change per request >> -- it's defined per model. Every usage of the Profile model has an >> 'avatar' ImageField; it doesn't matter how or where you access that >> field, it needs to be accessed and displayed the same way. This is a >> per-model setting, not a per-view or per-request setting. > > That's ok, but in addition to the model and field I'd still pass the > request object to the backend, so you can check if the user hasn't > used up his quote or maybe other things. You're missing my point. You aren't guaranteed to have a request when you're using the field. Consider the case of a standalone data processing script. >> There is a larger issue here of how we should treat the problem of >> configuring the internals of reusable applications. It's analogous to >> the reasons why we dropped the 'using' argument from Meta declarations >> on models -- it isn't a decision that a reusable-app-maker can make; >> it needs to be configured as a deployment decision. >> >> The solution for 'using' was to introduce the idea of a database >> Router; perhaps an analogous approach is required here. > > The "delegate" backend is basically a router. With the > django-filetransfers API you can just write another backend which does > the routing. They're not quite the same. Sure, the Delegate interface defines a way to configure behaviour, but it doesn't provide an interface to decide which behavior is appropriate at any given time. Again, this comes back to the concept that different reusable apps may have different file storage requirements, and you need to be able to define the strategy for those requirements at a project level. Sure, the simple case of "put everything on S3" needs to be simple, but you also need to be able to say "put all the Profile.avatar images on S3, but all the Document.upload files on a AppEngine filestore". You also need to take into account that a file field may not necessarily be backed by a model at all. It could just be an upload. Form.FileField doesn't require that it is backed by a model.FileField. >>> Don't worry about the current code. We haven't yet officially >>> announced that project and I'll hold it back until it's clear whether >>> this will be part of Django core or not. We can completely reinvent >>> the API from scratch if necessary. >> >> In which, case, you need to make a specific proposal. I'll admit that >> this is a problem that needs to be addressed, and I'm interested in >> seeing approaches that addressing this problem, I can't say it's a >> particularly high priority for me. I'm happy to give you feedback on a >> specific proposal, but I have many much higher priority items on my >> plate for 1.3. > > OK, so before I send a patch, here is how I'd like to do it: > > From the end-user perspective > ----------------------------------------------- > > FileField gets a new method prepare_upload() which takes the following > arguments: > * request Again - this isn't always available. > * upload_url: the target URL of the upload view > * private: should this be only privately accessibly or also publicly? > (default: False; whether this actually works depends on the chosen > backend's capabilities and your hosting setup) As Robert and Luke both point out, public/private is neither a necessary nor sufficient basis on which to make this decision. > The function returns a tuple with a new target submission URL and a > dict with additional POST data. > > forms.Form also gets a prepare_upload() function which searches for a > FileField. If multiple FileFields exist and the backend doesn't > support multi-file uploads an exception is raised. Ok - no problems with this bit. I have no problems with the idea that if you've configured your file backend to use a particular strategy on your form, and your form won't support that strategy, an exception will need to be raised. > The function > doesn't return anything, but instead stores the results as > self.upload_url and self.some_file_field.post_data. When the form is > rendered the FileField automatically generates <input type="hidden" /> > fields for self.post_data. Q: or should prepare_upload() better be > defined on forms.FileField? This sounds like a better place for it. > File (from FileField) gets a public_download_url() method which takes > no arguments. It returns the file's permanent public URL or None if no > such URL exists. Sorry - are you saying this is on the FileField, or on the File object? If it's on the FileField, how do you get the download URL when there isn't a model backing the form? And if it's on the File object, what's wrong with the url() method that is already there? > File (from FileField) gets a serve() method which takes the following > arguments: > * request > * save_as: if False (default) lets the browser decide whether to > display or download the file; if True forces browser to download as > "file.name"; if it's a string it forces a download with the given > string as the file name > * content_type: if None (default) automatically detects content type > based on file name; otherwise it overrides the default content type > This returns an HttpResponse. > > Example: > > Upload view: > form = FileForm() > form.prepare_upload(request, '/upload') I'm not wile that the prepare_upload step is required. It feels like something that should be handled internally, based on the configuration of handler for the field. > Upload template: > <form action="{{ form.upload_url }}" ...> > {% csrf_token %} > <table>{{ form }}</table> > </form> I'm not sure I follow what happends with the non-form data here. From what I can make out, upload_url won't be able to handle any non-file data. I'm also not sure what happens for the default case (i.e., the case the defines the current file upload behaviour), where multiple files can be uploaded to the normal form handling API. > Download template for private-only downloads (entity is a model instance): > <a href="{% url download_view pk=entity.pk %}">Download</a> What determines the view name "download_view"? > Download template for public downloads (entity is a model instance > with a FileField called "file"): > {% url download_view pk=entity.pk as fallback_url %} > <a href="{% firstof entity.file.public_download_url fallback_url > %}">Download</a> > > Download view for public and private downloads (public only as a > fall-back if there is no public_download_url): > entity.file.serve(request) > > The prepare_upload(), public_download_url(), and serve() functions > each have their own backend type. Each of the three backend types is > configured separately in settings.py: > PREPARE_UPLOAD_BACKEND = { > 'backend': 'some.backend', > 'setting': ..., > 'setting2': ..., > } > PUBLIC_DOWNLOAD_URL_BACKEND = { > 'backend': 'some.backend', > ... > } > SERVE_FILE_BACKEND = { > 'backend': 'some.backend', > ... > } > > Backend API > -------------------------------------------------------- > I won't, yet talk too much about the backend API itself because we > should first talk about the end-user API. Each backend type gets all > of the parameters that the user passes to the respective public > function (prepare_upload(), etc.). Additionally, each function gets > the model and FileField. Also, the File methods (public_download_url() > and serve()) get the file instance: > def prepare_upload(model, field, request, upload_url, private, **kwargs): > ... > > def public_download_url(model, field, file, **kwargs): > ... > > def serve_file(model, field, file, request, save_as, content_type, **kwargs): > ... > > There is no special Router API. Instead, you can write your own > backend which routes/delegates to some other backend. Again, there will be a need for *some* sort of routing API, because this isn't a decision that can be configured on a project-wide basis with a single setting (or a pair of settings on an arbitrary public/private distinction). Yours, Russ Magee %-) -- You received this message because you are subscribed to the Google Groups "Django developers" group. To post to this group, send email to django-develop...@googlegroups.com. To unsubscribe from this group, send email to django-developers+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.