[damned-lies] [vertimus] Use proxy models for State classes



commit 79f1f5edfb7906570281ae6abaa22ea3cf1f028e
Author: Claude Paroz <claude 2xlibre net>
Date:   Fri May 20 22:56:54 2011 +0200

    [vertimus] Use proxy models for State classes
    
    Next step is to do the same for Action classes...

 people/views.py            |    4 +-
 stats/models.py            |    9 +-
 teams/tests.py             |    2 +-
 vertimus/admin.py          |    6 +-
 vertimus/models.py         |  268 +++++++++++++++++++-------------------------
 vertimus/tests/__init__.py |  192 +++++++++++++------------------
 vertimus/views.py          |   14 +--
 7 files changed, 213 insertions(+), 282 deletions(-)
---
diff --git a/people/views.py b/people/views.py
index fdfbd26..428204a 100644
--- a/people/views.py
+++ b/people/views.py
@@ -36,7 +36,7 @@ from django.views.generic import ListView, DetailView, UpdateView
 from people.models import Person
 from teams.models import Team, Role
 from people.forms import TeamJoinForm, DetailForm
-from vertimus.models import StateDb
+from vertimus.models import State
 
 
 class PeopleListView(ListView):
@@ -54,7 +54,7 @@ class PersonDetailView(DetailView):
 
     def get_context_data(self, **kwargs):
         context = super(PersonDetailView, self).get_context_data(**kwargs)
