def test_exclude_field_attributes_invalid_type():
    with pytest.raises(AssertionError) as e:
        serializer.dumps(
            {
                "book": {
                    "title": "The Grapes of Wrath",
                    "author": "John Steinbeck"
                }
            },
            exclude_field_attributes=("book.blob"),
        )
def test_exclude_field_attributes_invalid_attribute():
    with pytest.raises(InvalidFieldAttributeError) as e:
        serializer.dumps(
            {
                "book": {
                    "title": "The Grapes of Wrath",
                    "author": "John Steinbeck"
                }
            },
            exclude_field_attributes=("book.blob", ),
        )

    assert (
        e.exconly() ==
        "django_unicorn.serializer.InvalidFieldAttributeError: Cannot resolve 'blob'. Choices on 'book' are: title, author"
    )
def test_model_many_to_many(django_assert_num_queries):
    flavor_one = Flavor(name="name1", label="label1")
    flavor_one.save()

    taste1 = Taste(name="Bitter1")
    taste1.save()
    taste2 = Taste(name="Bitter2")
    taste2.save()
    taste3 = Taste(name="Bitter3")
    taste3.save()

    flavor_one.taste_set.add(taste1)
    flavor_one.taste_set.add(taste2)
    flavor_one.taste_set.add(taste3)

    with django_assert_num_queries(2):
        actual = serializer.dumps(flavor_one)

    expected = {
        "name": "name1",
        "label": "label1",
        "parent": None,
        "float_value": None,
        "decimal_value": None,
        "uuid": str(flavor_one.uuid),
        "datetime": None,
        "date": None,
        "time": None,
        "duration": None,
        "pk": 1,
        "taste_set": [taste1.pk, taste2.pk, taste3.pk],
        "origins": [],
    }

    assert expected == json.loads(actual)
示例#4
0
def test_simple_model():
    simple_test_model = SimpleTestModel(id=1, name="abc")
    expected = '{"simple_test_model":{"name":"abc","pk":1}}'

    actual = serializer.dumps({"simple_test_model": simple_test_model})

    assert expected == actual
示例#5
0
    def get_data(self):
        try:
            serialized_value = loads(dumps(self.value))
            serialized_args = loads(dumps(self.args))
            serialized_kwargs = loads(dumps(self.kwargs))

            return {
                "method": self.method_name,
                "args": serialized_args,
                "kwargs": serialized_kwargs,
                "value": serialized_value,
            }
        except Exception as e:
            logger.exception(e)

        return {}
示例#6
0
def test_list():
    expected = '{"name":["abc","def"]}'
    actual = serializer.dumps({"name": [
        "abc",
        "def",
    ]})

    assert expected == actual
示例#7
0
def test_nested_list_float_less_complicated():
    expected = '{"another":[{"great":"1.0","ok":["1.6","0.0",4]}]}'
    actual = serializer.dumps({
        "another": [{
            "great": 1.0,
            "ok": [1.6, 0.0, 4]
        }],
    })

    assert expected == actual
示例#8
0
def test_model_foreign_key():
    test_model_one = ComplicatedTestModel(id=1, name="abc")
    test_model_two = ComplicatedTestModel(id=2,
                                          name="def",
                                          parent=test_model_one)
    expected = '{"test_model_two":{"name":"def","parent":1,"pk":2}}'

    actual = serializer.dumps({"test_model_two": test_model_two})

    assert expected == actual
示例#9
0
def test_pydantic():
    from pydantic import BaseModel

    class Book(BaseModel):
        title = "The Grapes of Wrath"
        author = "John Steinbeck"

    expected = '{"title":"The Grapes of Wrath","author":"John Steinbeck"}'
    actual = serializer.dumps(Book())

    assert expected == actual
def test_exclude_field_attributes_none():
    expected = '{"book":{"title":"The Grapes of Wrath","author":"John Steinbeck"}}'

    actual = serializer.dumps(
        {"book": {
            "title": "The Grapes of Wrath",
            "author": "John Steinbeck"
        }},
        exclude_field_attributes=None,
    )

    assert expected == actual
def test_exclude_field_attributes_no_fix_floats():
    expected = '{"book":{"title":"The Grapes of Wrath"}}'

    actual = serializer.dumps(
        {"book": {
            "title": "The Grapes of Wrath",
            "author": "John Steinbeck"
        }},
        fix_floats=False,
        exclude_field_attributes=("book.author", ),
    )

    assert expected == actual
示例#12
0
    def validate_checksum(self):
        """
        Validates that the checksum in the request matches the data.

        Returns:
            Raises `AssertionError` if the checksums don't match.
        """
        checksum = self.body.get("checksum")
        assert checksum, "Missing checksum"

        generated_checksum = generate_checksum(
            dumps(self.data, fix_floats=False))
        assert checksum == generated_checksum, "Checksum does not match"
