move xml check to celery,
[redakcja.git] / apps / dvcs / models.py
index 1668dee..bb79627 100644 (file)
@@ -1,22 +1,21 @@
 from datetime import datetime
 from datetime import datetime
+import os.path
 
 
+from django.contrib.auth.models import User
 from django.core.files.base import ContentFile
 from django.core.files.storage import FileSystemStorage
 from django.db import models
 from django.db.models.base import ModelBase
 from django.core.files.base import ContentFile
 from django.core.files.storage import FileSystemStorage
 from django.db import models
 from django.db.models.base import ModelBase
-from django.contrib.auth.models import User
 from django.utils.translation import ugettext_lazy as _
 from mercurial import mdiff, simplemerge
 
 from django.utils.translation import ugettext_lazy as _
 from mercurial import mdiff, simplemerge
 
-from dvcs.fields import GzipFileSystemStorage
-from dvcs.settings import REPO_PATH
+from django.conf import settings
+from dvcs.signals import post_commit, post_publishable
+from dvcs.storage import GzipFileSystemStorage
 
 
 class Tag(models.Model):
 
 
 class Tag(models.Model):
-    """
-        a tag (e.g. document stage) which can be applied to a change
-    """
-
+    """A tag (e.g. document stage) which can be applied to a Change."""
     name = models.CharField(_('name'), max_length=64)
     slug = models.SlugField(_('slug'), unique=True, max_length=64, 
             null=True, blank=True)
     name = models.CharField(_('name'), max_length=64)
     slug = models.SlugField(_('slug'), unique=True, max_length=64, 
             null=True, blank=True)
@@ -27,6 +26,8 @@ class Tag(models.Model):
     class Meta:
         abstract = True
         ordering = ['ordering']
     class Meta:
         abstract = True
         ordering = ['ordering']
+        verbose_name = _("tag")
+        verbose_name_plural = _("tags")
 
     def __unicode__(self):
         return self.name
 
     def __unicode__(self):
         return self.name
@@ -50,15 +51,13 @@ class Tag(models.Model):
             Returns None for the last stage.
         """
         try:
             Returns None for the last stage.
         """
         try:
-            return Tag.objects.filter(ordering__gt=self.ordering)[0]
+            return type(self).objects.filter(ordering__gt=self.ordering)[0]
         except IndexError:
             return None
 
 models.signals.pre_save.connect(Tag.listener_changed, sender=Tag)
 
 
         except IndexError:
             return None
 
 models.signals.pre_save.connect(Tag.listener_changed, sender=Tag)
 
 
-repo = GzipFileSystemStorage(location=REPO_PATH)
-
 def data_upload_to(instance, filename):
     return "%d/%d" % (instance.tree.pk, instance.pk)
 
 def data_upload_to(instance, filename):
     return "%d/%d" % (instance.tree.pk, instance.pk)
 
@@ -70,29 +69,38 @@ class Change(models.Model):
         
         Data file contains a gzipped text of the document.
     """
         
         Data file contains a gzipped text of the document.
     """
-    author = models.ForeignKey(User, null=True, blank=True)
-    author_name = models.CharField(max_length=128, null=True, blank=True)
-    author_email = models.CharField(max_length=128, null=True, blank=True)
-    data = models.FileField(upload_to=data_upload_to, storage=repo)
-    revision = models.IntegerField(db_index=True)
+    author = models.ForeignKey(User, null=True, blank=True, verbose_name=_('author'))
+    author_name = models.CharField(_('author name'), max_length=128,
+                        null=True, blank=True,
+                        help_text=_("Used if author is not set.")
+                        )
+    author_email = models.CharField(_('author email'), max_length=128,
+                        null=True, blank=True,
+                        help_text=_("Used if author is not set.")
+                        )
+    revision = models.IntegerField(_('revision'), db_index=True)
 
     parent = models.ForeignKey('self',
                         null=True, blank=True, default=None,
 
     parent = models.ForeignKey('self',
                         null=True, blank=True, default=None,
+                        verbose_name=_('parent'),
                         related_name="children")
 
     merge_parent = models.ForeignKey('self',
                         null=True, blank=True, default=None,
                         related_name="children")
 
     merge_parent = models.ForeignKey('self',
                         null=True, blank=True, default=None,
+                        verbose_name=_('merge parent'),
                         related_name="merge_children")
 
                         related_name="merge_children")
 
