[damned-lies] Add inactive translators category (fixes #634130)



commit 00809243f70bd323d30248a2a7b08452b423a190
Author: Adorilson Bezerra <adorilson gmail com>
Date:   Tue Dec 21 10:04:52 2010 +0100

    Add inactive translators category (fixes #634130)

 common/models.py                                   |    1 +
 common/tests.py                                    |   92 ++++++++++++++++++++
 common/views.py                                    |    3 +
 .../0003_auto__add_field_role_is_active.py         |   90 +++++++++++++++++++
 teams/models.py                                    |   28 +++++-
 teams/tests.py                                     |   92 +++++++++++++++++---
 teams/views.py                                     |    5 +
 vertimus/models.py                                 |    9 ++
 vertimus/tests/__init__.py                         |   23 +++---
 9 files changed, 317 insertions(+), 26 deletions(-)
---
diff --git a/common/models.py b/common/models.py
new file mode 100644
index 0000000..864a180
--- /dev/null
+++ b/common/models.py
@@ -0,0 +1 @@
+# This empty file is necessary to run the tests
diff --git a/common/tests.py b/common/tests.py
new file mode 100644
index 0000000..baab0a4
--- /dev/null
+++ b/common/tests.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2010 Adorilson Bezerra <adorilson gmail com>
+#
+# This file is part of Damned Lies.
+#
+# Damned Lies is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Damned Lies 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Damned Lies; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from datetime import datetime, timedelta
+
+from django.test import TestCase
+from django.test.client import Client
+
+from common import views
+from people.models import Person
+from teams.models import Team, Role
+
+class CommonTest(TestCase):
+
+    def setUp(self):
+        self.pn = Person(first_name='John', last_name='Note',
+            email='jn devnull com', username= 'jn', activation_key='a_activation_key')
+        self.pn.save()
+
+        self.pr = Person(first_name='John', last_name='Reviewer',
+            username='jr', date_joined=datetime.now()-timedelta(days=11),
+            activation_key='non-activated-key') # non-activeted person
+        self.pr.save()
+
+        self.pt = Person(first_name='John', last_name='Translator',
+            username='jt', last_login=datetime.now()-timedelta(days=30*6+1),) # inactive person
+        self.pt.save()
+
+        self.t1 = Team(name='fr', description='French')
+        self.t1.save()
+   
+        self.t2 = Team(name='fr2', description='French')
+        self.t2.save()
+
+        self.r1 = Role(team=self.t1, person=self.pt)
+        self.r1.save()
+
+        self.r2 = Role(team=self.t2, person=self.pt)
+        self.r2.save() 
+        
+
+    def test_activate_account(self):
+        # Testing if is_active is False by default
+        self.pn = Person.objects.get(first_name='John', last_name='Note')
+        self.assertTrue(self.pn.is_active)
+        
+        c = Client()
+
+        # Testing with a invalid activation key
+        response = c.get('/register/activate/a_invalid_key')
+        self.assertContains(response, 'Sorry, the key you provided is not valid.')        
+
+        response = c.get('/register/activate/a_activation_key')
+        self.assertContains(response,  'Your account has been activated.')
+        
+        self.pn = Person.objects.get(first_name='John', last_name='Note')
+        self.assertTrue(self.pn.is_active)
+
+    def test_house_keeping(self):
+        c = Client()
+        response = c.get('/register/activate/a_activation_key')
+        self.assertContains(response,  'Your account has been activated.')
+
+        # Testing if the non-activated accounts were deleted
+        jn = Person.objects.filter(first_name='John', last_name='Note')
+        self.assertEqual(jn.count(), 1)       
+
+        jr = Person.objects.filter(first_name='John', last_name='Reviewer')
+        self.assertEqual(jr.count(), 0)
+
+        # Testing if the inactive roles were updated
+        jt = Person.objects.get(first_name='John', last_name='Translator')
+        for role in Role.objects.filter(person=jt):
+            self.assertFalse(role.is_active)
+
diff --git a/common/views.py b/common/views.py
index 71baaaf..02034e4 100644
--- a/common/views.py
+++ b/common/views.py
@@ -118,5 +118,8 @@ def activate_account(request, key):
     except Person.DoesNotExist:
         return render_to_response('error.html', {'error':"Sorry, the key you provided is not valid."})
     person.activate()
+    #TODO: Here does not seem to be a good place for this. 
+    # We should move this in a cron-like system
     Person.clean_unactivated_accounts()
+    Role.inactivate_unused_roles()
     return site_login(request, msgs=[_("Your account has been activated.")])
diff --git a/teams/migrations/0003_auto__add_field_role_is_active.py b/teams/migrations/0003_auto__add_field_role_is_active.py
new file mode 100644
index 0000000..1ccfaaa
--- /dev/null
+++ b/teams/migrations/0003_auto__add_field_role_is_active.py
@@ -0,0 +1,90 @@
+# 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 field 'Role.is_active'
+        db.add_column('role', 'is_active', self.gf('django.db.models.fields.BooleanField')(default=True), keep_default=False)
+
+
+    def backwards(self, orm):
+
+        # Deleting field 'Role.is_active'
+        db.delete_column('role', 'is_active')
+
+
+    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'})
+        },
+        'people.person': {
+            'Meta': {'ordering': "('username',)", 'object_name': 'Person', 'db_table': "'person'", '_ormbases': ['auth.User']},
+            'activation_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'bugzilla_account': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+            'irc_nick': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '20', 'null': 'True', 'blank': 'True'}),
+            'svn_account': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '20', 'null': 'True', 'blank': 'True'}),
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),
+            'webpage_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
+        },
+        'teams.role': {
+            'Meta': {'unique_together': "(('team', 'person'),)", 'object_name': 'Role', 'db_table': "'role'"},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['people.Person']"}),
+            'role': ('django.db.models.fields.CharField', [], {'default': "'translator'", 'max_length': '15'}),
+            'team': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['teams.Team']"})
+        },
+        'teams.team': {
+            'Meta': {'ordering': "('description',)", 'object_name': 'Team', 'db_table': "'team'"},
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mailing_list': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'mailing_list_subscribe': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+            'members': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'teams'", 'symmetrical': 'False', 'through': "orm['teams.Role']", 'to': "orm['people.Person']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'presentation': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'use_workflow': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'webpage_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['teams']
diff --git a/teams/models.py b/teams/models.py
index 0d85ee2..cc9aed6 100644
--- a/teams/models.py
+++ b/teams/models.py
@@ -19,6 +19,8 @@
 # along with Damned Lies; if not, write to the Free Software Foundation, Inc.,
 # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+from datetime import datetime, timedelta
+
 from django.db import models
 from django.core import mail
 from django.utils import translation
@@ -37,7 +39,8 @@ class TeamManager(models.Manager):
         roles.
         """
         teams = self.all()
-        roles = Role.objects.select_related("person").filter(role='coordinator')
+        roles = Role.objects.select_related("person").filter(role='coordinator',
+                                                                is_active=True)
         role_dict = {}
 
         for role in roles:
@@ -60,7 +63,7 @@ class TeamManager(models.Manager):
         reduce subsequent database access.
         """
         teams = self.all()
-        roles = Role.objects.select_related("person").all()
+        roles = Role.objects.select_related("person").filter(is_active=True)
         role_dict = {}
 
         for role in roles:
@@ -143,11 +146,13 @@ class Team(models.Model):
                 return None
 
     def get_members_by_role_exact(self, role):
+        """ Return a list of active members """
         try:
             return self.roles[role]
         except:
             members = list(Person.objects.filter(role__team__id=self.id,
-                                                 role__role=role))
+                                                 role__role=role,
+                                                 role__is_active=True))
             return members
 
     def get_committers_exact(self):
