*.pyc
*.sqlite
localsettings.py
+/media
redis
vine
-paramiko>=1.7.7.1 # fabric dependency with http://code.fabfile.org/issues/show/214 fixed
-fabric>=1.0,<2
mutagen
+requests
+requests-oauthlib
--- /dev/null
+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))
+
--- /dev/null
+# -*- 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,),
+ ),
+ ]
--- /dev/null
+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
+
+
--- /dev/null
+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/')
--- /dev/null
+# -*- 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'),
+]
--- /dev/null
+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('/')
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:
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 fabric import api
-from fabric.network import disconnect_all
+from django.contrib.auth.models import User
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.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
-api.env.host_string = UPLOAD_HOST
-api.env.user = UPLOAD_USER
-api.env.password = UPLOAD_PASSWORD
class AudioFormatTask(Task):
abstract = True
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)
- 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)
+ user = User.objects.get(id=uid)
+
try:
os.makedirs(BUILD_PATH)
except OSError as e:
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)
{% 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 %}">
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)
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())
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
+ 'apiclient',
'archive',
)
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'),