=== modified file 'src/recommender/api/forms.py'
--- src/recommender/api/forms.py	2012-06-22 19:55:46 +0000
+++ src/recommender/api/forms.py	2012-08-20 14:19:20 +0000
@@ -22,11 +22,13 @@
 __metaclass__ = type
 __all__ = [
     'RecommendationFeedbackForm',
+    'RemoveAppForm',
+    'ImplicitFeedbackForm',
 ]
 
 from django import forms
 
-from recommender.models import UserRecommendation
+from recommender.models import UserRecommendation, ImplicitFeedback
 
 
 class RecommendationFeedbackForm(forms.ModelForm):
@@ -41,3 +43,9 @@
 class RemoveAppForm(forms.Form):
     app = forms.CharField(max_length=64)
     remove = forms.BooleanField(required=False)
+
+
+class ImplicitFeedbackForm(forms.ModelForm):
+    class Meta:
+        model = ImplicitFeedback
+        fields = ('package_name', 'action')

=== modified file 'src/recommender/api/handlers.py'
--- src/recommender/api/handlers.py	2012-06-22 19:55:46 +0000
+++ src/recommender/api/handlers.py	2012-08-20 14:19:20 +0000
@@ -25,6 +25,8 @@
     'ProfileHandler',
     'MeHandler',
     'AppHandler',
+    'FeedbackHandler',
+    'ImplicitFeedbackHandler',
 ]
 
 import json
@@ -48,6 +50,7 @@
 from .forms import (
     RecommendationFeedbackForm,
     RemoveAppForm,
+    ImplicitFeedbackForm,
 )
 
 
@@ -184,6 +187,25 @@
         return 'ok'
 
 
+class ImplicitFeedbackHandler(BaseHandler):
+    allowed_methods = ('POST',)
+
+    def create(self, request):
+        if not hasattr(request, 'data'):
+            return HttpResponseBadRequest('Invalid data submitted')
+
+        form = ImplicitFeedbackForm(request.data)
+        if not form.is_valid():
+            result = serialize_form_errors(form)
+            return HttpResponseBadRequest(result)
+
+        feedback = form.save(commit=False)
+        feedback.user = request.user
+        feedback.save()
+
+        return 'ok'
+
+
 class RemoveHandler(BaseHandler):
     allowed_methods = ('POST',)
 

=== modified file 'src/recommender/api/urls.py'
--- src/recommender/api/urls.py	2012-06-22 19:55:46 +0000
+++ src/recommender/api/urls.py	2012-08-20 14:19:20 +0000
@@ -39,6 +39,7 @@
 from .handlers import (
     AppHandler,
     FeedbackHandler,
+    ImplicitFeedbackHandler,
     MeHandler,
     ProfileHandler,
     RemoveHandler,
@@ -69,6 +70,8 @@
 top_resource = dont_vary(cached_resource(Resource(handler=TopHandler)))
 feedback_resource = never_cache(Resource(handler=FeedbackHandler,
                                 authentication=auth))
+implicit_feedback_resource = never_cache(
+    Resource(handler=ImplicitFeedbackHandler, authentication=auth))
 remove_resource = never_cache(Resource(handler=RemoveHandler,
                               authentication=auth))
 
@@ -89,5 +92,7 @@
     url(r'^recommend_top/$', top_resource,
         name='api-recommend-top'),
     url(r'^feedback/$', feedback_resource, name='api-feedback'),
+    url(r'^implicit_feedback/$', implicit_feedback_resource,
+        name='api-feedback-implicit'),
     url(r'^remove_app/$', remove_resource, name='api-remove-app'),
 )