-        states = StateDb.objects.filter(actiondb__person=self.object).distinct()
+        states = State.objects.filter(actiondb__person=self.object).distinct()
         all_languages = [(lg[0], LANG_INFO.get(lg[0], {'name_local': lg[1]})['name_local']) for lg in settings.LANGUAGES]
         all_languages.sort(key=itemgetter(1))
         context.update({
diff --git a/stats/models.py b/stats/models.py
index 7e4696e..6171473 100644
--- a/stats/models.py
+++ b/stats/models.py
@@ -1496,7 +1496,8 @@ class Statistics(models.Model):
                 'all_errors':[]
             }
         """
-        from vertimus.models import StateDb, ActionDb # import here to prevent a circular dependency
+        # Import here to prevent a circular dependency
+        from vertimus.models import State, ActionDb
 
         if dtype.endswith('-part'):
             dtype = dtype[:-5]
@@ -1523,15 +1524,15 @@ class Statistics(models.Model):
 
         infos_dict = Information.get_info_dict(lang)
 
-        # Prepare StateDb objects in a dict (with "branch_id-domain_id" as key), to save database queries later
-        vt_states = StateDb.objects.select_related('branch','domain')
+        # Prepare State objects in a dict (with "branch_id-domain_id" as key), to save database queries later
+        vt_states = State.objects.select_related('branch','domain')
         if release:
             vt_states = vt_states.filter(language=lang, branch__releases=release, domain__dtype=dtype)
         else:
             vt_states = vt_states.filter(language=lang, domain__dtype=dtype)
         vt_states_dict = dict([("%d-%d" % (vt.branch.id, vt.domain.id),vt) for vt in vt_states])
 
-        # Get comments from last action of StateDb objects
+        # Get comments from last action of State objects
         actions = ActionDb.objects.filter(state_db__in=vt_states, comment__isnull=False).order_by('created')
         actions_dict = dict([(act.state_db_id, act) for act in actions])
         for vt_state in vt_states_dict.values():
diff --git a/teams/tests.py b/teams/tests.py
index b705aca..4a379c8 100644
--- a/teams/tests.py
+++ b/teams/tests.py
@@ -37,7 +37,7 @@ class TeamsAndRolesTests(TestCase):
         self.pcoo.set_password('password')
         self.pcoo.save()
 
-        self.t = Team(name='fr', description='French')
+        self.t = Team(name='fr', description='French', mailing_list='nowhere example org')
         self.t.save()
 
         self.t2 = Team(name='pt', description='Portuguese')
diff --git a/vertimus/admin.py b/vertimus/admin.py
index ea4e0fa..a24473f 100644
--- a/vertimus/admin.py
+++ b/vertimus/admin.py
@@ -1,13 +1,13 @@
 # -*- coding: utf-8 -*-
 from django.contrib import admin
 
-from vertimus.models import StateDb, ActionDb
+from vertimus.models import State, ActionDb
 
-class StateDbAdmin(admin.ModelAdmin):
+class StateAdmin(admin.ModelAdmin):
     raw_id_fields = ('branch', 'domain', 'person',)
 
 class ActionDbAdmin(admin.ModelAdmin):
     list_display = ('__unicode__', 'state_db')
 
-admin.site.register(StateDb, StateDbAdmin)
+admin.site.register(State, StateAdmin)
 admin.site.register(ActionDb, ActionDbAdmin)
diff --git a/vertimus/models.py b/vertimus/models.py
index 89a9bbe..bc2f97e 100644
--- a/vertimus/models.py
+++ b/vertimus/models.py
@@ -42,11 +42,8 @@ from teams.models import Role
 # States
 #
 
-# Sadly, the Django ORM isn't as powerful than SQLAlchemy :-(
-# So we need to use composition with StateDB and State to obtain
-# the desired behaviour.
-class StateDb(models.Model):
-    """Database storage of a State"""
+class State(models.Model):
+    """State of a module translation"""
     branch = models.ForeignKey(Branch)
     domain = models.ForeignKey(Domain)
     language = models.ForeignKey(Language)
@@ -60,10 +57,21 @@ class StateDb(models.Model):
         verbose_name = 'state'
         unique_together = ('branch', 'domain', 'language')
 
-    def get_state(self):
-        state = eval('State'+self.name)()
-        state._state_db = self
-        return state
+    def __init__(self, *args, **kwargs):
+        super(State, self).__init__(*args, **kwargs)
+        if self.name == 'None' and getattr(self.__class__, 'name', 'None') != 'None':
+            self.name = self.__class__.name
+        self.__class__ = {
+            'None'        : StateNone,
+            'Translating' : StateTranslating,
+            'Translated'  : StateTranslated,
+            'Proofreading': StateProofreading,
+            'Proofread'   : StateProofread,
+            'ToReview'    : StateToReview,
+            'ToCommit'    : StateToCommit,
+            'Committing'  : StateCommitting,
+            'Committed'   : StateCommitted,
+        }.get(self.name, State)
 
     def __unicode__(self):
         return "%s: %s %s (%s - %s)" % (self.name, self.branch.module.name,
@@ -73,37 +81,10 @@ class StateDb(models.Model):
     def get_absolute_url(self):
         return ('vertimus_by_ids', [self.branch.id, self.domain.id, self.language.id])
 
-class StateAbstract(object):
-    """Abstract class"""
-
-    @property
-    def branch(self):
-        return self._state_db.branch
-
-    @property
-    def domain(self):
-        return self._state_db.domain
-
-    @property
-    def language(self):
-        return self._state_db.language
-
-    def get_person(self):
-        return self._state_db.person
-
-    def set_person(self, person):
-        self._state_db.person = person
-    person = property(get_person, set_person)
-
-    @property
-    def updated(self):
-        return self._state_db.updated
-
-    def get_state_db(self):
-        return self._state_db
-
-    def __unicode__(self):
-        return unicode(self.description)
+    def change_state(self, state_class):
+        self.name = state_class.name
+        self.__class__ = state_class
+        self.save()
 
     def _get_available_actions(self, person, action_names):
         action_names.append('WC')
@@ -116,24 +97,14 @@ class StateAbstract(object):
     def apply_action(self, action, person, comment=None, file=None):
         # Check the permission to use this action
         if action.name in (a.name for a in self.get_available_actions(person)):
-            new_state = action.apply(self, person, comment, file)
-            if new_state != None:
-                # Reuse the current state_db
-                new_state._state_db = self._state_db
-                # Only the name and the person change
-                new_state._state_db.name = new_state.name
-                new_state._state_db.person = person
-
-                if isinstance(new_state, StateCommitted):
-                    # Committed is the last state of the workflow
-                    new_state.save()
-
-                    # Archive actions
-                    return new_state.apply_action(ActionAA(), person)
-
-                return new_state
-            else:
-                return self
+            action.apply(self, person, comment, file)
+            if not isinstance(self, StateNone):
+                self.person = person
+                self.save()
+
+                if isinstance(self, StateCommitted):
+                    # Committed is the last state of the workflow, archive actions
+                    self.apply_action(ActionAA(), person)
         else:
             raise Exception('Not allowed')
 
@@ -142,20 +113,20 @@ class StateAbstract(object):
            The first level is 1."""
         assert level > 0, "Level must be greater than 0"
 
-        query = ActionDbArchived.objects.filter(state_db=self._state_db).values('sequence').distinct().order_by('-sequence')[level-1:level]
+        query = self.actiondbarchived_set.all().values('sequence').distinct().order_by('-sequence')[level-1:level]
         sequence = None
         if len(query) > 0:
             sequence = query[0]['sequence']
         return sequence
 
-    def save(self):
-        self._state_db.save()
 
-
-class StateNone(StateAbstract):
+class StateNone(State):
     name = 'None'
     description = _('Inactive')
 
+    class Meta:
+        proxy = True
+
     def get_available_actions(self, person):
         action_names = []
 
@@ -165,10 +136,13 @@ class StateNone(StateAbstract):
         return self._get_available_actions(person, action_names)
 
 
-class StateTranslating(StateAbstract):
+class StateTranslating(State):
     name = 'Translating'
     description = _('Translating')
 
+    class Meta:
+        proxy = True
+
     def get_available_actions(self, person):
         action_names = []
 
@@ -178,10 +152,13 @@ class StateTranslating(StateAbstract):
         return self._get_available_actions(person, action_names)
 
 
-class StateTranslated(StateAbstract):
+class StateTranslated(State):
     name = 'Translated'
     description = _('Translated')
 
+    class Meta:
+        proxy = True
+
     def get_available_actions(self, person):
         action_names = []
 
@@ -198,10 +175,13 @@ class StateTranslated(StateAbstract):
         return self._get_available_actions(person, action_names)
 
 
-class StateProofreading(StateAbstract):
+class StateProofreading(State):
     name = 'Proofreading'
     description = _('Proofreading')
 
+    class Meta:
+        proxy = True
+
     def get_available_actions(self, person):
         action_names = []
 
@@ -212,11 +192,14 @@ class StateProofreading(StateAbstract):
         return self._get_available_actions(person, action_names)
 
 
-class StateProofread(StateAbstract):
+class StateProofread(State):
     name = 'Proofread'
     # Translators: This is a status, not a verb
     description = _('Proofread')
 
+    class Meta:
+        proxy = True
+
     def get_available_actions(self, person):
         if person.is_reviewer(self.language.team):
             action_names = ['TC', 'RP', 'TR']
@@ -228,10 +211,13 @@ class StateProofread(StateAbstract):
         return self._get_available_actions(person, action_names)
 
 
-class StateToReview(StateAbstract):
+class StateToReview(State):
     name = 'ToReview'
     description = _('To Review')
 
+    class Meta:
+        proxy = True
+
     def get_available_actions(self, person):
         action_names = []
         if person.is_translator(self.language.team):
@@ -240,10 +226,13 @@ class StateToReview(StateAbstract):
         return self._get_available_actions(person, action_names)
 
 
-class StateToCommit(StateAbstract):
+class StateToCommit(State):
     name = 'ToCommit'
     description = _('To Commit')
 
+    class Meta:
+        proxy = True
+
     def get_available_actions(self, person):
         if person.is_committer(self.language.team):
             action_names = ['RC', 'TR']
@@ -255,10 +244,13 @@ class StateToCommit(StateAbstract):
         return self._get_available_actions(person, action_names)
 
 
-class StateCommitting(StateAbstract):
+class StateCommitting(State):
     name = 'Committing'
     description = _('Committing')
 
+    class Meta:
+        proxy = True
+
     def get_available_actions(self, person):
         action_names = []
 
@@ -269,10 +261,13 @@ class StateCommitting(StateAbstract):
         return self._get_available_actions(person, action_names)
 
 
-class StateCommitted(StateAbstract):
+class StateCommitted(State):
     name = 'Committed'
     description = _('Committed')
 
+    class Meta:
+        proxy = True
+
     def get_available_actions(self, person):
         if person.is_committer(self.language.team):
             action_names = ['AA']
@@ -281,6 +276,7 @@ class StateCommitted(StateAbstract):
 
         return self._get_available_actions(person, action_names)
 
+
 #
 # Actions
 #
@@ -335,7 +331,7 @@ def action_db_get_action_history(cls, state_db=None, sequence=None):
     return history
 
 class ActionDb(models.Model):
-    state_db = models.ForeignKey(StateDb)
+    state_db = models.ForeignKey(State)
     person = models.ForeignKey(Person)
 
     name = models.SlugField(max_length=8)
@@ -395,7 +391,7 @@ def generate_archive_filename(instance, original_filename):
     return "%s/%s" % (settings.UPLOAD_ARCHIVED_DIR, os.path.basename(original_filename))
 
 class ActionDbArchived(models.Model):
-    state_db = models.ForeignKey(StateDb)
+    state_db = models.ForeignKey(State)
     person = models.ForeignKey(Person)
 
     name = models.SlugField(max_length=8)
@@ -488,8 +484,8 @@ class ActionAbstract(object):
 
     def save_action_db(self, state, person, comment=None, file=None):
         """Used by apply"""
-        self._action_db = ActionDb(state_db=state._state_db,
-            person=person, name=self.name, comment=comment, file=file)
+        self._action_db = ActionDb(state_db=state, person=person,
+            name=self.name, comment=comment, file=file)
         if file:
             self._action_db.file.save(file.name, file, save=False)
         self._action_db.save()
@@ -529,33 +525,33 @@ class ActionAbstract(object):
         except:
             return False
 
-    def send_mail_new_state(self, old_state, new_state, recipient_list):
+    def send_mail_new_state(self, state, recipient_list):
         # Remove None and empty string items from the list
         recipient_list = filter(lambda x: x and x is not None, recipient_list)
 
         if recipient_list:
             current_lang = get_language()
-            activate(old_state.language.locale)
+            activate(state.language.locale)
             current_site = Site.objects.get_current()
             url = "http://%s%s"; % (current_site.domain, urlresolvers.reverse(
                 'vertimus_by_names',
                  args = (
-                    old_state.branch.module.name,
-                    old_state.branch.name,
-                    old_state.domain.name,
-                    old_state.language.locale)))
-            subject = old_state.branch.module.name + u' - ' + old_state.branch.name
+                    state.branch.module.name,
+                    state.branch.name,
+                    state.domain.name,
+                    state.language.locale)))
+            subject = state.branch.module.name + u' - ' + state.branch.name
             message = _(u"""Hello,
 
 The new state of %(module)s - %(branch)s - %(domain)s (%(language)s) is now '%(new_state)s'.
 %(url)s
 
 """) % {
-                'module': old_state.branch.module.name,
-                'branch': old_state.branch.name,
-                'domain': old_state.domain.name,
-                'language': old_state.language.get_name(),
-                'new_state': new_state,
+                'module': state.branch.module.name,
+                'branch': state.branch.name,
+                'domain': state.domain.name,
+                'language': state.language.get_name(),
+                'new_state': state,
                 'url': url
             }
             message += self.comment or ugettext("Without comment")