-    description = models.TextField(blank=True, default='')
+    description = models.TextField(_('description'), blank=True, default='')
     created_at = models.DateTimeField(editable=False, db_index=True, 
                         default=datetime.now)
     created_at = models.DateTimeField(editable=False, db_index=True, 
                         default=datetime.now)
-    publishable = models.BooleanField(default=False)
+    publishable = models.BooleanField(_('publishable'), default=False)
 
     class Meta:
         abstract = True
         ordering = ('created_at',)
         unique_together = ['tree', 'revision']
 
     class Meta:
         abstract = True
         ordering = ('created_at',)
         unique_together = ['tree', 'revision']
+        verbose_name = _("change")
+        verbose_name_plural = _("changes")
 
     def __unicode__(self):
         return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data)
 
     def __unicode__(self):
         return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data)
@@ -117,7 +125,7 @@ class Change(models.Model):
         if self.revision is None:
             tree_rev = self.tree.revision()
             if tree_rev is None:
         if self.revision is None:
             tree_rev = self.tree.revision()
             if tree_rev is None:
-                self.revision = 0
+                self.revision = 1
             else:
                 self.revision = tree_rev + 1
         return super(Change, self).save(*args, **kwargs)
             else:
                 self.revision = tree_rev + 1
         return super(Change, self).save(*args, **kwargs)
@@ -156,58 +164,79 @@ class Change(models.Model):
         """ commit this version of a doc as new head """
         self.tree.commit(text=self.materialize(), **kwargs)
 
         """ commit this version of a doc as new head """
         self.tree.commit(text=self.materialize(), **kwargs)
 
+    def set_publishable(self, publishable):
+        self.publishable = publishable
+        self.save()
+        post_publishable.send(sender=self, publishable=publishable)
+
 
 def create_tag_model(model):
     name = model.__name__ + 'Tag'
 
 def create_tag_model(model):
     name = model.__name__ + 'Tag'
+
+    class Meta(Tag.Meta):
+        app_label = model._meta.app_label
+
     attrs = {
         '__module__': model.__module__,
     attrs = {
         '__module__': model.__module__,
+        'Meta': Meta,
     }
     return type(name, (Tag,), attrs)
 
 
 def create_change_model(model):
     name = model.__name__ + 'Change'
     }
     return type(name, (Tag,), attrs)
 
 
 def create_change_model(model):
     name = model.__name__ + 'Change'
+    repo = GzipFileSystemStorage(location=model.REPO_PATH)
+
+    class Meta(Change.Meta):
+        app_label = model._meta.app_label
 
     attrs = {
         '__module__': model.__module__,
 
     attrs = {
         '__module__': model.__module__,
-        'tree': models.ForeignKey(model, related_name='change_set'),
-        'tags': models.ManyToManyField(model.tag_model, related_name='change_set'),
+        'tree': models.ForeignKey(model, related_name='change_set', verbose_name=_('document')),
+        'tags': models.ManyToManyField(model.tag_model, verbose_name=_('tags'), related_name='change_set'),
+        'data': models.FileField(_('data'), upload_to=data_upload_to, storage=repo),
+        'Meta': Meta,
     }
     return type(name, (Change,), attrs)
 
 
     }
     return type(name, (Change,), attrs)
 
 
-
 class DocumentMeta(ModelBase):
     "Metaclass for Document models."
     def __new__(cls, name, bases, attrs):
 class DocumentMeta(ModelBase):
     "Metaclass for Document models."
     def __new__(cls, name, bases, attrs):
+
         model = super(DocumentMeta, cls).__new__(cls, name, bases, attrs)
         if not model._meta.abstract:
             # create a real Tag object and `stage' fk
             model.tag_model = create_tag_model(model)
         model = super(DocumentMeta, cls).__new__(cls, name, bases, attrs)
         if not model._meta.abstract:
             # create a real Tag object and `stage' fk
             model.tag_model = create_tag_model(model)
-            models.ForeignKey(model.tag_model, 
+            models.ForeignKey(model.tag_model, verbose_name=_('stage'),
                 null=True, blank=True).contribute_to_class(model, 'stage')
 
             # create real Change model and `head' fk
             model.change_model = create_change_model(model)
                 null=True, blank=True).contribute_to_class(model, 'stage')
 
             # create real Change model and `head' fk
             model.change_model = create_change_model(model)
+
             models.ForeignKey(model.change_model,
                     null=True, blank=True, default=None,
             models.ForeignKey(model.change_model,
                     null=True, blank=True, default=None,
+                    verbose_name=_('head'), 
                     help_text=_("This document's current head."),
                     editable=False).contribute_to_class(model, 'head')
 
                     help_text=_("This document's current head."),
                     editable=False).contribute_to_class(model, 'head')
 
