From: Marcin Koziej Date: Mon, 21 May 2012 09:39:49 +0000 (+0200) Subject: Merge branch 'mass_edit' X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/c4599df2f08c95fd317967746a3325c3b168acdd?hp=180e82abec8f380595152c2f51d2967ff2d6d42e Merge branch 'mass_edit' --- diff --git a/apps/catalogue/helpers.py b/apps/catalogue/helpers.py index df64ade1..a97a0400 100644 --- a/apps/catalogue/helpers.py +++ b/apps/catalogue/helpers.py @@ -1,5 +1,11 @@ from datetime import date from functools import wraps +from os.path import join +from os import listdir, stat +from shutil import move, rmtree +from django.conf import settings +import re +import filecmp from django.db.models import Count @@ -36,3 +42,104 @@ def parse_isodate(isodate): return date(*[int(p) for p in isodate.split('-')]) except (AttributeError, TypeError, ValueError): raise ValueError("Not a date in ISO format.") + + +class GalleryMerger(object): + def __init__(self, dest_gallery, src_gallery): + assert isinstance(dest_gallery, str) + assert isinstance(src_gallery, str) + self.dest = dest_gallery + self.src = src_gallery + self.dest_size = None + self.src_size = None + self.num_deleted = 0 + + @staticmethod + def path(gallery): + return join(settings.MEDIA_ROOT, settings.IMAGE_DIR, gallery) + + @staticmethod + def get_prefix(name): + m = re.match(r"^([0-9])-", name) + if m: + return int(m.groups()[0]) + return None + + @staticmethod + def set_prefix(name, prefix, always=False): + m = not always and re.match(r"^([0-9])-", name) + return "%1d-%s" % (prefix, m and name[2:] or name) + + def merge(self): + if not self.dest: + return self.src + if not self.src: + return self.dest + + files = listdir(self.path(self.dest)) + self.dest_size = len(files) + files_other = listdir(self.path(self.src)) + self.src_size = len(files_other) + + if files and files_other: + print "compare %s with %s" % (files[-1], files_other[0]) + if filecmp.cmp( + join(self.path(self.dest), files[-1]), + join(self.path(self.src), files_other[0]), + False + ): + files_other.pop(0) + self.num_deleted = 1 + + prefixes = {} + renamed_files = {} + renamed_files_other = {} + last_pfx = -1 + + # check if all elements of my files have a prefix + files_prefixed = True + for f in files: + p = self.get_prefix(f) + if p: + if p > last_pfx: last_pfx = p + else: + files_prefixed = False + break + + # if not, add a 0 prefix to them + if not files_prefixed: + prefixes[0] = 0 + for f in files: + renamed_files[f] = self.set_prefix(f, 0, True) + + # two cases here - either all are prefixed or not. + files_other_prefixed = True + for f in files_other: + pfx = self.get_prefix(f) + if pfx is not None: + if not pfx in prefixes: + last_pfx += 1 + prefixes[pfx] = last_pfx + renamed_files_other[f] = self.set_prefix(f, prefixes[pfx]) + else: + # ops, not all files here were prefixed. + files_other_prefixed = False + break + + # just set a 1- prefix to all of them + if not files_other_prefixed: + for f in files_other: + renamed_files_other[f] = self.set_prefix(f, 1, True) + + # finally, move / rename files. + from nose.tools import set_trace + # set_trace() + for frm, to in renamed_files.items(): + move(join(self.path(self.dest), frm), + join(self.path(self.dest), to)) + for frm, to in renamed_files_other.items(): + move(join(self.path(self.src), frm), + join(self.path(self.dest), to)) + + rmtree(join(self.path(self.src))) + return self.dest diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo index e6f7d3b6..21585b12 100644 Binary files a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo and b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo differ diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.po b/apps/catalogue/locale/pl/LC_MESSAGES/django.po index 65d9ba35..7195b771 100644 --- a/apps/catalogue/locale/pl/LC_MESSAGES/django.po +++ b/apps/catalogue/locale/pl/LC_MESSAGES/django.po @@ -7,9 +7,9 @@ msgid "" msgstr "" "Project-Id-Version: Platforma Redakcyjna\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-12-01 16:21+0100\n" -"PO-Revision-Date: 2011-12-01 16:23+0100\n" -"Last-Translator: Radek Czajka \n" +"POT-Creation-Date: 2012-05-10 16:00+0200\n" +"PO-Revision-Date: 2012-05-10 16:05+0100\n" +"Last-Translator: Marcin Koziej \n" "Language-Team: Fundacja Nowoczesna Polska \n" "Language: pl\n" "MIME-Version: 1.0\n" @@ -46,16 +46,16 @@ msgstr "Część z tym slugiem już istnieje" msgid "Append to" msgstr "Dołącz do" -#: views.py:158 +#: views.py:160 #, python-format msgid "Slug already used for %s" msgstr "Slug taki sam jak dla pliku %s" -#: views.py:160 +#: views.py:162 msgid "Slug already used in repository." msgstr "Dokument o tym slugu już istnieje w repozytorium." -#: views.py:166 +#: views.py:168 msgid "File should be UTF-8 encoded." msgstr "Plik powinien mieć kodowanie UTF-8." @@ -85,37 +85,37 @@ msgstr "rodzic" msgid "parent number" msgstr "numeracja rodzica" -#: models/book.py:45 +#: models/book.py:47 #: models/chunk.py:21 #: models/publish_log.py:17 msgid "book" msgstr "książka" -#: models/book.py:46 +#: models/book.py:48 msgid "books" msgstr "książki" -#: models/book.py:221 +#: models/book.py:235 msgid "No chunks in the book." msgstr "Książka nie ma części." -#: models/book.py:225 +#: models/book.py:239 msgid "Not all chunks have publishable revisions." msgstr "Niektóre części nie są gotowe do publikacji." -#: models/book.py:234 +#: models/book.py:248 msgid "Invalid XML" msgstr "Nieprawidłowy XML" -#: models/book.py:236 +#: models/book.py:250 msgid "No Dublin Core found." msgstr "Brak sekcji Dublin Core." -#: models/book.py:238 +#: models/book.py:252 msgid "Invalid Dublin Core" msgstr "Nieprawidłowy Dublin Core" -#: models/book.py:241 +#: models/book.py:255 msgid "rdf:about is not" msgstr "rdf:about jest różny od" @@ -197,58 +197,58 @@ msgstr "Części" msgid "Publication" msgstr "Publikacja" -#: templates/catalogue/book_detail.html:44 +#: templates/catalogue/book_detail.html:46 msgid "Last published" msgstr "Ostatnio opublikowano" -#: templates/catalogue/book_detail.html:54 +#: templates/catalogue/book_detail.html:56 msgid "Full XML" msgstr "Pełny XML" -#: templates/catalogue/book_detail.html:55 +#: templates/catalogue/book_detail.html:57 msgid "HTML version" msgstr "Wersja HTML" -#: templates/catalogue/book_detail.html:56 +#: templates/catalogue/book_detail.html:58 msgid "TXT version" msgstr "Wersja TXT" -#: templates/catalogue/book_detail.html:57 +#: templates/catalogue/book_detail.html:59 msgid "PDF version" msgstr "Wersja PDF" -#: templates/catalogue/book_detail.html:58 +#: templates/catalogue/book_detail.html:60 msgid "EPUB version" msgstr "Wersja EPUB" -#: templates/catalogue/book_detail.html:71 +#: templates/catalogue/book_detail.html:73 msgid "Publish" msgstr "Opublikuj" -#: templates/catalogue/book_detail.html:75 +#: templates/catalogue/book_detail.html:77 msgid "Log in to publish." msgstr "Zaloguj się, aby opublikować." -#: templates/catalogue/book_detail.html:78 +#: templates/catalogue/book_detail.html:80 msgid "This book can't be published yet, because:" msgstr "Ta książka nie może jeszcze zostać opublikowana. Powód:" -#: templates/catalogue/book_detail.html:87 +#: templates/catalogue/book_detail.html:90 msgid "Comments" msgstr "Komentarze" -#: templates/catalogue/book_html.html:13 +#: templates/catalogue/book_text.html:7 +msgid "Redakcja" +msgstr "" + +#: templates/catalogue/book_text.html:15 msgid "Table of contents" msgstr "Spis treści" -#: templates/catalogue/book_html.html:14 +#: templates/catalogue/book_text.html:17 msgid "Edit. note" msgstr "Nota red." -#: templates/catalogue/book_html.html:15 -msgid "Infobox" -msgstr "Informacje" - #: templates/catalogue/chunk_add.html:5 #: templates/catalogue/chunk_edit.html:19 msgid "Split chunk" @@ -263,8 +263,8 @@ msgid "Add chunk" msgstr "Dodaj część" #: templates/catalogue/chunk_edit.html:6 -#: templates/catalogue/book_list/book.html:7 -#: templates/catalogue/book_list/chunk.html:5 +#: templates/catalogue/book_list/book.html:8 +#: templates/catalogue/book_list/chunk.html:6 msgid "Chunk settings" msgstr "Ustawienia części" @@ -289,7 +289,6 @@ msgid "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with < msgstr "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie kończące się na .xml zostaną zignorowane." #: templates/catalogue/document_upload.html:17 -#: templates/catalogue/upload_pdf.html:13 #: templatetags/catalogue.py:35 msgid "Upload" msgstr "Załaduj" @@ -331,10 +330,6 @@ msgstr "Twoje ostatnie edycje" msgid "Recent activity for" msgstr "Ostatnia aktywność dla:" -#: templates/catalogue/upload_pdf.html:8 -msgid "PDF file upload" -msgstr "" - #: templates/catalogue/user_list.html:7 #: templatetags/catalogue.py:31 msgid "Users" @@ -348,37 +343,37 @@ msgstr "nie zalogowany" msgid "No activity recorded." msgstr "Nie zanotowano aktywności." -#: templates/catalogue/book_list/book.html:6 -#: templates/catalogue/book_list/book.html:25 +#: templates/catalogue/book_list/book.html:7 +#: templates/catalogue/book_list/book.html:27 msgid "Book settings" msgstr "Ustawienia książki" -#: templates/catalogue/book_list/book_list.html:19 +#: templates/catalogue/book_list/book_list.html:21 msgid "Show hidden books" msgstr "Pokaż ukryte książki" -#: templates/catalogue/book_list/book_list.html:24 +#: templates/catalogue/book_list/book_list.html:26 msgid "Search in book titles" msgstr "Szukaj w tytułach książek" -#: templates/catalogue/book_list/book_list.html:29 +#: templates/catalogue/book_list/book_list.html:31 msgid "stage" msgstr "etap" -#: templates/catalogue/book_list/book_list.html:31 -#: templates/catalogue/book_list/book_list.html:42 +#: templates/catalogue/book_list/book_list.html:33 +#: templates/catalogue/book_list/book_list.html:44 msgid "none" msgstr "brak" -#: templates/catalogue/book_list/book_list.html:40 +#: templates/catalogue/book_list/book_list.html:42 msgid "editor" msgstr "redaktor" -#: templates/catalogue/book_list/book_list.html:51 +#: templates/catalogue/book_list/book_list.html:53 msgid "status" msgstr "status" -#: templates/catalogue/book_list/book_list.html:75 +#: templates/catalogue/book_list/book_list.html:77 #, python-format msgid "%(c)s book" msgid_plural "%(c)s books" @@ -386,10 +381,30 @@ msgstr[0] "%(c)s książka" msgstr[1] "%(c)s książki" msgstr[2] "%(c)s książek" -#: templates/catalogue/book_list/book_list.html:80 +#: templates/catalogue/book_list/book_list.html:82 msgid "No books found." msgstr "Nie znaleziono książek." +#: templates/catalogue/book_list/book_list.html:88 +msgid "Set stage" +msgstr "Ustaw etap" + +#: templates/catalogue/book_list/book_list.html:89 +msgid "Set user" +msgstr "Przypisz redaktora" + +#: templates/catalogue/book_list/book_list.html:91 +msgid "Mark publishable" +msgstr "Oznacz do publikacji" + +#: templates/catalogue/book_list/book_list.html:92 +msgid "Mark not publishable" +msgstr "Odznacz do publikacji" + +#: templates/catalogue/book_list/book_list.html:93 +msgid "Other user" +msgstr "Inny użytkownik" + #: templatetags/book_list.py:84 msgid "publishable" msgstr "do publikacji" @@ -434,6 +449,9 @@ msgstr "Zmiana" msgid "Comment" msgstr "Komentarz" +#~ msgid "Infobox" +#~ msgstr "Informacje" + #~ msgid "Admin" #~ msgstr "Administracja" diff --git a/apps/catalogue/models/book.py b/apps/catalogue/models/book.py index ade9a429..df1ff4e0 100755 --- a/apps/catalogue/models/book.py +++ b/apps/catalogue/models/book.py @@ -7,15 +7,19 @@ from django.contrib.sites.models import Site from django.db import models, transaction from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ +from django.conf import settings from slughifi import slughifi + import apiclient -from catalogue.helpers import cached_in_field +from catalogue.helpers import cached_in_field, GalleryMerger from catalogue.models import BookPublishRecord, ChunkPublishRecord from catalogue.signals import post_publish from catalogue.tasks import refresh_instance, book_content_updated from catalogue.xml_tools import compile_text, split_xml - +import os +import shutil +import re class Book(models.Model): """ A document edited on the wiki """ @@ -191,8 +195,19 @@ class Book(models.Model): chunk.save() number += 1 assert not other.chunk_set.exists() + + gm = GalleryMerger(self.gallery, other.gallery) + self.gallery = gm.merge() + + # and move the gallery starts + num_files = gm.dest_size + for chunk in self[len(self) - len_other:]: + chunk.gallery_start += gm.dest_size - gm.num_deleted + chunk.save() + other.delete() + @transaction.commit_on_success def prepend_history(self, other): """Prepend history from all the other book's chunks to own.""" diff --git a/apps/catalogue/templates/catalogue/base.html b/apps/catalogue/templates/catalogue/base.html index f3ebcd98..949b5f29 100644 --- a/apps/catalogue/templates/catalogue/base.html +++ b/apps/catalogue/templates/catalogue/base.html @@ -6,6 +6,7 @@ {% compressed_css 'catalogue' %} {% block title %}{% trans "Platforma Redakcyjna" %}{% endblock title %} + {% block add_css %}{% endblock %} @@ -42,8 +43,9 @@ - + {% compressed_js 'catalogue' %} +{% block add_js %}{% endblock %} {% block extrabody %} {% endblock %} diff --git a/apps/catalogue/templates/catalogue/book_list/book.html b/apps/catalogue/templates/catalogue/book_list/book.html index 46d5ae12..1e025e39 100755 --- a/apps/catalogue/templates/catalogue/book_list/book.html +++ b/apps/catalogue/templates/catalogue/book_list/book.html @@ -3,6 +3,7 @@ {% if book.single %} {% with book.0 as chunk %} + [B] [c] + [B] {{ book.title }} diff --git a/apps/catalogue/templates/catalogue/book_list/book_list.html b/apps/catalogue/templates/catalogue/book_list/book_list.html index 73811cab..c2f5e7c8 100755 --- a/apps/catalogue/templates/catalogue/book_list/book_list.html +++ b/apps/catalogue/templates/catalogue/book_list/book_list.html @@ -12,8 +12,10 @@ + + +
{% trans "No books found." %}

{% endif %} + + + + + diff --git a/apps/catalogue/templates/catalogue/book_list/chunk.html b/apps/catalogue/templates/catalogue/book_list/chunk.html index 14599428..fc893fdf 100755 --- a/apps/catalogue/templates/catalogue/book_list/chunk.html +++ b/apps/catalogue/templates/catalogue/book_list/chunk.html @@ -1,6 +1,7 @@ {% load i18n %}
[c] diff --git a/apps/catalogue/templates/catalogue/document_list.html b/apps/catalogue/templates/catalogue/document_list.html index 920f25a6..294c6299 100644 --- a/apps/catalogue/templates/catalogue/document_list.html +++ b/apps/catalogue/templates/catalogue/document_list.html @@ -2,8 +2,17 @@ {% load i18n %} {% load catalogue book_list %} +{% load compressed %} +{% block add_js %} +{% compressed_js 'book_list' %} +{% endblock %} + +{% block add_css %} +{% compressed_css 'book_list' %} +{% endblock %} + {% block content %} {% book_list %} {% endblock content %} diff --git a/apps/catalogue/templatetags/book_list.py b/apps/catalogue/templatetags/book_list.py index f7e70474..46cf87a4 100755 --- a/apps/catalogue/templatetags/book_list.py +++ b/apps/catalogue/templatetags/book_list.py @@ -125,9 +125,14 @@ def book_list(context, user=None): new_context = {"viewed_user": user} else: filters = {} - new_context = {"users": User.objects.annotate( + new_context = { + "users": User.objects.annotate( count=Count('chunk')).filter(count__gt=0).order_by( - '-count', 'last_name', 'first_name')} + '-count', 'last_name', 'first_name'), + "other_users": User.objects.annotate( + count=Count('chunk')).filter(count=0).order_by( + 'last_name', 'first_name'), + } new_context.update({ "filters": True, diff --git a/apps/catalogue/tests/__init__.py b/apps/catalogue/tests/__init__.py index 600dbd20..ac9298cd 100755 --- a/apps/catalogue/tests/__init__.py +++ b/apps/catalogue/tests/__init__.py @@ -1,10 +1,12 @@ -from os.path import abspath, dirname, join +from os.path import abspath, dirname, join, basename, exists +from os import makedirs, listdir from nose.tools import * from mock import patch from django.test import TestCase from django.contrib.auth.models import User from catalogue.models import Book, BookPublishRecord - +from tempfile import mkdtemp +from django.conf import settings def get_fixture(path): f_path = join(dirname(abspath(__file__)), 'files', path) @@ -82,3 +84,132 @@ class ManipulationTests(TestCase): self.assertEqual( Book.objects.get(slug='book3').materialize(), 'I survived!') + + +class GalleryAppendTests(TestCase): + def setUp(self): + self.user = User.objects.create(username='tester') + self.book1 = Book.create(self.user, 'book 1', slug='book1') + self.book1.chunk_set.create(number=2, title='Second chunk', + slug='book 1 / 2') + c=self.book1[0] + c.gallery_start=0 + c=self.book1[1] + c.gallery_start=2 + + self.scandir = join(settings.MEDIA_ROOT, settings.IMAGE_DIR) + if not exists(self.scandir): + makedirs(self.scandir) + + def make_gallery(self, book, files): + d = mkdtemp('gallery', dir=self.scandir) + for named, cont in files.items(): + f = open(join(d, named), 'w') + f.write(cont) + f.close() + book.gallery = basename(d) + + + def test_both_indexed(self): + self.book2 = Book.create(self.user, 'book 2', slug='book2') + self.book2.chunk_set.create(number=2, title='Second chunk of second book', + slug='book 2 / 2') + c = self.book2[0] + c.gallery_start = 0 + c = self.book2[1] + c.gallery_start = 2 + + self.make_gallery(self.book1, { + '1-0001_1l' : 'aa', + '1-0001_2r' : 'bb', + '1-0002_1l' : 'cc', + '1-0002_2r' : 'dd', + }) + + self.make_gallery(self.book2, { + '1-0001_1l' : 'dd', # the same, should not be moved + '1-0001_2r' : 'ff', + '2-0002_1l' : 'gg', + '2-0002_2r' : 'hh', + }) + + self.book1.append(self.book2) + + files = listdir(join(self.scandir, self.book1.gallery)) + self.assertEqual(files, [ + '1-0001_1l', + '1-0001_2r', + '1-0002_1l', + '1-0002_2r', + # '2-0001_1l', + '2-0001_2r', + '3-0002_1l', + '3-0002_2r', + ]) + + self.assertEqual((3, 5), (self.book1[2].gallery_start, self.book1[3].gallery_start)) + + + def test_none_indexed(self): + self.book2 = Book.create(self.user, 'book 2', slug='book2') + self.make_gallery(self.book1, { + '0001_1l' : 'aa', + '0001_2r' : 'bb', + '0002_1l' : 'cc', + '0002_2r' : 'dd', + }) + + self.make_gallery(self.book2, { + '0001_1l' : 'ee', + '0001_2r' : 'ff', + '0002_1l' : 'gg', + '0002_2r' : 'hh', + }) + + self.book1.append(self.book2) + + files = listdir(join(self.scandir, self.book1.gallery)) + print files + self.assertEqual(files, [ + '0-0001_1l', + '0-0001_2r', + '0-0002_1l', + '0-0002_2r', + '1-0001_1l', + '1-0001_2r', + '1-0002_1l', + '1-0002_2r', + ]) + + + def test_none_indexed(self): + import nose.tools + self.book2 = Book.create(self.user, 'book 2', slug='book2') + self.make_gallery(self.book1, { + '1-0001_1l' : 'aa', + '1-0001_2r' : 'bb', + '1002_1l' : 'cc', + '1002_2r' : 'dd', + }) + + self.make_gallery(self.book2, { + '0001_1l' : 'ee', + '0001_2r' : 'ff', + '0002_1l' : 'gg', + '0002_2r' : 'hh', + }) + + self.book1.append(self.book2) + + files = listdir(join(self.scandir, self.book1.gallery)) + print files + self.assertEqual(files, [ + '0-1-0001_1l', + '0-1-0001_2r', + '0-1002_1l', + '0-1002_2r', + '1-0001_1l', + '1-0001_2r', + '1-0002_1l', + '1-0002_2r', + ]) diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index 9bd56bbe..14e4a69a 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -39,6 +39,8 @@ urlpatterns = patterns('catalogue.views', 'chunk_edit', name="catalogue_chunk_edit"), url(r'^book_append/(?P[^/]+)/$', 'book_append', name="catalogue_book_append"), + url(r'^chunk_mass_edit', + 'chunk_mass_edit', name='catalogue_chunk_mass_edit'), url(r'^track/(?P[^/]*)/$', PublishTrackFeed()), ) diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index d0213daa..657a1257 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -10,6 +10,7 @@ from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required, permission_required from django.core.urlresolvers import reverse from django.db.models import Count, Q +from django.db import transaction from django import http from django.http import Http404, HttpResponse, HttpResponseForbidden from django.shortcuts import get_object_or_404, render, render_to_response @@ -387,6 +388,54 @@ def chunk_edit(request, slug, chunk): }) +@transaction.commit_on_success +def chunk_mass_edit(request): + if request.method == 'POST': + ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(','))) + chunks = map(lambda i: Chunk.objects.get(id=i), ids) + + stage = request.POST.get('stage') + if stage: + try: + stage = Chunk.tag_model.objects.get(slug=stage) + except Chunk.DoesNotExist, e: + stage = None + + for c in chunks: c.stage = stage + + username = request.POST.get('user') + logger.info("username: %s" % username) + logger.info(request.POST) + if username: + try: + user = User.objects.get(username=username) + except User.DoesNotExist, e: + user = None + + for c in chunks: c.user = user + + status = request.POST.get('status') + if status: + books_affected = set() + for c in chunks: + if status == 'publish': + c.head.publishable = True + c.head.save() + elif status == 'unpublish': + c.head.publishable = False + c.head.save() + c.touch() # cache + books_affected.add(c.book) + for b in books_affected: + b.touch() # cache + + for c in chunks: c.save() + + return HttpResponse("", content_type="text/plain") + else: + raise Http404 + + @permission_required('catalogue.change_book') def book_append(request, slug): book = get_object_or_404(Book, slug=slug) diff --git a/apps/wiki/templates/wiki/tabs/gallery_view.html b/apps/wiki/templates/wiki/tabs/gallery_view.html index 22f98c92..c62a3b51 100644 --- a/apps/wiki/templates/wiki/tabs/gallery_view.html +++ b/apps/wiki/templates/wiki/tabs/gallery_view.html @@ -4,7 +4,7 @@