@@ -570,15 +566,12 @@ class ActionWC(ActionAbstract):
     description = _('Write a comment')
     comment_is_required = True
 
-    def _new_state(self):
-        return None
-
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
 
         # Send an email to all translators of the page
         translator_emails = set()
-        for d in Person.objects.filter(actiondb__state_db=state.get_state_db()).values('email'):
+        for d in Person.objects.filter(actiondb__state_db=state).values('email'):
             translator_emails.add(d['email'])
 
         # Remove None items from the list
@@ -614,158 +607,129 @@ A new comment has been left on %(module)s - %(branch)s - %(domain)s (%(language)
             mail.send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, translator_emails)
             activate(current_lang)
 
-        return self._new_state()
-
 class ActionRT(ActionAbstract):
     name = 'RT'
     description = _('Reserve for translation')
+    target_state = StateTranslating
     file_is_prohibited = True
 
-    def _new_state(self):
-        return StateTranslating()
-
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
-        return self._new_state()
+        state.change_state(self.target_state)
 
 class ActionUT(ActionAbstract):
     name = 'UT'
     description = _('Upload the new translation')
+    target_state = StateTranslated
     file_is_required = True
 
-    def _new_state(self):
-        return StateTranslated()
-
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
 
-        new_state = self._new_state()
-        self.send_mail_new_state(state, new_state, (state.language.team.mailing_list,))
-        return new_state
+        state.change_state(self.target_state)
+        self.send_mail_new_state(state, (state.language.team.mailing_list,))
 
 class ActionRP(ActionAbstract):
     name = 'RP'
     description = _('Reserve for proofreading')
