X-Git-Url: https://git.mdrn.pl/redakcja.git/blobdiff_plain/5913c54d19b8f6775633176032161d49f9b2f1aa..aac3fdbb766e03b97da2fa17bc2cd3e8378bdefb:/src/dvcs/models.py diff --git a/src/dvcs/models.py b/src/dvcs/models.py index 24bdeb3a..5d465caf 100644 --- a/src/dvcs/models.py +++ b/src/dvcs/models.py @@ -1,21 +1,21 @@ -# -*- coding: utf-8 -*- -# # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from datetime import datetime import os.path +from zlib import compress, decompress 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, transaction from django.db.models.base import ModelBase -from django.utils.translation import string_concat, ugettext_lazy as _ -from mercurial import simplemerge +from django.utils.text import format_lazy +from django.utils.translation import gettext_lazy as _ +import merge3 from django.conf import settings from dvcs.signals import post_commit, post_publishable -from dvcs.storage import GzipFileSystemStorage class Tag(models.Model): @@ -30,7 +30,7 @@ class Tag(models.Model): abstract = True ordering = ['ordering'] - def __unicode__(self): + def __str__(self): return self.name @classmethod @@ -71,7 +71,7 @@ class Change(models.Model): Data file contains a gzipped text of the document. """ - author = models.ForeignKey(User, null=True, blank=True, verbose_name=_('author')) + author = models.ForeignKey(User, models.SET_NULL, 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( @@ -79,10 +79,10 @@ class Change(models.Model): revision = models.IntegerField(_('revision'), db_index=True) parent = models.ForeignKey( - 'self', null=True, blank=True, default=None, verbose_name=_('parent'), related_name="children") + 'self', models.SET_NULL, null=True, blank=True, default=None, verbose_name=_('parent'), related_name="children") merge_parent = models.ForeignKey( - 'self', null=True, blank=True, default=None, verbose_name=_('merge parent'), related_name="merge_children") + 'self', models.SET_NULL, null=True, blank=True, default=None, verbose_name=_('merge parent'), related_name="merge_children") description = models.TextField(_('description'), blank=True, default='') created_at = models.DateTimeField(editable=False, db_index=True, default=datetime.now) @@ -93,8 +93,8 @@ class Change(models.Model): ordering = ('created_at',) unique_together = ['tree', 'revision'] - def __unicode__(self): - return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data) + def __str__(self): + return "rev. {} @ {}".format(self.revision, self.created_at) def author_str(self): if self.author: @@ -120,11 +120,17 @@ class Change(models.Model): self.revision = tree_rev + 1 return super(Change, self).save(*args, **kwargs) + def save_text(self, text, **kwargs): + self.data.save( + '', + ContentFile(compress(text.encode('utf-8'))), + **kwargs + ) + def materialize(self): - f = self.data.storage.open(self.data) - text = f.read() - f.close() - return unicode(text, 'utf-8') + with self.data.open('rb') as f: + content = f.read() + return decompress(content).decode('utf-8') def merge_with(self, other, author=None, author_name=None, author_email=None, @@ -135,11 +141,11 @@ class Change(models.Model): # immediate child - fast forward return other - local = self.materialize().encode('utf-8') - base = other.parent.materialize().encode('utf-8') - remote = other.materialize().encode('utf-8') + local = self.materialize().splitlines(True) + base = other.parent.materialize().splitlines(True) + remote = other.materialize().splitlines(True) - merge = simplemerge.Merge3Text(base, local, remote) + merge = merge3.Merge3(base, local, remote) result = ''.join(merge.merge_lines()) merge_node = self.children.create( merge_parent=other, tree=self.tree, @@ -147,7 +153,7 @@ class Change(models.Model): author_name=author_name, author_email=author_email, description=description) - merge_node.data.save('', ContentFile(result)) + merge_node.save_text(result) return merge_node def revert(self, **kwargs): @@ -165,10 +171,10 @@ def create_tag_model(model): class Meta(Tag.Meta): app_label = model._meta.app_label - verbose_name = string_concat( - _("tag"), " ", _("for:"), " ", model._meta.verbose_name) - verbose_name_plural = string_concat( - _("tags"), " ", _("for:"), " ", model._meta.verbose_name) + verbose_name = format_lazy( + '{} {} {}', _('tag'), _('for:'), model._meta.verbose_name) + verbose_name_plural = format_lazy( + '{} {} {}', _("tags"), _("for:"), model._meta.verbose_name) attrs = { '__module__': model.__module__, @@ -179,18 +185,18 @@ def create_tag_model(model): def create_change_model(model): name = model.__name__ + 'Change' - repo = GzipFileSystemStorage(location=model.REPO_PATH) + repo = FileSystemStorage(location=model.REPO_PATH) class Meta(Change.Meta): app_label = model._meta.app_label - verbose_name = string_concat( - _("change"), " ", _("for:"), " ", model._meta.verbose_name) - verbose_name_plural = string_concat( - _("changes"), " ", _("for:"), " ", model._meta.verbose_name) + verbose_name = format_lazy( + '{} {} {}', _("change"), _("for:"), model._meta.verbose_name) + verbose_name_plural = format_lazy( + '{} {} {}', _("changes"), _("for:"), model._meta.verbose_name) attrs = { '__module__': model.__module__, - 'tree': models.ForeignKey(model, related_name='change_set', verbose_name=_('document')), + 'tree': models.ForeignKey(model, models.CASCADE, 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, @@ -206,39 +212,37 @@ class DocumentMeta(ModelBase): 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, verbose_name=_('stage'), + models.ForeignKey(model.tag_model, models.CASCADE, 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) models.ForeignKey( - model.change_model, null=True, blank=True, default=None, + model.change_model, models.SET_NULL, null=True, blank=True, default=None, verbose_name=_('head'), help_text=_("This document's current head."), editable=False).contribute_to_class(model, 'head') models.ForeignKey( - User, null=True, blank=True, editable=False, + User, models.SET_NULL, 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, metaclass=DocumentMeta): """File in repository. Subclass it to use version control in your app.""" - __metaclass__ = DocumentMeta - # default repository path REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs') - user = models.ForeignKey(User, null=True, blank=True, verbose_name=_('user'), help_text=_('Work assignment.')) + user = models.ForeignKey(User, models.SET_NULL, null=True, blank=True, verbose_name=_('user'), help_text=_('Work assignment.')) class Meta: abstract = True - def __unicode__(self): + def __str__(self): return u"{0}, HEAD: {1}".format(self.id, self.head_id) def materialize(self, change=None): @@ -256,12 +260,12 @@ class Document(models.Model): 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 str 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 str author_name: commiter name (if ``author`` not specified) + :param str 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 @@ -282,8 +286,8 @@ class Document(models.Model): author=author, author_name=author_name, author_email=author_email, description=kwargs.get('description', ''), publishable=publishable, parent=parent) - change.tags = tags - change.data.save('', ContentFile(text.encode('utf-8'))) + change.tags.set(tags) + change.save_text(text) change.save() if self.head: