15.2.175. camcops_server.cc_modules.cc_view_classes¶
camcops_server/cc_modules/cc_view_classes.py
Copyright (C) 2012, University of Cambridge, Department of Psychiatry. Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
CamCOPS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
Django-style class-based views for Pyramid.
Adapted from Django’s views/generic/base.py
and views/generic/edit.py
.
Django has the following licence:
Copyright (c) Django Software Foundation and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Django nor the names of its contributors may be
used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Custom views typically inherit from CreateView
, DeleteView
or
UpdateView
.
A Pyramid view function with a named route should create a view of the custom
class, passing in the request, and return the results of its dispatch()
method. For example:
@view_config(route_name="edit_server_created_patient")
def edit_server_created_patient(req: Request) -> Response:
return EditServerCreatedPatientView(req).dispatch()
To provide a custom view class to create a new object in the database:
Inherit from
CreateView
.Set the
object_class
property.Set the
form_class
property.Set the
template_name
property or implementget_template_name()
.Override
get_extra_context()
for any extra parameters to pass to the template.Set
success_url
or overrideget_success_url()
to be the redirect on successful creation.Override
get_form_kwargs()
for any extra parameters to pass to the form constructor.For simple views, set the
model_form_dict
property to be a mapping of object properties to form parameters.Override
get_form_values()
with any values additional tomodel_form_dict
to populate the form.Override
save_object()
to do anything more than a simple record save (saving related objects, for example).
To provide a custom view class to delete an object from the database:
Inherit from
DeleteView
.Set the
object_class
property.Set the
form_class
property.Set the
template_name
property or implementget_template_name()
.Override
get_extra_context()
. for any extra parameters to pass to the template.Set
success_url
or overrideget_success_url()
to be the redirect on successful creation.Override
get_form_kwargs()
for any extra parameters to pass to the form constructor.Set the
pk_param
property to be the name of the parameter in the request that holds the unique/primary key of the object to be deleted.Set the
server_pk_name
property to be the name of the property on the object that is the unique/primary key.Override
get_object()
if the object cannot be retrieved with the above.Override
delete()
to do anything more than a simple record delete; for example, to delete dependant objects
To provide a custom view class to update an object in the database:
Inherit from
UpdateView
.Set the
object_class
property.Set the
form_class
property.Set the
template_name
property or implementget_template_name()
.Override
get_extra_context()
for any extra parameters to pass to the template.Set
success_url
or overrideget_success_url()
to be the redirect on successful creation.Override
get_form_kwargs()
for any extra parameters to pass to the form constructor.Set the
pk_param
property to be the name of the parameter in the request that holds the unique/primary key of the object to be updated.Set the
server_pk_name
property to be the name of the property on the object that is the unique/primary key.Override
get_object()
if the object cannot be retrieved with the above.For simple views, set the
model_form_dict
property to be a mapping of object properties to form parameters.Override
save_object()
to do anything more than a simple record save (saving related objects, for example).
You can use mixins for settings common to multiple views.
Note
When we move to Python 3.8, there is typing.Protocol
, which allows
mixins to be type-checked properly. Currently we suppress warnings.
Some examples are in webview.py
.
- class camcops_server.cc_modules.cc_view_classes.BaseCreateView(request: CamcopsRequest)[source]¶
Base view for creating a new object instance.
Using this base class requires subclassing to provide a response mixin.
- post() Any [source]¶
Handle POST requests:
if the user has cancelled, redirect to the cancellation URL;
instantiate a form instance with the passed POST variables and then check if it’s valid;
if it’s invalid, call
form_invalid()
, which typically renders the form to show the errors and allow resubmission;if it’s valid, call
form_valid()
, which in the default handlerprocesses data via
form_valid_process_data()
, andreturns a response (either another form or redirection to another URL) via
form_valid_response()
.
- class camcops_server.cc_modules.cc_view_classes.BaseDeleteView(request: CamcopsRequest)[source]¶
Base view for deleting an object.
Using this base class requires subclassing to provide a response mixin.
- form_valid_process_data(form: Form, appstruct: Dict[str, Any]) None [source]¶
Called when the form is valid. Deletes the associated model.
- class camcops_server.cc_modules.cc_view_classes.BaseFormView(request: CamcopsRequest)[source]¶
A base view for displaying a form.
- class camcops_server.cc_modules.cc_view_classes.BaseUpdateView(request: CamcopsRequest)[source]¶
Base view for updating an existing object.
Using this base class requires subclassing to provide a response mixin.
- post() Any [source]¶
Handle POST requests:
if the user has cancelled, redirect to the cancellation URL;
instantiate a form instance with the passed POST variables and then check if it’s valid;
if it’s invalid, call
form_invalid()
, which typically renders the form to show the errors and allow resubmission;if it’s valid, call
form_valid()
, which in the default handlerprocesses data via
form_valid_process_data()
, andreturns a response (either another form or redirection to another URL) via
form_valid_response()
.
- class camcops_server.cc_modules.cc_view_classes.ContextMixin[source]¶
A default context mixin that passes the keyword arguments received by get_context_data() as the template context.
- get_context_data(**kwargs: Any) Dict[str, Any] [source]¶
Provides context for a template, including the
view
argument and any additional context provided byget_extra_context()
.
- get_extra_context() Dict[str, Any] [source]¶
Override to provide extra context, merged in by
get_context_data()
.
- class camcops_server.cc_modules.cc_view_classes.CreateView(request: CamcopsRequest)[source]¶
View for creating a new object, with a response rendered by a template.
- class camcops_server.cc_modules.cc_view_classes.DeleteView(request: CamcopsRequest)[source]¶
View for deleting an object retrieved with self.get_object(), with a response rendered by a template.
- class camcops_server.cc_modules.cc_view_classes.FormMixin[source]¶
Provide a way to show and handle a single form in a request.
- form_invalid(validation_error: deform.exception.ValidationFailure) pyramid.response.Response [source]¶
Called when the form is submitted via POST and is invalid. Returns a response with a rendering of the invalid form.
- form_valid_response(form: Form, appstruct: Dict[str, Any]) pyramid.response.Response [source]¶
Called when the form is submitted via POST and is valid. Redirects to the supplied “success” URL.
- get_context_data(**kwargs: Any) Dict[str, Any] [source]¶
Insert the rendered form (as HTML) into the context dict.
- class camcops_server.cc_modules.cc_view_classes.FormView(request: CamcopsRequest)[source]¶
A view for displaying a form and rendering a template response.
- class camcops_server.cc_modules.cc_view_classes.FormWizardMixin(*args, **kwargs)[source]¶
Basic support for multi-step form entry. For more complexity we could do something like https://github.com/jazzband/django-formtools/tree/master/formtools/wizard
We store temporary state in the
form_state
dictionary on theCamcopsSession
object on the request. Arbitrary values can be stored inform_state
. The following are used by this mixin:“step” stores the name of the current form entry step.
“route_name” stores the name of the current route, so we can detect if the form state is stale from a previous incomplete operation.
Views using this Mixin should implement:
wizard_first_step
: The name of the first form entry stepwizard_forms
: step name -> :class:Form
dictwizard_templates
: step name -> template filename dictwizard_extra_contexts
: step name -> context dict dictAlternatively, subclasses can override
get_first_step()
etc.The logic of changing steps is left to the subclass.
- __init__(*args, **kwargs) None [source]¶
We prevent stale state from messing things up by clearing state when a form sequence starts. Form sequences start with HTTP GET and proceed via HTTP POST. So, if this is a GET request, we clear the state. We do so in the __init__ sequence, as others may wish to write state before the view is dispatched.
An example of stale state: the user sets an MFA method but then that is disallowed on the server whilst they are halfway through login. (That leaves users totally stuffed as they are not properly “logged in” and therefore can’t easily log out.)
There are other examples seen in testing. This method gets round all those. (For example, the worst-case situation is then advising the user to log in again, or start whatever form-based process it was again).
We also reset the state if the stored route name doesn’t match the current route name.
- finish() None [source]¶
Ends, by marking the state as finished, and clearing any other state except the current route/step (the step in particular may be useful for subsequent functions).
- form_valid_response(form: Form, appstruct: Dict[str, Any]) pyramid.response.Response [source]¶
Called when the form is submitted via POST and is valid. Redirects to the supplied “success” URL.
- get_extra_context() Dict[str, Any] [source]¶
Returns any extra context information (as a dictionary) for the current step.
- get_form_class() Optional[Type[Form]] [source]¶
Returns the class of Form to be used for the current step (not a form instance).
- get_template_name() str [source]¶
Returns the Mako template filename to be used for the current step.
- property route_name: Optional[str]¶
Get the name of the current route. See class help.
- property state: Dict[str, Any]¶
Returns the (arbitrary) state dictionary. See class help.
- property step: str¶
Returns the current step.
- class camcops_server.cc_modules.cc_view_classes.ModelFormMixin[source]¶
Represents an ORM object (the model) and an associated form.
- form_valid_process_data(form: Form, appstruct: Dict[str, Any]) None [source]¶
Called when the form is valid. Saves the associated model.
- get_form_values() Dict[str, Any] [source]¶
Reads form values from the object (or provides an empty dictionary if there is no object yet). Returns a form dictionary.
- get_model_form_dict() Dict[str, str] [source]¶
Returns the dictionary mapping model attribute names to form parameter names.
- class camcops_server.cc_modules.cc_view_classes.ProcessFormView(request: CamcopsRequest)[source]¶
Render a form on GET and processes it on POST.
Requires ContextMixin.
- form_invalid(validation_error: deform.exception.ValidationFailure) pyramid.response.Response [source]¶
Called when the form is submitted via POST and is invalid. Returns a response with a rendering of the invalid form.
- form_valid(form: Form, appstruct: Dict[str, Any]) pyramid.response.Response [source]¶
2021-10-05: separate data handling and the response to return. Why? Because:
returning a response can involve “return response” or “raise HTTPFound”, making flow harder to track;
the Python method resolution order (MRO) makes it harder to be clear on the flow through the combination function.
- form_valid_process_data(form: Form, appstruct: Dict[str, Any]) None [source]¶
Perform any handling of data from the form.
Override in subclasses or mixins if necessary. Be sure to call the superclass method to ensure all actions are performed.
- form_valid_response(form: Form, appstruct: Dict[str, Any]) pyramid.response.Response [source]¶
Return the response (or raise a redirection exception) following valid form submission.
- get() pyramid.response.Response [source]¶
Handle GET requests: instantiate a blank version of the form and render it.
- post() pyramid.response.Response [source]¶
Handle POST requests:
if the user has cancelled, redirect to the cancellation URL;
instantiate a form instance with the passed POST variables and then check if it’s valid;
if it’s invalid, call
form_invalid()
, which typically renders the form to show the errors and allow resubmission;if it’s valid, call
form_valid()
, which in the default handlerprocesses data via
form_valid_process_data()
, andreturns a response (either another form or redirection to another URL) via
form_valid_response()
.
- class camcops_server.cc_modules.cc_view_classes.SingleObjectMixin[source]¶
Represents a single ORM object, for use as a mixin.
- class camcops_server.cc_modules.cc_view_classes.TemplateResponseMixin[source]¶
A mixin that can be used to render a Mako template.
- class camcops_server.cc_modules.cc_view_classes.UpdateView(request: CamcopsRequest)[source]¶
View for updating an object, with a response rendered by a template.
- class camcops_server.cc_modules.cc_view_classes.View(request: CamcopsRequest)[source]¶
Simple parent class for all views. Owns the request object and provides a dispatcher for HTTP requests.
Derived classes typically implement
get()
andpost()
.- __init__(request: CamcopsRequest) None [source]¶
- Parameters
request – a
camcops_server.cc_modules.cc_request.CamcopsRequest
- dispatch() pyramid.response.Response [source]¶
Try to dispatch to the right HTTP method (e.g. GET, POST). If a method doesn’t exist, defer to the error handler. Also defer to the error handler if the request method isn’t on the approved list.
Specifically, this ends up calling
self.get()
orself.post()
orself.http_method_not_allowed()
.