Reduce the crazy, just upload things.
authorRadek Czajka <rczajka@rczajka.pl>
Fri, 1 Mar 2019 13:22:25 +0000 (14:22 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Fri, 1 Mar 2019 13:22:25 +0000 (14:22 +0100)
15 files changed:
.gitignore
requirements.txt
src/apiclient/__init__.py [new file with mode: 0644]
src/apiclient/migrations/0001_initial.py [new file with mode: 0644]
src/apiclient/migrations/__init__.py [new file with mode: 0644]
src/apiclient/models.py [new file with mode: 0644]
src/apiclient/settings.py [new file with mode: 0755]
src/apiclient/urls.py [new file with mode: 0755]
src/apiclient/views.py [new file with mode: 0644]
src/archive/settings.py
src/archive/tasks.py
src/archive/templates/archive/file_managed.html
src/archive/views.py
src/audiobooks/settings.py
src/audiobooks/urls.py

index f1e760b..4078890 100644 (file)
@@ -1,3 +1,4 @@
 *.pyc
 *.sqlite
 localsettings.py
 *.pyc
 *.sqlite
 localsettings.py
+/media
index 6f60425..d5b0006 100644 (file)
@@ -9,6 +9,6 @@ kombu
 redis
 vine
 
 redis
 vine
 
-paramiko>=1.7.7.1 # fabric dependency with http://code.fabfile.org/issues/show/214 fixed
-fabric>=1.0,<2
 mutagen
 mutagen
+requests
+requests-oauthlib
diff --git a/src/apiclient/__init__.py b/src/apiclient/__init__.py
new file mode 100644 (file)
index 0000000..e489807
--- /dev/null
@@ -0,0 +1,34 @@
+import requests
+from requests_oauthlib import OAuth1
+from apiclient.settings import WL_CONSUMER_KEY, WL_CONSUMER_SECRET, WL_API_URL
+
+
+class ApiError(BaseException):
+    pass
+
+
+class NotAuthorizedError(BaseException):
+    pass
+
+
+def api_call(user, path, method='POST', data=None, files=None):
+    from .models import OAuthConnection
+    conn = OAuthConnection.get(user=user)
+    if not conn.access:
+        raise NotAuthorizedError("No WL authorization for user %s." % user)
+
+    auth = OAuth1(WL_CONSUMER_KEY, WL_CONSUMER_SECRET, conn.token, conn.token_secret)
+
+    url = WL_API_URL + path
+
+    r = requests.request(method=method, url=url, data=data, files=files, auth=auth)
+
+    if r.status_code == 200:
+        return r.content
+    elif 201 <= r.status_code < 300:
+        return
+    elif r.status_code == 401:
+        raise ApiError('User not authorized for publishing.')
+    else:
+        raise ApiError("WL API call error %s, path: %s" % (r.status_code, path))
+
diff --git a/src/apiclient/migrations/0001_initial.py b/src/apiclient/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..5d62406
--- /dev/null
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='OAuthConnection',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('access', models.BooleanField(default=False)),
+                ('token', models.CharField(max_length=64, null=True, blank=True)),
+                ('token_secret', models.CharField(max_length=64, null=True, blank=True)),
+                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+            },
+            bases=(models.Model,),
+        ),
+    ]
diff --git a/src/apiclient/migrations/__init__.py b/src/apiclient/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/apiclient/models.py b/src/apiclient/models.py
new file mode 100644 (file)
index 0000000..6c9542d
--- /dev/null
@@ -0,0 +1,20 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+
+class OAuthConnection(models.Model):
+    user = models.ForeignKey(User)
+    access = models.BooleanField(default=False)
+    token = models.CharField(max_length=64, null=True, blank=True)
+    token_secret = models.CharField(max_length=64, null=True, blank=True)
+
+    @classmethod
+    def get(cls, user):
+        try:
+            return cls.objects.get(user=user)
+        except cls.DoesNotExist:
+            o = cls(user=user)
+            o.save()
+            return o
+
+
diff --git a/src/apiclient/settings.py b/src/apiclient/settings.py
new file mode 100755 (executable)
index 0000000..49037fe
--- /dev/null
@@ -0,0 +1,14 @@
+from django.conf import settings
+
+
+WL_CONSUMER_KEY = getattr(settings, 'APICLIENT_WL_CONSUMER_KEY', None)
+WL_CONSUMER_SECRET = getattr(settings, 'APICLIENT_WL_CONSUMER_SECRET', None)
+
+WL_API_URL = getattr(settings, 'APICLIENT_WL_API_URL', 'https://wolnelektury.pl/api/')
+
+WL_REQUEST_TOKEN_URL = getattr(settings, 'APICLIENT_WL_REQUEST_TOKEN_URL',
+        WL_API_URL + 'oauth/request_token/')
+WL_ACCESS_TOKEN_URL = getattr(settings, 'APICLIENT_WL_ACCESS_TOKEN_URL',
+        WL_API_URL + 'oauth/access_token/')
+WL_AUTHORIZE_URL = getattr(settings, 'APICLIENT_WL_AUTHORIZE_URL',
+        WL_API_URL + 'oauth/authorize/')
diff --git a/src/apiclient/urls.py b/src/apiclient/urls.py
new file mode 100755 (executable)
index 0000000..8e93697
--- /dev/null
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+from django.conf.urls import url
+from . import views
+
+
+urlpatterns = [
+    url(r'^oauth/$', views.oauth, name='apiclient_oauth'),
+    url(r'^oauth_callback/$', views.oauth_callback, name='apiclient_oauth_callback'),
+]
diff --git a/src/apiclient/views.py b/src/apiclient/views.py
new file mode 100644 (file)
index 0000000..351dd2e
--- /dev/null
@@ -0,0 +1,46 @@
+import cgi
+
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect, HttpResponse
+import requests
+from requests_oauthlib import OAuth1Session
+
+from apiclient.models import OAuthConnection
+from apiclient.settings import WL_CONSUMER_KEY, WL_CONSUMER_SECRET
+from apiclient.settings import WL_REQUEST_TOKEN_URL, WL_ACCESS_TOKEN_URL, WL_AUTHORIZE_URL
+
+
+@login_required
+def oauth(request):
+    oauth = OAuth1Session(WL_CONSUMER_KEY, WL_CONSUMER_SECRET)
+    request_token = oauth.fetch_request_token(WL_REQUEST_TOKEN_URL)
+
+    conn = OAuthConnection.get(request.user)
+    conn.access = False
+    conn.token = request_token['oauth_token']
+    conn.token_secret = request_token['oauth_token_secret']
+    conn.save()
+
+    url = oauth.authorization_url(WL_AUTHORIZE_URL)
+    url += '&oauth_callback=' + request.build_absolute_uri(reverse("apiclient_oauth_callback"))
+    return HttpResponseRedirect(url)
+
+
+@login_required
+def oauth_callback(request):
+    conn = OAuthConnection.get(request.user)
+    oauth_verifier = request.GET.get('oauth_verifier', 'verifier')
+
+    oauth = OAuth1Session(
+        WL_CONSUMER_KEY, WL_CONSUMER_SECRET,
+                          conn.token, conn.token_secret,
+                          verifier=oauth_verifier)
+    access_token = oauth.fetch_access_token(WL_ACCESS_TOKEN_URL)
+
+    conn.access = True
+    conn.token = access_token['oauth_token']
+    conn.token_secret = access_token['oauth_token_secret']
+    conn.save()
+
+    return HttpResponseRedirect('/')
index 38dc2a3..747287d 100644 (file)
@@ -45,36 +45,11 @@ except AttributeError:
     BUILD_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT,
                         "archive/build"))
 
     BUILD_PATH = os.path.abspath(os.path.join(settings.MEDIA_ROOT,
                         "archive/build"))
 
