some moving works
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 9 Jun 2011 08:51:14 +0000 (10:51 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 9 Jun 2011 08:51:14 +0000 (10:51 +0200)
32 files changed:
.gitignore [new file with mode: 0755]
apps/archive/__init__.py [new file with mode: 0644]
apps/archive/admin.py [new file with mode: 0644]
apps/archive/forms.py [new file with mode: 0755]
apps/archive/locale/pl/LC_MESSAGES/django.mo [new file with mode: 0644]
apps/archive/locale/pl/LC_MESSAGES/django.po [new file with mode: 0644]
apps/archive/migrations/0001_initial.py [new file with mode: 0644]
apps/archive/migrations/__init__.py [new file with mode: 0644]
apps/archive/models.py [new file with mode: 0644]
apps/archive/settings.py [new file with mode: 0755]
apps/archive/static/style.css [new file with mode: 0755]
apps/archive/templates/archive/base.html [new file with mode: 0644]
apps/archive/templates/archive/file_managed.html [new file with mode: 0755]
apps/archive/templates/archive/file_new.html [new file with mode: 0644]
apps/archive/templates/archive/file_unmanaged.html [new file with mode: 0755]
apps/archive/templates/archive/list.html [new file with mode: 0644]
apps/archive/templates/archive/list_new.html [new file with mode: 0644]
apps/archive/templates/archive/list_published.html [new file with mode: 0755]
apps/archive/templates/archive/list_unmanaged.html [new file with mode: 0755]
apps/archive/templates/archive/list_unpublished.html [new file with mode: 0755]
apps/archive/templates/archive/tags/multiple_tags_table.html [new file with mode: 0755]
apps/archive/templatetags/__init__.py [new file with mode: 0755]
apps/archive/templatetags/tags.py [new file with mode: 0755]
apps/archive/tests.py [new file with mode: 0644]
apps/archive/urls.py [new file with mode: 0644]
apps/archive/utils.py [new file with mode: 0755]
apps/archive/views.py [new file with mode: 0644]
audiobooks/__init__.py [new file with mode: 0644]
audiobooks/manage.py [new file with mode: 0755]
audiobooks/settings.py [new file with mode: 0644]
audiobooks/urls.py [new file with mode: 0644]
requirements.txt [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100755 (executable)
index 0000000..f1e760b
--- /dev/null
@@ -0,0 +1,3 @@
+*.pyc
+*.sqlite
+localsettings.py
diff --git a/apps/archive/__init__.py b/apps/archive/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/archive/admin.py b/apps/archive/admin.py
new file mode 100644 (file)
index 0000000..d0804be
--- /dev/null
@@ -0,0 +1,5 @@
+from archive.models import Project, Audiobook
+from django.contrib import admin
+
+admin.site.register(Project)
+admin.site.register(Audiobook)
diff --git a/apps/archive/forms.py b/apps/archive/forms.py
new file mode 100755 (executable)
index 0000000..772ec0a
--- /dev/null
@@ -0,0 +1,30 @@
+import os
+import os.path
+from datetime import datetime
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+import mutagen
+
+from archive.models import Audiobook
+from archive.settings import FILES_PATH
+from archive.utils import ExistingFile
+
+class AudiobookForm(forms.ModelForm):
+    class Meta:
+        model = Audiobook
+
+    def save(self, commit=True, path=None):
+        m = super(AudiobookForm, self).save(commit=False)
+        m.modified = datetime.now()
+
+        if path:
+            # adding a new audiobook
+            if not os.path.isdir(FILES_PATH):
+                os.makedirs(FILES_PATH)
+            # save the file in model
+            m.source_file.save(os.path.basename(path), ExistingFile(path))
+
+        if commit:
+            m.save()
+
diff --git a/apps/archive/locale/pl/LC_MESSAGES/django.mo b/apps/archive/locale/pl/LC_MESSAGES/django.mo
new file mode 100644 (file)
index 0000000..638fe28
Binary files /dev/null and b/apps/archive/locale/pl/LC_MESSAGES/django.mo differ
diff --git a/apps/archive/locale/pl/LC_MESSAGES/django.po b/apps/archive/locale/pl/LC_MESSAGES/django.po
new file mode 100644 (file)
index 0000000..a4a6ca4
--- /dev/null
@@ -0,0 +1,145 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2011 Fundacja Nowoczesna Polska
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-05-17 12:35+0200\n"
+"PO-Revision-Date: 2011-05-17 12:37+0100\n"
+"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+
+#: models.py:15
+#: models.py:32
+msgid "project"
+msgstr "projekt"
+
+#: models.py:16
+msgid "projects"
+msgstr "projekty"
+
+#: models.py:24
+msgid "source file"
+msgstr "plik źródłowy"
+
+#: models.py:26
+msgid "title"
+msgstr "tytuł"
+
+#: models.py:27
+msgid "artist"
+msgstr "lektor"
+
+#: models.py:28
+msgid "arranger"
+msgstr "aranżer"
+
+#: models.py:29
+msgid "encoded by"
+msgstr "przyg. techn."
+
+#: models.py:30
+msgid "date"
+msgstr "data"
+
+#: models.py:33
+msgid "book url"
+msgstr "URL książki"
+
+#: models.py:46
+msgid "audiobook"
+msgstr "audiobook"
+
+#: models.py:47
+msgid "audiobooks"
+msgstr "audiobooki"
+
+#: templates/archive/base.html:7
+msgid "Audiobook repository"
+msgstr "Repozytorium audiobooków"
+
+#: templates/archive/base.html:12
+msgid "New"
+msgstr "Nowe"
+
+#: templates/archive/base.html:13
+msgid "Unpublished"
+msgstr "Nie opublikowane"
+
+#: templates/archive/base.html:14
+msgid "Published"
+msgstr "Opublikowane"
+
+#: templates/archive/base.html:15
+msgid "Archive"
+msgstr "Archiwum"
+
+#: templates/archive/file_managed.html:6
+msgid "Publishing"
+msgstr "Publikacja"
+
+#: templates/archive/file_managed.html:9
+msgid "Audiobook marked for publishing with tags:"
+msgstr "Audiobook zaznaczony do publikacji z tagami:"
+
+#: templates/archive/file_managed.html:18
+msgid "Publishing already in progress."
+msgstr "Publikowanie rozpoczęte."
+
+#: templates/archive/file_managed.html:22
+msgid "Cancel publishing"
+msgstr "Anuluj publikację"
+
+#: templates/archive/file_managed.html:37
+msgid "Publish"
+msgstr "Opublikuj"
+
+#: templates/archive/file_managed.html:50
+msgid "Update tags"
+msgstr "Uaktualnij tagi"
+
+#: templates/archive/file_managed.html:69
+#: templates/archive/file_new.html:19
+msgid "Commit"
+msgstr "Zatwierdź"
+
+#: templates/archive/file_new.html:8
+msgid "Move to archive"
+msgstr "Przenieś do archiwum"
+
+#: templates/archive/file_unmanaged.html:6
+msgid "File with same name already exists!"
+msgstr "Plik o tej nazwie już istnieje!"
+
+#: templates/archive/file_unmanaged.html:28
+msgid "Move to new files"
+msgstr "Przenieś do nowych plików"
+
+#: templates/archive/list_new.html:6
+msgid "New audiobooks"
+msgstr "Nowe audiobooki"
+
+#: templates/archive/list_new.html:11
+msgid "Put source audiobooks in:"
+msgstr "Umieść nowe audiobooki w:"
+
+#: templates/archive/list_published.html:6
+msgid "Published audiobooks"
+msgstr "Opublikowane audiobooki"
+
+#: templates/archive/list_unmanaged.html:5
+msgid "Unmanaged archive"
+msgstr "Audiobooki archiwalne"
+
+#: templates/archive/list_unpublished.html:6
+msgid "Unpublished audiobooks"
+msgstr "Nie opublikowane audiobooki"
+
diff --git a/apps/archive/migrations/0001_initial.py b/apps/archive/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..ac5a2c9
--- /dev/null
@@ -0,0 +1,80 @@
+# 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 'Project'
+        db.create_table('archive_project', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128, db_index=True)),
+            ('sponsors', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+        ))
+        db.send_create_signal('archive', ['Project'])
+
+        # Adding model 'Audiobook'
+        db.create_table('archive_audiobook', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('source_file', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('artist', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('conductor', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('encoded_by', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('date', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['archive.Project'])),
+            ('url', self.gf('django.db.models.fields.URLField')(max_length=255)),
+            ('modified', self.gf('django.db.models.fields.DateTimeField')(null=True)),
+            ('published_tags', self.gf('jsonfield.fields.JSONField')(null=True)),
+            ('mp3_file', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True)),
+            ('ogg_file', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True)),
+            ('publishing_tags', self.gf('jsonfield.fields.JSONField')(null=True)),
+            ('publish_wait', self.gf('django.db.models.fields.DateTimeField')(null=True)),
+            ('publishing', self.gf('django.db.models.fields.BooleanField')(default=False)),
+            ('published', self.gf('django.db.models.fields.DateTimeField')(null=True)),
+        ))
+        db.send_create_signal('archive', ['Audiobook'])
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'Project'
+        db.delete_table('archive_project')
+
+        # Deleting model 'Audiobook'
+        db.delete_table('archive_audiobook')
+
+
+    models = {
+        'archive.audiobook': {
+            'Meta': {'ordering': "('title',)", 'object_name': 'Audiobook'},
+            'artist': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'conductor': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'date': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'encoded_by': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'mp3_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+            'ogg_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['archive.Project']"}),
+            'publish_wait': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'published': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'published_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+            'publishing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'publishing_tags': ('jsonfield.fields.JSONField', [], {'null': 'True'}),
+            'source_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '255'})
+        },
+        'archive.project': {
+            'Meta': {'ordering': "('name',)", 'object_name': 'Project'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
+            'sponsors': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['archive']
diff --git a/apps/archive/migrations/__init__.py b/apps/archive/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/archive/models.py b/apps/archive/models.py
new file mode 100644 (file)
index 0000000..81a8594
--- /dev/null
@@ -0,0 +1,56 @@
+from django.db import models
+from jsonfield.fields import JSONField
+from django.utils.translation import ugettext_lazy as _
+
+# Create your models here.
+
+
+class Project(models.Model):
+    """ an audiobook project, needed for specyfing sponsors """
+
+    name = models.CharField(max_length=128, unique=True, db_index=True, verbose_name="Nazwa")
+    sponsors = models.TextField(blank=True, null=True, verbose_name="Sponsorzy")
+
+    class Meta:
+        verbose_name = _("project")
+        verbose_name_plural = _("projects")
+        ordering = ("name",)
+
+    def __unicode__(self):
+        return self.name
+
+
+class Audiobook(models.Model):
+    source_file = models.FileField(upload_to='archive/files', verbose_name=_('source file'), editable=False)
+
+    title = models.CharField(max_length=255, verbose_name=_('title'))
+    artist = models.CharField(max_length=255, verbose_name=_('artist'))
+    conductor = models.CharField(max_length=255, verbose_name=_('conductor'))
+    encoded_by = models.CharField(max_length=255, verbose_name=_('encoded by'))
+    date = models.CharField(max_length=255, verbose_name=_('date'))
+    project = models.ForeignKey(Project, verbose_name=_('project'))
+    url = models.URLField(max_length=255, verbose_name=_('book url'))
+    modified = models.DateTimeField(null=True, editable=False)
+
+    published_tags = JSONField(null=True, editable=False)
+    mp3_file = models.FileField(null=True, upload_to='archive/final', editable=False)
+    ogg_file = models.FileField(null=True, upload_to='archive/final', editable=False)
+    publishing_tags = JSONField(null=True, editable=False)
+
+    publish_wait = models.DateTimeField(null=True, editable=False) # somebody hit "publish"
+    publishing = models.BooleanField(default=False, editable=False)
+    published = models.DateTimeField(null=True, editable=False)
+
+    class Meta:
+        verbose_name = _("audiobook")
+        verbose_name_plural = _("audiobooks")
+        ordering = ("title",)
+
+    def __unicode__(self):
+        return self.title
+
+    def new_publish_tags(self):
+        return {
+            'title': self.title,
+            'copyright': 'Fundacja Nowoczesna Polska',
+        }
diff --git a/apps/archive/settings.py b/apps/archive/settings.py
new file mode 100755 (executable)
index 0000000..ea42c15
--- /dev/null
@@ -0,0 +1,32 @@
+import os.path
+from django.conf import settings
+
+# this is where the end user puts new files
+try:
+    NEW_PATH = settings.ARCHIVE_NEW_PATH
+except AttributeError:
+    NEW_FILES_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT,
+                        "archive/new"))
+
+# here the application keeps its managed files
+try:
+    FILES_PATH = settings.ARCHIVE_FILES_PATH
+except AttributeError:
+    FILES_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT,
+                        "archive/files"))
+
+
+# here the app keeps the unmanaged (archive) files
+try:
+    UNMANAGED_PATH = settings.ARCHIVE_UNMANAGED_PATH
+except AttributeError:
+    UNMANAGED_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT,
+                        "archive/unmanaged"))
+
+
+# here the app keeps the resulting (published) files
+try:
+    FINAL_PATH = settings.ARCHIVE_FINAL_PATH
+except AttributeError:
+    FINAL_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT,
+                        "archive/final"))
diff --git a/apps/archive/static/style.css b/apps/archive/static/style.css
new file mode 100755 (executable)
index 0000000..0d30ad6
--- /dev/null
@@ -0,0 +1,49 @@
+body {
+    margin: 0;
+    font-family: verdana, sans-serif;
+    font-size: 12px;
+}
+
+a {
+    color: #bf6000;
+    text-decoration: none;
+}
+
+.clr {
+    clear: both;
+}
+
+
+#repo-zones-nav {
+    padding: 5px 5px 0 10px;
+    background: #ffdfbf;
+    border-bottom: 1px solid #ff8000;
+}
+
+#repo-zones-nav a {
+    display: block;
+    float: left;
+    padding: 5px 20px 5px 20px;
+    margin-bottom: -1px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: rgba(0,0,0,0);
+}
+
+#repo-zones-nav .active {
+    background: white;
+    border-color: #ff8000 #ff8000 white #ff8000;
+}
+
+#content {
+    padding: 10px;
+}
+
+
+.errorlist {
+    margin: 0;
+}
+
+.errorlist li {
+    color: red;
+}
diff --git a/apps/archive/templates/archive/base.html b/apps/archive/templates/archive/base.html
new file mode 100644 (file)
index 0000000..807227b
--- /dev/null
@@ -0,0 +1,28 @@
+{% load i18n %}
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+    <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}style.css" />
+    <title>{% trans "Audiobook repository" %}</title>
+</head>
+<body>
+
+<div id="repo-zones-nav">
+    <a {% if division = "new" %}class="active" {% endif %}href="{% url list_new %}">{% trans "New" %}</a>
+    <a {% if division = "unpublished" %}class="active" {% endif %}href="{% url list_unpublished %}">{% trans "Unpublished" %}</a>
+    <a {% if division = "published" %}class="active" {% endif %}href="{% url list_published %}">{% trans "Published" %}</a>
+    <a {% if division = "unmanaged" %}class="active" {% endif %}href="{% url list_unmanaged %}">{% trans "Archive" %}</a>
+    <div class='clr' ></div>
+</div>
+
+<div id="messages">
+{% block messages %}{% endblock %}
+</div>
+
+<div id="content">
+{% block content %}{% endblock %}
+</div>
+
+</body>
+</html>
diff --git a/apps/archive/templates/archive/file_managed.html b/apps/archive/templates/archive/file_managed.html
new file mode 100755 (executable)
index 0000000..897aae2
--- /dev/null
@@ -0,0 +1,76 @@
+{% extends "archive/base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+<h2>{% trans "Publishing" %}</h2>
+
+{% if audiobook.publish_wait %}
+    <p>{% trans "Audiobook marked for publishing with tags:" %}</p>
+
+    <table>
+        {% for t, v in audiobook.publishing_tags.items %}
+            <tr><th>{{ t }}</th><td>{{ v }}</td></tr>
+        {% endfor %}
+    </table>
+
+    {% if audiobook.publishing %}
+        <p>{% trans "Publishing already in progress." %}</p>
+    {% else %}
+        <form method="post" action="{% url cancel_publishing audiobook.id %}">
+            {% csrf_token %}
+            <input type="submit" value="{% trans "Cancel publishing" %}" />
+        </form>
+    {% endif %}
+{% else %}
+    {% if audiobook.published %}
+        Here be currently published version, for comparison.
+    {% endif %}
+
+    <form method="post" action="{% url publish audiobook.id %}">
+    <table>
+        {% for k, v in audiobook.new_publish_tags.items %}
+            <tr><th>{{ k }}</th><td>{{ v }}</td></tr>
+        {% endfor %}
+
+        {% csrf_token %}
+        <tr><th></th><td><input type="submit" value="{% trans "Publish" %}" /></td></tr>
+    </table>
+    </form>
+{% endif %}
+
+
+
+
+<hr />
+
+
+
+
+<h2>{% trans "Update tags" %}</h2>
+
+
+<table class="file_tags">
+    {% for t, v in tags.items %}
+        <tr><th>{{t}}</th><td>
+        {% for x in v %}
+            {{x}}<br />
+        {% endfor %}
+        </td></tr>
+    {% endfor %}
+</table>
+
+
+
+<form method='post' action='.'>
+    {% csrf_token %}
+    <table>
+        {{ form.as_table }}
+        <td></td><td><input type="submit" value='{% trans "Commit" %}' /></td></td>
+    </table>
+</form>
+
+
+
+
+{% endblock %}
diff --git a/apps/archive/templates/archive/file_new.html b/apps/archive/templates/archive/file_new.html
new file mode 100644 (file)
index 0000000..466c576
--- /dev/null
@@ -0,0 +1,23 @@
+{% extends "archive/base.html" %}
+{% load i18n tags %}
+
+{% block content %}
+
+<form method="post" action="{% url move_to_archive filename %}">
+    {% csrf_token %}
+    <input type="submit" value="{% trans "Move to archive" %}" />
+</form>
+
+
+{% multiple_tags_table tags %}
+
+
+<form method='post' action='.'>
+    {% csrf_token %}
+    <table>
+        {{ form.as_table }}
+        <td></td><td><input type="submit" value='{% trans "Commit" %}' /></td></td>
+    </table>
+</form>
+
+{% endblock %}
diff --git a/apps/archive/templates/archive/file_unmanaged.html b/apps/archive/templates/archive/file_unmanaged.html
new file mode 100755 (executable)
index 0000000..addd28e
--- /dev/null
@@ -0,0 +1,31 @@
+{% extends "archive/base.html" %}
+{% load i18n %}
+
+{% block messages %}
+    {% if err_exists %}
+        <p>{% trans "File with same name already exists!" %}</p>
+    {% endif %}
+{% endblock %}
+
+
+{% block content %}
+
+
+<ul id="tags">
+    {% for t, v in tags.items %}
+        <li>{{t}}
+        <ul>
+        {% for x in v %}
+            <li>{{x}}</li>
+        {% endfor %}
+        </ul></li>
+    {% endfor %}
+</ul>
+
+
+<form method="post" action="{% url move_to_new filename %}">
+    {% csrf_token %}
+    <input type="submit" value="{% trans "Move to new files" %}" />
+</form>
+
+{% endblock %}
diff --git a/apps/archive/templates/archive/list.html b/apps/archive/templates/archive/list.html
new file mode 100644 (file)
index 0000000..9e3d85a
--- /dev/null
@@ -0,0 +1,13 @@
+{% extends "archive/base.html" %}
+{% load i18n %}
+
+
+{% block content %}
+
+    <div id="file-list">
+        <h1>{% block file-list-title %}{% endblock %}</h1>
+        <div>{% block file-list-info %}{% endblock %}</div>
+        <ul>{% block file-list %}{% endblock %}</ul>
+    </div>
+
+{% endblock %}
diff --git a/apps/archive/templates/archive/list_new.html b/apps/archive/templates/archive/list_new.html
new file mode 100644 (file)
index 0000000..c837cfd
--- /dev/null
@@ -0,0 +1,22 @@
+{% extends "archive/list.html" %}
+{% load i18n %}
+
+
+{% block file-list-title %}
+    {% trans "New audiobooks" %}
+{% endblock %}
+
+
+{% block file-list-info %}
+    {% trans "Put source audiobooks in:" %}
+    <span>{{ path }}</span>
+{% endblock %}
+
+
+{% block file-list %}
+    {% for file in objects %}
+        <li>
+            <a href='{% url file_new file %}'>{{ file }}</a>
+        </li>
+    {% endfor %}
+{% endblock %}
diff --git a/apps/archive/templates/archive/list_published.html b/apps/archive/templates/archive/list_published.html
new file mode 100755 (executable)
index 0000000..c51cace
--- /dev/null
@@ -0,0 +1,20 @@
+{% extends "archive/list.html" %}
+{% load i18n %}
+
+
+{% block file-list-title %}
+    {% trans "Published audiobooks" %}
+{% endblock %}
+
+
+{% block file-list-info %}
+{% endblock %}
+
+
+{% block file-list %}
+    {% for file in objects %}
+        <li>
+            <a href='{% url file file %}'>{{ file }}</a></form>
+        </li>
+    {% endfor %}
+{% endblock %}
diff --git a/apps/archive/templates/archive/list_unmanaged.html b/apps/archive/templates/archive/list_unmanaged.html
new file mode 100755 (executable)
index 0000000..96e45d3
--- /dev/null
@@ -0,0 +1,19 @@
+{% extends "archive/list.html" %}
+{% load i18n %}
+
+{% block file-list-title %}
+    {% trans "Unmanaged archive" %}
+{% endblock %}
+
+
+{% block file-list-info %}
+{% endblock %}
+
+
+{% block file-list %}
+    {% for file in objects %}
+        <li>
+            <a href='{% url file_unmanaged file %}'>{{ file }}</a>
+        </li>
+    {% endfor %}
+{% endblock %}
diff --git a/apps/archive/templates/archive/list_unpublished.html b/apps/archive/templates/archive/list_unpublished.html
new file mode 100755 (executable)
index 0000000..466072c
--- /dev/null
@@ -0,0 +1,20 @@
+{% extends "archive/list.html" %}
+{% load i18n %}
+
+
+{% block file-list-title %}
+    {% trans "Unpublished audiobooks" %}
+{% endblock %}
+
+
+{% block file-list-info %}
+{% endblock %}
+
+
+{% block file-list %}
+    {% for file in objects %}
+        <li>
+            <a href='{% url file file.id %}'>{{ file }}</a></form>
+        </li>
+    {% endfor %}
+{% endblock %}
diff --git a/apps/archive/templates/archive/tags/multiple_tags_table.html b/apps/archive/templates/archive/tags/multiple_tags_table.html
new file mode 100755 (executable)
index 0000000..4a39808
--- /dev/null
@@ -0,0 +1,9 @@
+<table>
+    {% for t, v in tags.items %}
+        <tr><th>{{t}}</th><td>
+        {% for x in v %}
+            <div>{{x}}</div>
+        {% endfor %}
+        </td></tr>
+    {% endfor %}
+</table>
diff --git a/apps/archive/templatetags/__init__.py b/apps/archive/templatetags/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/apps/archive/templatetags/tags.py b/apps/archive/templatetags/tags.py
new file mode 100755 (executable)
index 0000000..68a6a9b
--- /dev/null
@@ -0,0 +1,19 @@
+from django import template
+
+register = template.Library()
+
+@register.simple_tag
+def multiple_tags_table(tags):
+    return template.loader.render_to_string(
+            "archive/tags/multiple_tags_table.html",
+            {"tags": tags}
+        )
+
+
+#@register.simple_tag
+#def multiple_tags_table(tags):
+#    return template.loader.render_to_string(
+#            "archive/tags/multiple_tags_table.html",
+#            {"tags": tags}
+#        )
+
diff --git a/apps/archive/tests.py b/apps/archive/tests.py
new file mode 100644 (file)
index 0000000..501deb7
--- /dev/null
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.assertEqual(1 + 1, 2)
diff --git a/apps/archive/urls.py b/apps/archive/urls.py
new file mode 100644 (file)
index 0000000..c5fe97c
--- /dev/null
@@ -0,0 +1,19 @@
+from django.conf.urls.defaults import patterns, include, url
+
+urlpatterns = patterns('',
+    url(r'^$', 'django.views.generic.simple.redirect_to', {'url': 'new/'}),
+
+    url(r'^new/$', 'archive.views.list_new', name="list_new"),
+    url(r'^new/([^/]+)/$', 'archive.views.file_new', name="file_new"),
+    url(r'^move_to_archive/([^/]+)/$', 'archive.views.move_to_archive', name="move_to_archive"),
+
+    url(r'^unpublished/$', 'archive.views.list_unpublished', name="list_unpublished"),
+    url(r'^published/$', 'archive.views.list_published', name="list_published"),
+    url(r'^file/(\d+)/$', 'archive.views.file_managed', name="file"),
+    url(r'^publish/(\d+)/$', 'archive.views.publish', name="publish"),
+    url(r'^cancel/(\d+)/$', 'archive.views.cancel_publishing', name="cancel_publishing"),
+
+    url(r'^unmanaged/$', 'archive.views.list_unmanaged', name="list_unmanaged"),
+    url(r'^unmanaged/([^/]+)/$', 'archive.views.file_unmanaged', name="file_unmanaged"),
+    url(r'^move_to_new/([^/]+)/$', 'archive.views.move_to_new', name="move_to_new"),
+)
diff --git a/apps/archive/utils.py b/apps/archive/utils.py
new file mode 100755 (executable)
index 0000000..4710c20
--- /dev/null
@@ -0,0 +1,13 @@
+from django.core.files.uploadedfile import UploadedFile
+
+class ExistingFile(UploadedFile):
+
+    def __init__(self, path, *args, **kwargs):
+        self.path = path
+        return super(ExistingFile, self).__init__(*args, **kwargs)
+
+    def temporary_file_path(self):
+        return self.path
+
+    def close(self):
+        pass
diff --git a/apps/archive/views.py b/apps/archive/views.py
new file mode 100644 (file)
index 0000000..54bb28f
--- /dev/null
@@ -0,0 +1,170 @@
+# Create your views here.
+
+from datetime import datetime
+import os
+import os.path
+
+from archive import settings
+from django.core.urlresolvers import reverse
+from django.http import Http404
+from django.shortcuts import render, redirect, get_object_or_404
+from django.views.decorators.http import require_POST
+
+import mutagen
+
+from archive import models
+from archive.forms import AudiobookForm
+
+
+def list_new(request):
+    division = 'new'
+
+    path = settings.NEW_PATH
+    objects = sorted(os.listdir(path))
+    return render(request, "archive/list_new.html", locals())
+
+
+def file_new(request, filename):
+    division = 'new'
+
+    filepath = os.path.join(settings.NEW_PATH, filename)
+    if request.POST:
+        form = AudiobookForm(request.POST)
+        if form.is_valid():
+            try:
+                form.save(path=filepath)
+            except IOError:
+                raise Http404
+            return redirect(list_new)
+
+    try:
+        tags = mutagen.File(filepath)
+    except IOError:
+        raise Http404
+    d = {}
+    for tag in tags:
+        value = tags[tag]
+        if isinstance(value, list):
+            d[tag] = value[0]
+        else:
+            d[tag] = value
+        if tag == 'project':
+            try:
+                d[tag] = models.Project.objects.get(name=d[tag]).pk
+            except models.Project.DoesNotExist:
+                d[tag] = None
+        print d
+
+    if not request.POST:
+        form = AudiobookForm(d)
+    return render(request, "archive/file_new.html", locals())
+
+
+@require_POST
+def move_to_archive(request, filename):
+    """ move a new file to the unmanaged files dir """
+
+    old_path = os.path.join(settings.NEW_PATH, filename)
+    if not os.path.isdir(settings.UNMANAGED_PATH):
+        os.makedirs(settings.UNMANAGED_PATH)
+    new_path = os.path.join(settings.UNMANAGED_PATH, filename)
+
+    if not os.path.isfile(old_path):
+        raise Http404
+
+    try:
+        os.link(old_path, new_path)
+        os.unlink(old_path)
+    except OSError:
+        # destination file exists, don't overwrite it
+        # TODO: this should probably be more informative
+        return redirect(file_new, filename)
+
+    return redirect(list_new)
+
+
+@require_POST
+def move_to_new(request, filename):
+    """ move a unmanaged file to new files dir """
+
+    old_path = os.path.join(settings.UNMANAGED_PATH, filename)
+    if not os.path.isdir(settings.NEW_PATH):
+        os.makedirs(settings.NEW_PATH)
+    new_path = os.path.join(settings.NEW_PATH, filename)
+
+    if not os.path.isfile(old_path):
+        raise Http404
+
+    try:
+        os.link(old_path, new_path)
+        os.unlink(old_path)
+    except OSError:
+        # destination file exists, don't overwrite it
+        # TODO: this should probably be more informative
+        return redirect(reverse(file_unmanaged, args=[filename]) + "?exists=1")
+
+    return redirect(list_unmanaged)
+
+@require_POST
+def publish(request, id):
+    """ mark file for publishing """
+    audiobook = get_object_or_404(models.Audiobook, id=id)
+    audiobook.publish_wait = datetime.now()
+    audiobook.publishing_tags = audiobook.new_publish_tags()
+    audiobook.save()
+    return redirect(file_managed, id)
+
+@require_POST
+def cancel_publishing(request, id):
+    """ cancel scheduled publishing """
+    audiobook = get_object_or_404(models.Audiobook, id=id)
+    if not audiobook.publishing:
+        audiobook.publish_wait = None
+        audiobook.publishing_tags = None
+        audiobook.save()
+    return redirect(file_managed, id)
+
+
+def list_unpublished(request):
+    division = 'unpublished'
+
+    objects = models.Audiobook.objects.filter(published=None)
+    return render(request, "archive/list_unpublished.html", locals())
+
+
+
+def file_managed(request, id):
+    audiobook = get_object_or_404(models.Audiobook, id=id)
+    division = 'published' if audiobook.published else 'unpublished'
+
+    # for tags update
+    tags = mutagen.File(audiobook.source_file.path)
+    form = AudiobookForm(instance=audiobook)
+
+    return render(request, "archive/file_managed.html", locals())
+
+
+
+def list_published(request):
+    division = 'published'
+
+    objects = models.Audiobook.objects.exclude(published=None)
+    return render(request, "archive/list_published.html", locals())
+
+
+
+
+def list_unmanaged(request):
+    division = 'unmanaged'
+
+    objects = sorted(os.listdir(settings.UNMANAGED_PATH))
+    return render(request, "archive/list_unmanaged.html", locals())
+
+
+def file_unmanaged(request, filename):
+    division = 'unmanaged'
+
+    tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename))
+    err_exists = request.GET.get('exists')
+    return render(request, "archive/file_unmanaged.html", locals())
+
diff --git a/audiobooks/__init__.py b/audiobooks/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/audiobooks/manage.py b/audiobooks/manage.py
new file mode 100755 (executable)
index 0000000..3e4eedc
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+import imp
+try:
+    imp.find_module('settings') # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
+    sys.exit(1)
+
+import settings
+
+if __name__ == "__main__":
+    execute_manager(settings)
diff --git a/audiobooks/settings.py b/audiobooks/settings.py
new file mode 100644 (file)
index 0000000..9f0731a
--- /dev/null
@@ -0,0 +1,163 @@
+# Django settings for audiobooks project.
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
+sys.path.insert(0, os.path.join(PROJECT_ROOT, "../apps"))
+
+
+
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+    # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': os.path.join(PROJECT_ROOT, 'dev.sqlite'),                      # Or path to database file if using sqlite3.
+        'USER': '',                      # Not used with sqlite3.
+        'PASSWORD': '',                  # Not used with sqlite3.
+        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
+        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
+    }
+}
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'Europe/Warsaw'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'pl'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = os.path.join(PROJECT_ROOT, '../media')
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://media.lawrence.com/static/"
+STATIC_URL = '/static/'
+
+# URL prefix for admin static files -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+    # Put strings here, like "/home/html/static" or "C:/www/django/static".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+    'django.contrib.staticfiles.finders.FileSystemFinder',
+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '8ehk^-+pr(o)k6lh_gl9+ks6gkd9u#fka7fv%ikpk(c%llqa6%'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.Loader',
+    'django.template.loaders.app_directories.Loader',
+#     'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'audiobooks.urls'
+
+TEMPLATE_DIRS = (
+    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    # Uncomment the next line to enable the admin:
+    'django.contrib.admin',
+    # Uncomment the next line to enable admin documentation:
+    # 'django.contrib.admindocs',
+
+    'south',
+    'archive',
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'handlers': {
+        'mail_admins': {
+            'level': 'ERROR',
+            'class': 'django.utils.log.AdminEmailHandler'
+        }
+    },
+    'loggers': {
+        'django.request': {
+            'handlers': ['mail_admins'],
+            'level': 'ERROR',
+            'propagate': True,
+        },
+    }
+}
+
+
+
+try:
+    from localsettings import *
+except:
+    pass
diff --git a/audiobooks/urls.py b/audiobooks/urls.py
new file mode 100644 (file)
index 0000000..2cb91e0
--- /dev/null
@@ -0,0 +1,12 @@
+from django.conf.urls.defaults import patterns, include, url
+
+# Uncomment the next two lines to enable the admin:
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+    url(r'^$', 'django.views.generic.simple.redirect_to', {'url': 'archive/'}),
+    url(r'^archive/', include('archive.urls')),
+
+    url(r'^admin/', include(admin.site.urls)),
+)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644 (file)
index 0000000..7b29e99
--- /dev/null
@@ -0,0 +1,5 @@
+django>=1.3
+django-jsonfield
+South>=0.7
+
+mutagen