# -*- coding: utf-8 -*- from __future__ import unicode_literals import os import hashlib import binascii import base64 import html2text import re import sys from datetime import date, timedelta from contextlib import contextmanager from functools import wraps from django.utils import translation from django.core.mail import EmailMultiAlternatives from django.core.files.storage import FileSystemStorage from django.conf import settings from django.template.loader import get_template from django.template import Context, TemplateDoesNotExist from django.contrib.sites.models import Site # Stockage des fichiers privés (comme les factures par exemple) private_files_storage = FileSystemStorage(location=settings.PRIVATE_FILES_ROOT) # regexp which matches for ex irc://irc.example.tld/#channel re_chat_url = re.compile(r'(?P\w+://)(?P[\w\.]+)/(?P.*)') def str_or_none(obj): return str(obj) if obj else None def rstrip_str(s, suffix): """Return a copy of the string [s] with the string [suffix] removed from the end (if [s] ends with [suffix], otherwise return s).""" if s.endswith(suffix): return s[:-len(suffix)] else: return s def ldap_hash(password): """Hash a password for use with LDAP. If the password is already hashed, do nothing. Implementation details: Django provides us with a unicode object, so we have to encode/decode it as needed to switch between unicode and bytes. The code should work fine with both python2 and python3. """ if password and not password.startswith('{SSHA}'): salt = binascii.hexlify(os.urandom(8)) digest = hashlib.sha1(password.encode("utf-8") + salt).digest() return '{SSHA}' + base64.b64encode(digest + salt).decode("utf-8") else: return password def send_templated_email(to, subject_template, body_template, context={}, attachements=[], **kwargs): """ Send a multialternative email based on html and optional txt template. :param **kwargs: extra-args pased as-is to EmailMultiAlternatives() """ # Ensure arrays when needed if not isinstance(to, list): to = [to] if not isinstance(attachements, list): attachements = [attachements] # Add domain in context context['domain'] = Site.objects.get_current() # If .html/.txt is specified in template name remove it body_template = body_template.split('.')[0] subject_template = subject_template.split('.')[0] # Get html template for body, fail if not exists template_html = get_template('%s.html' % (body_template,)) html_content = template_html.render(Context(context)) # Get txt template for subject, fail if not exists subject_template = get_template('%s.txt' % (subject_template,)) subject = subject_template.render(Context(context)) # Get rid of newlines subject = subject.strip().replace('\n', '') # Try to get a txt version, convert from html to markdown style # (using html2text) if fail try: template_txt = get_template('%s.txt' % (body_template,)) text_content = template_txt.render_to_string(Context(context)) except TemplateDoesNotExist: text_content = html2text.html2text(html_content) # make multipart email default : text, alternative : html msg = EmailMultiAlternatives(subject=subject, body=text_content, to=to, **kwargs) msg.attach_alternative(html_content, "text/html") # Set attachements for attachement in attachements: msg.attach_file(attachement) # Send email msg.send() def delete_selected(modeladmin, request, queryset): """Overrides QuerySet's delete() function to remove objects one by one so, that they are deleted in the LDAP (Redmine issue #195).""" for obj in queryset: obj.delete() delete_selected.short_description = "Supprimer tous les objets sélectionnés." # Time-related functions def in_one_year(): return date.today() + timedelta(365) def start_of_month(): return date(date.today().year, date.today().month, 1) def end_of_month(): today = date.today() if today.month == 12: return date(today.year + 1, 1, 1) - timedelta(days=1) else: return date(today.year, today.month + 1, 1) - timedelta(days=1) @contextmanager def respect_language(language): """Context manager that changes the current translation language for all code inside the following block. Can be used like this:: from amorce.utils import respect_language def my_func(language='fr'): with respect_language(language): pass """ if language: prev = translation.get_language() translation.activate(language) try: yield finally: translation.activate(prev) else: yield def respects_language(fun): """Associated decorator""" @wraps(fun) def _inner(*args, **kwargs): with respect_language(kwargs.pop('language', None)): return fun(*args, **kwargs) return _inner def disable_for_loaddata(signal_handler): """Decorator for post_save events that disables them when loading data from fixtures.""" @wraps(signal_handler) def wrapper(*args, **kwargs): if kwargs['raw']: return signal_handler(*args, **kwargs) return wrapper def postgresql_regexp(regexp): """ Make a PCRE regexp PostgreSQL compatible (kinda) PostgreSQL forbids using capture-group names, this function removes them. :param regexp: a PCRE regexp or pattern :return a PostgreSQL regexp """ try: original_pattern = regexp.pattern except AttributeError: original_pattern = regexp return re.sub( r'\?P<.*?>', '', original_pattern) if __name__ == '__main__': # ldap_hash expects an unicode string print(ldap_hash(sys.argv[1].decode("utf-8")))