damned-lies r1239 - in trunk: . media/img templates templates/vertimus vertimus vertimus/tests



Author: stephaner
Date: Wed Dec 24 00:59:37 2008
New Revision: 1239
URL: http://svn.gnome.org/viewvc/damned-lies?rev=1239&view=rev

Log:
2008-12-24  StÃphane Raimbault  <stephane raimbault gmail com>

	* settings_sample.py: New settings for email and upload.
	* templates/stats_show.html: Link to Vertimus view.
	* templates/vertimus/vertimus_detail.html: Vertimus template
	* urls.py: Forward to vertimus.urls
	* vertimus/models.py: Major model changes.
	* vertimus/tests/__init__.py: 20 unit tests.
	* vertimus/urls.py: Vertimus URLs.
	* vertimus/views.py: View and form management.
	* media/img/person.png: Nice person icon.


Added:
   trunk/media/img/person.png   (contents, props changed)
   trunk/templates/vertimus/
   trunk/templates/vertimus/vertimus_detail.html   (contents, props changed)
   trunk/vertimus/urls.py
Modified:
   trunk/ChangeLog
   trunk/settings_sample.py
   trunk/templates/stats_show.html
   trunk/urls.py
   trunk/vertimus/models.py
   trunk/vertimus/tests/__init__.py
   trunk/vertimus/views.py

Added: trunk/media/img/person.png
==============================================================================
Binary file. No diff available.

Modified: trunk/settings_sample.py
==============================================================================
--- trunk/settings_sample.py	(original)
+++ trunk/settings_sample.py	Wed Dec 24 00:59:37 2008
@@ -38,6 +38,12 @@
 # 
 # Please refer to the README file to create an UTF-8 database with MySQL.
 
+EMAIL_HOST = 'localhost'
+EMAIL_HOST_USER = ''
+EMAIL_HOST_PASSWORD = ''
+EMAIL_SUBJECT_PREFIX = '[DL]'
+SERVER_EMAIL = 'server l10n gnome org'
+
 # Local time zone for this installation. Choices can be found here:
 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
 # although not all choices may be available on all operating systems.
@@ -64,6 +70,11 @@
 # Examples: "http://media.lawrence.com";, "http://example.com/media/";
 MEDIA_URL = 'media'
 
+# By default, Django stores files locally, using the MEDIA_ROOT and MEDIA_URL settings
+UPLOAD_DIR = 'upload'
+UPLOAD_BACKUP_DIR = 'upload-backup'
+FILE_UPLOAD_PERMISSIONS = 0600
+
 # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
 # trailing slash.
 # Examples: "http://foo.com/media/";, "/media/".
@@ -86,6 +97,7 @@
     'django.middleware.common.CommonMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.middleware.transaction.TransactionMiddleware',
 )
 
 ROOT_URLCONF = 'djamnedlies.urls'

Modified: trunk/templates/stats_show.html
==============================================================================
--- trunk/templates/stats_show.html	(original)
+++ trunk/templates/stats_show.html	Wed Dec 24 00:59:37 2008
@@ -5,57 +5,61 @@
   <h3>{% trans stat1.domain.get_description %}
   {% ifnotequal stat1.domain.directory 'help' %}
     {% ifnotequal stat1.domain.directory 'po' %}
-    <br /><span class='path'>{{ stat1.domain.directory }}</span>
+    <br />
+    <span class="path">{{ stat1.domain.directory }}</span>
     {% endifnotequal %}
-  {% endifnotequal %}</h3>
+  {% endifnotequal %}
+  </h3>
   <a href="{{ stat1.po_url }}"><img src="/media/img/download.png" alt="Download POT file" /></a>
   {{ stat1.pot_text }}
-  
-  <!-- /* This is the title of the section that lists notices about a module */ -->
+  <!-- This is the title of the section that lists notices about a module -->
   {% if stat1.information_set.all %}
-    <h4>{% trans "Notices" %}</h4>
-    <table>
+  <h4>{% trans "Notices" %}</h4>
+  <table>
     {% for msg in stat1.information_set.all %}
-      <tr><td valign="top"><img src="{{ msg.get_icon }}" alt="{{ msg.type }}" /></td><td>{{ msg.get_description|safe }}</td></tr>
+    <tr>
+      <td valign="top"><img src="{{ msg.get_icon }}" alt="{{ msg.type }}" /></td>
+      <td>{{ msg.get_description|safe }}</td>
+    </tr>
     {% endfor %}
-    </table>
+  </table>
   {% endif %}
 
-<table class="stats">
-<thead><tr><th>{% trans "Language" %}</th><th>{% trans "Translated" %}</th>
- {% if stat1.fig_count %}
-    <th></th>
- {% endif %}
-<th>{% trans "Graph" %}</th></tr></thead>
-
+  <table class="stats">
+    <thead><tr>
+        <th>{% trans "Language" %}</th><th>{% trans "Translated" %}</th>
+        {% if stat1.fig_count %}
+        <th></th>
+        {% endif %}
+        <th>{% trans "Graph" %}</th>
+    </tr></thead>
+    <!-- FIXME line / stat / stats naming -->
     {% for line in stat %}
-      {% if not forloop.first %}
-      <tr>
-      <td class="leftcell"><a href="{{ line.po_url }}">{{ line.get_lang }}</a>
-      {% with line.most_important_message as msg %} 
-      {% if msg %}
-      <img src="{{ msg.get_icon }}" title="{{ msg.get_description }}" alt="{{ msg.type }}" />
-      {% endif %}
-      {% endwith %}
+    {% if not forloop.first %}
+    <tr>
+      <td class="leftcell"><a href="{% url vertimus-stats-id-view line.id %}">{{ line.get_lang }}</a>
+        {% with line.most_important_message as msg %} 
+        {% if msg %}
+        <img src="{{ msg.get_icon }}" title="{{ msg.get_description }}" alt="{{ msg.type }}" />
+        {% endif %}
+        {% endwith %}
       </td>
       <td>{{ line.get_translationstat|safe }}</td>
       {% if stat1.fig_count %}
