From a336af46c2b70ea997f4d0bdb9790ceb5a092e9f Mon Sep 17 00:00:00 2001
From: Jan Dittberner <jan@dittberner.info>
Date: Mon, 29 Dec 2014 15:55:57 +0100
Subject: [PATCH] add taskresults app to handle celery task results

---
 docs/changelog.rst                            |  7 ++-
 docs/code.rst                                 | 26 ++++++++++
 gnuviechadmin/gnuviechadmin/settings/base.py  |  1 +
 gnuviechadmin/taskresults/__init__.py         |  5 ++
 gnuviechadmin/taskresults/admin.py            | 12 +++++
 .../taskresults/management/__init__.py        |  0
 .../management/commands/__init__.py           |  4 ++
 .../management/commands/fetch_taskresults.py  | 20 ++++++++
 .../taskresults/migrations/0001_initial.py    | 29 ++++++++++++
 .../taskresults/migrations/__init__.py        |  0
 gnuviechadmin/taskresults/models.py           | 47 +++++++++++++++++++
 11 files changed, 149 insertions(+), 2 deletions(-)
 create mode 100644 gnuviechadmin/taskresults/__init__.py
 create mode 100644 gnuviechadmin/taskresults/admin.py
 create mode 100644 gnuviechadmin/taskresults/management/__init__.py
 create mode 100644 gnuviechadmin/taskresults/management/commands/__init__.py
 create mode 100644 gnuviechadmin/taskresults/management/commands/fetch_taskresults.py
 create mode 100644 gnuviechadmin/taskresults/migrations/0001_initial.py
 create mode 100644 gnuviechadmin/taskresults/migrations/__init__.py
 create mode 100644 gnuviechadmin/taskresults/models.py

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 5ab9cd9..67fc19f 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,8 @@
 Changelog
 =========
 
+* :feature:`-` add new app :py:mod:`taskresults` that takes care of handling
+  asynchronous `Celery`_ results
 * :feature:`-` add new task :py:func:`osusers.tasks.delete_ldap_group` (needs
   gvaldap >= 0.2.0 on the LDAP side)
 * :feature:`-` add a `customer` field to :py:class:`osusers.models.User`
@@ -30,10 +32,11 @@ Changelog
 * :feature:`-` full test suite for osusers
 * :feature:`-` full test suite for managemails app
 * :feature:`-` full test suite for domains app
-* :feature:`-` `Celery <http://www.celeryproject.com/>`_ integration for ldap
-  synchronization
+* :feature:`-` `Celery`_ integration for ldap synchronization
 
 * :release:`0.1 <2014-05-25>`
 * :feature:`-` initial model code for os users
 * :feature:`-` initial model code for mail address and mailbox management
 * :feature:`-` initial model code for domains
+
+.. _Celery: http://www.celeryproject.org/
diff --git a/docs/code.rst b/docs/code.rst
index db64b7f..eb4f62f 100644
--- a/docs/code.rst
+++ b/docs/code.rst
@@ -149,3 +149,29 @@ provides some functionality that is common to all gnuviechadmin subprojects.
 .. autotask:: osusers.tasks.remove_ldap_user_from_group
 .. autotask:: osusers.tasks.setup_file_mail_userdir
 .. autotask:: osusers.tasks.setup_file_sftp_userdir