@@ -167,7 +172,8 @@ class Team(models.Model):
                 members += self.roles[role]
         except:
             members = list(Person.objects.filter(role__team__id=self.id,
-                                                 role__role__in=roles))
+                                                 role__role__in=roles,
+                                                 role__is_active=True))
         return members
 
     def get_committers(self):
@@ -187,6 +193,12 @@ class Team(models.Model):
             members = list(self.members.all())
         return members
 
+    def get_inactive_members(self):
+        """ Return the inactive members """
+        members = list(Person.objects.filter(role__team__id=self.id,
+                                             role__is_active=False)) 
+        return members
+
     def send_mail_to_coordinator(self, subject, message, messagekw={}):
         """ Send a message to the coordinator, in her language if available
             and if subject and message are lazy strings """
@@ -250,6 +262,7 @@ class Role(models.Model):
     person = models.ForeignKey(Person)
     role = models.CharField(max_length=15, choices=ROLE_CHOICES,
                             default='translator')
+    is_active = models.BooleanField(default=True)
 
     class Meta:
         db_table = 'role'
@@ -258,3 +271,10 @@ class Role(models.Model):
     def __unicode__(self):
         return "%s is %s in %s team" % (self.person.name, self.role,
                                         self.team.description)
+
+    @classmethod
+    def inactivate_unused_roles(cls):
+         """ Inactivate the roles when login older than 180 days  """
+         last_login = datetime.now() - timedelta(days=30*6)
+         cls.objects.filter(person__last_login__lt=last_login,
+                            is_active=True).update(is_active=False)
diff --git a/teams/tests.py b/teams/tests.py
index 597dacb..b705aca 100644
--- a/teams/tests.py
+++ b/teams/tests.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+
+from datetime import datetime, timedelta
+
 from django.test import TestCase
 from django.test.client import Client
 from django.core.urlresolvers import reverse
