Education.
authorRadek Czajka <rczajka@rczajka.pl>
Tue, 20 Dec 2022 14:33:32 +0000 (15:33 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Tue, 20 Dec 2022 14:33:32 +0000 (15:33 +0100)
17 files changed:
src/catalogue/admin.py
src/catalogue/templates/catalogue/2022/collections.html
src/club/templates/club/2022/donation_step_base.html
src/education/__init__.py [new file with mode: 0644]
src/education/admin.py [new file with mode: 0644]
src/education/apps.py [new file with mode: 0644]
src/education/migrations/0001_initial.py [new file with mode: 0644]
src/education/migrations/0002_youtubetoken.py [new file with mode: 0644]
src/education/migrations/0003_item_title.py [new file with mode: 0644]
src/education/migrations/0004_alter_item_options_course_title.py [new file with mode: 0644]
src/education/migrations/__init__.py [new file with mode: 0644]
src/education/models.py [new file with mode: 0644]
src/education/templates/education/course_detail.html [new file with mode: 0644]
src/education/tests.py [new file with mode: 0644]
src/education/urls.py [new file with mode: 0644]
src/education/views.py [new file with mode: 0644]
src/wolnelektury/urls.py

index 2866fcc..420cd3f 100644 (file)
@@ -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',)
index 5fb4078..4210c1b 100644 (file)
@@ -16,7 +16,7 @@
 
   {% for collection in objects %}
     <section class="l-section">
-      <div class="l-collections js-collections">
+      <div class="l-collections -js-collections">
         <div class="l-collections__header">
           <h3><a href="{{ collection.get_absolute_url }}">{{ collection.title }}</a></h3>
         </div>
index 4659079..1b292a8 100644 (file)
             <button class="l-article__read-more" aria-label="Kliknij aby rozwinąć" data-label="Więcej" data-action="Mniej">Więcej</button>
           </div>
         </div>
+        <div class="l-checkout__footer__content__item">
+          <h3>Regulamin darowizn</h3>
+          <div>
+            <div class="l-article__overlay" data-max-height="91">
+              {% chunk 'donation-regulamin' %}
+            </div>
+            <button class="l-article__read-more" aria-label="Kliknij aby rozwinąć" data-label="Więcej" data-action="Mniej">Więcej</button>
+          </div>
+        </div>
       </div>
     </div>
   </main>
diff --git a/src/education/__init__.py b/src/education/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/education/admin.py b/src/education/admin.py
new file mode 100644 (file)
index 0000000..3996493
--- /dev/null
@@ -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 (file)
index 0000000..4a92e70
--- /dev/null
@@ -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 (file)
index 0000000..2e18df4
--- /dev/null
@@ -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 (file)
index 0000000..efb198e
--- /dev/null
@@ -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 (file)
index 0000000..7ffa796
--- /dev/null
@@ -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 (file)
index 0000000..a9ac114
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/src/education/models.py b/src/education/models.py
new file mode 100644 (file)
index 0000000..5d11004
--- /dev/null
@@ -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 (file)
index 0000000..f5a9398
--- /dev/null
@@ -0,0 +1,37 @@
+{% extends '2022/base.html' %}
+
+
+{% block main %}
+  <div class="l-section">
+    <div class="l-author__header">
+      <h1>
+        {{ object.title }}
+      </h1>
+    </div>
+  </div>
+
+  <div class="l-section">
+    <div class="l-books__header">
+      <div class="l-books__input">
+        <i class="icon icon-filter"></i>
+        <input type="text" placeholder="filtry, tytuł" class="quick-filter" data-for="book-list">
+      </div>
+    </div>
+  </div>
+
+  <div class="l-section l-section--col">
+    <div class="l-books__grid" id="book-list">
+      {% for item in object.item_set.all %}
+        <article class="l-books__item">
+          <figure class="l-books__item__img book-container book-container-{{ book.pk }}" style="height:120px" data-book="{{ book.pk }}">
+            <a href="https://www.youtube.com/watch?v={{ item.youtube_id }}" target="_blank">
+              <img src="https://i.ytimg.com/vi/{{ item.youtube_id }}/mqdefault.jpg" />
+            </a>
+          </figure>
+          <h2><a href="https://www.youtube.com/watch?v={{ item.youtube_id }}" target="_blank">
+            {{ item.title }}</a></h2>
+        </article>
+      {% endfor %}
+    </div>
+  </div>
+{% endblock %}
diff --git a/src/education/tests.py b/src/education/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -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 (file)
index 0000000..6c09cad
--- /dev/null
@@ -0,0 +1,8 @@
+from django.urls import path
+from django.views.generic import DetailView
+from . import models
+
+
+urlpatterns = [
+    path('<slug:slug>/', DetailView.as_view(model=models.Course)),
+]
diff --git a/src/education/views.py b/src/education/views.py
new file mode 100644 (file)
index 0000000..91ea44a
--- /dev/null
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
index f71b02b..804e69d 100644 (file)
@@ -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/<int:pk>/',  club.views.PayUNotifyView.as_view(), name='club_payu_notify'),
     path('towarzystwo/', RedirectView.as_view(url='/pomagam/', permanent=False, query_string=True)),
     path('towarzystwo/<path:path>', RedirectView.as_view(