widgets.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. # -*- coding: utf-8 -*-
  2. import six
  3. from six.moves import urllib
  4. from django import forms
  5. from django.contrib.admin.sites import site
  6. from django.contrib.admin.widgets import ForeignKeyRawIdWidget
  7. from django.template.loader import render_to_string
  8. from django.templatetags.static import static
  9. from django.urls import reverse
  10. from django.utils.safestring import mark_safe
  11. from django.utils.text import Truncator
  12. class ForeignKeySearchInput(ForeignKeyRawIdWidget):
  13. """
  14. Widget for displaying ForeignKeys in an autocomplete search input
  15. instead in a <select> box.
  16. """
  17. # Set in subclass to render the widget with a different template
  18. widget_template = None
  19. # Set this to the patch of the search view
  20. search_path = None
  21. def _media(self):
  22. js_files = [
  23. static('django_extensions/js/jquery.bgiframe.js'),
  24. static('django_extensions/js/jquery.ajaxQueue.js'),
  25. static('django_extensions/js/jquery.autocomplete.js'),
  26. ]
  27. return forms.Media(
  28. css={'all': (static('django_extensions/css/jquery.autocomplete.css'), )},
  29. js=js_files,
  30. )
  31. media = property(_media)
  32. def label_for_value(self, value):
  33. key = self.rel.get_related_field().name
  34. obj = self.rel.model._default_manager.get(**{key: value})
  35. return Truncator(obj).words(14, truncate='...')
  36. def __init__(self, rel, search_fields, attrs=None):
  37. self.search_fields = search_fields
  38. super().__init__(rel, site, attrs)
  39. def render(self, name, value, attrs=None, renderer=None):
  40. if attrs is None:
  41. attrs = {}
  42. opts = self.rel.model._meta
  43. app_label = opts.app_label
  44. model_name = opts.object_name.lower()
  45. related_url = reverse('admin:%s_%s_changelist' % (app_label, model_name))
  46. if not self.search_path:
  47. self.search_path = urllib.parse.urljoin(related_url, 'foreignkey_autocomplete/')
  48. params = self.url_parameters()
  49. if params:
  50. url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
  51. else:
  52. url = ''
  53. if 'class' not in attrs:
  54. attrs['class'] = 'vForeignKeyRawIdAdminField'
  55. # Call the TextInput render method directly to have more control
  56. output = [forms.TextInput.render(self, name, value, attrs)]
  57. if value:
  58. label = self.label_for_value(value)
  59. else:
  60. label = six.u('')
  61. context = {
  62. 'url': url,
  63. 'related_url': related_url,
  64. 'search_path': self.search_path,
  65. 'search_fields': ','.join(self.search_fields),
  66. 'app_label': app_label,
  67. 'model_name': model_name,
  68. 'label': label,
  69. 'name': name,
  70. }
  71. output.append(render_to_string(self.widget_template or (
  72. 'django_extensions/widgets/%s/%s/foreignkey_searchinput.html' % (app_label, model_name),
  73. 'django_extensions/widgets/%s/foreignkey_searchinput.html' % app_label,
  74. 'django_extensions/widgets/foreignkey_searchinput.html',
  75. ), context))
  76. output.reverse()
  77. return mark_safe(six.u('').join(output))