From: Radek Czajka Date: Fri, 1 Mar 2019 13:22:25 +0000 (+0100) Subject: Reduce the crazy, just upload things. X-Git-Url: https://git.mdrn.pl/audio.git/commitdiff_plain/f2ed1449ece59c6247b8befc9ca6f423f7e4b004?hp=ac39503a5a9a16c1098e65ee16a866cf8eb19a8f Reduce the crazy, just upload things. --- diff --git a/.gitignore b/.gitignore index f1e760b..4078890 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc *.sqlite localsettings.py +/media diff --git a/requirements.txt b/requirements.txt index 6f60425..d5b0006 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,6 @@ kombu 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 diff --git a/src/apiclient/__init__.py b/src/apiclient/__init__.py new file mode 100644 index 0000000..e489807 --- /dev/null +++ b/src/apiclient/__init__.py @@ -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 index 0000000..5d62406 --- /dev/null +++ b/src/apiclient/migrations/0001_initial.py @@ -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 index 0000000..e69de29 diff --git a/src/apiclient/models.py b/src/apiclient/models.py new file mode 100644 index 0000000..6c9542d --- /dev/null +++ b/src/apiclient/models.py @@ -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 index 0000000..49037fe --- /dev/null +++ b/src/apiclient/settings.py @@ -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 index 0000000..8e93697 --- /dev/null +++ b/src/apiclient/urls.py @@ -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 index 0000000..351dd2e --- /dev/null +++ b/src/apiclient/views.py @@ -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('/') diff --git a/src/archive/settings.py b/src/archive/settings.py index 38dc2a3..747287d 100644 --- a/src/archive/settings.py +++ b/src/archive/settings.py @@ -45,36 +45,11 @@ except AttributeError: 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: diff --git a/src/archive/tasks.py b/src/archive/tasks.py index be0eb1a..2b3d696 100644 --- a/src/archive/tasks.py +++ b/src/archive/tasks.py @@ -9,25 +9,18 @@ import subprocess 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 @@ -77,33 +70,28 @@ class AudioFormatTask(Task): 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: @@ -120,7 +108,7 @@ class AudioFormatTask(Task): 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) diff --git a/src/archive/templates/archive/file_managed.html b/src/archive/templates/archive/file_managed.html index 8917243..498b27b 100755 --- a/src/archive/templates/archive/file_managed.html +++ b/src/archive/templates/archive/file_managed.html @@ -46,10 +46,14 @@ {% tags_table audiobook.new_publish_tags 0 %} -
- {% csrf_token %} - -
+ {% if user_can_publish %} +
+ {% csrf_token %} + +
+ {% else %} + Podłącz się + {% endif %} {% if not audiobook.mp3_published or not audiobook.ogg_published %}
diff --git a/src/archive/views.py b/src/archive/views.py index 31ea9c3..98556ef 100644 --- a/src/archive/views.py +++ b/src/archive/views.py @@ -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_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) @@ -258,6 +258,10 @@ def file_managed(request, id): 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()) diff --git a/src/audiobooks/settings.py b/src/audiobooks/settings.py index 2d8a682..714fadb 100644 --- a/src/audiobooks/settings.py +++ b/src/audiobooks/settings.py @@ -132,6 +132,7 @@ INSTALLED_APPS = ( # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', + 'apiclient', 'archive', ) diff --git a/src/audiobooks/urls.py b/src/audiobooks/urls.py index eb4f030..eb198b0 100644 --- a/src/audiobooks/urls.py +++ b/src/audiobooks/urls.py @@ -10,6 +10,7 @@ admin.autodiscover() 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'),