content: import json
  import uuid
  from datetime import timedelta
  from decimal import Decimal
  from types import MappingProxyType
  from typing import Dict
  
  import pytest
  from django.db import models
  from django.utils.timezone import now
  from pydantic import BaseModel
  
  from django_unicorn import serializer
  from django_unicorn.serializer import InvalidFieldAttributeError, InvalidFieldNameError
  from django_unicorn.utils import dicts_equal
  from example.coffee.models import Flavor, NewFlavor, Taste
  
  
  class SimpleTestModel(models.Model):
      name = models.CharField(max_length=10)
  
      class Meta:
          app_label = "tests"
  
  
  class SubclassSimpleTestModel(SimpleTestModel):
      subclass_name = models.CharField(max_length=10)
  
      class Meta:
          app_label = "tests"
  
  
  class ComplicatedTestModel(models.Model):
      name = models.CharField(max_length=10)
      datetime = models.DateTimeField(auto_now=True, auto_now_add=True)
      float_value = models.FloatField(default=0.583)
      decimal_value = models.DecimalField(max_digits=10, decimal_places=2, default=0.984)
      uuid = models.UUIDField(default=uuid.uuid4)
      date = models.DateField(auto_now=True, auto_now_add=True)
      time = models.TimeField(auto_now=True, auto_now_add=True)
      duration = models.DurationField(default="-1 day, 19:00:00")
  
      class Meta:
          app_label = "tests"
  
  
  class SubclassComplicatedTestModel(ComplicatedTestModel):
      subclass_name = models.CharField(max_length=10)
  
      class Meta:
          app_label = "tests"
  
  
  class ForeignKeyTestModel(models.Model):
      name = models.CharField(max_length=10)
      parent = models.ForeignKey("self", blank=True, null=True, on_delete=models.SET_NULL)
  
      class Meta:
          app_label = "tests"
  
  
  class SubclassForeignKeyTestModel(ForeignKeyTestModel):
      subclass_name = models.CharField(max_length=10)
  
      class Meta:
          app_label = "tests"
  
  
  class ManyToManyTestModel(models.Model):
      name = models.CharField(max_length=10)
      parents = models.ManyToManyField("self")
  
      class Meta:
          app_label = "tests"
  
  
  class SubclassManyToManyTestModel(ManyToManyTestModel):
      subclass_name = models.CharField(max_length=10)
  
      class Meta:
          app_label = "tests"
  
  
  def assert_dicts(expected: Dict, serialized_dumps: str) -> None:
      actual = json.loads(serialized_dumps)
  
      assert dicts_equal(expected, actual)
  
  
  def test_int():
      expected = '{"name":123}'
      actual = serializer.dumps({"name": 123})
  
      assert expected == actual
  
  
  def test_decimal():
      expected = '{"name":"123.1"}'
      actual = serializer.dumps({"name": Decimal("123.1")})
  
      assert expected == actual
  
  
  def test_mapping_proxy_type():
      expected = '{"name":{"id":"123.1"}}'
      actual = serializer.dumps({"name": MappingProxyType({"id": Decimal("123.1")})})
  
      assert expected == actual
  
  
  def test_string():
      expected = '{"name":"abc"}'
      actual = serializer.dumps({"name": "abc"})
  
      assert expected == actual
  
  
  def test_list():
      expected = '{"name":["abc","def"]}'
      actual = serializer.dumps(
          {
              "name": [
                  "abc",
                  "def",
              ]
          }
      )
  
      assert expected == actual
  
  
  def test_simple_model():
      expected = {"name": "abc", "pk": 1}
  
      simple_test_model = SimpleTestModel(id=1, name="abc")
      actual = serializer.dumps(simple_test_model)
  
      assert_dicts(expected, actual)
  
  
  def test_subclass_simple_model():
      expected = {"subclass_name": "def", "pk": 2, "name": "abc"}
  
      subclass_simple_test_model = SubclassSimpleTestModel(id=2, name="abc", subclass_name="def")
      actual = serializer.dumps(subclass_simple_test_model)
  
      assert_dicts(expected, actual)
  
  
  def test_complicated_model():
      now_dt = now()
      duration = timedelta(days=-1, seconds=68400)
  
      model = ComplicatedTestModel(
          id=2,
          name="abc",
          datetime=now_dt,
          date=now_dt.date(),
          time=now_dt.time(),
          duration=duration,
      )
  
      expected = {
          "date": str(model.date),
          "datetime": model.datetime.isoformat(timespec="milliseconds").replace("+00:00", "Z"),
          "decimal_value": "0.984",
          "duration": "-1 19:00:00",
          "float_value": "0.583",
          "name": "abc",
          "pk": 2,
          "time": str(model.time)[:-3],
          "uuid": str(model.uuid),
      }
  
      actual = serializer.dumps(model)
  
      assert_dicts(expected, actual)
  
  
  def test_subclass_complicated_model():
      now_dt = now()
      duration = timedelta(days=-1, seconds=68400)
  
      model = SubclassComplicatedTestModel(
          id=2,
          name="abc",
          subclass_name="def",
          datetime=now_dt,
          date=now_dt.date(),
          time=now_dt.time(),
          duration=duration,
      )
  
      expected = {
          "subclass_name": "def",
          "pk": 2,
          "name": "abc",
          "datetime": model.datetime.isoformat(timespec="milliseconds").replace("+00:00", "Z"),
          "float_value": "0.583",
          "decimal_value": "0.984",
          "uuid": str(model.uuid),
          "date": str(model.date),
          "time": str(model.time)[:-3],
          "duration": "-1 19:00:00",
      }
  
      actual = serializer.dumps(model)
  
      assert_dicts(expected, actual)
  
  
  def test_model_with_timedelta(db):  # noqa: ARG001
      now_dt = now()
      duration = timedelta(days=-1, seconds=68400)
  
      flavor = Flavor(
          id=8,
          name="name1",
          datetime=now_dt,
          date=now_dt.date(),
          time=now_dt.time(),
          duration=duration,
      )
  
      expected = {
          "flavor": {
              "name": "name1",
              "label": "",
              "parent": None,
              "float_value": None,
              "decimal_value": None,
              "uuid": str(flavor.uuid),
              "date": str(now_dt.date()),
              "datetime": now_dt.isoformat(timespec="milliseconds").replace("+00:00", "Z"),
              "time": str(now_dt.time())[:-3],
              "duration": "-1 19:00:00",
              "pk": 8,
              "taste_set": [],
              "origins": [],
          }
      }
  
      actual = serializer.dumps({"flavor": flavor})
  
      assert_dicts(expected, actual)
  
  
  def test_model_with_datetime_as_string(db):  # noqa: ARG001
      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,
              "taste_set": [],
              "origins": [],
          }
      }
  
      actual = serializer.dumps({"flavor": flavor})
  
      assert_dicts(expected, actual)
  
  
  def test_model_with_time_as_string(db):  # noqa: ARG001
      time = now().strftime("%H:%M:%S.%f")[:-3]
      flavor = Flavor(name="name1", time=time)
  
      expected = {
          "flavor": {
              "name": "name1",
              "label": "",
              "parent": None,
              "float_value": None,
              "decimal_value": None,
              "uuid": str(flavor.uuid),
              "date": None,
              "datetime": None,
              "time": time,
              "duration": None,
              "pk": None,
              "taste_set": [],
              "origins": [],
          }
      }
  
      actual = serializer.dumps({"flavor": flavor})
  
      assert_dicts(expected, actual)
  
  
  def test_model_with_duration_as_string(db):  # noqa: ARG001
      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,
              "taste_set": [],
              "origins": [],
          }
      }
  
      actual = serializer.dumps({"flavor": flavor})
  
      assert_dicts(expected, actual)
  
  
  def test_model_foreign_key():
      test_model_one = ForeignKeyTestModel(id=1, name="abc")
      test_model_two = ForeignKeyTestModel(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_dicts(expected, actual)
  
  
  def test_model_foreign_key_subclass():
      test_model_one = ForeignKeyTestModel(id=1, name="abc")
      test_model_two = SubclassForeignKeyTestModel(id=2, name="def", subclass_name="ghi", parent=test_model_one)
      expected = {"name": "def", "parent": 1, "pk": 2, "subclass_name": "ghi"}
  
      actual = serializer.dumps(test_model_two)
  
      assert_dicts(expected, actual)
  
  
  @pytest.mark.django_db
  def test_model_inherited_model_with_many_to_many_and_foreign_key():
      flavor_one = Flavor(id=3, name="name0")
      flavor_one.save()
  
      flavor_two = NewFlavor(id=4, new_name="new_name_1", name="name_1", parent=flavor_one)
      flavor_two.save()
  
      taste1 = Taste(name="Bitter1")
      taste1.save()
      flavor_two.taste_set.add(taste1)
  
      expected = {
          "new_name": "new_name_1",
          "pk": 4,
          "taste_set": [1],
          "origins": [],
          "name": "name_1",
          "label": "",
          "parent": 3,
          "float_value": "null",
          "decimal_value": "null",
          "uuid": str(flavor_two.uuid),
          "datetime": "null",
          "date": "null",
          "time": "null",
          "duration": "null",
      }
  
      actual = serializer.dumps(flavor_two)
  
      assert_dicts(expected, actual)
  
  
  def test_model_foreign_key_recursive_parent():
      test_model_one = ForeignKeyTestModel(id=1, name="abc")
      test_model_two = ForeignKeyTestModel(id=2, name="def", parent=test_model_one)
      test_model_one.parent = test_model_two
      expected = '{"test_model_two":{"name":"def","parent":1,"pk":2}}'
  
      actual = serializer.dumps({"test_model_two": test_model_two})
  
      assert expected == actual
  
  
  @pytest.mark.django_db
  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 = json.loads(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 dicts_equal(expected, actual)
  
  
  @pytest.mark.django_db
  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",
              ),
          )
          actual = json.loads(actual)
  
      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 dicts_equal(expected, actual)
  
  
  @pytest.mark.django_db
  def test_dumps_queryset(db):  # noqa: ARG001
      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": 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 = json.loads(serializer.dumps({"flavors": flavors}))
      assert dicts_equal(expected, actual)
  
  
  def test_get_model_dict():
      flavor_one = Flavor(name="name1", label="label1")
      actual = serializer._get_model_dict(flavor_one)
  
      expected = {
          "pk": None,
          "name": "name1",
          "label": "label1",
          "parent": None,
          "decimal_value": None,
          "float_value": None,
          "uuid": str(flavor_one.uuid),
          "date": None,
          "datetime": None,
          "time": None,
          "duration": None,
          "taste_set": [],
          "origins": [],
      }
  
      assert dicts_equal(expected, actual)
  
  
  @pytest.mark.django_db
  def test_get_model_dict_many_to_many_is_referenced(django_assert_num_queries):
      flavor_one = Flavor(name="name1", label="label1")
      flavor_one.save()
  
      taste1 = Taste(name="Bitter")
      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)
  
      expected = {
          "pk": 1,
          "name": "name1",
          "label": "label1",
          "parent": None,
          "decimal_value": None,
          "float_value": None,
          "uuid": str(flavor_one.uuid),
          "date": None,
          "datetime": None,
          "time": None,
          "duration": None,
          "taste_set": [taste1.pk, taste2.pk, taste3.pk],
          "origins": [],
      }
  
      flavor_one = Flavor.objects.filter(id=flavor_one.id).first()
  
      with django_assert_num_queries(2):
          actual = serializer._get_model_dict(flavor_one)
  
      assert dicts_equal(expected, actual)
  
  
  @pytest.mark.django_db
  def test_get_model_dict_many_to_many_is_referenced_prefetched(
      django_assert_num_queries,
  ):
      flavor_one = Flavor(name="name1", label="label1")
      flavor_one.save()
  
      taste1 = Taste(name="Bitter")
      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)
  
      expected = {
          "pk": 1,
          "name": "name1",
          "label": "label1",
          "parent": None,
          "decimal_value": None,
          "float_value": None,
          "uuid": str(flavor_one.uuid),
          "date": None,
          "datetime": None,
          "time": None,
          "duration": None,
          "taste_set": [taste1.pk, taste2.pk, taste3.pk],
          "origins": [],
      }
  
      flavor_one = Flavor.objects.prefetch_related("taste_set").filter(id=flavor_one.id).first()
  
      # prefetch_related should reduce the database calls
      with django_assert_num_queries(1):
          actual = serializer._get_model_dict(flavor_one)
  
      assert dicts_equal(expected, actual)
  
  
  @pytest.mark.django_db
  def test_get_model_dict_many_to_many_references_model():
      taste = Taste(name="Bitter")
      taste.save()
      flavor_one = Flavor(name="name1", label="label1")
      flavor_one.save()
      flavor_one.taste_set.add(taste)
      actual = serializer._get_model_dict(taste)
  
      expected = {"name": taste.name, "flavor": [flavor_one.pk], "pk": taste.pk}
  
      assert dicts_equal(expected, actual)
  
  
  def test_float():
      expected = '{"name":"0.0"}'
      actual = serializer.dumps({"name": 0.0})
  
      assert expected == actual
  
  
  def test_dict_float():
      expected = '{"name":{"another":"0.0"}}'
      actual = serializer.dumps({"name": {"another": 0.0}})
  
      assert expected == actual
  
  
  def test_list_float():
      expected = '{"name":[1,2,"0.0"]}'
      actual = serializer.dumps({"name": [1, 2, 0.0]})
  
      assert expected == actual
  
  
  def test_nested_list_float():
      expected = '{"name":{"blob":[1,2,"0.0"]}}'
      actual = serializer.dumps({"name": {"blob": [1, 2, 0.0]}})
  
      assert expected == actual
  
  
  def test_nested_list_float_complicated():
      expected = '{"another":[{"great":"1.0","ok":["1.6","0.0",4]}],"more":["1.9",2,5],"name":{"blob":[1,2,"0.0"]}}'
      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_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
  
  
  def test_pydantic():
      class Book(BaseModel):
          title = "The Grapes of Wrath"
          author = "John Steinbeck"
  
      expected = '{"author":"John Steinbeck","title":"The Grapes of Wrath"}'
      actual = serializer.dumps(Book())
  
      assert expected == actual
  
  
  def test_exclude_field_attributes():
      expected = '{"book":{"title":"The Grapes of Wrath"}}'
  
      actual = serializer.dumps(
          {"book": {"title": "The Grapes of Wrath", "author": "John Steinbeck"}},
          exclude_field_attributes=("book.author",),
      )
  
      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
  
  
  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
  
  
  def test_exclude_field_attributes_invalid_field_attribute():
      expected = '{"book":{"author":"John Steinbeck","title":"The Grapes of Wrath"}}'
  
      actual = serializer.dumps(
          {"book": {"title": "The Grapes of Wrath", "author": "John Steinbeck"}},
          exclude_field_attributes=("blob",),
      )
  
      assert expected == actual
  
  
  def test_exclude_field_attributes_empty_string():
      expected = '{"book":{"author":"John Steinbeck","title":"The Grapes of Wrath"}}'
  
      actual = serializer.dumps(
          {"book": {"title": "The Grapes of Wrath", "author": "John Steinbeck"}},
          exclude_field_attributes=("",),
      )
  
      assert expected == actual
  
  
  def test_exclude_field_attributes_none():
      expected = '{"book":{"author":"John Steinbeck","title":"The Grapes of Wrath"}}'
  
      actual = serializer.dumps(
          {"book": {"title": "The Grapes of Wrath", "author": "John Steinbeck"}},
          exclude_field_attributes=None,
      )
  
      assert expected == actual
  
  
  def test_exclude_field_attributes_invalid_name():
      with pytest.raises(InvalidFieldNameError) as e:
          serializer.dumps(
              {"book": {"title": "The Grapes of Wrath", "author": "John Steinbeck"}},
              exclude_field_attributes=("blob.monster",),
          )
  
      assert e.exconly() == "django_unicorn.serializer.InvalidFieldNameError: Cannot resolve 'blob'. Choices are: book"
  
  
  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"  # noqa: E501
      )
  
  
  def test_exclude_field_attributes_invalid_type():
      with pytest.raises(AssertionError):
          serializer.dumps(
              {"book": {"title": "The Grapes of Wrath", "author": "John Steinbeck"}},
              exclude_field_attributes=("book.blob"),
          )
  
  
  def test_dictionary_with_int_keys_as_strings():
      # Browsers will sort a dictionary that has stringified integers as if they the keys
      # were integers which messes up checksum calculation
      expected = '{"1":"a","2":"b","4":"c","11":"d","24":"e"}'
  
      actual = serializer.dumps(
          {
              "11": "d",
              "4": "c",
              "1": "a",
              "24": "e",
              "2": "b",
          }
      )
  
      assert expected == actual
  
  
  def test_dictionary_with_int_keys_as_strings_no_sort():
      expected = '{"11":"d","4":"c","1":"a","24":"e","2":"b"}'
  
      actual = serializer.dumps(
          {
              "11": "d",
              "4": "c",
              "1": "a",
              "24": "e",
              "2": "b",
          },
          sort_dict=False,
      )
  
      assert expected == actual
  