Initial commit
This commit is contained in:
commit
12ac4e1c0e
|
@ -0,0 +1,6 @@
|
|||
django-feed-manager is an application for Django which manages different RSS feeds and their lifetime.
|
||||
|
||||
This application has been developed to gather results of recurrent and automatized actions by multiple servers and allow different users to be updated using a standard format (which then allow automatic treatment of the notifications).
|
||||
|
||||
Feeds and feed's items can be created using HTTP Post requests. A cli python script is given in this repository to do that for you.
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
#!/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import sys
|
||||
import configparser
|
||||
|
||||
CONFIG_PATH = '/etc/cloud_reporter.conf'
|
||||
|
||||
|
||||
def generatePassword(length=20):
|
||||
import random
|
||||
import string
|
||||
random.seed(os.urandom(4096))
|
||||
chars = string.ascii_letters + string.digits + '"«»()@+-/*=%`°≠×÷−±@)[><—$,;.:…’'
|
||||
return ''.join(random.choice(chars) for i in range(length))
|
||||
|
||||
def getConfigParser():
|
||||
parser = configparser.ConfigParser()
|
||||
parser.read(CONFIG_PATH)
|
||||
return parser
|
||||
|
||||
def getCredential():
|
||||
section_name = 'credential'
|
||||
parser = getConfigParser()
|
||||
return {
|
||||
'username': parser.get(section_name, 'username'),
|
||||
'password': parser.get(section_name, 'password'),
|
||||
}
|
||||
|
||||
def subscribe():
|
||||
import socket
|
||||
data = {
|
||||
'username': socket.gethostname(),
|
||||
'password': generatePassword(),
|
||||
}
|
||||
response = requests.post('%s/createAccount/' % parser.get('server', 'base_url') , data=data)
|
||||
if response.status_code == requests.codes.ok:
|
||||
feed_id = json.loads(response.text)['feed_id']
|
||||
parser = getConfigParser()
|
||||
parser.add_section('feed')
|
||||
parser.set('feed', 'id', feed_id)
|
||||
parser.set('feed', 'username', data['username'])
|
||||
parser.set('feed', 'password', data['password'])
|
||||
with open(CONFIG_PATH, 'w') as config_file:
|
||||
parser.write(config_file)
|
||||
else:
|
||||
sys.exit('Bad status returned by PubSub server')
|
||||
|
||||
def __getActionReport():
|
||||
from collections import deque
|
||||
content_list = deque()
|
||||
|
||||
with open('/var/log/ansible', 'r') as log:
|
||||
log_line_list = log.readlines()
|
||||
log_line_list.reverse()
|
||||
for line in log_line_list:
|
||||
content_list.appendleft(line)
|
||||
if line.startswith('HEAD is now at'):
|
||||
break
|
||||
|
||||
content_list.appendleft('\n\n-------\n\n')
|
||||
|
||||
with open('/var/log/aptitude', 'r') as log:
|
||||
log_line_list = log.readlines()
|
||||
log_line_list.reverse()
|
||||
for line in log_line_list:
|
||||
content_list.appendleft(line)
|
||||
if line.startswith('Aptitude') and line.endswith(': log report\n'):
|
||||
break
|
||||
|
||||
return '<pre>%s</pre>' % (''.join(content_list),)
|
||||
|
||||
def post():
|
||||
parser = getConfigParser()
|
||||
data = {
|
||||
'username': parser.get('feed', 'username'),
|
||||
'password': parser.get('feed', 'password'),
|
||||
'link': 'http://exemple.com',
|
||||
'description': __getActionReport(),
|
||||
'pubDate': datetime.datetime.now().isoformat(),
|
||||
}
|
||||
data['title'] = 'Status Report for %s, %s' % (parser.get('feed', 'username'), data['pubDate'][:10])
|
||||
response = requests.post(
|
||||
'%s/%s/postItem/' % (parser.get('server', 'base_url'), parser.get('feed', 'id')),
|
||||
data=data
|
||||
)
|
||||
print(response.status_code)
|
||||
|
||||
|
||||
def createBaseConfig(**kw):
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.add_section('server')
|
||||
parser.set('server', 'base_url', 'https://me.wavrant.xyz/feed')
|
||||
with open(CONFIG_PATH, 'w') as config_file:
|
||||
parser.write(config_file)
|
||||
|
||||
def parseArguments():
|
||||
parser = argparse.ArgumentParser()
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
if not os.path.exists(CONFIG_PATH):
|
||||
createBaseConfig()
|
||||
|
||||
command_list = {
|
||||
'subscribe': subscribe,
|
||||
'post': post,
|
||||
}
|
||||
|
||||
command_list[sys.argv[1]]()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,6 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import Feed, Item
|
||||
|
||||
admin.site.register(Feed)
|
||||
admin.site.register(Item)
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FeedConfig(AppConfig):
|
||||
name = 'feed'
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2017-04-29 08:28
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Feed',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('link', models.URLField()),
|
||||
('description', models.TextField()),
|
||||
('language', models.CharField(max_length=5)),
|
||||
('maximum_length', models.IntegerField()),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Item',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('link', models.URLField()),
|
||||
('description', models.TextField()),
|
||||
('pubDate', models.DateTimeField()),
|
||||
('feed', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feedmanager.Feed')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,27 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
|
||||
class Feed(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
link = models.URLField()
|
||||
description = models.TextField()
|
||||
language = models.CharField(max_length=5)
|
||||
maximum_length = models.IntegerField()
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def addItem(self, **kw):
|
||||
Item.objects.create(feed=self, **kw)
|
||||
|
||||
|
||||
class Item(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
link = models.URLField()
|
||||
description = models.TextField()
|
||||
pubDate = models.DateTimeField()
|
||||
feed = models.ForeignKey('Feed', on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return "%s[%s]" % (self.feed, self.title)
|
|
@ -0,0 +1,51 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import Item
|
||||
|
||||
class PeriodTestCase(TestCase):
|
||||
|
||||
def test_createAccountAndPostOneItem(self):
|
||||
data = {
|
||||
'username': 'user',
|
||||
'password': 'pass',
|
||||
}
|
||||
|
||||
# Create an account
|
||||
res = self.client.post(
|
||||
'/feed/createAccount/',
|
||||
{
|
||||
'username': data['username'],
|
||||
'password': data['password']
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
feed_id = res.json()['feed_id']
|
||||
|
||||
# Validate user
|
||||
user_set = User.objects.filter(username=data['username'])
|
||||
self.assertEqual(len(user_set), 1)
|
||||
|
||||
user = user_set.first()
|
||||
user.is_active = True
|
||||
user.save()
|
||||
|
||||
# Post a feed item
|
||||
res = self.client.post(
|
||||
'/feed/%d/postItem/' % feed_id,
|
||||
{
|
||||
'username': data['username'],
|
||||
'password': data['password'],
|
||||
'title': 'Item Title',
|
||||
'link': 'Item Link',
|
||||
'description': 'Item Description',
|
||||
'pubDate': timezone.now().isoformat()[:10],
|
||||
}
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
self.assertEqual(
|
||||
len(Item.objects.all()), 1)
|
|
@ -0,0 +1,9 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'(?P<feed_id>[0-9]+)/$', views.printFeed, name='printFeed'),
|
||||
url(r'(?P<feed_id>[0-9]+)/postItem/$', views.postItem, name='postItem'),
|
||||
url(r'createAccount/$', views.createAccount, name='createAccount'),
|
||||
]
|
|
@ -0,0 +1,73 @@
|
|||
import PyRSS2Gen as RSS2
|
||||
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
|
||||
from .models import Feed
|
||||
|
||||
FEED_ELEMENT_LIST = ['title', 'link', 'description', 'language']
|
||||
ITEM_ELEMENT_LIST = ['title', 'link', 'description', 'pubDate']
|
||||
|
||||
def __getRSSItemList(item_list):
|
||||
rss_item_list = []
|
||||
for item in item_list:
|
||||
rss_item_list.append(
|
||||
RSS2.RSSItem(
|
||||
**{element: getattr(item, element, '')
|
||||
for element in ITEM_ELEMENT_LIST}
|
||||
)
|
||||
)
|
||||
return rss_item_list
|
||||
|
||||
|
||||
def printFeed(request, feed_id):
|
||||
feed = get_object_or_404(Feed, pk=feed_id)
|
||||
item_list = feed.item_set.order_by('pubDate')
|
||||
|
||||
return HttpResponse(
|
||||
RSS2.RSS2(
|
||||
items=__getRSSItemList(item_list),
|
||||
**{element: getattr(feed, element, '')
|
||||
for element in FEED_ELEMENT_LIST}
|
||||
).to_xml()
|
||||
)
|
||||
|
||||
@csrf_exempt
|
||||
def createAccount(request):
|
||||
if 'username' not in request.POST \
|
||||
or 'password' not in request.POST:
|
||||
return HttpResponse(status=500)
|
||||
user = User.objects.create_user(request.POST['username'], password=request.POST['password'], is_active=False)
|
||||
user.save()
|
||||
feed = Feed.objects.create(
|
||||
title = "Computer status for %s" % request.POST['username'],
|
||||
link = "",
|
||||
description = "",
|
||||
language = "en",
|
||||
maximum_length = 50,
|
||||
user=user,
|
||||
)
|
||||
feed.save()
|
||||
return JsonResponse({'feed_id': feed.id})
|
||||
|
||||
@csrf_exempt
|
||||
def postItem(request, feed_id):
|
||||
feed = get_object_or_404(Feed, pk=feed_id)
|
||||
item_attribute_dict = {}
|
||||
for item in ITEM_ELEMENT_LIST:
|
||||
try:
|
||||
item_attribute_dict[item] = request.POST[item]
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
user = authenticate(username=request.POST['username'], password=request.POST['password'])
|
||||
if user is None:
|
||||
return HttpResponse(status=401)
|
||||
except KeyError:
|
||||
return HttpResponse(status=500)
|
||||
feed.addItem(**item_attribute_dict)
|
||||
return HttpResponse(status=200)
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import os
|
||||
from setuptools import setup
|
||||
|
||||
# Utility function to read the README file.
|
||||
# Used for the long_description. It's nice, because now 1) we have a top level
|
||||
# README file and 2) it's easier to type in the README file than to put a raw
|
||||
# string in below ...
|
||||
def read(fname):
|
||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
setup(
|
||||
name = "django-feed-manager",
|
||||
version = "0.1",
|
||||
author = "Nicolas Wavrant",
|
||||
author_email = "nicolas.wavrant@gmail.com",
|
||||
description = ("A feed manager for Django, aiming to report automated tasks results in a standard format."),
|
||||
license = "BSD",
|
||||
keywords = "django feed rss",
|
||||
url = "https://github.com/Sebatyne/django-feed-manager",
|
||||
long_description = read('README.md'),
|
||||
install_requires = [
|
||||
'requests',
|
||||
],
|
||||
entry_points = {
|
||||
'console_scripts': [
|
||||
'reporter=cli.reporter:main',
|
||||
],
|
||||
},
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Topic :: Utilities",
|
||||
"License :: OSI Approved :: BSD License",
|
||||
],
|
||||
)
|
Loading…
Reference in New Issue