utils.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import os
  4. import hashlib
  5. import binascii
  6. import base64
  7. import html2text
  8. import re
  9. import sys
  10. from datetime import date, timedelta
  11. from contextlib import contextmanager
  12. from functools import wraps
  13. from django.utils import translation
  14. from django.core.mail import EmailMultiAlternatives
  15. from django.core.files.storage import FileSystemStorage
  16. from django.conf import settings
  17. from django.template.loader import get_template
  18. from django.template import Context, TemplateDoesNotExist
  19. from django.contrib.sites.models import Site
  20. # Stockage des fichiers privés (comme les factures par exemple)
  21. private_files_storage = FileSystemStorage(location=settings.PRIVATE_FILES_ROOT)
  22. # regexp which matches for ex irc://irc.example.tld/#channel
  23. re_chat_url = re.compile(r"(?P<protocol>\w+://)(?P<server>[\w\.]+)/(?P<channel>.*)")
  24. def str_or_none(obj):
  25. return str(obj) if obj else None
  26. def rstrip_str(s, suffix):
  27. """Return a copy of the string [s] with the string [suffix] removed from
  28. the end (if [s] ends with [suffix], otherwise return s)."""
  29. if s.endswith(suffix):
  30. return s[: -len(suffix)]
  31. else:
  32. return s
  33. def ldap_hash(password):
  34. """Hash a password for use with LDAP. If the password is already hashed,
  35. do nothing.
  36. Implementation details: Django provides us with a unicode object, so
  37. we have to encode/decode it as needed to switch between unicode and
  38. bytes. The code should work fine with both python2 and python3.
  39. """
  40. if password and not password.startswith("{SSHA}"):
  41. salt = binascii.hexlify(os.urandom(8))
  42. digest = hashlib.sha1(password.encode("utf-8") + salt).digest()
  43. return "{SSHA}" + base64.b64encode(digest + salt).decode("utf-8")
  44. else:
  45. return password
  46. def send_templated_email(
  47. to, subject_template, body_template, context={}, attachements=[], **kwargs
  48. ):
  49. """
  50. Send a multialternative email based on html and optional txt template.
  51. :param **kwargs: extra-args pased as-is to EmailMultiAlternatives()
  52. """
  53. # Ensure arrays when needed
  54. if not isinstance(to, list):
  55. to = [to]
  56. if not isinstance(attachements, list):
  57. attachements = [attachements]
  58. # Add domain in context
  59. context["domain"] = Site.objects.get_current()
  60. # If .html/.txt is specified in template name remove it
  61. body_template = body_template.split(".")[0]
  62. subject_template = subject_template.split(".")[0]
  63. # Get html template for body, fail if not exists
  64. template_html = get_template("%s.html" % (body_template,))
  65. html_content = template_html.render(Context(context))
  66. # Get txt template for subject, fail if not exists
  67. subject_template = get_template("%s.txt" % (subject_template,))
  68. subject = subject_template.render(Context(context))
  69. # Get rid of newlines
  70. subject = subject.strip().replace("\n", "")
  71. # Try to get a txt version, convert from html to markdown style
  72. # (using html2text) if fail
  73. try:
  74. template_txt = get_template("%s.txt" % (body_template,))
  75. text_content = template_txt.render_to_string(Context(context))
  76. except TemplateDoesNotExist:
  77. text_content = html2text.html2text(html_content)
  78. # make multipart email default : text, alternative : html
  79. msg = EmailMultiAlternatives(subject=subject, body=text_content, to=to, **kwargs)
  80. msg.attach_alternative(html_content, "text/html")
  81. # Set attachements
  82. for attachement in attachements:
  83. msg.attach_file(attachement)
  84. # Send email
  85. msg.send()
  86. def delete_selected(modeladmin, request, queryset):
  87. """Overrides QuerySet's delete() function to remove objects one by one
  88. so, that they are deleted in the LDAP (Redmine issue #195)."""
  89. for obj in queryset:
  90. obj.delete()
  91. delete_selected.short_description = "Supprimer tous les objets sélectionnés."
  92. # Time-related functions
  93. def in_one_year():
  94. return date.today() + timedelta(365)
  95. def start_of_month():
  96. return date(date.today().year, date.today().month, 1)
  97. def end_of_month():
  98. today = date.today()
  99. if today.month == 12:
  100. return date(today.year + 1, 1, 1) - timedelta(days=1)
  101. else:
  102. return date(today.year, today.month + 1, 1) - timedelta(days=1)
  103. @contextmanager
  104. def respect_language(language):
  105. """Context manager that changes the current translation language for
  106. all code inside the following block.
  107. Can be used like this::
  108. from amorce.utils import respect_language
  109. def my_func(language='fr'):
  110. with respect_language(language):
  111. pass
  112. """
  113. if language:
  114. prev = translation.get_language()
  115. translation.activate(language)
  116. try:
  117. yield
  118. finally:
  119. translation.activate(prev)
  120. else:
  121. yield
  122. def respects_language(fun):
  123. """Associated decorator"""
  124. @wraps(fun)
  125. def _inner(*args, **kwargs):
  126. with respect_language(kwargs.pop("language", None)):
  127. return fun(*args, **kwargs)
  128. return _inner
  129. def disable_for_loaddata(signal_handler):
  130. """Decorator for post_save events that disables them when loading
  131. data from fixtures."""
  132. @wraps(signal_handler)
  133. def wrapper(*args, **kwargs):
  134. if kwargs["raw"]:
  135. return
  136. signal_handler(*args, **kwargs)
  137. return wrapper
  138. def postgresql_regexp(regexp):
  139. """ Make a PCRE regexp PostgreSQL compatible (kinda)
  140. PostgreSQL forbids using capture-group names, this function removes them.
  141. :param regexp: a PCRE regexp or pattern
  142. :return a PostgreSQL regexp
  143. """
  144. try:
  145. original_pattern = regexp.pattern
  146. except AttributeError:
  147. original_pattern = regexp
  148. return re.sub(r"\?P<.*?>", "", original_pattern)
  149. if __name__ == "__main__":
  150. # ldap_hash expects an unicode string
  151. print(ldap_hash(sys.argv[1].decode("utf-8")))