@@ -8,8 +11,8 @@ from people.models import Person
 from teams.models import Team, Role
 from languages.models import Language
 
-class TeamTest(TestCase):
 
+class TeamsAndRolesTests(TestCase):
     def setUp(self):
         self.pn = Person(first_name='John', last_name='Nothing',
             email='jn devnull com', username= 'jn')
@@ -25,7 +28,8 @@ class TeamTest(TestCase):
         self.pr.save()
 
         self.pc = Person(first_name='John', last_name='Committer',
-            email='jc alinsudesonpleingre fr', username= 'jc')
+            email='jc alinsudesonpleingre fr', username= 'jc',
+            last_login=datetime.now()-timedelta(days=30*6-1)) #active person, but in limit date
         self.pc.save()
 
         self.pcoo = Person(first_name='John', last_name='Coordinator',
@@ -36,12 +40,18 @@ class TeamTest(TestCase):
         self.t = Team(name='fr', description='French')
         self.t.save()
 
+        self.t2 = Team(name='pt', description='Portuguese')
+        self.t2.save()
+
         self.l = Language(name='French', locale='fr', team=self.t)
         self.l.save()
 
         self.role = Role(team=self.t, person=self.pt)
         self.role.save()
 
+        self.role = Role(team=self.t2, person=self.pt, role='reviewer')
+        self.role.save()
+
         self.role = Role(team=self.t, person=self.pr, role='reviewer')
         self.role.save()
 
@@ -51,16 +61,36 @@ class TeamTest(TestCase):
         self.role = Role(team=self.t, person=self.pcoo, role='coordinator')
         self.role.save()
 
-    def tearDown(self):
-        for role in Role.objects.all():
-            role.delete()
-        self.pcoo.delete()
-        self.pc.delete()
-        self.pr.delete()
-        self.pt.delete()
-        self.pn.delete()
-        self.t.delete()
+class TeamTest(TeamsAndRolesTests):
+    def setUp(self):
+        super(TeamTest, self).setUp()
+
+    def test_get_members_by_role_exact(self):
+        members = self.t.get_members_by_role_exact('committer')
+        t = Team.objects.get(name='fr')
+        self.assertEqual(len(members), 1)
+        self.assertEqual(members[0], self.pc)
 
+        role = Role.objects.get(person=self.pc, team=t)
+        role.is_active = False
+        role.save()
+    
+        members = self.t.get_members_by_role_exact('committer')
+        self.assertEqual(len(members), 0)
+        
+    def test_get_inactive_members(self):
+        members = self.t.get_inactive_members()
+        self.assertEqual(len(members), 0)
+
+        t = Team.objects.get(name='fr')
+        role = Role.objects.get(person=self.pc, team=t)
+        role.is_active = False
+        role.save()
+        
+        members = self.t.get_inactive_members()
+        self.assertEqual(len(members), 1)
+        self.assertEqual(members[0], self.pc)
+    
     def run_roles_exact_test(self, team):
         pcoo = team.get_coordinator()
         self.assertEqual(pcoo, self.pcoo)
@@ -148,3 +178,43 @@ class TeamTest(TestCase):
         team = Team.objects.get(name='fr')
         self.assertEquals(team.webpage_url, u"http://www.gnomefr.org/";)
 
+
+class RoleTest(TeamsAndRolesTests):
+
+    def setUp(self):
+        super(RoleTest, self).setUp()
+
+        self.pt.last_login = datetime.now()-timedelta(days=10) # active person
+        self.pt.save()
+
+        self.pr.last_login = datetime.now()-timedelta(days=30*6) # inactive person
+        self.pr.save()
+
+        self.pc.last_login = datetime.now()-timedelta(days=30*6-1) #active person, but in limit date
+        self.pc.save()
+
+        self.role = Role.objects.get(team=self.t, person=self.pt)
+        self.role2 = Role.objects.get(team=self.t2, person=self.pt)
+        self.role_inactive = Role.objects.get(team=self.t, person=self.pr)
+        self.role_limit_date = Role.objects.get(team=self.t, person=self.pc)
+
+    def test_inactivating_roles(self):
+        # Testing if is_active is True by default
+        self.assertTrue(self.role.is_active)
+        self.assertTrue(self.role2.is_active)
+        self.assertTrue(self.role_limit_date.is_active)
+        self.assertTrue(self.role_inactive.is_active)        
+
+        Role.inactivate_unused_roles()
+
+        # Getting roles from database after update the unused roles
+        self.role = Role.objects.get(team=self.t, person=self.pt)
+        self.role2 = Role.objects.get(team=self.t2, person=self.pt)
+        self.role_inactive = Role.objects.get(team=self.t, person=self.pr)
+        self.role_limit_date = Role.objects.get(team=self.t, person=self.pc)
+
+        self.assertTrue(self.role.is_active)
+        self.assertTrue(self.role2.is_active)
+        self.assertTrue(self.role_limit_date.is_active)         
+        self.assertFalse(self.role_inactive.is_active)
+
diff --git a/teams/views.py b/teams/views.py
index 0aaf7d5..9d90108 100644
--- a/teams/views.py
+++ b/teams/views.py
@@ -69,6 +69,11 @@ def team(request, team_slug):
                 'form': None,
                 'no_member': _("No translators")
                },
