commit eee0487e0e27e37060df8e9c89bb36ccb23a1f54
Author: Kostas Papadimitriou <kpap@grnet.gr>
Date:   Tue Oct 18 18:52:11 2011 +0300

    Initial userdata commit

diff --git a/settings.d/00-apps.conf b/settings.d/00-apps.conf
index dbba969..b424ee5 100644
--- a/settings.d/00-apps.conf
+++ b/settings.d/00-apps.conf
@@ -17,6 +17,8 @@ INSTALLED_APPS = (
     'synnefo.logic',
     'synnefo.invitations',
     'synnefo.helpdesk',
+    'synnefo.userdata',
+
     'south'
 )
 
diff --git a/userdata/__init__.py b/userdata/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/userdata/migrations/0001_initial.py b/userdata/migrations/0001_initial.py
new file mode 100644
index 0000000..2b986be
--- /dev/null
+++ b/userdata/migrations/0001_initial.py
@@ -0,0 +1,55 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding model 'PublicKeyPair'
+        db.create_table('userdata_publickeypair', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['db.SynnefoUser'])),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('content', self.gf('django.db.models.fields.TextField')()),
+        ))
+        db.send_create_signal('userdata', ['PublicKeyPair'])
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'PublicKeyPair'
+        db.delete_table('userdata_publickeypair')
+
+
+    models = {
+        'db.synnefouser': {
+            'Meta': {'object_name': 'SynnefoUser'},
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'credit': ('django.db.models.fields.IntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'max_invitations': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+            'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+            'state': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '30'}),
+            'tmp_auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'tmp_auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
+            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+        },
+        'userdata.publickeypair': {
+            'Meta': {'object_name': 'PublicKeyPair'},
+            'content': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"})
+        }
+    }
+
+    complete_apps = ['userdata']
diff --git a/userdata/migrations/__init__.py b/userdata/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/userdata/models.py b/userdata/models.py
new file mode 100644
index 0000000..93c5926
--- /dev/null
+++ b/userdata/models.py
@@ -0,0 +1,24 @@
+from django.db import models
+from synnefo.db import models as synnefo_models
+
+User = synnefo_models.SynnefoUser
+
+class ProfileModel(models.Model):
+    """
+    Abstract model, provides a basic interface for models that store
+    user specific information
+    """
+
+    user = models.ForeignKey(User)
+
+    class Meta:
+        abstract = True
+
+
+class PublicKeyPair(ProfileModel):
+    """
+    Public key model
+    """
+    name = models.CharField(max_length=255, null=False, blank=False)
+    content = models.TextField()
+
diff --git a/userdata/rest.py b/userdata/rest.py
new file mode 100644
index 0000000..c087dee
--- /dev/null
+++ b/userdata/rest.py
@@ -0,0 +1,158 @@
+from django import http
+from django.template import RequestContext, loader
+from django.utils import simplejson as json
+from django.core import serializers
+
+# base view class
+# https://github.com/bfirsh/django-class-based-views/blob/master/class_based_views/base.py
+class View(object):
+    """
+    Intentionally simple parent class for all views. Only implements
+    dispatch-by-method and simple sanity checking.
+    """
+
+    method_names = ['GET', 'POST', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE']
+
+    def __init__(self, *args, **kwargs):
+        """
+        Constructor. Called in the URLconf; can contain helpful extra
+        keyword arguments, and other things.
+        """
+        # Go through keyword arguments, and either save their values to our
+        # instance, or raise an error.
+        for key, value in kwargs.items():
+            if key in self.method_names:
+                raise TypeError(u"You tried to pass in the %s method name as a "
+                                u"keyword argument to %s(). Don't do that."
+                                % (key, self.__class__.__name__))
+            if hasattr(self, key):
+                setattr(self, key, value)
+            else:
+                raise TypeError(u"%s() received an invalid keyword %r" % (
+                    self.__class__.__name__,
+                    key,
+                ))
+
+    def instance_to_dict(self, i, exclude_fields=[]):
+        """
+        Convert model instance to python dict
+        """
+        d = {}
+        for field in i._meta.get_all_field_names():
+            if field in exclude_fields:
+                continue
+
+            d[field] = i.__getattribute__(field)
+        return d
+
+    def qs_to_dict_iter(self, qs, exclude_fields=[]):
+        """
+        Convert queryset to an iterator of model instances dicts
+        """
+        for i in qs:
+            yield self.instance_to_dict(i, exclude_fields)
+
+    def json_response(self, data):
+        return http.HttpResponse(json.dumps(data))
+
+    @classmethod
+    def as_view(cls, *initargs, **initkwargs):
+        """
+        Main entry point for a request-response process.
+        """
+        def view(request, *args, **kwargs):
+            self = cls(*initargs, **initkwargs)
+            return self.dispatch(request, *args, **kwargs)
+        return view
+
+    def dispatch(self, request, *args, **kwargs):
+        # Try to dispatch to the right method for that; if it doesn't exist,
+        # raise a big error.
+        if hasattr(self, request.method.upper()):
+            self.request = request
+            self.args = args
+            self.kwargs = kwargs
+            data = request.raw_post_data
+
+            if request.method.upper() in ['POST', 'PUT']:
+                # Expect json data
+                if request.META.get('CONTENT_TYPE').startswith('application/json'):
+                    try:
+                        data = json.loads(data)
+                    except ValueError:
+                        raise http.HttpResponseServerError('Invalid JSON data.')
+                else:
+                    raise http.HttpResponseServerError('Unsupported Content-Type.')
+
+            return getattr(self, request.method.upper())(request, data, *args, **kwargs)
+        else:
+            allowed_methods = [m for m in self.method_names if hasattr(self, m)]
+            return http.HttpResponseNotAllowed(allowed_methods)
+
+
+class ResourceView(View):
+    method_names = ['GET', 'POST', 'PUT', 'DELETE']
+
+    model = None
+    exclude_fields = []
+
+    def queryset(self):
+        return self.model.objects.all()
+
+    def instance(self):
+        """
+        Retrieve selected instance based on url parameter
+
+        id parameter should be set in urlpatterns expression
+        """
+        try:
+            return self.queryset().get(pk=self.kwargs.get("id"))
+        except self.model.DoesNotExist:
+            raise http.Http404
+
+    def GET(self, request, data, *args, **kwargs):
+        return self.json_response(self.instance_to_dict(self.instance(),
+            self.exclude_fields))
+
+    def POST(self, request, data, *args, **kwargs):
+        pass
+
+    def DELETE(self, request, data, *args, **kwargs):
+        self.instance().delete()
+        return HttpResponse()
+
+
+class CollectionView(View):
+    method_names = ['GET', 'POST', 'PUT', 'DELETE']
+
+    model = None
+    exclude_fields = []
+
+    def queryset(self):
+        return self.model.objects.all()
+
+    def GET(self, request, data, *args, **kwargs):
+        return self.json_response(list(self.qs_to_dict_iter(self.queryset(),
+            self.exclude_fields)))
+
+    def PUT(self, request, data, *args, **kwargs):
+        pass
+
+    def DELETE(self, request, data, *args, **kwargs):
+        pass
+
+class UserResourceView(ResourceView):
+    """
+    Filter resource queryset for request user entries
+    """
+    def queryset(self):
+        return super(UserResourceView,
+                self).queryset().filter(user=self.request.user)
+
+class UserCollectionView(CollectionView):
+    """
+    Filter collection queryset for request user entries
+    """
+    def queryset(self):
+        return super(UserCollectionView, self).queryset().filter(user=self.request.user)
+
diff --git a/userdata/tests.py b/userdata/tests.py
new file mode 100644
index 0000000..3b848c1
--- /dev/null
+++ b/userdata/tests.py
@@ -0,0 +1,49 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+from django.conf import settings
+from django.test.client import Client
+from django.core.urlresolvers import clear_url_caches
+
+from synnefo.userdata.models import User
+from synnefo.userdata.models import *
+
+class AaiClient(Client):
+
+    def request(self, **request):
+        request['HTTP_X_AUTH_TOKEN'] = '46e427d657b20defe352804f0eb6f8a2'
+        return super(AaiClient, self).request(**request)
+
+class TestRestViews(TestCase):
+
+    fixtures = ['users']
+
+    def setUp(self):
+        settings.ROOT_URLCONF = 'synnefo.userdata.urls'
+        clear_url_caches()
+        self.client = AaiClient()
+        self.user = User.objects.get(pk=1)
+
+    def test_keys_collection_get(self):
+        resp = self.client.get("/keys/")
+        self.assertEqual(resp.content, "[]")
+
+        PublicKeyPair.objects.create(user=self.user, name="key pair 1",
+                content="content1")
+
+        resp = self.client.get("/keys/")
+        self.assertEqual(resp.content, """[{"content": "content1", "id": 1, "name": "key pair 1"}]""")
+
+        PublicKeyPair.objects.create(user=self.user, name="key pair 2",
+                content="content2")
+
+        resp = self.client.get("/keys/")
+        self.assertEqual(resp.content, """[{"content": "content1", "id": 1, "name": "key pair 1"}, {"content": "content2", "id": 2, "name": "key pair 2"}]""")
+
+    def test_keys_resourse_get(self):
+        pass
diff --git a/userdata/urls.py b/userdata/urls.py
new file mode 100644
index 0000000..780ca50
--- /dev/null
+++ b/userdata/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import *
+from synnefo.userdata import views
+
+urlpatterns = patterns('',
+    (r'^keys/$', views.PublicKeyPairCollectionView.as_view()),
+    (r'^keys/(?P<id>\d+)/$', views.PublicKeyPairResourceView.as_view()),
+)
diff --git a/userdata/views.py b/userdata/views.py
new file mode 100644
index 0000000..dedf97a
--- /dev/null
+++ b/userdata/views.py
@@ -0,0 +1,14 @@
+from django import http
+from django.template import RequestContext, loader
+from django.utils import simplejson as json
+
+from synnefo.userdata import rest
+from synnefo.userdata.models import PublicKeyPair
+
+class PublicKeyPairResourceView(rest.UserResourceView):
+    model = PublicKeyPair
+    exclude_fields = ["user"]
+
+class PublicKeyPairCollectionView(rest.UserCollectionView):
+    model = PublicKeyPair
+    exclude_fields = ["user"]
