From e9f489960eb8a1fc8c1fc39123589a9358c5d569 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 19 Aug 2011 13:12:20 +0200 Subject: [PATCH] publish log + some fixes --- apps/catalogue/forms.py | 2 +- ...d_chunkpublishrecord__del_field_book_la.py | 140 ++++++++++++++++++ apps/catalogue/models.py | 71 ++++++++- .../templates/catalogue/document_list.html | 49 ++++-- apps/catalogue/templatetags/catalogue.py | 40 +++-- apps/catalogue/urls.py | 1 - apps/catalogue/views.py | 22 +-- redakcja/templates/registration/login.html | 2 +- 8 files changed, 270 insertions(+), 57 deletions(-) create mode 100644 apps/catalogue/migrations/0002_auto__add_bookpublishrecord__add_chunkpublishrecord__del_field_book_la.py diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py index 33ccbe6f..6a56e764 100644 --- a/apps/catalogue/forms.py +++ b/apps/catalogue/forms.py @@ -65,7 +65,7 @@ class ChunkForm(forms.ModelForm): """ user = forms.ModelChoiceField(queryset= User.objects.annotate(count=Count('chunk')). - order_by('-count', 'last_name', 'first_name')) + order_by('-count', 'last_name', 'first_name'), required=False) class Meta: diff --git a/apps/catalogue/migrations/0002_auto__add_bookpublishrecord__add_chunkpublishrecord__del_field_book_la.py b/apps/catalogue/migrations/0002_auto__add_bookpublishrecord__add_chunkpublishrecord__del_field_book_la.py new file mode 100644 index 00000000..0c8efc08 --- /dev/null +++ b/apps/catalogue/migrations/0002_auto__add_bookpublishrecord__add_chunkpublishrecord__del_field_book_la.py @@ -0,0 +1,140 @@ +# 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 'BookPublishRecord' + db.create_table('catalogue_bookpublishrecord', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Book'])), + ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + )) + db.send_create_signal('catalogue', ['BookPublishRecord']) + + # Adding model 'ChunkPublishRecord' + db.create_table('catalogue_chunkpublishrecord', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('book_record', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.BookPublishRecord'])), + ('change', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ChunkChange'])), + )) + db.send_create_signal('catalogue', ['ChunkPublishRecord']) + + # Deleting field 'Book.last_published' + db.delete_column('catalogue_book', 'last_published') + + + def backwards(self, orm): + + # Deleting model 'BookPublishRecord' + db.delete_table('catalogue_bookpublishrecord') + + # Deleting model 'ChunkPublishRecord' + db.delete_table('catalogue_chunkpublishrecord') + + # Adding field 'Book.last_published' + db.add_column('catalogue_book', 'last_published', self.gf('django.db.models.fields.DateTimeField')(null=True, db_index=True), keep_default=False) + + + 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', 'content_type__model', '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'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, + 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'patch': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + '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'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index ff3d434e..f9686547 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -3,6 +3,7 @@ # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # +from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -23,7 +24,6 @@ class Book(models.Model): parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children") parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True) - last_published = models.DateTimeField(null=True, editable=False, db_index=True) class NoTextError(BaseException): pass @@ -32,6 +32,7 @@ class Book(models.Model): ordering = ['parent_number', 'title'] verbose_name = _('book') verbose_name_plural = _('books') + permissions = [('can_pubmark', 'Can mark for publishing')] def __unicode__(self): return self.title @@ -56,11 +57,20 @@ class Book(models.Model): def __getitem__(self, chunk): return self.chunk_set.all()[chunk] - def materialize(self, publishable=True): - """ - Get full text of the document compiled from chunks. - Takes the current versions of all texts - or versions most recently tagged for publishing. + def __len__(self): + return self.chunk_set.count() + + def __nonzero__(self): + """ + Necessary so that __len__ isn't used for bool evaluation. + """ + return True + + def get_current_changes(self, publishable=True): + """ + Returns a list containing one Change for every Chunk in the Book. + Takes the most recent revision (publishable, if set). + Throws an error, if a proper revision is unavailable for a Chunk. """ if publishable: changes = [chunk.publishable() for chunk in self] @@ -68,16 +78,41 @@ class Book(models.Model): changes = [chunk.head for chunk in self] if None in changes: raise self.NoTextError('Some chunks have no available text.') + return changes + + def materialize(self, publishable=False, changes=None): + """ + Get full text of the document compiled from chunks. + Takes the current versions of all texts + or versions most recently tagged for publishing, + or a specified iterable changes. + """ + if changes is None: + changes = self.get_current_changes(publishable) return compile_text(change.materialize() for change in changes) def publishable(self): - if not len(self): + if not self.chunk_set.exists(): return False for chunk in self: if not chunk.publishable(): return False return True + def publish(self, user): + """ + Publishes a book on behalf of a (local) user. + """ + from apiclient import api_call + + changes = self.get_current_changes(publishable=True) + book_xml = book.materialize(changes=changes) + #api_call(user, "books", {"book_xml": book_xml}) + # record the publish + br = BookPublishRecord.objects.create(book=self, user=user) + for c in changes: + ChunkPublishRecord.objects.create(book_record=br, change=c) + def make_chunk_slug(self, proposed): """ Finds a chunk slug not yet used in the book. @@ -176,3 +211,25 @@ class Chunk(dvcs_models.Document): instance.book.save() models.signals.post_save.connect(Chunk.listener_saved, sender=Chunk) + + +class BookPublishRecord(models.Model): + """ + A record left after publishing a Book. + """ + + book = models.ForeignKey(Book) + timestamp = models.DateTimeField(auto_now_add=True) + user = models.ForeignKey(User) + + class Meta: + ordering = ['-timestamp'] + + +class ChunkPublishRecord(models.Model): + """ + BookPublishRecord details for each Chunk. + """ + + book_record = models.ForeignKey(BookPublishRecord) + change = models.ForeignKey(Chunk.change_model) diff --git a/apps/catalogue/templates/catalogue/document_list.html b/apps/catalogue/templates/catalogue/document_list.html index cb458c4f..3473b596 100644 --- a/apps/catalogue/templates/catalogue/document_list.html +++ b/apps/catalogue/templates/catalogue/document_list.html @@ -32,15 +32,19 @@ $(function() { {% endif %}value="{% set_get_parameter stage=stage.slug,page= %}">{{ stage.name }} {% endfor %} - + + {% if not viewed_user %} + + {% endif %} + @@ -61,8 +65,9 @@ $(function() { {{ book.title }} {% if chunk.stage %} ({{ chunk.stage }}) + {% else %}– {% endif %} - {% if chunk.user %}{{ chunk.user.first_name }} {{ chunk.user.last_name }}{% endif %} + {% if chunk.user %}{{ chunk.user.first_name }} {{ chunk.user.last_name }}{% endif %} {% endwith %} {% else %} @@ -78,8 +83,19 @@ $(function() { {{ chunk.number }}. {{ chunk.comment }} - ({{ chunk.stage }}) - {% if chunk.user %}{{ chunk.user.first_name }} {{ chunk.user.last_name }}{% endif %} + {% if chunk.stage %} + {{ chunk.stage }} + {% else %} + – + {% endif %} + {% if not viewed_user %} + {% if chunk.user %} + + {{ chunk.user.first_name }} {{ chunk.user.last_name }} + {% else %} + + {% endif %} + {% endif %} {% endfor %} {% endifequal %} @@ -102,6 +118,11 @@ $(function() { -

