Hi,

thanks for taking time to provide such a detailed answer.

> You can and should split up your code into separate functions however
> you like. Form validation can be done in a separate function that
> returns a dictionary that is used to update the context of the calling
> view, for example. Your alternate solution sounds a bit complex for
> working around the things you've described, but c'est la vie...

While it may sound complex, it allows me to write the actual views and 
handlers really simply.

For example, I have this one-statement view dispatcher:

@access_restricted
def brochure_page(request, brochure_id, page):
    return item_or_404({
        "property_address"  : show_form_property,
        "property_building" : show_form_property,
        "documents"         : show_form_brochure,
        "pdf"               : show_form_pdf,
        ...
    }, page)(
        request,
        enforce_editable(get_object_or_404(models.brochure, pk=brochure_id),
            request.user),
        page,
    )

and this one-statement function powers several pages at once:

def show_form_property(request, brochure, page):
    return response_from_main(brochure, page, request,
        form=forms.by_page(page)(data=attempted_post(request),
            instance=brochure.property))

this one, on the contrary, shows two forms on one page:

def show_form_pdf(request, brochure, page):
    return response_from_main(brochure, page, request,
        form_brochure_xslfo=forms.brochure_xslfo(data=attempted_post(request),
            instance=brochure),
        form_pdf_map_new=forms.brochure_pdf_map(data=attempted_post(request)),
    )


and this one-statement handler processes submissions generated by the former:

@post_handler(action="update_property", next=redirect_to_console_page())
@access_restricted
@autoload_require_editable(models.property, pk="property_id")
def do_update_property(request, property):
    enforce_valid(forms.by_page(request.POST["page"])(request.POST,
        instance=property)).save()

While this handler uses request.POST["page"] to know where to redirect, the 
following one creates an object and returns a URL to it explicitly:

@post_handler(action="create_photo")
@access_restricted
@autoload_require_editable(models.feature, pk="feature_id")
def do_create_photo(request, feature, property):
    enforce_valid(forms.photo(request.POST, request.FILES,
        instance=models.photo(feature=feature))).save()
    return url_from_view(feature_edit, brochure_id=
        request.POST["brochure_id"], feature_id=feature.pk)

So my views contain not a single statement that would be related to form 
_processing_, and handlers are likewise agnostic of views.

> I don't really understand all the stuff about ignoring the URL in your
> solution

The idea is that every form does its POST to its own URL, so the POST contains 
two pointers: one, contained in the “submission” parameter, tells the 
application what to do with the data submitted, and the URL tells it what to 
do in case validation fails. If it succeeds, the target of the redirect is 
deduced from POST. So, for example, a form looks like this in HTML:

        <form action="" method="post">
            <input type="hidden" name="submission" value="add_tag" />
            <input type="hidden" name="brochure_id" value="4" />
            <input type="hidden" name="property_id" value="6" />
            <input type="hidden" name="page" value="brochure_tags" />

            <input type="text" name="tag" />

            <input type="submit" />
        </form>

If everything’s OK, the app never looks at the request URI. Only if there’s a 
validation failure, the middleware lets Django find the view that corresponds 
to the URI--which is the same view that caused the POST--and the view will 
display the form(s) again, noting the errors.

I used to have all POSTs directed to a single URI and pass the needed 
parameters in the request body. However, that did not allow for user-friendly 
form validation, so I made all //form/@action empty, but the parameters 
stayed where they had been before. There’s no need for user-friendly URLs 
when doing POST, they aren’t even shown to the user anyway (as long as 
submissions succeed), they are only relevant in the case the POST handler 
can’t do a redirect.

While that’s not too semantic, it preserves shareable/bookmarkable URLs in all 
cases. Every URL is guaranteed to return the same form. For a simple example, 
let there be two pages that have similar forms that allow editing something.


/admin/posts/2009/01/04/hello-world

<form action="???" method="post">
<input type="hidden" name="post_id" value="42" />
Title: <input type="text" name="title" value="Hello world!" />
Text: <textarea>...</textarea>
</form>


/admin/posts/2009/01/

<form action="???" method="post">
<input type="hidden" name="post_id" value="41" />
Title: <input type="text" name="title" value="First post!" />
Text: <textarea>...</textarea>
</form>

<hr />

<form action="???" method="post">
<input type="hidden" name="post_id" value="42" />
Title: <input type="text" name="title" value="Hello world!" />
Text: <textarea>...</textarea>
</form>


What should be in the action field? If there’s the same URL, 
maybe /admin/posts/edit, then a failed form validation would show that URL in 
the address field, hardly helpful. What if a user wants to ask someone for 
help, how can he/she indicate which page is causing problems? But if each 
form has action="", then the URL will be meaningful.

> Since #6094 is about handling exceptions in middleware, and you want
> exception handling in middleware to work as described in #6094, the
> answer to your question is "yes".

OK. Until then, I’ve added this to my middleware, copied from Django source:

        except:
            if django.db.transaction.is_dirty():
                django.db.transaction.rollback()
                django.db.transaction.leave_transaction_management()

            if not settings.DEBUG or settings.DEBUG_PROPAGATE_EXCEPTIONS:
                raise
            return django.views.debug.technical_500_response(request,
                *sys.exc_info())

Hopefully it’s not too dirty a hack?

-- 
TIA
Roman.

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to