Skip to content

Commit b9e6461

Browse files
committed
More docs
1 parent dc53205 commit b9e6461

File tree

3 files changed

+264
-11
lines changed

3 files changed

+264
-11
lines changed

bolt/bolt/utils/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Utilities
2+
3+
Various utilities for common things like text manipulation, parsing, dates, and more.

bolt/bolt/views/README.md

Lines changed: 259 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,267 @@
22

33
Take a request, return a response.
44

5-
Class-based vs function-based views is a well-known debate in Django.
5+
Bolt views are written as classes,
6+
with a straightforward API that keeps simple views simple,
7+
but gives you the power of a full class to handle more complex cases.
68

7-
My take is that class-based views are the way to go,
8-
but the implmentation of Django's base classes was too clunky.
9+
```python
10+
from bolt.views import View
911

10-
I'm not saying it's easy to get "right",
11-
but there's not much argument that it could be better.
1212

13-
Bolt includes my take on simpler class-based views:
13+
class ExampleView(View):
14+
def get(self):
15+
return "Hello, world!"
16+
```
1417

15-
- fewer base classes
16-
- fewer method `*args` and `**kwargs`
17-
- not explicitly tied to models
18-
- can return a `Response` object, `str`, or `list`/`dict` (which will be converted to JSON)
18+
## HTTP methods -> class methods
1919