-# upload conf
-try:
-    UPLOAD_HOST = settings.ARCHIVE_UPLOAD_HOST
-except AttributeError:
-    UPLOAD_HOST = 'wolnelektury.pl'
-
-try:
-    UPLOAD_USER = settings.ARCHIVE_UPLOAD_USER
-except AttributeError:
-    UPLOAD_USER = 'username'
-
-try:
-    UPLOAD_PASSWORD = settings.ARCHIVE_UPLOAD_PASSWORD
-except AttributeError:
-    UPLOAD_PASSWORD = None
-
-try:
-    UPLOAD_PATH = settings.ARCHIVE_UPLOAD_PATH
-except AttributeError:
-    UPLOAD_PATH = ''
-
-try:
-    UPLOAD_CMD = settings.ARCHIVE_UPLOAD_CMD
-except AttributeError:
-    UPLOAD_CMD = '/path/to/manage.py savemedia'
-
-try:
-    UPLOAD_SUDO = settings.ARCHIVE_UPLOAD_SUDO
-except AttributeError:
-    UPLOAD_SUDO = None
+UPLOAD_URL = getattr(
+    settings,
+    'ARCHIVE_UPLOAD_URL',
+    'audiobooks/'
+)
 
 
 try:
 
 
 try:
index be0eb1a..2b3d696 100644 (file)
@@ -9,25 +9,18 @@ import subprocess
 from tempfile import NamedTemporaryFile
 from time import sleep
 
 from tempfile import NamedTemporaryFile
 from time import sleep
 
