--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'Change'
+ db.create_table('dvcs_change', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+ ('patch', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ('tree', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dvcs.Document'])),
+ ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['dvcs.Change'])),
+ ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['dvcs.Change'])),
+ ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
+ ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ))
+ db.send_create_signal('dvcs', ['Change'])
+
+ # Adding model 'Document'
+ db.create_table('dvcs_document', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('creator', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+ ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['dvcs.Change'], null=True, blank=True)),
+ ))
+ db.send_create_signal('dvcs', ['Document'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Change'
+ db.delete_table('dvcs_change')
+
+ # Deleting model 'Document'
+ db.delete_table('dvcs_document')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'dvcs.change': {
+ 'Meta': {'ordering': "('created_at',)", 'object_name': 'Change'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
+ 'patch': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'tree': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dvcs.Document']"})
+ },
+ 'dvcs.document': {
+ 'Meta': {'object_name': 'Document'},
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['dvcs.Change']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ }
+ }
+
+ complete_apps = ['dvcs']
return pickle.dumps(mdiff.textdiff(src, dst))
def materialize(self):
- changes = Change.objects.exclude(parent=None).filter(
+ # special care for merged nodes
+ if self.parent is None and self.merge_parent is not None:
+ return self.apply_to(self.merge_parent.materialize())
+
+ changes = Change.objects.filter(
+ ~models.Q(parent=None) | models.Q(merge_parent=None),
tree=self.tree,
created_at__lte=self.created_at).order_by('created_at')
text = u''
patch=patch, merge_parent=other, tree=self.tree,
author=author, description=description)
+ def revert(self, **kwargs):
+ """ commit this version of a doc as new head """
+ self.tree.commit(text=self.materialize(), **kwargs)
+
class Document(models.Model):
"""
null=True, blank=True, default=None,
help_text=_("This document's current head."))
+ @classmethod
+ def create(cls, text='', *args, **kwargs):
+ instance = cls(*args, **kwargs)
+ instance.save()
+ head = instance.head
+ head.patch = Change.make_patch('', text)
+ head.save()
+ return instance
+
def __unicode__(self):
return u"{0}, HEAD: {1}".format(self.id, self.head_id)
'version': self.head_id,
})
- def materialize(self, version=None):
+ def materialize(self, change=None):
if self.head is None:
return u''
- if version is None:
- version = self.head
- elif not isinstance(version, Change):
- version = self.change_set.get(pk=version)
- return version.materialize()
+ if change is None:
+ change = self.head
+ elif not isinstance(change, Change):
+ change = self.change_set.get(pk=change)
+ return change.materialize()
def commit(self, **kwargs):
if 'parent' not in kwargs:
if 'patch' not in kwargs:
if 'text' not in kwargs:
raise ValueError("You must provide either patch or target document.")
- patch = Change.make_patch(self.materialize(version=parent), kwargs['text'])
+ patch = Change.make_patch(self.materialize(change=parent), kwargs['text'])
else:
if 'text' in kwargs:
raise ValueError("You can provide only text or patch - not both")
self.head = old_head.merge_with(change, author=kwargs['author'])
else:
self.head = parent.make_child(patch, kwargs['author'], kwargs.get('description', ''))
+
self.save()
return self.head
def history(self):
- return self.changes.all()
+ return self.change_set.all()
+
+ def revision(self):
+ return self.change_set.all().count()
+
+ def at_revision(self, rev):
+ if rev:
+ return self.change_set.all()[rev-1]
+ else:
+ return self.head
@staticmethod
def listener_initial_commit(sender, instance, created, **kwargs):
if created:
instance.head = Change.objects.create(
author=instance.creator,
- patch=pickle.dumps(mdiff.textdiff('', '')),
+ patch=Change.make_patch('', ''),
tree=instance)
instance.save()
from wiki import models
+admin.site.register(models.Book)
admin.site.register(models.Theme)
#
from django import forms
from wiki.constants import DOCUMENT_TAGS, DOCUMENT_STAGES
+from wiki.models import Book
from django.utils.translation import ugettext_lazy as _
revision = forms.IntegerField(widget=forms.HiddenInput)
-class DocumentCreateForm(forms.Form):
+class DocumentCreateForm(forms.ModelForm):
"""
Form used for creating new documents.
"""
- title = forms.CharField()
- id = forms.RegexField(regex=ur"^[-\wąćęłńóśźżĄĆĘŁŃÓŚŹŻ]+$")
file = forms.FileField(required=False)
text = forms.CharField(required=False, widget=forms.Textarea)
+ class Meta:
+ model = Book
+ exclude = ['gallery']
+
def clean(self):
+ super(DocumentCreateForm, self).clean()
file = self.cleaned_data['file']
if file is not None:
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'Book'
+ db.create_table('wiki_book', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=255, db_index=True)),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
+ ('doc', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dvcs.Document'])),
+ ('gallery', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
+ ))
+ db.send_create_signal('wiki', ['Book'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Book'
+ db.delete_table('wiki_book')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'dvcs.change': {
+ 'Meta': {'ordering': "('created_at',)", 'object_name': 'Change'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
+ 'patch': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'tree': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dvcs.Document']"})
+ },
+ 'dvcs.document': {
+ 'Meta': {'object_name': 'Document'},
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['dvcs.Change']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'wiki.book': {
+ 'Meta': {'ordering': "['title']", 'object_name': 'Book'},
+ 'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dvcs.Document']"}),
+ 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
+ },
+ 'wiki.theme': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Theme'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['wiki']
logger = logging.getLogger("fnp.wiki")
-class Document(models.Model):
+class Book(models.Model):
""" A document edited on the wiki """
- slug = models.CharField(_('slug'))
- title = models.CharField(_('displayed title'), blank=True)
- data = models.ForeignKey(dvcs_models.Document)
- gallery = models.CharField(_('scan gallery name'), blank=True)
+ slug = models.SlugField(_('slug'), max_length=255, unique=True)
+ title = models.CharField(_('displayed title'), max_length=255, blank=True)
+ doc = models.ForeignKey(dvcs_models.Document, editable=False)
+ gallery = models.CharField(_('scan gallery name'), max_length=255, blank=True)
+ class Meta:
+ ordering = ['title']
+ verbose_name = _('book')
+ verbose_name_plural = _('books')
+
+ def __unicode__(self):
+ return self.title
+
+ @classmethod
+ def create(cls, creator=None, text=u'', *args, **kwargs):
+ instance = cls(*args, **kwargs)
+ instance.doc = dvcs_models.Document.create(creator=creator, text=text)
+ instance.save()
+ return instance
+
+ @staticmethod
+ def listener_create(sender, instance, created, **kwargs):
+ if created and instance.doc is None:
+ instance.doc = dvcs_models.Document.objects.create()
+ instance.save()
+
+models.signals.post_save.connect(Book.listener_create, sender=Book)
{% extends "base.html" %}
{% load toolbar_tags i18n %}
-{% block title %}{{ document.name }} - {{ block.super }}{% endblock %}
+{% block title %}{{ book.title }} - {{ block.super }}{% endblock %}
{% block extrahead %}
{% load compressed %}
{% compressed_css 'detail' %}
{% block maincontent %}
<div id="document-meta"
- data-document-name="{{ document.name }}" style="display:none">
+ data-document-name="{{ book.slug }}" style="display:none">
- {% for k, v in document_meta.items %}
- <span data-key="{{ k }}">{{ v }}</span>
- {% endfor %}
-
- {% for k, v in document_info.items %}
- <span data-key="{{ k }}">{{ v }}</span>
- {% endfor %}
+ <span data-key="gallery">{{ book.gallery }}</span>
+ <span data-key="revision">{{ revision }}</span>
{% block meta-extra %} {% endblock %}
</div>
{% extends "wiki/base.html" %}
{% load i18n %}
-{% load wiki %}
{% block extrabody %}
{{ block.super }}
</tr>
</thead>
<tbody>
- {% for doc in docs %}
+ {% for book in books %}
<tr>
- <td colspan="3"><a target="_blank" data-id="{{doc}}"
- href="{% url wiki_editor doc %}">{{ doc|wiki_title }}</a></td>
+ <td colspan="3"><a target="_blank" data-id="{{ book.slug }}"
+ href="{% url wiki_editor book.slug %}">{{ book.title }}</a></td>
<!-- placeholder </td> -->
</tr>
{% endfor %}
<div id="last-edited-list">
<h2>{% trans "Your last edited documents" %}</h2>
<ol>
- {% for name, date in last_docs %}
- <li><a href="{% url wiki_editor name %}"
- target="_blank">{{ name|wiki_title }}</a><br/><span class="date">({{ date|date:"H:i:s, d/m/Y" }})</span></li>
+ {% for slug, item in last_books %}
+ <li><a href="{% url wiki_editor slug %}"
+ target="_blank">{{ item.title }}</a><br/><span class="date">({{ item.time|date:"H:i:s, d/m/Y" }})</span></li>
{% endfor %}
</ol>
</div>
{% extends "wiki/base.html" %}
{% load i18n %}
-{% load wiki %}
{% block leftcolumn %}
<h3>{% trans "Offending files" %}</h3>
<ul id='error-list'>
{% for filename, title, error in error_list %}
- <li>{{title|wiki_title}} (<code>{{ filename }}</code>): {{ error }}</li>
+ <li>{{ title }} (<code>{{ filename }}</code>): {{ error }}</li>
{% endfor %}
</ul>
{% if ok_list %}
<h3>{% trans "Correct files" %}</h3>
<ul>
- {% for filename, title in ok_list %}
- <li>{{title|wiki_title}} (<code>{{ filename }}</code>)</li>
+ {% for filename, slug, title in ok_list %}
+ <li>{{ title }} (<code>{{ filename }}</code>)</li>
{% endfor %}
</ul>
{% endif %}
<p class='success'>{% trans "Files have been successfully uploaded to the repository." %}</p>
<h3>{% trans "Uploaded files" %}</h3>
<ul id='ok-list'>
- {% for filename, title in ok_list %}
- <li><a href='{% url wiki_editor title %}'>{{ title|wiki_title }}</a> (<code>{{ filename }})</a></li>
+ {% for filename, slug, title in ok_list %}
+ <li><a href='{% url wiki_editor slug %}'>{{ title }}</a> (<code>{{ filename }})</a></li>
{% endfor %}
</ul>
{% endif %}
data-enabled-when="1" disabled="disabled">{% trans "Revert document" %}</button>
<button id="open-preview-button" disabled="disabled"
data-enabled-when="1"
- data-basehref="{% url wiki_editor_readonly document_name %}">{% trans "View version" %}</button>
+ data-basehref="{% url wiki_editor_readonly book.slug %}">{% trans "View version" %}</button>
</div>
<div id="history-view">
{% load toolbar_tags i18n %}
<div id="source-editor" class="editor">
- {% if not document_info.readonly %}{% toolbar %}{% endif %}
+ {% if not readonly %}{% toolbar %}{% endif %}
<textarea id="codemirror_placeholder"><br/></textarea>
- <!-- <input type="hidden" name="name" value="{{ document.name }}" />
+ <!-- <input type="hidden" name="name" value="{{ book.slug }}" />
<input type="hidden" name="author" value="annonymous" />
<input type="hidden" name="comment" value="no comment" />
- <input type="hidden" name="revision" value="{{ document_info.revision }}" />
+ <input type="hidden" name="revision" value="{{ book.doc.revision }}" />
-->
-</div>
\ No newline at end of file
+</div>
{% load i18n %}
-{% load wiki %}
<div id="summary-view-editor" class="editor" style="display: none">
<!-- <div class="toolbar">
</div> -->
<h2>
<label for="title">{% trans "Title" %}:</label>
<span data-ui-editable="true" data-edit-target="meta.displayTitle"
- >{{ document.name|wiki_title }}</span>
+ >{{ book.title }}</span>
</h2>
<p>
<label>{% trans "Document ID" %}:</label>
- <span>{{ document.name }}</span>
+ <span>{{ book.slug }}</span>
</p>
<p>
<label>{% trans "Current version" %}:</label>
- {{ document_info.revision }} ({{document_info.date}})
+ {{ book.doc.revision }} ({{ book.doc.head.created_at }})
<p>
<label>{% trans "Last edited by" %}:</label>
- {{document_info.author}}
+ {{ book.doc.head.author }}
</p>
<p>
<label for="gallery">{% trans "Link to gallery" %}:</label>
<span data-ui-editable="true" data-edit-target="meta.galleryLink"
- >{{ document_meta.gallery}}</span>
+ >{{ book.gallery }}</span>
</p>
<p><button type="button" id="publish_button">{% trans "Publish" %}</button></p>
{% load i18n %}
-{% load wiki %}
<li id="SummaryPerspective" data-ui-related="summary-view-editor" data-ui-jsclass="SummaryPerspective">
<span>{% trans "Summary" %}</span>
</li>
</div>
<div class="toolbar">
- {% if not document_info.readonly %}
+ {% if not readonly %}
<button id="insert-theme-button">
{% trans "Insert theme" %}
</button>
from django.conf import settings
-PART = ur"""[ ĄĆĘŁŃÓŚŻŹąćęłńóśżź0-9\w_.-]+"""
+#PART = ur"""[ ĄĆĘŁŃÓŚŻŹąćęłńóśżź0-9\w_.-]+"""
urlpatterns = patterns('wiki.views',
url(r'^$', redirect_to, {'url': 'catalogue/'}),
url(r'^catalogue/$', 'document_list', name='wiki_document_list'),
- url(r'^catalogue/([^/]+)/$', 'document_list'),
- url(r'^catalogue/([^/]+)/([^/]+)/$', 'document_list'),
- url(r'^catalogue/([^/]+)/([^/]+)/([^/]+)$', 'document_list'),
+ #url(r'^catalogue/([^/]+)/$', 'document_list'),
+ #url(r'^catalogue/([^/]+)/([^/]+)/$', 'document_list'),
+ #url(r'^catalogue/([^/]+)/([^/]+)/([^/]+)$', 'document_list'),
- url(r'^(?P<name>%s)$' % PART,
+ url(r'^(?P<slug>[^/]+)/$',
'editor', name="wiki_editor"),
- url(r'^(?P<name>[^/]+)/readonly$',
+ url(r'^(?P<slug>[^/]+)/readonly$',
'editor_readonly', name="wiki_editor_readonly"),
url(r'^upload/$',
'upload', name='wiki_upload'),
- url(r'^create/(?P<name>[^/]+)',
+ url(r'^create/(?P<slug>[^/]+)',
'create_missing', name='wiki_create_missing'),
url(r'^(?P<directory>[^/]+)/gallery$',
'gallery', name="wiki_gallery"),
- url(r'^(?P<name>[^/]+)/history$',
+ url(r'^(?P<slug>[^/]+)/history$',
'history', name="wiki_history"),
- url(r'^(?P<name>[^/]+)/rev$',
+ url(r'^(?P<slug>[^/]+)/rev$',
'revision', name="wiki_revision"),
- url(r'^(?P<name>[^/]+)/text$',
+ url(r'^(?P<slug>[^/]+)/text$',
'text', name="wiki_text"),
- url(r'^(?P<name>[^/]+)/revert$',
+ url(r'^(?P<slug>[^/]+)/revert$',
'revert', name='wiki_revert'),
- url(r'^(?P<name>[^/]+)/publish$', 'publish', name="wiki_publish"),
- url(r'^(?P<name>[^/]+)/publish/(?P<version>\d+)$', 'publish', name="wiki_publish"),
+ #url(r'^(?P<name>[^/]+)/publish$', 'publish', name="wiki_publish"),
+ #url(r'^(?P<name>[^/]+)/publish/(?P<version>\d+)$', 'publish', name="wiki_publish"),
- url(r'^(?P<name>[^/]+)/diff$', 'diff', name="wiki_diff"),
- url(r'^(?P<name>[^/]+)/tags$', 'add_tag', name="wiki_add_tag"),
+ url(r'^(?P<slug>[^/]+)/diff$', 'diff', name="wiki_diff"),
+ #url(r'^(?P<name>[^/]+)/tags$', 'add_tag', name="wiki_add_tag"),
import os
-import functools
import logging
logger = logging.getLogger("fnp.wiki")
from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
ajax_require_permission, recursive_groupby)
from django import http
+from django.shortcuts import get_object_or_404, redirect
-from wiki.models import getstorage, DocumentNotFound, normalize_name, split_name, join_name, Theme
+from wiki.models import Book, Theme
from wiki.forms import DocumentTextSaveForm, DocumentTextRevertForm, DocumentTagForm, DocumentCreateForm, DocumentsUploadForm
from datetime import datetime
from django.utils.encoding import smart_unicode
#
from django.views.decorators.cache import never_cache
-import wlapi
import nice_diff
import operator
MAX_LAST_DOCS = 10
-def normalized_name(view):
-
- @functools.wraps(view)
- def decorated(request, name, *args):
- normalized = normalize_name(name)
- logger.debug('View check %r -> %r', name, normalized)
-
- if normalized != name:
- return http.HttpResponseRedirect(
- reverse('wiki_' + view.__name__, kwargs={'name': normalized}))
-
- return view(request, name, *args)
-
- return decorated
-
-
@never_cache
def document_list(request):
return direct_to_template(request, 'wiki/document_list.html', extra_context={
- 'docs': getstorage().all(),
- 'last_docs': sorted(request.session.get("wiki_last_docs", {}).items(),
- key=operator.itemgetter(1), reverse=True),
+ 'books': Book.objects.all(),
+ 'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
+ key=lambda x: x[1]['time'], reverse=True),
})
@never_cache
-@normalized_name
-def editor(request, name, template_name='wiki/document_details.html'):
- storage = getstorage()
-
+def editor(request, slug, template_name='wiki/document_details.html'):
try:
- document = storage.get(name)
- except DocumentNotFound:
- return http.HttpResponseRedirect(reverse("wiki_create_missing", args=[name]))
+ book = Book.objects.get(slug=slug)
+ except Book.DoesNotExist:
+ return http.HttpResponseRedirect(reverse("wiki_create_missing", args=[slug]))
access_time = datetime.now()
- last_documents = request.session.get("wiki_last_docs", {})
- last_documents[name] = access_time
+ last_books = request.session.get("wiki_last_books", {})
+ last_books[slug] = {
+ 'time': access_time,
+ 'title': book.title,
+ }
- if len(last_documents) > MAX_LAST_DOCS:
- oldest_key = min(last_documents, key=last_documents.__getitem__)
- del last_documents[oldest_key]
- request.session['wiki_last_docs'] = last_documents
+ if len(last_books) > MAX_LAST_DOCS:
+ oldest_key = min(last_books, key=operator.itemgetter('time'))
+ del last_books[oldest_key]
+ request.session['wiki_last_books'] = last_books
return direct_to_template(request, template_name, extra_context={
- 'document': document,
- 'document_name': document.name,
- 'document_info': document.info,
- 'document_meta': document.meta,
+ 'book': book,
'forms': {
"text_save": DocumentTextSaveForm(prefix="textsave"),
"text_revert": DocumentTextRevertForm(prefix="textrevert"),
@require_GET
-@normalized_name
-def editor_readonly(request, name, template_name='wiki/document_details_readonly.html'):
- name = normalize_name(name)
- storage = getstorage()
-
+def editor_readonly(request, slug, template_name='wiki/document_details_readonly.html'):
try:
+ book = Book.objects.get(slug=slug)
revision = request.GET['revision']
- document = storage.get(name, revision)
- except (KeyError, DocumentNotFound):
+ except KeyError:
raise http.Http404
access_time = datetime.now()
- last_documents = request.session.get("wiki_last_docs", {})
- last_documents[name] = access_time
+ last_books = request.session.get("wiki_last_books", {})
+ last_books[slug] = {
+ 'time': access_time,
+ 'title': book.title,
+ }
- if len(last_documents) > MAX_LAST_DOCS:
- oldest_key = min(last_documents, key=last_documents.__getitem__)
- del last_documents[oldest_key]
- request.session['wiki_last_docs'] = last_documents
+ if len(last_books) > MAX_LAST_DOCS:
+ oldest_key = min(last_books, key=operator.itemgetter('time'))
+ del last_books[oldest_key]
+ request.session['wiki_last_books'] = last_books
return direct_to_template(request, template_name, extra_context={
- 'document': document,
- 'document_name': document.name,
- 'document_info': dict(document.info(), readonly=True),
- 'document_meta': document.meta,
+ 'book': book,
+ 'revision': revision,
+ 'readonly': True,
'REDMINE_URL': settings.REDMINE_URL,
})
-@normalized_name
-def create_missing(request, name):
- storage = getstorage()
+def create_missing(request, slug):
+ slug = slug.replace(' ', '-')
if request.method == "POST":
form = DocumentCreateForm(request.POST, request.FILES)
if form.is_valid():
- doc = storage.create_document(
- name=form.cleaned_data['id'],
+
+ if request.user.is_authenticated():
+ creator = request.user
+ else:
+ creator = None
+ book = Book.create(creator=creator,
+ slug=form.cleaned_data['slug'],
+ title=form.cleaned_data['title'],
text=form.cleaned_data['text'],
)
- return http.HttpResponseRedirect(reverse("wiki_editor", args=[doc.name]))
+ return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
else:
form = DocumentCreateForm(initial={
- "id": name.replace(" ", "_"),
- "title": name.title(),
+ "slug": slug,
+ "title": slug.replace('-', ' ').title(),
})
return direct_to_template(request, "wiki/document_create_missing.html", extra_context={
- "document_name": name,
+ "slug": slug,
"form": form,
})
def upload(request):
- storage = getstorage()
-
if request.method == "POST":
form = DocumentsUploadForm(request.POST, request.FILES)
if form.is_valid():
+ import slughifi
+
+ if request.user.is_authenticated():
+ creator = request.user
+ else:
+ creator = None
+
zip = form.cleaned_data['zip']
skipped_list = []
ok_list = []
error_list = []
- titles = {}
- existing = storage.all()
+ slugs = {}
+ existing = [book.slug for book in Book.objects.all()]
for filename in zip.namelist():
if filename[-1] == '/':
continue
- title = normalize_name(os.path.basename(filename)[:-4])
- if not (title and filename.endswith('.xml')):
+ title = os.path.basename(filename)[:-4]
+ slug = slughifi(title)
+ if not (slug and filename.endswith('.xml')):
skipped_list.append(filename)
- elif title in titles:
- error_list.append((filename, title, _('Title already used for %s' % titles[title])))
- elif title in existing:
- error_list.append((filename, title, _('Title already used in repository.')))
+ elif slug in slugs:
+ error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
+ elif slug in existing:
+ error_list.append((filename, slug, _('Slug already used in repository.')))
else:
try:
zip.read(filename).decode('utf-8') # test read
- ok_list.append((filename, title))
+ ok_list.append((filename, slug, title))
except UnicodeDecodeError:
error_list.append((filename, title, _('File should be UTF-8 encoded.')))
- titles[title] = filename
+ slugs[slug] = filename
if not error_list:
- for filename, title in ok_list:
- storage.create_document(
- name=title,
- text=zip.read(filename).decode('utf-8')
+ for filename, slug, title in ok_list:
+ Book.create(creator=creator,
+ slug=slug,
+ title=title,
+ text=zip.read(filename).decode('utf-8'),
)
return direct_to_template(request, "wiki/document_upload.html", extra_context={
"skipped_list": skipped_list,
"error_list": error_list,
})
- #doc = storage.create_document(
- # name=base,
- # text=form.cleaned_data['text'],
-
-
- return http.HttpResponse('\n'.join(yeslist) + '\n\n' + '\n'.join(nolist))
else:
form = DocumentsUploadForm()
@never_cache
-@normalized_name
@decorator_from_middleware(GZipMiddleware)
-def text(request, name):
- storage = getstorage()
+def text(request, slug):
+ doc = get_object_or_404(Book, slug=slug).doc
if request.method == 'POST':
form = DocumentTextSaveForm(request.POST, prefix="textsave")
if form.is_valid():
- revision = form.cleaned_data['parent_revision']
- document = storage.get_or_404(name, revision)
- document.text = form.cleaned_data['text']
- comment = form.cleaned_data['comment']
- if form.cleaned_data['stage_completed']:
- comment += '\n#stage-finished: %s\n' % form.cleaned_data['stage_completed']
+ # TODO:
+ # - stage completion should be stored (as a relation)
+
if request.user.is_authenticated():
- author_name = request.user
- author_email = request.user.email
+ author = request.user
else:
- author_name = form.cleaned_data['author_name']
- author_email = form.cleaned_data['author_email']
- author = "%s <%s>" % (author_name, author_email)
- storage.put(document, author=author, comment=comment, parent=revision)
- document = storage.get(name)
+ author = None
+ text = form.cleaned_data['text']
+ parent_revision = form.cleaned_data['parent_revision']
+ parent = doc.at_revision(parent_revision)
+ doc.commit(author=author,
+ text=text,
+ parent=parent,
+ description=form.cleaned_data['comment'],
+ )
+ revision = doc.revision()
return JSONResponse({
- 'text': document.plain_text if revision != document.revision else None,
- 'meta': document.meta(),
- 'revision': document.revision,
+ 'text': doc.materialize() if parent_revision != revision else None,
+ 'meta': {},
+ 'revision': revision,
})
else:
return JSONFormInvalid(form)
else:
revision = request.GET.get("revision", None)
-
+
try:
- try:
- revision = revision and int(revision)
- logger.info("Fetching %s", revision)
- document = storage.get(name, revision)
- except ValueError:
- # treat as a tag
- logger.info("Fetching tag %s", revision)
- document = storage.get_by_tag(name, revision)
- except DocumentNotFound:
- raise http.Http404
+ revision = int(revision)
+ except ValueError:
+ revision = None
return JSONResponse({
- 'text': document.plain_text,
- 'meta': document.meta(),
- 'revision': document.revision,
+ 'text': doc.at_revision(revision).materialize(),
+ 'meta': {},
+ 'revision': revision if revision else doc.revision(),
})
@never_cache
-@normalized_name
@require_POST
-def revert(request, name):
- storage = getstorage()
+def revert(request, slug):
form = DocumentTextRevertForm(request.POST, prefix="textrevert")
if form.is_valid():
- print 'valid'
+ doc = get_object_or_404(Book, slug=slug).doc
revision = form.cleaned_data['revision']
comment = form.cleaned_data['comment']
comment += "\n#revert to %s" % revision
if request.user.is_authenticated():
- author_name = request.user
- author_email = request.user.email
+ author = request.user
else:
- author_name = form.cleaned_data['author_name']
- author_email = form.cleaned_data['author_email']
- author = "%s <%s>" % (author_name, author_email)
-
- before = storage.get(name).revision
- logger.info("Reverting %s to %s", name, revision)
- storage.revert(name, revision, comment=comment, author=author)
- logger.info("Fetching %s", name)
- document = storage.get(name)
+ author = None
+ before = doc.revision()
+ logger.info("Reverting %s to %s", slug, revision)
+ doc.at_revision(revision).revert(author=author, description=comment)
return JSONResponse({
- 'text': document.plain_text if before != document.revision else None,
- 'meta': document.meta(),
- 'revision': document.revision,
+ 'text': doc.materialize() if before != doc.revision() else None,
+ 'meta': {},
+ 'revision': doc.revision(),
})
else:
- print 'invalid'
return JSONFormInvalid(form)
@never_cache
-@normalized_name
-def diff(request, name):
- storage = getstorage()
-
+def diff(request, slug):
revA = int(request.GET.get('from', 0))
revB = int(request.GET.get('to', 0))
if revB == 0:
revB = None
- docA = storage.get_or_404(name, int(revA))
- docB = storage.get_or_404(name, int(revB))
+ doc = get_object_or_404(Book, slug=slug).doc
+ docA = doc.at_revision(revA).materialize()
+ docB = doc.at_revision(revB).materialize()
- return http.HttpResponse(nice_diff.html_diff_table(docA.plain_text.splitlines(),
- docB.plain_text.splitlines(), context=3))
+ return http.HttpResponse(nice_diff.html_diff_table(docA.splitlines(),
+ docB.splitlines(), context=3))
@never_cache
-@normalized_name
-def revision(request, name):
- storage = getstorage()
-
- try:
- return http.HttpResponse(str(storage.doc_meta(name)['revision']))
- except DocumentNotFound:
- raise http.Http404
+def revision(request, slug):
+ book = get_object_or_404(Book, slug=slug)
+ return http.HttpResponse(str(book.doc.revision()))
@never_cache
-@normalized_name
-def history(request, name):
- storage = getstorage()
-
+def history(request, slug):
# TODO: pagination
- changesets = list(storage.history(name))
+ book = get_object_or_404(Book, slug=slug)
+ rev = book.doc.revision()
+ changes = []
+ for change in book.doc.history().order_by('-created_at'):
+ if change.author:
+ author = "%s %s <%s>" % (
+ change.author.first_name,
+ change.author.last_name,
+ change.author.email)
+ else:
+ author = None
+ changes.append({
+ "version": rev,
+ "description": change.description,
+ "author": author,
+ "date": change.created_at,
+ "tag": [],
+ })
+ rev -= 1
+ return JSONResponse(changes)
+
- return JSONResponse(changesets)
+
+"""
+import wlapi
@require_POST
return JSONResponse({"result": api.publish_book(document)})
except wlapi.APICallException, e:
return JSONServerError({"message": str(e)})
-
+"""
def themes(request):
prefix = request.GET.get('q', '')
data[this.name] = this.value;
});
- var metaComment = '<!--';
- metaComment += '\n\tgallery:' + self.galleryLink;
- metaComment += '\n-->\n'
-
- data['textsave-text'] = metaComment + self.text;
+ data['textsave-text'] = self.text;
$.ajax({
url: reverse("ajax_document_text", self.id),