-       <td><a href="{% url stats.views.docimages module_name=module.name,potbase=stat1.domain.name,branch_name=branch.name,langcode=line.language.locale %}">
-           <img src="/media/img/figure.png" alt="{% trans "Display document figures" %}"></a></td>
+      <td><a href="{% url stats.views.docimages module_name=module.name,potbase=stat1.domain.name,branch_name=branch.name,langcode=line.language.locale %}">
+          <img src="/media/img/figure.png" alt="{% trans "Display document figures" %}"></a>
+      </td>
       {% endif %}
-      <td style="width: 108px; text-align: center;"><div class="graph">
-        <div class="translated" style="width: {{ line.tr_percentage }}px;"></div>
-        <div class="fuzzy" style="{{ LANGUAGE_BIDI|yesno:"right,left" }}:{{ line.tr_percentage }}px; width:{{ line.fu_percentage }}px;"></div>
-        <div class="untranslated" style="{{ LANGUAGE_BIDI|yesno:"right,left" }}:{{ line.tr_percentage|add:line.fu_percentage }}px; width: {{ line.un_percentage }}px;"></div>
+      <td style="width: 108px; text-align: center;">
+        <div class="graph">
+          <div class="translated" style="width: {{ line.tr_percentage }}px;"></div>
+          <div class="fuzzy" style="{{ LANGUAGE_BIDI|yesno:"right,left" }}:{{ line.tr_percentage }}px; width:{{ line.fu_percentage }}px;"></div>
+          <div class="untranslated" style="{{ LANGUAGE_BIDI|yesno:"right,left" }}:{{ line.tr_percentage|add:line.fu_percentage }}px; width: {{ line.un_percentage }}px;"></div>
         </div>
       </td>
-      </tr>
-      
-      {% endif %}
+    </tr>
+    {% endif %}
     {% endfor %}
+  </table>
   {% endwith %}
-
-</table>
-
 {% endfor %}
-

Added: trunk/templates/vertimus/vertimus_detail.html
==============================================================================
--- (empty file)
+++ trunk/templates/vertimus/vertimus_detail.html	Wed Dec 24 00:59:37 2008
@@ -0,0 +1,104 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block title %}
+{% blocktrans with module.get_description as name %}Module Translation: {{ name }}{% endblocktrans %}
+{% endblock %}
+
+{% block content %}
+<div class="mainpage">
+
+<h1>{{ module.get_description }} - {{ branch.name }} - {% trans domain.description %} - {% trans language %}</h1>
+
+{% if module.comment %}
+  <p>{{ module.comment|safe }}</p>
+{% else %}
+  {% ifnotequal module.vcs_root "http://svn.gnome.org/svn"; %}
+  <p><i><img src="/media/img/warn.png" alt="Warning logo" />{% trans "This module is not part of the GNOME SVN repository. Please check the module's web page to see where to send translations." %}</i></p>
+  {% endifnotequal %}
+{% endif %}
+
+<h3>State: {{ state }}
+{% ifnotequal state.name 'none' %}
+({{ state.updated|date:"D d M Y P" }})
+{% endifnotequal %}
+</h3>
+
+<a href="{{ stats.po_url }}"><img src="/media/img/download.png" alt="Download POT file" /></a>
+{{ stats.pot_text }}
+ 
+{% if stats.information_set.all %}
+<h4>{% trans "Notices" %}</h4>
+<table>
+  {% for info in stats.information_set.all %}
+  <tr>
+    <td valign="top"><img src="{{ info.get_icon }}" alt="{{ info.type }}" /></td>
+    <td>{{ info.get_description|safe }}</td>
+  </tr>
+  {% endfor %}
+</table>
+{% endif %}
+
+<table>
+  <tr>
+    <td>{% trans "Translated:" %}</td>
+    <td>{{ stats.get_translationstat|safe }}</td>
+  </tr>
+  <tr>
+    <td>{% trans "Graph:" %}</td>
+    <td><div class="graph">
+        <div class="translated" style="width: {{ stats.tr_percentage }}px;"></div>
+        <div class="fuzzy" style="left:{{ stats.tr_percentage }}px; width:{{ stats.fu_percentage }}px;"></div>
+        <div class="untranslated" style="left:{{ stats.tr_percentage|add:stats.fu_percentage }}px; width: {{ stats.un_percentage }}px;"></div>
+      </div>
+    </td>
+  </tr>
+</table>
+
+{% if stats.fig_count %}
+<p><a href="{% url stats.views.docimages module_name=module.name,potbase=stats.domain.name,branch_name=branch.name,langcode=stats.language.locale %}">
+    <img src="/media/img/figure.png" alt="{% trans "Display document figures" %}"></a>
+</p>
+{% endif %}
+
+{% if action_history %}
+<h3>{% trans "Action History" %}</h3>
+<table>
+  {% for action in action_history %}
+  <tr>
+    <td>{{ action }}, 
+      <img src="/media/img/person.png" alt="Person"/>
+      <!-- Spam protection -->
+      <a href="{{ action.person.get_absolute_url }}">{{ action.person.name }}</a>
+      {{ action.created }}
+    </td>
+  </tr>
+  <tr>
+    <td>
+      {% if action.file %}
+      {% trans "Uploaded file:" %} <a href="{{ action.file.url }}">{{ action.file.name }}</a>
+        {% if action.comment %}<br/>{% endif %}
+      {% endif %}
+      {{ action.comment|linebreaksbr|default:"No comment" }}<br/>
+    </td>
+  </tr>
+  <tr class="noborder"><td>&nbsp;</td></tr>
+  {% endfor %}
+</table>
+{% endif %}
+
+<h3>{% trans "New Action" %}</h3>
+
+{% if action_form %}
+  <form enctype="multipart/form-data" action="/vertimus/{{ stats.id }}" method="POST">
+    <table>
+      {{ action_form.as_table }}
+      <tr><td></td>
+        <td><input type="submit" value="Submit"></td><tr>
+    </table>
+  </form>
+{% else %}
+  {% blocktrans with language.team.description as team_name %}You need to be authenticated and to be member of the {{ team_name }} team.{% endblocktrans %}
+{% endif %}
+
+{% endblock %}

