def test_typeutil(self): # Tester la conversion de chaîne à un autre type self.assertEqual(str_to("c", int), 0) self.assertEqual(str_to("9", int), 9) # Tester make_iterable self.assertEqual(make_iterable(None), []) self.assertEqual(make_iterable([1, 2, 3], tuple), (1, 2, 3)) # Tester les noms de couleur self.assertIn(closest_color((0, 255, 255)), ('aqua', 'cyan'))
def check_file_extension(filename, resource_path, extensions): """ Renvoyer le nom d'un fichier avec l'extension correspondant à son type MIME :param extensions: extensions autorisées pour le renommage :param filename: nom de fichier :param resource_path: chemin du fichier de données à analyser """ if os.path.exists(resource_path) and filename: # Réparer l'extension de fichier extension_add = True extensions = make_iterable(extensions) for extension in extensions: if filename.lower().endswith(extension.lower()): extension_add = False if extension_add: # Trouver le type MIME mime = Magic(mime=True, keep_going=True) mimetype = mime.from_file(resource_path) new_extension = guess_extension(mimetype) if filename and new_extension: filename = "{}{}".format(filename, new_extension) if new_extension.lower() in extensions: return filename else: return None else: return filename return filename
def queue(self, recipient, typename, data=None, forced=False): """ Mettre en file un événement mail :param recipient: utilisateur ou email :param typename: type d'email enregistré (voir MailType) ex. 'messaging.message.staff' :param data: données à attacher au mail. Les données sont agrégées si possible au données en file. :param forced: indique si le mail fait partie des contenus prioritaires :type data: dict :type forced: bool :type recipient: str | User :type typename: str """ from scoop.messaging.models.mailtype import MailType # Convertir les éléments du dictionnaire passé en listes data = dict() if data is None else data mail_data = {item: make_iterable(data[item]) for item in data} # Créer ou récupérer un événement mail mail_type = MailType.objects.get_named(typename) kwargs = {'sent_email': recipient} if isinstance(recipient, str) else {'recipient': recipient, 'sent_email': recipient.email} mail, created = self.get_or_create(sent=False, forced=forced, type=mail_type, **kwargs) # Ajouter des données au mail en file for item in mail_data: mail.set_data(item, mail_data[item] if mail.get_data(item) is None else mail.get_data(item) + mail_data[item]) if created is True: mail.minimum_time = timezone.now() + datetime.timedelta(minutes=mail.type.interval) mail.save()
def post(self, authors, category, title, body, visible=True, **kwargs): """ Créer un nouveau contenu :param authors: auteur ou liste des utilisateurs auteurs :param category: nom de catégorie du nouveau contenu :param title: titre du contenu :param body: texte du contenu :param visible: définir si le contenu est visible du public """ try: [ kwargs.pop(name, None) for name in ['authors', 'category', 'title', 'body', 'visible'] ] category_instance = Category.objects.get_by_name(category) content, _ = self.get_or_create(category=category_instance, title=title, body=body, published=visible, **kwargs) authors = make_iterable(authors, list) content.authors.add(*authors) return content except AttributeError as e: print_exc(e) return None
def hide_from_author(self, author=None): """ Rendre invisible les contenus d'un auteur :param author: utilisateur ou liste d'utilisateurs """ if author: author = make_iterable(author) return self.filter(author__in=author).update(visible=False)
def create_with(self, name, pictures, **kwargs): """ Créer un album et y intégrer une liste d'images """ pictures = make_iterable(pictures) description = kwargs.get('description', '') author = kwargs.get('author', None) album = Album(name=name, description=description, author=author) album.save() album.add(pictures) return album
def message_on_invalid(forms, request, message, level=None, extra_tags=None): """ Afficher un message à l'utilisateur si un formulaire n'est pas valide """ forms = make_iterable(forms) for item in forms: if not item.is_valid(): messages.add_message(request, level or messages.SUCCESS, message.format(errors=error_labels(forms)), extra_tags=extra_tags)
def get_users(self, exclude=None, only_active=True): """ Renvoyer les utilisateurs participant au fil """ criteria = {'user_recipients__active': True} if only_active else {} exclusion = { 'id__in': map(lambda i: i.id, make_iterable(exclude)) } if exclude else {} users = get_user_model().objects.filter( user_recipients__thread=self, **criteria).exclude(**exclusion).distinct() return users
def get_recipients(self, exclude=None, only_active=True): """ Renvoyer les objets Recipient du fil """ criteria = {'active': True} if only_active else {} exclusion = { 'id__in': map(lambda i: i.id, make_iterable(exclude)) } if exclude else {} recipients = self.recipients.select_related( 'user', 'user__profile__picture').filter(**criteria).exclude(**exclusion) return recipients
def _send_mail(self, sender, to, title, text, html=None): """ Envoyer immédiatement un courrier """ message = EmailMultiRelated(title, text, sender, make_iterable(to)) if html is not None: message.attach_alternative(html, "text/html") try: message.send() return True except (SMTPException, SMTPResponseException): return False
def find_files_in_folder(path, extensions): """ Renvoyer tous les fichiers d'un répertoire correspondant à une ou plusieurs extensions """ files = [] extensions = make_iterable(extensions) for folder, _directories, filenames in os.walk(path): for filename in filenames: for extension in extensions: if filename.lower().endswith(extension): files.append(join(folder, filename)) return files
def acknowledge(self, user=None, threads=None): """ Prendre connaissance de l'existence d'un fil """ queryset = self if user is not None: queryset = queryset.filter(user=user) if threads is not None: threads = make_iterable(threads) queryset = queryset.filter(thread__in=threads) updated = self.update(acknowledged=True) return updated
def error_labels(forms): """ Renvoyer la liste des étiquettes de champs pour les erreurs d'un formulaire """ forms = make_iterable(forms) labels = [] for item in forms: if item.errors: labels += [field.label for field in item if field.errors] if labels: return humanize_join(labels, 10, "field;fields") else: return None
def by_tags(self, tags, **kwargs): """ Renvoyer les contenus ayant des étiquettes précises :param tags: liste de noms de tags ou liste de tags :param kwargs: options de filtrage supplémentaires """ assert isinstance(self, (models.QuerySet, models.Manager)) tags = make_iterable(tags, list) ttype = type(tags[0]) return self.filter(tags__short_name__in=tags, **kwargs) if issubclass( ttype, str) else self.filter(terms__in=tags, **kwargs)
def remove_recipient(self, user): """ Supprimer un ou plusieurs destinataires du fil """ for recipient in self.recipients.filter(user__in=make_iterable(user), thread=self): recipient.disable() # Fermer ou supprimer si plus assez de participants count = self.get_recipient_count() actions = { 0: (lambda t: t.delete()), 1: (lambda t: t.toggle(force_close=True)) } actions.get(count, lambda t: t)(self)
def ban_users(request, users, mail=True): """ Bannir les profils et alterter les utilisateurs qui ont été en contact """ users = make_iterable(users) banned = [] for user in users: if user.profile.ban() is True: Alert.objects.alert_related(user, 'user.profile.fake', 'security', {'profile': user.profile}, as_mail=mail) banned.append(user) if banned: messages.success(request, humanize_join(banned, 3, _("user has been banned;users have been banned"))) return True else: messages.info(request, _("No user has been banned.")) return False
def is_safe(self, code2): """ Renvoyer si un pays portant un code est considéré sain :param code2: code pays ou liste de codes pays :type code2: str | list | set | tuple """ codes = make_iterable(code2, set) codes = (code.lower().strip() for code in codes) # Si un des pays est non safe if codes and codes != {""}: if self.filter(safe=False, code2__in=codes).exists(): return False return True
def by_category(self, categories, attribute='short_name', **kwargs): """ Renvoyer les contenus correspondant à des catégories précises :param categories: liste de catégories ou liste de noms de catégories :param attribute: sur quel attribut de la classe catégorie effectuer la recherche """ assert isinstance(self, (models.QuerySet, models.Manager)) categories = make_iterable(categories, list) ctype = type(categories[0]) kwargs.update({ ('category__{}__in' if issubclass(ctype, str) else 'category__in').format(attribute): categories }) return self.filter(**kwargs)
def has_post(request, action=None): """ Renvoyer si l'objet HttpRequest contient des données POST :param request: requête :param action: nom de la donnée POST à chercher, ou liste de noms POST. Si action est une liste, vérifier qu'au moins une donnée est présente. Si action est None ou vide, vérifier uniquement que method == POST :rtype: bool """ posted = (request.method == 'POST') action = make_iterable(action) if posted and action: return posted and any(name in request.POST for name in action) return posted
def add_recipients(self, recipients): """ Ajouter des destinataires au fil """ from scoop.rogue.models import Blocklist # Vérifier la possibilité d'ajouter chaque destinataire recipients = make_iterable(recipients, set) for recipient in recipients: if Blocklist.objects.is_safe(self.author, recipient): recipient, created = self.recipients.get_or_create( thread=self, user=recipient) if not created: if not recipient.active: recipient.enable() else: self.population += 1 self.save()
def flag_by_lookup(self, model_name, identifier, *args, **kwargs): """ Signaler un objet via app_label.model et id :param model_name: nom de modèle Django :param identifier: identifiant de l'objet, ou identifiants :type identifier: int | list """ identifiers = make_iterable(identifier) for identifier in identifiers: try: item = ContentType.get_object_for_this_type(model=model_name, pk=identifier) self.flag(item, **kwargs) return True except ContentType.DoesNotExist: return False
def default_pre_thread(sender, author, recipients, request, unique, force, **kwargs): """ Traiter la pré-création d'un thread """ errors = set() recipients = make_iterable(recipients) # Ne rien faire si le quota de sujets pour user est atteint if Quota.objects.exceeded_threads_for(author) and not force: errors.add(_("You have started discussions with too much people today.")) # Ne rien faire s'il n'y a aucun participant ou juste author if recipients == {author} or not recipients: if not force: errors.add(_("Cannot send a new message to nobody.")) # Vérifier que l'expéditeur n'est pas bloqué par un destinataire if getattr(settings, 'MESSAGING_BLACKLIST_ENABLE', False) is True: for recipient in recipients: if not Blocklist.objects.is_safe(recipient, author): # or recipient.rule.is_blocked(author): errors.add(_("User policy prevents you from talking to at least one recipient.")) return {'messages': errors} if errors else True
def default_mail_send(sender, mailtype, recipient, data, **kwargs): """ Traiter la mise en file d'un nouvel événement mail Certains mails peuvent ne pas être envoyés dans les cas suivants : - L'utilisateur est en ligne et le mail est spécifique au hors-ligne Certains mails sont toujours envoyés : - Les mails avec un type dit forcé (avec une catégorie "important") :param recipient: L'adresse email ou l'utilisateur cible :type mailtype: str | MailType :type recipient: str | User | list :param data: données à joindre au mail """ category_option = { 'message': 'email_on_message', 'staff': 'email_on_staff', 'subscription': 'email_on_subscription' } recipients = make_iterable(recipient) # Récupérer l'obet MailType correspondant au mailtype passé mailtype = MailType.objects.get_named(mailtype) if isinstance( mailtype, str) else mailtype categories = mailtype.get_categories() # Recenser les cas où le mail peut, doit ou ne peut pas être envoyé for recipient in recipients: chose_to_receive = any([ ConfigurationForm.get_option_for(recipient, category_option[category]) for category in categories if category in category_option ]) can_receive = all([ category not in category_option for category in categories ]) # Si aucune catégorie correspond à une option autoriser par défaut must_receive = mailtype.has_category('important') restrict_online = False if not isinstance(recipient, str): restrict_online = recipient.is_active and recipient.is_online( 600) and not mailtype.has_category('online') # Si le mail doit, ou peut être envoyé, ajouter en file if must_receive or ((chose_to_receive or can_receive) and not restrict_online): MailEvent.objects.queue(recipient, mailtype.short_name, data, forced=must_receive)
def remove_pictures(request, users): """ Retirer toutes les images d'un profil """ users = make_iterable(users) destroyed = [] for user in users: pictures = user.profile.get_pictures() count = pictures.count() if count > 0: Alert.objects.alert(user, 'user.profile.picture.invalid', 'notification', {'count': count, 'pictures': pictures}) record.send(get_user_model(), request.user, 'content.delete.picture', user) pictures.delete() destroyed.append(user) if destroyed: messages.success(request, humanize_join(destroyed, 3, _("user has images removed;users have images removed"))) return True else: messages.info(request, _("No picture has to be deleted.")) return False
def alert(self, recipients, mailtype_name, data, level=0, as_mail=None, **kwargs): """ Envoyer une alerte à un ou plusieurs utilisateurs :param recipients: utilisateurs destinataires :param mailtype_name: nom de code du type de courrier/alerte à envoyer, :see: messaging.models.MailType :param data: dictionnaire d'infos pour le rendu de l'alerte/courrier :param level: niveau d'urgence de l'alerte :param as_mail: indique si un mail récapitulatif doit être envoyé. Par défaut None=n'envoie que les alertes de sécurité :returns: la liste des alertes effectivement envoyées :rtype: list<Alert> """ from scoop.messaging.models.mailtype import MailType # Maximum de 1000 membres à qui envoyer l'alerte recipients = make_iterable(recipients)[0:1000] context = default_context() mailtype = MailType.objects.get_named(mailtype_name) template = 'messaging/alert/{name}.html'.format(name=mailtype.template) title, html = [ render_block_to_string(template, label, data, context=context) for label in ['title', 'html'] ] as_mail = level == Alert.SECURITY if as_mail is None else as_mail alerts = [] # Créer une alerte pour chacun des destinataires for recipient in recipients: new_alert = self.create(user=recipient, title=one_line(title), text=html, level=level) alerts.append(new_alert) if as_mail is True: mailable_event.send(sender=None, mailtype=mailtype_name, recipient=recipient, data=data) return alerts
def form(request, config, initial=None): """ Créer un formulaire initialisé selon l'état de la requête. Si request contient des données POST, créer le formulaire avec ces données. Exemple : >> a, b, c = form(request, ((A, None), (B, {'instance': y}), (C, {'instance': z})), initial=None) >> [a] = form(request, {A: {'instance': x}}) :param request: requête HTTP :param config: Configuration des formulaires :type config: Form or list[Form] or collections.OrderedDict or tuple[tuple] or dict(len=1) or list[list] :param initial: Valeurs des champs par défaut en l'absence de POST :returns: une liste de formulaires ou None :rtype: list """ forms = list() config = make_iterable(config, list) if is_iterable(config) and not is_multi_dimensional(config): config = OrderedDict(((item, None) for item in config)) else: config = OrderedDict(config) for form_class in config.keys(): args, kwargs = list(), dict() # Réunir les arguments d'initialisation du formulaire if request and request.has_post(): args.append(request.POST) args.append(request.FILES) elif initial is not None: if isinstance(initial, QueryDict): # Charger des données POST args.append(initial.dict()) else: kwargs['initial'] = _normalize_initial(initial, form_class) if isclass(form_class) and issubclass( form_class, ModelForm) and config[form_class] is not None: kwargs['instance'] = config[form_class] # Vérifier que le formulaire fonctionne avec initial temp_form = form_class(*args, **kwargs) temp_form.request = request forms.append(temp_form) # Renvoyer les formulaires return forms if forms else None
def get_item_count(self, model_list, per_class=False): """ Renvoyer le nombre d'instances d'un ou plusieurs modèles avec cette étiquette :param per_class: renvoyer un dictionnaire avec le nombre d'instances par classe :type per_class: bool :param model_list: classe(s) de modèles avec étiquette à recenser :type model_list: list | models.Model :returns: un dictionnaire {Model:int} ou int """ count = {} if per_class else 0 model_list = make_iterable(model_list) for model in model_list: if issubclass(model, LabelableModel): total = model.objects.filter(labels=self).count() if per_class is True: count[model] = total else: count = count + total return count
def batch_execute(path, extensions, command): """ Exécuter une commande shell sur tous les fichers de <path> correspondant aux extensions passées en paramètre (liste ou simple chaîne) :param command: chaîne contenant la commande à exécuter sur chaque fichier. Autorise l'utilisation du placeholder {file} pour indiquer le nom de fichier """ total_processed = 0 for root, _dummy, files in os.walk(path): for f in files: for extension in make_iterable(extensions): if f.lower().endswith(extension.lower()): for i, _dummyx in enumerate(command): command[i] = command[i].format( file=os.path.join(path, root, f)) subprocess.call(command) total_processed += 1 logger.info("Batch executed successfully on {count} files.".format( count=total_processed))
def block_ips(self, ips, harm=3, category=0, description=None): """ Bloquer une ou plusieurs adresse IP :param ips: id ou instance d'IP ou chaîne au format A.B.C.D :type ips: list | tuple | set | IP | str | int :returns: les nouveaux blocages d'IP :param harm: niveau de danger de la plage, entre 0 et 3 :param category: type de blocage, voir IPBlock :param description: description et raison du blocage """ from scoop.user.access.models.ip import IP ips = make_iterable(ips) new_blocks = [] for ip in ips: try: # Convertir en une instance d'IP if isinstance(ip, str): ip = IP.objects.get_by_ip(ip) elif isinstance(ip, int): ip = IP.objects.get(id=ip) elif isinstance(ip, IP): ip = ip else: raise TypeError("IPs must be strings, ints or IPs.") if not ip.is_protected(): block, created = self.get_or_create(type=IPBlock.SINGLE, ip1=ip.ip, isp=ip.isp, harm=harm, category=category, description=description or "") if created is False: block.active = True block.save() else: new_blocks.append(block) except TypeError: pass return new_blocks
def add(self, pictures): """ Insérer une image dans l'album """ pictures = make_iterable(pictures) for picture in pictures: AlbumPicture.objects.get_or_create(album=self, picture=picture) self.save() # Mettre à jour l'état pictured