示例#13
0
def test_dumps_queryset(db):
    flavor_one = Flavor(name="name1", label="label1")
    flavor_one.save()

    flavor_two = Flavor(name="name2", label="label2", parent=flavor_one)
    flavor_two.save()

    flavors = Flavor.objects.all()

    expected = '{"flavors":[{"name":"name1","label":"label1","parent":null,"float_value":null,"decimal_value":null,"pk":1},{"name":"name2","label":"label2","parent":1,"float_value":null,"decimal_value":null,"pk":2}]}'
    actual = serializer.dumps({"flavors": flavors})

    assert expected == actual
示例#14
0
def test_model():
    flavor = Flavor(name="first-flavor")
    flavor.save()

    str_data = dumps({"flavor": flavor})
    data = loads(str_data)
    flavor_data = data["flavor"]

    actual = _construct_model(Flavor, flavor_data)

    assert actual.pk == flavor.id
    assert actual.name == flavor.name
    assert actual.parent is None
示例#15
0
def test_nested_list_float_complicated():
    expected = '{"name":{"blob":[1,2,"0.0"]},"more":["1.9",2,5],"another":[{"great":"1.0","ok":["1.6","0.0",4]}]}'
    actual = serializer.dumps({
        "name": {
            "blob": [1, 2, 0.0]
        },
        "more": [1.9, 2, 5],
        "another": [{
            "great": 1.0,
            "ok": [1.6, 0.0, 4]
        }],
    })

    assert expected == actual
def test_exclude_field_attributes_nested():
    expected = '{"classic":{"book":{"title":"The Grapes of Wrath"}}}'

    actual = serializer.dumps(
        {
            "classic": {
                "book": {
                    "title": "The Grapes of Wrath",
                    "author": "John Steinbeck"
                }
            }
        },
        exclude_field_attributes=("classic.book.author", ),
    )

    assert expected == actual
示例#17
0
def test_model_foreign_key():
    parent = Flavor(name="parent-flavor")
    parent.save()
    flavor = Flavor(name="first-flavor", parent=parent)
    flavor.save()

    str_data = dumps({"flavor": flavor})
    data = loads(str_data)
    flavor_data = data["flavor"]

    actual = _construct_model(Flavor, flavor_data)

    assert actual.pk == flavor.id
    assert actual.name == flavor.name
    assert actual.parent.pk == parent.id
    assert actual.parent.name == parent.name
示例#18
0
def test_queryset_values():
    test_component = FakeComponent(component_name="test", component_id="asdf")
    assert test_component.flavors.count() == 0

    flavor = Flavor(name="values-first-flavor")
    flavor.save()

    flavors = Flavor.objects.filter(name="values-first-flavor").values("uuid")
    str_data = dumps({"flavors": flavors})
    data = loads(str_data)
    flavors_data = data["flavors"]

    set_property_from_data(test_component, "flavors", flavors_data)

    assert test_component.flavors.count() == 1
    assert test_component.flavors[0].uuid == str(flavor.uuid)
    assert test_component.flavors[0].id is None
def test_dumps_queryset(db):
    flavor_one = Flavor(name="name1", label="label1")
    flavor_one.save()

    flavor_two = Flavor(name="name2", label="label2", parent=flavor_one)
    flavor_two.save()

    flavors = Flavor.objects.all()

    expected_data = {
        "flavors": [
            {
                "name": "name1",
                "label": "label1",
                "parent": None,
                "float_value": None,
                "decimal_value": None,
                "uuid": str(flavor_one.uuid),
                "date": None,
                "datetime": None,
                "time": None,
                "duration": None,
                "pk": 1,
                "taste_set": [],
                "origins": [],
            },
            {
                "name": "name2",
                "label": "label2",
                "parent": 1,
                "float_value": None,
                "decimal_value": None,
                "uuid": str(flavor_two.uuid),
                "date": None,
                "datetime": None,
                "time": None,
                "duration": None,
                "pk": 2,
                "taste_set": [],
                "origins": [],
            },
        ]
    }

    actual = serializer.dumps({"flavors": flavors})
    assert expected_data == json.loads(actual)