+    target_state = StateProofreading
     file_is_prohibited = True
 
-    def _new_state(self):
-        return StateProofreading()
-
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
-        return self._new_state()
+        state.change_state(self.target_state)
 
 class ActionUP(ActionAbstract):
     name = 'UP'
     description = _('Upload the proofread translation')
+    target_state = StateProofread
     file_is_required = True
 
-    def _new_state(self):
-        return StateProofread()
-
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
 
-        new_state = self._new_state()
-        self.send_mail_new_state(state, new_state, (state.language.team.mailing_list,))
-        return new_state
+        state.change_state(self.target_state)
+        self.send_mail_new_state(state, (state.language.team.mailing_list,))
 
 class ActionTC(ActionAbstract):
     name = 'TC'
     # Translators: this means the file is ready to be committed in repository
     description = _('Ready for submission')
-
-    def _new_state(self):
-        return StateToCommit()
+    target_state = StateToCommit
 
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
 
-        new_state = self._new_state()
+        state.change_state(self.target_state)
         # Send an email to all committers of the team
         committers = [c.email for c in state.language.team.get_committers()]
-        self.send_mail_new_state(state, new_state, committers)
-        return new_state
+        self.send_mail_new_state(state, committers)
 
 class ActionCI(ActionAbstract):
     name = 'CI'
     description = _('Submit to repository')
+    target_state = StateCommitted
     file_is_prohibited = True
 
-    def _new_state(self):
-        return StateCommitted()
-
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
         action_with_po = self.get_previous_action_with_po()
         try:
             state.branch.commit_po(action_with_po.file.path, state.domain, state.language, person)
-            new_state = self._new_state()
+            state.change_state(self.target_state)
         except:
             # Commit failed, state unchanged
             self._action_db.delete()
             # FIXME: somewhere the error should be catched and handled properly
-            #new_state = state
             raise Exception(_("The commit failed. The error was: '%s'") % sys.exc_info()[1])
-        if state != new_state:
-            self.send_mail_new_state(state, new_state, (state.language.team.mailing_list,))
-        return new_state
+
+        self.send_mail_new_state(state, (state.language.team.mailing_list,))
 
 class ActionRC(ActionAbstract):
     name = 'RC'
     # Translators: this indicates a committer is going to commit the file in the repository
     description = _('Reserve to submit')
+    target_state = StateCommitting
     file_is_prohibited = True
 
-    def _new_state(self):
-        return StateCommitting()
-
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
-        return self._new_state()
+        state.change_state(self.target_state)
 
 class ActionIC(ActionAbstract):
     name = 'IC'
     # Translators: this is used to indicate the file has been committed in the repository
     description = _('Inform of submission')
-
-    def _new_state(self):
-        return StateCommitted()
+    target_state = StateCommitted
 
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
 
-        new_state = self._new_state()
-        self.send_mail_new_state(state, new_state, (state.language.team.mailing_list,))
-        return new_state
+        state.change_state(self.target_state)
+        self.send_mail_new_state(state, (state.language.team.mailing_list,))
 
 class ActionTR(ActionAbstract):
     name = 'TR'
     # Translators: regardless of the translation completion, this file need to be reviewed
     description = _('Review required')
+    target_state = StateToReview
     arg_is_required = True
 
-    def _new_state(self):
-        return StateToReview()
-
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
 
-        new_state = self._new_state()
-        self.send_mail_new_state(state, new_state, (state.language.team.mailing_list,))
-        return new_state
+        state.change_state(self.target_state)
+        self.send_mail_new_state(state, (state.language.team.mailing_list,))
 
 class ActionAA(ActionAbstract):
     name = 'AA'
     description = _('Archive the actions')
