@@ -1417,9 +1417,9 @@ settings:
14171417
14181418* New in DRF-extensions 0.3.2*
14191419
1420- In addition, ` APIETAGProcessor ` exists explicitly requires a function that creates an ETag value from model instances.
1420+ In addition, ` APIETAGProcessor ` explicitly requires a function that (ideally) creates an ETag value from model instances.
14211421If the ` @api_etag ` decorator is used without ` etag_func ` the framework will raise an ` AssertionError ` .
1422- The following snipped would not work:
1422+ Hence, the following snipped would not work:
14231423
14241424 # BEGIN BAD CODE:
14251425 class View(views.APIView):
@@ -1434,7 +1434,7 @@ response on conditional requests, even if the resource was modified meanwhile.
14341434Therefore the ` APIETAGProcessor ` cannot be used without specifying an ` etag_func ` as keyword argument and there exists convenient
14351435[ mixin classes] ( #apietagmixin ) .
14361436
1437- You can use the decorator in regular ` APIView ` and subclasses from the ` rest_framework.generics ` module,
1437+ You can use the decorator in regular ` APIView ` , and subclasses from the ` rest_framework.generics ` module,
14381438but ensure to include a ` queryset ` attribute or override ` get_queryset() ` :
14391439
14401440 from rest_framework import generics
@@ -1454,7 +1454,38 @@ but ensure to include a `queryset` attribute or override `get_queryset()`:
14541454 return Response(status=status.HTTP_204_NO_CONTENT)
14551455
14561456
1457- #### Usage with caching
1457+ The next difference to the ` @etag ` decorator is that it defines an explicit map of
1458+ required headers for each HTTP request verb, using the following default values for unsafe methods:
1459+
1460+ precondition_map = {'PUT': ['If-Match'],
1461+ 'PATCH': ['If-Match'],
1462+ 'DELETE': ['If-Match']}
1463+
1464+ You can specify a custom set of headers in the decorator by passing the ` precondition_map ` keyword argument.
1465+ For instance, this statement
1466+
1467+ @api_etag(etag_func=default_api_object_etag_func, precondition_map={'PUT': ['X-mycorp-custom']})
1468+ def put(self, request, *args, **kwargs):
1469+ obj = Book.objects.get(id=kwargs['pk'])
1470+ # ... perform some custom operations here ...
1471+ obj.save()
1472+ return Response(status=status.HTTP_200_OK)
1473+
1474+ checks for the presence of a custom header ` X-mycorp-custom ` in the request and permits the request, if it is present,
1475+ or returns a ` 428 PRECONDITION REQUIRED ` response.
1476+
1477+ Similarly, to disable all checks for a particular method simply pass an empty dict:
1478+
1479+ @api_etag(etag_func=default_api_object_etag_func, precondition_map={})
1480+ def put(self, request, *args, **kwargs):
1481+ obj = Book.objects.get(id=kwargs['pk'])
1482+ # ... perform some custom operations here ...
1483+ obj.save()
1484+ return Response(status=status.HTTP_200_OK)
1485+
1486+ Please note that passing ` None ` in the ` precondition_map ` argument falls back to using the default map.
1487+
1488+ #### Usage ETag with caching
14581489
14591490As you can see ` @etag ` and ` @cache_response ` decorators has similar key calculation approaches.
14601491They both can take key from simple callable function. And more than this - in many cases they share the same calculation logic.
@@ -1560,63 +1591,72 @@ Instead of obtaining a lock, the client attempts a write operation with the toke
15601591The operation succeeds if the token is still valid and fails otherwise.
15611592
15621593HTTP, being a stateless application control, is designed for optimistic concurrency control.
1563- ** NB: The current implementation DOES NOT enforce the If-Match header. It returns 403 if the ETag is not supplied, and permits the unsafe method! **
1564-
1565- PUT/PATCH
1566- |
1567- +-------------+
1568- | Etag |
1569- | supplied? |
1570- +------------- +
1571- | |
1572- Yes No
1573- | |
1574- +--------------------+ + ------------------ -----+
1575- | Do preconditions | | Does the |
1576- | match? | | resource exist? |
1577- +--------------------+ +-----------------------+
1578- | | | |
1579- Yes No Yes No
1580- | | | |
1581- +--------------+ +--------------------+ + -------------+ |
1582- | Update the | | 412 Precondition | | 403 | |
1583- | resource | | failed | | Forbidden | |
1584- +--------------+ +--------------------+ +-------------+ |
1585- |
1586- +-----------------------+
1587- | Can clients |
1588- | create resources? |
1589- +----------------- ------+
1590- | |
1591- Yes No
1592- | |
1593- +-----------+ +-------------+
1594- | 201 | | 404 |
1595- | Created | | Not Found |
1596- +-----------+ +-------------+
1594+ According to [ RFC 6585 ] ( https://tools.ietf.org/html/rfc6585 ) , the server can optionally require
1595+ a condition for a request. This library returns a ` 428 ` status, if no ETag is supplied, but would be mandatory
1596+ for a request to succeed.
1597+
1598+ Update:
1599+
1600+ PUT/PATCH
1601+ +
1602+ +-----------+------------+
1603+ | ETag |
1604+ | supplied? |
1605+ ++ -----------------+ -----+
1606+ | |
1607+ Yes No
1608+ | |
1609+ +---------------------++ ++-------------+
1610+ | Do preconditions | | Precondition |
1611+ | match? | | required? |
1612+ +---+ -----------------++ ++ ------------++
1613+ | | | |
1614+ Yes No No Yes
1615+ | | | |
1616+ +----------+------+ +-------+----------+ +---+-----+ |
1617+ | Does resource | | 412 Precondition | | 200 OK | |
1618+ | exist? | | failed | | Update | |
1619+ ++---------------++ +------------------+ +---------+ |
1620+ | | +-----------+ ------+
1621+ Yes No | 428 Precondition |
1622+ | | | required |
1623+ +----+----+ +----+----+ +------------------+
1624+ | 200 OK | | 404 Not |
1625+ | Update | | found |
1626+ +---------+ +---------+
1627+
15971628
15981629Delete:
15991630
1600- DELETE
1601- |
1602- +-------------+
1603- | Etag |
1604- | supplied? |
1605- +-------------+
1606- | |
1607- Yes No
1608- | |
1609- +--------------------+ +-------------+
1610- | Do preconditions | | 403 |
1611- | match? | | Forbidden |
1612- +--------------------+ +-------------+
1613- | |
1614- Yes No
1615- | |
1616- +--------------+ +--------------------+
1617- | Delete the | | 412 Precondition |
1618- | resource | | failed |
1619- +--------------+ +--------------------+
1631+ DELETE
1632+ +
1633+ +-----------+------------+
1634+ | ETag |
1635+ | supplied? |
1636+ ++-----------------+-----+
1637+ | |
1638+ Yes No
1639+ | |
1640+ +---------------------++ ++-------------+
1641+ | Do preconditions | | Precondition |
1642+ | match? | | required? |
1643+ +---+-----------------++ ++------------++
1644+ | | | |
1645+ Yes No No Yes
1646+ | | | |
1647+ +----------+------+ +-------+----------+ +---+-----+ |
1648+ | Does resource | | 412 Precondition | | 204 No | |
1649+ | exist? | | failed | | content | |
1650+ ++---------------++ +------------------+ +---------+ |
1651+ | | +-----------+------+
1652+ Yes No | 428 Precondition |
1653+ | | | required |
1654+ +----+----+ +----+----+ +------------------+
1655+ | 204 No | | 404 Not |
1656+ | content | | found |
1657+ +---------+ +---------+
1658+
1659+
16201660
16211661** Example: transient key construction**
16221662
@@ -1913,8 +1953,9 @@ There are other mixins for more granular ETag calculation in `rest_framework_ext
19131953* ** APIDestroyETAGMixin** - only for ` destroy ` method
19141954* ** APIUpdateETAGMixin** - only for ` update ` method
19151955
1956+ By default, all mixins require the conditional requests, i.e. they use the default ` precondition_map ` from the
1957+ ` APIETAGProcessor ` class.
19161958
1917- #### Example: persistent key construction
19181959
19191960#### Gzipped ETags
19201961
@@ -2151,8 +2192,9 @@ You can read about versioning, deprecation policy and upgrading from
21512192
21522193#### 0.3.2
21532194
2154- * Jan 2 , 2017*
2195+ * Jan 4 , 2017*
21552196
2197+ * Added ` rest_framework_extensions.exceptions.PreconditionRequiredException ` as subclass of ` rest_framework.exceptions.APIException `
21562198* Added ` @api_etag ` decorator function and ` APIETAGProcessor ` that uses * semantic* ETags per API resource, decoupled from views, such that it can be used in optimistic concurrency control
21572199* Added new default key bits ` RetrieveModelKeyBit ` and ` ListModelKeyBit ` for computing the semantic fingerprint of a django model instance
21582200* Added ` APIETAGMixin ` to be used in DRF viewsets and views
0 commit comments