diff --git a/dr_botzo/dispatch/__init__.py b/dr_botzo/dispatch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dr_botzo/dispatch/admin.py b/dr_botzo/dispatch/admin.py new file mode 100644 index 0000000..21c55ed --- /dev/null +++ b/dr_botzo/dispatch/admin.py @@ -0,0 +1,8 @@ +"""Manage dispatch models in the admin interface.""" + +from django.contrib import admin + +from dispatch.models import Dispatcher + + +admin.site.register(Dispatcher) diff --git a/dr_botzo/dispatch/migrations/0001_initial.py b/dr_botzo/dispatch/migrations/0001_initial.py new file mode 100644 index 0000000..2725b01 --- /dev/null +++ b/dr_botzo/dispatch/migrations/0001_initial.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Dispatcher', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('key', models.CharField(unique=True, max_length=16)), + ('type', models.CharField(max_length=16, choices=[(b'privmsg', b'IRC privmsg'), (b'file', b'Write to file')])), + ('destination', models.CharField(max_length=200)), + ], + ), + ] diff --git a/dr_botzo/dispatch/migrations/__init__.py b/dr_botzo/dispatch/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dr_botzo/dispatch/models.py b/dr_botzo/dispatch/models.py new file mode 100644 index 0000000..919da0f --- /dev/null +++ b/dr_botzo/dispatch/models.py @@ -0,0 +1,30 @@ +"""Track dispatcher configurations.""" + +import logging + +from django.db import models + + +log = logging.getLogger('dispatch.models') + + +class Dispatcher(models.Model): + + """Handle incoming API requests and do something with them.""" + + PRIVMSG_TYPE = 'privmsg' + FILE_TYPE = 'file' + + TYPE_CHOICES = ( + (PRIVMSG_TYPE, "IRC privmsg"), + (FILE_TYPE, "Write to file"), + ) + + key = models.CharField(max_length=16, unique=True) + type = models.CharField(max_length=16, choices=TYPE_CHOICES) + destination = models.CharField(max_length=200) + + def __unicode__(self): + """String representation.""" + + return u"{0:s} -> {1:s}".format(self.key, self.destination) diff --git a/dr_botzo/dispatch/serializers.py b/dr_botzo/dispatch/serializers.py new file mode 100644 index 0000000..f14db08 --- /dev/null +++ b/dr_botzo/dispatch/serializers.py @@ -0,0 +1,17 @@ +"""Serializers for the dispatcher API objects.""" + +from rest_framework import serializers + +from dispatch.models import Dispatcher + + +class DispatcherSerializer(serializers.ModelSerializer): + + class Meta: + model = Dispatcher + fields = ('id', 'key', 'type', 'destination') + + +class DispatchMessageSerializer(serializers.Serializer): + + message = serializers.CharField() diff --git a/dr_botzo/dispatch/urls.py b/dr_botzo/dispatch/urls.py new file mode 100644 index 0000000..58c6a79 --- /dev/null +++ b/dr_botzo/dispatch/urls.py @@ -0,0 +1,12 @@ +"""URL patterns for the dispatcher API.""" + +from django.conf.urls import patterns, url + +from dispatch.views import DispatchMessage, DispatcherList, DispatcherDetail + + +urlpatterns = patterns('dispatch.views', + url(r'^api/dispatchers/$', DispatcherList.as_view(), name='dispatch_api_dispatchers'), + url(r'^api/dispatchers/(?P[0-9]+)/$', DispatcherDetail.as_view(), name='dispatch_api_dispatcher_detail'), + url(r'^api/dispatchers/(?P[0-9]+)/message$', DispatchMessage.as_view(), name='dispatch_api_dispatch_message'), +) diff --git a/dr_botzo/dispatch/views.py b/dr_botzo/dispatch/views.py new file mode 100644 index 0000000..bab2e0c --- /dev/null +++ b/dr_botzo/dispatch/views.py @@ -0,0 +1,72 @@ +"""Handle dispatcher API requests.""" + +from __future__ import unicode_literals + +import logging +import os +import xmlrpclib + +from django.conf import settings +from django.http import Http404 +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import generics, status + +from dispatch.models import Dispatcher +from dispatch.serializers import DispatchMessageSerializer, DispatcherSerializer + + +log = logging.getLogger('dispatch.views') + + +class DispatcherList(generics.ListAPIView): + + """List all dispatchers.""" + + queryset = Dispatcher.objects.all() + serializer_class = DispatcherSerializer + + +class DispatcherDetail(generics.RetrieveAPIView): + + """Detail the given dispatcher.""" + + queryset = Dispatcher.objects.all() + serializer_class = DispatcherSerializer + + +class DispatchMessage(APIView): + + """Send a message to the given dispatcher.""" + + queryset = Dispatcher.objects.none() + + def get_object(self, pk): + try: + return Dispatcher.objects.get(pk=pk) + except Dispatcher.DoesNotExist: + raise Http404 + + def get(self, request, pk, format=None): + dispatcher = self.get_object(pk) + return Response({'message': ""}) + + def post(self, request, pk, format=None): + dispatcher = self.get_object(pk) + serializer = DispatchMessageSerializer(data=request.data) + if serializer.is_valid(): + if dispatcher.type == Dispatcher.PRIVMSG_TYPE: + bot_url = 'http://{0:s}:{1:d}/'.format(settings.IRCBOT_XMLRPC_HOST, settings.IRCBOT_XMLRPC_PORT) + bot = xmlrpclib.ServerProxy(bot_url) + log.debug("sending '%s' to channel %s", serializer.data['message'], dispatcher.destination) + bot.privmsg(dispatcher.destination, serializer.data['message']) + return Response({'status': "OK"}) + elif dispatcher.type == Dispatcher.FILE_TYPE: + filename = os.path.abspath(dispatcher.destination) + log.debug("sending '%s' to file %s", serializer.data['message'], filename) + with open(filename, 'w') as f: + f.write(serializer.data['message']) + f.write('\n') + return Response({'status': "OK"}) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/dr_botzo/dr_botzo/settings.py b/dr_botzo/dr_botzo/settings.py index d857502..6999a62 100644 --- a/dr_botzo/dr_botzo/settings.py +++ b/dr_botzo/dr_botzo/settings.py @@ -37,7 +37,9 @@ INSTALLED_APPS = ( 'django.contrib.staticfiles', 'django_extensions', 'adminplus', + 'rest_framework', 'countdown', + 'dispatch', 'facts', 'ircbot', 'karma', @@ -121,6 +123,15 @@ STATICFILES_DIRS = ( ) +REST_FRAMEWORK = { + # Use Django's standard `django.contrib.auth` permissions, + # or allow read-only access for unauthenticated users. + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' + ] +} + + # IRC bot stuff # tuple of hostname, port number, and password (or None) diff --git a/dr_botzo/dr_botzo/urls.py b/dr_botzo/dr_botzo/urls.py index baef8e8..6196d80 100644 --- a/dr_botzo/dr_botzo/urls.py +++ b/dr_botzo/dr_botzo/urls.py @@ -9,6 +9,7 @@ admin.autodiscover() urlpatterns = patterns('', url(r'^$', 'dr_botzo.views.home', name='home'), + url(r'^dispatch/', include('dispatch.urls')), url(r'^markov/', include('markov.urls')), url(r'^races/', include('races.urls')), diff --git a/requirements.txt b/requirements.txt index c760540..09e2f26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,8 @@ Django==1.8.1 +django-adminplus==0.3 django-extensions==1.5.3 +django-filter==0.10.0 +djangorestframework==3.1.3 httplib2==0.7.4 inflect==0.2.5 irc==12.1.4 @@ -13,6 +16,7 @@ jaraco.logging==1.2 jaraco.text==1.3 logilab-astng==0.24.0 logilab-common==0.58.1 +Markdown==2.6.2 more-itertools==2.2 MySQL-python==1.2.3 oauth2==1.5.211 @@ -29,4 +33,3 @@ tempora==1.3 twython==3.0.0 yg.lockfile==2.0 zc.lockfile==1.1.0 -django-adminplus==0.3