-
-    def _new_state(self):
-        return StateNone()
+    target_state = StateNone
 
     def apply(self, state, person, comment=None, file=None):
         self.save_action_db(state, person, comment, file)
 
-        actions_db = ActionDb.objects.filter(state_db=state._state_db).order_by('id').all()
+        actions_db = ActionDb.objects.filter(state_db=state).order_by('id').all()
 
         sequence = None
         for action_db in actions_db:
@@ -791,8 +755,7 @@ class ActionAA(ActionAbstract):
             action_db_archived.save()
 
             action_db.delete() # The file is also automatically deleted, if it is not referenced elsewhere
-
-        return self._new_state()
+        state.change_state(self.target_state)
 
 class ActionUNDO(ActionAbstract):
     name = 'UNDO'
@@ -802,7 +765,7 @@ class ActionUNDO(ActionAbstract):
         self.save_action_db(state, person, comment, file)
 
         # Exclude WC because this action is a noop on State
-        actions_db = ActionDb.objects.filter(state_db__id=state._state_db.id).exclude(name='WC').order_by('-id')
+        actions_db = ActionDb.objects.filter(state_db__id=state.id).exclude(name='WC').order_by('-id')
         i = 0
         while (i < len(actions_db)):
             if actions_db[i].name == 'UNDO':
@@ -811,8 +774,9 @@ class ActionUNDO(ActionAbstract):
             else:
                 # Found
                 action = actions_db[i].get_action()
-                return action._new_state()
-        return StateNone()
+                state.change_state(action.target_state)
+                return
+        state.change_state(StateNone)
 
 class ActionSeparator(object):
     """ Fake action to add a separator in action menu """
diff --git a/vertimus/tests/__init__.py b/vertimus/tests/__init__.py
index f02298c..3c0a62a 100644
--- a/vertimus/tests/__init__.py
+++ b/vertimus/tests/__init__.py
@@ -20,14 +20,16 @@
 
 import os
 
+from django.conf import settings
 from django.core.files.base import File, ContentFile
 from django.core.files.uploadedfile import SimpleUploadedFile
+from django.core import mail
+from django.core.urlresolvers import reverse
 from django.http import QueryDict
 from django.utils.datastructures import MultiValueDict
-from django.conf import settings
 
 from teams.tests import TeamsAndRolesTests
-from stats.models import Module, Branch, Release, Category, Domain
+from stats.models import Module, Branch, Release, Category, Domain, Statistics
 from vertimus.models import *
 from vertimus.forms import ActionForm
 
@@ -62,10 +64,7 @@ class VertimusTest(TeamsAndRolesTests):
         self.d.save()
 
     def test_state_none(self):
-        sdb = StateDb(branch=self.b, domain=self.d, language=self.l)
-        sdb.name = 'None'
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateNone))
+        state = StateNone(branch=self.b, domain=self.d, language=self.l)
 
         action_names = [a.name for a in state.get_available_actions(self.pn)]
         self.assertEqual(action_names, ['WC'])
@@ -79,10 +78,7 @@ class VertimusTest(TeamsAndRolesTests):
             self.assertEqual(action_names, ['RT', 'WC', None, 'IC'])
 
     def test_state_translating(self):
-        sdb = StateDb(branch=self.b, domain=self.d, language=self.l, person=self.pt)
-        sdb.name = 'Translating'
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateTranslating))
+        state = StateTranslating(branch=self.b, domain=self.d, language=self.l, person=self.pt)
 
         for p in (self.pn, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
@@ -97,10 +93,7 @@ class VertimusTest(TeamsAndRolesTests):
         self.assertEqual(action_names, ['UT', 'UNDO', 'WC'])
 
     def test_state_translated(self):
-        sdb = StateDb(branch=self.b, domain=self.d, language=self.l, person=self.pt)
-        sdb.name = 'Translated'
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateTranslated))
+        state = StateTranslated(branch=self.b, domain=self.d, language=self.l, person=self.pt)
 
         action_names = [a.name for a in state.get_available_actions(self.pn)]
         self.assertEqual(action_names, ['WC'])
@@ -116,10 +109,7 @@ class VertimusTest(TeamsAndRolesTests):
             self.assertEqual(action_names, ['RP', 'RT', 'TR', 'TC', 'WC', None, 'IC', 'AA'])
 
     def test_state_proofreading(self):
-        sdb = StateDb(branch=self.b, domain=self.d, language=self.l, person=self.pr)
-        sdb.name = 'Proofreading'
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateProofreading))
+        state = StateProofreading(branch=self.b, domain=self.d, language=self.l, person=self.pr)
 
         for p in (self.pn, self.pt):
             action_names = [a.name for a in state.get_available_actions(p)]
@@ -134,10 +124,7 @@ class VertimusTest(TeamsAndRolesTests):
         self.assertEqual(action_names, ['UP', 'TR', 'TC', 'UNDO', 'WC'])
 
     def test_state_proofread(self):
-        sdb = StateDb(branch=self.b, domain=self.d, language=self.l, person=self.pr)
-        sdb.name = 'Proofread'
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateProofread))
+        state = StateProofread(branch=self.b, domain=self.d, language=self.l, person=self.pr)
 
         for p in (self.pn, self.pt):
             action_names = [a.name for a in state.get_available_actions(p)]
