123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- import datetime
- import decimal
- import ipaddress
- import uuid
- from django.db import models
- from django.template import Context, Template
- from django.test import SimpleTestCase
- from django.utils.deprecation import RemovedInDjango60Warning
- from django.utils.functional import Promise
- from django.utils.translation import gettext_lazy as _
- from django.utils.version import PY311
- class Suit(models.IntegerChoices):
- DIAMOND = 1, _("Diamond")
- SPADE = 2, _("Spade")
- HEART = 3, _("Heart")
- CLUB = 4, _("Club")
- class YearInSchool(models.TextChoices):
- FRESHMAN = "FR", _("Freshman")
- SOPHOMORE = "SO", _("Sophomore")
- JUNIOR = "JR", _("Junior")
- SENIOR = "SR", _("Senior")
- GRADUATE = "GR", _("Graduate")
- class Vehicle(models.IntegerChoices):
- CAR = 1, "Carriage"
- TRUCK = 2
- JET_SKI = 3
- __empty__ = _("(Unknown)")
- class Gender(models.TextChoices):
- MALE = "M"
- FEMALE = "F"
- NOT_SPECIFIED = "X"
- __empty__ = "(Undeclared)"
- class ChoicesTests(SimpleTestCase):
- def test_integerchoices(self):
- self.assertEqual(
- Suit.choices, [(1, "Diamond"), (2, "Spade"), (3, "Heart"), (4, "Club")]
- )
- self.assertEqual(Suit.labels, ["Diamond", "Spade", "Heart", "Club"])
- self.assertEqual(Suit.values, [1, 2, 3, 4])
- self.assertEqual(Suit.names, ["DIAMOND", "SPADE", "HEART", "CLUB"])
- self.assertEqual(repr(Suit.DIAMOND), "Suit.DIAMOND")
- self.assertEqual(Suit.DIAMOND.label, "Diamond")
- self.assertEqual(Suit.DIAMOND.value, 1)
- self.assertEqual(Suit["DIAMOND"], Suit.DIAMOND)
- self.assertEqual(Suit(1), Suit.DIAMOND)
- self.assertIsInstance(Suit, type(models.Choices))
- self.assertIsInstance(Suit.DIAMOND, Suit)
- self.assertIsInstance(Suit.DIAMOND.label, Promise)
- self.assertIsInstance(Suit.DIAMOND.value, int)
- def test_integerchoices_auto_label(self):
- self.assertEqual(Vehicle.CAR.label, "Carriage")
- self.assertEqual(Vehicle.TRUCK.label, "Truck")
- self.assertEqual(Vehicle.JET_SKI.label, "Jet Ski")
- def test_integerchoices_empty_label(self):
- self.assertEqual(Vehicle.choices[0], (None, "(Unknown)"))
- self.assertEqual(Vehicle.labels[0], "(Unknown)")
- self.assertIsNone(Vehicle.values[0])
- self.assertEqual(Vehicle.names[0], "__empty__")
- def test_integerchoices_functional_api(self):
- Place = models.IntegerChoices("Place", "FIRST SECOND THIRD")
- self.assertEqual(Place.labels, ["First", "Second", "Third"])
- self.assertEqual(Place.values, [1, 2, 3])
- self.assertEqual(Place.names, ["FIRST", "SECOND", "THIRD"])
- def test_integerchoices_containment(self):
- self.assertIn(Suit.DIAMOND, Suit)
- self.assertIn(1, Suit)
- self.assertNotIn(0, Suit)
- def test_textchoices(self):
- self.assertEqual(
- YearInSchool.choices,
- [
- ("FR", "Freshman"),
- ("SO", "Sophomore"),
- ("JR", "Junior"),
- ("SR", "Senior"),
- ("GR", "Graduate"),
- ],
- )
- self.assertEqual(
- YearInSchool.labels,
- ["Freshman", "Sophomore", "Junior", "Senior", "Graduate"],
- )
- self.assertEqual(YearInSchool.values, ["FR", "SO", "JR", "SR", "GR"])
- self.assertEqual(
- YearInSchool.names,
- ["FRESHMAN", "SOPHOMORE", "JUNIOR", "SENIOR", "GRADUATE"],
- )
- self.assertEqual(repr(YearInSchool.FRESHMAN), "YearInSchool.FRESHMAN")
- self.assertEqual(YearInSchool.FRESHMAN.label, "Freshman")
- self.assertEqual(YearInSchool.FRESHMAN.value, "FR")
- self.assertEqual(YearInSchool["FRESHMAN"], YearInSchool.FRESHMAN)
- self.assertEqual(YearInSchool("FR"), YearInSchool.FRESHMAN)
- self.assertIsInstance(YearInSchool, type(models.Choices))
- self.assertIsInstance(YearInSchool.FRESHMAN, YearInSchool)
- self.assertIsInstance(YearInSchool.FRESHMAN.label, Promise)
- self.assertIsInstance(YearInSchool.FRESHMAN.value, str)
- def test_textchoices_auto_label(self):
- self.assertEqual(Gender.MALE.label, "Male")
- self.assertEqual(Gender.FEMALE.label, "Female")
- self.assertEqual(Gender.NOT_SPECIFIED.label, "Not Specified")
- def test_textchoices_empty_label(self):
- self.assertEqual(Gender.choices[0], (None, "(Undeclared)"))
- self.assertEqual(Gender.labels[0], "(Undeclared)")
- self.assertIsNone(Gender.values[0])
- self.assertEqual(Gender.names[0], "__empty__")
- def test_textchoices_functional_api(self):
- Medal = models.TextChoices("Medal", "GOLD SILVER BRONZE")
- self.assertEqual(Medal.labels, ["Gold", "Silver", "Bronze"])
- self.assertEqual(Medal.values, ["GOLD", "SILVER", "BRONZE"])
- self.assertEqual(Medal.names, ["GOLD", "SILVER", "BRONZE"])
- def test_textchoices_containment(self):
- self.assertIn(YearInSchool.FRESHMAN, YearInSchool)
- self.assertIn("FR", YearInSchool)
- self.assertNotIn("XX", YearInSchool)
- def test_textchoices_blank_value(self):
- class BlankStr(models.TextChoices):
- EMPTY = "", "(Empty)"
- ONE = "ONE", "One"
- self.assertEqual(BlankStr.labels, ["(Empty)", "One"])
- self.assertEqual(BlankStr.values, ["", "ONE"])
- self.assertEqual(BlankStr.names, ["EMPTY", "ONE"])
- def test_invalid_definition(self):
- msg = "'str' object cannot be interpreted as an integer"
- with self.assertRaisesMessage(TypeError, msg):
- class InvalidArgumentEnum(models.IntegerChoices):
- # A string is not permitted as the second argument to int().
- ONE = 1, "X", "Invalid"
- msg = "duplicate values found in <enum 'Fruit'>: PINEAPPLE -> APPLE"
- with self.assertRaisesMessage(ValueError, msg):
- class Fruit(models.IntegerChoices):
- APPLE = 1, "Apple"
- PINEAPPLE = 1, "Pineapple"
- def test_str(self):
- for test in [Gender, Suit, YearInSchool, Vehicle]:
- for member in test:
- with self.subTest(member=member):
- self.assertEqual(str(test[member.name]), str(member.value))
- def test_templates(self):
- template = Template("{{ Suit.DIAMOND.label }}|{{ Suit.DIAMOND.value }}")
- output = template.render(Context({"Suit": Suit}))
- self.assertEqual(output, "Diamond|1")
- def test_property_names_conflict_with_member_names(self):
- with self.assertRaises(AttributeError):
- models.TextChoices("Properties", "choices labels names values")
- def test_label_member(self):
- # label can be used as a member.
- Stationery = models.TextChoices("Stationery", "label stamp sticker")
- self.assertEqual(Stationery.label.label, "Label")
- self.assertEqual(Stationery.label.value, "label")
- self.assertEqual(Stationery.label.name, "label")
- def test_do_not_call_in_templates_member(self):
- # do_not_call_in_templates is not implicitly treated as a member.
- Special = models.IntegerChoices("Special", "do_not_call_in_templates")
- self.assertIn("do_not_call_in_templates", Special.__members__)
- self.assertEqual(
- Special.do_not_call_in_templates.label,
- "Do Not Call In Templates",
- )
- self.assertEqual(Special.do_not_call_in_templates.value, 1)
- self.assertEqual(
- Special.do_not_call_in_templates.name,
- "do_not_call_in_templates",
- )
- def test_do_not_call_in_templates_nonmember(self):
- self.assertNotIn("do_not_call_in_templates", Suit.__members__)
- if PY311:
- self.assertIs(Suit.do_not_call_in_templates, True)
- else:
- # Using @property on an enum does not behave as expected.
- self.assertTrue(Suit.do_not_call_in_templates)
- self.assertIsNot(Suit.do_not_call_in_templates, True)
- self.assertIsInstance(Suit.do_not_call_in_templates, property)
- class Separator(bytes, models.Choices):
- FS = b"\x1c", "File Separator"
- GS = b"\x1d", "Group Separator"
- RS = b"\x1e", "Record Separator"
- US = b"\x1f", "Unit Separator"
- class Constants(float, models.Choices):
- PI = 3.141592653589793, "π"
- TAU = 6.283185307179586, "τ"
- class Set(frozenset, models.Choices):
- A = {1, 2}
- B = {2, 3}
- UNION = A | B
- DIFFERENCE = A - B
- INTERSECTION = A & B
- class MoonLandings(datetime.date, models.Choices):
- APOLLO_11 = 1969, 7, 20, "Apollo 11 (Eagle)"
- APOLLO_12 = 1969, 11, 19, "Apollo 12 (Intrepid)"
- APOLLO_14 = 1971, 2, 5, "Apollo 14 (Antares)"
- APOLLO_15 = 1971, 7, 30, "Apollo 15 (Falcon)"
- APOLLO_16 = 1972, 4, 21, "Apollo 16 (Orion)"
- APOLLO_17 = 1972, 12, 11, "Apollo 17 (Challenger)"
- class DateAndTime(datetime.datetime, models.Choices):
- A = 2010, 10, 10, 10, 10, 10
- B = 2011, 11, 11, 11, 11, 11
- C = 2012, 12, 12, 12, 12, 12
- class MealTimes(datetime.time, models.Choices):
- BREAKFAST = 7, 0
- LUNCH = 13, 0
- DINNER = 18, 30
- class Frequency(datetime.timedelta, models.Choices):
- WEEK = 0, 0, 0, 0, 0, 0, 1, "Week"
- DAY = 1, "Day"
- HOUR = 0, 0, 0, 0, 0, 1, "Hour"
- MINUTE = 0, 0, 0, 0, 1, "Hour"
- SECOND = 0, 1, "Second"
- class Number(decimal.Decimal, models.Choices):
- E = 2.718281828459045, "e"
- PI = "3.141592653589793", "π"
- TAU = decimal.Decimal("6.283185307179586"), "τ"
- class IPv4Address(ipaddress.IPv4Address, models.Choices):
- LOCALHOST = "127.0.0.1", "Localhost"
- GATEWAY = "192.168.0.1", "Gateway"
- BROADCAST = "192.168.0.255", "Broadcast"
- class IPv6Address(ipaddress.IPv6Address, models.Choices):
- LOCALHOST = "::1", "Localhost"
- UNSPECIFIED = "::", "Unspecified"
- class IPv4Network(ipaddress.IPv4Network, models.Choices):
- LOOPBACK = "127.0.0.0/8", "Loopback"
- LINK_LOCAL = "169.254.0.0/16", "Link-Local"
- PRIVATE_USE_A = "10.0.0.0/8", "Private-Use (Class A)"
- class IPv6Network(ipaddress.IPv6Network, models.Choices):
- LOOPBACK = "::1/128", "Loopback"
- UNSPECIFIED = "::/128", "Unspecified"
- UNIQUE_LOCAL = "fc00::/7", "Unique-Local"
- LINK_LOCAL_UNICAST = "fe80::/10", "Link-Local Unicast"
- class CustomChoicesTests(SimpleTestCase):
- def test_labels_valid(self):
- enums = (
- Separator,
- Constants,
- Set,
- MoonLandings,
- DateAndTime,
- MealTimes,
- Frequency,
- Number,
- IPv4Address,
- IPv6Address,
- IPv4Network,
- IPv6Network,
- )
- for choice_enum in enums:
- with self.subTest(choice_enum.__name__):
- self.assertNotIn(None, choice_enum.labels)
- def test_bool_unsupported(self):
- msg = "type 'bool' is not an acceptable base type"
- with self.assertRaisesMessage(TypeError, msg):
- class Boolean(bool, models.Choices):
- pass
- def test_uuid_unsupported(self):
- with self.assertRaises(TypeError):
- class Identifier(uuid.UUID, models.Choices):
- A = "972ce4eb-a95f-4a56-9339-68c208a76f18"
- class ChoicesMetaDeprecationTests(SimpleTestCase):
- def test_deprecation_warning(self):
- from django.db.models import enums
- msg = "ChoicesMeta is deprecated in favor of ChoicesType."
- with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
- enums.ChoicesMeta
- self.assertEqual(ctx.filename, __file__)
|