Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

formats.py 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import datetime
  2. import decimal
  3. import unicodedata
  4. from importlib import import_module
  5. from django.conf import settings
  6. from django.utils import dateformat, datetime_safe, numberformat
  7. from django.utils.functional import lazy
  8. from django.utils.translation import (
  9. check_for_language, get_language, to_locale,
  10. )
  11. # format_cache is a mapping from (format_type, lang) to the format string.
  12. # By using the cache, it is possible to avoid running get_format_modules
  13. # repeatedly.
  14. _format_cache = {}
  15. _format_modules_cache = {}
  16. ISO_INPUT_FORMATS = {
  17. 'DATE_INPUT_FORMATS': ['%Y-%m-%d'],
  18. 'TIME_INPUT_FORMATS': ['%H:%M:%S', '%H:%M:%S.%f', '%H:%M'],
  19. 'DATETIME_INPUT_FORMATS': [
  20. '%Y-%m-%d %H:%M:%S',
  21. '%Y-%m-%d %H:%M:%S.%f',
  22. '%Y-%m-%d %H:%M',
  23. '%Y-%m-%d'
  24. ],
  25. }
  26. FORMAT_SETTINGS = frozenset([
  27. 'DECIMAL_SEPARATOR',
  28. 'THOUSAND_SEPARATOR',
  29. 'NUMBER_GROUPING',
  30. 'FIRST_DAY_OF_WEEK',
  31. 'MONTH_DAY_FORMAT',
  32. 'TIME_FORMAT',
  33. 'DATE_FORMAT',
  34. 'DATETIME_FORMAT',
  35. 'SHORT_DATE_FORMAT',
  36. 'SHORT_DATETIME_FORMAT',
  37. 'YEAR_MONTH_FORMAT',
  38. 'DATE_INPUT_FORMATS',
  39. 'TIME_INPUT_FORMATS',
  40. 'DATETIME_INPUT_FORMATS',
  41. ])
  42. def reset_format_cache():
  43. """Clear any cached formats.
  44. This method is provided primarily for testing purposes,
  45. so that the effects of cached formats can be removed.
  46. """
  47. global _format_cache, _format_modules_cache
  48. _format_cache = {}
  49. _format_modules_cache = {}
  50. def iter_format_modules(lang, format_module_path=None):
  51. """Find format modules."""
  52. if not check_for_language(lang):
  53. return
  54. if format_module_path is None:
  55. format_module_path = settings.FORMAT_MODULE_PATH
  56. format_locations = []
  57. if format_module_path:
  58. if isinstance(format_module_path, str):
  59. format_module_path = [format_module_path]
  60. for path in format_module_path:
  61. format_locations.append(path + '.%s')
  62. format_locations.append('django.conf.locale.%s')
  63. locale = to_locale(lang)
  64. locales = [locale]
  65. if '_' in locale:
  66. locales.append(locale.split('_')[0])
  67. for location in format_locations:
  68. for loc in locales:
  69. try:
  70. yield import_module('%s.formats' % (location % loc))
  71. except ImportError:
  72. pass
  73. def get_format_modules(lang=None, reverse=False):
  74. """Return a list of the format modules found."""
  75. if lang is None:
  76. lang = get_language()
  77. if lang not in _format_modules_cache:
  78. _format_modules_cache[lang] = list(iter_format_modules(lang, settings.FORMAT_MODULE_PATH))
  79. modules = _format_modules_cache[lang]
  80. if reverse:
  81. return list(reversed(modules))
  82. return modules
  83. def get_format(format_type, lang=None, use_l10n=None):
  84. """
  85. For a specific format type, return the format for the current
  86. language (locale). Default to the format in the settings.
  87. format_type is the name of the format, e.g. 'DATE_FORMAT'.
  88. If use_l10n is provided and is not None, it forces the value to
  89. be localized (or not), overriding the value of settings.USE_L10N.
  90. """
  91. use_l10n = use_l10n or (use_l10n is None and settings.USE_L10N)
  92. if use_l10n and lang is None:
  93. lang = get_language()
  94. cache_key = (format_type, lang)
  95. try:
  96. return _format_cache[cache_key]
  97. except KeyError:
  98. pass
  99. # The requested format_type has not been cached yet. Try to find it in any
  100. # of the format_modules for the given lang if l10n is enabled. If it's not
  101. # there or if l10n is disabled, fall back to the project settings.
  102. val = None
  103. if use_l10n:
  104. for module in get_format_modules(lang):
  105. val = getattr(module, format_type, None)
  106. if val is not None:
  107. break
  108. if val is None:
  109. if format_type not in FORMAT_SETTINGS:
  110. return format_type
  111. val = getattr(settings, format_type)
  112. elif format_type in ISO_INPUT_FORMATS:
  113. # If a list of input formats from one of the format_modules was
  114. # retrieved, make sure the ISO_INPUT_FORMATS are in this list.
  115. val = list(val)
  116. for iso_input in ISO_INPUT_FORMATS.get(format_type, ()):
  117. if iso_input not in val:
  118. val.append(iso_input)
  119. _format_cache[cache_key] = val
  120. return val
  121. get_format_lazy = lazy(get_format, str, list, tuple)
  122. def date_format(value, format=None, use_l10n=None):
  123. """
  124. Format a datetime.date or datetime.datetime object using a
  125. localizable format.
  126. If use_l10n is provided and is not None, that will force the value to
  127. be localized (or not), overriding the value of settings.USE_L10N.
  128. """
  129. return dateformat.format(value, get_format(format or 'DATE_FORMAT', use_l10n=use_l10n))
  130. def time_format(value, format=None, use_l10n=None):
  131. """
  132. Format a datetime.time object using a localizable format.
  133. If use_l10n is provided and is not None, it forces the value to
  134. be localized (or not), overriding the value of settings.USE_L10N.
  135. """
  136. return dateformat.time_format(value, get_format(format or 'TIME_FORMAT', use_l10n=use_l10n))
  137. def number_format(value, decimal_pos=None, use_l10n=None, force_grouping=False):
  138. """
  139. Format a numeric value using localization settings.
  140. If use_l10n is provided and is not None, it forces the value to
  141. be localized (or not), overriding the value of settings.USE_L10N.
  142. """
  143. if use_l10n or (use_l10n is None and settings.USE_L10N):
  144. lang = get_language()
  145. else:
  146. lang = None
  147. return numberformat.format(
  148. value,
  149. get_format('DECIMAL_SEPARATOR', lang, use_l10n=use_l10n),
  150. decimal_pos,
  151. get_format('NUMBER_GROUPING', lang, use_l10n=use_l10n),
  152. get_format('THOUSAND_SEPARATOR', lang, use_l10n=use_l10n),
  153. force_grouping=force_grouping
  154. )
  155. def localize(value, use_l10n=None):
  156. """
  157. Check if value is a localizable type (date, number...) and return it
  158. formatted as a string using current locale format.
  159. If use_l10n is provided and is not None, it forces the value to
  160. be localized (or not), overriding the value of settings.USE_L10N.
  161. """
  162. if isinstance(value, str): # Handle strings first for performance reasons.
  163. return value
  164. elif isinstance(value, bool): # Make sure booleans don't get treated as numbers
  165. return str(value)
  166. elif isinstance(value, (decimal.Decimal, float, int)):
  167. return number_format(value, use_l10n=use_l10n)
  168. elif isinstance(value, datetime.datetime):
  169. return date_format(value, 'DATETIME_FORMAT', use_l10n=use_l10n)
  170. elif isinstance(value, datetime.date):
  171. return date_format(value, use_l10n=use_l10n)
  172. elif isinstance(value, datetime.time):
  173. return time_format(value, 'TIME_FORMAT', use_l10n=use_l10n)
  174. return value
  175. def localize_input(value, default=None):
  176. """
  177. Check if an input value is a localizable type and return it
  178. formatted with the appropriate formatting string of the current locale.
  179. """
  180. if isinstance(value, str): # Handle strings first for performance reasons.
  181. return value
  182. elif isinstance(value, bool): # Don't treat booleans as numbers.
  183. return str(value)
  184. elif isinstance(value, (decimal.Decimal, float, int)):
  185. return number_format(value)
  186. elif isinstance(value, datetime.datetime):
  187. value = datetime_safe.new_datetime(value)
  188. format = default or get_format('DATETIME_INPUT_FORMATS')[0]
  189. return value.strftime(format)
  190. elif isinstance(value, datetime.date):
  191. value = datetime_safe.new_date(value)
  192. format = default or get_format('DATE_INPUT_FORMATS')[0]
  193. return value.strftime(format)
  194. elif isinstance(value, datetime.time):
  195. format = default or get_format('TIME_INPUT_FORMATS')[0]
  196. return value.strftime(format)
  197. return value
  198. def sanitize_separators(value):
  199. """
  200. Sanitize a value according to the current decimal and
  201. thousand separator setting. Used with form field input.
  202. """
  203. if isinstance(value, str):
  204. parts = []
  205. decimal_separator = get_format('DECIMAL_SEPARATOR')
  206. if decimal_separator in value:
  207. value, decimals = value.split(decimal_separator, 1)
  208. parts.append(decimals)
  209. if settings.USE_THOUSAND_SEPARATOR:
  210. thousand_sep = get_format('THOUSAND_SEPARATOR')
  211. if thousand_sep == '.' and value.count('.') == 1 and len(value.split('.')[-1]) != 3:
  212. # Special case where we suspect a dot meant decimal separator (see #22171)
  213. pass
  214. else:
  215. for replacement in {
  216. thousand_sep, unicodedata.normalize('NFKD', thousand_sep)}:
  217. value = value.replace(replacement, '')
  218. parts.append(value)
  219. value = '.'.join(reversed(parts))
  220. return value