1. from django import forms
    
  2. from django.contrib import admin
    
  3. from django.contrib.admin.widgets import AutocompleteSelect
    
  4. from django.forms import ModelChoiceField
    
  5. from django.test import TestCase, override_settings
    
  6. from django.utils import translation
    
  7. 
    
  8. from .models import Album, Band, ReleaseEvent, VideoStream
    
  9. 
    
  10. 
    
  11. class AlbumForm(forms.ModelForm):
    
  12.     class Meta:
    
  13.         model = Album
    
  14.         fields = ["band", "featuring"]
    
  15.         widgets = {
    
  16.             "band": AutocompleteSelect(
    
  17.                 Album._meta.get_field("band"),
    
  18.                 admin.site,
    
  19.                 attrs={"class": "my-class"},
    
  20.             ),
    
  21.             "featuring": AutocompleteSelect(
    
  22.                 Album._meta.get_field("featuring"),
    
  23.                 admin.site,
    
  24.             ),
    
  25.         }
    
  26. 
    
  27. 
    
  28. class NotRequiredBandForm(forms.Form):
    
  29.     band = ModelChoiceField(
    
  30.         queryset=Album.objects.all(),
    
  31.         widget=AutocompleteSelect(
    
  32.             Album._meta.get_field("band").remote_field, admin.site
    
  33.         ),
    
  34.         required=False,
    
  35.     )
    
  36. 
    
  37. 
    
  38. class RequiredBandForm(forms.Form):
    
  39.     band = ModelChoiceField(
    
  40.         queryset=Album.objects.all(),
    
  41.         widget=AutocompleteSelect(
    
  42.             Album._meta.get_field("band").remote_field, admin.site
    
  43.         ),
    
  44.         required=True,
    
  45.     )
    
  46. 
    
  47. 
    
  48. class VideoStreamForm(forms.ModelForm):
    
  49.     class Meta:
    
  50.         model = VideoStream
    
  51.         fields = ["release_event"]
    
  52.         widgets = {
    
  53.             "release_event": AutocompleteSelect(
    
  54.                 VideoStream._meta.get_field("release_event"),
    
  55.                 admin.site,
    
  56.             ),
    
  57.         }
    
  58. 
    
  59. 
    
  60. @override_settings(ROOT_URLCONF="admin_widgets.urls")
    
  61. class AutocompleteMixinTests(TestCase):
    
  62.     empty_option = '<option value=""></option>'
    
  63.     maxDiff = 1000
    
  64. 
    
  65.     def test_build_attrs(self):
    
  66.         form = AlbumForm()
    
  67.         attrs = form["band"].field.widget.get_context(
    
  68.             name="my_field", value=None, attrs={}
    
  69.         )["widget"]["attrs"]
    
  70.         self.assertEqual(
    
  71.             attrs,
    
  72.             {
    
  73.                 "class": "my-class admin-autocomplete",
    
  74.                 "data-ajax--cache": "true",
    
  75.                 "data-ajax--delay": 250,
    
  76.                 "data-ajax--type": "GET",
    
  77.                 "data-ajax--url": "/autocomplete/",
    
  78.                 "data-theme": "admin-autocomplete",
    
  79.                 "data-allow-clear": "false",
    
  80.                 "data-app-label": "admin_widgets",
    
  81.                 "data-field-name": "band",
    
  82.                 "data-model-name": "album",
    
  83.                 "data-placeholder": "",
    
  84.                 "lang": "en",
    
  85.             },
    
  86.         )
    
  87. 
    
  88.     def test_build_attrs_no_custom_class(self):
    
  89.         form = AlbumForm()
    
  90.         attrs = form["featuring"].field.widget.get_context(
    
  91.             name="name", value=None, attrs={}
    
  92.         )["widget"]["attrs"]
    
  93.         self.assertEqual(attrs["class"], "admin-autocomplete")
    
  94. 
    
  95.     def test_build_attrs_not_required_field(self):
    
  96.         form = NotRequiredBandForm()
    
  97.         attrs = form["band"].field.widget.build_attrs({})
    
  98.         self.assertJSONEqual(attrs["data-allow-clear"], True)
    
  99. 
    
  100.     def test_build_attrs_required_field(self):
    
  101.         form = RequiredBandForm()
    
  102.         attrs = form["band"].field.widget.build_attrs({})
    
  103.         self.assertJSONEqual(attrs["data-allow-clear"], False)
    
  104. 
    
  105.     def test_get_url(self):
    
  106.         rel = Album._meta.get_field("band")
    
  107.         w = AutocompleteSelect(rel, admin.site)
    
  108.         url = w.get_url()
    
  109.         self.assertEqual(url, "/autocomplete/")
    
  110. 
    
  111.     def test_render_options(self):
    
  112.         beatles = Band.objects.create(name="The Beatles", style="rock")
    
  113.         who = Band.objects.create(name="The Who", style="rock")
    
  114.         # With 'band', a ForeignKey.
    
  115.         form = AlbumForm(initial={"band": beatles.uuid})
    
  116.         output = form.as_table()
    
  117.         selected_option = (
    
  118.             '<option value="%s" selected>The Beatles</option>' % beatles.uuid
    
  119.         )
    
  120.         option = '<option value="%s">The Who</option>' % who.uuid
    
  121.         self.assertIn(selected_option, output)
    
  122.         self.assertNotIn(option, output)
    
  123.         # With 'featuring', a ManyToManyField.
    
  124.         form = AlbumForm(initial={"featuring": [beatles.pk, who.pk]})
    
  125.         output = form.as_table()
    
  126.         selected_option = (
    
  127.             '<option value="%s" selected>The Beatles</option>' % beatles.pk
    
  128.         )
    
  129.         option = '<option value="%s" selected>The Who</option>' % who.pk
    
  130.         self.assertIn(selected_option, output)
    
  131.         self.assertIn(option, output)
    
  132. 
    
  133.     def test_render_options_required_field(self):
    
  134.         """Empty option is present if the field isn't required."""
    
  135.         form = NotRequiredBandForm()
    
  136.         output = form.as_table()
    
  137.         self.assertIn(self.empty_option, output)
    
  138. 
    
  139.     def test_render_options_not_required_field(self):
    
  140.         """Empty option isn't present if the field isn't required."""
    
  141.         form = RequiredBandForm()
    
  142.         output = form.as_table()
    
  143.         self.assertNotIn(self.empty_option, output)
    
  144. 
    
  145.     def test_render_options_fk_as_pk(self):
    
  146.         beatles = Band.objects.create(name="The Beatles", style="rock")
    
  147.         rubber_soul = Album.objects.create(name="Rubber Soul", band=beatles)
    
  148.         release_event = ReleaseEvent.objects.create(
    
  149.             name="Test Target", album=rubber_soul
    
  150.         )
    
  151.         form = VideoStreamForm(initial={"release_event": release_event.pk})
    
  152.         output = form.as_table()
    
  153.         selected_option = (
    
  154.             '<option value="%s" selected>Test Target</option>' % release_event.pk
    
  155.         )
    
  156.         self.assertIn(selected_option, output)
    
  157. 
    
  158.     def test_media(self):
    
  159.         rel = Album._meta.get_field("band").remote_field
    
  160.         base_files = (
    
  161.             "admin/js/vendor/jquery/jquery.min.js",
    
  162.             "admin/js/vendor/select2/select2.full.min.js",
    
  163.             # Language file is inserted here.
    
  164.             "admin/js/jquery.init.js",
    
  165.             "admin/js/autocomplete.js",
    
  166.         )
    
  167.         languages = (
    
  168.             ("de", "de"),
    
  169.             # Subsequent language codes are used when the language code is not
    
  170.             # supported.
    
  171.             ("de-at", "de"),
    
  172.             ("de-ch-1901", "de"),
    
  173.             ("en-latn-us", "en"),
    
  174.             ("nl-nl-x-informal", "nl"),
    
  175.             ("zh-hans-HK", "zh-CN"),
    
  176.             # Language with code 00 does not exist.
    
  177.             ("00", None),
    
  178.             # Language files are case sensitive.
    
  179.             ("sr-cyrl", "sr-Cyrl"),
    
  180.             ("zh-hans", "zh-CN"),
    
  181.             ("zh-hant", "zh-TW"),
    
  182.             (None, None),
    
  183.         )
    
  184.         for lang, select_lang in languages:
    
  185.             with self.subTest(lang=lang):
    
  186.                 if select_lang:
    
  187.                     expected_files = (
    
  188.                         base_files[:2]
    
  189.                         + (("admin/js/vendor/select2/i18n/%s.js" % select_lang),)
    
  190.                         + base_files[2:]
    
  191.                     )
    
  192.                 else:
    
  193.                     expected_files = base_files
    
  194.                 with translation.override(lang):
    
  195.                     self.assertEqual(
    
  196.                         AutocompleteSelect(rel, admin.site).media._js,
    
  197.                         list(expected_files),
    
  198.                     )