From 66ab038f6f74eb2ebcaf945cf85bb0d7813f791c Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 20 Dec 2022 15:33:32 +0100 Subject: [PATCH] Education. --- src/catalogue/admin.py | 1 + .../templates/catalogue/2022/collections.html | 2 +- .../club/2022/donation_step_base.html | 9 ++ src/education/__init__.py | 0 src/education/admin.py | 12 ++ src/education/apps.py | 6 + src/education/migrations/0001_initial.py | 54 +++++++++ src/education/migrations/0002_youtubetoken.py | 20 ++++ src/education/migrations/0003_item_title.py | 18 +++ .../0004_alter_item_options_course_title.py | 23 ++++ src/education/migrations/__init__.py | 0 src/education/models.py | 110 ++++++++++++++++++ .../templates/education/course_detail.html | 37 ++++++ src/education/tests.py | 3 + src/education/urls.py | 8 ++ src/education/views.py | 3 + src/wolnelektury/urls.py | 1 + 17 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 src/education/__init__.py create mode 100644 src/education/admin.py create mode 100644 src/education/apps.py create mode 100644 src/education/migrations/0001_initial.py create mode 100644 src/education/migrations/0002_youtubetoken.py create mode 100644 src/education/migrations/0003_item_title.py create mode 100644 src/education/migrations/0004_alter_item_options_course_title.py create mode 100644 src/education/migrations/__init__.py create mode 100644 src/education/models.py create mode 100644 src/education/templates/education/course_detail.html create mode 100644 src/education/tests.py create mode 100644 src/education/urls.py create mode 100644 src/education/views.py diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py index 2866fcc12..420cd3f49 100644 --- a/src/catalogue/admin.py +++ b/src/catalogue/admin.py @@ -39,6 +39,7 @@ class BookAdmin(admin.ModelAdmin): 'print_on_demand', ('wiki_link', BlankFieldListFilter), ('parent', EmptyFieldListFilter), + ('media', admin.EmptyFieldListFilter), ] date_hierarchy = 'created_at' search_fields = ('title',) diff --git a/src/catalogue/templates/catalogue/2022/collections.html b/src/catalogue/templates/catalogue/2022/collections.html index 5fb4078ea..4210c1b97 100644 --- a/src/catalogue/templates/catalogue/2022/collections.html +++ b/src/catalogue/templates/catalogue/2022/collections.html @@ -16,7 +16,7 @@ {% for collection in objects %}
-
+
diff --git a/src/club/templates/club/2022/donation_step_base.html b/src/club/templates/club/2022/donation_step_base.html index 46590798a..1b292a80b 100644 --- a/src/club/templates/club/2022/donation_step_base.html +++ b/src/club/templates/club/2022/donation_step_base.html @@ -107,6 +107,15 @@
+ diff --git a/src/education/__init__.py b/src/education/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/education/admin.py b/src/education/admin.py new file mode 100644 index 000000000..399649338 --- /dev/null +++ b/src/education/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from . import models + + +admin.site.register(models.Course) +admin.site.register(models.Track) +admin.site.register(models.Tag) +admin.site.register(models.Item) +admin.site.register(models.YPlaylist) +admin.site.register(models.YouTubeToken) + +# Register your models here. diff --git a/src/education/apps.py b/src/education/apps.py new file mode 100644 index 000000000..4a92e70e5 --- /dev/null +++ b/src/education/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EducationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'education' diff --git a/src/education/migrations/0001_initial.py b/src/education/migrations/0001_initial.py new file mode 100644 index 000000000..2e18df43f --- /dev/null +++ b/src/education/migrations/0001_initial.py @@ -0,0 +1,54 @@ +# Generated by Django 4.0.8 on 2022-12-20 13:23 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Course', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ], + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='YPlaylist', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('youtube_id', models.CharField(blank=True, max_length=255)), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.course')), + ], + ), + migrations.CreateModel( + name='Track', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.course')), + ], + ), + migrations.CreateModel( + name='Item', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('youtube_id', models.CharField(blank=True, max_length=255)), + ('order', models.IntegerField(blank=True, default=0)), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.course')), + ('tags', models.ManyToManyField(blank=True, to='education.tag')), + ], + ), + ] diff --git a/src/education/migrations/0002_youtubetoken.py b/src/education/migrations/0002_youtubetoken.py new file mode 100644 index 000000000..efb198e51 --- /dev/null +++ b/src/education/migrations/0002_youtubetoken.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0.8 on 2022-12-20 13:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('education', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='YouTubeToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('token', models.TextField()), + ], + ), + ] diff --git a/src/education/migrations/0003_item_title.py b/src/education/migrations/0003_item_title.py new file mode 100644 index 000000000..7ffa79601 --- /dev/null +++ b/src/education/migrations/0003_item_title.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.8 on 2022-12-20 14:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('education', '0002_youtubetoken'), + ] + + operations = [ + migrations.AddField( + model_name='item', + name='title', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/src/education/migrations/0004_alter_item_options_course_title.py b/src/education/migrations/0004_alter_item_options_course_title.py new file mode 100644 index 000000000..a9ac11465 --- /dev/null +++ b/src/education/migrations/0004_alter_item_options_course_title.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.8 on 2022-12-20 14:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('education', '0003_item_title'), + ] + + operations = [ + migrations.AlterModelOptions( + name='item', + options={'ordering': ('order',)}, + ), + migrations.AddField( + model_name='course', + name='title', + field=models.CharField(default='', max_length=255), + preserve_default=False, + ), + ] diff --git a/src/education/migrations/__init__.py b/src/education/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/education/models.py b/src/education/models.py new file mode 100644 index 000000000..5d11004b8 --- /dev/null +++ b/src/education/models.py @@ -0,0 +1,110 @@ +import json +from django.conf import settings +from django.db import models +from requests_oauthlib import OAuth2Session + + +YOUTUBE_SCOPE = [ + 'https://www.googleapis.com/auth/youtube', +] +YOUTUBE_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth' +YOUTUBE_TOKEN_URL = 'https://oauth2.googleapis.com/token' + + +class Course(models.Model): + slug = models.SlugField() + title = models.CharField(max_length=255) + + def __str__(self): + return self.slug + + +class Track(models.Model): + course = models.ForeignKey(Course, models.CASCADE) + + +class Tag(models.Model): + name = models.CharField(max_length=255) + + +class Item(models.Model): + course = models.ForeignKey(Course, models.CASCADE) + title = models.CharField(max_length=255, blank=True) + tags = models.ManyToManyField(Tag, blank=True) + youtube_id = models.CharField(max_length=255, blank=True) + order = models.IntegerField(default=0, blank=True) + + class Meta: + ordering = ('order',) + + def __str__(self): + return self.title + + +class YPlaylist(models.Model): + course = models.ForeignKey(Course, models.CASCADE) + youtube_id = models.CharField(max_length=255, blank=True) + + def save(self): + super().save() + self.download() + + def download(self): + response = YouTubeToken.objects.first().call( + "GET", + "https://www.googleapis.com/youtube/v3/playlistItems", + params={ + 'part': 'snippet', + 'playlistId': self.youtube_id, + 'maxResults': 50, + }, + ) + data = response.json() + for item in data['items']: + self.course.item_set.update_or_create( + youtube_id=item['snippet']['resourceId']['videoId'], + defaults={ + 'title': item['snippet']['title'], + 'order': item['snippet']['position'], + } + ) + + + +class YouTubeToken(models.Model): + token = models.TextField() + + def token_updater(self, token): + self.token = json.dumps(token) + self.save() + + def get_session(self): + return OAuth2Session( + client_id=settings.YOUTUBE_CLIENT_ID, + auto_refresh_url=YOUTUBE_TOKEN_URL, + token=json.loads(self.token), + auto_refresh_kwargs={'client_id':settings.YOUTUBE_CLIENT_ID,'client_secret':settings.YOUTUBE_CLIENT_SECRET}, + token_updater=self.token_updater + ) + + def call(self, method, url, params=None, json=None, data=None, resumable_file_path=None): + params = params or {} + if resumable_file_path: + params['uploadType'] = 'resumable' + file_size = os.stat(resumable_file_path).st_size + + session = self.get_session() + response = session.request( + method=method, + url=url, + json=json, + data=data, + params=params, + headers={ + 'X-Upload-Content-Length': str(file_size), + 'x-upload-content-type': 'application/octet-stream', + } if resumable_file_path else {} + ) + response.raise_for_status() + return response + diff --git a/src/education/templates/education/course_detail.html b/src/education/templates/education/course_detail.html new file mode 100644 index 000000000..f5a9398dd --- /dev/null +++ b/src/education/templates/education/course_detail.html @@ -0,0 +1,37 @@ +{% extends '2022/base.html' %} + + +{% block main %} +
+
+