Modified: trunk/urls.py
==============================================================================
--- trunk/urls.py	(original)
+++ trunk/urls.py	Wed Dec 24 00:59:37 2008
@@ -16,7 +16,7 @@
     # users is the hardcoded url in the contrib.auth User class, making it identical to /people
     (r'^users/', include('people.urls')),
     (r'^languages/', include('languages.urls')),
-    #(r'^stats/', include('stats.urls')),
+    (r'^vertimus/', include('vertimus.urls')),
     (r'^admin/(.*)', admin.site.root),
 )
 

Modified: trunk/vertimus/models.py
==============================================================================
--- trunk/vertimus/models.py	(original)
+++ trunk/vertimus/models.py	Wed Dec 24 00:59:37 2008
@@ -18,271 +18,545 @@
 # 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
 from django.db import models
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ugettext as _
+from django.core import mail, urlresolvers
+from django.contrib.sites.models import Site
+from django.conf import settings
+from django.core.files.storage import default_storage
+
 from stats.models import Branch, Domain
 from languages.models import Language
 from people.models import Person
 
-ACTION_CODES = (
-    'WC', 
-    'RT', 'UT',
-    'RP', 'UP',
-    'TC', 'RC',
-    'IC', 'TR',
-    'BA', 'UNDO')
+#
+# States
+#
 
-class VtmAction(models.Model):
+# 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"""
     branch = models.ForeignKey(Branch)
     domain = models.ForeignKey(Domain)
     language = models.ForeignKey(Language)
-    person = models.ForeignKey(Person)
-
-    code = models.CharField(max_length=8)
-    created = models.DateField(auto_now_add=True, editable=False)
-    comment = models.TextField(blank=True, null=True)
-    file = models.FileField(upload_to='vertimus/%Y/%m/', blank=True, null=True)
+    person = models.ForeignKey(Person, default=None, null=True)
+    
+    name = models.SlugField(max_length=20, default='None')
+    updated = models.DateTimeField(default=datetime.now, editable=False)
 
     class Meta:
-        db_table = 'vtm_action'
-        ordering = ('created',)
-        get_latest_by = 'created'
+        db_table = 'state'
+        unique_together = ('branch', 'domain', 'language')
 
-    @classmethod
-    def get_all(cls):
-        actions = []
-        for code in ACTION_CODES:
-            actions.append(eval('VtmAction' + code + '()'))
-    
-    @classmethod
-    def get_last_action(cls, branch, domain, language):
-        return VtmAction.objects.filter(
-            branch=branch, domain=domain, language=language).order_by('-created')[0]
-
-    def apply(self, branch, domain, language, person, comment=None, file=None):
-        self.branch = branch
-        self.domain = domain
-        self.language = language
-        self.person = person
-        self.comment = comment
-        self.file = file
-        self.save()
-
-        return self._apply_child()
+    def get_state(self):
+        state = eval('State'+self.name)()
+        state._state_db = self
+        return state
 
     def __unicode__(self):
         return self.name
 
-#
-# States
-#
-
-class VtmState(object):
+class StateAbstract(object):
     """Abstract class"""
-    def __init__(self, code, name):
-        self.code = code
-        self.name = name
-        self.color = ''
 
-    def __unicode__(self):
-        return self.name
+    @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_code(self):
-        return self.code
+    def get_state_db(self):
+        return self._state_db
 
-    @classmethod
-    def get_actions(cls, action_codes=[]):
-        action_codes.append('WC')
-        return [ eval('VtmAction' + action_code)() for action_code in action_codes ]
-
-    def apply_action(self, action, branch, domain, language, person, comment=None, file=None):
-        new_state = action.apply(branch, domain, language, person, comment, file)
-        if new_state == None:
-            return self
+    def __unicode__(self):
+        return self.description
+
+    def _get_available_actions(self, action_names):
+        action_names.append('WC')
+        return [eval('Action' + action_name)() for action_name in action_names]
+
+    def apply_action(self, action, person, comment=None, file=None):
+        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
+                return new_state
+            else:
+                return self
         else:
-            return new_state
+            raise Exception('Not allowed')
 
-    def apply_action_code(self, action_code, branch, domain, language, person, comment=None, file=None):
-        action = eval('VtmAction' + action_code)()
-        self.apply_action(action, branch, domain, language, person, comment, file)
+    def save(self):
+        self._state_db.save()
 
 
-class VtmStateNone(VtmState):
-    def __init__(self):
-        super(VtmStateNone, self).__init__('None', 'Inactive')
+class StateNone(StateAbstract):
+    name = 'None'
+    description = _('Inactive')
 
-    def get_actions(self, branch, domain, language, person):
-        return super(VtmStateNone, self).get_actions(['RT'])
+    def get_available_actions(self, person):
+        action_names = []
 
+        if person.is_translator(self.language.team):
+            action_names = ['RT']
 
-class VtmStateTranslating(VtmState):
-    def __init__(self):
-        super(VtmStateTranslating, self).__init__('Translating', 'Translating')
+        return self._get_available_actions(action_names)
 
-    def get_actions(self, branch, domain, language, person):
-        action_codes = []
 
-        last_action = VtmAction.get_last_action(branch, domain, language)
-        if (last_action.person == person):
-            action_codes = ['UT', 'UNDO']
-                    
-        return super(VtmStateTranslating, self).get_actions(action_codes)
+class StateTranslating(StateAbstract):
+    name = 'Translating'
+    description = _('Translating') 
 
+    def get_available_actions(self, person):
+        action_names = []
 
-class VtmStateTranslated(VtmState):
-    def __init__(self):
-        super(VtmStateTranslated, self).__init__('Translated', 'Translated')
+        if (self.person == person):
+            action_names = ['UT', 'UNDO']
+                    
+        return self._get_available_actions(action_names)
 