20-
http://django-vanilla-views.org/
20+
The HTTP methd of the request will map to a class method of the same name on the view.
21+
22+
If a request comes in and there isn't a matching method on the view,
23+
Bolt will return a `405 Method Not Allowed` response.
24+
25+
```python
26+
from bolt.views import View
27+
28+
29+
class ExampleView(View):
30+
def get(self):
31+
pass
32+
33+
def post(self):
34+
pass
35+
36+
def put(self):
37+
pass
38+
39+
def patch(self):
40+
pass
41+
42+
def delete(self):
43+
pass
44+
45+
def trace(self):
46+
pass
47+
```
48+
49+
The [base `View` class](./base.py) defines default `options` and `head` behavior,
50+
but you can override these too.
51+
52+
## Return types
53+
54+
For simple plain text and JSON responses,
55+
you don't need to instantiate a `Response` object.
56+
57+
```python
58+
class TextView(View):
59+
def get(self):
60+
return "Hello, world!"
61+
62+
63+
class JsonView(View):
64+
def get(self):
65+
return {"message": "Hello, world!"}
66+
```
67+
68+
## Template views
69+
70+
The most common behavior for a view is to render a template.
71+
72+
```python
73+
from bolt.views import TemplateView
74+
75+
76+
class ExampleView(TemplateView):
77+
template_name = "example.html"
78+
79+
def get_template_context(self):
80+
context = super().get_template_context()
81+
context["message"] = "Hello, world!"
82+
return context
83+
```
84+
85+
The `TemplateView` is also the base class for *most* of the other built-in view classes.
86+
87+
## Form views
88+
89+
Standard [forms](../forms) can be rendered and processed by a `FormView`.
90+
91+
```python
92+
from bolt.views import FormView
93+
from .forms import ExampleForm
94+
95+
96+
class ExampleView(FormView):
97+
template_name = "example.html"
98+
form_class = ExampleForm
99+
success_url = "." # Redirect to the same page
100+
101+
def form_valid(self, form):
102+
# Do other successfull form processing here
103+
return super().form_valid(form)
104+
```
105+
106+
Rendering forms is done directly in the HTML.
107+
108+
```html
109+
{% extends "base.html" %}
110+
111+
{% block content %}
112+
113+
<form method="post">
114+
{{ csrf_input }}
115+
116+
<!-- Render general form errors -->
117+
{% for error in form.non_field_errors %}
118+
<div>{{ error }}</div>
119+
{% endfor %}
120+
121+
<!-- Render form fields individually (or with Jinja helps or other concepts) -->
122+
<label for="{{ form.email.html_id }}">Email</label>
123+
<input
124+
type="email"
125+
name="{{ form.email.html_name }}"
126+
id="{{ form.email.html_id }}"
127+
value="{{ form.email.value() or '' }}"
128+
autocomplete="email"
129+
autofocus
130+
required>
131+
{% if form.email.errors %}
132+
<div>{{ form.email.errors|join(', ') }}</div>
133+
{% endif %}
134+
135+
<button type="submit">Save</button>
136+
</form>
137+
138+
{% endblock %}
139+
```
140+
141+
## Object views
142+
143+
The object views support the standard CRUD (create, read/detail, update, delete) operations, plus a list view.
144+
145+
```python
146+
from bolt.views import DetailView, CreateView, UpdateView, DeleteView, ListView
147+
148+
149+
class ExampleDetailView(DetailView):
150+
template_name = "detail.html"
151+
152+
def get_object(self):
153+
return MyObjectClass.objects.get(
154+
pk=self.url_kwargs["pk"],
155+
user=self.request.user, # Limit access
156+
)
157+
158+
159+
class ExampleCreateView(CreateView):
160+
template_name = "create.html"
161+
form_class = CustomCreateForm
162+
success_url = "."
163+
164+
165+
class ExampleUpdateView(UpdateView):
166+
template_name = "update.html"
167+
form_class = CustomUpdateForm
168+
success_url = "."
169+
170+
def get_object(self):
171+
return MyObjectClass.objects.get(
172+
pk=self.url_kwargs["pk"],
173+
user=self.request.user, # Limit access
174+
)
175+
176+
177+
class ExampleDeleteView(DeleteView):
178+
template_name = "delete.html"
179+
success_url = "."
180+
181+
# No form class necessary.
182+
# Just POST to this view to delete the object.
183+
184+
def get_object(self):
185+
return MyObjectClass.objects.get(
186+
pk=self.url_kwargs["pk"],
187+
user=self.request.user, # Limit access
188+
)
189+
190+
191+
class ExampleListView(ListView):
192+
template_name = "list.html"
193+
194+
def get_objects(self):
195+
return MyObjectClass.objects.filter(
196+
user=self.request.user, # Limit access
197+
)
198+
```
199+
200+
## Response exceptions
201+
202+
At any point in the request handling,
203+
a view can raise a `ResponseException` to immediately exit and return the wrapped response.
204+
205+
This isn't always necessary, but can be useful for raising rate limits or authorization errors when you're a couple layers deep in the view handling or helper functions.
206+
207+
```python
208+
from bolt.views import DetailView
209+
from bolt.views.exceptions import ResponseException
210+
from bolt.http import Response
211+
212+
213+
class ExampleView(DetailView):
214+
def get_object(self):
215+
if self.request.user.exceeds_rate_limit:
216+
raise ResponseException(
217+
Response("Rate limit exceeded", status=429)
218+
)
219+
220+
return AnExpensiveObject()
221+
```
222+
223+
## Error views
224+
225+
By default, HTTP errors will be rendered by `templates/<status_code>.html` or `templates/error.html`.
226+
227+
You can define your own error views by pointing the `HTTP_ERROR_VIEWS` setting to a dictionary of status codes and view classes.
228+
229+
```python
230+
# app/settings.py
231+
HTTP_ERROR_VIEWS = {
232+
404: "errors.NotFoundView",
233+
}
234+
```
235+
236+
```python
237+
# app/errors.py
238+
from bolt.views import View
239+
240+
241+
class NotFoundView(View):
242+
def get(self):
243+
# A custom implementation or error view handling
244+
pass
245+
```
246+
247+
## Redirect views
248+
249+
```python
250+
from bolt.views import RedirectView
251+
252+
253+
class ExampleRedirectView(RedirectView):
254+
url = "/new-location/"
255+
permanent = True
256+
```
257+
258+
## CSRF exemption
259+
260+
```python
261+
from bolt.views import View
262+
from bolt.views.csrf import CsrfExemptViewMixin
263+
264+
265+
class ExemptView(CsrfExemptViewMixin, View):
266+
def post(self):
267+
return "Hello, world!"
268+
```

scripts/compile-readmes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ compile_readme() {
1414

1515
code_base_dir=$(dirname "$code_readme")
1616

17+
# support ../ and other paths...
18+
1719
# Find and replace any "](./" markdown links and replace with a full github.com url
1820
docs_url="https://boltframework.dev/docs"
1921
sed -i '' -e "s#](\./\([^)]*\))#]($docs_url/$code_base_dir/\1)#g" "$package_readme"

0 commit comments

Comments
 (0)