Path decoding¶
Path decoding for Django function based views is simple and elegant. Declare your function:
def album_view(request, album_pk):
album = get_object_or_404(Album, pk=album_pk)
...
Then declare your path mapping:
urlpatterns = [
path('<album_pk>/', album_view),
]
For class based views it’s a little more awkward with self.kwargs
, but
mostly the same. In iommi we have another approach that we think is more
elegant and faster to develop with:
URLConf and iommi¶
The url parameters, or path components, are available in iommi under the params
namespace and also as keyword arguments directly. A simple example is:
class MyPage(Page):
hello = html.div(
lambda name, **_: f'Hello {name}',
)
urlpatterns = [
path('<name>/', MyPage().as_view()),
]
This simple view will take the name
path parameter and print it on the page. This is sometimes useful, but it’s more common to need some more complex lookup on our parameters, so that leads us to path decoders:
iommi path decoders¶
In iommi we have a powerful and easy to use system for path decoding that also works smoothly with iommi views. It builds on top of the params feature described above. Let’s look at an example:
Register your models:
register_path_decoding(
artist_name=Artist.name,
artist_pk=Artist,
album_pk=Album,
)
Now in your path mapping you can write:
urlpatterns = [
path('<artist_name>/<album_pk>/', MyPage().as_view()),
]
…and you will get an Artist
instance as artist
(also in params.artist
) and an Album
instance in album
. The name lookup
assumes your name field is called name
on the model. There’s an advanced
registration system for more complex lookups, but this should cover most
usages.
Use iommi decoders on a function based view¶
You can use the iommi path decoders on a normal FBV too:
@decode_path
def my_view(request, artist, album):
return artist, album
If you want to get any of the raw values before they are decoded you can access them
via request.iommi_view_params
which has both the undecoded and the decoded parameters.
Advanced path decoders¶
For cases where you want to decode something other than a pk or name you need the advanced path decoders. Here’s a simple example:
register_path_decoding(
user_pk=User,
user_username=User.username,
user_email=User.email,
track_foo=lambda string, request, decoded_kwargs, kwargs, **_: Track.objects.get(name__iexact=string.strip())
)
This will allow you to do <user_pk>
, <user_username>
, <user_email>
in your url pattern for the User
model, and track_foo
for the Track
model.
The first example just maps pk
, username
and email
one to one to the model. So for an email lookup it will run User.objects.get(email=params.email)
to get the User
object.
The second example is for more complex use cases. As you have access to request
, decoded_kwargs
and kwargs
in addition to the model you can perform path decoding that is not possible with Django path decoders.
Path decoders for access control¶
Access control on path decoder level can be very powerful if you have row-level access rules. Let’s say that only staff users can edit Black Sabbath albums:
def album_pk_decoder(string, request, **_):
album = Album.objects.get(pk=string)
if album.artist.name == 'Black Sabbath' and not request.user.is_staff:
raise PermissionError('Only staff can edit Black Sabbath albums')
return album
register_path_decoding(album_pk=PathDecoder(decode=album_pk_decoder, name='album'))
The beauty of this approach is that if you do this consistently in your product, all views get decoded objects that are safe to user without further checks.