-#from celery.decorators import task
 from celery.task import Task
 from django.db.models import F
 from celery.task import Task
 from django.db.models import F
-from fabric import api
-from fabric.network import disconnect_all
+from django.contrib.auth.models import User
 from mutagen import File
 from mutagen import id3
 
 from mutagen import File
 from mutagen import id3
 
-import mutagen
-
+from apiclient import api_call
 from archive.constants import status
 from archive.models import Audiobook
 from archive.constants import status
 from archive.models import Audiobook
-from archive.settings import (BUILD_PATH, COVER_IMAGE,
-    UPLOAD_HOST, UPLOAD_USER, UPLOAD_PASSWORD, UPLOAD_PATH, UPLOAD_CMD, UPLOAD_SUDO)
+from archive.settings import BUILD_PATH, COVER_IMAGE, UPLOAD_URL
 from archive.utils import ExistingFile
 
 from archive.utils import ExistingFile
 
-api.env.host_string = UPLOAD_HOST
-api.env.user = UPLOAD_USER
-api.env.password = UPLOAD_PASSWORD
 
 class AudioFormatTask(Task):
     abstract = True
 
 class AudioFormatTask(Task):
     abstract = True
@@ -77,33 +70,28 @@ class AudioFormatTask(Task):
         Audiobook.objects.filter(pk=aid).update(**kwargs)
 
     @classmethod
         Audiobook.objects.filter(pk=aid).update(**kwargs)
 
     @classmethod
-    def put(cls, audiobook, path):
+    def put(cls, user, audiobook, path):
         tags = getattr(audiobook, "%s_tags" % cls.ext)
         tags = getattr(audiobook, "%s_tags" % cls.ext)
-        prefix, slug = tags['url'].rstrip('/').rsplit('/', 1)
-        name = tags['name']
-        command = UPLOAD_CMD + (u' %s %s %s %s %s %s > output.txt' % (
-            pipes.quote(os.path.join(UPLOAD_PATH, os.path.basename(path))),
-            pipes.quote(slug),
-            pipes.quote(name),
-            pipes.quote(audiobook.part_name),
-            audiobook.index,
-            audiobook.parts_count,
-            )).encode('utf-8')
-        try:
-            api.put(path, UPLOAD_PATH)
-            if UPLOAD_SUDO:
-                api.sudo(command, user=UPLOAD_SUDO, shell=False)
-            else:
-                api.run(command)
-            disconnect_all()
-        except SystemExit as e:
-            raise cls.RemoteOperationError
+        data = {
+            'book': tags['url'],
+            'type': cls.ext,
+            'name': tags['name'],
+            'part_name': audiobook.part_name,
+            'part_index': audiobook.index,
+            'parts_count': audiobook.parts_count,
+            'source_sha1': audiobook.source_sha1,
+        }
+        api_call(user, UPLOAD_URL, data=data, files={
+            "file": open(path, 'rb'),
+        })
 
 
-    def run(self, aid, publish=True):
+    def run(self, uid, aid, publish=True):
         aid = int(aid)
         audiobook = Audiobook.objects.get(id=aid)
         self.set_status(aid, status.ENCODING)
 
         aid = int(aid)
         audiobook = Audiobook.objects.get(id=aid)
         self.set_status(aid, status.ENCODING)
 