-        return model
+            models.ForeignKey(User, null=True, blank=True, editable=False,
+                verbose_name=_('creator'), related_name="created_%s" % name.lower()
+                ).contribute_to_class(model, 'creator')
 
 
+        return model
 
 
 class Document(models.Model):
 
 
 class Document(models.Model):
-    """
-        File in repository.        
-    """
+    """File in repository. Subclass it to use version control in your app."""
+
     __metaclass__ = DocumentMeta
 
     __metaclass__ = DocumentMeta
 
-    creator = models.ForeignKey(User, null=True, blank=True, editable=False,
-                related_name="created_documents")
+    # default repository path
+    REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs')
 
 
-    user = models.ForeignKey(User, null=True, blank=True)
+    user = models.ForeignKey(User, null=True, blank=True,
+        verbose_name=_('user'), help_text=_('Work assignment.'))
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
@@ -215,13 +244,6 @@ class Document(models.Model):
     def __unicode__(self):
         return u"{0}, HEAD: {1}".format(self.id, self.head_id)
 
     def __unicode__(self):
         return u"{0}, HEAD: {1}".format(self.id, self.head_id)
 
-    @models.permalink
-    def get_absolute_url(self):
-        return ('dvcs.views.document_data', (), {
-                        'document_id': self.id,
-                        'version': self.head_id,
-        })
-
     def materialize(self, change=None):
         if self.head is None:
             return u''
     def materialize(self, change=None):
         if self.head is None:
             return u''
@@ -231,7 +253,23 @@ class Document(models.Model):
             change = self.change_set.get(pk=change)
         return change.materialize()
 
             change = self.change_set.get(pk=change)
         return change.materialize()
 
-    def commit(self, text, **kwargs):
+    def commit(self, text, author=None, author_name=None, author_email=None,
+            publishable=False, **kwargs):
+        """Commits a new revision.
+
+        This will automatically merge the commit into the main branch,
+        if parent is not document's head.
+
+        :param unicode text: new version of the document
+        :param parent: parent revision (head, if not specified)
+        :type parent: Change or None
+        :param User author: the commiter
+        :param unicode author_name: commiter name (if ``author`` not specified)
+        :param unicode author_email: commiter e-mail (if ``author`` not specified)
+        :param Tag[] tags: list of tags to apply to the new commit
+        :param bool publishable: set new commit as ready to publish
+        :returns: new head
+        """
         if 'parent' not in kwargs:
             parent = self.head
         else:
         if 'parent' not in kwargs:
             parent = self.head
         else:
@@ -239,9 +277,6 @@ class Document(models.Model):
             if parent is not None and not isinstance(parent, Change):
                 parent = self.change_set.objects.get(pk=kwargs['parent'])
 
             if parent is not None and not isinstance(parent, Change):
                 parent = self.change_set.objects.get(pk=kwargs['parent'])
 
-        author = kwargs.get('author', None)
-        author_name = kwargs.get('author_name', None)
-        author_email = kwargs.get('author_email', None)
         tags = kwargs.get('tags', [])
         if tags:
             # set stage to next tag after the commited one
         tags = kwargs.get('tags', [])
         if tags:
             # set stage to next tag after the commited one
@@ -251,6 +286,7 @@ class Document(models.Model):
                     author_name=author_name,
                     author_email=author_email,
                     description=kwargs.get('description', ''),
                     author_name=author_name,
                     author_email=author_email,
                     description=kwargs.get('description', ''),
+                    publishable=publishable,
                     parent=parent)
 
         change.tags = tags
                     parent=parent)
 
         change.tags = tags
@@ -265,6 +301,9 @@ class Document(models.Model):
         else:
             self.head = change
         self.save()
         else:
             self.head = change
         self.save()
+
+        post_commit.send(sender=self.head)
+
         return self.head
 
     def history(self):
         return self.head
 
     def history(self):
@@ -280,8 +319,8 @@ class Document(models.Model):
         return self.change_set.get(revision=rev)
 
     def publishable(self):
         return self.change_set.get(revision=rev)
 
     def publishable(self):
-        changes = self.change_set.filter(publishable=True).order_by('-created_at')[:1]
-        if changes.count():
-            return changes[0]
+        changes = self.change_set.filter(publishable=True)
+        if changes.exists():
+            return changes.order_by('-created_at')[0]
         else:
             return None
         else:
             return None