Authors
-------
-* Łukasz Rekucki <lrekucki@gmail.com>
-* Marek Stępniowski <marek@stepniowski>
+* Łukasz Rekucki
+* Marek Stępniowski
+* Radek Czajka
+* Łukasz Anwajler
+* Marcin Koziej
+* Aleksander Łukasz
+* Jan Szejko
#. Przejdź do katalogu serwisu w konsoli
#. Zainstaluj wymagane biblioteki komendą::
- pip install -r requirements.txt
+ pip install -r requirements/requirements.txt
-#. Wypełnij bazę danych (Django poprosi o utworzenie pierwszego użytkownika)::
+#. Wypełnij bazę danych::
- ./project/manage.py syncdb
+ src/manage.py migrate
-#. Skopiuj zawartość pliku `project/localsettings.sample` do `project/localsettings.py` i zmień go zgodnie ze swoimi potrzebami.
+#. Skopiuj zawartość pliku `src/redakcja/localsettings.sample` do `src/redakcja/localsettings.py` i zmień go zgodnie ze swoimi potrzebami.
#. Uruchom serwer deweloperski::
- ./project/manage.py runserver
+ src/manage.py runserver
-#. Zalecane jest serwowanie aplikacji przez `modwsgi <http://code.google.com/p/modwsgi/>`_ na serwerze `Apache2 <http://httpd.apache.org/>`_ przy pomocy załączonego skryptu `dispatch.fcgi`. Inne strategie wdrożeniowe opisane są w `Dokumentacji Django <http://docs.djangoproject.com/en/dev/howto/deployment/#howto-deployment-index>`_.
+#. Strategie wdrożeniowe opisane są w `Dokumentacji Django <http://docs.djangoproject.com/en/dev/howto/deployment/#howto-deployment-index>`_.
-Wdrożenie
-=========
-#. Ściągnij i zainstaluj `fabric <http://docs.fabfile.org/>`_
-#. Przejdź do katalogu serwisu w konsoli
-#. Aby wdrożyć serwis na serwer deweloperski wpisz komendę::
-
- fab staging deploy
-
- Aby wdrożyć serwis na serwer produkcyjny wpisz::
-
- fab production deploy
Testy
====
$ pip install -r requirements-test.txt
- $ python src/manage.py test --settings=redakcja.settings.test
+ $ make test
JavaScript (wymagany node.js i xsltproc):
+++ /dev/null
-; =======================================
-; celeryd supervisor example for Django
-; =======================================
-
-[program:celery]
-command=$APP_DIR/redakcja/manage.py celeryd --loglevel=INFO
-directory=$APP_DIR/redakcja
-user=nobody
-numprocs=2
-stdout_logfile=$APP_DIR/log/celeryd.log
-stderr_logfile=$APP_DIR/log/celeryd.log
-autostart=true
-autorestart=true
-startsecs=10
-
-; Need to wait for currently executing tasks to finish at shutdown.
-; Increase this if you have very long running tasks.
-stopwaitsecs = 600
-
-; if rabbitmq is supervised, set its priority higher
-; so it starts first
-priority=998
python-slugify
django-extensions==1.5.7
-celery>=3.1.12,<3.2
-kombu>=3.0,<3.1
-
raven
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]),
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-import csv
-from optparse import make_option
-import re
-import sys
-import urllib
-import urllib2
-
-from django.contrib.auth.models import User
-from django.core.management.base import BaseCommand
-from django.core.management.color import color_style
-from django.db import transaction
-
-from slugify import slugify
-from catalogue.models import Chunk
-
-
-REDMINE_CSV = 'http://redmine.nowoczesnapolska.org.pl/projects/wl-publikacje/issues.csv'
-REDAKCJA_URL = 'http://redakcja.wolnelektury.pl/documents/'
-
-
-class Command(BaseCommand):
- option_list = BaseCommand.option_list + (
- make_option('-r', '--redakcja', dest='redakcja', metavar='URL',
- help='Base URL of Redakcja documents',
- default=REDAKCJA_URL),
- make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
- help='Less output'),
- make_option('-f', '--force', action='store_true', dest='force', default=False,
- help='Force assignment overwrite'),
- )
- help = 'Imports ticket assignments from Redmine.'
- args = '[redmine-csv-url]'
-
- def handle(self, *redmine_csv, **options):
-
- self.style = color_style()
-
- redakcja = options.get('redakcja')
- verbose = options.get('verbose')
- force = options.get('force')
-
- if not redmine_csv:
- if verbose:
- print "Using default Redmine CSV URL:", REDMINE_CSV
- redmine_csv = REDMINE_CSV
-
- # Start transaction management.
- transaction.enter_transaction_management()
-
- redakcja_link = re.compile(re.escape(redakcja) + r'([-_.:?&%/a-zA-Z0-9]*)')
-
- all_tickets = 0
- all_chunks = 0
- done_tickets = 0
- done_chunks = 0
- empty_users = 0
- unknown_users = {}
- unknown_books = []
- forced = []
-
- if verbose:
- print 'Downloading CSV file'
- for r in csv.reader(urllib2.urlopen(redmine_csv)):
- if r[0] == '#':
- continue
- all_tickets += 1
-
- username = r[6]
- if not username:
- if verbose:
- print "Empty user, skipping"
- empty_users += 1
- continue
-
- first_name, last_name = unicode(username, 'utf-8').rsplit(u' ', 1)
- try:
- user = User.objects.get(first_name=first_name, last_name=last_name)
- except User.DoesNotExist:
- print self.style.ERROR('Unknown user: ' + username)
- unknown_users.setdefault(username, 0)
- unknown_users[username] += 1
- continue
-
- ticket_done = False
- for fname in redakcja_link.findall(r[-1]):
- fname = unicode(urllib.unquote(fname), 'utf-8', 'ignore')
- if fname.endswith('.xml'):
- fname = fname[:-4]
- fname = fname.replace(' ', '_')
- fname = slugify(fname)
-
- chunks = Chunk.objects.filter(book__slug=fname)
- if not chunks:
- print self.style.ERROR('Unknown book: ' + fname)
- unknown_books.append(fname)
- continue
- all_chunks += chunks.count()
-
- for chunk in chunks:
- if chunk.user:
- if chunk.user == user:
- continue
- else:
- forced.append((chunk, chunk.user, user))
- if force:
- print self.style.WARNING(
- '%s assigned to %s, forcing change to %s.' %
- (chunk.pretty_name(), chunk.user, user))
- else:
- print self.style.WARNING(
- '%s assigned to %s not to %s, skipping.' %
- (chunk.pretty_name(), chunk.user, user))
- continue
- chunk.user = user
- chunk.save()
- ticket_done = True
- done_chunks += 1
-
- if ticket_done:
- done_tickets += 1
-
-
- # Print results
- print
- print "Results:"
- print "Assignments imported from %d/%d tickets to %d/%d relevalt chunks." % (
- done_tickets, all_tickets, done_chunks, all_chunks)
- if empty_users:
- print "%d tickets were unassigned." % empty_users
- if forced:
- print "%d assignments conficts (%s):" % (
- len(forced), "changed" if force else "left")
- for chunk, orig, user in forced:
- print " %s: \t%s \t-> %s" % (
- chunk.pretty_name(), orig.username, user.username)
- if unknown_books:
- print "%d unknown books:" % len(unknown_books)
- for fname in unknown_books:
- print " %s" % fname
- if unknown_users:
- print "%d unknown users:" % len(unknown_users)
- for name in unknown_users:
- print " %s (%d tickets)" % (name, unknown_users[name])
- print
-
-
- transaction.commit()
- transaction.leave_transaction_management()
-
--- /dev/null
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-03-07 15:43
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='book',
+ name='_short_html',
+ ),
+ migrations.RemoveField(
+ model_name='chunk',
+ name='_short_html',
+ ),
+ migrations.RemoveField(
+ model_name='image',
+ name='_short_html',
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-03-07 15:48
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0002_auto_20190307_1543'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='chunk',
+ name='_new_publishable',
+ field=models.NullBooleanField(editable=False),
+ ),
+ ]
from catalogue.helpers import cached_in_field, GalleryMerger
from catalogue.models import BookPublishRecord, ChunkPublishRecord, Project
from catalogue.signals import post_publish
-from catalogue.tasks import refresh_instance, book_content_updated
from catalogue.xml_tools import compile_text, split_xml
from cover.models import Image
import os
parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True, editable=False)
# Cache
- _short_html = models.TextField(null=True, blank=True, editable=False)
_single = models.NullBooleanField(editable=False, db_index=True)
_new_publishable = models.NullBooleanField(editable=False)
_published = models.NullBooleanField(editable=False)
return len(self) == 1
single = cached_in_field('_single')(is_single)
- @cached_in_field('_short_html')
- def short_html(self):
- return render_to_string('catalogue/book_list/book.html', {'book': self})
-
def book_info(self, publishable=True):
try:
book_xml = self.materialize(publishable=publishable)
Book.objects.filter(pk=self.pk).update(**update)
def touch(self):
- # this should only really be done when text or publishable status changes
- book_content_updated.delay(self)
-
update = {
"_new_publishable": self.is_new_publishable(),
"_published": self.is_published(),
"_single": self.is_single(),
"_on_track": self.get_on_track(),
- "_short_html": None,
}
Book.objects.filter(pk=self.pk).update(**update)
- refresh_instance(self)
-
- def refresh(self):
- """This should be done offline."""
- self.short_html
- self.single
- self.new_publishable
- self.published
+ self.refresh_dc_cache()
# Materializing & publishing
# ==========================
from django.utils.translation import ugettext_lazy as _
from catalogue.helpers import cached_in_field
from catalogue.managers import VisibleManager
-from catalogue.tasks import refresh_instance
from dvcs import models as dvcs_models
gallery_start = models.IntegerField(_('gallery start'), null=True, blank=True, default=1)
# cache
- _short_html = models.TextField(null=True, blank=True, editable=False)
_hidden = models.NullBooleanField(editable=False)
_changed = models.NullBooleanField(editable=False)
+ _new_publishable = models.NullBooleanField(editable=False)
# managers
objects = models.Manager()
# State & cache
# =============
- def new_publishable(self):
+ def is_new_publishable(self):
change = self.publishable()
if not change:
return False
return not change.publish_log.exists()
+ new_publishable = cached_in_field('_new_publishable')(is_new_publishable)
def is_changed(self):
if self.head is None:
return self.book.hidden()
hidden = cached_in_field('_hidden')(is_hidden)
- @cached_in_field('_short_html')
- def short_html(self):
- return render_to_string(
- 'catalogue/book_list/chunk.html', {'chunk': self})
-
def touch(self):
update = {
"_changed": self.is_changed(),
+ "_new_publishable": self.is_new_publishable(),
"_hidden": self.is_hidden(),
- "_short_html": None,
}
Chunk.objects.filter(pk=self.pk).update(**update)
- refresh_instance(self)
-
- def refresh(self):
- """This should be done offline."""
- self.changed
- self.hidden
- self.short_html
from django.utils.translation import ugettext_lazy as _
from catalogue.helpers import cached_in_field
from catalogue.models import Project
-from catalogue.tasks import refresh_instance
from dvcs import models as dvcs_models
project = models.ForeignKey(Project, null=True, blank=True)
# cache
- _short_html = models.TextField(null=True, blank=True, editable=False)
_new_publishable = models.NullBooleanField(editable=False)
_published = models.NullBooleanField(editable=False)
_changed = models.NullBooleanField(editable=False)
return not self.head.publishable
changed = cached_in_field('_changed')(is_changed)
- @cached_in_field('_short_html')
- def short_html(self):
- return render_to_string(
- 'catalogue/image_short.html', {'image': self})
-
- def refresh(self):
- """This should be done offline."""
- self.short_html
- self.single
- self.new_publishable
- self.published
-
def touch(self):
update = {
"_changed": self.is_changed(),
- "_short_html": None,
"_new_publishable": self.is_new_publishable(),
"_published": self.is_published(),
}
Image.objects.filter(pk=self.pk).update(**update)
- refresh_instance(self)
-
- def refresh(self):
- """This should be done offline."""
- self.changed
- self.short_html
-
# Publishing
# ==========
models.signals.post_save.connect(image_changed, sender=Image)
-def user_changed(sender, instance, *args, **kwargs):
- if 'last_login' in (kwargs.get('update_fields') or {}):
- # Quick hack - this change seems to result from logging user in so just ignore it.
- return
- books = set()
- for c in instance.chunk_set.all():
- books.add(c.book)
- c.touch()
- for b in books:
- b.touch()
-models.signals.post_save.connect(user_changed, sender=User)
-
-
def publish_listener(sender, *args, **kwargs):
if isinstance(sender, BookPublishRecord):
sender.book.touch()
+++ /dev/null
-# -*- coding: utf-8 -*-
-from celery.task import task
-from django.utils import translation
-
-
-@task(ignore_result=True)
-def _refresh_by_pk(cls, pk, language=None):
- prev_language = translation.get_language()
- if language:
- translation.activate(language)
- try:
- cls._default_manager.get(pk=pk).refresh()
- except cls.DoesNotExist:
- pass
- finally:
- translation.activate(prev_language)
-
-
-def refresh_instance(instance):
- _refresh_by_pk.delay(type(instance), instance.pk, translation.get_language())
-
-
-@task(ignore_result=True)
-def book_content_updated(book):
- book.refresh_dc_cache()
<table class='single-book-list'><tbody>
{% for chunk in book %}
- {{ chunk.short_html|safe }}
+ {% include 'catalogue/book_list/chunk.html' %}
{% endfor %}
</tbody></table>
{% load username from common_tags %}
{% if book.single %}
- {% with book.0 as chunk %}
+ {% with chunk as chunk %}
<tr>
<td><input type="checkbox" name="select_book" value="{{book.id}}" data-chunk-id="{{chunk.id}}"/></td>
<td><a href="{% url 'catalogue_book' book.slug %}" title='{% trans "Book settings" %}'>[B]</a></td>
<thead><tr>
<th></th>
<th></th>
- <th>
- <input class='check-filter' type='checkbox' name='all' title='{% trans "Show hidden books" %}'
- {% if request.GET.all %}checked='checked'{% endif %} />
- </th>
+ <th></th>
<th class='book-search-column'>
<form>
<input title='{% trans "Search in book titles" %}' name="title"
{% autopaginate books 100 %}
<tbody>
{% for item in books %}
- {% with item.book as book %}
- {{ book.short_html|safe }}
+ {% with book=item.book chunk=item.chunks.0 %}
+ {% include 'catalogue/book_list/book.html' %}
{% if not book.single %}
{% for chunk in item.chunks %}
- {{ chunk.short_html|safe }}
+ {% include 'catalogue/book_list/chunk.html' %}
{% endfor %}
{% endif %}
{% endwith %}
{% with cnt=objects|length %}
{% autopaginate objects 100 %}
<tbody>
- {% for item in objects %}
- {{ item.short_html|safe }}
+ {% for image in objects %}
+ {% include 'catalogue/image_short.html' %}
{% endfor %}
<tr><th class='paginator' colspan="6">
{% paginate %}
class ChunksList(object):
def __init__(self, chunk_qs):
- self.chunk_qs = chunk_qs.select_related('book')
+ self.chunk_qs = chunk_qs.select_related('book', 'book__project', 'stage', 'user')
self.book_qs = chunk_qs.values('book_id')
def __getitem__(self, key):
def arg_or_GET(field):
return kwargs.get(field, request.GET.get(field))
- images = Image.objects.all()
+ images = Image.objects.all().select_related('user', 'stage', 'project')
if not request.user.is_authenticated():
images = images.filter(public=True)
c.gallery_start = 3
c.save()
- print "gallery starts:",self.book2[0].gallery_start, self.book2[1].gallery_start
-
self.make_gallery(self.book1, {
'1-0001_1l' : 'aa',
'1-0001_2r' : 'bb',
files = listdir(join(self.scandir, self.book1.gallery))
files.sort()
- print files
self.assertEqual(files, [
'1-0001_1l',
'1-0001_2r',
files = listdir(join(self.scandir, self.book1.gallery))
files.sort()
- print files
self.assertEqual(files, [
'0-0001_1l',
'0-0001_2r',
files = listdir(join(self.scandir, self.book1.gallery))
files.sort()
- print files
self.assertEqual(files, [
'0-1-0001_1l',
'0-1-0001_2r',
-from __future__ import absolute_import
-
-# This will make sure the app is always imported when
-# Django starts so that shared_task will use this app.
-from .celery import app as celery_app
+++ /dev/null
-from __future__ import absolute_import
-
-import os
-import sys
-
-ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-sys.path = [
- os.path.join(ROOT, 'apps'),
- os.path.join(ROOT, 'lib'),
- os.path.join(ROOT, 'lib/librarian'),
-] + sys.path
-
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'redakcja.localsettings')
-
-from celery import Celery
-from django.conf import settings
-
-app = Celery('redakcja')
-
-# Using a string here means the worker will not have to
-# pickle the object when using Windows.
-app.config_from_object('django.conf:settings')
-app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
APICLIENT_WL_CONSUMER_KEY = None
APICLIENT_WL_CONSUMER_SECRET = None
-CELERY_ALWAYS_EAGER = False
-
SECRET_KEY = ''
'fnp_django_pagination',
'django_gravatar',
'fileupload',
- 'kombu.transport.django',
'pipeline',
'fnpdjango',
LOGIN_REDIRECT_URL = '/documents/user'
-CELERY_EAGER_PROPAGATES_EXCEPTIONS = True
-CELERY_SEND_TASK_ERROR_EMAILS = True
-CELERY_ACCEPT_CONTENT = ['pickle'] # Remove when all tasks jsonable.
-
MIN_COVER_SIZE = (915, 1270)
STATICFILES_FINDERS = (
CATALOGUE_REPO_PATH = tempfile.mkdtemp(prefix='redakcja-repo')
CATALOGUE_IMAGE_REPO_PATH = tempfile.mkdtemp(prefix='redakcja-repo-img')
MEDIA_ROOT = tempfile.mkdtemp(prefix='media-root')
-CELERY_ALWAYS_EAGER = True
INSTALLED_APPS += ('dvcs.tests',)
{% endblock %}
{% block tabs-menu %}
- {% include "wiki_img/tabs/summary_view_item.html" %}
+ {#% include "wiki_img/tabs/summary_view_item.html" %#}
{% include "wiki_img/tabs/motifs_editor_item.html" %}
{% include "wiki_img/tabs/objects_editor_item.html" %}
{% include "wiki_img/tabs/source_editor_item.html" %}
{% endblock %}
{% block tabs-content %}
- {% include "wiki_img/tabs/summary_view.html" %}
+ {#% include "wiki_img/tabs/summary_view.html" %#}
{% include "wiki_img/tabs/motifs_editor.html" %}
{% include "wiki_img/tabs/objects_editor.html" %}
{% include "wiki_img/tabs/source_editor.html" %}
+++ /dev/null
-{% load i18n %}
-{% load wiki %}
-<div id="summary-view-editor" class="editor" style="display: none">
- <!-- <div class="toolbar">
- </div> -->
- <div id="summary-view">
- <h2>
- <label for="title">{% trans "Title" %}:</label>
- <span data-ui-editable="true" data-edit-target="meta.displayTitle"
- >{{ document.name|wiki_title }}</span>
- </h2>
- <p>
- <label>{% trans "Document ID" %}:</label>
- <span>{{ document.name }}</span>
- </p>
- <p>
- <label>{% trans "Current version" %}:</label>
- {{ document_info.revision }} ({{document_info.date}})
- <p>
- <label>{% trans "Last edited by" %}:</label>
- {{document_info.author}}
- </p>
- </div>
-</div>
+++ /dev/null
-{% load i18n %}
-{% load wiki %}
-<li id="SummaryPerspective" data-ui-related="summary-view-editor" data-ui-jsclass="SummaryPerspective">
- <a href="#">{% trans "Summary" %}</a>
-</li>