+
+
+:py:mod:`taskresults` app
+=========================
+
+.. automodule:: taskresults
+
+:py:mod:`admin <taskresults.admin>`
+-----------------------------------
+
+.. automodule:: taskresults.admin
+
+:py:mod:`management.commands <taskresults.management.commands>`
+---------------------------------------------------------------
+
+.. automodule:: taskresults.management.commands
+
+:py:mod:`fetch_taskresults <taskresult.management.commands.fetch_taskresults>`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. automodule:: taskresults.management.commands.fetch_taskresults
+
+:py:mod:`models <taskresults.models>`
+-------------------------------------
+
+.. automodule:: taskresults.models
diff --git a/gnuviechadmin/gnuviechadmin/settings/base.py b/gnuviechadmin/gnuviechadmin/settings/base.py
index b770e8d..e30df47 100644
--- a/gnuviechadmin/gnuviechadmin/settings/base.py
+++ b/gnuviechadmin/gnuviechadmin/settings/base.py
@@ -224,6 +224,7 @@ DJANGO_APPS = (
 
 # Apps specific for this project go here.
 LOCAL_APPS = (
+    'taskresults',
     'domains',
     'osusers',
     'managemails',
diff --git a/gnuviechadmin/taskresults/__init__.py b/gnuviechadmin/taskresults/__init__.py
new file mode 100644
index 0000000..0b3d916
--- /dev/null
+++ b/gnuviechadmin/taskresults/__init__.py
@@ -0,0 +1,5 @@
+"""
+This is the taskresults app that is used for storing the results from
+asynchronous `Celery <http://www.celeryproject.org>`_ tasks.
+
+"""
diff --git a/gnuviechadmin/taskresults/admin.py b/gnuviechadmin/taskresults/admin.py
new file mode 100644
index 0000000..cd47dc8
--- /dev/null
+++ b/gnuviechadmin/taskresults/admin.py
@@ -0,0 +1,12 @@
+"""
+This module defines the admin interface for the taskresults app.
+
+"""
+from __future__ import absolute_import
+
+from django.contrib import admin
+
+from .models import TaskResult
+
+
+admin.site.register(TaskResult)
diff --git a/gnuviechadmin/taskresults/management/__init__.py b/gnuviechadmin/taskresults/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gnuviechadmin/taskresults/management/commands/__init__.py b/gnuviechadmin/taskresults/management/commands/__init__.py
new file mode 100644
index 0000000..4e6e692
--- /dev/null
+++ b/gnuviechadmin/taskresults/management/commands/__init__.py
@@ -0,0 +1,4 @@
+"""
+This module defines management commands for the taskresults app.
+
+"""
diff --git a/gnuviechadmin/taskresults/management/commands/fetch_taskresults.py b/gnuviechadmin/taskresults/management/commands/fetch_taskresults.py
new file mode 100644
index 0000000..8ccbd78
--- /dev/null
+++ b/gnuviechadmin/taskresults/management/commands/fetch_taskresults.py
@@ -0,0 +1,20 @@
+"""
+This model contains the implementation of a management command to fetch the
+results of all `Celery <http://www.celeryproject.org/>`_ tasks that are not
+marked as finished yet.
+
+"""
+from __future__ import unicode_literals
+
+from django.core.management.base import BaseCommand
+
+from taskresults.models import TaskResult
+
+
+class Command(BaseCommand):
+    help = "fetch task results"
+
+    def handle(self, *args, **options):
+        for taskresult in TaskResult.objects.filter(finished=False):
+            taskresult.fetch_result()
+            taskresult.save()
diff --git a/gnuviechadmin/taskresults/migrations/0001_initial.py b/gnuviechadmin/taskresults/migrations/0001_initial.py
new file mode 100644
index 0000000..7c405be
--- /dev/null
+++ b/gnuviechadmin/taskresults/migrations/0001_initial.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='TaskResult',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('task_id', models.CharField(max_length=36, verbose_name='Task id')),
+                ('task_name', models.CharField(max_length=64, verbose_name='Task name')),
+                ('result', models.TextField(verbose_name='Task result')),
+                ('finished', models.BooleanField(default=False)),
+                ('state', models.CharField(max_length=16, verbose_name='Task state')),
+            ],
+            options={
+                'verbose_name': 'Task result',
+                'verbose_name_plural': 'Task results',
+            },
+            bases=(models.Model,),
+        ),
+    ]
diff --git a/gnuviechadmin/taskresults/migrations/__init__.py b/gnuviechadmin/taskresults/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gnuviechadmin/taskresults/models.py b/gnuviechadmin/taskresults/models.py
new file mode 100644
index 0000000..7178c84
--- /dev/null
+++ b/gnuviechadmin/taskresults/models.py
@@ -0,0 +1,47 @@
+"""
+This model defines the database models to handle Celery AsyncResults.
+
+"""
+from __future__ import unicode_literals
+
+from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+from django.utils.translation import ugettext as _
+
+from gnuviechadmin.celery import app
+
+
+class TaskResultManager(models.Manager):
+    def create_task_result(self, asyncresult, name):
+        taskresult = self.create(task_id=asyncresult.id, task_name=name)
+        return taskresult
+
+
+@python_2_unicode_compatible
+class TaskResult(models.Model):
+    task_id = models.CharField(_('Task id'), max_length=36)
+    task_name = models.CharField(_('Task name'), max_length=64)
+    result = models.TextField(_('Task result'))
+    finished = models.BooleanField(default=False)
+    state = models.CharField(_('Task state'), max_length=16)
+
+    objects = TaskResultManager()
+
+    class Meta:
+        verbose_name = _('Task result')
+        verbose_name_plural = _('Task results')
+
+    def __str__(self):
+        return "{task_name} ({task_id}): {finished}".format(
+            task_name=self.task_name,
+            task_id=self.task_id,
+            finished=_('yes') if self.finished else _('no')
+        )
+
+    def fetch_result(self):
+        if not self.finished:
+            ar = app.AsyncResult(self.task_id)
+            res = ar.get(no_ack=True, timeout=1)
+            self.result = str(res)
+            self.state = ar.state
+            self.finished = True