Commit f9b52e62 authored by Gabriel Detraz's avatar Gabriel Detraz Committed by root
Browse files

Remove app news de l'intranet

parent c05ce7c2
# -*- coding: utf-8 -*-
"""Webnews"""
from django.conf import settings
newsgroups_list =[
"^crans\.general$",
"^crans\.crans$",
"^crans\.crans\.annonces$",
"^crans\.tele$",
"^crans\.web-sympas$",
"^crans\.radio-ragots$",
"^crans\.petites-annonces$",
"^crans\.politique$",
"^crans\.culture$",
"^crans\.dino$",
"^crans\.sports$",
"^crans\.stages$",
"^crans\.informatique$",
"^crans\.informatique\..*$",
"^crans\.ratp$",
"^tac\.dpt\..*$",
"^tac\.bde$",
"^tac\.crous$",
"^crans\.cns$",
"^crans\.club\..*$",
"^crans\.test$",
"^tac\.test$",
]
last_news_group = [
"crans.general",
"crans.crans",
"crans.petites-annonces",
]
_DEFAULTS = {
'NEWS_SERVER':"news.crans.org",
'LIST_GROUP': newsgroups_list,
'MESSAGES_BY_PAGE': 25,
'LAST_NEWS_GROUP': last_news_group,
'LAST_NEWS_NUMBER':3,
}
for key,value in list(_DEFAULTS.items()):
try:
getattr(settings, key)
except AttributeError:
setattr(settings, key, value)
# Suppress errors from DJANGO_SETTINGS_MODULE not being set
except ImportError:
pass
from django.contrib import admin
# Register your models here.
news_server = "news.crans.org"
news_by_page = 25
newsgroups_list =[
"^crans\.general$",
"^crans\.crans$",
"^crans\.crans\.annonces$",
"^crans\.tele$",
"^crans\.web-sympas$",
"^crans\.radio-ragots$",
"^crans\.petites-annonces$",
"^crans\.politique$",
"^crans\.culture$",
"^crans\.dino$",
"^crans\.sports$",
"^crans\.stages$",
"^crans\.informatique$",
"^crans\.informatique\..*$",
"^crans\.ratp$",
"^tac\.dpt\..*$",
"^tac\.bde$",
"^tac\.crous$",
"^crans\.cns$",
"^crans\.club\..*$",
"^crans\.test$",
"^tac\.test$",
]
# -*- coding: utf-8 -*-
from django import forms
from news import gestion_nntp
class MessageForm(forms.Form):
groups_choices = [(group, group) for group,_ in gestion_nntp.get_groups_list()]
name = forms.CharField(label="nom")
sender = forms.EmailField(label="adresse")
subject = forms.CharField(label="sujet")
body = forms.CharField(label="message",widget=forms.Textarea(), required=False)
newsgroups = forms.ChoiceField(label="Newsgroups", choices=groups_choices)
class CrossMessageForm(MessageForm):
newsgroups = forms.MultipleChoiceField(label="NewsGroups", choices=MessageForm.groups_choices)
followupto = forms.ChoiceField(label="Followup-To", choices=MessageForm.groups_choices, required=True)
# -*- coding: utf-8 -*-
import nntplib
from django.conf import settings
from socket import gaierror
import re, chardet, quopri
from email.header import decode_header
from email.parser import Parser
from email.utils import parseaddr
from email.mime.text import MIMEText
from datetime import datetime
import string
from cStringIO import StringIO
# Exception de base
class NNTPError(Exception):
"""Exception NNTP de base"""
pass
# Exception pour echec de connection au serveur
class ConnexionError(NNTPError):
"""Exception pour echec de connection"""
def __init__(self, server_name):
self.server_name = server_name
# Exception pour echec de selection d'un groupe
class GroupError(NNTPError):
"""Erreur lors de la selection d'un groupe"""
def __init__(self, server_name, group_name):
self.server_name = server_name
self.group_name = group_name
# Exception pour echec de récupération d'un message
class MessageError(NNTPError):
"""Erreur lors de la selection d'un message"""
def __init__(self, server_name, group_name, message_number):
self.server_name = server_name
self.group_name = group_name
self.message_number = message_number
# Exception pour echec de récupération d'un message
class PageError(NNTPError):
"""Erreur lors de la récupération d'un message"""
def __init__(self, server_name, group_name, page_number):
self.server_name = server_name
self.group_name = group_name
self.page_number = page_number
# Exception pour un échec à l'envoie d'un message
class SendError(NNTPError):
"""Erreur lors de l'envoie d'un message"""
def __init__(self, message_error):
self.message_error = message_error
# Décorateur: établit une connexion avec le serveur des news, soulève une
# exception ConnexionError en cas d'échec de connexion, clos la connexion
# en fin de fonction
def server_connexion(func):
"""Decorateur: cree une connection avec le serveur des news et gere les
erreurs"""
def _decorected(*args, **kwargs):
try:
news_server = nntplib.NNTP(settings.NEWS_SERVER)
except gaierror:
raise ConnexionError(settings.NEWS_SERVER)
res = func(news_server, *args, **kwargs)
news_server.quit()
return res
return _decorected
@server_connexion
def get_groups_list(news_server):
"""Renvoie la liste de settings.LIST_GROUP present sur le serveur avec leurs descriptions"""
_, raw_groups = news_server.list()
groups = []
for group_pattern in settings.LIST_GROUP:
for group in raw_groups:
if re.search(group_pattern, group[0]):
groups.append((group[0].decode("utf-8"), news_server.description(group[0])))
return groups
# Décode les headers, fait attention au encodage foireux (utilise chardet en cas
# de souci)
def _decode_news_header(header):
"""Decode proprement les headers"""
decoded = u""
for dh in decode_header(header):
try:
decoded += dh[0].decode(dh[1])
except (UnicodeDecodeError, LookupError, TypeError):
encodage = chardet.detect(dh[0])["encoding"]
if encodage is None or encodage[:8] == "ISO-8859":
encodage = "ISO-8859-15"
decoded += dh[0].decode(encodage)
decoded += u" "
return decoded[:-1]
# Idem _decode_news_header, décode proprement le corp du message
def _decode_news_body(body, encodage):
"""Decode proprement le corp du message"""
parsed = quopri.decodestring(body)
try:
decoded = parsed.decode(encodage)
except (UnicodeDecodeError, LookupError, TypeError):
encodage = chardet.detect(parsed)["encoding"]
if encodage is None or encodage[:8] == "ISO-8859":
encodage = "ISO-8859-15"
decoded = parsed.decode(encodage)
return decoded
# Filtre le message et renvoie les info utiles dans un dico.
def _filtre_message(message, date_pattern, message_number, header=False):
"""Parse le message et renvoie les infos utiles dans un dico"""
message_dic = {"subject":u"", "date":datetime.today(), "exp":(u"",u""),
"message_id":u"", "references":[], "message_number":message_number,
"body":[], "piece_jointe":[], "newsgroups":[]}
parsed_message = Parser().parsestr(string.join(message,"\n"),header)
if parsed_message["date"]:
message_dic["date"] = datetime.strptime(date_pattern.search(parsed_message["date"]).group(), "%d %b %Y %H:%M:%S")
if parsed_message["from"]:
message_dic["exp"] = (_decode_news_header(parseaddr(parsed_message["from"])[0]),_decode_news_header(parseaddr(parsed_message["from"])[1]))
if parsed_message["subject"]:
message_dic["subject"] = _decode_news_header(parsed_message["subject"])
if parsed_message["references"]:
message_dic["references"] = parsed_message["references"].split()
if parsed_message["message-id"]:
message_dic["message_id"] = _decode_news_header(parsed_message["message-id"])
if parsed_message["newsgroups"]:
message_dic["newsgroups"] = string.split(parsed_message["Newsgroups"].decode(), ",")
if parsed_message["followup-to"]:
message_dic["followupto"] = parsed_message["followup-to"].decode()
if parsed_message["x-webnews"]:
message_dic["xwebnews"] = parsed_message["x-webnews"].decode()
if parsed_message["x-cancel-lock"]:
message_dic["xcancellock"] = parsed_message["x-cancel-lock"].decode()
if not header:
for (i,part) in enumerate(parsed_message.walk()):
if part.get_content_maintype() == "text":
message_dic["body"].append(_decode_news_body(part.get_payload(decode=True), part.get_content_charset()))
elif part.get_filename():
message_dic["piece_jointe"].append({"name":part.get_filename(),"number":i, "main_type":part.get_content_maintype()})
return message_dic
# Test d'appartenant d'un message à une liste en fonction de son id
def _message_not_in_list(m,l):
"""Teste si un message est dans une liste avec l'id des messages"""
for i in l:
if i["message_id"] == m["message_id"]:
return False
return True
# Fonction récursive qui traite les fils d'un message pour l'affichage sous forme
# de thread (voir _thread_message)
def _son(message,l, depth = 0):
"""Mets sous forme de thread les messages"""
message["depth"] = depth
threaded_son = [message]
for m in l:
if message["message_id"] in m["references"] and _message_not_in_list(m,threaded_son):
threaded_son.extend(_son(m, l, depth + 1))
return threaded_son
# Trie les messages pour les afficher sous forme de thread et enregistre la profondeur
# du message dans l'arbre
def _thread_message(l):
"""Construit la foret des messages"""
sort_list = []
for message in l:
if _message_not_in_list(message, sort_list):
sort_list = _son(message,l) + sort_list
return sort_list
# Retourne une liste de (groupes,derniers messanges dans le group)
# Permets de mettre en avant les derniers messages de certain groupes
# La liste des groupes est défini dans settings.LAST_NEWS_GROUP
@server_connexion
def get_last_messages(news_server):
# expression reguliere pour parser les dates des messages
date_pattern = re.compile("[0-9]?[0-9] [a-zA-Z]+ [0-9]{4} [0-2]?[0-9]:[0-5][0-9]:[0-5][0-9]")
last_news_list = []
for group in settings.LAST_NEWS_GROUP:
try:
_, _, _, last, _ = news_server.group(group)
except nntplib.NNTPTemporaryError:
raise GroupError(settings.NEWS_SERVER, group)
news_server.stat(last)
current = last
messages = []
try:
for i in range(settings.LAST_NEWS_NUMBER):
_,_,_,h = news_server.head(current)
messages.append(_filtre_message(h, date_pattern, current, True))
_,current,_ = news_server.last()
except nntplib.NNTPTemporaryError:
pass
messages.reverse()
last_news_list.append((group,_thread_message(messages)))
return last_news_list
# Retourne la liste des messages qui doivent apparaitres la page page du groupe group
# Les messages sont triée par arbre de conversation et par date des racines des arbres
@server_connexion
def get_messages_list(news_server, group, page):
try:
_, count, first, last, _ = news_server.group(group)
except nntplib.NNTPTemporaryError:
raise GroupError(settings.NEWS_SERVER, group)
# Nombre de page
max_page = (int(count)+ 24)/settings.MESSAGES_BY_PAGE
# Verifie que la page demandée existe, sinon redirige vers la page 1
if page > max_page or page <= 0:
raise PageError(settings.NEWS_SERVER, group, page)
# expression reguliere pour parser les dates des messages
date_pattern = re.compile("[0-9]?[0-9] [a-zA-Z]+ [0-9]{4} [0-2]?[0-9]:[0-5][0-9]:[0-5][0-9]")
# Si on est plus loin que la moitié des pages on compte a partir de la fin
# Intitialise sur le dernier message du groupe
if page <= max_page/2:
news_server.stat(last)
current = last
else:
current = first
# Boucle jusqu'au messages à afficher (certains messages peuvent être supprime
# il faut donc tous les compter)
for i in range(settings.MESSAGES_BY_PAGE * min((page-1),max_page-page)):
_,current,_ = page <= max_page/2 and news_server.last() or news_server.next()
# Charge la liste de messages de la page, si c'est la derniere page on
# rattrape l'exception et on a fini
messages = []
try:
for i in range(settings.MESSAGES_BY_PAGE):
_,_,_,h = news_server.head(current)
messages.append(_filtre_message(h, date_pattern, current, True))
_,current,_ = page <= max_page/2 and news_server.last() or news_server.next()
except nntplib.NNTPTemporaryError:
pass
if page <= max_page/2:
messages.reverse()
# Trie les messages pour les affichers sous forme de thread
messages = _thread_message(messages)
return (max_page, messages)
# Retourne le message défini par son numéro après avoir été filtré
@server_connexion
def get_message(news_server, group, message_number):
try:
_ = news_server.group(group)
except nntplib.NNTPTemporaryError:
raise GroupError(settings.NEWS_SERVER, group)
try:
message = news_server.article(message_number)
except nntplib.NNTPTemporaryError:
raise MessageError(settings.NEWS_SERVER, group, message_number)
date_pattern = re.compile("[0-9]?[0-9] [a-zA-Z]+ [0-9]{4} [0-2]?[0-9]:[0-5][0-9]:[0-5][0-9]")
message = _filtre_message(message[3], date_pattern, message_number, False)
message["family_tree"] = _thread_history(news_server, message, date_pattern)
return message
# Retourne l'arbre formé des messages de la conversation à laquelle le message appartient
def _thread_history(news_server, message, date_pattern):
current = message["message_number"]
racine = message
# on cherche si un ancètre au message existe, le plus vieux sera la racine
for m in message["references"]:
try:
racine = _filtre_message(news_server.head(m)[3], date_pattern, current, True)
break
except nntplib.NNTPTemporaryError:
pass
messages = [racine]
message_tmp = message
try:
compt = 0 # pour éviter de remonter trop loin
while racine["message_id"] != message_tmp["message_id"] and compt < 500:
_,_,_,h = news_server.head(current)
message_tmp = _filtre_message(h, date_pattern, current, True)
if racine["message_id"] in message_tmp["references"]:
messages.insert(1,message_tmp)
_,current,_ = news_server.last()
compt += 1
messages[0] = message_tmp
except nntplib.NNTPTemporaryError:
pass
try:
news_server.stat(message["message_number"])
_, current,_ = news_server.next()
for i in range(500):
_,_,_,h = news_server.head(current)
message_tmp = _filtre_message(h, date_pattern, current, True)
if racine["message_id"] in message_tmp["references"]:
messages.append(message_tmp)
_,current,_ = news_server.next()
except nntplib.NNTPTemporaryError:
pass
return _thread_message(messages)
# Retourne le message tel qu'envoyer par le serveur
@server_connexion
def get_raw_message(news_server, group, message_number):
try:
_ = news_server.group(group)
except nntplib.NNTPTemporaryError:
raise GroupError(settings.NEWS_SERVER, group)
try:
message = news_server.article(message_number)
except nntplib.NNTPTemporaryError:
raise MessageError(settings.NEWS_SERVER, group, message_number)
return string.join(message[3], "\n")
# Retourne la partie du message correspondant a la piece jointe en raw
def get_attachment(group, message_number, attachment):
message = Parser().parsestr(get_raw_message(group, message_number))
return list(message.walk())[attachment]
# Remplis les champs constant, exemple User-Agent
# TODO: remplir les champs de control pour supprimer des messages
def init_mime_message(message):
message["User-Agent"] = "Django Web-news, v.0.1 (by Fardale) pour le Cr@ns"
message["X-Webnews"] = "Yoloswag" # TODO: implementer la vérif envoyer par le webnews
message["X-Cancel-Lock"] = "Yoloswag" # TODO: idem le faire
return message
# Envoie un message de control au server
# les messages de control sont definis ici
# https://en.wikipedia.org/wiki/Control_message exemple pour supprimer un message:
# send_control_message("blabla@crans.org", "crans.test", "cancel", "<id_messages>")
@server_connexion
def send_control_message(news_server, email, group, commande, *arg):
message = init_mime_message(MIMEText("", "plain"))
for a in arg:
commande += " %s" % a
message["From"] = email
message["Subject"] = "cmsg %s" % commande
message["Control"] = commande
message["Newsgroups"] = group
try:
return news_server.post(StringIO(message.as_string()))
except nntplib.NNTPTemporaryError as error:
raise SendError(error.message)
# Envoie un message au serveur, les arguments doivent être en unicode
# TODO: ajouter des pièces jointes
@server_connexion
def send_message(news_server, nom, email, groups, subject, message, followupto=None):
message = init_mime_message(MIMEText(message, "plain"))
message["From"] = "%s <%s>" % (nom.encode("utf-8"), email.encode("utf-8"))
newsgroups = groups[0].encode("utf-8")
for group in groups[1:]:
newsgroups += ".%s" % group.encode("utf-8")
message["Newsgroups"] = newsgroups.encode("utf-8")
message["Subject"] = subject.encode("utf-8")
if followupto:
message["Followup-To"] = followupto.encode("utf-8")
try:
return news_server.post(StringIO(message.as_string()))
except nntplib.NNTPTemporaryError as error:
raise SendError(error.message)
from django.db import models
# Create your models here.
{% extends "template.html" %}
{% load news_extras %}
{% block title %}WebNews{% endblock %}
{% block h1 %}WebNews{% endblock %}
{% block content %}
<h2>Groupe {{ group }}</h2>
<form class="form-full-width" method="get">
<div class="row">
<div class="four columns">
<a class="button" href="{% url 'news:index' %}">Liste des groupes</a>
</div>
<div class="two columns">
<label for="group_page">Aller à la page :</label>
<select id="group_page" name="group_page">
{% for i in max_page|get_range %}
<option value="{{ i|add:"1" }}">{{ i|add:"1" }}</option>
{% endfor %}
</select>
</div>
<div class="two columns">
<input type="submit" value="Go">
</div>
<div class="four columns">
<a class="button" href="{% url 'news:new_message' group %}">Nouveau message</a>
</div>
</div>
</form>
<nav class="tab">
<ul>
{% if current_page != 1 and current_page != 2 %}
<li><a href="{% url 'news:group' group %}">1</a></li>
<li>...</li>
{% endif %}
{% if current_page > 1 %}
<li><a href="{% url 'news:group' group current_page|add:"-1" %}">{{ current_page|add:"-1" }}</a></li>
{% endif %}
<li>{{ current_page }}</li>
{% if current_page < max_page %}
<li><a href="{% url 'news:group' group current_page|add:"1" %}">{{ current_page|add:"1" }}</a></li>
{% endif %}
{% if current_page != max_page and current_page.number != max_page|add:"-1" %}
<li>...</li>
<li><a href="{% url 'news:group' group max_page %}">{{ max_page }}</a></li>
{% endif %}
</ul>
</nav>
<table class="mobile-friendly">
<thead>
<tr>
<th>Sujet</th>
<th>From</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{% for message in messages_list %}
<tr>
<td mobile-header=Sujet>{% for i in message.depth|get_range %}
&nbsp;&nbsp;&nbsp;
{% endfor %}
{% if message.depth > 0 %}
<img src="/static/img/webnews_bar_L.gif">&nbsp;
{% endif %}
<a href="{% url 'news:message' group current_page message.message_number %}">
{{ message.subject }}
</a>
</td>
<td mobile-header=From><a href="mailto:{{ message.exp.1 }}">
{% if message.exp.0 != "" %}{{ message.exp.0 }}{% else %}{{ message.exp.1 }}{% endif %}</a></td>
<td mobile-header=Date>{{ message.date }}</td>
</tr>
{% empty %}
<tr>
<td colspan='4'>Aucune news</td>
</tr>
{% endfor %}
</tbody>
</table>
<footer>
<div class="row">
<div class="six columns">
<a class="button" href="{% url 'news:index' %}">Liste des groupes</a>
</div>
</div>
</footer>
{% endblock content %}
{% extends "template.html" %}
{% load news_extras %}
{% block title %}WebNews{% endblock %}