'print_on_demand',
('wiki_link', BlankFieldListFilter),
('parent', EmptyFieldListFilter),
+ ('media', admin.EmptyFieldListFilter),
]
date_hierarchy = 'created_at'
search_fields = ('title',)
{% 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>
<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>
--- /dev/null
+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.
--- /dev/null
+from django.apps import AppConfig
+
+
+class EducationConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'education'
--- /dev/null
+# 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')),
+ ],
+ ),
+ ]
--- /dev/null
+# 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()),
+ ],
+ ),
+ ]
--- /dev/null
+# 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),
+ ),
+ ]
--- /dev/null
+# 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,
+ ),
+ ]
--- /dev/null
+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
+
--- /dev/null
+{% 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 %}
--- /dev/null
+from django.test import TestCase
+
+# Create your tests here.
--- /dev/null
+from django.urls import path
+from django.views.generic import DetailView
+from . import models
+
+
+urlpatterns = [
+ path('<slug:slug>/', DetailView.as_view(model=models.Course)),
+]
--- /dev/null
+from django.shortcuts import render
+
+# Create your views here.
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(