Skip to content

Commit f06c8d2

Browse files
authored
V2: Add merge props (#60)
* Add merge props * add docs
1 parent 36e2974 commit f06c8d2

File tree

9 files changed

+148
-34
lines changed

9 files changed

+148
-34
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,34 @@ def example(request):
183183

184184
In the example above, the `data1`, and `data2` props will be fetched in one request, while the `data` prop will be fetched in a separate request in parallel. Group names are arbitrary strings and can be anything you choose.
185185

186+
### Merge Props
187+
188+
By default, Inertia overwrites props with the same name when reloading a page. However, there are instances, such as pagination or infinite scrolling, where that is not the desired behavior. In these cases, you can merge props instead of overwriting them.
189+
190+
```python
191+
from inertia import merge, inertia
192+
193+
@inertia('ExampleComponent')
194+
def example(request):
195+
return {
196+
'name': lambda: 'Brandon',
197+
'data': merge(Paginator(objects, 3)),
198+
}
199+
```
200+
201+
You can also combine deferred props with mergeable props to defer the loading of the prop and ultimately mark it as mergeable once it's loaded.
202+
203+
```python
204+
from inertia import defer, inertia
205+
206+
@inertia('ExampleComponent')
207+
def example(request):
208+
return {
209+
'name': lambda: 'Brandon',
210+
'data': defer(lambda: Paginator(objects, 3), merge=True),
211+
}
212+
```
213+
186214
### Json Encoding
187215

188216
Inertia Django ships with a custom JsonEncoder at `inertia.utils.InertiaJsonEncoder` that extends Django's

inertia/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .http import inertia, render, location
2-
from .utils import lazy, optional, defer
2+
from .utils import lazy, optional, defer, merge
33
from .share import share

inertia/http.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from json import dumps as json_encode
66
from functools import wraps
77
import requests
8-
from .utils import DeferredProp, LazyProp
8+
from .prop_classes import IgnoreOnFirstLoadProp, DeferredProp, MergeableProp
99

1010
INERTIA_REQUEST_ENCRYPT_HISTORY = "_inertia_encrypt_history"
1111
INERTIA_SESSION_CLEAR_HISTORY = "_inertia_clear_history"
@@ -37,7 +37,7 @@ def build_props():
3737
if key not in partial_keys():
3838
del _props[key]
3939
else:
40-
if isinstance(_props[key], LazyProp) or isinstance(_props[key], DeferredProp):
40+
if isinstance(_props[key], IgnoreOnFirstLoadProp):
4141
del _props[key]
4242

4343
return deep_transform_callables(_props)
@@ -52,7 +52,20 @@ def build_deferred_props():
5252
_deferred_props.setdefault(prop.group, []).append(key)
5353

5454
return _deferred_props
55-
55+
56+
def build_merge_props():
57+
reset_keys = request.headers.get('X-Inertia-Reset', '').split(',')
58+
59+
return [
60+
key
61+
for key, prop in props.items()
62+
if (
63+
isinstance(prop, MergeableProp)
64+
and prop.should_merge()
65+
and key not in reset_keys
66+
)
67+
]
68+
5669
def render_ssr():
5770
data = json_encode(page_data(), cls=settings.INERTIA_JSON_ENCODER)
5871
response = requests.post(
@@ -88,6 +101,10 @@ def page_data():
88101
if _deferred_props:
89102
_page['deferredProps'] = _deferred_props
90103

104+
_merge_props = build_merge_props()
105+
if _merge_props:
106+
_page['mergeProps'] = _merge_props
107+
91108
return _page
92109

93110
if 'X-Inertia' in request.headers:

inertia/prop_classes.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from abc import ABC, abstractmethod
2+
import warnings
3+
4+
class CallableProp(ABC):
5+
def __init__(self, prop):
6+
self.prop = prop
7+
8+
def __call__(self):
9+
return self.prop() if callable(self.prop) else self.prop
10+
11+
class MergeableProp(ABC):
12+
@abstractmethod
13+
def should_merge(self):
14+
pass
15+
16+
class IgnoreOnFirstLoadProp(ABC):
17+
pass
18+
19+
class OptionalProp(CallableProp, IgnoreOnFirstLoadProp):
20+
pass
21+
22+
class DeferredProp(CallableProp, MergeableProp, IgnoreOnFirstLoadProp):
23+
def __init__(self, prop, group, merge=False):
24+
super().__init__(prop)
25+
self.group = group
26+
self.merge = merge
27+
28+
def should_merge(self):
29+
return self.merge
30+
31+
class MergeProp(CallableProp, MergeableProp):
32+
def should_merge(self):
33+
return True

inertia/test.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ def page(self):
2929

3030
def props(self):
3131
return self.page()['props']
32+
33+
def merge_props(self):
34+
return self.page()['mergeProps']
35+
36+
def deferred_props(self):
37+
return self.page()['deferredProps']
3238

3339
def template_data(self):
3440
context = self.mock_render.call_args.args[2]
@@ -53,7 +59,7 @@ def assertHasExactTemplateData(self, template_data):
5359
def assertComponentUsed(self, component_name):
5460
self.assertEqual(component_name, self.component())
5561

56-
def inertia_page(url, component='TestComponent', props={}, template_data={}, deferred_props=None):
62+
def inertia_page(url, component='TestComponent', props={}, template_data={}, deferred_props=None, merge_props=None):
5763
_page = {
5864
'component': component,
5965
'props': props,
@@ -65,6 +71,9 @@ def inertia_page(url, component='TestComponent', props={}, template_data={}, def
6571

6672
if deferred_props:
6773
_page['deferredProps'] = deferred_props
74+
75+
if merge_props:
76+
_page['mergeProps'] = merge_props
6877

6978
return _page
7079

inertia/tests/test_rendering.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,36 @@ def test_only_deferred_props_in_group_are_included_when_requested(self):
162162
self.inertia.get('/defer-group/', HTTP_X_INERTIA_PARTIAL_DATA='grit', HTTP_X_INERTIA_PARTIAL_COMPONENT='TestComponent'),
163163
inertia_page('defer-group', props={'grit': 'intense'})
164164
)
165+
166+
class MergePropsTestCase(InertiaTestCase):
167+
def test_merge_props_are_included_on_initial_load(self):
168+
self.assertJSONResponse(
169+
self.inertia.get('/merge/'),
170+
inertia_page('merge', props={
171+
'name': 'Brandon',
172+
'sport': 'Hockey',
173+
}, merge_props=['sport', 'team'], deferred_props={'default': ['team']})
174+
)
175+
176+
177+
def test_deferred_merge_props_are_included_on_subsequent_load(self):
178+
self.assertJSONResponse(
179+
self.inertia.get('/merge/', HTTP_X_INERTIA_PARTIAL_DATA='team', HTTP_X_INERTIA_PARTIAL_COMPONENT='TestComponent'),
180+
inertia_page('merge', props={
181+
'team': 'Penguins',
182+
}, merge_props=['sport', 'team'])
183+
)
184+
185+
def test_merge_props_are_not_included_when_reset(self):
186+
self.assertJSONResponse(
187+
self.inertia.get(
188+
'/merge/',
189+
HTTP_X_INERTIA_PARTIAL_DATA='sport,team',
190+
HTTP_X_INERTIA_PARTIAL_COMPONENT='TestComponent',
191+
HTTP_X_INERTIA_RESET='sport,team'
192+
),
193+
inertia_page('merge', props={
194+
'sport': 'Hockey',
195+
'team': 'Penguins',
196+
})
197+
)

inertia/tests/testapp/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
path('lazy/', views.lazy_test),
1111
path('optional/', views.optional_test),
1212
path('defer/', views.defer_test),
13-
path('defer-group/', views.defer_group_test),
13+
path('defer-group/', views.defer_group_test),
14+
path('merge/', views.merge_test),
1415
path('complex-props/', views.complex_props_test),
1516
path('share/', views.share_test),
1617
path('inertia-redirect/', views.inertia_redirect_test),

inertia/tests/testapp/views.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.http.response import HttpResponse
22
from django.shortcuts import redirect
33
from django.utils.decorators import decorator_from_middleware
4-
from inertia import inertia, render, lazy, optional, defer, share, location
4+
from inertia import inertia, render, lazy, merge, optional, defer, share, location
55
from inertia.http import INERTIA_SESSION_CLEAR_HISTORY, clear_history, encrypt_history
66

77
class ShareMiddleware:
@@ -77,6 +77,14 @@ def defer_group_test(request):
7777
'grit': defer(lambda: 'intense')
7878
}
7979

80+
@inertia('TestComponent')
81+
def merge_test(request):
82+
return {
83+
'name': 'Brandon',
84+
'sport': merge(lambda: 'Hockey'),
85+
'team': defer(lambda: 'Penguins', merge=True),
86+
}
87+
8088
@inertia('TestComponent')
8189
def complex_props_test(request):
8290
return {

inertia/utils.py

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.db import models
33
from django.db.models.query import QuerySet
44
from django.forms.models import model_to_dict as base_model_to_dict
5+
from .prop_classes import OptionalProp, DeferredProp, MergeProp
56
import warnings
67

78
def model_to_dict(model):
@@ -17,35 +18,19 @@ def default(self, value):
1718

1819
return super().default(value)
1920

20-
class LazyProp:
21-
def __init__(self, prop):
22-
warnings.warn(
23-
"lazy and LazyProp are deprecated and will be removed in a future version. Please use optional instead.",
24-
DeprecationWarning,
25-
stacklevel=2
26-
)
27-
self.prop = prop
28-
29-
def __call__(self):
30-
return self.prop() if callable(self.prop) else self.prop
31-
32-
class OptionalProp(LazyProp):
33-
def __init__(self, prop):
34-
self.prop = prop
35-
36-
class DeferredProp:
37-
def __init__(self, prop, group):
38-
self.prop = prop
39-
self.group = group
40-
41-
def __call__(self):
42-
return self.prop() if callable(self.prop) else self.prop
43-
4421
def lazy(prop):
45-
return LazyProp(prop)
22+
warnings.warn(
23+
"lazy is deprecated and will be removed in a future version. Please use optional instead.",
24+
DeprecationWarning,
25+
stacklevel=2
26+
)
27+
return optional(prop)
4628

4729
def optional(prop):
4830
return OptionalProp(prop)
4931

50-
def defer(prop, group="default"):
51-
return DeferredProp(prop, group)
32+
def defer(prop, group="default", merge=False):
33+
return DeferredProp(prop, group=group, merge=merge)
34+
35+
def merge(prop):
36+
return MergeProp(prop)

0 commit comments

Comments
 (0)