+ {{ object.title }} +

+
+
+ +
+
+
+ + +
+
+
+ +
+
+ {% for item in object.item_set.all %} + + {% endfor %} +
+
+{% endblock %} diff --git a/src/education/tests.py b/src/education/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/src/education/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/education/urls.py b/src/education/urls.py new file mode 100644 index 000000000..6c09cad10 --- /dev/null +++ b/src/education/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from django.views.generic import DetailView +from . import models + + +urlpatterns = [ + path('/', DetailView.as_view(model=models.Course)), +] diff --git a/src/education/views.py b/src/education/views.py new file mode 100644 index 000000000..91ea44a21 --- /dev/null +++ b/src/education/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/wolnelektury/urls.py b/src/wolnelektury/urls.py index f71b02ba8..804e69dce 100644 --- a/src/wolnelektury/urls.py +++ b/src/wolnelektury/urls.py @@ -53,6 +53,7 @@ urlpatterns += [ path('paypal/', include('paypal.urls')), path('powiadomienie/', include('push.urls')), path('pomagam/', include('club.urls')), + path('szkola-', include('education.urls')), path('towarzystwo/notify//', club.views.PayUNotifyView.as_view(), name='club_payu_notify'), path('towarzystwo/', RedirectView.as_view(url='/pomagam/', permanent=False, query_string=True)), path('towarzystwo/', RedirectView.as_view( -- 2.20.1