From 9a2a7bbfcfa36ffcb5196337c74d231d8dae753c Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Thu, 5 Apr 2012 17:03:10 +0200 Subject: [PATCH] making room for django-activity-stream --- INSTALL | 5 ++ VISION.txt | 38 +++++++------ agora_site/agora_core/admin.py | 10 ++-- .../agora_core/migrations/0001_initial.py | 56 ++++++------------- agora_site/agora_core/models.py | 20 ++++--- agora_site/agora_core/urls.py | 21 +++++++ agora_site/agora_core/views.py | 43 ++++++++++++++ agora_site/settings.py | 14 +++++ agora_site/urls.py | 19 +++++++ agora_site/views.py | 16 +++++- dependencies.txt | 1 + 11 files changed, 173 insertions(+), 70 deletions(-) create mode 100644 agora_site/agora_core/urls.py create mode 100644 agora_site/agora_core/views.py diff --git a/INSTALL b/INSTALL index dd5c0354..bd04868d 100644 --- a/INSTALL +++ b/INSTALL @@ -40,6 +40,11 @@ available yet $ ./manage.py loaddata initial-data.json +Agora relies in django-reversion for storing historical data. To make it work, +you first need to create the initialize it: + + $ ./manage.py createinitialrevisions + That's it! Start the webserver with: $ ./manage.py runserver diff --git a/VISION.txt b/VISION.txt index 9c6ab1cd..bf26f524 100644 --- a/VISION.txt +++ b/VISION.txt @@ -2,28 +2,29 @@ These are the different things that should be registered as activity: -* Agora X was created by User Y -* Agora X was closed by User Y -* Agora X biography was modified by User Y -* Agora X settings were modified by User Y -* User X joined Agora Y -* User X leaved Agora Y +* Agora X was created by User Y: actor=userY, verb="created", object=agoraX +* Agora X was closed by User Y: actor=user, verb="closed", object=agoraX +* Agora X biography was modified by User Y: actor=userY, verb="modified biography", object=agoraX +* Agora X settings were modified by User Y: actor=userY, verb="modified settings", object=agoraX -* Election Y in Agora Z created by User X -* Election Y in Agora Z modified by User X -* Election Y in Agora Z was confirmed by User X -* Election X in Agora Y finished, winning option was Z with ZZ % of votes +* User X joined Agora Y: actor=userX, verb="joined", object=agoraY +* User X leaved Agora Y: actor=userX, verb="leaved", object=agoraY -* User X voted in election Y in Agora Z -* User X updated his vote in election Y in Agora Z -* User X delegated in User Y in Agora Z -* User X removed his delegation in User Y in Agora Z +* Election Y in Agora Z created by User X: actor=userX, verb="created", object=electionY, target=agoraZ +* Election Y in Agora Z modified by User X: actor=userX, verb="modifed", object=electionY, target=agoraZ +* Election Y in Agora Z was confirmed by User X: actor=userX, verb="confirmed", object=electionY, target=agoraZ +* Election X in Agora Y finished, winning option was Z with ZZ % of votes: actor=adminsX, verb="published results", object=electionY, target=agoraZ -* User X commented on Agora Y -* User X commented on Voting Y from Agora Z -* User X commented on User Y -* User X updated his biography +* User X voted in election Y in Agora Z: actor=userX, verb="voted", object=electionY, target=agoraZ +* User X updated his vote in election Y in Agora Z: actor=userX, verb="changed his vote", object=electionY, target=agoraZ +* User X delegated in User Y in Agora Z: actor=userX, verb="delegated", object=userY, target=agoraZ +* User X removed his delegation in User Y in Agora Z: actor=userX, verb="removed delegation", object=userY, target=agoraZ + +* User X commented on Agora Y: actor=userX, verb="commented", object=agoraY +* User X commented on Voting Y from Agora Z: actor=userX, verb="commented", object=votingY, target=agoraZ +* User X commented on User Y: actor=userX, verb="commented", object=userY +* User X updated his biography: actor=userX, verb="updated his biography" Filtering can be done from relevant log items. Relevant log items are: @@ -39,3 +40,4 @@ For an agora timeline: For an election timeline: * updates related to the election * delegations in the agora related to the election + diff --git a/agora_site/agora_core/admin.py b/agora_site/agora_core/admin.py index 5405799f..6312a4fc 100644 --- a/agora_site/agora_core/admin.py +++ b/agora_site/agora_core/admin.py @@ -20,14 +20,16 @@ from django.contrib.auth.models import User, Group from django.contrib.auth.admin import UserAdmin from agora_site.agora_core.models import Profile, Agora, Election, CastVote +from reversion.admin import VersionAdmin admin.site.unregister(User) class ProfileInline(admin.StackedInline): model = Profile -class ProfileAdmin(UserAdmin): +class ProfileAdmin(VersionAdmin): inlines = [ProfileInline] + model = User fieldsets = ( (None, {'fields': ('username', 'password')}), (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}), @@ -38,8 +40,8 @@ class ProfileAdmin(UserAdmin): admin.site.register(User, ProfileAdmin) -admin.site.register(Agora) +admin.site.register(Agora, VersionAdmin) -admin.site.register(Election) +admin.site.register(Election, VersionAdmin) -admin.site.register(CastVote) +admin.site.register(CastVote, VersionAdmin) diff --git a/agora_site/agora_core/migrations/0001_initial.py b/agora_site/agora_core/migrations/0001_initial.py index c17a1fc8..ac320a66 100644 --- a/agora_site/agora_core/migrations/0001_initial.py +++ b/agora_site/agora_core/migrations/0001_initial.py @@ -15,18 +15,11 @@ def forwards(self, orm): ('short_description', self.gf('django.db.models.fields.CharField')(max_length=140)), ('biography', self.gf('django.db.models.fields.TextField')()), ('user_type', self.gf('django.db.models.fields.CharField')(default='PASSWORD', max_length=50)), + ('last_activity_read_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), ('extra', self.gf('agora_site.misc.utils.JSONField')(null=True)), )) db.send_create_signal('agora_core', ['Profile']) - # Adding M2M table for field followers on 'Profile' - db.create_table('agora_core_profile_followers', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('from_profile', models.ForeignKey(orm['agora_core.profile'], null=False)), - ('to_profile', models.ForeignKey(orm['agora_core.profile'], null=False)) - )) - db.create_unique('agora_core_profile_followers', ['from_profile_id', 'to_profile_id']) - # Adding model 'Agora' db.create_table('agora_core_agora', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), @@ -63,14 +56,6 @@ def forwards(self, orm): )) db.create_unique('agora_core_agora_admins', ['agora_id', 'user_id']) - # Adding M2M table for field followers on 'Agora' - db.create_table('agora_core_agora_followers', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('agora', models.ForeignKey(orm['agora_core.agora'], null=False)), - ('user', models.ForeignKey(orm['auth.user'], null=False)) - )) - db.create_unique('agora_core_agora_followers', ['agora_id', 'user_id']) - # Adding model 'Election' db.create_table('agora_core_election', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), @@ -103,14 +88,6 @@ def forwards(self, orm): )) db.send_create_signal('agora_core', ['Election']) - # Adding M2M table for field followers on 'Election' - db.create_table('agora_core_election_followers', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('election', models.ForeignKey(orm['agora_core.election'], null=False)), - ('user', models.ForeignKey(orm['auth.user'], null=False)) - )) - db.create_unique('agora_core_election_followers', ['election_id', 'user_id']) - # Adding M2M table for field electorate on 'Election' db.create_table('agora_core_election_electorate', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), @@ -144,9 +121,6 @@ def backwards(self, orm): # Deleting model 'Profile' db.delete_table('agora_core_profile') - # Removing M2M table for field followers on 'Profile' - db.delete_table('agora_core_profile_followers') - # Deleting model 'Agora' db.delete_table('agora_core_agora') @@ -156,15 +130,9 @@ def backwards(self, orm): # Removing M2M table for field admins on 'Agora' db.delete_table('agora_core_agora_admins') - # Removing M2M table for field followers on 'Agora' - db.delete_table('agora_core_agora_followers') - # Deleting model 'Election' db.delete_table('agora_core_election') - # Removing M2M table for field followers on 'Election' - db.delete_table('agora_core_election_followers') - # Removing M2M table for field electorate on 'Election' db.delete_table('agora_core_election_electorate') @@ -173,6 +141,20 @@ def backwards(self, orm): models = { + 'actstream.action': { + 'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Action'}, + 'action_object_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'action_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}), + 'action_object_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'actor_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actor'", 'to': "orm['contenttypes.ContentType']"}), + 'actor_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'target_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'target'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}), + 'target_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'verb': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, 'agora_core.agora': { 'Meta': {'object_name': 'Agora'}, 'admins': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'adminstrated_agoras'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), @@ -184,7 +166,6 @@ def backwards(self, orm): 'election_type': ('django.db.models.fields.CharField', [], {'default': "'ONCE_CHOICE'", 'max_length': '50'}), 'eligibility': ('agora_site.misc.utils.JSONField', [], {'null': 'True'}), 'extra_data': ('agora_site.misc.utils.JSONField', [], {'null': 'True'}), - 'followers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_agoras'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'image_url': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '200', 'blank': 'True'}), 'is_vote_secret': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -220,7 +201,6 @@ def backwards(self, orm): 'electorate': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'elections'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), 'eligibility': ('agora_site.misc.utils.JSONField', [], {'null': 'True'}), 'extra_data': ('agora_site.misc.utils.JSONField', [], {'null': 'True'}), - 'followers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_elections'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), 'frozen_at_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), 'hash': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), @@ -241,8 +221,8 @@ def backwards(self, orm): 'Meta': {'object_name': 'Profile'}, 'biography': ('django.db.models.fields.TextField', [], {}), 'extra': ('agora_site.misc.utils.JSONField', [], {'null': 'True'}), - 'followers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followers_rel_+'", 'to': "orm['agora_core.Profile']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_activity_read_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'short_description': ('django.db.models.fields.CharField', [], {'max_length': '140'}), 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}), 'user_type': ('django.db.models.fields.CharField', [], {'default': "'PASSWORD'", 'max_length': '50'}) @@ -262,7 +242,7 @@ def backwards(self, orm): }, 'auth.user': { 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 4, 4, 23, 52, 48, 983915)'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 4, 5, 16, 40, 44, 310482)'}), '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'}), @@ -270,7 +250,7 @@ def backwards(self, orm): '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(2012, 4, 4, 23, 52, 48, 983824)'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 4, 5, 16, 40, 44, 310405)'}), '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'}), diff --git a/agora_site/agora_core/models.py b/agora_site/agora_core/models.py index 5e9c4b39..513e0f4b 100644 --- a/agora_site/agora_core/models.py +++ b/agora_site/agora_core/models.py @@ -1,4 +1,4 @@ -# Copyright (C) 2010 Eduardo Robles Elvira +# Copyright (C) 2012 Eduardo Robles Elvira # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -17,6 +17,12 @@ from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ from agora_site.misc.utils import JSONField +from reversion.models import Revision + +import reversion +from actstream.models import Follow +if reversion.is_registered(Follow): + reversion.unregister(Follow) class Profile(models.Model): ''' @@ -41,8 +47,10 @@ class Profile(models.Model): user_type = models.CharField(max_length=50, choices=USER_TYPES, default=USER_TYPES[0][0]) - followers = models.ManyToManyField('self', related_name='followed_users', - verbose_name=_('Followers')) + # This marks the date of the last activity item known to be read by the user + # so that later on we can for example send to the user update email only + # showing activity from this date on + last_activity_read_date = models.DateTimeField(_(u'Last Activity Read Date'), auto_now_add=True, editable=True) # Stores extra data # it will something like: @@ -117,9 +125,6 @@ class Agora(models.Model): admins = models.ManyToManyField(User, related_name='adminstrated_agoras', verbose_name=_('Administrators')) - followers = models.ManyToManyField(User, related_name='followed_agoras', - verbose_name=_('Followers')) - # Stores extra data # it will something like: #{ @@ -148,9 +153,6 @@ class Election(models.Model): creator = models.ForeignKey(User, related_name='created_elections', verbose_name=_('Creator'), blank=False) - followers = models.ManyToManyField(User, related_name='followed_elections', - verbose_name=_('Followers')) - # We might need to freeze the list of voters so that if someone signs in, # he cannot vote. # NOTE that on a voting of type SIMPLE_DELEGATION, the list is unused, diff --git a/agora_site/agora_core/urls.py b/agora_site/agora_core/urls.py new file mode 100644 index 00000000..ba88fb82 --- /dev/null +++ b/agora_site/agora_core/urls.py @@ -0,0 +1,21 @@ +# Copyright (C) 2012 Eduardo Robles Elvira +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.conf.urls.defaults import * +from agora_site.agora_core.views import TestView + +urlpatterns = patterns('', + (r'^test_view', TestView.as_view()), +) diff --git a/agora_site/agora_core/views.py b/agora_site/agora_core/views.py new file mode 100644 index 00000000..c73b7627 --- /dev/null +++ b/agora_site/agora_core/views.py @@ -0,0 +1,43 @@ +# Copyright (C) 2012 Eduardo Robles Elvira +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.conf.urls import patterns, url, include +from django.views.generic import TemplateView +from django.views.generic import ListView +from django import http +from django.utils import simplejson as json +from django.contrib.auth.models import User +import reversion + +class TestView(TemplateView): + template_name = 'base.html' + + def render_to_response(self, context): + + #user = User.objects.all()[0] + #print reversion.get_unique_for_object(user) + #with reversion.create_revision(): + #user.last_name = user.last_name + ", aa" + #user.save() + #reversion.set_user(user) + #reversion.set_comment("Comment text...") + + #print reversion.get_unique_for_object(user) + + return super(TestView, self).render_to_response(context) + + #@method_decorator(login_required) + def dispatch(self, *args, **kwargs): + return super(TestView, self).dispatch(*args, **kwargs) \ No newline at end of file diff --git a/agora_site/settings.py b/agora_site/settings.py index 27a56c09..ad3e2069 100644 --- a/agora_site/settings.py +++ b/agora_site/settings.py @@ -104,13 +104,27 @@ 'django.contrib.messages', 'django.contrib.admin', 'django.contrib.admindocs', + 'django.contrib.comments', 'reversion', 'south', 'guardian', 'rosetta', + 'actstream', 'agora_site.agora_core', ) +# A list the models that you want to enable actions for. Models must be in the +# format app_label.model_name . In the background, django-activity-stream sets +# up GenericRelations to handle stream generation. +# More info: http://justquick.github.com/django-activity-stream/configuration.html + +ACTSTREAM_ACTION_MODELS = [ + 'auth.User', + 'agora_core.Agora', + 'agora_core.Election', + 'agora_core.CastVote', +] + # Modify the defaults to use BCrypt by default, because it's more secure, better # for long term password storage PASSWORD_HASHERS = ( diff --git a/agora_site/urls.py b/agora_site/urls.py index 820fdc83..7abd2d13 100644 --- a/agora_site/urls.py +++ b/agora_site/urls.py @@ -1,3 +1,18 @@ +# Copyright (C) 2012 Eduardo Robles Elvira +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + from django.conf.urls.defaults import * from django.conf import settings @@ -16,4 +31,8 @@ # Uncomment the next line to enable the admin: (r'^admin/', include(admin.site.urls)), + + (r'^comments/', include('django.contrib.comments.urls')), + + (r'^', include('agora_site.agora_core.urls')), ) diff --git a/agora_site/views.py b/agora_site/views.py index 66063fd1..a59df6d2 100644 --- a/agora_site/views.py +++ b/agora_site/views.py @@ -1,5 +1,19 @@ +# Copyright (C) 2012 Eduardo Robles Elvira +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . from django.shortcuts import render_to_response def serve_templates(request, document_root, path): - return render_to_response(document_root + "/" + path) + return render_to_response(document_root + "/" + path) diff --git a/dependencies.txt b/dependencies.txt index 60941870..cc444366 100644 --- a/dependencies.txt +++ b/dependencies.txt @@ -7,3 +7,4 @@ django-rosetta py-bcrypt django-guardian django-reversion +django-activity-stream