-    def get_actions(self, branch, domain, language, person):
-        # FIXME
-        if person.is_reviewer:
-            action_codes = ['RP']
-        else:
-            action_codes = []
 
-        action_codes.append('RT')
-        return super(VtmStateTranslated, self).get_actions(action_codes)
+class StateTranslated(StateAbstract):
+    name = 'Translated'
+    description = _('Translated')
+
+    def get_available_actions(self, person):
+        action_names = []
 
+        if person.is_reviewer(self.language.team):
+            action_names.append('RP')
 
-class VtmStateToReview(VtmState):
-    def __init__(self):
-        super(VtmStateToReview, self).__init__('ToReview', 'To Review')
-        self.color = 'needswork';
+        if person.is_translator(self.language.team):
+            action_names.append('RT')
+            action_names.append('TR')
 
-    def get_actions(self, branch, domain, language, person):
-        return super(VtmStateToReview, self).get_actions(['RT'])
+        return self._get_available_actions(action_names)
 
 
-class VtmStateProofreading(VtmState):
-    def __init__(self):
-        super(VtmStateProofreading, self).__init__('Proofreading', 'Proofreading')
+class StateProofreading(StateAbstract):
+    name = 'Proofreading'
+    description = 'Proofreading'
 
-    def get_actions(self, branch, domain, language, person):
-        action_codes = []
+    def get_available_actions(self, person):
+        action_names = []
         
-        # FIXME
-        if person.is_commiter:
-            last_action = VtmAction.get_last_action(branch, domain, language)
-            if last_action.person == person:
-                action_codes = ['UP', 'TR', 'TC', 'UNDO']
+        if person.is_reviewer(self.language.team):
+            if (self.person == person):
+                action_names = ['UP', 'TR', 'TC', 'UNDO']
                     
-        return super(VtmStateProofreading, self).get_actions(action_codes)
+        return self._get_available_actions(action_names)
 
 
-class VtmStateProofread(VtmState):
-    def __init__(self):
-        super(VtmStateProofread, self).__init__('Proofread', 'Proofread')
+class StateProofread(StateAbstract):
+    name = 'Proofread'
+    description = _('Proofread')
 
-    def get_actions(self, branch, domain, language, person):
-        if person.is_reviewer:
-            action_codes = ['TC', 'TR']
+    def get_available_actions(self, person):
+        if person.is_reviewer(self.language.team):
+            action_names = ['TC', 'TR']
         else:
-            action_codes = []
+            action_names = []
+
+        return self._get_available_actions(action_names)
 
-        return super(VtmStateProofread, self).get_actions(action_codes)
 
+class StateToReview(StateAbstract):
+    name = 'ToReview'
+    description = 'To Review'
 
-class VtmStateToCommit(VtmState):
-    def __init__(self):
-        super(VtmStateToCommit, self).__init__('ToCommit', 'To Commit')
+    def get_available_actions(self, person):
+        action_names = []
+        if person.is_translator(self.language.team):
+            action_names.append('RT')
 
-    def get_actions(self, branch, domain, language, person):
-        if person.is_commiter:
-            action_codes = ['RC', 'TR']
+        return self._get_available_actions(action_names)
+
+
+class StateToCommit(StateAbstract):
+    name = 'ToCommit'
+    description = _('To Commit')
+
+    def get_available_actions(self, person):
+        if person.is_committer(self.language.team):
+            action_names = ['RC', 'TR']
         else:
-            action_codes = []
+            action_names = []
             
-        return super(VtmStateToCommit, self).get_actions(action_codes)
+        return self._get_available_actions(action_names)
 
 
-class VtmStateCommitting(VtmState):
-    def __init__(self):
-        super(VtmStateCommitting, self).__init__('Committing', 'Committing')
+class StateCommitting(StateAbstract):
+    name = 'Committing'
+    description = _('Committing')
         
-    def get_actions(self, branch, domain, language, person):
-        action_codes = []
+    def get_available_actions(self, person):
+        action_names = []
 
-        # FIXME
-        if person.is_commiter:
-            last_action = VtmAction.get_last_action(branch, domain, language)
-            if (last_action.person == person):
-                action_codes = ['IC', 'TR', 'TC', 'UNDO']
+        if person.is_committer(self.language.team):
+            if (self.person == person):
+                action_names = ['IC', 'TR', 'UNDO']
             
-        return super(VtmStateCommitting, self).get_actions(action_codes)
+        return self._get_available_actions(action_names)
 
 
-class VtmStateCommitted(VtmState):
-    def __init__(self):
-        super(VtmStateCommitted, self).__init__('Committed', 'Committed')
+class StateCommitted(StateAbstract):
+    name = 'Committed'
+    description = _('Committed')
 
-    def get_actions(self, branch, domain, language, person):
-        return super(VtmStateCommitted, self).get_actions()
+    def get_available_actions(self, person):
+        if person.is_committer(self.language.team):
+            action_names = ['BA']
+        else:            action_names = []
+
+        return self._get_available_actions(action_names)
 
 #
 # Actions 
 #
 
-class VtmActionWC(VtmAction):
+
+ACTION_NAMES = (
+    'WC',
+    'RT', 'UT',
+    'RP', 'UP',
+    'TC', 'RC',
+    'IC', 'TR',
+    'BA', 'UNDO')
+
+class ActionDb(models.Model):
+    state_db = models.ForeignKey(StateDb)
+    person = models.ForeignKey(Person)
+
+    name = models.SlugField(max_length=8)
+    description = None
+    created = models.DateTimeField(auto_now_add=True, editable=False)
+    comment = models.TextField(blank=True, null=True)
+    file = models.FileField(upload_to=settings.UPLOAD_DIR, blank=True, null=True)
+
     class Meta:
-        db_table = 'vtm_action_wc'
+        db_table = 'action'
+        ordering = ('-created',)
+
+    def get_action(self):
+        action = eval('Action' + self.name)()
+        action._action_db = self
+        return action
+
+    @classmethod
+    def get_action_history(cls, state_db):
+        if state_db:
+            return [va_db.get_action() for va_db in ActionDb.objects.filter(
+                    state_db__id=state_db.id).order_by('created')]
+        else:
+            return []
 
