[extensions-web: 16/30] Move from a single "popularity" field to a popularity table
- From: Jasper St. Pierre <jstpierre src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [extensions-web: 16/30] Move from a single "popularity" field to a popularity table
- Date: Thu, 26 Jan 2012 10:41:52 +0000 (UTC)
commit 52b5231b2730fe4da26455a6023a4956fd3036f2
Author: Jasper St. Pierre <jstpierre mecheye net>
Date: Fri Dec 23 17:37:58 2011 -0500
Move from a single "popularity" field to a popularity table
Tracking each item separately along with a datetime field, we can
now execute complex queries such as "what's the popularity of an
extension in the past week"
The migrations here don't match up to the model changes because of
some sillyness with south and rebase.
.../migrations/0012_add_extensionpopularityitem.py | 107 +++++++++++++++++++
.../migrations/0013_move_to_popularity_items.py | 109 ++++++++++++++++++++
sweettooth/extensions/models.py | 12 ++-
sweettooth/extensions/views.py | 34 ++++--
4 files changed, 246 insertions(+), 16 deletions(-)
---
diff --git a/sweettooth/extensions/migrations/0012_add_extensionpopularityitem.py b/sweettooth/extensions/migrations/0012_add_extensionpopularityitem.py
new file mode 100644
index 0000000..da9f1cf
--- /dev/null
+++ b/sweettooth/extensions/migrations/0012_add_extensionpopularityitem.py
@@ -0,0 +1,107 @@
+# 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 'ExtensionPopularityItem'
+ db.create_table('extensions_extensionpopularityitem', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('extension', self.gf('django.db.models.fields.related.ForeignKey')(related_name='popularity_items', to=orm['extensions.Extension'])),
+ ('offset', self.gf('django.db.models.fields.IntegerField')()),
+ ('date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ))
+ db.send_create_signal('extensions', ['ExtensionPopularityItem'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'ExtensionPopularityItem'
+ db.delete_table('extensions_extensionpopularityitem')
+
+
+ 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'})
+ },
+ 'extensions.extension': {
+ 'Meta': {'object_name': 'Extension'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'disables': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'downloads': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'enables': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'icon': ('django.db.models.fields.files.ImageField', [], {'default': "'/static/images/plugin.png'", 'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'popularity': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'screenshot': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100', 'blank': 'True'}),
+ 'slug': ('autoslug.fields.AutoSlugField', [], {'unique_with': '()', 'max_length': '50', 'populate_from': 'None', 'db_index': 'True'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200', 'db_index': 'True'})
+ },
+ 'extensions.extensionpopularityitem': {
+ 'Meta': {'object_name': 'ExtensionPopularityItem'},
+ 'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'extension': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'popularity_items'", 'to': "orm['extensions.Extension']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'offset': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'extensions.extensionversion': {
+ 'Meta': {'unique_together': "(('extension', 'version'),)", 'object_name': 'ExtensionVersion'},
+ 'extension': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'versions'", 'to': "orm['extensions.Extension']"}),
+ 'extra_json_fields': ('django.db.models.fields.TextField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'shell_versions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['extensions.ShellVersion']", 'symmetrical': 'False'}),
+ 'source': ('django.db.models.fields.files.FileField', [], {'max_length': '223'}),
+ 'status': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'version': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+ },
+ 'extensions.shellversion': {
+ 'Meta': {'object_name': 'ShellVersion'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'major': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'minor': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'point': ('django.db.models.fields.IntegerField', [], {})
+ }
+ }
+
+ complete_apps = ['extensions']
diff --git a/sweettooth/extensions/migrations/0013_move_to_popularity_items.py b/sweettooth/extensions/migrations/0013_move_to_popularity_items.py
new file mode 100644
index 0000000..4383b89
--- /dev/null
+++ b/sweettooth/extensions/migrations/0013_move_to_popularity_items.py
@@ -0,0 +1,109 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ "Write your forwards methods here."
+ for ext in orm.Extension.objects.all():
+ for i in xrange(ext.enables):
+ pop = orm.ExtensionPopularityItem(extension = ext,
+ offset = +1)
+ pop.save()
+
+ for i in xrange(ext.disables):
+ pop = orm.ExtensionPopularityItem(extension = ext,
+ offset = -1)
+ pop.save()
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+ epi = orm.ExtensionPopularityItem.objects
+ for ext in orm.Extension.objects.all():
+ ext.enables = epi.filter(extension=ext, offset=+1).count()
+ ext.disables = epi.filter(extension=ext, offset=-1).count()
+
+ 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'})
+ },
+ 'extensions.extension': {
+ 'Meta': {'object_name': 'Extension'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'disables': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'downloads': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'enables': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'icon': ('django.db.models.fields.files.ImageField', [], {'default': "'/static/images/plugin.png'", 'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'popularity': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'screenshot': ('sorl.thumbnail.fields.ImageField', [], {'max_length': '100', 'blank': 'True'}),
+ 'slug': ('autoslug.fields.AutoSlugField', [], {'unique_with': '()', 'max_length': '50', 'populate_from': 'None', 'db_index': 'True'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200', 'db_index': 'True'})
+ },
+ 'extensions.extensionpopularityitem': {
+ 'Meta': {'object_name': 'ExtensionPopularityItem'},
+ 'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'extension': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'popularity_items'", 'to': "orm['extensions.Extension']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'offset': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'extensions.extensionversion': {
+ 'Meta': {'unique_together': "(('extension', 'version'),)", 'object_name': 'ExtensionVersion'},
+ 'extension': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'versions'", 'to': "orm['extensions.Extension']"}),
+ 'extra_json_fields': ('django.db.models.fields.TextField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'shell_versions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['extensions.ShellVersion']", 'symmetrical': 'False'}),
+ 'source': ('django.db.models.fields.files.FileField', [], {'max_length': '223'}),
+ 'status': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'version': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+ },
+ 'extensions.shellversion': {
+ 'Meta': {'object_name': 'ShellVersion'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'major': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'minor': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'point': ('django.db.models.fields.IntegerField', [], {})
+ }
+ }
+
+ complete_apps = ['extensions']
diff --git a/sweettooth/extensions/models.py b/sweettooth/extensions/models.py
index f7d807a..cc34a18 100644
--- a/sweettooth/extensions/models.py
+++ b/sweettooth/extensions/models.py
@@ -73,10 +73,6 @@ class Extension(models.Model):
created = models.DateTimeField(auto_now_add=True)
downloads = models.PositiveIntegerField(default=0)
- enables = models.IntegerField(default=0)
- disables = models.IntegerField(default=0)
- popularity = models.IntegerField(default=0)
-
class Meta:
permissions = (
("can-modify-data", "Can modify extension data"),
@@ -144,6 +140,14 @@ class Extension(models.Model):
return reverse('extensions-detail', kwargs=dict(pk=self.pk,
slug=self.slug))
+
+
+class ExtensionPopularityItem(models.Model):
+ extension = models.ForeignKey(Extension, db_index=True,
+ related_name='popularity_items')
+ offset = models.IntegerField()
+ date = models.DateTimeField(auto_now_add=True)
+
class InvalidShellVersion(Exception):
pass
diff --git a/sweettooth/extensions/views.py b/sweettooth/extensions/views.py
index 1019f87..8c2ed41 100644
--- a/sweettooth/extensions/views.py
+++ b/sweettooth/extensions/views.py
@@ -1,7 +1,10 @@
+import datetime
+
from django.core.exceptions import ValidationError
from django.core.paginator import Paginator, InvalidPage
from django.core.urlresolvers import reverse
+from django.db.models import Sum
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import HttpResponseForbidden, HttpResponseServerError, Http404
@@ -92,14 +95,22 @@ def ajax_query_params_query(request):
if sort not in ('created', 'downloads', 'popularity', 'name'):
raise Http404()
- if 'order' in request.GET:
- order = request.GET['order']
- order = dict(desc='-', asc='').get(order, '-')
- else:
- # order by ASC for 'name', DESC for everything else
- order = dict(name='').get(sort, '-')
+ if sort == 'popularity':
+ # XXX - This filters out extensions which don't
+ # have any popularity items in the past week. Hopefully
+ # this will never happen in a real-world scenario
+ queryset = (queryset
+ .filter(popularity_items__date__gt=(datetime.datetime.now()-datetime.timedelta(days=7)))
+ .annotate(popularity=Sum('popularity_items__offset')))
+
+ queryset = queryset.order_by(sort)
+
+ # sort by DESC for name, ASC for everything else
+ default_order = dict(name='asc').get(sort, 'desc')
+
+ if request.GET.get('order', default_order) == 'desc':
+ queryset = queryset.reverse()
- queryset = queryset.order_by('%s%s' % (order, sort))
return queryset
@ajax_view
@@ -213,17 +224,16 @@ def ajax_adjust_popularity_view(request):
action = request.GET['action']
extension = models.Extension.objects.get(uuid=uuid)
+ pop = models.ExtensionPopularityItem(extension=extension)
if action == 'enable':
- extension.enables += 1
- extension.popularity += 1
+ pop.offset = +1
elif action == 'disable':
- extension.disables += 1
- extension.popularity -= 1
+ pop.offset = -1
else:
return HttpResponseServerError()
- extension.save()
+ pop.save()
@ajax_view
@require_POST
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]