Class-based Views¶
This page introduces using theView andMethodViewclasses to write class-based views.
A class-based view is a class that acts as a view function. Because itis a class, different instances of the class can be created withdifferent arguments, to change the behavior of the view. This is alsoknown as generic, reusable, or pluggable views.
An example of where this is useful is defining a class that creates anAPI based on the database model it is initialized with.
For more complex API behavior and customization, look into the variousAPI extensions for Flask.
Basic Reusable View¶
Let’s walk through an example converting a view function to a viewclass. We start with a view function that queries a list of users thenrenders a template to show the list.
@app.route("/users/")defuser_list():users=User.query.all()returnrender_template("users.html",users=users)
This works for the user model, but let’s say you also had more modelsthat needed list pages. You’d need to write another view function foreach model, even though the only thing that would change is the modeland template name.
Instead, you can write aView subclass that will query a modeland render a template. As the first step, we’ll convert the view to aclass without any customization.
fromflask.viewsimportViewclassUserList(View):defdispatch_request(self):users=User.query.all()returnrender_template("users.html",objects=users)app.add_url_rule("/users/",view_func=UserList.as_view("user_list"))
TheView.dispatch_request() method is the equivalent of the viewfunction. CallingView.as_view() method will create a viewfunction that can be registered on the app with itsadd_url_rule() method. The first argument toas_view is the name to use to refer to the view withurl_for().
Note
You can’t decorate the class with@app.route() the way you’ddo with a basic view function.
Next, we need to be able to register the same view class for differentmodels and templates, to make it more useful than the original function.The class will take two arguments, the model and template, and storethem onself. Thendispatch_request can reference these insteadof hard-coded values.
classListView(View):def__init__(self,model,template):self.model=modelself.template=templatedefdispatch_request(self):items=self.model.query.all()returnrender_template(self.template,items=items)
Remember, we create the view function withView.as_view() instead ofcreating the class directly. Any extra arguments passed toas_vieware then passed when creating the class. Now we can register the sameview to handle multiple models.
app.add_url_rule("/users/",view_func=ListView.as_view("user_list",User,"users.html"),)app.add_url_rule("/stories/",view_func=ListView.as_view("story_list",Story,"stories.html"),)
URL Variables¶
Any variables captured by the URL are passed as keyword arguments to thedispatch_request method, as they would be for a regular viewfunction.
classDetailView(View):def__init__(self,model):self.model=modelself.template=f"{model.__name__.lower()}/detail.html"defdispatch_request(self,id)item=self.model.query.get_or_404(id)returnrender_template(self.template,item=item)app.add_url_rule("/users/<int:id>",view_func=DetailView.as_view("user_detail",User))
View Lifetime andself¶
By default, a new instance of the view class is created every time arequest is handled. This means that it is safe to write other data toself during the request, since the next request will not see it,unlike other forms of global state.
However, if your view class needs to do a lot of complex initialization,doing it for every request is unnecessary and can be inefficient. Toavoid this, setView.init_every_request toFalse, which willonly create one instance of the class and use it for every request. Inthis case, writing toself is not safe. If you need to store dataduring the request, useg instead.
In theListView example, nothing writes toself during therequest, so it is more efficient to create a single instance.
classListView(View):init_every_request=Falsedef__init__(self,model,template):self.model=modelself.template=templatedefdispatch_request(self):items=self.model.query.all()returnrender_template(self.template,items=items)
Different instances will still be created each for eachas_viewcall, but not for each request to those views.
View Decorators¶
The view class itself is not the view function. View decorators need tobe applied to the view function returned byas_view, not the classitself. SetView.decorators to a list of decorators to apply.
classUserList(View):decorators=[cache(minutes=2),login_required]app.add_url_rule('/users/',view_func=UserList.as_view())
If you didn’t setdecorators, you could apply them manually instead.This is equivalent to:
view=UserList.as_view("users_list")view=cache(minutes=2)(view)view=login_required(view)app.add_url_rule('/users/',view_func=view)
Keep in mind that order matters. If you’re used to@decorator style,this is equivalent to:
@app.route("/users/")@login_required@cache(minutes=2)defuser_list():...
Method Hints¶
A common pattern is to register a view withmethods=["GET","POST"],then checkrequest.method=="POST" to decide what to do. SettingView.methods is equivalent to passing the list of methods toadd_url_rule orroute.
classMyView(View):methods=["GET","POST"]defdispatch_request(self):ifrequest.method=="POST":......app.add_url_rule('/my-view',view_func=MyView.as_view('my-view'))
This is equivalent to the following, except further subclasses caninherit or change the methods.
app.add_url_rule("/my-view",view_func=MyView.as_view("my-view"),methods=["GET","POST"],)
Method Dispatching and APIs¶
For APIs it can be helpful to use a different function for each HTTPmethod.MethodView extends the basicView to dispatchto different methods of the class based on the request method. Each HTTPmethod maps to a method of the class with the same (lowercase) name.
MethodView automatically setsView.methods based on themethods defined by the class. It even knows how to handle subclassesthat override or define other methods.
We can make a genericItemAPI class that provides get (detail),patch (edit), and delete methods for a given model. AGroupAPI canprovide get (list) and post (create) methods.
fromflask.viewsimportMethodViewclassItemAPI(MethodView):init_every_request=Falsedef__init__(self,model):self.model=modelself.validator=generate_validator(model)def_get_item(self,id):returnself.model.query.get_or_404(id)defget(self,id):item=self._get_item(id)returnjsonify(item.to_json())defpatch(self,id):item=self._get_item(id)errors=self.validator.validate(item,request.json)iferrors:returnjsonify(errors),400item.update_from_json(request.json)db.session.commit()returnjsonify(item.to_json())defdelete(self,id):item=self._get_item(id)db.session.delete(item)db.session.commit()return"",204classGroupAPI(MethodView):init_every_request=Falsedef__init__(self,model):self.model=modelself.validator=generate_validator(model,create=True)defget(self):items=self.model.query.all()returnjsonify([item.to_json()foriteminitems])defpost(self):errors=self.validator.validate(request.json)iferrors:returnjsonify(errors),400db.session.add(self.model.from_json(request.json))db.session.commit()returnjsonify(item.to_json())defregister_api(app,model,name):item=ItemAPI.as_view(f"{name}-item",model)group=GroupAPI.as_view(f"{name}-group",model)app.add_url_rule(f"/{name}/<int:id>",view_func=item)app.add_url_rule(f"/{name}/",view_func=group)register_api(app,User,"users")register_api(app,Story,"stories")
This produces the following views, a standard REST API!
URL | Method | Description |
|
| List all users |
|
| Create a new user |
|
| Show a single user |
|
| Update a user |
|
| Delete a user |
|
| List all stories |
|
| Create a new story |
|
| Show a single story |
|
| Update a story |
|
| Delete a story |