@@ -151,10 +138,7 @@ class VertimusTest(TeamsAndRolesTests):
             self.assertEqual(action_names, ['TC', 'RP', 'TR', 'WC', None, 'IC', 'AA'])
 
     def test_state_to_review(self):
-        sdb = StateDb(branch=self.b, domain=self.d, language=self.l, person=self.pt)
-        sdb.name = 'ToReview'
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateToReview))
+        state = StateToReview(branch=self.b, domain=self.d, language=self.l, person=self.pt)
 
         action_names = [a.name for a in state.get_available_actions(self.pn)]
         self.assertEqual(action_names, ['WC'])
@@ -168,10 +152,7 @@ class VertimusTest(TeamsAndRolesTests):
             self.assertEqual(action_names, ['RT', 'WC', None, 'IC', 'AA'])
 
     def test_state_to_commit(self):
-        sdb = StateDb(branch=self.b, domain=self.d, language=self.l, person=self.pr)
-        sdb.name = 'ToCommit'
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateToCommit))
+        state = StateToCommit(branch=self.b, domain=self.d, language=self.l, person=self.pr)
 
         for p in (self.pn, self.pt, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
@@ -182,10 +163,7 @@ class VertimusTest(TeamsAndRolesTests):
             self.assertEqual(action_names, ['RC', 'TR', 'WC', None, 'IC', 'AA'])
 
     def test_state_committing(self):
-        sdb = StateDb(branch=self.b, domain=self.d, language=self.l, person=self.pc)
-        sdb.name = 'Committing'
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateCommitting))
+        state = StateCommitting(branch=self.b, domain=self.d, language=self.l, person=self.pc)
 
         for p in (self.pn, self.pt, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
@@ -198,10 +176,7 @@ class VertimusTest(TeamsAndRolesTests):
         self.assertEqual(action_names, ['IC', 'TR', 'UNDO', 'WC'])
 
     def test_state_committed(self):
-        sdb = StateDb(branch=self.b, domain=self.d, language=self.l, person=self.pc)
-        sdb.name = 'Committed'
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateCommitted))
+        state = StateCommitted(branch=self.b, domain=self.d, language=self.l, person=self.pc)
 
         for p in (self.pn, self.pt, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
@@ -212,23 +187,22 @@ class VertimusTest(TeamsAndRolesTests):
             self.assertEqual(action_names, ['AA', 'WC', None, 'IC'])
 
     def test_action_wc(self):
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='None').get_state()
+        state = StateNone(branch=self.b, domain=self.d, language=self.l)
         state.save()
 
         action = ActionAbstract.new_by_name('WC')
-        new_state = state.apply_action(action, self.pt, "Hi!", None)
-        new_state.save()
+        state.apply_action(action, self.pt, "Hi!", None)
         # Test that submitting a comment without text generates a validation error
         form = ActionForm([('WC', u'Write a comment')], QueryDict('action=WC&comment='))
         self.assertTrue("A comment is needed" in str(form.errors))
 
     def test_action_rt(self):
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='None').get_state()
+        state = StateNone(branch=self.b, domain=self.d, language=self.l)
         state.save()
 
         action = ActionAbstract.new_by_name('RT')
-        new_state = state.apply_action(action, self.pt, "Reserved!", None)
-        new_state.save()
+        state.apply_action(action, self.pt, "Reserved!", None)
+        self.assertTrue(isinstance(state, StateTranslating))
 
     def test_action_ut(self):
         # Disabling the role
@@ -236,57 +210,61 @@ class VertimusTest(TeamsAndRolesTests):
         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 = StateTranslating(branch=self.b, domain=self.d, language=self.l, person=self.pt)
         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()
+        state.apply_action(action, self.pt, "Done by translator.", test_file)
+        self.assertTrue(isinstance(state, StateTranslated))
+        # Mail sent to mailing list
+        self.assertEquals(len(mail.outbox), 1)
+        self.assertEquals(mail.outbox[0].recipients(), [self.l.team.mailing_list])
+        self.assertEquals(mail.outbox[0].subject, u"gedit - gnome-2-24")
 
         # 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 = StateTranslated(branch=self.b, domain=self.d, language=self.l)
         state.save()
 
         action = ActionAbstract.new_by_name('RP')
-        new_state = state.apply_action(action, self.pr, "Reserved by a reviewer!")
-        new_state.save()
+        state.apply_action(action, self.pr, "Reserved by a reviewer!")
+        self.assertTrue(isinstance(state, StateProofreading))
 
     def test_action_up(self):
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Proofreading', person=self.pr).get_state()
+        state = StateProofreading(branch=self.b, domain=self.d, language=self.l, person=self.pr)
         state.save()
 
         test_file = ContentFile('test content')
         test_file.name = 'mytestfile.po'
 
         action = ActionAbstract.new_by_name('UP')
-        new_state = state.apply_action(action, self.pr, "Done.", test_file)
-        new_state.save()
+        state.apply_action(action, self.pr, "Done.", test_file)
+        self.assertTrue(isinstance(state, StateProofread))
 
     def test_action_tc(self):
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Proofread').get_state()
+        state = StateProofread(branch=self.b, domain=self.d, language=self.l)
         state.save()
 
         action = ActionAbstract.new_by_name('TC')
