Skip to content

Commit f57233f

Browse files
committed
Handle calling values() on QuerySet.
1 parent fe39f52 commit f57233f

File tree

4 files changed

+192
-17
lines changed

4 files changed

+192
-17
lines changed

django_unicorn/serializer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ def _json_serializer(obj):
101101
queryset_json = []
102102

103103
for model in obj:
104-
model_json = _get_model_dict(model)
104+
if obj.query.values_select and isinstance(model, dict):
105+
# If the queryset was created with values it's already a dictionary
106+
model_json = model
107+
else:
108+
model_json = _get_model_dict(model)
109+
105110
queryset_json.append(model_json)
106111

107112
return queryset_json

django_unicorn/views/utils.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from dataclasses import is_dataclass
3-
from typing import Any, Dict, Set, Union
3+
from typing import Any, Dict, Union
44

55
from django.db.models import Model, QuerySet
66

@@ -198,23 +198,23 @@ class TestComponent(UnicornView):
198198
queryset._result_cache = []
199199

200200
for (idx, model) in enumerate(queryset._result_cache):
201-
if model.pk == model_value.get("pk"):
202-
constructed_model = _construct_model(
203-
model_type, model_value, constructed_models
204-
)
201+
if hasattr(model, "pk") and model.pk == model_value.get("pk"):
202+
constructed_model = _construct_model(model_type, model_value)
205203
queryset._result_cache[idx] = constructed_model
206204
model_found = True
207205

208206
if not model_found:
209-
constructed_model = _construct_model(
210-
model_type, model_value, constructed_models
211-
)
207+
constructed_model = _construct_model(model_type, model_value)
212208
queryset._result_cache.append(constructed_model)
213209

214210
return queryset
215211

216212

217213
def _construct_model(model_type, model_data: Dict):
214+
"""
215+
Construct a model based on the type and dictionary data.
216+
"""
217+
218218
if not model_data:
219219
return None
220220

@@ -223,14 +223,13 @@ def _construct_model(model_type, model_data: Dict):
223223
for field_name in model_data.keys():
224224
for field in model._meta.fields:
225225
if field.name == field_name or (field_name == "pk" and field.primary_key):
226-
# if field.is_relation:
227-
# related_model = _construct_model(
228-
# field.model, model_data[field_name]
229-
# )
230-
# setattr(model, field.name, related_model)
231-
# else:
232-
233-
setattr(model, field.name, model_data[field_name])
226+
column_name = field.name
227+
228+
if field.is_relation:
229+
column_name = field.attname
230+
231+
setattr(model, column_name, model_data[field_name])
232+
234233
break
235234

236235
return model