-    def __init__(self, *args, **kwargs):
-        models.Model.__init__(self, *args, **kwargs)
-        self.code = 'WC'
-        self.name = 'Write a comment'
+    def __unicode__(self):
+        return self.name
+
+class ActionAbstract(object):
+    """Abstract class"""
+
+    @classmethod
+    def new_by_name(cls, action_name):
+         return eval('Action' + action_name)()
+
+    @classmethod
+    def get_all(cls):
+        """Reserved to the admins to access all actions"""
+        return [eval('Action' + action_name)() for action_name in ACTION_NAMES]
+
+    @property
+    def person(self):
+        return self._action_db.person
+
+    @property
+    def comment(self):
+        return self._action_db.comment
+
+    @property
+    def file(self):
+        return self._action_db.file
+
+    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.save()
+
+    def __unicode__(self):
+        return self.description
+
+    def send_mail_new_state(self, old_state, new_state, recipient_list):
+        # Remove None items from the list
+        recipient_list = filter(lambda x: x is not None, recipient_list)
+
+        if recipient_list:
+            current_site = Site.objects.get_current()
+            url = "http://%s%s"; % (current_site.domain, urlresolvers.reverse(
+                'vertimus-names-view',
+                 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
+            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.name, 
+                'new_state': new_state, 
+                'url': url
+            }
+            message += self.comment or _("Without comment")
+            message += "\n" + self.person.name
+            mail.send_mail(subject, message, self.person.email, recipient_list)
+
+class ActionWC(ActionAbstract):
+    name = 'WC'
+    description = _('Write a comment')
         
     def _new_state(self):
         return None
 
-    def _apply_child(self):
+    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'):
+            translator_emails.add(d['email'])
+
+        # Remove None items from the list
+        translator_emails = filter(lambda x: x is not None, translator_emails)
+
+        if translator_emails:
+            current_site = Site.objects.get_current()
+            url = "http://%s%s"; % (current_site.domain, urlresolvers.reverse(
+                'vertimus-names-view',
+                args = (
+                    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,
+
+A new comment has been left on %(module)s - %(branch)s - %(domain)s (%(language)s).
+%(url)s
+
+""") % { 
+                'module': state.branch.module.name,
+                'branch': state.branch.name,
+                'domain': state.domain.name,
+                'language': state.language.name, 
+                'url': url
+            }
+            message += comment or _("Without comment")
+            message += "\n" + person.name
+            mail.send_mail(subject, message, person.email, translator_emails)
+
         return self._new_state()
 
+class ActionRT(ActionAbstract):
+    name = 'RT'
+    description = _('Reserve for translation')
+        
+    def _new_state(self):
+        return StateTranslating()
 
-class VtmActionRT(VtmAction):
-    class Meta:
-        db_table = 'vtm_action_rt'
+    def apply(self, state, person, comment=None, file=None):
+        self.save_action_db(state, person, comment, file)
+        return self._new_state()
 
-    def __init__(self, *args, **kwargs):
-        models.Model.__init__(self, *args, **kwargs)
-        self.code = 'RT'
-        self.name = 'Reserve for translation'
+class ActionUT(ActionAbstract):
+    name = 'UT'
+    description = _('Upload the new translation')
         
     def _new_state(self):
-        return VtmStateTranslating()
+        return StateTranslated()
+
+    def apply(self, state, person, comment=None, file=None):
+        self.save_action_db(state, person, comment, file)
 
-    def _apply_child(self):
+        new_state = self._new_state()
+        self.send_mail_new_state(state, new_state, (state.language.team.mailing_list,))
+        return new_state
+
+class ActionRP(ActionAbstract):
+    name = 'RP'
+    description = _('Reserve for proofreading')
+
+    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()
 
+class ActionUP(ActionAbstract):
+    name = 'UP'
+    description = _('Upload for proofreading')
 
-class VtmActionUT(VtmAction):
-    class Meta:
-        db_table = 'vtm_action_ut'
+    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
+
+class ActionTC(ActionAbstract):
+    name = 'TC'
+    description = _('Ready for submission')
 
-    def __init__(self, *args, **kwargs):
-        models.Model.__init__(self, *args, **kwargs)
-        self.code = 'UT'
-        self.name = 'Upload the new translation'
-        
     def _new_state(self):
-        return VtmStateTranslated()
+        return StateToCommit()
+
+    def apply(self, state, person, comment=None, file=None):
+        self.save_action_db(state, person, comment, file)
+
+        new_state = self._new_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
+
+class ActionRC(ActionAbstract):
+    name = 'RC'
+    description = _('Reserve to submit')
 
-    def _apply_child(self):
+    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()
 
+class ActionIC(ActionAbstract):
+    name = 'IC'
+    description = _('Inform of submission')
+
+    def _new_state(self):
+        return 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
+
+class ActionTR(ActionAbstract):
+    name = 'TR'
+    description = _('Requiring review')
+
+    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
+
+class ActionDbBackup(models.Model):
+    state_db = models.ForeignKey(StateDb)
+    person = models.ForeignKey(Person)
+
+    name = models.SlugField(max_length=8)
+    created = models.DateField(auto_now_add=True, editable=False)
+    comment = models.TextField(blank=True, null=True)
+    file = models.FileField(upload_to=settings.UPLOAD_BACKUP_DIR, blank=True, null=True)
+    sequence = models.IntegerField(blank=True, null=True)
 
-class VtmActionRP(VtmAction):
     class Meta:
-        db_table = 'vtm_action_rp'
-        
-    def __init__(self, *args, **kwargs):
-        models.Model.__init__(self, *args, **kwargs)
-        self.code = 'RP'
-        self.name = 'Reserve for proofreading'
+        db_table = 'action_backup'
+
+class ActionBA(ActionAbstract):
+    name = 'BA'
+    description = _('Backup the actions')
 
     def _new_state(self):
-        return VtmStateProofreading()
+        return 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).all()
+
+        sequence = None
+        for action_db in actions_db:
+            action_db_backup = ActionDbBackup(
+                state_db=action_db.state_db,
+                person=action_db.person,
+                name=action_db.name,
+                created=action_db.created,
+                comment=action_db.comment,
+                file=action_db.file)
+            action_db_backup.save()
+
+            if sequence == None:
+                sequence = action_db_backup.id
+                action_db_backup.sequence = sequence
+                action_db_backup.save()
+
+            if action_db.file:
+                # Move the file to UPLOAD_BACKUP_DIR
+                # FIXME Hack
+                default_storage.save(
+                    settings.UPLOAD_BACKUP_DIR + '/' + action_db.file.name[len(settings.UPLOAD_DIR):],
+                    action_db.file)
+                default_storage.delete(action_db.file.path)
+
+            action_db.delete()            
 
-    def _apply_child(self, branch, domain, language, person):
         return self._new_state()
 
-    
+class ActionUNDO(ActionAbstract):
+    name = 'UNDO'
+    description = _('Undo the last state change')
+
+    def apply(self, state, person, comment=None, file=None):
+        self.save_action_db(state, person, comment, file)
+
+        try:
+            # Exclude himself
+            action_db = ActionDb.objects.filter(state_db__id=state._state_db.id).exclude(
+                name__in=['WC', 'UNDO']).order_by('-created')[1]
+            action = action_db.get_action()
+            return action._new_state()
+        except:
+            return StateNone()

Modified: trunk/vertimus/tests/__init__.py
==============================================================================
--- trunk/vertimus/tests/__init__.py	(original)
+++ trunk/vertimus/tests/__init__.py	Wed Dec 24 00:59:37 2008
@@ -19,22 +19,54 @@
 # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 from django.test import TestCase
+from django.core.files.storage import default_storage
+from django.core.files.base import ContentFile
+from django.conf import settings
+
 from people.models import Person
-from teams.models import Team
+from teams.models import Team, Role
 from languages.models import Language
 from stats.models import Module, Branch, Release, Category, Domain
 from vertimus.models import *
 
-class VtmActionTests(TestCase):
+class VertimusTests(TestCase):
 
     def setUp(self):
-        self.p = Person(first_name='John', last_name='Lennon',
-            email='jlennon beatles com')
-        self.p.save()
+        self.pn = Person(first_name='John', last_name='Nothing',
+            email='jn devnull com', username= 'jn')
+        self.pn.save()
+        
+        self.pt = Person(first_name='John', last_name='Translator',
+            email='jt tf1 com', username= 'jt')
+        self.pt.save()
+
+        self.pr = Person(first_name='John', last_name='Reviewer',
+            email='jr csa com', username= 'jr')
+        self.pr.save()
+
+        self.pc = Person(first_name='John', last_name='Committer',
+            email='jc alinsudesonpleingre fr', username= 'jc')
+        self.pc.save()
+
+        self.pcoo = Person(first_name='John', last_name='Coordinator',
+            email='jcoo imthebigboss fr', username= 'jcoo')
+        self.pcoo.save()
 
-        self.t = Team(name='fr', description='French', coordinator=self.p)
+        self.t = Team(name='fr', description='French')
         self.t.save()
 
+        self.role = Role(team=self.t, person=self.pt)
+        self.role.save()
+
+        self.role = Role(team=self.t, person=self.pr, role='reviewer')
+        self.role.save()
+
+        self.role = Role(team=self.t, person=self.pc, role='committer')
+        self.role.save()
+
+        self.role = Role(team=self.t, person=self.pcoo, role='coordinator')
+        self.role.save()
+
         self.l = Language(name='french', locale='fr', team=self.t)
         self.l.save()
 
@@ -46,7 +78,8 @@
         self.m.save()
 
         self.b = Branch(name='gnome-2-24', module=self.m) 
-        self.b.save()
+        # Block the update of Statistics by the thread
+        self.b.save(update_statistics=False)
 
         self.r = Release(name='gnome-2-24', status='official',
             description='GNOME 2.24 (stable)',
@@ -68,44 +101,277 @@
         self.b.delete()
         self.m.delete()
         self.l.delete()
+        for role in Role.objects.all():
+            role.delete()
         self.t.delete()
-        self.p.delete()
+        self.pn.delete()
+        self.pt.delete()
+        self.pr.delete()
+        self.pc.delete()
+        self.pcoo.delete()
+
+    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))
+
+        action_names = [a.name for a in state.get_available_actions(self.pn)]
+        self.assertEqual(action_names, ['WC'])
+
+        for p in (self.pt, self.pr, self.pc, self.pcoo):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['RT', 'WC'])
+        
+    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))
+
+        for p in (self.pn, self.pr, self.pc, self.pcoo):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['WC'])
+            
+        # Same person
+        action_names = [a.name for a in state.get_available_actions(self.pt)]
+        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))
+        
+        action_names = [a.name for a in state.get_available_actions(self.pn)]
+        self.assertEqual(action_names, ['WC'])
 
-    def test_action_wc(self):
-        a = VtmActionWC()
-        self.assertEquals(a.code, 'WC')
-        s_next = a.apply(self.b, self.d, self.l, self.p, 'Hi!')
-        self.assertEquals(s_next, None)
+        action_names = [a.name for a in state.get_available_actions(self.pt)]
+        self.assertEqual(action_names, ['RT', 'TR', 'WC'])
+
+        for p in (self.pr, self.pc, self.pcoo):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['RP', 'RT', 'TR', 'WC'])
+
+    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))
+        
+        for p in (self.pn, self.pt, self.pc, self.pcoo):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['WC'])
+
+        # Same person and reviewer
+        action_names = [a.name for a in state.get_available_actions(self.pr)]
+        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))
+
+        for p in (self.pn, self.pt):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['WC'])
+
+        for p in (self.pr, self.pc, self.pcoo):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['TC', 'TR', 'WC'])
+
+    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))
+
+        action_names = [a.name for a in state.get_available_actions(self.pn)]
+        self.assertEqual(action_names, ['WC'])
+
+        for p in (self.pt, self.pr, self.pc, self.pcoo):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['RT', 'WC'])
+
+    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))
+
+        for p in (self.pn, self.pt, self.pr):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['WC'])
+
+        for p in (self.pc, self.pcoo):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['RC', 'TR', 'WC'])
+
+    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))
+
+        for p in (self.pn, self.pt, self.pr, self.pcoo):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['WC'])
+
+        action_names = [a.name for a in state.get_available_actions(self.pc)]
+        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))
+
+        for p in (self.pn, self.pt, self.pr):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['WC'])
+
+        for p in (self.pc, self.pcoo):
+            action_names = [a.name for a in state.get_available_actions(p)]
+            self.assertEqual(action_names, ['BA', 'WC'])
+
+    def test_action_wc(self):
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='None').get_state()
+        state.save()
+
+        action = ActionAbstract.new_by_name('WC')
+        new_state = state.apply_action(action, self.pt, "Hi!", None)
+        new_state.save()
+
     def test_action_rt(self):
-        a = VtmActionRT()
-        self.assertEquals(a.code, 'RT')
-        s_next = a.apply(self.b, self.d, self.l, self.p, "Let's go!")
-        self.assertEquals(s_next.get_code(), 'Translating')
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='None').get_state()
+        state.save()
+
+        action = ActionAbstract.new_by_name('RT')
+        new_state = state.apply_action(action, self.pt, "Reserved!", None)
+        new_state.save()
 
     def test_action_ut(self):
-        # Only the person who have reserved the translation is able to
-        # upload the new one
-        a = VtmActionRT()
-        s_current = a.apply(self.b, self.d, self.l, self.p, "Let's go!")
-        a = VtmActionUT()
-        self.assertEquals(a.code, 'UT')
-        s_next = s_current.apply_action(a, self.b, self.d, self.l, self.p, 'Just updated', 'fr.po')
-        self.assertEquals(s_next.get_code(), 'Translated')
-
-    def test_state_committed_action_wc(self):
-        s_current = VtmStateCommitted()
-        a_wc = None
-        # Writing a comment is always a possible action
-        for action in s_current.get_actions(self.b, self.d, self.l, self.p):
-            if isinstance(action, VtmActionWC):
-                a_wc = action
-                break
-        self.assertNotEquals(a_wc, None)
-        s_next = s_current.apply_action(a_wc, self.b, self.d, self.l, self.p, 'My comment')
-        self.assertEquals(s_current, s_next)
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Translating', person=self.pt).get_state()
+        state.save()
+
+        action = ActionAbstract.new_by_name('UT')
+        new_state = state.apply_action(action, self.pt, "Done by translator.", 'myfile.po')
+        new_state.save()
+
+    def test_action_rp(self):
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Translated').get_state()
+        state.save()
+
+        action = ActionAbstract.new_by_name('RP')
+        new_state = state.apply_action(action, self.pr, "Reserved by a reviewer!")
+        new_state.save()
+
+    def test_action_up(self):
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Proofreading', person=self.pr).get_state()
+        state.save()
+
+        action = ActionAbstract.new_by_name('UP')
+        new_state = state.apply_action(action, self.pr, "Done.", 'myfile.po')
+        new_state.save()
+
+    def test_action_tc(self):
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Proofread').get_state()
+        state.save()
 
+        action = ActionAbstract.new_by_name('TC')
+        new_state = state.apply_action(action, self.pr, "Ready!")
+        new_state.save()
+
+    def test_action_rc(self):
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='ToCommit').get_state()
+        state.save()
+
+        action = ActionAbstract.new_by_name('RC')
+        new_state = state.apply_action(action, self.pc, "This work is mine!")
+        new_state.save()
+
+    def test_action_ic(self):
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Committing', person=self.pc).get_state()
+        state.save()
+
+        action = ActionAbstract.new_by_name('IC')
+        new_state = state.apply_action(action, self.pc, "Committed.")
+        new_state.save()
+
+    def test_action_tr(self):
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Translated').get_state()
+        state.save()
+
+        action = ActionAbstract.new_by_name('TR')
+        new_state = state.apply_action(action, self.pc, "Bad work :-/")
+        new_state.save()
+
+    def test_action_ba(self):
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='Proofreading', person=self.pr).get_state()
+        state.save()
+
+        # Create a new file
+        filename = 'mytestfile.po'
+        path = default_storage.save(settings.UPLOAD_DIR + filename, ContentFile('test content'))
+
+        action = ActionAbstract.new_by_name('UP')
+        state = state.apply_action(action, self.pr, "Done.", path)
+        state.save()
+
+        action = ActionAbstract.new_by_name('TC')
+        state = state.apply_action(action, self.pc, "To commit.")
+        state.save()
+
+        action = ActionAbstract.new_by_name('RC')
+        state = state.apply_action(action, self.pc, "Reserved commit.")
+        state.save()
+
+        action = ActionAbstract.new_by_name('IC')
+        state = state.apply_action(action, self.pc, "Committed.")
+        state.save()
+
+        action = ActionAbstract.new_by_name('BA')
+        state = state.apply_action(action, self.pc, comment="I don't want to disappear")
+        state.save()
+
+        self.assert_(not default_storage.exists(path))
+        self.assert_(default_storage.exists(settings.UPLOAD_BACKUP_DIR + '/' + filename))
+
+        # Remove test file
+        default_storage.delete(settings.UPLOAD_BACKUP_DIR + '/' + filename)
+
+    def test_action_undo(self):
+        state = StateDb(branch=self.b, domain=self.d, language=self.l, name='None').get_state()
+        state.save()
+
+        action = ActionAbstract.new_by_name('RT')
+        state = state.apply_action(action, self.pt, "Reserved!")
+        state.save()
+
+        action = ActionAbstract.new_by_name('UNDO')
+        state = state.apply_action(action, self.pt, "Ooops! I don't want to do that. Sorry.")
+        state.save()
+
+        self.assertEqual(state.name, 'None')
+
+        action = ActionAbstract.new_by_name('RT')
+        state = state.apply_action(action, self.pt, "Translating")
+        state.save()
         
+        action = ActionAbstract.new_by_name('UT')
+        state = state.apply_action(action, self.pt, "Translated")
+        state.save()
+
+        action = ActionAbstract.new_by_name('RT')
+        state = state.apply_action(action, self.pt, "Reserved!")
+        state.save()
+
+        action = ActionAbstract.new_by_name('UNDO')
+        state = state.apply_action(action, self.pt, "Ooops! I don't want to do that. Sorry.")
+        state.save()
 
+        self.assertEqual(state.name, 'Translated')
         
+
         

Added: trunk/vertimus/urls.py
==============================================================================
--- (empty file)
+++ trunk/vertimus/urls.py	Wed Dec 24 00:59:37 2008
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('vertimus.views',
+    url(r'^(?P<stats_id>\d+)', 'vertimus_by_stats_id', name='vertimus-stats-id-view'),
+    url(r'^(?P<branch_id>\d+)/(?P<domain_id>\d+)/(?P<language_id>\d+)', 'vertimus_by_ids', name='vertimus-ids-view'),
+    url(r'^(?P<module_name>[\w\-\.]+)/(?P<branch_name>[\w\-]+)/(?P<domain_name>[\w\-]+)/(?P<locale_name>[\w\- ]+)', 'vertimus_by_names', name='vertimus-names-view')
+)

Modified: trunk/vertimus/views.py
==============================================================================
--- trunk/vertimus/views.py	(original)
+++ trunk/vertimus/views.py	Wed Dec 24 00:59:37 2008
@@ -1 +1,134 @@
-# Create your views here.
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2008 StÃphane Raimbault <stephane raimbault 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
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect, Http404
+from django.template import RequestContext
+from django.utils.translation import ugettext_lazy as _
+from django import forms
+from django.core import urlresolvers
+from django.conf import settings
+from django.core.files.storage import default_storage
+
+from people.models import Person
+from stats.models import Statistics, Module, Branch, Domain, Language
+from vertimus.models import StateDb, ActionDb, ActionAbstract
+
+class ActionForm(forms.Form):
+    action = forms.ChoiceField(label=_("Action"),
+#        help_text="Choose an action you want to apply",
+        choices=())
+    comment = forms.CharField(label=_("Comment"),
+#        help_text="Leave a comment to explain your action",
+        max_length=1000,
+        required=False,
+        widget=forms.Textarea)
+    file = forms.FileField(label=_("File"), required=False)
+
+    def __init__(self, available_actions, *args, **kwargs):
+        super(ActionForm, self).__init__(*args, **kwargs)
+        self.fields['action'].choices = available_actions
+
+def vertimus_by_stats_id(request, stats_id):
+    """Access to Vertimus view by a Statistics ID"""
+    stats = get_object_or_404(Statistics, pk=stats_id)
+    return vertimus(request, stats.branch, stats.domain, stats.language, stats)
+
+def vertimus_by_ids(request, branch_id, domain_id, language_id):
+    """Access to Vertimus view by Branch, Domain and language IDs"""
+    branch = get_object_or_404(Branch, pk=branch_id)
+    domain = get_object_or_404(Domain, pk=domain_id)
+    language = get_object_or_404(Language, pk=language_id)
+    return vertimus(request, branch, domain, language)
+
+def vertimus_by_names(request, module_name, branch_name, domain_name, locale_name):
+    """Access to Vertimus view by Branch, Domain and language names"""
+    module = get_object_or_404(Module, name=module_name)
+    branch = get_object_or_404(Branch, name=branch_name, module__id=module.id)
+    domain = get_object_or_404(Domain, name=domain_name, module__id=module.id)
+    language = get_object_or_404(Language, locale=locale_name)
+
+    return vertimus(request, branch, domain, language)
+
+def handle_uploaded_file(f, branch, domain, language):
+    filename = "%s-%s-%s-%s.po" % (branch.module.name, branch.name, domain.name, language.locale)
+    path = default_storage.save(settings.UPLOAD_DIR + '/' + filename, f)
+    
+    # Keep only the new filename (duplicate files)
+    return path
+
+def vertimus(request, branch, domain, language, stats=None):
+    """The Vertimus view and form management"""
+    if not stats:
+        stats = get_object_or_404(Statistics, branch=branch, domain=domain, language=language)
+
+    # Get the state of the translation
+    (state_db, created) = StateDb.objects.get_or_create(
+        branch=branch,
+        domain=domain,
+        language=language)
+
+    state = state_db.get_state()
+    action_history = ActionDb.get_action_history(state_db)
+
+    if request.user.is_authenticated():
+        # Only authenticated user can act on the translation
+        person = request.user.person
+        
+        available_actions = [(va.name, va.description) for va in state.get_available_actions(person)]
+        if request.method == 'POST':
+            action_form = ActionForm(available_actions, request.POST, request.FILES)
+
+            if action_form.is_valid():
+                # Process the data in form.cleaned_data
+                action = action_form.cleaned_data['action']
+                comment = action_form.cleaned_data['comment']
+                
+                if 'file' in request.FILES:
+                    file = handle_uploaded_file(request.FILES['file'], branch, domain, language)
+                else:
+                    file = None
+
+                action = ActionAbstract.new_by_name(action)
+                new_state = state.apply_action(action, person, comment, file)
+                new_state.save()
+
+                return HttpResponseRedirect(
+                    urlresolvers.reverse('vertimus-names-view', 
+                        args=(branch.module.name, branch.name, domain.name, language.locale)))
+        else:
+            action_form = ActionForm(available_actions)
+    else:
+        action_form = None
+
+    context = {
+        'pageSection': 'module',
+        'stats': stats,
+        'branch': branch,
+        'domain': domain,
+        'language': language,
+        'module': branch.module,
+        'state': state,
+        'action_history': action_history, 
+        'action_form': action_form
+    }
+    return render_to_response('vertimus/vertimus_detail.html', context,
+                              context_instance=RequestContext(request)) 



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