-        new_state = state.apply_action(action, self.pr, "Ready!")
-        new_state.save()
+        state.apply_action(action, self.pr, "Ready!")
+        self.assertTrue(isinstance(state, StateToCommit))
 
     def test_action_rc(self):
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='ToCommit').get_state()
+        state = StateToCommit(branch=self.b, domain=self.d, language=self.l)
         state.save()
 
         action = ActionAbstract.new_by_name('RC')
-        new_state = state.apply_action(action, self.pc, "This work is mine!")
-        new_state.save()
+        state.apply_action(action, self.pc, "This work is mine!")
+        self.assertTrue(isinstance(state, StateCommitting))
 
     def test_action_ic(self):
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Proofreading', person=self.pr).get_state()
+        state = StateProofreading(branch=self.b, domain=self.d, language=self.l, person=self.pr)
         state.save()
 
         # Create a new file
@@ -294,23 +272,19 @@ class VertimusTest(TeamsAndRolesTests):
         test_file.name = 'mytestfile.po'
 
         action = ActionAbstract.new_by_name('UP')
-        state = state.apply_action(action, self.pr, "Done.", test_file)
-        state.save()
+        state.apply_action(action, self.pr, "Done.", test_file)
 
         file_path = os.path.join(settings.MEDIA_ROOT, action.file.name)
         self.assertTrue(os.access(file_path, os.W_OK))
 
         action = ActionAbstract.new_by_name('TC')
-        state = state.apply_action(action, self.pc, "To commit.")
-        state.save()
+        state.apply_action(action, self.pc, "To commit.")
 
         action = ActionAbstract.new_by_name('RC')
-        state = state.apply_action(action, self.pc, "Reserved commit.")
-        state.save()
+        state.apply_action(action, self.pc, "Reserved commit.")
 
         action = ActionAbstract.new_by_name('IC')
-        state = state.apply_action(action, self.pc, "Committed.")
-        state.save()
+        state.apply_action(action, self.pc, "Committed.")
 
         self.assertTrue(not os.access(file_path, os.F_OK), "%s not deleted" % file_path)
 
@@ -321,75 +295,75 @@ class VertimusTest(TeamsAndRolesTests):
         self.assertTrue(not os.access(filename_archived, os.F_OK), "%s not deleted" % filename_archived)
 
     def test_action_tr(self):
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Translated').get_state()
+        state = StateTranslated(branch=self.b, domain=self.d, language=self.l)
         state.save()
 
         action = ActionAbstract.new_by_name('TR')
-        state = state.apply_action(action, self.pc, "Bad work :-/")
-        state.save()
+        state.apply_action(action, self.pc, "Bad work :-/")
+        self.assertTrue(isinstance(state, StateToReview))
 
     def test_action_ba(self):
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Committed', person=self.pr).get_state()
+        state = StateCommitted(branch=self.b, domain=self.d, language=self.l, person=self.pr)
         state.save()
 
         action = ActionAbstract.new_by_name('AA')
-        state = state.apply_action(action, self.pc, comment="I don't want to disappear :)")
-        state.save()
+        state.apply_action(action, self.pc, comment="I don't want to disappear :)")
 
-        sdb = StateDb.objects.get(branch=self.b, domain=self.d, language=self.l)
-        state = sdb.get_state()
-        self.assert_(isinstance(state, StateNone))
+        state = State.objects.get(branch=self.b, domain=self.d, language=self.l)
+        self.assertTrue(isinstance(state, StateNone))
 
     def test_action_undo(self):
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='None').get_state()
+        state = StateNone(branch=self.b, domain=self.d, language=self.l)
         state.save()
 
         action = ActionAbstract.new_by_name('RT')
-        state = state.apply_action(action, self.pt, "Reserved!")
-        state.save()
+        state.apply_action(action, self.pt, "Reserved!")
 
         action = ActionAbstract.new_by_name('UNDO')
-        state = state.apply_action(action, self.pt, "Ooops! I don't want to do that. Sorry.")
-        state.save()
+        state.apply_action(action, self.pt, "Ooops! I don't want to do that. Sorry.")
 
         self.assertEqual(state.name, 'None')
 
         action = ActionAbstract.new_by_name('RT')
-        state = state.apply_action(action, self.pt, "Translating")
-        state.save()
+        state.apply_action(action, self.pt, "Translating")
 
         action = ActionAbstract.new_by_name('UT')
-        state = state.apply_action(action, self.pt, "Translated")
-        state.save()
+        state.apply_action(action, self.pt, "Translated")
 
         action = ActionAbstract.new_by_name('RT')
-        state = state.apply_action(action, self.pt, "Reserved!")
-        state.save()
+        state.apply_action(action, self.pt, "Reserved!")
 
         action = ActionAbstract.new_by_name('UNDO')
-        state = state.apply_action(action, self.pt, "Ooops! I don't want to do that. Sorry.")
-        state.save()
+        state.apply_action(action, self.pt, "Ooops! I don't want to do that. Sorry.")
 
         self.assertEqual(state.name, 'Translated')
 
         action = ActionAbstract.new_by_name('RT')
-        state = state.apply_action(action, self.pt, "Translating 1")
-        state.save()
+        state.apply_action(action, self.pt, "Translating 1")
 
         action = ActionAbstract.new_by_name('UNDO')