=== added file 'src/recommender/migrations/0016_add_implicitfeedback.py'
--- src/recommender/migrations/0016_add_implicitfeedback.py	1970-01-01 00:00:00 +0000
+++ src/recommender/migrations/0016_add_implicitfeedback.py	2012-08-20 14:19:20 +0000
@@ -0,0 +1,163 @@
+# encoding: utf-8
+from south.db import db
+from south.v2 import SchemaMigration
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        db.create_table('recommender_implicitfeedback', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
+            ('package_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('action', self.gf('django.db.models.fields.CharField')(max_length=20)),
+        ))
+        db.send_create_signal('recommender', ['ImplicitFeedback'])
+
+    def backwards(self, orm):
+        db.delete_table('recommender_implicitfeedback')
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'recommender.apprecommendation': {
+            'Meta': {'object_name': 'AppRecommendation'},
+            'data': ('django.db.models.fields.TextField', [], {}),
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}),
+            'feedback': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['recommender.Package']"}),
+            'rid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
+        },
+        'recommender.consumer': {
+            'Meta': {'object_name': 'Consumer'},
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'secret': ('django.db.models.fields.CharField', [], {'default': "'MPwSyovHBlpbsiAutqLwyXzsFroEfJ'", 'max_length': '255', 'blank': 'True'}),
+            'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
+        },
+        'recommender.implicitfeedback': {
+            'Meta': {'object_name': 'ImplicitFeedback'},
+            'action': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'recommender.machineprofile': {
+            'Meta': {'object_name': 'MachineProfile'},
+            'data': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+        },
+        'recommender.nonce': {
+            'Meta': {'object_name': 'Nonce'},
+            'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['recommender.Consumer']"}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['recommender.Token']"})
+        },
+        'recommender.package': {
+            'Meta': {'unique_together': "(('name', 'app_name'),)", 'object_name': 'Package'},
+            'app_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'wilson_score': ('django.db.models.fields.FloatField', [], {'default': '0.0'})
+        },
+        'recommender.personidentification': {
+            'Meta': {'object_name': 'PersonIdentification'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'rnr_username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True'})
+        },
+        'recommender.ratingsandreviewsdataitem': {
+            'Meta': {'ordering': "('date_created',)", 'unique_together': "(('package', 'person_identification'),)", 'object_name': 'RatingsAndReviewsDataItem'},
+            'date_created': ('django.db.models.fields.DateTimeField', [], {}),
+            'distroseries': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
+            'origin': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['recommender.Package']", 'null': 'True'}),
+            'person_identification': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['recommender.PersonIdentification']"}),
+            'rating': ('django.db.models.fields.IntegerField', [], {}),
+            'review_text': ('django.db.models.fields.TextField', [], {}),
+            'slope_one_used': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'recommender.removedapp': {
+            'Meta': {'object_name': 'RemovedApp'},
+            'app': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['recommender.Package']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'recommender.slopeonefrequency': {
+            'Meta': {'object_name': 'SlopeOneFrequency'},
+            'difference': ('django.db.models.fields.FloatField', [], {}),
+            'frequency': ('django.db.models.fields.IntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package_1': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['recommender.Package']"}),
+            'package_2': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['recommender.Package']"})
+        },
+        'recommender.token': {
+            'Meta': {'object_name': 'Token'},
+            'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['recommender.Consumer']"}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'default': "'KWBATTYcabhHCRPupLeaPxRTzfFIzdiSrEYjNtEGkYfMTnwQoC'", 'max_length': '50', 'primary_key': 'True'}),
+            'token_secret': ('django.db.models.fields.CharField', [], {'default': "'rwzMGwfoyQSfUeQcrwDwwTALbWpddIVmgiAHCfTdQyQjkZwikS'", 'max_length': '50'}),
+            'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+        },
+        'recommender.userprofile': {
+            'Meta': {'object_name': 'UserProfile'},
+            'data': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'recommender.userrecommendation': {
+            'Meta': {'object_name': 'UserRecommendation'},
+            'data': ('django.db.models.fields.TextField', [], {}),
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}),
+            'feedback': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'rid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        }
+    }
+
+    complete_apps = ['recommender']

=== modified file 'src/recommender/models/recommendations.py'
--- src/recommender/models/recommendations.py	2012-06-22 19:55:46 +0000
+++ src/recommender/models/recommendations.py	2012-08-20 14:19:20 +0000
@@ -27,6 +27,7 @@
     'RemovedApp',
     'UserProfile',
     'UserRecommendation',
+    'ImplicitFeedback',
 ]
 
 from datetime import datetime
@@ -83,3 +84,12 @@
 
     class Meta:
         app_label = 'recommender'
+
+
+class ImplicitFeedback(models.Model):
+    user = models.ForeignKey(User)
+    package_name = models.CharField(max_length=255)
+    action = models.CharField(max_length=20)
+
+    class Meta:
+        app_label = 'recommender'

=== modified file 'src/recommender/tests/test_api.py'
--- src/recommender/tests/test_api.py	2012-06-22 19:55:46 +0000
+++ src/recommender/tests/test_api.py	2012-08-20 14:19:20 +0000
@@ -26,6 +26,7 @@
     'ProfileTestCase',
     'RecommendAppTestCase',
     'RecommendationFeedbackTestCase',
+    'ImplicitFeedbackTestCase',
     'ObsoleteRecommendMeTestCase',
     'RecommendMeTestCase',
     'RecommendTopTestCase',
@@ -52,6 +53,7 @@
     Token,
     UserProfile,
     UserRecommendation,
+    ImplicitFeedback,
 )
 from .helpers import (
     create_oauth_token_and_consumer,
@@ -510,6 +512,70 @@
         self.assertEqual('"ok"', response.content)
 
 
+class ImplicitFeedbackTestCase(TestCase):
+
+    def setUp(self):
+        super(ImplicitFeedbackTestCase, self).setUp()
+        self.token, self.consumer = create_oauth_token_and_consumer(
+            self.factory)
+        self.url = reverse('api-feedback-implicit')
+        self.header = header_from_token(self.url, self.token, self.consumer)
+
+    def test_implicit_feedback_isnt_anonymous(self):
+        response = self.client.post(self.url)
+        self.assertEqual(401, response.status_code)
+
+    def test_implicit_feedback_requires_json_content_type(self):
+        response = self.client.post(
+            self.url, data={'id': 'foobar'}, **self.header)
+        self.assertEqual('Invalid data submitted', response.content)
+        self.assertEqual(400, response.status_code)
+
+    def test_implicit_feedback_requires_valid_json(self):
+        response = self.client.post(
+            self.url, data='foobar',
+            content_type='application/json', **self.header)
+        self.assertEqual('Bad Request', response.content)
+        self.assertEqual(400, response.status_code)
+
+    def test_implicit_feedback_requires_valid_args(self):
+        response = self.client.post(
+            self.url, data='{"action": "b"}',
+            content_type='application/json', **self.header)
+        self.assertEqual(
+            '{"package_name": ["This field is required."]}',
+            response.content)
+        self.assertEqual(400, response.status_code)
+
+    def test_implicit_feedback_success(self):
+        data = json.dumps({
+            'package_name': "foobar",
+            'action': "installed",
+        })
+        response = self.client.post(
+            self.url, data=data, content_type='application/json',
+            **self.header)
+        self.assertEqual(200, response.status_code)
+        self.assertEqual('"ok"', response.content)
+
+    def test_implicit_feedback_sets_user_on_the_model(self):
+        data = json.dumps({
+            'package_name': "foobar",
+            'action': "installed",
+        })
+        self.client.post(
+            self.url, data=data, content_type='application/json',
+            **self.header)
+        self.assertEqual(
+            1,
+            ImplicitFeedback.objects.filter(
+                user=self.consumer.user,
+                package_name="foobar",
+                action="installed"
+            ).count()
+        )
+
+
 class RemoveAppTestCase(TestCase):
     def setUp(self):
         super(RemoveAppTestCase, self).setUp()