tests/test_model_lifecycle.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import uuid
2+
3+
import pytest
4+
5+
from django_unicorn.components import UnicornView, unicorn_view
6+
from django_unicorn.serializer import dumps, loads
7+
from django_unicorn.views.utils import _construct_model, set_property_from_data
8+
from example.coffee.models import Flavor
9+
10+
11+
class FakeComponent(UnicornView):
12+
flavors = Flavor.objects.none()
13+
14+
def __init__(self, **kwargs):
15+
self.flavors = Flavor.objects.none()
16+
super().__init__(**kwargs)
17+
18+
19+
@pytest.mark.django_db
20+
def test_model():
21+
flavor = Flavor(name="first-flavor")
22+
flavor.save()
23+
24+
str_data = dumps({"flavor": flavor})
25+
data = loads(str_data)
26+
flavor_data = data["flavor"]
27+
28+
actual = _construct_model(Flavor, flavor_data)
29+
30+
assert actual.pk == flavor.id
31+
assert actual.name == flavor.name
32+
assert actual.parent is None
33+
34+
35+
@pytest.mark.django_db
36+
def test_model_foreign_key():
37+
parent = Flavor(name="parent-flavor")
38+
parent.save()
39+
flavor = Flavor(name="first-flavor", parent=parent)
40+
flavor.save()
41+
42+
str_data = dumps({"flavor": flavor})
43+
data = loads(str_data)
44+
flavor_data = data["flavor"]
45+
46+
actual = _construct_model(Flavor, flavor_data)
47+
48+
assert actual.pk == flavor.id
49+
assert actual.name == flavor.name
50+
assert actual.parent.pk == parent.id
51+
assert actual.parent.name == parent.name
52+
53+
54+
@pytest.mark.django_db
55+
def test_queryset():
56+
test_component = FakeComponent(component_name="test", component_id="asdf")
57+
assert test_component.flavors.count() == 0
58+
59+
flavor = Flavor(name="qs-first-flavor")
60+
flavor.save()
61+
62+
flavors = Flavor.objects.filter(name="qs-first-flavor")
63+
str_data = dumps({"flavors": flavors})
64+
data = loads(str_data)
65+
flavors_data = data["flavors"]
66+
67+
set_property_from_data(test_component, "flavors", flavors_data)
68+
69+
assert test_component.flavors.count() == 1
70+
assert test_component.flavors[0].uuid == str(flavor.uuid)
71+
assert test_component.flavors[0].id == flavor.id
72+
73+
74+
@pytest.mark.django_db
75+
def test_queryset_values():
76+
test_component = FakeComponent(component_name="test", component_id="asdf")
77+
assert test_component.flavors.count() == 0
78+
79+
flavor = Flavor(name="values-first-flavor")
80+
flavor.save()
81+
82+
flavors = Flavor.objects.filter(name="values-first-flavor").values("uuid")
83+
str_data = dumps({"flavors": flavors})
84+
data = loads(str_data)
85+
flavors_data = data["flavors"]
86+
87+
set_property_from_data(test_component, "flavors", flavors_data)
88+
89+
assert test_component.flavors.count() == 1
90+
assert test_component.flavors[0].uuid == str(flavor.uuid)
91+
assert test_component.flavors[0].id is None
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import pytest
2+
3+
from django_unicorn.views.utils import _construct_model
4+
from example.books.models import Author, Book
5+
from example.coffee.models import Flavor
6+
7+
8+
def test_construct_model_simple_model():
9+
model_data = {
10+
"pk": 1,
11+
"name": "test-name",
12+
}
13+
14+
actual = _construct_model(Flavor, model_data)
15+
16+
assert actual.pk == 1
17+
assert actual.name == "test-name"
18+
19+
20+
@pytest.mark.django_db
21+
def test_construct_model_foreign_key():
22+
flavor = Flavor(name="first-flavor")
23+
flavor.save()
24+
parent = Flavor(name="parent-flavor")
25+
parent.save()
26+
27+
model_data = {
28+
"pk": flavor.id,
29+
"name": flavor.name,
30+
"parent": parent.id,
31+
}
32+
33+
actual = _construct_model(Flavor, model_data)
34+
35+
assert actual.pk == flavor.pk
36+
assert actual.name == flavor.name
37+
assert actual.parent.pk == parent.pk
38+
assert actual.parent.name == parent.name
39+
40+
41+
@pytest.mark.django_db
42+
@pytest.mark.skip(
43+
"This test isn't all that helpful unless related models get serialized"
44+
)
45+
def test_construct_model_recursive_foreign_key():
46+
flavor = Flavor(name="first-flavor")
47+
flavor.save()
48+
parent = Flavor(name="parent-flavor")
49+
parent.save()
50+
51+
model_data = {
52+
"pk": flavor.pk,
53+
"name": flavor.name,
54+
"parent": parent.pk,
55+
}
56+
57+
actual = _construct_model(Flavor, model_data)
58+
59+
assert actual.pk == flavor.pk
60+
assert actual.name == flavor.name
61+
assert actual.parent.pk == parent.pk
62+
assert actual.parent.name == parent.name
63+
64+
65+
@pytest.mark.django_db
66+
def test_construct_model_many_to_many():
67+
author = Author(name="author 1")
68+
author.save()
69+
book = Book(title="book 1", date_published="2021-01-01")
70+
book.save()
71+
author.books.add(book)
72+
73+
author_data = {"pk": author.pk, "name": author.name, "books": [book.pk,]}
74+
75+
actual = _construct_model(Author, author_data)
76+
77+
assert actual.pk == author.pk
78+
assert actual.name == author.name
79+
assert actual.books.count() == 1
80+
assert actual.books.all()[0].title == book.title

0 commit comments

Comments
 (0)