{% trans "Recent activity" %}

- {% wall %} + {% if viewed_user %} +

{% trans "Recent activity for" %} {{ viewed_user }}

+ {% wall viewed_user %} + {% else %} +

{% trans "Recent activity" %}

+ {% wall %} + {% endif %} {% endblock rightcolumn %} diff --git a/apps/catalogue/templatetags/catalogue.py b/apps/catalogue/templatetags/catalogue.py index cdb19404..850a2e80 100644 --- a/apps/catalogue/templatetags/catalogue.py +++ b/apps/catalogue/templatetags/catalogue.py @@ -1,13 +1,13 @@ from __future__ import absolute_import -from django.db.models import Count +from django.db.models import Count, Q from django.core.urlresolvers import reverse from django.contrib.comments.models import Comment from django.template.defaultfilters import stringfilter from django import template from django.utils.translation import ugettext as _ -from catalogue.models import Book, Chunk +from catalogue.models import Book, Chunk, BookPublishRecord register = template.Library() @@ -32,7 +32,7 @@ def main_tabs(context): if user.is_authenticated(): tabs.append(Tab('my', _('My page'), reverse("catalogue_user"))) - tabs.append(Tab('unassigned', _('Unassigned'), reverse("catalogue_unassigned"))) + tabs.append(Tab('all', _('All'), reverse("catalogue_document_list"))) tabs.append(Tab('users', _('Users'), reverse("catalogue_users"))) tabs.append(Tab('create', _('Add'), reverse("catalogue_create_missing"))) tabs.append(Tab('upload', _('Upload'), reverse("catalogue_upload"))) @@ -61,10 +61,12 @@ class WallItem(object): return self.email -def changes_wall(max_len): +def changes_wall(user, max_len): qs = Chunk.change_model.objects.filter(revision__gt=-1).order_by('-created_at') qs = qs.defer('patch') qs = qs.select_related('author', 'tree', 'tree__book__title') + if user: + qs = qs.filter(Q(author=user) | Q(tree__user=user)) qs = qs[:max_len] for item in qs: tag = 'stage' if item.tags.count() else 'change' @@ -80,21 +82,28 @@ def changes_wall(max_len): yield w -def published_wall(max_len): - qs = Book.objects.exclude(last_published=None).order_by('-last_published') +# TODO: marked for publishing + + +def published_wall(user, max_len): + qs = BookPublishRecord.objects.select_related('book__title') + if user: + # TODO: published my book + qs = qs.filter(Q(user=user)) qs = qs[:max_len] for item in qs: w = WallItem('publish') - w.title = item.title - w.summary = item.title + w.title = item.book.title + #w.summary = w.url = chunk.book.get_absolute_url() - w.timestamp = item.last_published - w.user = item.last_published_by yield w -def comments_wall(max_len): +def comments_wall(user, max_len): qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date') + if user: + # TODO: comments concerning my books + qs = qs.filter(Q(user=user)) qs = qs[:max_len] for item in qs: w = WallItem('comment') @@ -130,12 +139,13 @@ def big_wall(max_len, *args): @register.inclusion_tag("catalogue/wall.html", takes_context=True) -def wall(context, max_len=10): +def wall(context, user=None, max_len=10): + print user return { "request": context['request'], "STATIC_URL": context['STATIC_URL'], "wall": big_wall(max_len, - changes_wall(max_len), - published_wall(max_len), - comments_wall(max_len), + changes_wall(user, max_len), + published_wall(user, max_len), + comments_wall(user, max_len), )} diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index 93c02b13..e9e5e893 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -7,7 +7,6 @@ urlpatterns = patterns('catalogue.views', url(r'^$', redirect_to, {'url': 'catalogue/'}), url(r'^catalogue/$', 'document_list', name='catalogue_document_list'), - url(r'^unassigned/$', 'unassigned', name='catalogue_unassigned'), url(r'^user/$', 'my', name='catalogue_user'), url(r'^user/(?P[^/]+)/$', 'user', name='catalogue_user'), url(r'^users/$', 'users', name='catalogue_users'), diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index 9a795a36..3c58f3f0 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -19,11 +19,10 @@ from django.views.generic.simple import direct_to_template import librarian.html import librarian.text -from apiclient import api_call from catalogue import forms from catalogue import helpers from catalogue.helpers import active_tab -from catalogue.models import Book, Chunk +from catalogue.models import Book, Chunk, BookPublishRecord, ChunkPublishRecord from catalogue import xml_tools # @@ -66,19 +65,6 @@ def document_list(request, filters=None): }) -@active_tab('unassigned') -@never_cache -def unassigned(request): - chunks_list = helpers.ChunksList(Chunk.objects.filter( - user=None).order_by('book__title', 'book__id', 'number')) - - return direct_to_template(request, 'catalogue/document_list.html', extra_context={ - 'books': chunks_list, - 'last_books': sorted(request.session.get("wiki_last_books", {}).items(), - key=lambda x: x[1]['time'], reverse=True), - }) - - @never_cache def user(request, username=None): if username is None: @@ -96,6 +82,8 @@ def user(request, username=None): 'books': chunks_list, 'last_books': sorted(request.session.get("wiki_last_books", {}).items(), key=lambda x: x[1]['time'], reverse=True), + 'viewed_user': user, + 'stages': Chunk.tag_model.objects.all(), }) my = login_required(active_tab('my')(user)) @@ -427,10 +415,8 @@ def book_edit(request, slug): def publish(request, slug): book = get_object_or_404(Book, slug=slug) try: - ret = api_call(request.user, "books", {"book_xml": book.materialize()}) + book.publish(request.user) except BaseException, e: return http.HttpResponse(e) else: - book.last_published = datetime.now() - book.save() return http.HttpResponseRedirect(book.get_absolute_url()) diff --git a/redakcja/templates/registration/login.html b/redakcja/templates/registration/login.html index e2b0465f..1f0698b6 100644 --- a/redakcja/templates/registration/login.html +++ b/redakcja/templates/registration/login.html @@ -1,4 +1,4 @@ -{% extends "wiki/base.html" %} +{% extends "catalogue/base.html" %} {% block subtitle %} - Logowanie {% endblock subtitle %} -- 2.20.1