+        user = User.objects.get(id=uid)
+
         try:
             os.makedirs(BUILD_PATH)
         except OSError as e:
         try:
             os.makedirs(BUILD_PATH)
         except OSError as e:
@@ -120,7 +108,7 @@ class AudioFormatTask(Task):
         self.set_status(aid, status.SENDING)
 
         if publish:
         self.set_status(aid, status.SENDING)
 
         if publish:
-            self.put(audiobook, out_file.name)
+            self.put(user, audiobook, out_file.name)
             self.published(aid)
         else:
             self.set_status(aid, None)
             self.published(aid)
         else:
             self.set_status(aid, None)
index 8917243..498b27b 100755 (executable)
         {% tags_table audiobook.new_publish_tags 0 %}
         <tr><th></th><td>
 
         {% tags_table audiobook.new_publish_tags 0 %}
         <tr><th></th><td>
 
-            <form method="post" action="{% url 'publish' audiobook.id %}">
-                {% csrf_token %}
-                <input type="submit" value="{% trans "Publish" %}" />
-            </form>
+            {% if user_can_publish %}
+                <form method="post" action="{% url 'publish' audiobook.id %}">
+                    {% csrf_token %}
+                    <input type="submit" value="{% trans "Publish" %}" />
+                </form>
+            {% else %}
+                <a href="{% url 'apiclient_oauth' %}">Podłącz się</a>
+            {% endif %}
 
             {% if not audiobook.mp3_published or not audiobook.ogg_published %}
             <form method="post" action="{% url 'convert' audiobook.id %}">
 
             {% if not audiobook.mp3_published or not audiobook.ogg_published %}
             <form method="post" action="{% url 'convert' audiobook.id %}">
index 31ea9c3..98556ef 100644 (file)
@@ -171,8 +171,8 @@ def publish(request, aid, publish=True):
     audiobook.mp3_status = audiobook.ogg_status = status.WAITING
     audiobook.save()
     # isn't there a race here?
     audiobook.mp3_status = audiobook.ogg_status = status.WAITING
     audiobook.save()
     # isn't there a race here?
-    audiobook.mp3_task = tasks.Mp3Task.delay(aid, publish).task_id
-    audiobook.ogg_task = tasks.OggTask.delay(aid, publish).task_id
+    audiobook.mp3_task = tasks.Mp3Task.delay(request.user.id, aid, publish).task_id
+    audiobook.ogg_task = tasks.OggTask.delay(request.user.id, aid, publish).task_id
     audiobook.save()
 
     return redirect(file_managed, aid)
     audiobook.save()
 
     return redirect(file_managed, aid)
@@ -258,6 +258,10 @@ def file_managed(request, id):
         tags = {}
     form = AudiobookForm(instance=audiobook)
 
         tags = {}
     form = AudiobookForm(instance=audiobook)
 
+    user_can_publish = (
+        request.user.is_authenticated and
+        request.user.oauthconnection_set.filter(access=True).exists())
+
     return render(request, "archive/file_managed.html", locals())
 
 
     return render(request, "archive/file_managed.html", locals())
 
 
index 2d8a682..714fadb 100644 (file)
@@ -132,6 +132,7 @@ INSTALLED_APPS = (
     # Uncomment the next line to enable admin documentation:
     # 'django.contrib.admindocs',
 
     # Uncomment the next line to enable admin documentation:
     # 'django.contrib.admindocs',
 
+    'apiclient',
     'archive',
 )
 
     'archive',
 )
 
index eb4f030..eb198b0 100644 (file)
@@ -10,6 +10,7 @@ admin.autodiscover()
 urlpatterns = [
     url(r'^$', RedirectView.as_view(url='archive/', permanent=False)),
     url(r'^archive/', include('archive.urls')),
 urlpatterns = [
     url(r'^$', RedirectView.as_view(url='archive/', permanent=False)),
     url(r'^archive/', include('archive.urls')),
+    url(r'^publish/', include('apiclient.urls')),
 
     url(r'^admin/', include(admin.site.urls)),
     url(r'^accounts/login/$', django_cas.views.login, name='login'),
 
     url(r'^admin/', include(admin.site.urls)),
     url(r'^accounts/login/$', django_cas.views.login, name='login'),