+               {'title': _("Inactive members"),
+                'members': team.get_inactive_members(),
+                'form': None,
+                'no_member': _("No inactive members")
+               },
         )
     except Team.DoesNotExist:
         lang = get_object_or_404(Language, locale=team_slug)
diff --git a/vertimus/models.py b/vertimus/models.py
index 7eeba16..cd6ef37 100644
--- a/vertimus/models.py
+++ b/vertimus/models.py
@@ -34,6 +34,8 @@ from stats.utils import run_shell_command
 from languages.models import Language
 from people.models import Person
 
+from teams.models import Role
+
 #
 # States
 #
@@ -620,6 +622,13 @@ class ActionUT(ActionAbstract):
 
         new_state = self._new_state()
         self.send_mail_new_state(state, new_state, (state.language.team.mailing_list,))
+
+        # Reactivating the role if needed      
+        role = Role.objects.get(person=person, team=state.language.team)
+        if not role.is_active:
+            role.is_active = True
+            role.save()
+
         return new_state
 
 class ActionRP(ActionAbstract):
diff --git a/vertimus/tests/__init__.py b/vertimus/tests/__init__.py
index 15d5939..2087c75 100644
--- a/vertimus/tests/__init__.py
+++ b/vertimus/tests/__init__.py
@@ -26,12 +26,12 @@ from django.http import QueryDict
 from django.utils.datastructures import MultiValueDict
 from django.conf import settings
 
-from teams.tests import TeamTest
+from teams.tests import TeamsAndRolesTests
 from stats.models import Module, Branch, Release, Category, Domain
 from vertimus.models import *
 from vertimus.forms import ActionForm
 
-class VertimusTest(TeamTest):
+class VertimusTest(TeamsAndRolesTests):
 
     def setUp(self):
         super(VertimusTest, self).setUp()
@@ -61,14 +61,6 @@ class VertimusTest(TeamTest):
             dtype='ui', directory='po')
         self.d.save()
 
-    def tearDown(self):
-        self.d.delete()
-        self.c.delete()
-        self.r.delete()
-        self.b.delete()
-        self.m.delete()
-        super(VertimusTest, self).tearDown()
-
     def test_state_none(self):
         sdb = StateDb(branch=self.b, domain=self.d, language=self.l)
         sdb.name = 'None'
@@ -239,16 +231,25 @@ class VertimusTest(TeamTest):
         new_state.save()
 
     def test_action_ut(self):
+        # Disabling the role
+        role = Role.objects.get(person=self.pt, team=self.l.team)
+        role.is_active = False
+        role.save()
+        
         state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Translating', person=self.pt).get_state()
         state.save()
 
         test_file = ContentFile('test content')
         test_file.name = 'mytestfile.po'
-
+        
         action = ActionAbstract.new_by_name('UT')
         new_state = state.apply_action(action, self.pt, "Done by translator.", test_file)
         new_state.save()
 
+        # Testing if the role was activated
+        role = Role.objects.get(person=self.pt, team=self.l.team)
+        self.assertTrue(role.is_active)
+
     def test_action_rp(self):
         state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Translated').get_state()
         state.save()



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]