-        state = state.apply_action(action, self.pt, "Undo 1")
-        state.save()
+        state.apply_action(action, self.pt, "Undo 1")
 
         action = ActionAbstract.new_by_name('RT')
-        state = state.apply_action(action, self.pt, "Translating 2")
-        state.save()
+        state.apply_action(action, self.pt, "Translating 2")
 
         action = ActionAbstract.new_by_name('UNDO')
-        state = state.apply_action(action, self.pt, "Undo 2")
-        state.save()
+        state.apply_action(action, self.pt, "Undo 2")
 
         self.assertEqual(state.name, 'Translated')
 
+    def test_vertimus_view(self):
+        pot_stat = Statistics(language=None, branch=self.b, domain=self.d)
+        pot_stat.save()
+
+        url = reverse('vertimus_by_ids', args=[self.b.id, self.d.id, self.l.id])
+        response = self.client.get(url)
+        self.assertNotContains(response, '<option value="WC">')
+
+        self.client.login(username=self.pn.username, password='password')
+        response = self.client.get(url)
+        self.assertContains(response, '<option value="WC">')
+
     def test_uploaded_file_validation(self):
         # Test a non valid po file
         post_content = QueryDict('action=WC&comment=Test1')
@@ -411,34 +385,28 @@ class VertimusTest(TeamsAndRolesTests):
 
     def test_mysql(self):
         # Copied from test_action_undo() with minor changes
-        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='None').get_state()
+        state = StateNone(branch=self.b, domain=self.d, language=self.l)
         state.save()
 
         action = ActionAbstract.new_by_name('RT')
-        state = state.apply_action(action, self.pr, "Reserved!")
-        state.save()
+        state.apply_action(action, self.pr, "Reserved!")
 
         action = ActionAbstract.new_by_name('UNDO')
-        state = state.apply_action(action, self.pr, "Ooops! I don't want to do that. Sorry.")
-        state.save()
+        state.apply_action(action, self.pr, "Ooops! I don't want to do that. Sorry.")
 
         action = ActionAbstract.new_by_name('RT')
-        state = state.apply_action(action, self.pr, "Translating")
-        state.save()
+        state.apply_action(action, self.pr, "Translating")
 
         action = ActionAbstract.new_by_name('UT')
-        state = state.apply_action(action, self.pr, "Translated")
-        state.save()
+        state.apply_action(action, self.pr, "Translated")
 
         action = ActionAbstract.new_by_name('RP')
-        state = state.apply_action(action, self.pr, "Proofreading")
-        state.save()
+        state.apply_action(action, self.pr, "Proofreading")
 
         action = ActionAbstract.new_by_name('UNDO')
-        state = state.apply_action(action, self.pr, "Ooops! I don't want to do that. Sorry.")
-        state.save()
+        state.apply_action(action, self.pr, "Ooops! I don't want to do that. Sorry.")
 
-        actions_db = ActionDb.objects.filter(state_db__id=state._state_db.id).exclude(name='WC').order_by('-id')
+        actions_db = ActionDb.objects.filter(state_db__id=state.id).exclude(name='WC').order_by('-id')
 
         # So the last action is UNDO
         self.assert_(isinstance(actions_db[0].get_action(), ActionUNDO))
diff --git a/vertimus/views.py b/vertimus/views.py
index b21c2bc..a04e038 100644
--- a/vertimus/views.py
+++ b/vertimus/views.py
@@ -27,7 +27,7 @@ from django.utils.translation import ugettext as _
 
 from stats.models import Statistics, Module, Branch, Domain, Language
 from stats.utils import is_po_reduced
-from vertimus.models import StateDb, ActionDb, ActionDbArchived, ActionAbstract
+from vertimus.models import State, ActionDb, ActionDbArchived, ActionAbstract
 from vertimus.forms import ActionForm
 
 def vertimus_by_stats_id(request, stats_id, lang_id):
@@ -71,17 +71,16 @@ def vertimus(request, branch, domain, language, stats=None, level="0"):
         po_url = stats.po_url()
 
     # Get the state of the translation
-    (state_db, created) = StateDb.objects.get_or_create(
+    (state, created) = State.objects.get_or_create(
         branch=branch,
         domain=domain,
         language=language)
-    other_branch_states = StateDb.objects.filter(
+    other_branch_states = State.objects.filter(
         domain=domain, language=language).exclude(branch=branch.pk).exclude(name='None')
 
-    state = state_db.get_state()
     if level == 0:
         # Current actions
-        action_history = ActionDb.get_action_history(state_db)
+        action_history = ActionDb.get_action_history(state)
     else:
         sequence = state.get_action_sequence_from_level(level)
         action_history = ActionDbArchived.get_action_history(sequence)
@@ -107,9 +106,8 @@ def vertimus(request, branch, domain, language, stats=None, level="0"):
                 comment = action_form.cleaned_data['comment']
 
                 action = ActionAbstract.new_by_name(action)
-                new_state = state.apply_action(action, person, comment,
-                                               request.FILES.get('file', None))
-                new_state.save()
+                state.apply_action(action, person, comment,
+                                   request.FILES.get('file', None))
 
                 return HttpResponseRedirect(
                     urlresolvers.reverse('vertimus_by_names',



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