def test_model_many_to_many_with_excludes(django_assert_num_queries):
    flavor_one = Flavor(name="name1", label="label1")
    flavor_one.save()

    taste1 = Taste(name="Bitter1")
    taste1.save()
    taste2 = Taste(name="Bitter2")
    taste2.save()
    taste3 = Taste(name="Bitter3")
    taste3.save()

    flavor_one.taste_set.add(taste1)
    flavor_one.taste_set.add(taste2)
    flavor_one.taste_set.add(taste3)

    flavor_one = Flavor.objects.prefetch_related(
        "taste_set", "origins").get(pk=flavor_one.pk)

    # This shouldn't make any database calls because of the prefetch_related
    with django_assert_num_queries(0):
        actual = serializer.dumps(
            {"flavor": flavor_one},
            exclude_field_attributes=(
                "flavor.taste_set",
                "flavor.origins",
            ),
        )

    expected = {
        "flavor": {
            "name": "name1",
            "label": "label1",
            "parent": None,
            "float_value": None,
            "decimal_value": None,
            "uuid": str(flavor_one.uuid),
            "datetime": None,
            "date": None,
            "time": None,
            "duration": None,
            "pk": 1,
        }
    }

    assert expected == json.loads(actual)
示例#21
0
def test_model_with_duration_as_string(db):
    duration = "-1 day, 19:00:00"
    flavor = Flavor(name="name1", duration=duration)

    expected = {
        "flavor": {
            "name": "name1",
            "label": "",
            "parent": None,
            "float_value": None,
            "decimal_value": None,
            "uuid": str(flavor.uuid),
            "date": None,
            "datetime": None,
            "time": None,
            "duration": "-1 19:00:00",
            "pk": None,
        }
    }

    actual = serializer.dumps({"flavor": flavor})
    assert dicts_equal(expected, json.loads(actual))
示例#22
0
def test_model_with_datetime_as_string(db):
    datetime = now().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
    flavor = Flavor(name="name1", datetime=datetime)

    expected = {
        "flavor": {
            "name": "name1",
            "label": "",
            "parent": None,
            "float_value": None,
            "decimal_value": None,
            "uuid": str(flavor.uuid),
            "date": None,
            "datetime": datetime,
            "time": None,
            "duration": None,
            "pk": None,
        }
    }

    actual = serializer.dumps({"flavor": flavor})
    assert dicts_equal(expected, json.loads(actual))
示例#23
0
def _process_component_request(
    request: HttpRequest, component_request: ComponentRequest
) -> Dict:
    """
    Process a `ComponentRequest`:
        1. construct a Component view
        2. set all of the properties on the view from the data
        3. execute the type
            - update the properties based on the payload for "syncInput"
            - call the method specified for "callMethod"
        4. validate any fields specified in a Django form
        5. construct a `dict` that will get returned in a `JsonResponse` later on

    Args:
        param request: HttpRequest for the function-based view.
        param: component_request: Component request to process.

    Returns:
        `dict` with the following structure:
        {
            "id": component_id,
            "dom": html,  // re-rendered version of the component after actions in the payload are completed
            "data": {},  // updated data after actions in the payload are completed
            "errors": {},  // form validation errors
            "return": {}, // optional return value from an executed action
            "parent": {},  // optional representation of the parent component
        }
    """
    component = UnicornView.create(
        component_id=component_request.id,
        component_name=component_request.name,
        request=request,
    )

    # Get a deepcopy of the data passed in to determine what fields are updated later
    original_data = copy.deepcopy(component_request.data)

    # Set component properties based on request data
    for (property_name, property_value) in component_request.data.items():
        set_property_from_data(component, property_name, property_value)
    component.hydrate()

    validate_all_fields = False
    is_reset_called = False
    is_refresh_called = False
    return_data = None
    partials = []

    for action in component_request.action_queue:
        if action.partial:
            partials.append(action.partial)
        else:
            partials = action.partials

        if action.action_type == "syncInput":
            sync_input.handle(component_request, component, action.payload)
        elif action.action_type == "callMethod":
            (
                component,
                _is_refresh_called,
                _is_reset_called,
                _validate_all_fields,
                return_data,
            ) = call_method.handle(component_request, component, action.payload)

            is_refresh_called = is_refresh_called | _is_refresh_called
            is_reset_called = is_reset_called | _is_reset_called
            validate_all_fields = validate_all_fields | _validate_all_fields
        else:
            raise UnicornViewError(f"Unknown action_type '{action.action_type}'")

    component.complete()

    # Re-load frontend context variables to deal with non-serializable properties
    component_request.data = orjson.loads(component.get_frontend_context_variables())

    # Get set of attributes that should be marked as `safe`
    safe_fields = []
    if hasattr(component, "Meta") and hasattr(component.Meta, "safe"):
        if isinstance(component.Meta.safe, Sequence):
            for field_name in component.Meta.safe:
                if field_name in component._attributes().keys():
                    safe_fields.append(field_name)

    # Mark safe attributes as such before rendering
    for field_name in safe_fields:
        value = getattr(component, field_name)
        if isinstance(value, str):
            setattr(component, field_name, mark_safe(value))

    # Send back all available data for reset or refresh actions
    updated_data = component_request.data

    if not is_reset_called:
        if not is_refresh_called:
            updated_data = {}

            for key, value in original_data.items():
                if value != component_request.data.get(key):
                    updated_data[key] = component_request.data.get(key)

        if validate_all_fields:
            component.validate()
        else:
            component.validate(model_names=list(updated_data.keys()))

    # Pass the current request so that it can be used inside the component template
    rendered_component = component.render(request=request)
    component.rendered(rendered_component)

    cache = caches[get_cache_alias()]

    try:
        cache.set(component.component_cache_key, get_cacheable_component(component))
    except UnicornCacheError as e:
        logger.warning(e)

    partial_doms = []

    if partials and all(partials):
        soup = BeautifulSoup(rendered_component, features="html.parser")

        for partial in partials:
            partial_found = False
            only_id = False
            only_key = False

            target = partial.get("target")

            if not target:
                target = partial.get("key")

                if target:
                    only_key = True

            if not target:
                target = partial.get("id")

                if target:
                    only_id = True

            assert target, "Partial target is required"

            if not only_id:
                for element in soup.find_all():
                    if (
                        "unicorn:key" in element.attrs
                        and element.attrs["unicorn:key"] == target
                    ):
                        partial_doms.append({"key": target, "dom": str(element)})
                        partial_found = True
                        break

            if not partial_found and not only_key:
                for element in soup.find_all():
                    if "id" in element.attrs and element.attrs["id"] == target:
                        partial_doms.append({"id": target, "dom": str(element)})
                        partial_found = True
                        break

    res = {
        "id": component_request.id,
        "data": updated_data,
        "errors": component.errors,
        "calls": component.calls,
        "checksum": generate_checksum(orjson.dumps(component_request.data)),
    }

    if partial_doms:
        res.update({"partials": partial_doms})
    else:
        hash = generate_checksum(rendered_component)

        if (
            component_request.hash == hash
            and (not return_data or not return_data.value)
            and not component.calls
        ):
            raise RenderNotModified()

        # Make sure that partials with comments or blank lines before the root element only return the root element
        soup = BeautifulSoup(rendered_component, features="html.parser")
        rendered_component = str(get_root_element(soup))

        res.update(
            {
                "dom": rendered_component,
                "hash": hash,
            }
        )

    if return_data:
        res.update(
            {
                "return": return_data.get_data(),
            }
        )

        if return_data.redirect:
            res.update(
                {
                    "redirect": return_data.redirect,
                }
            )

        if return_data.poll:
            res.update(
                {
                    "poll": return_data.poll,
                }
            )

    parent_component = component.parent

    if parent_component:
        # TODO: Should parent_component.hydrate() be called?
        parent_frontend_context_variables = loads(
            parent_component.get_frontend_context_variables()
        )
        parent_checksum = generate_checksum(dumps(parent_frontend_context_variables))

        parent = {
            "id": parent_component.component_id,
            "checksum": parent_checksum,
        }

        if not partial_doms:
            parent_dom = parent_component.render()
            component.parent_rendered(parent_dom)

            try:
                cache.set(
                    parent_component.component_cache_key,
                    get_cacheable_component(parent_component),
                )
            except UnicornCacheError as e:
                logger.warning(e)

            parent.update(
                {
                    "dom": parent_dom,
                    "data": parent_frontend_context_variables,
                    "errors": parent_component.errors,
                }
            )

        res.update({"parent": parent})

    return res
示例#24
0
def test_string():
    expected = '{"name":"abc"}'
    actual = serializer.dumps({"name": "abc"})

    assert expected == actual
示例#25
0
def test_int():
    expected = '{"name":123}'
    actual = serializer.dumps({"name": 123})

    assert expected == actual
示例#26
0
def test_nested_list_float():
    expected = '{"name":{"blob":[1,2,"0.0"]}}'
    actual = serializer.dumps({"name": {"blob": [1, 2, 0.0]}})

    assert expected == actual
示例#27
0
def test_dict_float():
    expected = '{"name":{"another":"0.0"}}'
    actual = serializer.dumps({"name": {"another": 0.0}})

    assert expected == actual
示例#28
0
def test_float():
    expected = '{"name":"0.0"}'
    actual = serializer.dumps({"name": 0.0})

    assert expected == actual
示例#29
0
def test_decimal():
    expected = '{"name":"123.1"}'
    actual = serializer.dumps({"name": Decimal("123.1")})

    assert expected == actual